- The Hub header badge could render 'vdev' because the runtime
/etc/nixos/VERSION lookup fell back to the literal string 'dev' when
the file was missing (e.g. dev/test environments, or before the
Nix-generated config carried a version at all).
- modules/core/sovran-hub.nix now reads the repo's VERSION file at
Nix eval time and bakes a real semantic version (sovran_version)
into the generated config.json and a VERSION file shipped with the
package, so the Hub always has a solid value to display.
- server.py's _get_sovran_version() now reads that baked-in
sovran_version first, and explicitly rejects a literal 'dev' value
from any of its file-based fallbacks so the badge never shows
'vdev' again.
- templates/index.html + header.css: wrapped the title and the
version badge in a '.title-group' column so the version badge sits
directly underneath 'Sovran_SystemsOS Hub', left-aligned with it.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
- Add release-stable.sh script with automatic tagging, CHANGELOG updates,
GitHub/Gitea release creation, and VERSION file management
- Update iso/common.nix to include version from VERSION file in ISO filename
- Add VERSION file (current: 1.0.3)
- Polish OS version badge in Sovran Hub header (top-right)
- Update README.md download link to versioned ISO
- Update CHANGELOG.md with existing tags (v1.0.0 – v1.0.3)
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Previously only bitcoind.service showed a version on the tile.
Now every service with a parsable Nix store path gets its version
displayed (electrs, btcpayserver, lnd, rtl, mempool, vaultwarden,
matrix-synapse, caddy, tor, etc.).
The existing _get_service_version() helper reads systemctl show
<unit> --property=ExecStart and extracts the version from the Nix
store path. Results are cached for 5 minutes per unit.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Naming: user-facing 'Wallet Connections' -> 'Lightning Wallet Connections'
across the Hub, feature registry, tile, and NixOS modules. Internal ids
(nwc-wallets, albyhub.service, /api/nwc/*) are unchanged.
UX: the service-detail modal put status, domain diagnostics, router ports,
the enable/disable toggle, restart, the liquidity guide and the whole wallet
manager in one cramped scrolling column. For this feature the modal is now
980px wide and split into two tabs:
- Wallets: wallet grid, create/share/verify flows, collapsible liquidity guide
- Service & Setup: description, status, domain checklist, ports, enable, restart
A status dot and domain chip sit in the tab bar so state is visible from both
tabs, and the modal opens on Setup when the service is off or the Lightning
Address domain is unconfigured. Wallet cards gain a balance chip, pending
badge, a prominent address row, and separated destructive actions.
Non-NWC services keep the original single-column layout and width.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Each NWC wallet connection gets its own shareable LNURL QR so the owner
can receive payments from anyone without creating invoices (the core
LNURL value proposition: a static QR that never expires).
Hub UI (service detail modal):
- New 'Share QR' button on every wallet connection card
- Dedicated share view: large QR, Lightning Address + copy, raw bech32
LNURL + copy
- Download PNG (websites/social), download SVG (vector, print-sharp at
any size), and a Print button that opens a print-ready payment card
which auto-invokes the browser print dialog
Backend (FastAPI, session-authenticated):
- GET /api/nwc/wallets/{id}/lnurl -> alias, lightning_address, LNURL
- GET /api/nwc/wallets/{id}/lnurl-qr.png (?download=1, ?scale=)
- GET /api/nwc/wallets/{id}/lnurl-qr.svg (?download=1)
- GET /api/nwc/wallets/{id}/lnurl-qr/print (print-ready HTML card)
- Stdlib bech32 encoder (BIP-173, verified against the official LUD-01
golden vector); LNURL is uppercased for QR alphanumeric-mode density
per LUD-01 guidance; QR rendered with the existing qrencode package
- Resolves wallets by app id or pubkey; 404 unknown wallet, 503 when no
Lightning domain is configured; all routes return 401 unauthenticated
No new system dependencies.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
The Element Call tile still appended a synthetic 'Step 4: Router Setup
Needed' to the domain diagnostic checklist, and every port table carried a
'Sovran_SystemsOS Status' column with Ready / Not ready yet verdicts.
Both were misleading: port forwarding happens on the router, which this
computer cannot inspect. A local ss/firewall probe can neither prove nor
disprove that forwarding works — and the LiveKit TURN relay range binds on
demand, so it reported 'Not ready yet' even on a perfectly working system.
- server.py: add ROUTER_FORWARD_ONLY_UNITS ({livekit.service}); skip the
local probe for those units, drop the step-4 append, replace extra_ports
with router_ports (no status field), and exclude router-only ports from
both tile health and /api/ports/health so they can't raise false alarms.
- helpers.js: new shared renderPortForwardGuideHtml() — one intro naming the
internal IP, explicit instructions (same internal/external port, match the
protocol, use port-range fields for 30000-40000), a colour-coded
TCP / UDP / TCP+UDP badge per row, and a closing note that the only real
test is loading the service from a phone on mobile data.
- features.js: enable-time modal uses the shared guide and now always lists
every port to forward (the old local pre-filter hid ports the user still
had to open).
- service-detail.js: tile port section uses the same guide; the SSH/non-domain
branch keeps a small 'not open on this computer yet' hint, which is a real
local fact, separate from router forwarding.
- onboarding.js: step 3 router note reworded to match (same number for
internal/external, notes that Element Call adds UDP ports).
- domain-setup.css: styles for the protocol badges and instruction list.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
- Onboarding: remove the redundant/error-prone 'Router Setup' step (5 steps -> 4).
A compact 80/443 (+22 SSH) note now lives inside Domain Configuration,
and the Element Call ports are only shown at the moment they matter:
when enabling the feature, and afterwards on the service tile.
- Onboarding step 3: fix domain prefill bug (API returns {domains: {...}}),
make /api/network fetch best-effort so it can never block the step.
- Enable-time port modal: streamline copy (one intro + table + pointer to
the tile's live status view).
- Element Call tile detail: replace 5 repetitive prose blocks with 2 compact
notes around the live-status port table.
- Njal.la DDNS: run njalla.sh immediately when a DDNS-backed feature is
enabled (previously only ran on domain save or the 15-min cron tick).
- Harden njalla.sh handling: create the base script (shebang + IP lookup)
if missing before appending curl lines; invoke via bash explicitly.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
ReadOnlyPaths for nwc-lnurl.service now lists only the specific file
/var/lib/albyhub/unlock-password (least-privilege) instead of the
whole /var/lib/albyhub directory.
Amount-duplicate error message changed to "A single amount parameter is
required" (clearer for 2+ values than "Exactly one").
Test assertion updated to match new message text.
Blocker 1: run nwc-lnurl.service as albyhub user/group so it can read
/var/lib/albyhub/unlock-password (mode 0600, dir mode 0700).
Remove the now-unused nwc-lnurl user, group, and /var/lib/nwc-lnurl
state directory. ReadOnlyPaths updated to allow the whole albyhub dir.
Blocker 2: require exactly one amount query parameter in the LNURL
callback HTTP handler. Duplicate values now return a 400 protocol
error ("Exactly one amount parameter is required") before the helper is
called. The missing-amount and non-integer paths are unchanged.
Blocker 3: partial-funding failure message now reads "was created
successfully" instead of "already exists" to avoid confusion with a
duplicate-name error, while retaining the warning not to recreate.
Tests added:
- LnurlHandlerAmountTests — HTTP-handler level tests for duplicate (2×,
3×), single-valid, and missing amount parameters.
- test_create_partial_failure_message_says_created_successfully —
asserts exact wording of the partial-funding message.
- test_nwc_lnurl_service_runs_as_albyhub — asserts the Nix service block
sets User/Group to albyhub.
- test_nwc_module_no_separate_nwc_lnurl_user — asserts no standalone
nwc-lnurl user/group is declared.
All 229 Python tests pass (1 skipped). JS syntax clean. No secrets.
- nwc_hub_manager.py: Fix BOLT11 regex to accept only valid prefixes (bc/tb/bcrt/tbs); add path to timeout error message; add early-exit to _get_app_pending_txs pagination
- nwc_lnurl_service.py: Allow NWC_LNURL_PORT env var override for port
- nwc-wallets.nix: Remove || true from gofmt so build fails on syntax errors
- test_wallet_connections.py: Extract _call_body() helper; use c.args[] access
- nwc_hub_manager.py: Fix Authorization header to use real ****** (was hardcoded to literal asterisks due to display redaction)
- server.py: Use exc.args[0] instead of str(exc) in NWC error handlers to prevent CodeQL stack-trace taint flow to HTTP responses; update albyhub.service description key
- Add nwc_hub_manager.py: AlbyHubManager with real Alby Hub API (setup, auth, CRUD, drain, delete, invoice)
- Add nwc_lnurl_service.py: dedicated loopback LNURL service on port 8181
- server.py: remove JSON scaffolding (state.json, fake invoice generator, fake NWC URI, LNURL routes); replace with real manager calls; update service maps to albyhub.service; remove LNURL auth-exempt paths
- nwc_wallet_cli.py: rewrite to use real AlbyHubManager instead of JSON state
- modules/nwc-wallets.nix: replace with albyhub user/service, nwc-lnurl service, LND macaroon, unlock-password generation
- modules/core/caddy.nix: proxy LNURL routes to port 8181 (dedicated service) instead of 8937 (Hub)
- modules/core/sovran-hub.nix: service tile points to albyhub.service
- docs/wallet-connections.md: document real architecture, Alby Hub pin/patches, backup sensitivity
- test_wallet_connections.py: replace scaffolding tests with 54 real manager tests using mocked Alby Hub
- Use set -eu (not set -euf) in sovran-hosts-update.sh
- Add sync note for domain validation regex between shell and Python
- Rename `seen` to `unique_addresses` in _resolve_all_addresses
- Improve loopback override UI message with external DNS check guidance
- Add docstring note about first-address display in _resolve_all_addresses"
- Add modules/core/local-domain-loopback.nix: systemd service and
activation script that write configured service domains to a
Sovran-managed block in /etc/hosts (127.0.0.1 / ::1) so requests
originating on this computer reach Caddy without NAT loopback.
- Import local-domain-loopback.nix in modules/modules.nix.
- server.py: add _validate_domain_value, _is_loopback_address,
_resolve_all_addresses, _trigger_hosts_update helpers.
- server.py: update _check_domain_reachable to use --resolve so
reachability is checked locally via Caddy, not via NAT loopback.
- server.py: update _evaluate_domain_checklist, api_services inline DNS
check, and api_domains_check to recognise loopback resolution as an
intentional local override rather than a DNS mismatch.
- server.py: call _trigger_hosts_update from api_domains_set after
saving a service domain so the /etc/hosts entry is applied immediately.
- Add app/tests/test_loopback_diagnostics.py with 47 tests covering
domain validation, loopback detection, diagnostic checklist logic,
composite health, and api_domains_check."