From 338570920844afc0c30a51a6477aad5e2ec095b6 Mon Sep 17 00:00:00 2001 From: Katharina Heidenreich Date: Sat, 4 Apr 2026 19:45:40 +0200 Subject: [PATCH] feat: add port offset handling for auto SSH devices --- config/network.nix | 1 + intermediate/remote.nix | 26 ++++++++++++++++++-------- validation/auto_ssh.nix | 16 +++++++++++++++- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/config/network.nix b/config/network.nix index 74688d2..5a7d46c 100644 --- a/config/network.nix +++ b/config/network.nix @@ -36,6 +36,7 @@ rec { sshUser = "root"; key = secrets.byName.autossh_remote_proxy_key.path; known_hosts = secrets.byName.autossh_remote_proxy_known_hosts.path; + portOffset = 10000; }; }; }; diff --git a/intermediate/remote.nix b/intermediate/remote.nix index 4cbd677..251cfb8 100644 --- a/intermediate/remote.nix +++ b/intermediate/remote.nix @@ -19,22 +19,31 @@ let getRemotePortMap = device: autoSshValidation.getRemotePortMap device; - resolveRemotePort = remotePortMap: endpoint: + getPortOffset = device: + autoSshValidation.getPortOffset device; + + ensurePortRange = context: port: + if builtins.isInt port && port >= 1 && port <= 65535 then + port + else + throw "${context} must be in range 1..65535."; + + resolveRemotePort = remotePortMap: portOffset: endpoint: let localPort = if endpoint ? port then - endpoint.port + ensurePortRange "Endpoint port '${toString endpoint.port}'" endpoint.port else throw "Endpoint is missing required field: port."; overrides = lib.filter (entry: entry.localPort == localPort) remotePortMap; in if overrides != [] then - (builtins.head overrides).remotePort + ensurePortRange "remotePortMap remotePort '${toString (builtins.head overrides).remotePort}'" (builtins.head overrides).remotePort else - localPort; + ensurePortRange "Computed remote port (local ${toString localPort} + offset ${toString portOffset})" (localPort + portOffset); - mapEndpointToForward = remotePortMap: endpoint: { - remote = resolveRemotePort remotePortMap endpoint; + mapEndpointToForward = remotePortMap: portOffset: endpoint: { + remote = resolveRemotePort remotePortMap portOffset endpoint; localAddress = "localhost"; localPort = endpoint.port; }; @@ -50,6 +59,7 @@ let else throw "Auto SSH device is missing required field: domain."; remotePortMap = getRemotePortMap device; + portOffset = getPortOffset device; matchedEndpoints = lib.filter (endpoint: if endpoint.force_ssl && endpoint.port == 80 then @@ -61,8 +71,8 @@ let ) endpoints; forwards = lib.concatMap (endpoint: let - baseForward = mapEndpointToForward remotePortMap endpoint; - httpRedirectForward = mapEndpointToForward remotePortMap (endpointForHttpRedirect endpoint); + baseForward = mapEndpointToForward remotePortMap portOffset endpoint; + httpRedirectForward = mapEndpointToForward remotePortMap portOffset (endpointForHttpRedirect endpoint); in if endpoint.force_ssl then [ baseForward httpRedirectForward ] diff --git a/validation/auto_ssh.nix b/validation/auto_ssh.nix index 0035ce3..14070ee 100644 --- a/validation/auto_ssh.nix +++ b/validation/auto_ssh.nix @@ -53,9 +53,23 @@ let else device.auto_ssh.remotePortMap; + getPortOffset = device: + if !(device ? auto_ssh) then + 0 + else if !builtins.isAttrs device.auto_ssh then + throw "Device auto_ssh must be an attrset when present." + else if !(device.auto_ssh ? portOffset) then + 0 + else if !builtins.isInt device.auto_ssh.portOffset then + throw "Device auto_ssh.portOffset must be an int." + else if device.auto_ssh.portOffset < 0 then + throw "Device auto_ssh.portOffset must be >= 0." + else + device.auto_ssh.portOffset; + isSafeName = name: builtins.match "^[a-z_][a-z0-9_-]*$" name != null; in { - inherit getDevices getAutoSshDevices getAutoSshDomains getAutoSshConfig getRemotePortMap isSafeName; + inherit getDevices getAutoSshDevices getAutoSshDomains getAutoSshConfig getRemotePortMap getPortOffset isSafeName; }