feat(portal): move edge proxy services from Jeeves

- move HAProxy and ACME certificate management to Portal
- route application backends to Jeeves over Tailscale
- restrict Jeeves backend ports to the Tailscale interface
- expose Gems for remote proxy access
This commit is contained in:
2026-08-26 07:33:31 -04:00
parent 8245d0f82e
commit 7f08a6f788
11 changed files with 28 additions and 31 deletions
-2
View File
@@ -4,8 +4,6 @@
... ...
}: }:
{ {
networking.firewall.trustedInterfaces = [ "tailscale0" ];
nix.settings = { nix.settings = {
trusted-substituters = [ "http://jeeves:5000" ]; trusted-substituters = [ "http://jeeves:5000" ];
substituters = [ "http://jeeves:5000/?priority=1&want-mass-query=true" ]; substituters = [ "http://jeeves:5000/?priority=1&want-mass-query=true" ];
+9
View File
@@ -17,6 +17,15 @@
allowedTCPPorts = [ ]; allowedTCPPorts = [ ];
allowedUDPPorts = [ ]; allowedUDPPorts = [ ];
}; };
interfaces.tailscale0.allowedTCPPorts = [
5000 # Nix binary cache
6443 # Gitea HTTP
8000 # Audiobookshelf
8001 # Norn Sight
8002 # Gems
8091 # Share
8096 # Jellyfin HTTP
];
allowedTCPPorts = [ allowedTCPPorts = [
8070 8070
]; ];
+1 -1
View File
@@ -36,7 +36,7 @@ in
Type = "simple"; Type = "simple";
User = "gems"; User = "gems";
Group = "gems"; Group = "gems";
ExecStart = "${pkgs.my_python}/bin/python -m python.gems.main --host 127.0.0.1 --port 8002"; ExecStart = "${pkgs.my_python}/bin/python -m python.gems.main --host 0.0.0.0 --port 8002";
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "5s"; RestartSec = "5s";
StandardOutput = "journal"; StandardOutput = "journal";
-1
View File
@@ -3,7 +3,6 @@ let
in in
{ {
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
6443
2223 2223
]; ];
+1 -1
View File
@@ -4,7 +4,7 @@ in
{ {
services.jellyfin = { services.jellyfin = {
enable = true; enable = true;
openFirewall = true; openFirewall = false;
dataDir = "${vars.services}/jellyfin"; dataDir = "${vars.services}/jellyfin";
cacheDir = "${vars.services}/jellyfin/cache"; cacheDir = "${vars.services}/jellyfin/cache";
}; };
+1 -1
View File
@@ -7,6 +7,6 @@ in
package = pkgs.nix-serve-ng; package = pkgs.nix-serve-ng;
enable = true; enable = true;
secretKeyFile = "${vars.secrets}/services/nix-cache/cache-priv-key.pem"; secretKeyFile = "${vars.secrets}/services/nix-cache/cache-priv-key.pem";
openFirewall = true; openFirewall = false;
}; };
} }
-9
View File
@@ -1,9 +0,0 @@
{ lib, ... }:
{
imports =
let
files = builtins.attrNames (builtins.readDir ./.);
nixFiles = builtins.filter (name: lib.hasSuffix ".nix" name && name != "default.nix") files;
in
map (file: ./. + "/${file}") nixFiles;
}
@@ -32,15 +32,12 @@ let
++ map (domain: "acme-${domain}.service") extraDomains; ++ map (domain: "acme-${domain}.service") extraDomains;
in in
{ {
users.users.haproxy.extraGroups = [ "acme" ];
security.acme = { security.acme = {
acceptTerms = true; acceptTerms = true;
defaults.email = "Richie@tmmworkshop.com"; defaults.email = "Richie@tmmworkshop.com";
certs = builtins.listToAttrs ((map makeCert domains) ++ (map makeExtraCert extraDomains)); certs = builtins.listToAttrs ((map makeCert domains) ++ (map makeExtraCert extraDomains));
}; };
# Minimal nginx to serve ACME HTTP-01 challenge files for HAProxy
services.nginx = { services.nginx = {
enable = true; enable = true;
virtualHosts."acme-challenge" = { virtualHosts."acme-challenge" = {
@@ -56,18 +53,18 @@ in
}; };
}; };
# Ensure the challenge directory exists with correct permissions
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d /var/lib/acme/.challenges 0750 acme acme - -" "d /var/lib/acme/.challenges 0750 acme acme - -"
"d /var/lib/acme/.challenges/.well-known 0750 acme acme - -" "d /var/lib/acme/.challenges/.well-known 0750 acme acme - -"
"d /var/lib/acme/.challenges/.well-known/acme-challenge 0750 acme acme - -" "d /var/lib/acme/.challenges/.well-known/acme-challenge 0750 acme acme - -"
]; ];
users.users.nginx.extraGroups = [ "acme" ]; users.users = {
haproxy.extraGroups = [ "acme" ];
nginx.extraGroups = [ "acme" ];
};
# HAProxy needs certs to exist before it can bind :443. # HAProxy needs the initial certificates before it can bind to port 443.
# NixOS's acme module generates self-signed placeholders on first boot
# via acme-<domain>.service — just make HAProxy wait for them.
systemd.services.haproxy = { systemd.services.haproxy = {
after = acmeServices; after = acmeServices;
wants = acmeServices; wants = acmeServices;
@@ -1,4 +1,7 @@
{ ... }:
{ {
imports = [ ./acme.nix ];
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
80 80
443 443
@@ -120,35 +120,35 @@ backend st_gems_join
backend acme_challenge backend acme_challenge
mode http mode http
server acme 127.0.0.1:8402 server acme jeeves:8402
backend audiobookshelf_nodes backend audiobookshelf_nodes
mode http mode http
server server 127.0.0.1:8000 server server jeeves:8000
backend cache_nodes backend cache_nodes
mode http mode http
server server 127.0.0.1:5000 server server jeeves:5000
backend jellyfin backend jellyfin
mode http mode http
option forwardfor option forwardfor
server jellyfin 127.0.0.1:8096 server jellyfin jeeves:8096
backend share_nodes backend share_nodes
mode http mode http
server server 127.0.0.1:8091 server server jeeves:8091
backend gitea backend gitea
mode http mode http
server server 127.0.0.1:6443 server server jeeves:6443
backend gems backend gems
mode http mode http
option forwardfor option forwardfor
timeout server 1h timeout server 1h
server gems 127.0.0.1:8002 server gems jeeves:8002
backend norn_sight backend norn_sight
mode http mode http
server server 127.0.0.1:8001 server server jeeves:8001