Compare commits

...

10 commits

Author SHA1 Message Date
800afe647f examples: add neutrino backend example for lnd
Some checks are pending
nix-bitcoin tests / build_test_drivers (push) Waiting to run
nix-bitcoin tests / test_scenario (default) (push) Blocked by required conditions
nix-bitcoin tests / test_scenario (joinmarket-bitcoind-29) (push) Blocked by required conditions
nix-bitcoin tests / test_scenario (netns) (push) Blocked by required conditions
nix-bitcoin tests / test_scenario (netnsRegtest) (push) Blocked by required conditions
nix-bitcoin tests / check_flake (push) Waiting to run
Show users how to configure lnd with the neutrino backend instead of
bitcoind. This is useful for resource-constrained systems that can't
run a full Bitcoin node, but provides less privacy and security than
a local bitcoind.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 17:38:21 +01:00
a019f79283 lnd: add neutrino.maxPeers option
Allow configuring the maximum number of inbound and outbound peers
for neutrino. Default is 8, matching lnd's default.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 17:38:02 +01:00
7d06fed28c lnd: rename neutrino.addpeers to neutrino.peers
Use simpler naming for the NixOS option. The option defines which
peers to connect to, not the action of adding them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 17:19:56 +01:00
ab188f03f8 lnd: use optionals instead of optional for list additions
Use optionals with explicit list syntax for requires, after, and
extraGroups. This makes it clearer that we're conditionally adding
elements to a list.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 17:13:26 +01:00
ebd13a2c87 rtl: change default port from 3000 to 3001
Avoid conflict with lamassu-server which also defaults to port 3000.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:04:37 +01:00
9d2884b6f7 lamassu: register module in modules.nix
Without this, the services.lamassu-server option doesn't exist.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:04:37 +01:00
4db5e80ac8 lamassu: document future peer authentication implementation
Upstream lamassu-server doesn't support DATABASE_URL, so peer auth
via Unix socket isn't currently possible. Document the required
upstream changes and module updates for future implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:04:37 +01:00
2b46736e69 Revert "lamassu: switch to peer authentication for PostgreSQL"
This reverts commit a819ec5ad2fef050fd878afde8243576ebaa7f88.
2025-12-24 16:04:37 +01:00
1bbd7d6bc5 lamassu: switch to peer authentication for PostgreSQL
Replace password-based TCP auth with Unix socket peer authentication:
- Remove lamassu-db-password secret (no password needed)
- Remove lamassu-postgres-setup service entirely
- Use DATABASE_URL with Unix socket: postgresql://user@/db?host=/run/postgresql
- Remove POSTGRES_HOST, POSTGRES_PORT, POSTGRES_PASSWORD env vars

This follows the same pattern as btcpayserver and simplifies the module
significantly. Peer auth uses OS-level user authentication via Unix socket.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:04:37 +01:00
27f133efd8 lamassu: use nix-bitcoin secrets for database password
Replace hardcoded database password with nix-bitcoin secrets pattern:
- Add lamassu-db-password secret (readable by lamassu user and postgres)
- Generate random 20-char password using makePasswordSecret
- Read password at runtime in service wrapper scripts
- Update lamassu-postgres-setup to read password from secrets
- Update helper scripts to read password at runtime

The password is now automatically generated on first deploy and stored
in ${secretsDir}/lamassu-db-password.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:04:37 +01:00
6 changed files with 220 additions and 32 deletions

View file

@ -0,0 +1,167 @@
# Lamassu Server: Future Peer Authentication Implementation
This document describes how to implement PostgreSQL peer authentication (Unix socket) for the lamassu-server module in the future.
## Current Limitation
The lamassu-server's `constants.js` builds the database URL from individual environment variables:
```javascript
const POSTGRES_USER = process.env.POSTGRES_USER
const POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD
const POSTGRES_HOST = process.env.POSTGRES_HOST
const POSTGRES_PORT = process.env.POSTGRES_PORT
const POSTGRES_DB = process.env.POSTGRES_DB
const PSQL_URL = `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`
```
This means:
- `DATABASE_URL` environment variable is **not supported**
- Unix socket connections are **not possible** (setting `POSTGRES_HOST=/run/postgresql` produces invalid URL syntax)
- Password authentication via TCP is **required**
## Required Upstream Changes
### 1. Modify `packages/server/lib/constants.js`
Add support for `DATABASE_URL` environment variable:
```javascript
const POSTGRES_USER = process.env.POSTGRES_USER
const POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD
const POSTGRES_HOST = process.env.POSTGRES_HOST
const POSTGRES_PORT = process.env.POSTGRES_PORT
const POSTGRES_DB = process.env.POSTGRES_DB
// Support DATABASE_URL for Unix socket connections (peer auth)
const PSQL_URL = process.env.DATABASE_URL ||
`postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`
```
### 2. Update nix-bitcoin module
Once upstream supports `DATABASE_URL`, update `modules/lamassu-lnbits.nix`:
#### Remove password secret
```nix
nix-bitcoin.secrets = {
lamassu-key.user = cfg.user;
lamassu-cert = {
user = cfg.user;
permissions = "444";
};
# Remove: lamassu-db-password
};
nix-bitcoin.generateSecretsCmds.lamassu = ''
makeCert lamassu '${nbLib.mkCertExtraAltNames cfg.certificate}'
# Remove: makePasswordSecret lamassu-db-password
'';
```
#### Remove lamassu-postgres-setup service
Delete the entire `systemd.services.lamassu-postgres-setup` block.
#### Simplify PostgreSQL config
```nix
services.postgresql = {
enable = true;
package = pkgs.postgresql_15;
ensureDatabases = [ cfg.database.name ];
ensureUsers = [
{
name = cfg.database.user;
ensureDBOwnership = true;
}
];
# No custom authentication needed - default peer auth works
};
```
#### Update service wrapper scripts
```nix
lamassuEnv = pkgs.writeShellScript "lamassu-env" ''
#!/bin/bash
set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH
# Use Unix socket for peer authentication (no password needed)
export DATABASE_URL="postgresql://${cfg.database.user}@/${cfg.database.name}?host=/run/postgresql"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules
cd ${cfg.package}
exec "$@"
'';
```
#### Remove password-related env vars
Remove from service `environment` blocks:
- `POSTGRES_HOST`
- `POSTGRES_PORT`
- `POSTGRES_PASSWORD`
## Benefits of Peer Authentication
1. **No password to manage** - No secrets generation, storage, or rotation
2. **More secure** - Authentication handled by OS kernel, not application
3. **Simpler module** - ~55 fewer lines of code
4. **Consistent with btcpayserver** - Same pattern used by other nix-bitcoin modules
## Architecture Comparison
### Current (password auth via TCP)
```
┌─────────────────────────────────────────────────────────────┐
│ lamassu-server │
│ └── connects to 127.0.0.1:5432 with password │
│ │ │
│ ▼ │
│ PostgreSQL (TCP) │
│ └── validates password via md5 auth │
└─────────────────────────────────────────────────────────────┘
Requires:
- lamassu-db-password secret
- lamassu-postgres-setup service
- POSTGRES_HOST, POSTGRES_PORT, POSTGRES_PASSWORD env vars
```
### Future (peer auth via Unix socket)
```
┌─────────────────────────────────────────────────────────────┐
│ lamassu-server (runs as user: lamassu-server) │
│ └── connects to /run/postgresql socket │
│ │ │
│ ▼ │
│ PostgreSQL (Unix socket) │
│ └── validates OS user matches DB user (peer auth) │
└─────────────────────────────────────────────────────────────┘
Requires:
- Nothing extra! Default NixOS PostgreSQL config works.
```
## DATABASE_URL Format for Unix Socket
The node-postgres library supports Unix sockets via the `host` query parameter:
```
postgresql://username@/database?host=/run/postgresql
```
Components:
- `username` - PostgreSQL user (must match OS user for peer auth)
- `database` - Database name
- `host=/run/postgresql` - Path to Unix socket directory
## Reference
- btcpayserver module uses peer auth: `postgres=User ID=btcpayserver;Host=/run/postgresql;Database=btcpaydb`
- node-postgres Unix socket docs: https://node-postgres.com/features/connecting
- pg-promise (used by lamassu): https://github.com/vitaly-t/pg-promise

View file

@ -80,6 +80,15 @@
# Set this to enable lnd, a lightning implementation written in Go.
# services.lnd.enable = true;
#
# By default, lnd uses bitcoind as its backend. You can use neutrino instead
# to run lnd without a full Bitcoin node. This is useful for resource-constrained
# systems, but provides less privacy and security than a local bitcoind.
# services.lnd = {
# enable = true;
# backend = "neutrino";
# neutrino.peers = [ "btcd.example.com:8333" ];
# };
#
# NOTE: In order to avoid collisions with clightning you must disable clightning or
# change the services.clightning.port or services.lnd.port to a port other than
# 9735.

View file

@ -104,11 +104,8 @@ in
description = "PostgreSQL username";
};
password = mkOption {
type = types.str;
default = "lamassu123";
description = "PostgreSQL password for lamassu-server user";
};
# Password is managed by nix-bitcoin secrets system.
# See: ${secretsDir}/lamassu-db-password
};
hostname = mkOption {
@ -187,10 +184,15 @@ in
user = cfg.user;
permissions = "444"; # World readable (it's a public cert)
};
lamassu-db-password = {
user = cfg.user;
group = "postgres"; # PostgreSQL needs to read this too
};
};
nix-bitcoin.generateSecretsCmds.lamassu = ''
makeCert lamassu '${nbLib.mkCertExtraAltNames cfg.certificate}'
makePasswordSecret lamassu-db-password
'';
# ═══════════════════════════════════════════════════════════════════════════
@ -216,10 +218,6 @@ in
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
'';
# Set initial password for lamassu-server user
initialScript = pkgs.writeText "postgres-init.sql" ''
ALTER USER "${cfg.database.user}" WITH PASSWORD '${cfg.database.password}';
'';
};
# Create system users and groups
@ -249,11 +247,11 @@ in
"Z '${cfg.package}' 0755 ${cfg.user} ${cfg.group} - -"
];
# Service to set PostgreSQL password
# Service to set PostgreSQL password from nix-bitcoin secrets
systemd.services.lamassu-postgres-setup = {
description = "Setup PostgreSQL password for lamassu-server";
wantedBy = [ "multi-user.target" ];
after = [ "postgresql.service" ];
after = [ "postgresql.service" "nix-bitcoin-secrets.target" ];
wants = [ "postgresql.service" ];
serviceConfig = {
Type = "oneshot";
@ -261,11 +259,12 @@ in
User = "postgres";
};
script = ''
# Wait for user to exist, then set password
# Wait for user to exist, then set password from secrets
for i in {1..30}; do
if ${pkgs.postgresql}/bin/psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${cfg.database.user}'" | grep -q 1; then
echo "Setting password for ${cfg.database.user}..."
${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '${cfg.database.password}';"
password=$(cat ${secretsDir}/lamassu-db-password)
${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '$password';"
exit 0
fi
echo "Waiting for user ${cfg.database.user} to be created (attempt $i/30)..."
@ -286,12 +285,11 @@ in
environment = {
NODE_ENV = "production";
# Database configuration (using TCP with password auth)
# Database configuration (password read at runtime from secrets)
POSTGRES_HOST = "127.0.0.1";
POSTGRES_PORT = "5432";
POSTGRES_DB = cfg.database.name;
POSTGRES_USER = cfg.database.user;
POSTGRES_PASSWORD = cfg.database.password;
# Server configuration
SERVER_PORT = toString cfg.serverPort;
@ -329,8 +327,10 @@ in
#!/bin/bash
set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH
# Use TCP connection to localhost with password
export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
# Read database password from nix-bitcoin secrets
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password)
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}"
export POSTGRES_PASSWORD="$DB_PASSWORD"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules
cd ${cfg.package}
exec "$@"
@ -391,12 +391,11 @@ in
CA_PATH = cfg.certPath;
CERT_PATH = cfg.certPath;
KEY_PATH = cfg.keyPath;
# Database configuration (using TCP with password auth)
# Database configuration (password read at runtime from secrets)
POSTGRES_HOST = "127.0.0.1";
POSTGRES_PORT = "5432";
POSTGRES_DB = cfg.database.name;
POSTGRES_USER = cfg.database.user;
POSTGRES_PASSWORD = cfg.database.password;
MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic";
SKIP_2FA = if cfg.skip2FA then "true" else "false";
# Data directories
@ -411,8 +410,10 @@ in
#!/bin/bash
set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH
# Use TCP connection to localhost with password
export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
# Read database password from nix-bitcoin secrets
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password)
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}"
export POSTGRES_PASSWORD="$DB_PASSWORD"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/admin-server/node_modules
cd ${cfg.package}
exec "$@"
@ -444,14 +445,16 @@ in
nodePackages.pnpm
postgresql
(writeShellScriptBin "lamassu-register-user" ''
# Read database password from nix-bitcoin secrets
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password)
export NODE_PATH="${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules"
export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}"
export HOSTNAME="${cfg.hostname}"
export POSTGRES_HOST="127.0.0.1"
export POSTGRES_PORT="5432"
export POSTGRES_DB="${cfg.database.name}"
export POSTGRES_USER="${cfg.database.user}"
export POSTGRES_PASSWORD="${cfg.database.password}"
export POSTGRES_PASSWORD="$DB_PASSWORD"
export SKIP_2FA="${if cfg.skip2FA then "true" else "false"}"
sudo -E -u ${cfg.user} bash -c "cd ${cfg.package}/packages/server && ${pkgs.nodejs_22}/bin/node bin/lamassu-register \"\$@\"" -- "$@"

View file

@ -150,12 +150,12 @@ let
description = "The backend to use for fetching blockchain data.";
};
neutrino = {
addpeers = mkOption {
peers = mkOption {
type = types.listOf types.str;
default = [];
example = [ "192.168.1.1:8333" "btcd.example.com:8333" ];
description = ''
List of Bitcoin full node peers to connect to via neutrino.addpeer.
List of Bitcoin full node peers to connect to for neutrino.
Multiple peers provide redundancy for maximum uptime.
'';
};
@ -167,6 +167,13 @@ let
Required because neutrino doesn't have access to mempool data.
'';
};
maxPeers = mkOption {
type = types.int;
default = 8;
description = ''
Maximum number of inbound and outbound peers for neutrino.
'';
};
};
};
@ -206,7 +213,8 @@ let
bitcoind.zmqpubrawtx=${zmqHandleSpecialAddress bitcoind.zmqpubrawtx}
'' else ''
bitcoin.node=neutrino
${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.addpeers}
${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.peers}
neutrino.maxpeers=${toString cfg.neutrino.maxPeers}
fee.url=${cfg.neutrino.feeUrl}
''}
@ -232,10 +240,10 @@ in {
services.lnd.port to a port other than 9735.
'';
}
{ assertion = cfg.backend != "neutrino" || cfg.neutrino.addpeers != [];
{ assertion = cfg.backend != "neutrino" || cfg.neutrino.peers != [];
message = ''
When using neutrino backend, you must configure at least one peer
in services.lnd.neutrino.addpeers.
in services.lnd.neutrino.peers.
'';
}
];
@ -261,8 +269,8 @@ in {
systemd.services.lnd = {
wantedBy = [ "multi-user.target" ];
requires = optional (cfg.backend == "bitcoind") "bitcoind.service";
after = optional (cfg.backend == "bitcoind") "bitcoind.service" ++ [ "nix-bitcoin-secrets.target" ];
requires = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ];
after = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ] ++ [ "nix-bitcoin-secrets.target" ];
preStart = ''
install -m600 ${configFile} '${cfg.dataDir}/lnd.conf'
${optionalString (cfg.backend == "bitcoind") ''
@ -324,7 +332,7 @@ in {
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
extraGroups = optional (cfg.backend == "bitcoind") "bitcoinrpc-public";
extraGroups = optionals (cfg.backend == "bitcoind") [ "bitcoinrpc-public" ];
home = cfg.dataDir; # lnd creates .lnd dir in HOME
};
users.groups.${cfg.group} = {};

View file

@ -28,6 +28,7 @@
./joinmarket.nix
./joinmarket-ob-watcher.nix
./hardware-wallets.nix
./lamassu-lnbits.nix
# Support features
./versioning.nix

View file

@ -11,7 +11,7 @@ let
};
port = mkOption {
type = types.port;
default = 3000;
default = 3001;
description = "HTTP server port.";
};
dataDir = mkOption {