diff --git a/docs/lamassu-future-nginx.md b/docs/lamassu-future-nginx.md new file mode 100644 index 0000000..774fc80 --- /dev/null +++ b/docs/lamassu-future-nginx.md @@ -0,0 +1,156 @@ +# Lamassu Server: Future Nginx Implementation + +This document describes how to implement nginx reverse proxy support for the lamassu-server module in the future. + +## Current Limitation + +The lamassu-admin-server has **hardcoded ports** in upstream: +- Production mode (default): port 443 +- Dev mode (`--dev` flag): port 8070 + +This means nginx cannot be used as a reverse proxy on port 443 because the admin UI would already be bound to it. + +## Required Upstream Changes + +### 1. Add `--ui-port` flag to admin-server.js + +Modify `packages/server/lib/new-admin/admin-server.js`: + +```javascript +// Change from: +const devMode = require('minimist')(process.argv.slice(2)).dev + +// To: +const argv = require('minimist')(process.argv.slice(2)) +const devMode = argv.dev +const UI_PORT = argv['ui-port'] || process.env.UI_PORT + +// And in run(): +async function run() { + const app = await loadRoutes() + const defaultPort = devMode ? 8070 : 443 + const serverPort = UI_PORT ? parseInt(UI_PORT, 10) : defaultPort + // ... +} +``` + +This allows configuring the admin UI port via: +- `--ui-port 8443` command line flag +- `UI_PORT=8443` environment variable + +### 2. Update nix-bitcoin module + +Once upstream supports `--ui-port`, update `modules/lamassu-lnbits.nix`: + +#### Re-enable options + +```nix +# Uncomment and update: +adminPort = mkOption { + type = types.port; + default = 8443; # Internal port when nginx is used + description = "Port for the lamassu admin UI (internal, behind nginx)."; +}; + +nginx = { + enable = mkEnableOption "Nginx reverse proxy on port 443"; + hostname = mkOption { + type = types.nullOr types.str; + default = null; + description = "Hostname for nginx virtual host"; + }; +}; +``` + +#### Add nginx configuration + +```nix +services.nginx = mkIf cfg.nginx.enable { + enable = true; + recommendedTlsSettings = true; + recommendedProxySettings = true; + + virtualHosts.${cfg.nginx.hostname or cfg.hostname} = { + forceSSL = true; + sslCertificate = cfg.certPath; + sslCertificateKey = cfg.keyPath; + + # Route API endpoints to main server + locations."/ca".proxyPass = "https://127.0.0.1:${toString cfg.serverPort}"; + locations."/pair".proxyPass = "https://127.0.0.1:${toString cfg.serverPort}"; + + # Route everything else to admin UI + locations."/".proxyPass = "https://127.0.0.1:${toString cfg.adminPort}"; + }; +}; +``` + +#### Update admin server service + +```nix +ExecStart = "${lamassuAdminEnv} ... lamassu-admin-server --ui-port ${toString cfg.adminPort} ..."; + +# Only need CAP_NET_BIND_SERVICE if adminPort < 1024 and nginx is disabled +AmbientCapabilities = optionals (cfg.adminPort < 1024 && !cfg.nginx.enable) [ "CAP_NET_BIND_SERVICE" ]; +``` + +#### Update firewall + +```nix +networking.firewall.allowedTCPPorts = + [ cfg.serverPort ] ++ + (optional cfg.nginx.enable 443) ++ + (optional (!cfg.nginx.enable && cfg.adminPort < 1024) cfg.adminPort); +``` + +## Architecture + +### Current (no nginx support) + +``` + ┌─────────────────────────────┐ + :3000 ─────────►│ lamassu-server (API) │ + └─────────────────────────────┘ + ┌─────────────────────────────┐ + :443 ──────────►│ lamassu-admin-server (UI) │ + └─────────────────────────────┘ +``` + +### Future (with nginx) + +``` + ┌─────────────────────────────┐ + :3000 ─────────►│ lamassu-server (API) │ + └─────────────────────────────┘ + + ┌─────────────────────────────┐ + :443 ──────────►│ nginx │ + │ ├─ /ca, /pair ──► :3000 │ + │ └─ / ──────────► :8443 │ + └─────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────┐ + :8443 ◄────────│ lamassu-admin-server (UI) │ + └─────────────────────────────┘ +``` + +## Benefits of Nginx + +1. **Single port (443)** for all HTTPS traffic +2. **Better TLS handling** with nginx's recommended settings +3. **Consistent with other nix-bitcoin services** (BTCPayServer, mempool, etc.) +4. **Future extensibility** for rate limiting, caching, etc. + +## Port Conflict Note + +If both nginx and lamassu-admin-server try to bind to port 443, systemd will fail to start the services. The assertion below should be added to prevent this: + +```nix +assertions = [ + { + assertion = !(cfg.nginx.enable && cfg.adminPort == 443); + message = "Cannot use nginx with adminPort 443. Set a different adminPort (e.g., 8443)."; + } +]; +``` diff --git a/modules/lamassu-lnbits.nix b/modules/lamassu-lnbits.nix index 03f9252..dd2de20 100644 --- a/modules/lamassu-lnbits.nix +++ b/modules/lamassu-lnbits.nix @@ -40,15 +40,12 @@ in description = "Port for the main lamassu server API"; }; - adminPort = mkOption { - type = types.port; - default = 8070; - description = '' - Port for the lamassu admin interface in dev mode. - Only used when devMode = true. In production mode, the admin - interface always runs on port 443 (hardcoded in upstream). - ''; - }; + # NOTE: Admin UI port is currently hardcoded in upstream lamassu-server: + # - Production mode (default): port 443 + # - Dev mode (--dev flag): port 8070 + # Future: Add --ui-port support to upstream to make this configurable. + # This would also enable nginx reverse proxy (which also needs port 443). + # See docs/lamassu-future-nginx.md for implementation details. dataDir = mkOption { type = types.path; @@ -86,11 +83,13 @@ in description = "Skip 2FA authentication (useful for initial setup)"; }; - devMode = mkOption { - type = types.bool; - default = false; - description = "Run admin server in development mode (port 8070). When false, runs in production mode (port 443)."; - }; + # NOTE: devMode is disabled for now. Admin UI runs in production mode (port 443). + # Future: Re-enable when --ui-port is added to upstream lamassu-server. + # devMode = mkOption { + # type = types.bool; + # default = false; + # description = "Run admin server in development mode (port 8070)."; + # }; database = { name = mkOption { @@ -151,16 +150,18 @@ in description = "Path to the TLS private key."; }; - nginx = { - enable = mkEnableOption "Nginx reverse proxy on port 443"; - - hostname = mkOption { - type = types.nullOr types.str; - default = null; - description = "Hostname for nginx virtual host (if different from main hostname)"; - example = "lamassu.example.com"; - }; - }; + # NOTE: nginx is disabled for now because admin UI binds directly to port 443. + # Enabling nginx would cause a port conflict. + # Future: Add --ui-port to upstream, run admin UI on internal port (e.g., 8070), + # and use nginx as reverse proxy on 443. See docs/lamassu-future-nginx.md + # nginx = { + # enable = mkEnableOption "Nginx reverse proxy on port 443"; + # hostname = mkOption { + # type = types.nullOr types.str; + # default = null; + # description = "Hostname for nginx virtual host"; + # }; + # }; enableBitcoin = mkOption { type = types.bool; @@ -188,56 +189,8 @@ in # ═══════════════════════════════════════════════════════════════════════════ - # Nginx reverse proxy (optional, disabled by default) - services.nginx = mkIf cfg.nginx.enable { - enable = true; - recommendedTlsSettings = true; - recommendedProxySettings = true; - - virtualHosts.${if cfg.nginx.hostname != null then cfg.nginx.hostname else cfg.hostname} = { - forceSSL = true; - sslCertificate = cfg.certPath; - sslCertificateKey = cfg.keyPath; - - # Route API endpoints to main server (port 3000) - locations."/ca" = { - proxyPass = "https://127.0.0.1:${toString cfg.serverPort}"; - extraConfig = '' - proxy_ssl_verify off; - proxy_ssl_server_name on; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - ''; - }; - - locations."/pair" = { - proxyPass = "https://127.0.0.1:${toString cfg.serverPort}"; - extraConfig = '' - proxy_ssl_verify off; - proxy_ssl_server_name on; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - ''; - }; - - # Route everything else to admin server (port 8070) - locations."/" = { - proxyPass = "https://127.0.0.1:${toString cfg.adminPort}"; - extraConfig = '' - proxy_ssl_verify off; - proxy_ssl_server_name on; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - ''; - }; - }; - }; + # NOTE: Nginx reverse proxy is disabled. See docs/lamassu-future-nginx.md + # for future implementation when --ui-port is added to upstream. # Enable PostgreSQL services.postgresql = { @@ -272,9 +225,6 @@ in home = cfg.dataDir; createHome = true; }; - } // optionalAttrs cfg.nginx.enable { - # Add nginx user to lamassu-server group to access SSL certificates - nginx.extraGroups = [ cfg.group ]; }; users.groups.${cfg.group} = {}; @@ -463,7 +413,7 @@ in ''; in defaultHardening // { WorkingDirectory = "${cfg.package}/packages/server"; - ExecStart = "${lamassuAdminEnv} ${pkgs.nodejs_22}/bin/node packages/server/bin/lamassu-admin-server ${optionalString cfg.devMode "--dev"} --logLevel ${cfg.logLevel}"; + ExecStart = "${lamassuAdminEnv} ${pkgs.nodejs_22}/bin/node packages/server/bin/lamassu-admin-server --logLevel ${cfg.logLevel}"; MemoryDenyWriteExecute = false; ReadWritePaths = [ cfg.dataDir cfg.package ]; User = cfg.user; @@ -471,20 +421,16 @@ in Restart = "on-failure"; RestartSec = "10s"; - # Allow binding to privileged port 443 when in production mode (not dev mode) - AmbientCapabilities = optionals (!cfg.devMode) [ "CAP_NET_BIND_SERVICE" ]; - CapabilityBoundingSet = optionals (!cfg.devMode) [ "CAP_NET_BIND_SERVICE" ]; + # Allow binding to privileged port 443 (production mode) + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; }; }; # Open firewall ports - # Port 3000 for machine API access (required for pairing and operation) - # Port 8070 for admin interface (only in dev mode) - # Port 443 for admin interface (production mode) or nginx reverse proxy - networking.firewall.allowedTCPPorts = - [ cfg.serverPort ] ++ - (optionals cfg.devMode [ cfg.adminPort ]) ++ - (optionals (cfg.nginx.enable || !cfg.devMode) [ 443 ]); + # Port 3000 (configurable): machine API access (required for pairing and operation) + # Port 443: admin UI (production mode, hardcoded in upstream) + networking.firewall.allowedTCPPorts = [ cfg.serverPort 443 ]; # Add useful packages environment.systemPackages = with pkgs; [ @@ -513,7 +459,7 @@ in echo "" echo "=== Network Access ===" echo "Server API: https://localhost:${toString cfg.serverPort}" - echo "Admin UI: https://localhost:${if cfg.devMode then toString cfg.adminPort else "443"}" + echo "Admin UI: https://localhost:443" '') ]; };