diff --git a/.vscode/settings.json b/.vscode/settings.json index a50732f..5d3a962 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,7 +40,6 @@ "cgroupdriver", "charliermarsh", "Checkpointing", - "cloudflared", "codellama", "codezombiech", "compactmode", diff --git a/systems/jeeves/default.nix b/systems/jeeves/default.nix index 3dbbbb8..1f1b294 100644 --- a/systems/jeeves/default.nix +++ b/systems/jeeves/default.nix @@ -15,6 +15,7 @@ in "${inputs.self}/common/optional/zerotier.nix" ./docker ./services + ./web_services ./hardware.nix ./networking.nix ./programs.nix diff --git a/systems/jeeves/services/cloud_flare_tunnel.nix b/systems/jeeves/services/cloud_flare_tunnel.nix deleted file mode 100644 index 33a7bd6..0000000 --- a/systems/jeeves/services/cloud_flare_tunnel.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ pkgs, ... }: -let - vars = import ../vars.nix; -in -{ - systemd.services.cloud_flare_tunnel = { - description = "cloud_flare_tunnel proxy's traffic through cloudflare"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "simple"; - EnvironmentFile = "${vars.secrets}/docker/cloud_flare_tunnel"; - ExecStart = "${pkgs.cloudflared}/bin/cloudflared --no-autoupdate tunnel run"; - Restart = "on-failure"; - }; - }; -} diff --git a/systems/jeeves/services/gitea.nix b/systems/jeeves/services/gitea.nix index 6803cc8..5f511e8 100644 --- a/systems/jeeves/services/gitea.nix +++ b/systems/jeeves/services/gitea.nix @@ -2,7 +2,10 @@ let vars = import ../vars.nix; in { - networking.firewall.allowedTCPPorts = [ 6443 ]; + networking.firewall.allowedTCPPorts = [ + 6443 + 2223 + ]; services.gitea = { enable = true; @@ -24,7 +27,7 @@ in ROOT_URL = "https://gitea.tmmworkshop.com/"; HTTP_PORT = 6443; SSH_PORT = 2223; - SSH_LISTEN_PORT = 2224; + SSH_LISTEN_PORT = 2223; START_SSH_SERVER = true; PUBLIC_URL_DETECTION = "auto"; }; diff --git a/systems/jeeves/services/validate_system.toml b/systems/jeeves/services/validate_system.toml index d422154..81979c1 100644 --- a/systems/jeeves/services/validate_system.toml +++ b/systems/jeeves/services/validate_system.toml @@ -1,7 +1,6 @@ zpool = ["root_pool", "storage", "media"] services = [ "audiobookshelf", - "cloud_flare_tunnel", "haproxy", "docker", "home-assistant", diff --git a/systems/jeeves/web_services/acme.nix b/systems/jeeves/web_services/acme.nix new file mode 100644 index 0000000..538c1b6 --- /dev/null +++ b/systems/jeeves/web_services/acme.nix @@ -0,0 +1,62 @@ +let + domains = [ + "audiobookshelf" + "cache" + "gitea" + "jellyfin" + "share" + ]; + + makeCert = name: { + name = "${name}.tmmworkshop.com"; + value = { + webroot = "/var/lib/acme/.challenges"; + group = "acme"; + reloadServices = [ "haproxy.service" ]; + }; + }; + + acmeServices = map (domain: "acme-${domain}.tmmworkshop.com.service") domains; +in +{ + users.users.haproxy.extraGroups = [ "acme" ]; + + security.acme = { + acceptTerms = true; + defaults.email = "Richie@tmmworkshop.com"; + certs = builtins.listToAttrs (map makeCert domains); + }; + + # Minimal nginx to serve ACME HTTP-01 challenge files for HAProxy + services.nginx = { + enable = true; + virtualHosts."acme-challenge" = { + listen = [ + { + addr = "127.0.0.1"; + port = 8402; + } + ]; + locations."/.well-known/acme-challenge/" = { + root = "/var/lib/acme/.challenges"; + }; + }; + }; + + # Ensure the challenge directory exists with correct permissions + systemd.tmpfiles.rules = [ + "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/acme-challenge 0750 acme acme - -" + ]; + + users.users.nginx.extraGroups = [ "acme" ]; + + # HAProxy needs certs to exist before it can bind :443. + # NixOS's acme module generates self-signed placeholders on first boot + # via acme-.service — just make HAProxy wait for them. + systemd.services.haproxy = { + after = acmeServices; + wants = acmeServices; + }; +} diff --git a/systems/jeeves/web_services/default.nix b/systems/jeeves/web_services/default.nix new file mode 100644 index 0000000..1133fcb --- /dev/null +++ b/systems/jeeves/web_services/default.nix @@ -0,0 +1,9 @@ +{ 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; +} diff --git a/systems/jeeves/services/haproxy.cfg b/systems/jeeves/web_services/haproxy.cfg similarity index 62% rename from systems/jeeves/services/haproxy.cfg rename to systems/jeeves/web_services/haproxy.cfg index a8f22c9..f01ad17 100644 --- a/systems/jeeves/services/haproxy.cfg +++ b/systems/jeeves/web_services/haproxy.cfg @@ -6,6 +6,7 @@ global defaults log global mode http + option httplog retries 3 maxconn 2000 timeout connect 5s @@ -22,25 +23,37 @@ defaults #Application Setup frontend ContentSwitching bind *:80 v4v6 - bind *:443 v4v6 ssl crt /zfs/storage/secrets/docker/cloudflare.pem + bind *:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem mode http + + # ACME challenge routing (must be first) + acl is_acme path_beg /.well-known/acme-challenge/ + use_backend acme_challenge if is_acme + # tmmworkshop.com acl host_audiobookshelf hdr(host) -i audiobookshelf.tmmworkshop.com acl host_cache hdr(host) -i cache.tmmworkshop.com acl host_jellyfin hdr(host) -i jellyfin.tmmworkshop.com acl host_share hdr(host) -i share.tmmworkshop.com - acl host_gcw hdr(host) -i gcw.tmmworkshop.com - acl host_n8n hdr(host) -i n8n.tmmworkshop.com acl host_gitea hdr(host) -i gitea.tmmworkshop.com + # Hosts allowed to serve plain HTTP (add entries to skip the HTTPS redirect) + acl allow_http hdr(host) -i __none__ + # acl allow_http hdr(host) -i example.tmmworkshop.com + + # Redirect all HTTP to HTTPS unless on the allow list or ACME challenge + http-request redirect scheme https code 301 if !{ ssl_fc } !allow_http !is_acme + use_backend audiobookshelf_nodes if host_audiobookshelf use_backend cache_nodes if host_cache use_backend jellyfin if host_jellyfin use_backend share_nodes if host_share - use_backend gcw_nodes if host_gcw - use_backend n8n if host_n8n use_backend gitea if host_gitea +backend acme_challenge + mode http + server acme 127.0.0.1:8402 + backend audiobookshelf_nodes mode http server server 127.0.0.1:8000 @@ -60,14 +73,6 @@ backend share_nodes mode http server server 127.0.0.1:8091 -backend gcw_nodes - mode http - server server 127.0.0.1:8092 - -backend n8n - mode http - server server 127.0.0.1:5678 - backend gitea mode http - server server 127.0.0.1:6443 \ No newline at end of file + server server 127.0.0.1:6443 diff --git a/systems/jeeves/services/haproxy.nix b/systems/jeeves/web_services/haproxy.nix similarity index 100% rename from systems/jeeves/services/haproxy.nix rename to systems/jeeves/web_services/haproxy.nix