diff --git a/Partnership/partnership_onboard.sh b/Partnership/partnership_onboard.sh index bb06d06..34de173 100755 --- a/Partnership/partnership_onboard.sh +++ b/Partnership/partnership_onboard.sh @@ -264,7 +264,12 @@ write_onboard_phase() { local state_file="$(platform_setup_db_path)" [[ "$DRY_RUN" == true ]] && { warn "DRY RUN — would write ${key}=true"; return 0; } set_state_var "$state_file" "$key" "true" - platform_push_setup_state + # The push now reports failure, so it is warned about rather than returned. This function's + # contract is "the phase is recorded here" — the flag is on local disk either way, and an + # unreachable partner must not make a completed phase look like it did not happen. + platform_push_setup_state \ + || warn "Phase flag ${key} written locally but not delivered to the partner" + return 0 } diff --git a/Plugin/unraid/adapter.sh b/Plugin/unraid/adapter.sh index a7eafb2..17e1fd0 100755 --- a/Plugin/unraid/adapter.sh +++ b/Plugin/unraid/adapter.sh @@ -429,10 +429,14 @@ exit(\$failed > 0 ? 1 : 0); # ────────────────────────────────────────────────────────────────────────────────────────────── # platform_push_setup_state # Pushes the Varaverk wizard setup state to all partners via the WebGUI PHP API. -# No-op if php is unavailable. Always returns 0. +# Returns 1 if any partner did not take it, 0 otherwise. No-op returning 0 if php is absent. +# +# This used to end with a bare `return 0` under the comment "Always returns 0", which meant +# every caller that wrote `platform_push_setup_state || X=false` was testing a constant. +# Wiring a failure branch onto a helper that cannot fail is worse than hardcoding the tick, +# because the code reads as though it checked. # ────────────────────────────────────────────────────────────────────────────────────────────── platform_push_setup_state() { command -v php &>/dev/null || return 0 - php -r "require_once '/usr/local/emhttp/plugins/varaverk/include/config.php'; vv_push_setup_state();" 2>/dev/null - return 0 + php -r "require_once '/usr/local/emhttp/plugins/varaverk/include/config.php'; exit(vv_push_setup_state() > 0 ? 1 : 0);" 2>/dev/null } diff --git a/Plugin/unraid/include/config.php b/Plugin/unraid/include/config.php index 23141a1..5a9b1a5 100644 --- a/Plugin/unraid/include/config.php +++ b/Plugin/unraid/include/config.php @@ -238,12 +238,23 @@ function vv_setup_state_write(array $data): bool { // Push the setup state file to all remote hosts via scp. // Reads the remote's varaverk.cfg to find their actual SCRIPTS_DIR (handles appdata mode). -function vv_push_setup_state(): void { - if (!file_exists(VV_SETUP_STATE_FILE)) return; +// +// Returns the number of hosts the push FAILED to reach — 0 when every partner took it, and 0 +// when there was nothing to do. This was `: void`, and it threw away the exec() status too, so +// the adapter that wraps it was documented "Always returns 0" and every caller testing it was +// testing a constant. partnership_offboard.sh reported "Phase flags pushed ✅" on runs whose own +// body had just warned the push failed, which is the difference between a mirror that knows the +// partnership ended and one whose wizard still shows a finished onboard. +// +// A host with no resolvable Tailscale IP counts as a failure, not a skip: it is precisely the +// case where the partner keeps stale state and nobody is told. +function vv_push_setup_state(): int { + if (!file_exists(VV_SETUP_STATE_FILE)) return 0; $myHostId = vv_detect_host(); $vars = vv_conf_vars(); $sshKey = $vars[strtoupper($myHostId) . '_SSH_KEY'] ?? ''; - if (!$sshKey || !file_exists($sshKey)) return; + if (!$sshKey || !file_exists($sshKey)) return 0; + $failed = 0; $master = vv_read_conf_raw('master.conf'); preg_match_all('/^\s*(HOST\d+)(?:_NAME)?\s*=\s*["\']?(\S+?)["\']?\s*$/m', $master, $m); @@ -255,9 +266,9 @@ function vv_push_setup_state(): void { $hostname = trim($m[2][$i]); if (!$hostname) continue; $ip = vv_resolve_tailscale_ip($hostname); - if (!$ip) continue; + if (!$ip) { $failed++; continue; } $sshBase = 'ssh -i ' . escapeshellarg($sshKey) - . ' -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@' . $ip; + . ' -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes root@' . $ip; // Get remote SCRIPTS_DIR from varaverk.cfg — handles appdata mode on remote. // Falls back to the default install path if varaverk.cfg is absent (pre-install). @@ -282,9 +293,15 @@ function vv_push_setup_state(): void { . '[ -d "$sf" ] || { [ -d "' . $remoteSD . '/State_Files" ] ' . '&& sf="' . $remoteSD . '/State_Files"; }; ' . 'mkdir -p "$sf" && cat > "$sf/varaverk_setup.db"'; + // 2>&1 without capturing the status was the whole problem: the command could fail on an + // unreachable host, a full disk or a refused key and this function returned identically. + $out = []; + $rc = 0; exec('cat ' . escapeshellarg(VV_SETUP_STATE_FILE) . ' | ' - . $sshBase . ' ' . escapeshellarg($remoteResolve) . ' 2>&1'); + . $sshBase . ' ' . escapeshellarg($remoteResolve) . ' 2>&1', $out, $rc); + if ($rc !== 0) $failed++; } + return $failed; } // Push master.conf to all remote hosts via scp after a local save.