Files
Sovran_SystemsOS/app/sovran_systemsos_web/templates/index.html
T
Security Fix 78bfc5b408 hub: serve the Hub on its own port instead of through Caddy
Caddy fronted the Hub at http://sovransystemsos.local, but the Hub
already listens on 0.0.0.0:8937 itself, and nothing Caddy added is
something it needs:

- Not the name. That is avahi's: mDNS advertises a hostname, not a port,
  so the name resolves wherever the Hub listens.
- Not TLS (the site was plain http), not authentication, not cache
  headers. The header block duplicated NoCacheMiddleware, and its
  Clear-Site-Data ("cache") overrode the app's stronger ("cache",
  "storage").
- Not access control, and this is the point. With ports 80/443 forwarded
  for public services, a Host header on those ports reached the Hub. That
  second door is how the reported bug happened, and c33457f guards it
  with an address check instead of closing it.

The Hub is now served on port 8937 only, at
http://sovransystemsos.local:8937, and Caddy has no site for it. The
only thing Caddy answers on 80/443 is the public sites. Caddy keeps
Ride The Lightning (:3051) and Mempool (:60847), because those do need
it: Sovran_Bitcoin binds both to 127.0.0.1 and RTL's unit is sandboxed to
loopback besides, so Caddy is how the local network reaches them.

- caddy.nix: no Hub site. Caddy runs wherever RTL and Mempool do, which
  includes Bitcoin Node Only. There it did not run at all (enable was
  needsHttpsPorts || extraVhosts != ""), so :3051 and :60847 were open
  in the firewall with nothing listening. Ports 80/443 still follow
  needsHttpsPorts alone, so Node Only does not open them. The two sites
  are written only where their service exists; they were unconditional.
- sovran-hub.nix: 8937 follows the new hub.directPort, 60847 follows
  Mempool. It used to be `[ 8937 60847 ]` on every role, Desktop Only
  included.
- roles.nix: hub.directPort defaults to !roles.desktop: open on Server +
  Desktop and Bitcoin Node Only, closed on Desktop Only, where the Hub is
  reached from the machine itself through the desktop window on
  localhost.
- The bind stays 0.0.0.0, which is IPv4 only: with that bind [::1]:8937
  is refused and "localhost" falls back to 127.0.0.1. That is on purpose
  and is now said in the comment. An IPv6 listener would let in clients
  whose global addresses the Hub cannot tell from a stranger's, which is
  the question the previous commit declines to answer by guessing.
- README, SECURITY.md and two strings in index.html give the new URL.

Behaviour changes: the Hub's address gains :8937, and http://sovransystemsos.local
on port 80 no longer reaches it. Bitcoin Node Only now runs Caddy.

Evaluated with nix eval (nixpkgs as flake.lock pins it, Sovran_Bitcoin at
the locked revision), firewall TCP ports per role:

                      c33457f                      this commit
  Server + Desktop    22 80 443 3051 8937 60847    22 80 443 3051 8937
  Bitcoin Node Only   22 3051 8937 60847           22 3051 8937 60847   (Caddy now runs)
  Desktop Only        22 8937 60847                22

Port 22 is open on every role although sshd listens on loopback only;
the last commit of this series deals with that.

The Caddyfile the module really generates (the evaluated generator
script, run, then `caddy validate` with Caddy 2.9.1): Node Only gets the
two sites and nothing else; Server + Desktop with every domain
configured gets the seven domain sites plus :3051 and :60847 and no
mention of the Hub; with Bitcoin off there are no local-network sites;
Node Only with Bitcoin off and no domains leaves Caddy off.

Add tests/test_hub_direct.py and keep tests/test_caddy_lan_only.py for
the two sites it still covers.
2026-10-02 02:24:29 -05:00

599 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sovran_SystemsOS — The Hub</title>
<link rel="stylesheet" href="/static/css/base.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/buttons.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/header.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/layout.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/tiles.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/modals.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/features.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/onboarding.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/support.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/domain-setup.css?v={{ asset_version }}" />
<link rel="stylesheet" href="/static/css/security.css?v={{ asset_version }}" />
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<!-- service glyphs -->
<!-- ═══ Real service logos — extracted verbatim from app/icons/*.svg ═══
(internal ids namespaced per icon to avoid collisions) -->
<!-- symbolic helpers (nav + modal internals) -->
<!-- Bitcoin nav icon: the OFFICIAL logo silhouette (paths taken verbatim
from app/icons/bitcoin-core.svg) rendered monochrome in currentColor
so it matches the symbolic sidebar set -->
<symbol id="g-btc-sym" viewBox="-34 -34 580 580">
<path fill="currentColor" d="M317.871 7.656c-137.12-34.192-276.024 49.28-310.2 186.44-34.208 137.136 49.256 276.048 186.36 310.24 137.16 34.199 276.063-49.265 310.256-186.408 34.192-137.152-49.264-276.08-186.416-310.272m50.936 211.872c-3.688 24.936-17.512 37.008-35.864 41.24 25.2 13.12 38.024 33.239 25.809 68.12-15.16 43.319-51.176 46.976-99.072 37.912l-11.624 46.584-28.088-7 11.472-45.96a1076 1076 0 0 1-22.384-5.809l-11.512 46.177-28.056-7 11.624-46.673c-6.561-1.68-13.225-3.464-20.024-5.168l-36.552-9.111 13.943-32.152s20.696 5.504 20.416 5.096c7.952 1.969 11.48-3.216 12.872-6.672l18.368-73.64.048-.2 13.104-52.568c.344-5.968-1.712-13.496-13.088-16.336.439-.296-20.4-5.072-20.4-5.072l7.472-30 38.736 9.673-.032.144c5.824 1.448 11.824 2.824 17.937 4.216L245.423 89.2l28.072 7-11.28 45.224c7.536 1.721 15.12 3.456 22.504 5.297l11.2-44.929 28.088 7-11.504 46.145c35.464 12.215 61.401 30.527 56.304 64.591"/>
<path fill="currentColor" d="m254.647 174.6-13.983 56.08c15.855 3.951 64.735 20.071 72.656-11.656 8.248-33.096-42.817-40.472-58.673-44.424"/>
<path fill="currentColor" d="m233.608 258.984-15.425 61.832c19.04 4.729 77.769 23.584 86.448-11.296 9.072-36.376-51.984-45.784-71.023-50.536"/>
</symbol>
<symbol id="g-chat" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 5.5v7a2.5 2.5 0 0 1-2.5 2.5H10l-4 3.2c-.6.5-1 .3-1-.5V5.5A2.5 2.5 0 0 1 7.5 3h10A2.5 2.5 0 0 1 20 5.5z"/>
<circle cx="8.7" cy="9.2" r="0.5" fill="currentColor"/><circle cx="12" cy="9.2" r="0.5" fill="currentColor"/><circle cx="15.3" cy="9.2" r="0.5" fill="currentColor"/>
</g>
</symbol>
<symbol id="g-antenna" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round">
<path d="M5.4 9.9a9.3 9.3 0 0 1 13.2 0"/>
<path d="M8.1 12.9a5.6 5.6 0 0 1 7.8 0"/>
<circle cx="12" cy="16.6" r="1.5" fill="currentColor" stroke="none"/>
<path d="M12 16.6V21"/>
</g>
</symbol>
<symbol id="g-lock" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<rect x="5.5" y="10.5" width="13" height="9.5" rx="2.6"/>
<path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5"/>
<circle cx="12" cy="15" r="1.3" fill="currentColor" stroke="none"/>
</g>
</symbol>
<!-- nav + ui symbolic icons -->
<symbol id="g-home" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 11.2L12 4.5l8 6.7"/>
<path d="M6 10.5v8a1.2 1.2 0 0 0 1.2 1.2h9.6a1.2 1.2 0 0 0 1.2-1.2v-8"/>
<path d="M10 19.7v-5.4h4v5.4"/>
</g>
</symbol>
<symbol id="g-grid" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round">
<rect x="3.5" y="3.5" width="7.2" height="7.2" rx="2"/><rect x="13.3" y="3.5" width="7.2" height="7.2" rx="2"/>
<rect x="3.5" y="13.3" width="7.2" height="7.2" rx="2"/><rect x="13.3" y="13.3" width="7.2" height="7.2" rx="2"/>
</g>
</symbol>
<symbol id="g-server" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round">
<rect x="3.5" y="3.5" width="17" height="7.4" rx="2"/><rect x="3.5" y="13.1" width="17" height="7.4" rx="2"/>
<circle cx="7.2" cy="7.2" r="0.6" fill="currentColor"/><circle cx="7.2" cy="16.8" r="0.6" fill="currentColor"/>
</g>
</symbol>
<symbol id="g-dots" viewBox="0 0 24 24">
<g fill="currentColor">
<circle cx="5" cy="5" r="1.7"/><circle cx="12" cy="5" r="1.7"/><circle cx="19" cy="5" r="1.7"/>
<circle cx="5" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="19" cy="12" r="1.7"/>
<circle cx="5" cy="19" r="1.7"/><circle cx="12" cy="19" r="1.7"/><circle cx="19" cy="19" r="1.7"/>
</g>
</symbol>
<symbol id="g-refresh" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M20 12a8 8 0 1 1-2.9-6.2"/>
<path d="M20.6 2.6l-.5 4.9-4.6-1.7z" fill="currentColor" stroke="none"/>
</g>
</symbol>
<!-- Update System: down arrow into a tray (get / install updates) -->
<symbol id="g-update" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 4v10.5"/>
<path d="M7.8 10.3L12 14.5l4.2-4.2"/>
<path d="M4.5 16.5v2a2.5 2.5 0 0 0 2.5 2.5h10a2.5 2.5 0 0 0 2.5-2.5v-2"/>
</g>
</symbol>
<symbol id="g-box" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<rect x="3.5" y="4" width="17" height="4.4" rx="1.6"/>
<path d="M5.2 8.4v9a2.4 2.4 0 0 0 2.4 2.4h8.8a2.4 2.4 0 0 0 2.4-2.4v-9"/>
<path d="M10 12.5h4"/>
</g>
</symbol>
<symbol id="g-shield-check" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2.5 4.5 5.5v6c0 4.6 3.1 8 7.5 9.9 4.4-1.9 7.5-5.3 7.5-9.9v-6z"/>
<path d="M8.6 12l2.4 2.4 4.4-4.6"/>
</g>
</symbol>
<!-- Systems Operational: activity pulse (system health) — distinct from
the Security shield above -->
<symbol id="g-pulse" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M3 12h4l3-7 4 14 3-7h4"/>
</symbol>
<symbol id="g-lifebuoy" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
<circle cx="12" cy="12" r="8.6"/><circle cx="12" cy="12" r="3.6"/>
<path d="M6 6l3.2 3.2M18 6l-3.2 3.2M6 18l3.2-3.2M18 18l-3.2-3.2"/>
</g>
</symbol>
<symbol id="g-power" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M7.2 6.1a7 7 0 1 0 9.6 0"/>
<path d="M12 2.8v8.4"/>
</g>
</symbol>
<symbol id="g-logout" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 4h4.5A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5H14"/>
<path d="M4 12h10M10.5 8.5 14 12l-3.5 3.5"/>
</g>
</symbol>
<symbol id="g-search" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="6.5"/>
<path d="M15.8 15.8 21 21"/>
</g>
</symbol>
<symbol id="g-copy" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="11" height="11" rx="2.2"/>
<path d="M5.5 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v.5"/>
</g>
</symbol>
<symbol id="g-check" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.5l5 5 10-11"/></symbol>
<symbol id="g-chev" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" d="M9.5 5.5 16 12l-6.5 6.5"/></symbol>
<symbol id="g-x" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" d="M6 6l12 12M18 6 6 18"/></symbol>
<symbol id="g-wifi" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round">
<path d="M4.5 10.2a11 11 0 0 1 15 0"/>
<path d="M7.3 13.3a7 7 0 0 1 9.4 0"/>
<circle cx="12" cy="17" r="1.4" fill="currentColor" stroke="none"/>
</g>
</symbol>
<symbol id="g-key" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<circle cx="7.5" cy="15.5" r="4"/>
<path d="M10.4 12.6 20 3M16.2 6.8l2.4 2.4M18.8 4.2l2.2 2.2"/>
</g>
</symbol>
<symbol id="g-alert" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3.6 22.2 20.4H1.8z"/>
<path d="M12 9.8v4.6"/><circle cx="12" cy="17.2" r="0.6" fill="currentColor" stroke="none"/>
</g>
</symbol>
</svg>
<!-- ═══ BOOT SPLASH ═══ covers the shell while the first data loads;
dashboard.js lifts it once the welcome cards are rendered -->
<div id="app-splash" role="status" aria-live="polite">
<div class="splash-card">
<div class="splash-ring">
<img src="/static/sovran-hub-icon.svg" alt="" width="64" height="64" />
<span class="splash-ring-arc" aria-hidden="true"></span>
</div>
<div class="splash-title">Starting The Hub</div>
<div class="splash-sub" id="splash-sub">Gathering your services…</div>
</div>
</div>
<script>
(function () {
var splash = document.getElementById("app-splash");
var sub = document.getElementById("splash-sub");
var lifted = false;
function lift() {
if (lifted || !splash) return;
lifted = true;
splash.classList.add("done");
setTimeout(function () {
if (splash && splash.parentNode) splash.parentNode.removeChild(splash);
}, 500);
}
window.__liftAppSplash = lift;
// Reassure the user if the backend is slow (e.g. right after a reboot)
setTimeout(function () {
if (!lifted && sub) sub.textContent = "Still starting\u2026 this can take a moment after a reboot.";
}, 8000);
// Never trap the user behind the splash
setTimeout(lift, 25000);
})();
</script>
<div class="app">
<!-- ═══ SIDEBAR ═══ -->
<aside class="sidebar" id="sidebar">
<div class="brand">
<img src="/static/sovran-hub-icon.svg" class="brand-logo" alt="The Hub" />
<div class="brand-text">
<div class="brand-title">The <em>Hub</em></div>
<div class="brand-sub">Sovran_SystemsOS v{{ sovran_version }}</div>
</div>
</div>
<nav id="sidebar-nav" aria-label="Service categories"></nav>
<div class="nav-label">System</div>
<div id="sidebar-support"></div>
<!-- Feature Manager + Preferences (features.js) -->
<div id="sidebar-features"></div>
<div class="sidebar-spacer"></div>
</aside>
<!-- ═══ MAIN ═══ -->
<div class="main">
<header class="topbar">
<div class="page-title" id="page-title">Dashboard</div>
<div class="search-box">
<svg><use href="#g-search"/></svg>
<input id="search-input" type="text" placeholder="Search services…" autocomplete="off" />
</div>
<div class="top-actions">
<button class="btn btn-header-reboot" id="btn-header-reboot" title="Restart the entire computer">Reboot</button>
<button class="btn btn-logout" id="btn-logout" title="Sign out">Sign Out</button>
</div>
</header>
<main class="content" id="content">
<!-- ═══ WELCOME DASHBOARD (default view) ═══ -->
<section class="welcome" id="welcome-view">
<div class="welcome-bg" aria-hidden="true">
<div class="orb orb-a"></div>
<div class="orb orb-b"></div>
<div class="orb orb-c"></div>
</div>
<div class="welcome-inner">
<div class="welcome-greeting" id="welcome-greeting">Hello</div>
<h1 class="welcome-title">Welcome to Your <em>Sovereign</em> Digital &amp; Financial Life</h1>
<div class="welcome-meta">Sovran_SystemsOS v{{ sovran_version }}<span class="welcome-meta-sep">·</span><span id="welcome-role"></span></div>
<div class="welcome-cards" id="welcome-cards">
<div class="welcome-dyn" id="wc-systems"></div>
<div class="widget w-network" id="w-network">
<div class="widget-chip chip-green"><svg><use href="#g-wifi"/></svg></div>
<div class="w-body">
<h3>Network</h3>
<div class="net-row"><span class="net-k">LAN</span><span class="ip-value" id="ip-internal">…</span></div>
<div class="net-row"><span class="net-k">WAN</span><span class="ip-value" id="ip-external">…</span></div>
<div class="sub">sovransystemsos.local:8937</div>
</div>
</div>
<div class="welcome-dyn" id="wc-more"></div>
</div>
<button class="btn btn-ghost welcome-browse" id="welcome-browse-btn">Browse all services →</button>
</div>
</section>
<!-- ═══ SERVICES GRID ═══ -->
<div id="tiles-area" style="display:none">
<div class="dashboard-loading" role="status" aria-live="polite">
<span class="dashboard-loading-spinner" aria-hidden="true"></span>
<span>Loading service status…</span>
</div>
</div>
</main>
</div>
</div>
<!-- ═══ SYSTEMS OPERATIONAL MODAL (dashboard.js) ═══ -->
<div class="modal-overlay" id="systems-modal" role="dialog" aria-modal="true" aria-labelledby="systems-modal-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="systems-modal-title">
<span class="widget-chip chip-green" style="width:54px;height:54px;border-radius:16px"><svg style="width:27px;height:27px"><use href="#g-pulse"/></svg></span>
<span>Systems Operational</span>
</span>
<button class="creds-close-btn" id="systems-close-btn" title="Close">✕</button>
</div>
<div class="creds-body" id="systems-body">
<p class="creds-loading">Loading…</p>
</div>
<!-- Auto-launch preference (features.js) — moved here from the sidebar -->
<div class="sysprefs" id="autolaunch-slot"></div>
</div>
</div>
<!-- Update modal -->
<div class="modal-overlay" id="update-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title-text">
<div class="modal-dialog">
<div class="upd-header">
<span class="widget-chip chip-sovran upd-chip"><svg class="upd-chip-svg"><use href="#g-update"/></svg></span>
<span class="upd-title-wrap">
<span class="upd-title" id="modal-title-text">Sovran_SystemsOS Update</span>
<span class="upd-sub">
<span class="ver-chip">Sovran_SystemsOS v{{ sovran_version }}</span>
<span class="upd-pill" id="upd-pill"></span>
<span class="modal-spinner" id="modal-spinner"></span>
</span>
</span>
<button class="creds-close-btn" id="update-close-btn" title="Close">✕</button>
</div>
<div class="modal-body-scroll">
<div class="sysmodal-card">
<div class="sysmodal-card-title"><svg><use href="#g-server"/></svg>System Details</div>
<div class="sysstep"><div class="sysnum">1</div><div class="sysstep-x"><div class="sysstep-t">Current version</div><div class="sysval"><span class="sysval-text">{{ sovran_version }}</span></div></div></div>
<div class="sysstep"><div class="sysnum">2</div><div class="sysstep-x"><div class="sysstep-t">Release channel</div><div class="sysval"><span class="sysval-text">stable</span></div></div></div>
<div class="sysstep"><div class="sysnum">3</div><div class="sysstep-x"><div class="sysstep-t">Last checked</div><div class="sysval"><span class="sysval-text" id="upd-last-checked">—</span></div></div></div>
</div>
<div class="update-status-msg" id="modal-status">Checking for updates…</div>
<div class="modal-log" id="modal-log" aria-live="polite"></div>
</div>
<div class="modal-footer">
<button class="btn btn-save" id="btn-save-report" style="display:none">Save Error Report</button>
<button class="btn btn-save" id="btn-retry-update-status" style="display:none">Retry Status</button>
<button class="btn btn-reboot" id="btn-retry-update" style="display:none">Retry Update</button>
<button class="btn btn-reboot" id="btn-reboot" style="display:none">Restart Entire System</button>
<span class="modal-footer-spacer"></span>
<button class="btn btn-close-modal" id="btn-close-modal" disabled>Close</button>
<button class="btn btn-primary" id="btn-check-again"><svg><use href="#g-refresh"/></svg>Check again</button>
</div>
</div>
</div>
<!-- Credentials info modal -->
<div class="modal-overlay" id="creds-modal" role="dialog" aria-modal="true" aria-labelledby="creds-modal-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="creds-modal-title">Service Info</span>
<button class="creds-close-btn" id="creds-close-btn" title="Close">✕</button>
</div>
<div class="creds-body" id="creds-body">
<p class="creds-loading">Loading…</p>
</div>
</div>
</div>
<!-- Tech Support modal -->
<div class="modal-overlay" id="support-modal" role="dialog" aria-modal="true" aria-labelledby="support-modal-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title">
<span class="widget-chip chip-neutral" id="support-modal-chip" style="width:54px;height:54px;border-radius:16px"><svg style="width:27px;height:27px"><use href="#g-lifebuoy"/></svg></span>
<span id="support-modal-title">Tech Support</span>
</span>
<button class="creds-close-btn" id="support-close-btn" title="Close">✕</button>
</div>
<div class="creds-body" id="support-body">
<p class="creds-loading">Loading…</p>
</div>
</div>
</div>
<!-- Domain Setup Modal -->
<div class="modal-overlay" id="domain-setup-modal" role="dialog" aria-modal="true" aria-labelledby="domain-setup-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="domain-setup-title">🌐 Domain Setup</span>
<button class="creds-close-btn" id="domain-setup-close-btn" title="Close">✕</button>
</div>
<div class="creds-body" id="domain-setup-body"></div>
</div>
</div>
<!-- SSL Email Modal -->
<div class="modal-overlay" id="ssl-email-modal" role="dialog" aria-modal="true" aria-labelledby="ssl-email-title">
<div class="creds-dialog domain-narrow-dialog">
<div class="creds-header">
<span class="creds-title" id="ssl-email-title">📧 SSL Certificate Email</span>
<button class="creds-close-btn" id="ssl-email-close-btn" title="Close">✕</button>
</div>
<div class="creds-body">
<p class="support-desc">Let's Encrypt needs an email address for SSL certificate notifications.</p>
<div class="domain-field-group">
<label class="domain-field-label" for="ssl-email-input">Email:</label>
<input class="domain-field-input" type="email" id="ssl-email-input" placeholder="you@example.com" />
</div>
<div class="domain-field-actions">
<button class="btn btn-close-modal" id="ssl-email-cancel-btn">Cancel</button>
<button class="btn btn-primary" id="ssl-email-save-btn">Save</button>
</div>
</div>
</div>
</div>
<!-- Feature Confirm Modal -->
<div class="modal-overlay" id="feature-confirm-modal" role="dialog" aria-modal="true" aria-labelledby="feature-confirm-title">
<div class="creds-dialog domain-narrow-dialog">
<div class="creds-header">
<span class="creds-title" id="feature-confirm-title">Confirm Action</span>
<button class="creds-close-btn" id="feature-confirm-close-btn" title="Close">✕</button>
</div>
<div class="creds-body">
<p class="support-desc" id="feature-confirm-message"></p>
<div class="domain-field-actions">
<button class="btn btn-close-modal" id="feature-confirm-cancel-btn">Cancel</button>
<button class="btn btn-primary" id="feature-confirm-ok-btn">Continue</button>
</div>
</div>
</div>
</div>
<!-- Port Requirements Modal -->
<div class="modal-overlay" id="port-requirements-modal" role="dialog" aria-modal="true" aria-labelledby="port-req-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="port-req-title">🔌 Router / Firewall Port Requirements</span>
<button class="creds-close-btn" id="port-req-close-btn" title="Close">✕</button>
</div>
<div class="creds-body" id="port-req-body"></div>
</div>
</div>
<!-- Rebuild Modal -->
<div class="modal-overlay" id="rebuild-modal" role="dialog" aria-modal="true" aria-labelledby="rebuild-modal-title">
<div class="modal-dialog">
<div class="upd-header">
<span class="widget-chip chip-sovran upd-chip"><svg class="upd-chip-svg"><use href="#g-server"/></svg></span>
<span class="upd-title-wrap">
<span class="upd-title" id="rebuild-modal-title">Sovran_SystemsOS Rebuild</span>
<span class="upd-sub">
<span class="ver-chip">Sovran_SystemsOS v{{ sovran_version }}</span>
<span class="upd-pill" id="rebuild-pill"></span>
<span class="modal-spinner" id="rebuild-spinner"></span>
</span>
</span>
<button class="creds-close-btn" id="rebuild-close-hdr" title="Close">✕</button>
</div>
<div class="modal-body-scroll">
<div class="update-status-msg" id="rebuild-status">Rebuilding…</div>
<div class="modal-log" id="rebuild-log" aria-live="polite" style="display:none"></div>
</div>
<div class="modal-footer">
<button class="btn btn-save" id="rebuild-save-report" style="display:none">Save Error Report</button>
<button class="btn btn-reboot" id="rebuild-reboot-btn" style="display:none">Restart Entire System</button>
<span class="modal-footer-spacer"></span>
<button class="btn btn-close-modal" id="rebuild-close-btn" disabled>Close</button>
</div>
</div>
</div>
<!-- Upgrade Modal (Node → Server+Desktop) -->
<div class="modal-overlay" id="upgrade-modal" role="dialog" aria-modal="true" aria-labelledby="upgrade-modal-title">
<div class="creds-dialog upgrade-dialog">
<div class="creds-header">
<span class="creds-title" id="upgrade-modal-title">🚀 Upgrade to Server + Desktop</span>
<button class="creds-close-btn" id="upgrade-close-btn" title="Close">✕</button>
</div>
<div class="creds-body">
<p class="support-desc">
Upgrading to the full <strong>Server + Desktop</strong> experience will unlock all services —
encrypted messaging, password management, cloud storage, website hosting, and more.
</p>
<div class="upgrade-info-box">
<p class="upgrade-info-title">Simple, guided setup:</p>
<ul class="upgrade-info-list">
<li>Sovran_SystemsOS walks you through getting your domain from <a href="https://njal.la" target="_blank" rel="noopener noreferrer">Njal.la</a> and connecting your services</li>
<li>To make your services available outside your home, complete one router task: forward ports <strong>80 and 443</strong> to this computer</li>
</ul>
</div>
<p class="support-desc">
⚠️ <strong>Heads-up:</strong> your domain points at your home internet connection,
so anyone can look up your home IP address. Domain privacy does not hide it.
</p>
<p class="support-desc">
The Hub guides you through every step.
</p>
<p class="support-desc upgrade-rebuild-note">
The system will rebuild after upgrading. This may take several minutes.
</p>
<div class="domain-field-actions">
<button class="btn btn-close-modal" id="upgrade-cancel-btn">Cancel</button>
<button class="btn btn-primary" id="upgrade-confirm-btn">Yes, Upgrade</button>
</div>
</div>
</div>
</div>
<!-- Security Reset overlay -->
<div class="security-reset-overlay" id="security-reset-overlay">
<!-- Phase 1: wiping in progress -->
<div class="reboot-card" id="security-reset-phase1">
<div class="security-reset-overlay-icon">🛡</div>
<h2 class="reboot-title">Security Reset In Progress</h2>
<p class="reboot-message">
⚠️ Wiping all data and credentials.<br />
<strong>Do not power off your computer.</strong><br />
This may take several minutes.
</p>
<div class="reboot-dots">
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
</div>
<p class="reboot-submessage" id="security-reset-overlay-step">Erasing data and resetting credentials…</p>
</div>
<!-- Phase 2: password display -->
<div class="reboot-card" id="security-reset-phase2" style="display:none;">
<div class="security-reset-overlay-icon">🔑</div>
<h2 class="reboot-title">Security Reset Complete</h2>
<p class="security-reset-password-label">Your new login password is:</p>
<div class="security-reset-password-box" id="security-reset-new-password">&nbsp;</div>
<p class="security-reset-password-warning">
✍️ <strong>Write this down now.</strong><br />
You will need it to log in to your computer<br />and the Sovran Hub at <em>sovransystemsos.local:8937</em>.
</p>
<button class="security-reset-reboot-btn" id="security-reset-reboot-btn" disabled>
I have written down my new password — Restart Entire System
</button>
</div>
</div>
<!-- Reboot overlay -->
<div class="reboot-overlay" id="reboot-overlay">
<!-- Normal restarting card -->
<div class="reboot-card" id="reboot-main-card">
<div class="reboot-icon" aria-hidden="true"></div>
<h2 class="reboot-title">Restarting Entire System</h2>
<p class="reboot-message">
The entire computer is restarting, including the desktop and all hosted services.<br />
This page will reconnect automatically when Sovran_SystemsOS is back online.
</p>
<div class="reboot-dots" aria-hidden="true">
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
</div>
<p class="reboot-submessage" id="reboot-submessage" aria-live="polite">Sending restart request…</p>
</div>
<!-- Error card (shown if restart request fails definitively) -->
<div class="reboot-card" id="reboot-error-card" style="display:none">
<div class="reboot-icon" aria-hidden="true"></div>
<h2 class="reboot-title">Restart could not be started</h2>
<p class="reboot-message">
The computer did not begin restarting. No services were intentionally stopped. Please try again.
</p>
<div class="reboot-error-actions">
<button class="btn btn-close-modal" id="reboot-error-close-btn">Close</button>
<button class="btn btn-restart-amber" id="reboot-error-retry-btn">Try Again</button>
</div>
</div>
</div>
<!-- Restart Confirm Dialog -->
<div class="modal-overlay" id="restart-confirm-modal" role="dialog" aria-modal="true" aria-labelledby="restart-confirm-title">
<div class="creds-dialog domain-narrow-dialog">
<div class="creds-header">
<span class="creds-title" id="restart-confirm-title">Restart the entire computer?</span>
</div>
<div class="creds-body">
<div id="restart-conflict-box" class="restart-conflict-box" style="display:none">
<p class="restart-conflict-title">The system cannot restart right now.</p>
<p class="restart-conflict-desc">A system update, rebuild, backup, restore, or security operation is currently running. Wait for it to finish, then try again.</p>
</div>
<p class="support-desc"><strong>This will reboot the physical machine running Sovran_SystemsOS — not just the Hub.</strong></p>
<p class="support-desc">The desktop and all hosted services will stop temporarily and restart with the computer. Anyone currently using these services will be disconnected.</p>
<p class="support-desc">The system usually returns within 1–3 minutes. This page will reconnect automatically.</p>
<div class="domain-field-actions">
<button class="btn btn-close-modal" id="restart-confirm-cancel-btn">Cancel</button>
<button class="btn btn-restart-amber" id="restart-confirm-ok-btn">Restart Entire System</button>
</div>
</div>
</div>
</div>
<script src="/static/js/constants.js?v={{ asset_version }}"></script>
<script src="/static/js/state.js?v={{ asset_version }}"></script>
<script src="/static/js/helpers.js?v={{ asset_version }}"></script>
<script src="/static/js/domain-prereqs.js?v={{ asset_version }}"></script>
<script src="/static/js/tiles.js?v={{ asset_version }}"></script>
<script src="/static/js/service-detail.js?v={{ asset_version }}"></script>
<script src="/static/js/support.js?v={{ asset_version }}"></script>
<script src="/static/js/update.js?v={{ asset_version }}"></script>
<script src="/static/js/rebuild.js?v={{ asset_version }}"></script>
<script src="/static/js/features.js?v={{ asset_version }}"></script>
<script src="/static/js/security.js?v={{ asset_version }}"></script>
<script src="/static/js/dashboard.js?v={{ asset_version }}"></script>
<script src="/static/js/events.js?v={{ asset_version }}"></script>
</body>
</html>