Auth stack certs tab, arrs db fallbacks, cert monitor cache, conf parser fix
- Auth stack: fold cert monitor into Auth Stack page as fourth tab (Certs); remove standalone cert page and top-level tab - cert_monitor.sh: write JSON status cache to State_Files/cert_status.json after each run; expose per-domain days/expiry via _CERT_DAYS/_CERT_EXPIRY globals - api/cert.php: new — serves cached cert status; falls back to configured domains as UNKN when no cache exists; POST action=run triggers live check - arrs db fallbacks: vv_arr_cleanup_stats/discovery_stats/recovery_stats now read from data/*.db files when log JSON files don't yet exist - config.php vv_conf_vars(): unescape bash \$ → $ so passwords with dollar signs read correctly from conf files - host1.conf: fill in HOST1_NPM_USER/PASS and HOST1_LLDAP_USER/PASS - Partnership adapter pattern: Unraid-specific container logic extracted to Plugin/unraid/Partnership/; platform-agnostic structure stays in Partnership/ - First-run wizard: uniform multi-step flow for all hosts; HOST2 pull moved to checklist; auto SSH keygen and API key creation on save - api/checklist.php: live setup checklist with pull_master action - Fullscreen toggle: hide Unraid header/menu; state persists via localStorage
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# ================================= Mover Stop =================================================
|
||||
# ==============================================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Safely stops the unRAID mover with a wall warning, configurable timeout, and
|
||||
# SIGTERM → SIGKILL sequence. Use before planned reboots, disk operations, or
|
||||
# any operation where mover and rsync running simultaneously could corrupt files.
|
||||
# Exits cleanly if mover is not running.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Stop Sequence
|
||||
# 1. Check if mover is running — exit cleanly if not
|
||||
# 2. Wall message to all logged-in terminal users
|
||||
# 3. Wait MOVER_STOP_TIMEOUT seconds
|
||||
# 4. SIGTERM — allows mover to finish its current file before stopping
|
||||
# (no partial files — the mover completes what it is working on)
|
||||
# 5. Wait 5 seconds → verify stopped
|
||||
# 6. SIGKILL if still running — forced stop, partial files possible
|
||||
# 7. Final verify — error if still running after SIGKILL
|
||||
#
|
||||
# SIGTERM first because the mover has an opportunity to finish the file it is
|
||||
# currently moving, leaving no partial copies on cache or array. SIGKILL is only
|
||||
# used as a last resort and may leave a file split across cache and array.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Single Instance Lock
|
||||
# acquire_lock prevents concurrent stop attempts racing each other.
|
||||
#
|
||||
# Root Required
|
||||
# pkill on emhttp processes requires root.
|
||||
#
|
||||
# Final Verify
|
||||
# Confirms mover is actually stopped after the kill sequence — errors if it
|
||||
# is still running after SIGKILL.
|
||||
#
|
||||
# Silent When Clean
|
||||
# Mover not running = log() only, no visible output.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master.conf
|
||||
#
|
||||
# MOVER_STOP_TIMEOUT
|
||||
# Seconds between wall warning and SIGTERM. (default: 30)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# mover_stop.sh
|
||||
# Check if mover is running. If so, warn users and stop it.
|
||||
#
|
||||
# mover_stop.sh --dry-run
|
||||
# Show mover state and what would happen. No signals sent.
|
||||
#
|
||||
# mover_stop.sh --status
|
||||
# Show mover state (running, PID, start time). Then exit.
|
||||
#
|
||||
# mover_stop.sh --log
|
||||
# Verbose output showing each step of the stop sequence.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root — pkill on emhttp processes requires root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
platform_require_cmd \
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
||||
"" "" \
|
||||
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
||||
|
||||
validate_int MOVER_STOP_TIMEOUT "$MOVER_STOP_TIMEOUT"
|
||||
|
||||
acquire_lock
|
||||
|
||||
detect_hosts
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_MOVER Timeout: ${MOVER_STOP_TIMEOUT}s"
|
||||
echo "$ICON_GEAR Dry Run: $DRY_RUN"
|
||||
echo ""
|
||||
if platform_is_mover_running; then
|
||||
MOVER_PID=$(platform_get_mover_pid)
|
||||
MOVER_START=$(ps -o lstart= -p "$MOVER_PID" 2>/dev/null | xargs)
|
||||
echo " $ICON_MOVER Mover: RUNNING (PID $MOVER_PID)"
|
||||
[[ -n "$MOVER_START" ]] && echo " $ICON_TIME Started: $MOVER_START"
|
||||
else
|
||||
echo " $ICON_MOVER Mover: not running"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Mover Stop ━━━
|
||||
# ==============================================================================================
|
||||
START=$(date +%s)
|
||||
|
||||
if ! platform_is_mover_running; then
|
||||
echo "Mover is not running — nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MOVER_PID=$(platform_get_mover_pid)
|
||||
MOVER_START=$(ps -o lstart= -p "$MOVER_PID" 2>/dev/null | xargs)
|
||||
MOVER_ELAPSED=$(ps -o etimes= -p "$MOVER_PID" 2>/dev/null | tr -d ' ')
|
||||
warn "Mover is running (PID $MOVER_PID) — stopping in ${MOVER_STOP_TIMEOUT}s"
|
||||
log "$ICON_TIME Mover started: ${MOVER_START:-unknown} — running for $(format_duration "${MOVER_ELAPSED:-0}")"
|
||||
|
||||
# ── Warn users via wall ───────────────────────────────────────────────────────────────────────
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
wall "$ICON_WARN $MY_ID ($LOCAL_SERVER_NAME) — unRAID Mover stopping in ${MOVER_STOP_TIMEOUT}s"
|
||||
echo "Wall message sent — waiting ${MOVER_STOP_TIMEOUT}s..."
|
||||
sleep "$MOVER_STOP_TIMEOUT"
|
||||
else
|
||||
warn "DRY RUN — would send wall warning and wait ${MOVER_STOP_TIMEOUT}s"
|
||||
fi
|
||||
|
||||
# ── SIGTERM — graceful stop ───────────────────────────────────────────────────────────────────
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would send SIGTERM to mover (PID $MOVER_PID)"
|
||||
else
|
||||
log "Sending SIGTERM to mover (PID $MOVER_PID)..."
|
||||
kill -TERM "$MOVER_PID" 2>/dev/null || true
|
||||
sleep 5
|
||||
|
||||
# Verify stopped after SIGTERM
|
||||
if ! platform_is_mover_running; then
|
||||
warn "Mover stopped cleanly (SIGTERM) ✅"
|
||||
else
|
||||
# ── SIGKILL — forced stop ─────────────────────────────────────────────────────────────
|
||||
warn "Mover still running after SIGTERM — sending SIGKILL (may leave partial files)"
|
||||
kill -KILL "$MOVER_PID" 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Final verify
|
||||
if platform_is_mover_running; then
|
||||
error "Mover still running after SIGKILL — manual intervention needed"
|
||||
notify "Mover stop failed on $(hostname) ($MY_ID) — process unkillable" \
|
||||
"Mover Stop" "warning"
|
||||
exit 1
|
||||
else
|
||||
warn "Mover force-stopped (SIGKILL) — check for partial files on cache"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
END=$(date +%s)
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Summary ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY MOVER STOP SUMMARY ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_MOVER Timeout: ${MOVER_STOP_TIMEOUT}s"
|
||||
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
||||
echo ""
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — no changes made"
|
||||
else
|
||||
echo "$ICON_DONE Status: done — mover stopped ✅"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
@@ -1,247 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# ============================= PHP-FPM Max Children ===========================================
|
||||
# ==============================================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Raises PHP-FPM pm.max_children to prevent WebGUI slowdowns under load. Run
|
||||
# once at array start via ARRAY_START_SCRIPTS. Idempotent — silent when the
|
||||
# value is already correct, no restart on clean boot.
|
||||
#
|
||||
# unRAID's WebGUI runs through PHP-FPM. The default pm.max_children is very
|
||||
# low (4–8). Under load — multiple users, Docker operations, heavy dashboard
|
||||
# usage — all PHP workers saturate and new requests queue. The WebGUI becomes
|
||||
# slow or unresponsive.
|
||||
#
|
||||
# PHP_MAX_CHILDREN=250 is appropriate for 128GB RAM: ~2MB per worker = ~500MB
|
||||
# total. Too high wastes RAM; too low causes slowdowns.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# DESIGN PRINCIPLES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Idempotent Content Check
|
||||
# Reads the current pm.max_children value before writing. If already at
|
||||
# target → silent exit, no PHP-FPM restart. Restarting PHP-FPM unnecessarily
|
||||
# disrupts active WebGUI sessions on every boot.
|
||||
#
|
||||
# Pattern Match Before Write
|
||||
# Verifies the sed pattern finds pm.max_children in the config before
|
||||
# applying any change. Prevents silent failures where sed succeeds but
|
||||
# writes nothing because the key was missing or commented out.
|
||||
#
|
||||
# Apply Sequence
|
||||
# 1. Read current pm.max_children from PHP_CONF
|
||||
# 2. If already at target → exit silently
|
||||
# 3. Verify sed pattern matches before writing
|
||||
# 4. Apply sed replacement
|
||||
# 5. Restart PHP-FPM via rc.php-fpm
|
||||
# 6. Verify PHP-FPM process running after restart
|
||||
# 7. Read back config to confirm value applied
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Root Required
|
||||
# Writing to /etc/php83/ requires root.
|
||||
#
|
||||
# Single Instance Lock
|
||||
# acquire_lock prevents concurrent runs at array start.
|
||||
#
|
||||
# Process Verify
|
||||
# Confirms PHP-FPM running after restart — errors if it failed to start.
|
||||
#
|
||||
# Config Verify
|
||||
# Reads back config after restart to confirm the value was actually applied.
|
||||
#
|
||||
# Silent on Success
|
||||
# Runs every boot — no noise when already correct.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master.conf
|
||||
#
|
||||
# PHP_MAX_CHILDREN
|
||||
# Target pm.max_children value. (default: 250)
|
||||
#
|
||||
# PHP_CONF
|
||||
# Path to PHP-FPM www.conf. (default: /etc/php83/php-fpm.d/www.conf)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# php_fpm_max_children.sh
|
||||
# Read current value. Update and restart PHP-FPM only if changed. Silent if correct.
|
||||
#
|
||||
# php_fpm_max_children.sh --dry-run
|
||||
# Show current vs target value. No config write or restart.
|
||||
#
|
||||
# php_fpm_max_children.sh --status
|
||||
# Show current pm.max_children, target, and PHP-FPM process state.
|
||||
#
|
||||
# php_fpm_max_children.sh --log
|
||||
# Verbose output showing idempotent check, config write, and restart result.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root — writing system config requires root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
platform_require_cmd \
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
||||
"" "" \
|
||||
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
||||
|
||||
validate_int PHP_MAX_CHILDREN "$PHP_MAX_CHILDREN"
|
||||
require_var PHP_CONF
|
||||
|
||||
acquire_lock
|
||||
|
||||
detect_hosts
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY PHP-FPM STATUS ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_GEAR Config file: $PHP_CONF"
|
||||
echo "$ICON_PHP Target: pm.max_children = $PHP_MAX_CHILDREN"
|
||||
echo ""
|
||||
|
||||
if [[ -f "$PHP_CONF" ]]; then
|
||||
CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | \
|
||||
awk '{print $NF}')
|
||||
if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then
|
||||
echo " $ICON_SUCCESS Current: pm.max_children = $CURRENT_VAL (correct ✅)"
|
||||
else
|
||||
echo " $ICON_WARN Current: pm.max_children = ${CURRENT_VAL:-not set} (would update)"
|
||||
fi
|
||||
else
|
||||
echo " $ICON_ERROR Config file not found: $PHP_CONF"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if pgrep -f "php-fpm" >/dev/null 2>&1; then
|
||||
FPM_COUNT=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?")
|
||||
echo " $ICON_SUCCESS PHP-FPM: running ($FPM_COUNT worker(s))"
|
||||
else
|
||||
echo " $ICON_ERROR PHP-FPM: NOT running"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ PHP-FPM Config ━━━
|
||||
# ==============================================================================================
|
||||
START=$(date +%s)
|
||||
|
||||
if [[ ! -f "$PHP_CONF" ]]; then
|
||||
error "PHP config file not found: $PHP_CONF"
|
||||
notify "PHP-FPM config not found on $(hostname) ($MY_ID) — $PHP_CONF missing" \
|
||||
"PHP-FPM" "warning"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Idempotent check ─────────────────────────────────────────────────────────────────────────
|
||||
CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}')
|
||||
if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then
|
||||
echo "pm.max_children already $PHP_MAX_CHILDREN ✅"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
warn "pm.max_children: ${CURRENT_VAL:-not set} → $PHP_MAX_CHILDREN"
|
||||
|
||||
# ── Verify pattern exists before writing ─────────────────────────────────────────────────────
|
||||
if ! grep -qE "^pm\.max_children" "$PHP_CONF" 2>/dev/null; then
|
||||
error "pm.max_children not found in $PHP_CONF — cannot apply"
|
||||
error "Add 'pm.max_children = $PHP_MAX_CHILDREN' to $PHP_CONF manually"
|
||||
notify "PHP-FPM pm.max_children not found in config on $(hostname) ($MY_ID)" \
|
||||
"PHP-FPM" "warning"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would set pm.max_children = $PHP_MAX_CHILDREN in $PHP_CONF"
|
||||
warn "DRY RUN — would restart PHP-FPM"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Apply setting ─────────────────────────────────────────────────────────────────────────────
|
||||
log "Applying pm.max_children = $PHP_MAX_CHILDREN..."
|
||||
if ! sed -i "s/^pm\.max_children.*/pm.max_children = $PHP_MAX_CHILDREN/" "$PHP_CONF"; then
|
||||
error "Failed to update $PHP_CONF"
|
||||
notify "PHP-FPM config update failed on $(hostname) ($MY_ID)" \
|
||||
"PHP-FPM" "warning"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Config updated"
|
||||
|
||||
# ── Restart PHP-FPM ──────────────────────────────────────────────────────────────────────────
|
||||
log "Restarting PHP-FPM..."
|
||||
if ! platform_restart_service php-fpm; then
|
||||
error "PHP-FPM restart command failed"
|
||||
notify "PHP-FPM restart failed on $(hostname) ($MY_ID)" \
|
||||
"PHP-FPM" "warning"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 3 # Allow PHP-FPM workers to initialise
|
||||
|
||||
# ── Verify process running ────────────────────────────────────────────────────────────────────
|
||||
if ! pgrep -f "php-fpm" >/dev/null 2>&1; then
|
||||
error "PHP-FPM not running after restart — WebGUI may be broken"
|
||||
notify "PHP-FPM failed to start after config update on $(hostname) ($MY_ID)" \
|
||||
"PHP-FPM" "warning"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Verify config reflects target ────────────────────────────────────────────────────────────
|
||||
APPLIED_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}')
|
||||
if [[ "${APPLIED_VAL:-0}" -ne "$PHP_MAX_CHILDREN" ]]; then
|
||||
warn "Config reads pm.max_children = ${APPLIED_VAL:-unknown} — expected $PHP_MAX_CHILDREN"
|
||||
warn "Check $PHP_CONF manually"
|
||||
else
|
||||
log "Verified: pm.max_children = $APPLIED_VAL ✅"
|
||||
fi
|
||||
|
||||
FPM_WORKERS=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?")
|
||||
log "$ICON_PHP Workers running: $FPM_WORKERS"
|
||||
|
||||
END=$(date +%s)
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Summary ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY PHP-FPM SUMMARY ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_GEAR Config file: $PHP_CONF"
|
||||
echo "$ICON_PHP Applied: pm.max_children = $PHP_MAX_CHILDREN"
|
||||
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
||||
echo ""
|
||||
echo "$ICON_DONE Status: done ✅"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
exit 0
|
||||
@@ -1,153 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# ================================= Unraid API Key Renewal ====================================
|
||||
# ==============================================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Creates/overwrites the Varaverk API key in the unraid-api service registry at
|
||||
# array start. The registry is ephemeral — OS updates and service restarts clear
|
||||
# it. This script re-registers the key every boot so Varaverk's enhanced
|
||||
# monitoring self-heals without manual intervention.
|
||||
#
|
||||
# Also updates HOST*_UNRAID_API_KEY in the local host conf so the partnership
|
||||
# page always reflects the live key value.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# unraid_api_key_renew.sh
|
||||
# Renew the key. Silent on success.
|
||||
#
|
||||
# unraid_api_key_renew.sh --dry-run
|
||||
# Show what would happen — no changes made.
|
||||
#
|
||||
# unraid_api_key_renew.sh --log
|
||||
# Verbose output.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
acquire_lock
|
||||
detect_hosts
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
CONF_FILE="$SCRIPT_DIR/../Configurations/${MY_ID,,}.conf"
|
||||
VAR_NAME="${MY_ID}_UNRAID_API_KEY"
|
||||
|
||||
# Key name: "Varaverk <hostname>" stripping any unraid- prefix
|
||||
# Space separator — unRAID API only allows letters, numbers, and spaces
|
||||
HOSTNAME_SUFFIX=$(hostname -s 2>/dev/null | sed 's/^[Uu][Nn][Rr][Aa][Ii][Dd]-//' || hostname -s)
|
||||
KEY_NAME="Varaverk ${HOSTNAME_SUFFIX}"
|
||||
|
||||
log "$ICON_GEAR Conf file: $CONF_FILE"
|
||||
log "$ICON_GEAR Key var: $VAR_NAME"
|
||||
log "$ICON_GEAR Key name: $KEY_NAME"
|
||||
|
||||
if [[ ! -f "$CONF_FILE" ]]; then
|
||||
error "Conf file not found: $CONF_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would check registry for $KEY_NAME, renew only if missing"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Check if key already exists in the unraid-api registry before creating.
|
||||
# --overwrite generates a new key value every time, invalidating the old one.
|
||||
# Only renew if the registry has lost it.
|
||||
log "Checking unraid-api registry for $KEY_NAME..."
|
||||
EXISTING=$(timeout 5 /usr/local/sbin/unraid-api apikey --name "$KEY_NAME" --json </dev/null 2>/dev/null)
|
||||
KEY=$(echo "$EXISTING" | jq -r '.key // empty' 2>/dev/null)
|
||||
|
||||
if [[ -n "$KEY" ]]; then
|
||||
PREVIEW="${KEY:0:8}...${KEY: -4}"
|
||||
echo "API key valid ✅ — $VAR_NAME = $PREVIEW"
|
||||
log "Key found in registry — no renewal needed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Key not found in registry — creating new key..."
|
||||
|
||||
RAW=$(timeout 10 /usr/local/sbin/unraid-api apikey \
|
||||
--name "$KEY_NAME" --create --overwrite \
|
||||
--description "Varaverk plugin" --roles ADMIN --json </dev/null 2>&1)
|
||||
|
||||
if [[ -z "$RAW" ]]; then
|
||||
error "unraid-api returned no output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KEY=$(echo "$RAW" | jq -r '.key // empty' 2>/dev/null)
|
||||
if [[ -z "$KEY" ]]; then
|
||||
error "No key in unraid-api response: ${RAW:0:200}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
if grep -q "^\s*${VAR_NAME}\s*=" "$CONF_FILE"; then
|
||||
sed -i "s|^\(\s*${VAR_NAME}\s*=\s*\)\"[^\"]*\"|\1\"${KEY}\"|" "$CONF_FILE"
|
||||
else
|
||||
echo " ${VAR_NAME}=\"${KEY}\"" >> "$CONF_FILE"
|
||||
fi
|
||||
|
||||
PREVIEW="${KEY:0:8}...${KEY: -4}"
|
||||
log "Writing new key to: $CONF_FILE"
|
||||
warn "API key renewed ✅ — $VAR_NAME = $PREVIEW (registry had lost it)"
|
||||
|
||||
# ── Push renewed key into each partner's OWN conf ─────────────────────────────
|
||||
# Each host's conf is its complete keychest — no cross-host conf files needed.
|
||||
# SSH_KEY is set by detect_hosts() — this server's outbound private key.
|
||||
if [[ -z "$SSH_KEY" ]]; then
|
||||
log "No SSH key configured — skipping partner push"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for host_var in $(compgen -v | grep -E '^HOST[0-9]+$'); do
|
||||
partner_host="${!host_var}"
|
||||
[[ -z "$partner_host" ]] && continue
|
||||
[[ "${host_var,,}" == "${MY_ID,,}" ]] && continue
|
||||
|
||||
partner_slot="${host_var,,}" # e.g. host2
|
||||
partner_ip=$(resolve_tailscale_ip "$partner_host" 2>/dev/null || true)
|
||||
[[ -z "$partner_ip" ]] && { log "Cannot resolve IP for $partner_host — skipping"; continue; }
|
||||
|
||||
# Target is the partner's OWN conf on their machine
|
||||
partner_conf="/boot/config/plugins/varaverk/Configurations/${partner_slot}.conf"
|
||||
tmp=$(mktemp /tmp/vv_kp_XXXXXX.sh)
|
||||
remote="/tmp/vv_kp_${RANDOM}.sh"
|
||||
chmod 700 "$tmp"
|
||||
|
||||
# Key stays in the temp file — never appears in SSH command args
|
||||
cat > "$tmp" <<PUSHSCRIPT
|
||||
#!/bin/sh
|
||||
target='${partner_conf}'
|
||||
if grep -q "\b${VAR_NAME}\b" "\$target" 2>/dev/null; then
|
||||
sed -i 's|^\(\\s*${VAR_NAME}\\s*=\\s*\)"[^"]*"|\1"${KEY}"|' "\$target"
|
||||
else
|
||||
printf ' ${VAR_NAME}="%s"\n' '${KEY}' >> "\$target"
|
||||
fi
|
||||
echo ok
|
||||
PUSHSCRIPT
|
||||
|
||||
if timeout 10 scp -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=no "$tmp" "root@${partner_ip}:${remote}" 2>/dev/null; then
|
||||
if timeout 10 ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=no "root@${partner_ip}" \
|
||||
"bash '${remote}'; rc=\$?; rm -f '${remote}'; exit \$rc" 2>/dev/null | grep -q ok; then
|
||||
log "Key pushed to $partner_host ✅"
|
||||
else
|
||||
warn "Key push to $partner_host failed — they can create their own copy"
|
||||
fi
|
||||
else
|
||||
warn "SCP to $partner_host failed — skipping"
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
done
|
||||
@@ -1,256 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# ============================= User Scripts Stop ==============================================
|
||||
# ==============================================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Stops all running User Script processes spawned by the unRAID User Scripts
|
||||
# plugin. Shows script names not just PIDs so you know what's being stopped.
|
||||
# Called automatically by server_reboot.sh as part of the shutdown sequence,
|
||||
# and useful directly when a script is stuck and won't respond to the UI.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Process Identification
|
||||
# Scans /proc/*/cmdline for processes whose command line contains
|
||||
# "/tmp/user.scripts". The User Scripts plugin stages all scripts in
|
||||
# /tmp/user.scripts/ before execution — more reliable than process name
|
||||
# matching which can vary.
|
||||
#
|
||||
# Stop Sequence Per Process
|
||||
# 1. Send SIGTERM — allows the script to trap and clean up gracefully
|
||||
# 2. Wait 5 seconds
|
||||
# 3. If still running → SIGKILL (force)
|
||||
# 4. Verify dead after SIGKILL — error if still running
|
||||
#
|
||||
# Self-Exclusion
|
||||
# If this script is run via the User Scripts plugin it would find its own
|
||||
# PID in the scan. Self-exclusion by PID prevents killing its own process
|
||||
# tree mid-execution.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Root Required
|
||||
# kill requires root for other users' processes.
|
||||
#
|
||||
# Single Instance Lock
|
||||
# acquire_lock prevents concurrent stop attempts.
|
||||
#
|
||||
# SIGTERM → SIGKILL Sequence
|
||||
# Graceful first. Forced only if SIGTERM ignored after 5 seconds.
|
||||
#
|
||||
# Post-Kill Verify
|
||||
# Confirms each process is actually dead. Errors and notifies if unkillable.
|
||||
#
|
||||
# Silent When Clean
|
||||
# No processes running = log() only, no visible output.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# user_scripts_stop.sh
|
||||
# Find and stop all User Script processes. Silent if none running.
|
||||
#
|
||||
# user_scripts_stop.sh --dry-run
|
||||
# Show which processes would be stopped, with names and runtimes. No kills.
|
||||
#
|
||||
# user_scripts_stop.sh --status
|
||||
# Show currently running User Script processes with names and elapsed time.
|
||||
#
|
||||
# user_scripts_stop.sh --log
|
||||
# Verbose output — show each process found, each signal sent, each result.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
MY_PID=$$
|
||||
MY_PPID=$PPID
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root — kill requires root for other users' processes"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
platform_require_cmd \
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
||||
"" "" \
|
||||
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
||||
|
||||
acquire_lock
|
||||
|
||||
detect_hosts
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no processes will be killed"
|
||||
|
||||
# ==============================================================================================
|
||||
# ── HELPER FUNCTIONS ──────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
|
||||
# Get script name from PID — extracts meaningful name from /tmp/user.scripts path
|
||||
get_script_name() {
|
||||
local pid="$1"
|
||||
local cmdline
|
||||
cmdline=$(tr '\0' ' ' < /proc/"$pid"/cmdline 2>/dev/null || echo "")
|
||||
# Extract the script filename from the /tmp/user.scripts/... path
|
||||
echo "$cmdline" | grep -o '/tmp/user\.scripts[^ ]*' | \
|
||||
awk -F/ '{print $NF}' | head -1 || echo "pid-$pid"
|
||||
}
|
||||
|
||||
# Get all user script PIDs — excludes self and own parent process tree
|
||||
get_user_script_pids() {
|
||||
local -a pids=()
|
||||
while IFS= read -r pid; do
|
||||
[[ -z "$pid" ]] && continue
|
||||
# Self-exclusion — don't kill our own process or parent
|
||||
[[ "$pid" == "$MY_PID" ]] && continue
|
||||
[[ "$pid" == "$MY_PPID" ]] && continue
|
||||
pids+=("$pid")
|
||||
done < <(
|
||||
for dir in /proc/[0-9]*/cmdline; do
|
||||
pid="${dir%/cmdline}"
|
||||
pid="${pid#/proc/}"
|
||||
if grep -ql '/tmp/user\.scripts' "$dir" 2>/dev/null; then
|
||||
echo "$pid"
|
||||
fi
|
||||
done
|
||||
)
|
||||
(( ${#pids[@]} > 0 )) && printf '%s\n' "${pids[@]}"
|
||||
}
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY USER SCRIPTS STATUS ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo ""
|
||||
|
||||
mapfile -t PIDS < <(get_user_script_pids)
|
||||
|
||||
if [[ ${#PIDS[@]} -eq 0 ]]; then
|
||||
log "No User Script processes running"
|
||||
else
|
||||
echo " ${#PIDS[@]} User Script process(es) running:"
|
||||
for pid in "${PIDS[@]}"; do
|
||||
name=$(get_script_name "$pid")
|
||||
elapsed=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ')
|
||||
runtime=$(format_duration "${elapsed:-0}")
|
||||
echo " $ICON_RUNNING PID $pid — $name (${runtime})"
|
||||
done
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ User Scripts Stop ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━ $ICON_PLUGIN User Scripts Stop — $MY_ID ━━━"
|
||||
|
||||
START=$(date +%s)
|
||||
|
||||
mapfile -t PIDS < <(get_user_script_pids)
|
||||
|
||||
KILLED=()
|
||||
FAILED=()
|
||||
SKIPPED=()
|
||||
|
||||
if [[ ${#PIDS[@]} -eq 0 ]]; then
|
||||
echo "No User Script processes running — nothing to do"
|
||||
else
|
||||
warn "${#PIDS[@]} User Script process(es) found"
|
||||
echo ""
|
||||
|
||||
for pid in "${PIDS[@]}"; do
|
||||
name=$(get_script_name "$pid")
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would stop: $name (PID $pid)"
|
||||
SKIPPED+=("$name")
|
||||
continue
|
||||
fi
|
||||
|
||||
# Verify still running before trying to kill
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
log "$name (PID $pid) — already exited"
|
||||
continue
|
||||
fi
|
||||
|
||||
# SIGTERM — graceful stop
|
||||
log "Sending SIGTERM to $name (PID $pid)..."
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
sleep 5
|
||||
|
||||
# Check if stopped after SIGTERM
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
warn "Stopped: $name (PID $pid) ✅"
|
||||
KILLED+=("$name")
|
||||
continue
|
||||
fi
|
||||
|
||||
# SIGKILL — forced stop
|
||||
warn "$name still running after SIGTERM — sending SIGKILL"
|
||||
kill -KILL "$pid" 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Final verify
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
warn "Force-stopped: $name (PID $pid) ✅"
|
||||
KILLED+=("$name")
|
||||
else
|
||||
error "Failed to kill: $name (PID $pid)"
|
||||
FAILED+=("$name")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
END=$(date +%s)
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Summary ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY USER SCRIPTS STOP SUMMARY ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
||||
echo ""
|
||||
|
||||
if [[ ${#PIDS[@]} -eq 0 ]]; then
|
||||
echo "No processes were running"
|
||||
elif [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would have stopped ${#SKIPPED[@]} process(es): ${SKIPPED[*]}"
|
||||
else
|
||||
[[ ${#KILLED[@]} -gt 0 ]] && warn "Stopped (${#KILLED[@]}): ${KILLED[*]}"
|
||||
[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed (${#FAILED[@]}): ${FAILED[*]}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — no changes made"
|
||||
elif [[ ${#FAILED[@]} -gt 0 ]]; then
|
||||
echo "$ICON_ERROR Status: SOME PROCESSES COULD NOT BE KILLED"
|
||||
notify "User Scripts stop failed on $(hostname) ($MY_ID) — unkillable: ${FAILED[*]}" \
|
||||
"User Scripts Stop" "warning"
|
||||
else
|
||||
echo "$ICON_DONE Status: done ✅"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
[[ ${#FAILED[@]} -gt 0 ]] && exit 1
|
||||
exit 0
|
||||
Reference in New Issue
Block a user