Loading indicator:
- Branded boot splash (Hub logo inside an accent spinner ring,
"Starting The Hub") covers the shell while the first services
data loads, then fades out once the welcome dashboard has
rendered; never blocks longer than 25s and reassures the user
after 8s (message about post-reboot delays)
- Fire the network and update checks before the first services
render so the dashboard cards are current at first paint
- Sidebar Update button now adopts the last known update state
when built (order-independent), and the welcome dashboard
re-renders when the update state changes
Icons:
- New monochrome g-pulse glyph (activity line) for Systems
Operational: welcome card, dialog header, and System Status
section — the shield no longer doubles as Security
- Tech Support / Security dialog header gets a standard chip;
Security shows the shield chip with a plain "Security" title
(no emoji), and the shared dialog title now resets correctly
when reopening Tech Support after Security
The drifting orb glow was clipped to the centered 1040px column,
leaving visible walls where it met the content padding. Make the
welcome section full-bleed instead: cancel the content-area padding
with negative margins and stretch it to all four panel edges
(sidebar border, topbar, viewport right edge, bottom). The heading,
cards, and Browse button keep their centered 1040px column via
.welcome-inner.
Replace the All Services grid as the landing view with a
Nextcloud-style welcome dashboard:
- Greeting (time-of-day), "Welcome to Your Sovereign Digital &
Financial Life" headline, version + role meta line
- Status cards: Systems Operational, Network (LAN/WAN/hostname),
Bitcoin sync progress with ETA, and available-updates card
- Calm drifting orb background animation (transform-only,
disabled under prefers-reduced-motion)
- Services grid stays mounted but hidden until the user browses
(Browse button, search, or a nav category); Dashboard nav item
returns to the welcome view
- Move role badge and autolaunch preference out of the sidebar;
autolaunch toggle now lives in the Systems Operational modal
- Remove LAN/WAN chip from the topbar; IPs live on the Network card
The colored updater icon broke the monochrome system-action set in the
sidebar. Replace it with a new g-update glyph — a down arrow dropping
into an open tray ("get / install updates") — drawn in the same 24×24,
2px round-cap stroke style as the other sidebar glyphs.
- Sidebar Update System row: g-update glyph, currentColor like the rest.
- Update dialog header: same glyph in the green chip, matching the
rebuild dialog and Systems Operational header treatment.
- Also fixes a latent sizing bug: the previous colored <img> had no CSS
rule (the .upd-icon rule from the earlier patch never landed), so the
dialog header icon rendered at the source file's intrinsic 128px. The
chip markup uses the existing 54px .upd-chip rules.
- One "Bitcoin" category: the service catalog distinguishes bitcoin-base
from bitcoin-apps, which surfaced as two sidebar menus and two tile
sections. The Hub now normalizes both into a single "Bitcoin" category
(nav item with combined count, one tile section). CATEGORY_ALIASES in
constants.js maps the catalog keys; server config sends one
("bitcoin", "Bitcoin") entry and the node role allowlist is updated
to match. The nix catalog is unchanged.
- "Self-Hosted Apps" is now "Personal Apps" — every service here is
self-hosted, so the label added no distinction.
Coloring: the base surfaces were green-tinted darks, which read as a
green-hued background rather than green highlights. Shift the whole
surface ramp to neutral graphite (slightly cool, and lighter overall)
and keep green strictly for highlights — brand, buttons, switches,
status dots and pills, sync bars, focus rings.
- Tokens: bg #17191d, surface #1c1f24, card #23272c, hover #292e34,
elevated #26292e, inset #121417; text neutrals #e9edec/#a9b0b3/
#7a8388. Accent, borders, radii, shadows unchanged.
- Remove the green ambient radial washes behind the app (and login).
- Reboot / security-reset overlay gradients neutralized.
Updater icon: restore the repo's branded Sovran updater icon
(/static/icons/update.svg, true colors) in the sidebar Update System
row and in the update dialog header, replacing the generic refresh
glyph. The rebuild dialog keeps its glyph chip (no branded icon
exists for it).
- Restart and retry actions (topbar Reboot, Restart Entire System in the
update and rebuild dialogs and the restart confirmation, Retry Update,
Try Again) are now blue instead of amber — amber read as an error.
Status pills keep their semantic colors (amber = restart required).
- The service-modal domain section is no longer a checklist: it is titled
"Domain Status" and shows a single green "Domain is active" line with
the domain (or the backend's not-configured detail and Configure
Domain action when there is no domain yet). No "Step 1" wording.
- Systems Operational keeps the complexity hidden: the Router card now
shows just a verdict — green "Ports 80 and 443 are open" or red
"Ports 80 and 443 are not open" — derived from the same live backend
diagnostics. Port-forwarding instructions are gone (onboarding covers
them); on the Node-only role the card still explains that ports only
matter once BTCPay Server or Lightning Wallet Connections (LNURL) is
enabled.
Eight fixes from live testing:
Systems Operational modal:
- Drop the "test from your phone on mobile data" (hairpin NAT) sentence
from the router note — too technical for the intended audience.
- Node-only role: when BTCPay Server and Lightning Wallet Connections
(LNURL) are both off, the router card becomes a simple "No router
setup needed yet" note explaining that ports 80/443 only matter if
one of those services is turned on. When one is enabled, the card
shows the same port steps and live domain/port diagnostics as the
Desktop + Server role (diagnostics now poll only enabled services).
Service modals:
- The Domain Diagnostic Checklist now shows only the domain-active
step (Domain Configured). DNS and port diagnostics live in Systems
Operational, which shows the full checklist.
- Node-only role: BTCPay Server and Lightning Wallet Connections
modals gain a "Ports to Forward in Your Router" section with the
standard 80/443 wording and this computer's LAN address.
- Domain setup and reconfigure dialogs no longer contain router
port-forwarding instructions (already handled during Desktop +
Server onboarding and shown in Systems Operational).
Lightning Wallet Connections:
- Refresh and New Wallet toolbar buttons now share one height and
baseline (a leftover 12px top margin on Refresh was offsetting it).
- The header status chip has a proper gap between the status dot and
its label.
Zeus Connect / QR codes:
- QR codes render at the original 240px with the white frame and
pixelated upscaling, restoring scannability.
Brand:
- The sidebar logo loads via /static/sovran-hub-icon.svg (same as the
login page) instead of an inline <use> symbol — the gradient-heavy
symbol did not render reliably. The icon sprite is hidden with the
browser-safe zero-size pattern instead of display:none.
The update dialog kept the old bare title + spinner layout from the
previous theme. Rework it (and the rebuild dialog, for consistency) to
the approved The Hub dialog anatomy:
- Header: green chip icon, title, version chip, status pill
(Checking… / Up to date / Updating… / Restart required / Update
failed / Status unknown) and a header close button. A spinner appears
in the header while an update is starting or running.
- System Details card: current version, release channel, and last
checked (relative time, refreshed on every check).
- The log renders as a console with green "ok" and dim hint lines; the
up-to-date result shows as a single green console line exactly once
(the redundant status line is hidden in that state).
- Footer: Close plus a "Check again" primary action that re-runs the
update check. Close and Check again are disabled while a check or
update is in flight; all existing recovery actions (Save Error
Report, Retry Status, Retry Update, Restart Entire System) keep their
exact semantics.
- Opening the dialog now shows a "Checking…" state immediately while
the existing reattach-then-check logic runs; reattaching to an
in-progress update (page reload, RDP reconnect) is unchanged.
Sidebar Update System status tints now use the theme palette (red /
amber / blue / green), and the periodic background check refreshes the
dialog's "last checked" value.
Rebuild dialog gets the same header (icon, version chip, Applying… /
Done / Restart required / Failed pill, header close disabled while a
rebuild runs); its log stays hidden as before.
No API or state-machine changes: same endpoints, same polling, same
reattach and recovery behavior.
Apply the approved "The Hub" redesign to the web admin while keeping every
existing mechanic intact (polling, service-detail modals, Matrix and system
password management, NWC wallet manager, update/rebuild/backup/security/
reboot flows, feature manager, onboarding, role handling).
Layout (templates/index.html):
- Old header bar + IP bar replaced by a sidebar + topbar app shell.
Sidebar carries the brand (The Hub / Sovran_SystemsOS version), category
navigation with live counts, the System actions (Update System, Tech
Support, Manual Backup, Security, node-only Upgrade), Feature Manager /
Preferences, and the role badge.
- Topbar carries the page title, a service search box, the LAN | WAN
network chip (external IP always visible, one line), Reboot and Sign Out.
- New widgets row: Systems Operational summary (opens the new Systems
Operational modal) and Bitcoin Core sync progress with block/ETA.
- New Systems Operational modal: service counts, router port-forwarding
steps (80/443 to this machine's LAN IP), the live domain diagnostics
checklist, and which services use those ports.
New static/js/dashboard.js (namespaced IIFE, no new globals) renders the
nav, search filtering, widgets, and the Systems Operational modal; it is
driven by the existing /api/services payloads via a
window.dashboardServicesUpdated() hook called from buildTiles/updateTiles.
Visual design (static/css/*):
- New token set (softer dark surfaces, lifted contrast, Sovran green
reserved for status and actions) with legacy variable names aliased so
every secondary sheet re-skins automatically.
- Tiles, dialogs, buttons, inputs, toggles, tables, forms, overlays and
the login page restyled to the GNOME/libadwaita-flavored surfaces:
20px cards, 26px dialogs, pill buttons, libadwaita switches, mono value
pills with Copy buttons, consistent modal anatomy.
- Inline SVG symbol set for chrome/nav icons (monochrome, currentColor);
service icons still load from /static/icons/*.svg as before.
- Sidebar system buttons now use vector glyphs instead of emoji.
Behavioral details:
- Service detail modal header gains a status pill next to the version
chip; credentials render with pre-wrap for multiline values.
- First-login security banner now renders as a card inside the content
area instead of a full-width strip above the app.
- Search + category filtering hide/show sections and tiles without
touching the polling or update logic.
Onboarding and login pages rebranded to "The Hub" with aligned palette.
The Hub vendored a second copy of the NWC stack
(app/sovran_systemsos_web/nwc_hub_manager.py, nwc_audit.py,
nwc_lnurl_service.py, nwc_wallet_cli.py) and built its own nwc-wallet /
nwc-lnurl binaries from it. That copy drifted from the pinned Alby Hub
API contract (appId vs toAppId) and duplicated code that Sovran_Bitcoin
already ships and fixes.
Changes:
- Delete the four vendored modules; server.py now imports the canonical
implementation directly (from sovran_nwc import nwc_hub_manager) from
the sovran-nwc package (pkgs.sovran-bitcoin.nwc). API fixes in
Sovran_Bitcoin now propagate to the Hub web app automatically.
- sovran-hub-web launcher: add <sovran-nwc>/lib/sovran-nwc to
sys.path so the import resolves.
- Stop shipping nwc-wallet / nwc-lnurl binaries from sovran-hub-web:
the flake already provides them (env-wrapped nwc-wallet with
NWC_* vars via albyhub.nix, and nwc-lnurl.service via lnurl.nix).
Requires a Sovran_Bitcoin rev containing the toAppId fix (and the
LNURL module audit-log fix); bump the flake input afterwards:
nix flake update sovran-bitcoin
Test:
- nixos-rebuild switch
- Hub Wallet Connections tab still lists/creates wallets
- nwc-wallet list works from the operator shell
- journalctl -u nwc-lnurl shows no import/contract errors
Replace the nixpkgs albyhub overrideAttrs patch-chain with a fully
vendored package at packages/albyhub for v1.24.0.
- modules/core/sovran-hub.nix: bump fallback version 1.8.0 -> 1.24.0
- modules/nwc-wallets.nix: build via pkgs.callPackage ../packages/albyhub
- packages/albyhub:
- drop 0002-isolated-invoice-app-id.patch (fixed upstream)
- add 0004-lnd-only.patch: strip LDK/Bark/Cashu/CLN/Phoenix backends
from service/start.go, leaving only the LND case
- add 0005-no-frontend.patch: remove //go:embed dist and the
frontend handler registration
- add default.nix: buildGoModule for v1.24.0 with no nodejs/yarn/
bark-ffi-go/ldk-node deps (only stdenv.cc.cc), subPackages cmd/http
Keeps 0001-private-route-hints and 0003-loopback-bind-host. Does not
touch flake.nix, VERSION, or CHANGELOG.
The module mixed the `options` keyword attribute with a bare top-level
`system.*` setting. Once a module declares `options` (or `config`), every
other top-level attribute must be a reserved module keyword — the nixpkgs
unifyModuleSyntax check rejects anything else, so every nixos-rebuild
aborted at evaluation time with:
error: Module '.../modules/core/public-ip.nix' has an unsupported
attribute `system'. ... move all of them (namely: system) into the
`config' attribute.
Prefix the activation script with `config.` (equivalent to wrapping it in
`config = { ... };`) so the module evaluates again. The detector script
itself is unchanged.
Fixes: ac6c498615 (-feat(public-ip): unify public-IP detection into one privacy-first script-)
The public IP was previously detected independently in three places,
each contacting a different third party: the Hub (HTTPS echo via
api.ipify.org / ifconfig.me / icanhazip.com on every API call and
background tick), DDNS (myip.opendns.com via OpenDNS), and LiveKit
(embedded STUN). Consolidate into a single detector with one shared
cache so every consumer reads the same value with minimal exposure.
- add modules/core/public-ip.nix: installs /var/lib/sovran/public-ip.py
(pure Python stdlib, no new deps) writing /var/lib/secrets/external-ip
- detection chain (first success wins): explicit pin, fresh cache
(default TTL 300s), STUN binding request over UDP (one packet, no
metadata), DNS myip.opendns.com query, then OPT-IN HTTPS echo
(publicIP.httpsEcho, empty by default — never contacted unless listed)
- privacy: while the cache is fresh zero third parties are contacted;
at most one party learns the IP per refresh interval, via the least
exposing mechanism available
- hub (server.py): _get_external_ip() now reads the shared detector /
cache instead of calling ipify/ifconfig/icanhazip directly
- ddns (njalla.nix): use the shared detector instead of a separate
OpenDNS dig; allow the hardened service to write /var/lib/secrets
- element-calling: livekit-turn-setup falls back to the shared
detector on cold boot; add LiveKit webhooks to lk-jwt-service
(sfu_webhook) so abrupt disconnects are cleaned up immediately;
set LIVEKIT_SANITY_CHECK_INTERVAL_SECONDS=60 as a missed-webhook
guard; drop the dead services.livekit.settings block and set
openFirewall=false (Caddy fronts the SFU; no public 7880/tcp)
- new options: sovran_systemsOS.publicIP.{stunServer,stunPort,
dnsResolver,httpsEcho,cacheTTL}
The element-calling feature only advertised the LiveKit focus via the
well-known org.matrix.msc4143.rtc_foci file, and relied on STUN
auto-detection for the public IP. Element X queries the MatrixRTC
transports registry endpoint and fails with MISSING_MATRIX_RTC_TRANSPORT
when it is absent, and blocked STUN egress silently left LiveKit
advertising a private IP (call connects but no video across servers).
- synapse: enable msc4143_enabled and advertise matrix_rtc.transports
(MSC4519) with the site's element-calling URL, so Element X can
discover the LiveKit focus instead of erroring out
- livekit: determine the public IP to advertise at runtime —
explicit pin, then HTTPS egress detection (api.ipify.org /
checkip.amazonaws.com / ifconfig.me), then STUN fallback with a
warning; reject non-routable results (private/loopback/CGNAT)
- lk-jwt-service: append optional extra homeservers to
LIVEKIT_FULL_ACCESS_HOMESERVERS via the new
sovran_systemsOS.elementCalling.fullAccessHomeservers option
- add sovran_systemsOS.elementCalling.externalIP option to pin the
advertised public IP for multi-WAN/VPN setups
- add element-calling-public-check.service: boot-time diagnostics for
public DNS (via 1.1.1.1, bypassing local loopback overrides), JWT
healthz through Caddy and via the public IP, and the transports
endpoint — turns the silent -no media- failure into a visible error
- add restartTriggers so livekit/lk-jwt-service pick up regenerated
runtime configs on rebuild
NixOS already knows whether a reboot is pending: /nix/var/nix/profiles/
system vs /run/current-system. Marker files only the Hub's own updater
wrote desynced for terminal-updated machines (and markers from older
updaters could never clear), pinning the badge on forever. Reconcile
REBOOT_REQUIRED against live state on every read; the stale marker
self-heals to IDLE. The .generation marker write is now informational.
nixpkgs commit c8f9654 refactored the services.i2pd module to use
an RFC42-style settings attribute set and removed services.i2pd.proto.
After updating the root nixpkgs input from f13ff45 to ec2d622, the
vendored bitcoind module failed evaluation on the obsolete
services.i2pd.proto.sam.enable definition.
The error occurred even with services.bitcoind.i2p at its false default:
bitcoind was enabled, so NixOS still validated the obsolete option path
inside the conditional i2pd integration.
Read the SAM endpoint from services.i2pd.settings.sam and configure its
new upstream-style fields explicitly. Keep 127.0.0.1:7656, matching the
old typed option defaults that bitcoind uses to generate its i2psam
setting.
This preserves optional I2P support without activating it by default.
i2pd remains disabled until services.bitcoind.i2p is set to true or
"only-outgoing".
Nixpkgs migration: https://github.com/NixOS/nixpkgs/commit/c8f965411e812060a9377fa4c2d7d0f84e8b10e0
livekit-turn-setup.service detects the primary interface from the IPv4
default route, but had no ordering against network-online.target. With
NetworkManager+DHCP the default route is applied late at boot, so the
oneshot could run before it existed, exit 1, and — being a hard
dependency of livekit.service — take livekit down with it. The Hub then
showed a 'failed' red dot until livekit was restarted manually.
Order both livekit.service and livekit-turn-setup.service after
network-online.target. Also add a bounded retry when copying Caddy's ACME
cert so we never write an empty turn.crt/turn.key on a fresh boot.
The Hub launcher used an ephemeral /tmp profile deleted on exit, which
wiped the hub_manual_logout marker cookie. On reopen, /auto-login minted a
new session and logged the user straight back in without a password.
Use a persistent per-user profile under XDG_STATE_HOME and drop the
deletion trap so the logout marker survives close/reopen. Keep
--skip-origin-startup-dialog. Adds regression tests.