The value was written as typed, bash expanded it when the read-back sourced the file, and the guard rolled the whole write back with nothing on screen but "save failed" — which is also what an empty value, a trailing space, and a stale API-key check had been doing.
238 lines
11 KiB
Bash
Executable File
238 lines
11 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.
|
|
# Sourced, not pattern-matched. This was a grep -oP with a variable-length lookbehind, which
|
|
# Unraid's grep is ugrep and rejects outright — "length of lookbehind assertion is not
|
|
# limited", rc 2, swallowed by the || true. CONF_HAS_KEY was therefore always empty, the
|
|
# comparison below never matched, and this script logged "conf is stale — syncing" and
|
|
# rewrote the same key into the conf every fifteen minutes since it was written.
|
|
# Sourcing also means the value is read the way bash reads it, escapes and all.
|
|
CONF_HAS_KEY=$(bash -c 'source "$1" >/dev/null 2>&1 || exit 0; printf "%s" "${!2-}"' \
|
|
_ "$CONF_FILE" "$VAR_NAME" 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
|