Merge pull request #342 from naturallaw777/copilot/fix-lnd-alby-hub-port-collision
Resolve deterministic LND/Alby Hub port collision and enforce loopback-only binding
This commit is contained in:
@@ -0,0 +1,76 @@
|
|||||||
|
name: Wallet Connections Nix Validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 180
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: DeterminateSystems/nix-installer-action@v16
|
||||||
|
|
||||||
|
- name: Targeted Wallet Connections tests
|
||||||
|
run: python3 -m unittest app/tests/test_wallet_connections.py
|
||||||
|
|
||||||
|
- name: Full Python tests
|
||||||
|
run: python3 -m unittest discover -s app/tests -p 'test_*.py'
|
||||||
|
|
||||||
|
- name: JavaScript syntax checks
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for f in app/sovran_systemsos_web/static/js/*.js; do
|
||||||
|
node --check "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Verify Alby Hub patches apply to v1.23.0
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
curl -fsSL https://github.com/getAlby/hub/archive/refs/tags/v1.23.0.tar.gz | tar -xz -C "$tmpdir"
|
||||||
|
srcdir="$(find "$tmpdir" -maxdepth 1 -type d -name 'hub-*' | head -n1)"
|
||||||
|
test -n "$srcdir"
|
||||||
|
cd "$srcdir"
|
||||||
|
git init -q
|
||||||
|
git add -A
|
||||||
|
git commit -q -m "snapshot"
|
||||||
|
git apply --check "$GITHUB_WORKSPACE/packages/albyhub/0001-private-route-hints.patch"
|
||||||
|
git apply --check "$GITHUB_WORKSPACE/packages/albyhub/0002-isolated-invoice-app-id.patch"
|
||||||
|
git apply --check "$GITHUB_WORKSPACE/packages/albyhub/0003-loopback-bind-host.patch"
|
||||||
|
|
||||||
|
- name: Build patched Alby Hub
|
||||||
|
run: |
|
||||||
|
nix --extra-experimental-features 'nix-command flakes' build \
|
||||||
|
--no-link --print-build-logs --impure --expr \
|
||||||
|
'let
|
||||||
|
flake = builtins.getFlake (toString ./.);
|
||||||
|
pkgs = import flake.inputs.nixpkgs { system = "x86_64-linux"; };
|
||||||
|
patchedAlbyHub = pkgs.albyhub.overrideAttrs (old: {
|
||||||
|
patches = (old.patches or []) ++ [
|
||||||
|
./packages/albyhub/0001-private-route-hints.patch
|
||||||
|
./packages/albyhub/0002-isolated-invoice-app-id.patch
|
||||||
|
./packages/albyhub/0003-loopback-bind-host.patch
|
||||||
|
];
|
||||||
|
});
|
||||||
|
in patchedAlbyHub'
|
||||||
|
|
||||||
|
- name: Run real NixOS listener test
|
||||||
|
run: |
|
||||||
|
nix --extra-experimental-features 'nix-command flakes' build \
|
||||||
|
'.#checks.x86_64-linux.nwc-wallets-port-collision' \
|
||||||
|
--no-link --print-build-logs
|
||||||
|
|
||||||
|
- name: Build full system closure
|
||||||
|
run: |
|
||||||
|
nix --extra-experimental-features 'nix-command flakes' build \
|
||||||
|
'.#nixosConfigurations.nixos.config.system.build.toplevel' \
|
||||||
|
--no-link --print-build-logs
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Alby Hub manager — shared backend for Wallet Connections API and recovery CLI.
|
Alby Hub manager — shared backend for Wallet Connections API and recovery CLI.
|
||||||
|
|
||||||
Interfaces with the local Alby Hub instance at http://127.0.0.1:8080.
|
Interfaces with the local Alby Hub instance at
|
||||||
|
http://127.0.0.1:18080 by default (override with NWC_ALBY_HUB_API_BASE).
|
||||||
All sensitive values (passwords, bearer tokens, pairing URIs, macaroon
|
All sensitive values (passwords, bearer tokens, pairing URIs, macaroon
|
||||||
contents, Nostr private keys) are redacted from any exception messages
|
contents, Nostr private keys) are redacted from any exception messages
|
||||||
or log output.
|
or log output.
|
||||||
@@ -24,7 +25,10 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# ── Constants ──────────────────────────────────────────────────────
|
# ── Constants ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
DEFAULT_API_BASE = "http://127.0.0.1:8080"
|
DEFAULT_API_BASE = os.environ.get(
|
||||||
|
"NWC_ALBY_HUB_API_BASE",
|
||||||
|
"http://127.0.0.1:18080",
|
||||||
|
)
|
||||||
DEFAULT_UNLOCK_PASSWORD_FILE = "/var/lib/albyhub/unlock-password"
|
DEFAULT_UNLOCK_PASSWORD_FILE = "/var/lib/albyhub/unlock-password"
|
||||||
DEFAULT_MACAROON_FILE = os.environ.get(
|
DEFAULT_MACAROON_FILE = os.environ.get(
|
||||||
"NWC_LND_MACAROON_FILE", "/run/lnd/albyhub.macaroon"
|
"NWC_LND_MACAROON_FILE", "/run/lnd/albyhub.macaroon"
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ Tests cover:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -169,6 +171,28 @@ def _fresh_manager() -> mgr.AlbyHubManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerApiBaseConfigurationTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self._original_api_base = os.environ.get("NWC_ALBY_HUB_API_BASE")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if self._original_api_base is None:
|
||||||
|
os.environ.pop("NWC_ALBY_HUB_API_BASE", None)
|
||||||
|
else:
|
||||||
|
os.environ["NWC_ALBY_HUB_API_BASE"] = self._original_api_base
|
||||||
|
importlib.reload(mgr)
|
||||||
|
|
||||||
|
def test_default_api_base_falls_back_to_18080(self):
|
||||||
|
os.environ.pop("NWC_ALBY_HUB_API_BASE", None)
|
||||||
|
reloaded_mgr = importlib.reload(mgr)
|
||||||
|
self.assertEqual(reloaded_mgr.DEFAULT_API_BASE, "http://127.0.0.1:18080")
|
||||||
|
|
||||||
|
def test_env_api_base_overrides_default(self):
|
||||||
|
os.environ["NWC_ALBY_HUB_API_BASE"] = "http://127.0.0.1:19999"
|
||||||
|
reloaded_mgr = importlib.reload(mgr)
|
||||||
|
self.assertEqual(reloaded_mgr.DEFAULT_API_BASE, "http://127.0.0.1:19999")
|
||||||
|
|
||||||
|
|
||||||
# ── Feature registry tests ────────────────────────────────────────
|
# ── Feature registry tests ────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
@@ -1113,6 +1137,7 @@ class NixPatchContractTests(unittest.TestCase):
|
|||||||
"patchedAlbyHub = pkgs.albyhub.overrideAttrs (old: { patches = (old.patches or []) ++ [ "
|
"patchedAlbyHub = pkgs.albyhub.overrideAttrs (old: { patches = (old.patches or []) ++ [ "
|
||||||
"./packages/albyhub/0001-private-route-hints.patch "
|
"./packages/albyhub/0001-private-route-hints.patch "
|
||||||
"./packages/albyhub/0002-isolated-invoice-app-id.patch "
|
"./packages/albyhub/0002-isolated-invoice-app-id.patch "
|
||||||
|
"./packages/albyhub/0003-loopback-bind-host.patch "
|
||||||
"]; }); "
|
"]; }); "
|
||||||
f"in {result_expr}"
|
f"in {result_expr}"
|
||||||
)
|
)
|
||||||
@@ -1124,10 +1149,13 @@ class NixPatchContractTests(unittest.TestCase):
|
|||||||
self.assertIn("pkgs.albyhub.overrideAttrs", text)
|
self.assertIn("pkgs.albyhub.overrideAttrs", text)
|
||||||
self.assertIn("../packages/albyhub/0001-private-route-hints.patch", text)
|
self.assertIn("../packages/albyhub/0001-private-route-hints.patch", text)
|
||||||
self.assertIn("../packages/albyhub/0002-isolated-invoice-app-id.patch", text)
|
self.assertIn("../packages/albyhub/0002-isolated-invoice-app-id.patch", text)
|
||||||
|
self.assertIn("../packages/albyhub/0003-loopback-bind-host.patch", text)
|
||||||
self.assertNotIn("sha256-AAAA", text)
|
self.assertNotIn("sha256-AAAA", text)
|
||||||
self.assertNotIn("lib.fakeHash", text)
|
self.assertNotIn("lib.fakeHash", text)
|
||||||
self.assertIn("AUTO_UNLOCK_PASSWORD", text)
|
self.assertIn("AUTO_UNLOCK_PASSWORD", text)
|
||||||
self.assertNotIn("AUTO_UNLOCK_PASSWORD_FILE", text)
|
self.assertNotIn("AUTO_UNLOCK_PASSWORD_FILE", text)
|
||||||
|
self.assertIn("albyHubPort = 18080;", text)
|
||||||
|
self.assertIn('albyHubApiBase = "http://127.0.0.1:${toString albyHubPort}";', text)
|
||||||
|
|
||||||
def test_nwc_module_uses_lib_getexe_for_albyhub_binary(self):
|
def test_nwc_module_uses_lib_getexe_for_albyhub_binary(self):
|
||||||
repo_root = Path(__file__).resolve().parents[2]
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
@@ -1215,6 +1243,43 @@ class NixPatchContractTests(unittest.TestCase):
|
|||||||
self.assertIn("MakeInvoice(ctx, amountMsat, description,", text)
|
self.assertIn("MakeInvoice(ctx, amountMsat, description,", text)
|
||||||
self.assertIn(", appId, nil, nil)", text)
|
self.assertIn(", appId, nil, nil)", text)
|
||||||
|
|
||||||
|
def test_loopback_bind_host_patch_contains_required_changes(self):
|
||||||
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
|
patch_path = repo_root / "packages" / "albyhub" / "0003-loopback-bind-host.patch"
|
||||||
|
text = patch_path.read_text()
|
||||||
|
self.assertIn("diff --git a/cmd/http/main.go b/cmd/http/main.go", text)
|
||||||
|
self.assertIn("diff --git a/config/models.go b/config/models.go", text)
|
||||||
|
self.assertIn('Host string `envconfig:"HOST" default:"127.0.0.1"`', text)
|
||||||
|
self.assertIn("bindAddress := net.JoinHostPort", text)
|
||||||
|
self.assertIn("if err := e.Start(bindAddress);", text)
|
||||||
|
|
||||||
|
def test_nwc_services_share_authoritative_alby_hub_api_base(self):
|
||||||
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
|
module_path = repo_root / "modules" / "nwc-wallets.nix"
|
||||||
|
text = module_path.read_text()
|
||||||
|
self.assertIn("environment.NWC_ALBY_HUB_API_BASE = albyHubApiBase;", text)
|
||||||
|
self.assertIn("NWC_ALBY_HUB_API_BASE = albyHubApiBase;", text)
|
||||||
|
|
||||||
|
def test_albyhub_port_contract_and_assertions(self):
|
||||||
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
|
module_path = repo_root / "modules" / "nwc-wallets.nix"
|
||||||
|
text = module_path.read_text()
|
||||||
|
self.assertIn("PORT = toString albyHubPort;", text)
|
||||||
|
self.assertNotIn('PORT = "8080";', text)
|
||||||
|
self.assertIn("assertion = albyHubPort != config.services.lnd.restPort;", text)
|
||||||
|
self.assertIn("assertion = albyHubPort != 8181;", text)
|
||||||
|
self.assertIn("assertion = !(lib.elem albyHubPort config.networking.firewall.allowedTCPPorts);", text)
|
||||||
|
|
||||||
|
def test_recovery_cli_uses_manager_default_endpoint(self):
|
||||||
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
|
cli_path = repo_root / "app" / "sovran_systemsos_web" / "nwc_wallet_cli.py"
|
||||||
|
manager_path = repo_root / "app" / "sovran_systemsos_web" / "nwc_hub_manager.py"
|
||||||
|
cli_text = cli_path.read_text()
|
||||||
|
manager_text = manager_path.read_text()
|
||||||
|
self.assertIn("get_manager()", cli_text)
|
||||||
|
self.assertNotIn("127.0.0.1:8080", cli_text)
|
||||||
|
self.assertIn('"http://127.0.0.1:18080"', manager_text)
|
||||||
|
|
||||||
def test_nwc_lnurl_service_runs_as_albyhub(self):
|
def test_nwc_lnurl_service_runs_as_albyhub(self):
|
||||||
"""nwc-lnurl.service must run as albyhub to read /var/lib/albyhub/unlock-password."""
|
"""nwc-lnurl.service must run as albyhub to read /var/lib/albyhub/unlock-password."""
|
||||||
repo_root = Path(__file__).resolve().parents[2]
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Guardrails:
|
|||||||
|
|
||||||
```
|
```
|
||||||
Authenticated Hub management API
|
Authenticated Hub management API
|
||||||
-> local Alby Hub (port 8080, loopback only)
|
-> local Alby Hub (port 18080, loopback only)
|
||||||
-> local LND
|
-> local LND
|
||||||
|
|
||||||
Public Lightning Address
|
Public Lightning Address
|
||||||
@@ -48,7 +48,7 @@ Public Lightning Address
|
|||||||
|
|
||||||
Security invariants:
|
Security invariants:
|
||||||
|
|
||||||
- Alby Hub management port (8080) is never opened to the public firewall.
|
- Alby Hub management port (18080) is never opened to the public firewall.
|
||||||
- Dedicated LNURL service port (8181) is never opened to the public firewall.
|
- Dedicated LNURL service port (8181) is never opened to the public firewall.
|
||||||
- LNURL callback and discovery are exposed only through Caddy on 80/443.
|
- LNURL callback and discovery are exposed only through Caddy on 80/443.
|
||||||
- Management APIs are authenticated and remain under `/api/nwc/`.
|
- Management APIs are authenticated and remain under `/api/nwc/`.
|
||||||
@@ -68,10 +68,11 @@ Security invariants:
|
|||||||
|
|
||||||
## Alby Hub package and patches
|
## Alby Hub package and patches
|
||||||
|
|
||||||
Wallet Connections uses `pkgs.albyhub` from the repository's pinned `nixpkgs` input and applies two conventional patches via `overrideAttrs`:
|
Wallet Connections uses `pkgs.albyhub` from the repository's pinned `nixpkgs` input and applies three conventional patches via `overrideAttrs`:
|
||||||
|
|
||||||
1. **Private route hints** (`packages/albyhub/0001-private-route-hints.patch`): changes regular LND invoice creation from `Private: !hasPublicChannels` to `Private: true` and leaves hold-invoice logic unchanged.
|
1. **Private route hints** (`packages/albyhub/0001-private-route-hints.patch`): changes regular LND invoice creation from `Private: !hasPublicChannels` to `Private: true` and leaves hold-invoice logic unchanged.
|
||||||
2. **Invoice app attribution** (`packages/albyhub/0002-isolated-invoice-app-id.patch`): updates `api/models.go`, `api/transactions.go`, `http/http_service.go`, and `wails/wails_handlers.go` so invoice creation accepts and forwards optional `appId`.
|
2. **Invoice app attribution** (`packages/albyhub/0002-isolated-invoice-app-id.patch`): updates `api/models.go`, `api/transactions.go`, `http/http_service.go`, and `wails/wails_handlers.go` so invoice creation accepts and forwards optional `appId`.
|
||||||
|
3. **Loopback bind host** (`packages/albyhub/0003-loopback-bind-host.patch`): adds `HOST` to config and binds Echo to `HOST:PORT` instead of `:PORT`.
|
||||||
|
|
||||||
No placeholder source/vendor hashes are used in the Wallet Connections module.
|
No placeholder source/vendor hashes are used in the Wallet Connections module.
|
||||||
|
|
||||||
|
|||||||
@@ -57,5 +57,14 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nixosTests.nwc-wallets-port-collision =
|
||||||
|
import ./nix/tests/nwc-wallets-port-collision.nix {
|
||||||
|
inherit nixpkgs;
|
||||||
|
system = "x86_64-linux";
|
||||||
|
};
|
||||||
|
|
||||||
|
checks.x86_64-linux.nwc-wallets-port-collision =
|
||||||
|
self.nixosTests.nwc-wallets-port-collision;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ EOF
|
|||||||
$LIGHTNING {
|
$LIGHTNING {
|
||||||
# LNURL discovery and callback are served by the dedicated
|
# LNURL discovery and callback are served by the dedicated
|
||||||
# nwc-lnurl service on loopback port 8181. Only these paths
|
# nwc-lnurl service on loopback port 8181. Only these paths
|
||||||
# are proxied; the Alby Hub management port (8080) is never exposed.
|
# are proxied; the Alby Hub management port (18080) is never exposed.
|
||||||
reverse_proxy /.well-known/lnurlp/* http://127.0.0.1:8181
|
reverse_proxy /.well-known/lnurlp/* http://127.0.0.1:8181
|
||||||
reverse_proxy /lnurlp/* http://127.0.0.1:8181
|
reverse_proxy /lnurlp/* http://127.0.0.1:8181
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-1
@@ -1,10 +1,13 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
albyHubPort = 18080;
|
||||||
|
albyHubApiBase = "http://127.0.0.1:${toString albyHubPort}";
|
||||||
patchedAlbyHub = pkgs.albyhub.overrideAttrs (old: {
|
patchedAlbyHub = pkgs.albyhub.overrideAttrs (old: {
|
||||||
patches = (old.patches or []) ++ [
|
patches = (old.patches or []) ++ [
|
||||||
../packages/albyhub/0001-private-route-hints.patch
|
../packages/albyhub/0001-private-route-hints.patch
|
||||||
../packages/albyhub/0002-isolated-invoice-app-id.patch
|
../packages/albyhub/0002-isolated-invoice-app-id.patch
|
||||||
|
../packages/albyhub/0003-loopback-bind-host.patch
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,6 +36,18 @@ lib.mkIf config.sovran_systemsOS.features."nwc-wallets" {
|
|||||||
assertion = !(lib.attrByPath [ "nix-bitcoin" "netns-isolation" "enable" ] false config);
|
assertion = !(lib.attrByPath [ "nix-bitcoin" "netns-isolation" "enable" ] false config);
|
||||||
message = "Wallet Connections requires nix-bitcoin.netns-isolation.enable = false.";
|
message = "Wallet Connections requires nix-bitcoin.netns-isolation.enable = false.";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
assertion = albyHubPort != config.services.lnd.restPort;
|
||||||
|
message = "Alby Hub and LND REST must use different ports.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = albyHubPort != 8181;
|
||||||
|
message = "Alby Hub and the public LNURL service must use different ports.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !(lib.elem albyHubPort config.networking.firewall.allowedTCPPorts);
|
||||||
|
message = "Alby Hub management port must not be opened on the public TCP firewall.";
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
users.groups.albyhub = { };
|
users.groups.albyhub = { };
|
||||||
@@ -79,7 +94,7 @@ lib.mkIf config.sovran_systemsOS.features."nwc-wallets" {
|
|||||||
LND_MACAROON_FILE = "/run/lnd/albyhub.macaroon";
|
LND_MACAROON_FILE = "/run/lnd/albyhub.macaroon";
|
||||||
WORK_DIR = "/var/lib/albyhub";
|
WORK_DIR = "/var/lib/albyhub";
|
||||||
DATABASE_URI = "/var/lib/albyhub/nwc.db";
|
DATABASE_URI = "/var/lib/albyhub/nwc.db";
|
||||||
PORT = "8080";
|
PORT = toString albyHubPort;
|
||||||
RELAY = "wss://relay.getalby.com,wss://relay2.getalby.com";
|
RELAY = "wss://relay.getalby.com,wss://relay2.getalby.com";
|
||||||
AUTO_LINK_ALBY_ACCOUNT = "false";
|
AUTO_LINK_ALBY_ACCOUNT = "false";
|
||||||
SEND_EVENTS_TO_ALBY = "false";
|
SEND_EVENTS_TO_ALBY = "false";
|
||||||
@@ -110,6 +125,7 @@ lib.mkIf config.sovran_systemsOS.features."nwc-wallets" {
|
|||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "albyhub.service" "sovran-hub-web.service" ];
|
after = [ "albyhub.service" "sovran-hub-web.service" ];
|
||||||
wants = [ "albyhub.service" ];
|
wants = [ "albyhub.service" ];
|
||||||
|
environment.NWC_ALBY_HUB_API_BASE = albyHubApiBase;
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
@@ -131,6 +147,7 @@ lib.mkIf config.sovran_systemsOS.features."nwc-wallets" {
|
|||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.sovran-hub-web.environment = {
|
systemd.services.sovran-hub-web.environment = {
|
||||||
|
NWC_ALBY_HUB_API_BASE = albyHubApiBase;
|
||||||
NWC_LND_ADDRESS = "${lndRpcAddress}:${lndRpcPort}";
|
NWC_LND_ADDRESS = "${lndRpcAddress}:${lndRpcPort}";
|
||||||
NWC_LND_CERT_FILE = lndCertPath;
|
NWC_LND_CERT_FILE = lndCertPath;
|
||||||
NWC_LND_MACAROON_FILE = "/run/lnd/albyhub.macaroon";
|
NWC_LND_MACAROON_FILE = "/run/lnd/albyhub.macaroon";
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
{ nixpkgs, system ? "x86_64-linux" }:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in
|
||||||
|
pkgs.testers.runNixOSTest {
|
||||||
|
name = "nwc-wallets-port-collision";
|
||||||
|
|
||||||
|
nodes.machine = { lib, pkgs, ... }: {
|
||||||
|
imports = [ ../../modules/nwc-wallets.nix ];
|
||||||
|
|
||||||
|
options = {
|
||||||
|
sovran_systemsOS.features = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.bool;
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
sovran_systemsOS.domainRequirements = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.attrs;
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
services.sovranHub.webPackage = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
};
|
||||||
|
services.lnd = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
rpcAddress = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
};
|
||||||
|
rpcPort = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 10009;
|
||||||
|
};
|
||||||
|
restPort = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 8080;
|
||||||
|
};
|
||||||
|
certPath = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/lnd/tls.cert";
|
||||||
|
};
|
||||||
|
macaroons = lib.mkOption {
|
||||||
|
type = lib.types.attrs;
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
system.stateVersion = "24.11";
|
||||||
|
networking.firewall.enable = true;
|
||||||
|
|
||||||
|
sovran_systemsOS.features."nwc-wallets" = true;
|
||||||
|
services.lnd.enable = true;
|
||||||
|
services.lnd.restPort = 8080;
|
||||||
|
|
||||||
|
services.sovranHub.webPackage = pkgs.writeShellApplication {
|
||||||
|
name = "stub-sovran-hub-web";
|
||||||
|
runtimeInputs = [ pkgs.python3 ];
|
||||||
|
text = ''
|
||||||
|
if [ "$(basename "$0")" = "nwc-lnurl" ]; then
|
||||||
|
exec ${pkgs.python3}/bin/python -m http.server "''${NWC_LNURL_PORT:-8181}" --bind 127.0.0.1
|
||||||
|
fi
|
||||||
|
exec ${pkgs.coreutils}/bin/sleep infinity
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.sovran-hub-web = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${pkgs.coreutils}/bin/sleep infinity";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.lnd-prepare = {
|
||||||
|
description = "Prepare mock LND cert and macaroon files";
|
||||||
|
before = [ "lnd.service" "albyhub.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
script = ''
|
||||||
|
mkdir -p /var/lib/lnd /run/lnd /var/lib/domains
|
||||||
|
printf 'stub-cert\n' > /var/lib/lnd/tls.cert
|
||||||
|
printf 'stub-macaroon\n' > /run/lnd/albyhub.macaroon
|
||||||
|
printf 'lightning.example.com\n' > /var/lib/domains/lightning
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.lnd = {
|
||||||
|
description = "Mock LND REST listener";
|
||||||
|
after = [ "network.target" "lnd-prepare.service" ];
|
||||||
|
requires = [ "lnd-prepare.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${pkgs.python3}/bin/python -m http.server 8080 --bind 127.0.0.1";
|
||||||
|
Restart = "always";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
machine.wait_for_unit("lnd.service")
|
||||||
|
machine.wait_for_unit("sovran-hub-web.service")
|
||||||
|
machine.wait_for_unit("albyhub.service")
|
||||||
|
machine.wait_for_unit("nwc-lnurl.service")
|
||||||
|
|
||||||
|
machine.wait_for_open_port(8080)
|
||||||
|
machine.wait_for_open_port(18080)
|
||||||
|
machine.wait_for_open_port(8181)
|
||||||
|
|
||||||
|
machine.succeed("ss -ltn '( sport = :8080 )' | grep -F '127.0.0.1:8080'")
|
||||||
|
machine.succeed("ss -ltn '( sport = :18080 )' | grep -F '127.0.0.1:18080'")
|
||||||
|
machine.succeed("ss -ltn '( sport = :8181 )' | grep -F '127.0.0.1:8181'")
|
||||||
|
|
||||||
|
machine.fail("ss -ltn '( sport = :18080 )' | grep -E '0\\.0\\.0\\.0:18080|\\[::\\]:18080'")
|
||||||
|
machine.fail("ss -ltn '( sport = :8181 )' | grep -E '0\\.0\\.0\\.0:8181|\\[::\\]:8181'")
|
||||||
|
|
||||||
|
machine.succeed("${pkgs.curl}/bin/curl --fail --silent http://127.0.0.1:18080/api/info | ${pkgs.gnugrep}/bin/grep -q 'setupCompleted'")
|
||||||
|
|
||||||
|
machine.succeed("systemctl show -p Environment nwc-lnurl.service | grep -q 'NWC_ALBY_HUB_API_BASE=http://127.0.0.1:18080'")
|
||||||
|
machine.succeed("systemctl show -p Environment sovran-hub-web.service | grep -q 'NWC_ALBY_HUB_API_BASE=http://127.0.0.1:18080'")
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
diff --git a/cmd/http/main.go b/cmd/http/main.go
|
||||||
|
index ed31f96..4d31212 100644
|
||||||
|
--- a/cmd/http/main.go
|
||||||
|
+++ b/cmd/http/main.go
|
||||||
|
@@ -2,8 +2,8 @@ package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
- "fmt"
|
||||||
|
nethttp "net/http"
|
||||||
|
+ "net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
@@ -51,8 +51,9 @@ func main() {
|
||||||
|
httpSvc := http.NewHttpService(svc, svc.GetEventPublisher())
|
||||||
|
httpSvc.RegisterSharedRoutes(e)
|
||||||
|
//start Echo server
|
||||||
|
+ bindAddress := net.JoinHostPort(svc.GetConfig().GetEnv().Host, svc.GetConfig().GetEnv().Port)
|
||||||
|
go func() {
|
||||||
|
- if err := e.Start(fmt.Sprintf(":%v", svc.GetConfig().GetEnv().Port)); err != nil && err != nethttp.ErrServerClosed {
|
||||||
|
+ if err := e.Start(bindAddress); err != nil && err != nethttp.ErrServerClosed {
|
||||||
|
logger.Logger.WithError(err).Error("echo server failed to start")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
diff --git a/config/models.go b/config/models.go
|
||||||
|
index 0fb8870..dc11afd 100644
|
||||||
|
--- a/config/models.go
|
||||||
|
+++ b/config/models.go
|
||||||
|
@@ -24,6 +24,7 @@ type AppConfig struct {
|
||||||
|
LNDAddress string `envconfig:"LND_ADDRESS"`
|
||||||
|
LNDCertFile string `envconfig:"LND_CERT_FILE"`
|
||||||
|
LNDMacaroonFile string `envconfig:"LND_MACAROON_FILE"`
|
||||||
|
+ Host string `envconfig:"HOST" default:"127.0.0.1"`
|
||||||
|
Workdir string `envconfig:"WORK_DIR"`
|
||||||
|
Port string `envconfig:"PORT" default:"8080"`
|
||||||
|
DatabaseUri string `envconfig:"DATABASE_URI" default:"nwc.db"`
|
||||||
Reference in New Issue
Block a user