Fix node-to-server upgrade: reboot before rebuild so onboarding collects domains first

Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/8d0387a6-f66c-4fe8-8df1-0abf657b2fba

Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-14 00:46:50 +00:00
committed by GitHub
parent 55cd583569
commit ac47f39117
2 changed files with 43 additions and 19 deletions

View File

@@ -1683,6 +1683,27 @@ async def api_onboarding_complete():
f.write("") f.write("")
except OSError as exc: except OSError as exc:
raise HTTPException(status_code=500, detail=f"Could not write flag file: {exc}") raise HTTPException(status_code=500, detail=f"Could not write flag file: {exc}")
# Trigger a NixOS rebuild now that domains/ports/SSL are configured.
# This is especially important after a role upgrade (Node → Server+Desktop)
# where the rebuild was deferred until onboarding collected all required config.
try:
open(REBUILD_LOG, "w").close()
except OSError:
pass
await asyncio.create_subprocess_exec(
"systemctl", "reset-failed", REBUILD_UNIT,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
proc = await asyncio.create_subprocess_exec(
"systemctl", "start", "--no-block", REBUILD_UNIT,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await proc.wait()
return {"ok": True} return {"ok": True}
@@ -1748,7 +1769,7 @@ ROLE_STATE_NIX = """\
@app.post("/api/role/upgrade-to-server") @app.post("/api/role/upgrade-to-server")
async def api_upgrade_to_server(): async def api_upgrade_to_server():
"""Upgrade from Node role to Server+Desktop role by writing role-state.nix and rebuilding.""" """Upgrade from Node role to Server+Desktop role."""
cfg = load_config() cfg = load_config()
if cfg.get("role", "server_plus_desktop") != "node": if cfg.get("role", "server_plus_desktop") != "node":
raise HTTPException(status_code=400, detail="Upgrade is only available for the Node role.") raise HTTPException(status_code=400, detail="Upgrade is only available for the Node role.")
@@ -1765,25 +1786,14 @@ async def api_upgrade_to_server():
except FileNotFoundError: except FileNotFoundError:
pass pass
# Clear stale rebuild log # Don't rebuild yet — the user needs to configure domains, SSL email,
# and ports first via the onboarding wizard. Reboot so onboarding runs.
try: try:
open(REBUILD_LOG, "w").close() await asyncio.create_subprocess_exec(*REBOOT_COMMAND)
except OSError: except Exception as exc:
pass raise HTTPException(status_code=500, detail=f"Failed to initiate reboot: {exc}")
await asyncio.create_subprocess_exec( return {"ok": True, "status": "rebooting_to_onboarding"}
"systemctl", "reset-failed", REBUILD_UNIT,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
proc = await asyncio.create_subprocess_exec(
"systemctl", "start", "--no-block", REBUILD_UNIT,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await proc.wait()
return {"ok": True, "status": "rebuilding"}
# ── Bitcoin IBD sync helper ─────────────────────────────────────── # ── Bitcoin IBD sync helper ───────────────────────────────────────

View File

@@ -59,13 +59,27 @@ async function doUpgradeToServer() {
if (confirmBtn) { confirmBtn.disabled = true; confirmBtn.textContent = "Upgrading…"; } if (confirmBtn) { confirmBtn.disabled = true; confirmBtn.textContent = "Upgrading…"; }
closeUpgradeModal(); closeUpgradeModal();
// Reuse the rebuild modal to show progress // Reuse the rebuild modal to show reboot progress
_rebuildFeatureName = "Server + Desktop"; _rebuildFeatureName = "Server + Desktop";
_rebuildIsEnabling = true; _rebuildIsEnabling = true;
openRebuildModal(); openRebuildModal();
try { try {
await apiFetch("/api/role/upgrade-to-server", { method: "POST" }); await apiFetch("/api/role/upgrade-to-server", { method: "POST" });
// Server is rebooting — show message and wait for it to come back
if ($rebuildStatus) $rebuildStatus.textContent = "Rebooting — the setup wizard will guide you through domain and port configuration…";
if ($rebuildSpinner) $rebuildSpinner.classList.add("spinning");
// Poll until server comes back, then redirect to onboarding
var pollInterval = setInterval(async function() {
try {
await apiFetch("/api/ping");
clearInterval(pollInterval);
window.location.href = "/onboarding";
} catch (_) {
// Server still down — keep polling
}
}, 3000);
} catch (err) { } catch (err) {
if ($rebuildStatus) $rebuildStatus.textContent = "✗ Upgrade failed: " + err.message; if ($rebuildStatus) $rebuildStatus.textContent = "✗ Upgrade failed: " + err.message;
if ($rebuildSpinner) $rebuildSpinner.classList.remove("spinning"); if ($rebuildSpinner) $rebuildSpinner.classList.remove("spinning");