Files
Sovran_SystemsOS/modules/nwc-wallets.nix
T
copilot-swe-agent[bot]andGitHub 0e13779773 Address code review feedback: BOLT11 regex, error messages, PORT env var, gofmt, test helpers
- 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
2026-07-27 03:21:40 +00:00

225 lines
8.0 KiB
Nix

{ 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.
# Context lines must match getalby/hub v1.14.2 exactly; patch fails on drift.
patchPrivateRouteHints = pkgs.writeText "0001-lnd-private-route-hints.patch" ''
--- a/lnclient/lnd/lnd.go
+++ b/lnclient/lnd/lnd.go
@@ -1,5 +1,6 @@
invoice := &lnrpc.Invoice{
Memo: description,
Value: amountSat,
+ Private: true,
Expiry: expiry,
}
'';
# 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.
# Context lines must match getalby/hub v1.14.2 exactly; patch fails on drift.
patchAppIdAttribution = pkgs.writeText "0002-invoice-app-attribution.patch" ''
--- a/api/models.go
+++ b/api/models.go
@@ -1,5 +1,6 @@
type MakeInvoiceRequest struct {
Amount int64 `json:"amount"`
Description string `json:"description"`
DescriptionHash string `json:"descriptionHash"`
Expiry *int64 `json:"expiry"`
+ AppId *uint `json:"appId"`
}
'';
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
'';
meta = {
description = "Alby Hub — self-hosted NWC wallet server (Sovran_SystemsOS build)";
license = lib.licenses.gpl3;
mainProgram = "hub";
};
};
in
lib.mkIf config.sovran_systemsOS.features."nwc-wallets" {
assertions = [
{
assertion = config.services.lnd.enable;
message = "Wallet Connections requires services.lnd.enable = true.";
}
];
# ── Users and groups ─────────────────────────────────────────────
users.groups.albyhub = {};
users.users.albyhub = {
isSystemUser = 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/albyhub 0700 albyhub albyhub -"
"d /var/lib/nwc-lnurl 0750 nwc-lnurl nwc-lnurl -"
];
# ── 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";
RemainAfterExit = true;
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/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 = "${config.services.sovranHub.webPackage}/bin/nwc-lnurl";
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";
needsDDNS = true;
}
];
}