From a41adfe020f37ba3c5e82f4139f963f1ef6e1fba Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Wed, 3 Jun 2026 18:39:19 -0400 Subject: [PATCH] API keys: Varaverk_HOST1 registered on all machines, keys in host*.conf, cross-host GraphQL direct access --- Plugin/unraid/api/storage.php | 11 +- Plugin/unraid/pages/settings.php | 32 ++- unRAID_Essentials/unraid_api_key_renew.sh | 312 +++++++++++----------- 3 files changed, 186 insertions(+), 169 deletions(-) diff --git a/Plugin/unraid/api/storage.php b/Plugin/unraid/api/storage.php index 360706b..7f1e84d 100644 --- a/Plugin/unraid/api/storage.php +++ b/Plugin/unraid/api/storage.php @@ -95,7 +95,10 @@ if ($action === 'detect' && $_SERVER['REQUEST_METHOD'] === 'POST') { exit; } -// ── Unraid API key status — all hosts ──────────────────────────────────────── +// ── Unraid API key status ───────────────────────────────────────────────────── +// Keys live in host*.conf (private, not master.conf): +// host1.conf: HOST1_UNRAID_API_KEY (own) + HOST2_UNRAID_API_KEY (HOST1's access to HOST2) +// host2.conf: HOST2_UNRAID_API_KEY (own) + HOST1_UNRAID_API_KEY (HOST2's access to HOST1) if ($action === 'api_status') { require_once dirname(__DIR__) . '/include/unraid_api.php'; $localStatus = vv_api_get_status(); @@ -103,7 +106,6 @@ if ($action === 'api_status') { $myHost = vv_detect_host(); $myId = strtoupper($myHost); - // Collect status for every configured host $hosts = []; foreach ($vars as $k => $v) { if (!preg_match('/^HOST(\d+)$/', $k, $m) || !$v) continue; @@ -111,11 +113,14 @@ if ($action === 'api_status') { $keyVar = $id . '_UNRAID_API_KEY'; $key = $vars[$keyVar] ?? ''; $isLocal = ($id === $myId); + // For local: key is Varaverk_HOST1 registered on own machine + // For remote: key is Varaverk_HOST1 registered on HOST2's machine (stored in host1.conf) $hosts[] = [ 'host_id' => $id, 'hostname' => $v, 'is_local' => $isLocal, 'key_var' => $keyVar, + 'key_name' => 'Varaverk_' . ($isLocal ? $myId : $myId), // Varaverk_HOST1 on that registry 'key_present' => !empty($key), 'key_preview' => $key ? substr($key, 0, 8) . '...' . substr($key, -4) : null, 'api_ok' => $isLocal @@ -123,7 +128,7 @@ if ($action === 'api_status') { : !empty($key), ]; } - usort($hosts, fn($a,$b) => strcmp($a['host_id'], $b['host_id'])); + usort($hosts, fn($a, $b) => strcmp($a['host_id'], $b['host_id'])); echo json_encode([ 'ok' => true, diff --git a/Plugin/unraid/pages/settings.php b/Plugin/unraid/pages/settings.php index ea396cd..2009a9e 100644 --- a/Plugin/unraid/pages/settings.php +++ b/Plugin/unraid/pages/settings.php @@ -152,10 +152,12 @@ $_apiPreview = $_apiKey ? substr($_apiKey, 0, 8) . '...' . substr($_apiKey, -4)
- Each host registers a key named Varaverk_HOST1, - Varaverk_HOST2, etc. - Keys are stored in master.conf - so every host can query every other host's GraphQL API directly — no SSH needed for remote monitoring. + Each host registers + Varaverk_HOST1 + in every machine's local Unraid registry — own machine plus all partners. + The key values are stored in the private host*.conf + so each host can query every other host's GraphQL API directly — real-time monitoring, no SSH. + During offboard, partner keys are removed from each registry automatically.
@@ -349,16 +351,22 @@ function vvApiCheck() { badge.className = 'vv-set-badge ' + (allOk ? 'internal' : 'flash'); if (hosts) hosts.innerHTML = d.hosts.map(h => { - const local = h.is_local ? ' local' : ''; + const tag = h.is_local + ? `own` + : `${d.my_id}'s key on ${h.host_id}`; const keyBit = h.key_present ? `${h.key_preview}` - : `not set`; - const dot = h.api_ok ? '#4caf50' : '#555'; - const name = `Varaverk_${h.host_id}`; - return `
- - ${h.host_id}${local} - ${name} + : `not set — click Setup`; + const dot = h.api_ok ? '#4caf50' : (h.key_present ? '#ffb74d' : '#444'); + // Key name in that machine's registry: always Varaverk_ + const keyName = `Varaverk_${d.my_id}`; + const registry = h.is_local ? h.host_id : h.host_id; + return `
+ + ${h.host_id} + ${keyName} + on ${registry} + ${tag} ${keyBit}
`; }).join(''); diff --git a/unRAID_Essentials/unraid_api_key_renew.sh b/unRAID_Essentials/unraid_api_key_renew.sh index a8b93fb..6c31cf8 100755 --- a/unRAID_Essentials/unraid_api_key_renew.sh +++ b/unRAID_Essentials/unraid_api_key_renew.sh @@ -5,219 +5,223 @@ # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── -# Creates/syncs the Varaverk API key in the Unraid registry. The registry is -# ephemeral — OS updates and service restarts clear it. This script re-registers -# every boot so monitoring self-heals without manual intervention. +# Manages per-host named API keys in each machine's Unraid registry so every +# host can call every other host's GraphQL API directly for real-time monitoring. # -# Each host's key is named "Varaverk_HOST1", "Varaverk_HOST2", etc., and stored -# in master.conf (shared) so all hosts can call each other's GraphQL API directly -# for real-time monitoring without SSH. +# KEY MODEL +# ───────────────────────────────────────────────────────────────────────────── +# HOST1's Unraid registry holds: +# Varaverk_HOST1 — HOST1's own key (for HOST1 to call its own API) +# Varaverk_HOST2 — HOST2's access key to HOST1's API (removed on offboard) # -# --all-hosts also SSHes to each partner, creates their key there, and writes -# all keys into master.conf. Run once from Settings → API Key → Setup -# to fully wire cross-host API access. +# HOST2's Unraid registry holds: +# Varaverk_HOST2 — HOST2's own key +# Varaverk_HOST1 — HOST1's access key to HOST2's API (removed on offboard) +# +# host1.conf holds: +# HOST1_UNRAID_API_KEY — value of Varaverk_HOST1 from HOST1's registry +# HOST2_UNRAID_API_KEY — value of Varaverk_HOST1 from HOST2's registry +# (HOST1 uses this to call HOST2's GraphQL directly) +# +# host2.conf holds the mirror: +# HOST2_UNRAID_API_KEY — value of Varaverk_HOST2 from HOST2's registry +# HOST1_UNRAID_API_KEY — value of Varaverk_HOST2 from HOST1's registry +# (HOST2 uses this to call HOST1's GraphQL directly) +# +# ── vv_remote_hosts_stats() picks up HOST2_UNRAID_API_KEY from host1.conf +# automatically — direct GraphQL, no SSH needed for remote monitoring. # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # unraid_api_key_renew.sh -# Renew local key only — runs at array start, fast. +# Local only — runs at array start. Re-registers Varaverk_HOST1 if the +# ephemeral registry lost it. Fast, no SSH. # # unraid_api_key_renew.sh --all-hosts -# Renew local key AND SSH to each partner to create/sync their key. -# Writes all keys into master.conf and pushes to partners. +# Full cross-host key setup. Run once from Settings → API Keys → Setup. +# For each partner: +# • Creates Varaverk_HOST1 on PARTNER's registry → stores value in local host*.conf +# • Creates Varaverk_HOST2 on LOCAL registry → SSHes value to partner's host*.conf # -# unraid_api_key_renew.sh --dry-run -# unraid_api_key_renew.sh --log +# unraid_api_key_renew.sh --dry-run — show what would happen +# 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 -# ────────────────────────────────────────────────────────────────────────────── -# Parse --all-hosts from raw args (parse_args doesn't handle this flag) +# Parse --all-hosts ALL_HOSTS=false for _arg in "$@"; do [[ "$_arg" == "--all-hosts" ]] && ALL_HOSTS=true; done unset _arg -CONF_FILE="$SCRIPT_DIR/../Configurations/${MY_ID,,}.conf" -MASTER_CONF="$SCRIPT_DIR/../Configurations/master.conf" -VAR_NAME="${MY_ID}_UNRAID_API_KEY" -KEY_NAME="Varaverk_${MY_ID}" +LOCAL_CONF="$SCRIPT_DIR/../Configurations/${MY_ID,,}.conf" +LOCAL_KEY_NAME="Varaverk_${MY_ID}" +LOCAL_VAR="${MY_ID}_UNRAID_API_KEY" -log "$ICON_GEAR Conf file: $CONF_FILE" -log "$ICON_GEAR Key name: $KEY_NAME" -log "$ICON_GEAR Key var: $VAR_NAME" +log "$ICON_GEAR Local conf: $LOCAL_CONF" +log "$ICON_GEAR Key name: $LOCAL_KEY_NAME" log "$ICON_GEAR All hosts: $ALL_HOSTS" -if [[ ! -f "$CONF_FILE" ]]; then - error "Conf file not found: $CONF_FILE" - exit 1 +if [[ ! -f "$LOCAL_CONF" ]]; then + error "Conf file not found: $LOCAL_CONF"; exit 1 fi -if [[ "$DRY_RUN" == true ]]; then - warn "DRY RUN — would check registry, renew if missing, write to host conf + master.conf" - [[ "$ALL_HOSTS" == true ]] && warn "DRY RUN — would also SSH to all partners and sync their keys" +[[ "$DRY_RUN" == true ]] && { + warn "DRY RUN — would check/create $LOCAL_KEY_NAME in registry and sync to host conf" + [[ "$ALL_HOSTS" == true ]] && warn "DRY RUN — would also SSH all partners for cross-host key setup" exit 0 -fi +} -# ── Helper: write a key variable into a conf file ───────────────────────────── -_write_key_to_conf() { +# ── Helper: ensure a named key exists in the LOCAL registry, return its value ── +_ensure_local_key() { + local name="$1" + local existing key + existing=$(timeout 5 /usr/local/sbin/unraid-api apikey --name "$name" --json /dev/null) + key=$(echo "$existing" | jq -r '.key // empty' 2>/dev/null) + if [[ -n "$key" ]]; then + echo "$key"; return 0 + fi + local raw + raw=$(timeout 10 /usr/local/sbin/unraid-api apikey \ + --name "$name" --create --overwrite \ + --description "Varaverk plugin" --roles ADMIN --json &1) + key=$(echo "$raw" | jq -r '.key // empty' 2>/dev/null) + [[ -z "$key" ]] && { error "Failed to create $name: ${raw:0:200}"; return 1; } + echo "$key" +} + +# ── Helper: write HOST*_UNRAID_API_KEY into a conf file ──────────────────────── +_write_key() { local conf="$1" var="$2" key="$3" [[ ! -f "$conf" ]] && return 1 if grep -q "^\s*${var}\s*=" "$conf"; then sed -i "s|^\(\s*${var}\s*=\s*\)\"[^\"]*\"|\1\"${key}\"|" "$conf" else - echo " ${var}=\"${key}\"" >> "$conf" + printf ' %s="%s"\n' "$var" "$key" >> "$conf" fi } -# ── Helper: push master.conf to all partners ────────────────────────────────── -_push_master() { - php -r " - require_once '/usr/local/emhttp/plugins/varaverk/include/config.php'; - require_once '/usr/local/emhttp/plugins/varaverk/include/confform.php'; - vv_push_master_conf(); - " 2>/dev/null && log "master.conf pushed to partner(s)" || warn "master.conf push failed (partner offline?)" -} - -# ────────────────────────────────────────────────────────────────────────────── -# Step 1: Local key — check registry, create if missing, sync to conf -# ────────────────────────────────────────────────────────────────────────────── -echo "" -echo "━━━ $ICON_GEAR Local key ($KEY_NAME) ━━━" - -EXISTING=$(timeout 5 /usr/local/sbin/unraid-api apikey --name "$KEY_NAME" --json /dev/null) -KEY=$(echo "$EXISTING" | jq -r '.key // empty' 2>/dev/null) - -if [[ -n "$KEY" ]]; then - PREVIEW="${KEY:0:8}...${KEY: -4}" - CONF_KEY=$(grep "^\s*${VAR_NAME}\s*=" "$CONF_FILE" 2>/dev/null | sed 's/.*="\(.*\)".*/\1/' | tr -d '[:space:]') - MASTER_KEY=$(grep "^\s*${VAR_NAME}\s*=" "$MASTER_CONF" 2>/dev/null | sed 's/.*="\(.*\)".*/\1/' | tr -d '[:space:]') - - if [[ "$CONF_KEY" == "$KEY" && "$MASTER_KEY" == "$KEY" ]]; then - echo "API key valid ✅ — $VAR_NAME = $PREVIEW" - log "Key in sync across host conf + master.conf" - else - log "Syncing key to conf files..." - _write_key_to_conf "$CONF_FILE" "$VAR_NAME" "$KEY" - _write_key_to_conf "$MASTER_CONF" "$VAR_NAME" "$KEY" - _push_master - warn "API key synced ✅ — $VAR_NAME = $PREVIEW" - fi -else - log "Key not found in registry — creating $KEY_NAME..." - RAW=$(timeout 10 /usr/local/sbin/unraid-api apikey \ - --name "$KEY_NAME" --create --overwrite \ - --description "Varaverk plugin" --roles ADMIN --json &1) - - KEY=$(echo "$RAW" | jq -r '.key // empty' 2>/dev/null) - if [[ -z "$KEY" ]]; then - error "unraid-api returned no key: ${RAW:0:200}" - exit 1 - fi - - _write_key_to_conf "$CONF_FILE" "$VAR_NAME" "$KEY" - _write_key_to_conf "$MASTER_CONF" "$VAR_NAME" "$KEY" - _push_master - PREVIEW="${KEY:0:8}...${KEY: -4}" - warn "API key created ✅ — $VAR_NAME = $PREVIEW" -fi - -# ────────────────────────────────────────────────────────────────────────────── -# Step 2 (--all-hosts): SSH to each partner, create their key, write to master.conf -# ────────────────────────────────────────────────────────────────────────────── -[[ "$ALL_HOSTS" != true ]] && exit 0 - -echo "" -echo "━━━ $ICON_SYNC Partner keys ━━━" - -PARTNER_OK=0 -PARTNER_FAIL=0 - -for host_var in HOST1 HOST2 HOST3 HOST4 HOST5 HOST6 HOST7 HOST8; do - [[ "$host_var" == "$MY_ID" ]] && continue - hostname="${!host_var:-}" - [[ -z "$hostname" ]] && continue - - r_var_name="${host_var}_UNRAID_API_KEY" - r_key_name="Varaverk_${host_var}" - r_conf_path="/boot/config/plugins/varaverk/Configurations/${host_var,,}.conf" - - echo " $host_var ($hostname)…" - - REMOTE_IP=$(resolve_tailscale_ip "$hostname") - if [[ -z "$REMOTE_IP" ]]; then - warn " $host_var: cannot resolve Tailscale IP — skipping" - (( PARTNER_FAIL++ )) - continue - fi - - # SSH: check for existing key, create if missing, return the key value - REMOTE_KEY=$(ssh -i "$SSH_KEY" \ - -o ConnectTimeout=10 \ - -o StrictHostKeyChecking=no \ - -o BatchMode=yes \ - "root@${REMOTE_IP}" " - EXISTING=\$(timeout 5 /usr/local/sbin/unraid-api apikey --name '${r_key_name}' --json /dev/null) +# ── Helper: ensure a named key exists on a REMOTE registry via SSH ───────────── +_ensure_remote_key() { + local ssh_key="$1" remote_ip="$2" key_name="$3" + ssh -i "$ssh_key" \ + -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes \ + "root@${remote_ip}" " + EXISTING=\$(timeout 5 /usr/local/sbin/unraid-api apikey --name '${key_name}' --json /dev/null) KEY=\$(echo \"\$EXISTING\" | jq -r '.key // empty' 2>/dev/null) if [[ -n \"\$KEY\" ]]; then echo \"\$KEY\" else timeout 10 /usr/local/sbin/unraid-api apikey \\ - --name '${r_key_name}' --create --overwrite \\ + --name '${key_name}' --create --overwrite \\ --description 'Varaverk plugin' --roles ADMIN --json /dev/null \\ - | jq -r '.key // empty' 2>/dev/null + | jq -r '.key // empty' 2>/dev/null fi - " 2>/dev/null | tr -d '[:space:]') + " 2>/dev/null | tr -d '[:space:]' +} - if [[ -z "$REMOTE_KEY" ]]; then - warn " $host_var: could not get key from $hostname — skipping" - (( PARTNER_FAIL++ )) - continue +# ────────────────────────────────────────────────────────────────────────────── +# Step 1 — Local key (always) +# ────────────────────────────────────────────────────────────────────────────── +echo "" +echo "━━━ $ICON_GEAR Local — $LOCAL_KEY_NAME ━━━" + +LOCAL_KEY=$(_ensure_local_key "$LOCAL_KEY_NAME") +if [[ -z "$LOCAL_KEY" ]]; then + error "Could not obtain $LOCAL_KEY_NAME from registry" + exit 1 +fi + +# Sync to conf only if changed +CONF_KEY=$(grep "^\s*${LOCAL_VAR}\s*=" "$LOCAL_CONF" 2>/dev/null | sed 's/.*="\(.*\)".*/\1/' | tr -d '[:space:]') +if [[ "$CONF_KEY" != "$LOCAL_KEY" ]]; then + _write_key "$LOCAL_CONF" "$LOCAL_VAR" "$LOCAL_KEY" + warn "Synced $LOCAL_VAR → ${LOCAL_KEY:0:8}...${LOCAL_KEY: -4}" +else + echo " $LOCAL_VAR valid ✅ — ${LOCAL_KEY:0:8}...${LOCAL_KEY: -4}" +fi + +[[ "$ALL_HOSTS" != true ]] && exit 0 + +# ────────────────────────────────────────────────────────────────────────────── +# Step 2 — Cross-host key setup (--all-hosts) +# ────────────────────────────────────────────────────────────────────────────── +echo "" +echo "━━━ $ICON_SYNC Cross-host key setup ━━━" + +PARTNER_OK=0; PARTNER_FAIL=0 + +for host_var in HOST1 HOST2 HOST3 HOST4 HOST5 HOST6 HOST7 HOST8; do + [[ "$host_var" == "$MY_ID" ]] && continue + hostname="${!host_var:-}"; [[ -z "$hostname" ]] && continue + + r_var="${host_var}_UNRAID_API_KEY" # e.g. HOST2_UNRAID_API_KEY + r_my_key_name="Varaverk_${MY_ID}" # key HOST1 uses on HOST2's registry + r_their_key_name="Varaverk_${host_var}" # key HOST2 uses on HOST1's registry + r_conf="/boot/config/plugins/varaverk/Configurations/${host_var,,}.conf" + + echo "" + echo " $host_var ($hostname)" + + REMOTE_IP=$(resolve_tailscale_ip "$hostname") + if [[ -z "$REMOTE_IP" ]]; then + warn " $host_var: cannot resolve Tailscale IP — skipping" + (( PARTNER_FAIL++ )); continue fi - R_PREVIEW="${REMOTE_KEY:0:8}...${REMOTE_KEY: -4}" + # ── A: Get HOST1's access key from HOST2's registry ──────────────────────── + # Creates Varaverk_HOST1 on HOST2 → store in host1.conf as HOST2_UNRAID_API_KEY + log " Creating $r_my_key_name on $host_var registry…" + MY_KEY_ON_REMOTE=$(_ensure_remote_key "$SSH_KEY" "$REMOTE_IP" "$r_my_key_name") + if [[ -z "$MY_KEY_ON_REMOTE" ]]; then + warn " $host_var: could not create $r_my_key_name on their registry" + (( PARTNER_FAIL++ )); continue + fi + _write_key "$LOCAL_CONF" "$r_var" "$MY_KEY_ON_REMOTE" + echo " A ✅ $r_my_key_name on $host_var → stored as $r_var in ${MY_ID,,}.conf" - # Write remote key to master.conf locally - _write_key_to_conf "$MASTER_CONF" "$r_var_name" "$REMOTE_KEY" + # ── B: Create HOST2's access key on HOST1's registry ────────────────────── + # Creates Varaverk_HOST2 on HOST1 → store in host2.conf as HOST1_UNRAID_API_KEY + log " Creating $r_their_key_name on local registry…" + THEIR_KEY_ON_LOCAL=$(_ensure_local_key "$r_their_key_name") + if [[ -z "$THEIR_KEY_ON_LOCAL" ]]; then + warn " Could not create $r_their_key_name on local registry" + (( PARTNER_FAIL++ )); continue + fi - # Also write to remote's host*.conf so they have it locally + # Write to partner's conf via SSH ssh -i "$SSH_KEY" \ - -o ConnectTimeout=10 \ - -o StrictHostKeyChecking=no \ - -o BatchMode=yes \ + -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes \ "root@${REMOTE_IP}" " - CONF='${r_conf_path}' - if [[ -f \"\$CONF\" ]]; then - if grep -q '^\s*${r_var_name}\s*=' \"\$CONF\"; then - sed -i \"s|^\(\s*${r_var_name}\s*=\s*\)\\\"[^\\\"]*\\\"|\1\\\"${REMOTE_KEY}\\\"|\" \"\$CONF\" - else - echo ' ${r_var_name}=\\\"${REMOTE_KEY}\\\"' >> \"\$CONF\" - fi + CONF='$r_conf' + VAR='${MY_ID}_UNRAID_API_KEY' + KEY='$THEIR_KEY_ON_LOCAL' + [[ ! -f \"\$CONF\" ]] && exit 1 + if grep -q \"^\s*\${VAR}\s*=\" \"\$CONF\"; then + sed -i \"s|\(\s*\${VAR}\s*=\s*\)\\\"[^\\\"]*\\\"|\1\\\"\${KEY}\\\"|\" \"\$CONF\" + else + printf ' %s=\"%s\"\n' \"\$VAR\" \"\$KEY\" >> \"\$CONF\" fi - " 2>/dev/null + " 2>/dev/null \ + && echo " B ✅ $r_their_key_name on local → stored as ${MY_ID}_UNRAID_API_KEY in ${host_var,,}.conf" \ + || warn " B: could not write ${MY_ID}_UNRAID_API_KEY to ${host_var,,}.conf" - echo " $host_var: $r_key_name = $R_PREVIEW ✅" (( PARTNER_OK++ )) done -# Push master.conf with all updated keys to all partners -if (( PARTNER_OK > 0 )); then - echo "" - echo " Pushing master.conf with all keys…" - _push_master -fi - echo "" echo "━━━━━ $ICON_SUMMARY Key Setup Summary ━━━━━" -echo " Local: ✅ $VAR_NAME" -echo " Partners: $PARTNER_OK updated · $PARTNER_FAIL failed" +echo " Local key: $LOCAL_VAR ✅" +echo " Partner setup: $PARTNER_OK ok · $PARTNER_FAIL failed" +echo "" +echo " HOST1 can now call HOST2's GraphQL directly using HOST2_UNRAID_API_KEY" +echo " HOST2 can now call HOST1's GraphQL directly using HOST1_UNRAID_API_KEY" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"