Measured live: the apikey --name lookup takes 2.2-2.7s baseline with the system idle, against a 5s timeout that left almost no margin. Load spikes (confirmed correlated with resource_watchdog pressure events in the same log window) pushed it over 5s, and the script couldn't distinguish "timed out" from "genuinely missing" — it fell through to the create path and logged a false renewal every time, even though the on-disk key file's timestamp never actually changed. Bumped to 15s for real headroom.
198 lines
8.8 KiB
Bash
Executable File
198 lines
8.8 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 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
|
|
#
|
|
# ==============================================================================================
|
|
# 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.
|
|
#
|
|
# 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
|