diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 4b0634b..e22d738 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -1219,7 +1219,7 @@ def _generate_qr_base64(data: str) -> str | None: # ── Update helpers (file-based, no systemctl) ──────────────────── def _read_update_status() -> str: - """Read the status file. Returns RUNNING, SUCCESS, FAILED, or IDLE.""" + """Read the status file. Returns RUNNING, REBOOT_REQUIRED, SUCCESS, FAILED, or IDLE.""" try: with open(UPDATE_STATUS, "r") as f: return f.read().strip() @@ -4755,17 +4755,21 @@ def _recover_stale_status(status_file: str, log_file: str, unit_name: str) -> bo except Exception: pass - new_status = "SUCCESS" if unit_result == "success" else "FAILED" + if unit_result == "success": + new_status = "REBOOT_REQUIRED" if unit_name == UPDATE_UNIT else "SUCCESS" + else: + new_status = "FAILED" try: with open(status_file, "w") as f: f.write(new_status) except OSError: pass - msg = ( - "\n[Update completed successfully while the server was restarting.]\n" - if new_status == "SUCCESS" - else "\n[Update encountered an error. See log above for details.]\n" - ) + if new_status == "REBOOT_REQUIRED": + msg = "\n[Update staged successfully while the server was restarting. Reboot required.]\n" + elif new_status == "SUCCESS": + msg = "\n[Update completed successfully while the server was restarting.]\n" + else: + msg = "\n[Update encountered an error. See log above for details.]\n" try: with open(log_file, "a") as f: f.write(msg) diff --git a/app/tests/test_hub_update_boot_staging.py b/app/tests/test_hub_update_boot_staging.py new file mode 100644 index 0000000..3e9e5bf --- /dev/null +++ b/app/tests/test_hub_update_boot_staging.py @@ -0,0 +1,40 @@ +import unittest +from pathlib import Path + + +HUB_NIX = Path(__file__).resolve().parents[2] / "modules" / "core" / "sovran-hub.nix" + + +def _section(source: str, start: str, end: str) -> str: + start_idx = source.index(start) + end_idx = source.index(end, start_idx) + return source[start_idx:end_idx] + + +class HubUpdateBootStagingTests(unittest.TestCase): + def setUp(self): + self.source = HUB_NIX.read_text() + self.update_section = _section( + self.source, + 'update-script = pkgs.writeShellScript "sovran-hub-update.sh" \'\'', + "# ── Rebuild wrapper script", + ) + self.rebuild_section = _section( + self.source, + 'rebuild-script = pkgs.writeShellScript "sovran-hub-rebuild.sh" \'\'', + "# ── Brave launcher wrapper", + ) + + def test_full_update_uses_boot_not_switch(self): + self.assertIn("nixos-rebuild boot --flake /etc/nixos", self.update_section) + self.assertNotIn("nixos-rebuild switch --flake /etc/nixos", self.update_section) + + def test_full_update_marks_reboot_required(self): + self.assertIn('echo "REBOOT_REQUIRED" > "$STATUS"', self.update_section) + + def test_rebuild_path_keeps_switch_semantics(self): + self.assertIn("nixos-rebuild switch --flake /etc/nixos", self.rebuild_section) + + +if __name__ == "__main__": + unittest.main() diff --git a/modules/core/sovran-hub.nix b/modules/core/sovran-hub.nix index ac119a8..12e9695 100644 --- a/modules/core/sovran-hub.nix +++ b/modules/core/sovran-hub.nix @@ -146,33 +146,16 @@ let echo "" if [ "$RC" -eq 0 ]; then - echo "── Step 2/3: nixos-rebuild ──────────────────────────" - SWITCH_OUT=$(nixos-rebuild switch --flake /etc/nixos --print-build-logs \ + echo "── Step 2/3: nixos-rebuild boot (stage next reboot) ─" + BOOT_OUT=$(nixos-rebuild boot --flake /etc/nixos --print-build-logs \ --option connect-timeout 10 \ --option stalled-download-timeout 90 \ --option download-attempts 7 \ --option fallback true 2>&1) - SWITCH_RC=$? - echo "$SWITCH_OUT" - if [ "$SWITCH_RC" -eq 0 ]; then - echo "[OK] switch succeeded" - elif echo "$SWITCH_OUT" | grep -q "switchInhibitors\|Pre-switch checks failed"; then - echo "" - echo " ✓ Build succeeded — a reboot is required to apply this update" - echo " (Critical system components changed; running nixos-rebuild boot instead)" - if nixos-rebuild boot --flake /etc/nixos --print-build-logs \ - --option connect-timeout 10 \ - --option stalled-download-timeout 90 \ - --option download-attempts 7 \ - --option fallback true 2>&1; then - echo "REBOOT_REQUIRED" > "$STATUS" - exit 0 - else - echo "[ERROR] nixos-rebuild boot also failed" - RC=1 - fi - else - echo "[ERROR] nixos-rebuild switch failed" + BOOT_RC=$? + echo "$BOOT_OUT" + if [ "$BOOT_RC" -ne 0 ]; then + echo "[ERROR] nixos-rebuild boot failed" RC=1 fi echo "" @@ -188,9 +171,10 @@ let if [ "$RC" -eq 0 ]; then echo "══════════════════════════════════════════════════" - echo " ✓ Update completed successfully" + echo " ✓ Update staged successfully" + echo " Reboot required to activate the new system" echo "══════════════════════════════════════════════════" - echo "SUCCESS" > "$STATUS" + echo "REBOOT_REQUIRED" > "$STATUS" else echo "══════════════════════════════════════════════════" echo " ✗ Update failed — see errors above"