Finishes the pass: every script now documents its safeguards, and the deliberate absences in the sourced libraries are recorded so they are not "corrected" later.
231 lines
10 KiB
Bash
Executable File
231 lines
10 KiB
Bash
Executable File
#!/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.
|
|
#
|
|
# ==============================================================================================
|
|
# DESIGN PRINCIPLES
|
|
# ==============================================================================================
|
|
#
|
|
# Self-Healing at Boot
|
|
# The unraid-api registry is ephemeral — OS updates and service restarts clear
|
|
# it without warning. Running at every array start means the key is always
|
|
# present after boot without any manual intervention.
|
|
#
|
|
# Conf Stays Current
|
|
# HOST*_UNRAID_API_KEY in the local host conf is updated after every renewal.
|
|
# The partnership page reads the conf — it always reflects the live key value
|
|
# without a separate sync step.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL MODEL
|
|
# ==============================================================================================
|
|
#
|
|
# 1. Check whether a Varaverk key already exists in the unraid-api registry
|
|
# 2. Create or overwrite it — the registry is ephemeral, so re-registering is the norm
|
|
# 3. Write the resulting key into this host's conf, replacing any previous value
|
|
# 4. Report whether the key was created, refreshed, or unchanged
|
|
#
|
|
# Runs at array start. The registry does not survive OS updates or an unraid-api restart,
|
|
# which is why this re-registers unconditionally rather than only when the key is missing —
|
|
# a key present in the conf but absent from the registry is the exact failure it repairs.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL SAFEGUARDS
|
|
# ==============================================================================================
|
|
#
|
|
# acquire_lock — prevents concurrent renewal attempts at boot
|
|
# detect_hosts() — sets MY_ID to derive the correct conf var name
|
|
# Conf file check — aborts before any writes if the host conf is missing
|
|
# dry-run mode — shows what would happen without touching anything
|
|
#
|
|
# ==============================================================================================
|
|
# CONFIGURATION
|
|
# ==============================================================================================
|
|
#
|
|
# host*.conf
|
|
#
|
|
# HOST*_UNRAID_API_KEY
|
|
# Written by this script every array start. Read by the plugin's PHP for enhanced
|
|
# monitoring. Treated as output, not input — an existing value is always replaced,
|
|
# because a conf value that no longer matches the registry is precisely the broken
|
|
# state this exists to fix.
|
|
#
|
|
# Platform-owned:
|
|
#
|
|
# The unraid-api service registry — ephemeral, cleared by OS updates and service restarts.
|
|
#
|
|
# ==============================================================================================
|
|
# 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 "$@"
|
|
|
|
# Rewrites the API key into host*.conf and registers it with the unraid-api service.
|
|
[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }
|
|
|
|
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.
|
|
#
|
|
# Timeout was 5s — measured live 2026-07-19 at 2.2-2.7s baseline latency for this
|
|
# exact command with the system idle, leaving almost no margin. Any load spike
|
|
# (confirmed correlated with resource_watchdog "pressure escalating" events in the
|
|
# same log) pushed it past 5s, killing the lookup — the script then couldn't tell
|
|
# "timed out" from "genuinely not in the registry" and fell through to the create
|
|
# path, logging a false "API key renewed (registry had lost it)" even though the
|
|
# on-disk key file's timestamp never actually changed. 15s gives real headroom.
|
|
log "Checking unraid-api registry for $KEY_NAME..."
|
|
EXISTING=$(timeout 15 /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}"
|
|
# Always sync registry key → conf, even if the key was already there.
|
|
# Conf gets wiped on git pull / conf regeneration without touching the registry.
|
|
CONF_HAS_KEY=$(grep -oP "(?<=^\s*${VAR_NAME}=\")[^\"]*" "$CONF_FILE" 2>/dev/null || true)
|
|
if [[ "$CONF_HAS_KEY" == "$KEY" ]]; then
|
|
echo "API key valid ✅ — $VAR_NAME = $PREVIEW"
|
|
log "Key in registry and conf — no action needed"
|
|
exit 0
|
|
fi
|
|
log "Key in registry but conf is stale — syncing..."
|
|
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
|
|
echo "API key synced to conf ✅ — $VAR_NAME = $PREVIEW"
|
|
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
|
|
echo "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
|