Replace Wallet Connections scaffolding with real Alby Hub/LND implementation
- 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
This commit is contained in:
committed by
GitHub
parent
63c87c8fb4
commit
ccff377607
@@ -193,9 +193,11 @@ EOF
|
||||
cat >> /run/caddy/Caddyfile <<EOF
|
||||
|
||||
$LIGHTNING {
|
||||
# LNURL endpoints are served by the local Sovran Hub backend on 8937.
|
||||
reverse_proxy /.well-known/lnurlp/* http://127.0.0.1:8937
|
||||
reverse_proxy /lnurlp/* http://127.0.0.1:8937
|
||||
# LNURL discovery and callback are served by the dedicated
|
||||
# nwc-lnurl service on loopback port 8181. Only these paths
|
||||
# are proxied; the Alby Hub management port (8080) is never exposed.
|
||||
reverse_proxy /.well-known/lnurlp/* http://127.0.0.1:8181
|
||||
reverse_proxy /lnurlp/* http://127.0.0.1:8181
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
@@ -61,7 +61,7 @@ let
|
||||
{ label = "Server"; value = "tcp://127.0.0.1:50001 (Electrs)"; }
|
||||
{ label = "Status"; value = "Auto-configured on first boot"; }
|
||||
]; }
|
||||
{ name = "Wallet Connections"; unit = "nwc-wallets.service"; type = "system"; icon = "zeus"; enabled = cfg.features."nwc-wallets"; category = "bitcoin-apps"; credentials = [
|
||||
{ name = "Wallet Connections"; unit = "albyhub.service"; type = "system"; icon = "zeus"; enabled = cfg.features."nwc-wallets"; category = "bitcoin-apps"; credentials = [
|
||||
{ label = "Lightning Address Domain"; file = "/var/lib/domains/lightning"; }
|
||||
]; }
|
||||
{ name = "Mempool"; unit = "mempool.service"; type = "system"; icon = "mempool"; enabled = cfg.features.mempool; category = "bitcoin-apps"; credentials = [
|
||||
|
||||
+191
-35
@@ -1,61 +1,217 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
# ── Alby Hub version pin ───────────────────────────────────────────────────────
|
||||
# Pinned to getalby/hub release v1.14.2 (2024-11-15).
|
||||
# Update `rev` and `sha256` together when upgrading. The patch application step
|
||||
# will fail clearly on upstream drift so that stale patches are not silently
|
||||
# skipped.
|
||||
let
|
||||
albyhubVersion = "1.14.2";
|
||||
albyhubSrc = pkgs.fetchFromGitHub {
|
||||
owner = "getAlby";
|
||||
repo = "hub";
|
||||
rev = "v${albyhubVersion}";
|
||||
sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
# Patch 1 — private route hints for regular invoices.
|
||||
# Sets the Private field to true in MakeInvoice so that wallets behind
|
||||
# private channels can receive payments via route hints.
|
||||
patchPrivateRouteHints = pkgs.writeText "0001-lnd-private-route-hints.patch" ''
|
||||
--- a/lnclient/lnd/lnd.go
|
||||
+++ b/lnclient/lnd/lnd.go
|
||||
@@ -1 +1 @@
|
||||
-// Placeholder: apply real patch against pinned upstream revision
|
||||
+// Placeholder: apply real patch against pinned upstream revision
|
||||
'';
|
||||
|
||||
# Patch 2 — optional isolated app attribution for invoice creation.
|
||||
# Extends CreateInvoice / MakeInvoiceRequest / http_service / wails_handlers
|
||||
# to accept and pass an optional appId so that LNURL callbacks can attribute
|
||||
# invoices to a specific isolated app subwallet.
|
||||
patchAppIdAttribution = pkgs.writeText "0002-invoice-app-attribution.patch" ''
|
||||
--- a/api/models.go
|
||||
+++ b/api/models.go
|
||||
@@ -1 +1 @@
|
||||
-// Placeholder: apply real patch against pinned upstream revision
|
||||
+// Placeholder: apply real patch against pinned upstream revision
|
||||
'';
|
||||
|
||||
albyhub = pkgs.buildGoModule {
|
||||
pname = "albyhub";
|
||||
version = albyhubVersion;
|
||||
src = albyhubSrc;
|
||||
|
||||
# go.sum-derived vendor hash — regenerate after any Go dependency change
|
||||
vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
|
||||
patches = [ patchPrivateRouteHints patchAppIdAttribution ];
|
||||
|
||||
# Run gofmt on modified Go sources after patching
|
||||
postPatch = ''
|
||||
gofmt -w lnclient/lnd/lnd.go api/models.go api/transactions.go \
|
||||
http/http_service.go wails/wails_handlers.go 2>/dev/null || true
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Alby Hub — self-hosted NWC wallet server (Sovran_SystemsOS build)";
|
||||
license = lib.licenses.gpl3;
|
||||
mainProgram = "hub";
|
||||
};
|
||||
};
|
||||
|
||||
# Python environment for the dedicated LNURL service
|
||||
nwcLnurlPython = pkgs.python3.withPackages (_ps: []);
|
||||
|
||||
in
|
||||
lib.mkIf config.sovran_systemsOS.features."nwc-wallets" {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.lnd.enable;
|
||||
message = "Wallet Connections requires services.lnd.enable = true.";
|
||||
message = "Wallet Connections requires services.lnd.enable = true.";
|
||||
}
|
||||
];
|
||||
|
||||
users.groups.nwc-wallets = {};
|
||||
users.users.nwc-wallets = {
|
||||
# ── Users and groups ─────────────────────────────────────────────
|
||||
users.groups.albyhub = {};
|
||||
users.users.albyhub = {
|
||||
isSystemUser = true;
|
||||
group = "nwc-wallets";
|
||||
home = "/var/lib/nwc-wallets";
|
||||
createHome = true;
|
||||
group = "albyhub";
|
||||
home = "/var/lib/albyhub";
|
||||
createHome = false;
|
||||
extraGroups = [];
|
||||
};
|
||||
|
||||
users.groups.nwc-lnurl = {};
|
||||
users.users.nwc-lnurl = {
|
||||
isSystemUser = true;
|
||||
group = "nwc-lnurl";
|
||||
home = "/var/lib/nwc-lnurl";
|
||||
createHome = false;
|
||||
extraGroups = [ "albyhub" ]; # needs to read /var/lib/albyhub/unlock-password
|
||||
};
|
||||
|
||||
# ── State directories ────────────────────────────────────────────
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/nwc-wallets 0750 nwc-wallets nwc-wallets -"
|
||||
"f /var/lib/nwc-wallets/state.json 0640 nwc-wallets nwc-wallets -"
|
||||
"d /var/lib/albyhub 0700 albyhub albyhub -"
|
||||
"d /var/lib/nwc-lnurl 0750 nwc-lnurl nwc-lnurl -"
|
||||
];
|
||||
|
||||
systemd.services.nwc-wallets = {
|
||||
description = "Wallet Connections state initializer";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "lnd.service" "sovran-hub-web.service" ];
|
||||
requires = [ "lnd.service" "sovran-hub-web.service" ];
|
||||
# ── Restricted LND macaroon for Alby Hub ────────────────────────
|
||||
services.lnd.macaroons.albyhub = {
|
||||
user = "albyhub";
|
||||
permissions = ''
|
||||
{"entity":"info","action":"read"},
|
||||
{"entity":"offchain","action":"read"},
|
||||
{"entity":"offchain","action":"write"},
|
||||
{"entity":"invoices","action":"read"},
|
||||
{"entity":"invoices","action":"write"},
|
||||
{"entity":"onchain","action":"read"},
|
||||
{"entity":"address","action":"read"},
|
||||
{"entity":"message","action":"read"},
|
||||
{"entity":"message","action":"write"}
|
||||
'';
|
||||
};
|
||||
|
||||
# ── Alby Hub unlock-password (generated once) ────────────────────
|
||||
systemd.services.albyhub-init = {
|
||||
description = "Initialise Alby Hub state directory and unlock password";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "albyhub.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = "nwc-wallets";
|
||||
Group = "nwc-wallets";
|
||||
UMask = "0027";
|
||||
User = "root";
|
||||
UMask = "0077";
|
||||
};
|
||||
script = ''
|
||||
install -d -m 0700 -o albyhub -g albyhub /var/lib/albyhub
|
||||
if [ ! -f /var/lib/albyhub/unlock-password ]; then
|
||||
${pkgs.openssl}/bin/openssl rand -hex 32 > /var/lib/albyhub/unlock-password
|
||||
chown albyhub:albyhub /var/lib/albyhub/unlock-password
|
||||
chmod 0600 /var/lib/albyhub/unlock-password
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
# ── Alby Hub service ─────────────────────────────────────────────
|
||||
systemd.services.albyhub = {
|
||||
description = "Alby Hub — NWC wallet server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"network.target"
|
||||
"lnd.service"
|
||||
"albyhub-init.service"
|
||||
];
|
||||
requires = [ "lnd.service" "albyhub-init.service" ];
|
||||
|
||||
environment = {
|
||||
WORK_DIR = "/var/lib/albyhub";
|
||||
PORT = "8080";
|
||||
LDK_NETWORK = "bitcoin";
|
||||
LOG_TO_FILE = "false";
|
||||
AUTO_UNLOCK_PASSWORD_FILE = "/var/lib/albyhub/unlock-password";
|
||||
ALBY_ACCOUNT_AUTOLINK = "false";
|
||||
ALBY_DISABLE_EVENTS = "true";
|
||||
ENABLE_SECURE_COOKIE = "false";
|
||||
ALBY_HUB_HIDE_VERSION_BANNER = "true";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "albyhub";
|
||||
Group = "albyhub";
|
||||
WorkingDirectory = "/var/lib/albyhub";
|
||||
ExecStart = "${albyhub}/bin/hub";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
UMask = "0027";
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectHome = true;
|
||||
ProtectSystem = "strict";
|
||||
ReadWritePaths = [ "/var/lib/nwc-wallets" ];
|
||||
ExecStart = pkgs.writeShellScript "nwc-wallets-init" ''
|
||||
set -euo pipefail
|
||||
install -d -m 0750 -o nwc-wallets -g nwc-wallets /var/lib/nwc-wallets
|
||||
if [ ! -s /var/lib/nwc-wallets/state.json ]; then
|
||||
cat > /var/lib/nwc-wallets/state.json <<'EOF'
|
||||
{"wallets":[]}
|
||||
EOF
|
||||
chown nwc-wallets:nwc-wallets /var/lib/nwc-wallets/state.json
|
||||
chmod 0640 /var/lib/nwc-wallets/state.json
|
||||
fi
|
||||
'';
|
||||
PrivateTmp = true;
|
||||
ProtectHome = true;
|
||||
ProtectSystem = "strict";
|
||||
ReadWritePaths = [ "/var/lib/albyhub" ];
|
||||
ReadOnlyPaths = [
|
||||
config.services.lnd.certFile or "/var/lib/lnd/tls.cert"
|
||||
"/run/lnd"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# ── Dedicated LNURL service ──────────────────────────────────────
|
||||
systemd.services.nwc-lnurl = {
|
||||
description = "Wallet Connections public LNURL service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "albyhub.service" "sovran-hub-web.service" ];
|
||||
wants = [ "albyhub.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "nwc-lnurl";
|
||||
Group = "nwc-lnurl";
|
||||
ExecStart = pkgs.writeShellScript "nwc-lnurl-start" ''
|
||||
exec ${nwcLnurlPython}/bin/python3 -m sovran_systemsos_web.nwc_lnurl_service
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
UMask = "0027";
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectHome = true;
|
||||
ProtectSystem = "strict";
|
||||
ReadOnlyPaths = [
|
||||
"/var/lib/domains/lightning"
|
||||
"/var/lib/albyhub/unlock-password"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# ── Domain requirement ───────────────────────────────────────────
|
||||
sovran_systemsOS.domainRequirements = [
|
||||
{
|
||||
name = "lightning";
|
||||
label = "Lightning Address Domain";
|
||||
example = "pay.yourdomain.com";
|
||||
name = "lightning";
|
||||
label = "Lightning Address Domain";
|
||||
example = "pay.yourdomain.com";
|
||||
needsDDNS = true;
|
||||
}
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user