From 48c1dec6b6e5c2cab4e2ba4550b8c703f33359a5 Mon Sep 17 00:00:00 2001 From: flop Date: Sun, 5 Jul 2026 20:05:34 +0200 Subject: [PATCH] feat: add vhost mapping and automatic status management --- flake.nix | 45 ++++++++++++++++++-- machines/auth.domain.com/configuration.nix | 11 ----- machines/status.domain.com/configuration.nix | 10 +++++ modules/gatus_service.nix | 37 ++++++++-------- modules/keycloak.nix | 7 +-- modules/nginx.nix | 27 ++++++++++++ 6 files changed, 102 insertions(+), 35 deletions(-) create mode 100644 modules/nginx.nix diff --git a/flake.nix b/flake.nix index a658cd6..01da1df 100644 --- a/flake.nix +++ b/flake.nix @@ -37,16 +37,51 @@ sharedModules = [ ./modules/users.nix ./modules/auto-update.nix + ./modules/nginx.nix ]; - sharedArgs = { - inherit domain git_url key_mappings; + # Central subdomain -> service mapping. + # Each entry can set: + # description human-readable label (used as the gatus monitor name) + # ports.http local port nginx proxies to + # websockets enable WebSocket proxying + # gatus_health health-check path monitored by gatus (e.g. "/health"). + # Leave empty/unset to hide this host from the status page. + vhosts = { + "auth.${domain}" = { + description = "Keycloak"; + ports = {"http" = 8080; }; + gatus_health = "/health"; + }; + "status.${domain}" = { + description = "Gatus status page"; + ports = {"http" = 8081; }; + gatus_health = "/"; + }; + "pad.${domain}" = { + # TODO: not implemented (hidden from gatus until gatus_health is set) + description = "HedgeDoc"; + ports = {"http" = 3000; "httpws" = 3001; }; + websockets = true; + }; + "git.${domain}" = { + # TODO: not implemented (hidden from gatus until gatus_health is set) + description = "HedgeDoc"; + ports = {"http" = 3001; "ssh" = 22; }; + websockets = true; + }; + }; + + baseArgs = { + inherit domain git_url key_mappings vhosts; }; in { nixosConfigurations = { auth.domain.com = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; - specialArgs = sharedArgs; + specialArgs = baseArgs // { + domains = [ "auth.${domain}" "status.${domain}" ]; + }; modules = [ ./machines/auth.domain.com/configuration.nix ./machines/auth.domain.com/hardware-configuration.nix @@ -57,7 +92,9 @@ status.domain.com = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; - specialArgs = sharedArgs; + specialArgs = baseArgs // { + domains = [ "pad.${domain}" "gitea.${domain}" ]; + }; modules = [ ./machines/status.domain.com/configuration.nix ./machines/status.domain.com/hardware-configuration.nix diff --git a/machines/auth.domain.com/configuration.nix b/machines/auth.domain.com/configuration.nix index 5e79527..8c59e50 100644 --- a/machines/auth.domain.com/configuration.nix +++ b/machines/auth.domain.com/configuration.nix @@ -43,17 +43,6 @@ enable = true; hostname = "status.domain.com"; title = "Status"; - endpoints = [ - { - name = "Keycloak"; - url = "https://auth.domain.com/health"; - interval = "1m"; - conditions = [ - "[STATUS] == 200" - "[RESPONSE_TIME] < 500" - ]; - } - ]; }; infra.autoUpdate = { diff --git a/machines/status.domain.com/configuration.nix b/machines/status.domain.com/configuration.nix index 26133d9..ed1805d 100644 --- a/machines/status.domain.com/configuration.nix +++ b/machines/status.domain.com/configuration.nix @@ -28,6 +28,16 @@ bash ]; + # Central nginx subdomain mapping example (enable when ready): + # infra.nginx = { + # enable = true; + # vhosts."pad.${config.networking.domain}" = { + # description = "HedgeDoc"; + # internal_proxy = "http://localhost:3000"; + # websockets = true; + # }; + # }; + infra.autoUpdate = { enable = true; # selects the nixosConfigurations.status.domain.com output from the flake diff --git a/modules/gatus_service.nix b/modules/gatus_service.nix index d981181..b5d2aa0 100644 --- a/modules/gatus_service.nix +++ b/modules/gatus_service.nix @@ -1,9 +1,26 @@ -{ config, lib, pkgs, domain, ... }: +{ config, lib, pkgs, domain, vhosts ? { }, ... }: with lib; let cfg = config.infra.gatus; + vhost = vhosts.${cfg.hostname} or { }; + port = vhost.ports.http or 8081; + + # Auto-infer monitored hosts from the central vhosts map. + # Only entries with a non-empty `gatus_health` path are monitored. + monitored = lib.filterAttrs (_: v: (v.gatus_health or "") != "") vhosts; + autoEndpoints = lib.mapAttrsToList (name: v: + { + name = v.description or name; + url = "https://${name}${v.gatus_health}"; + interval = "1m"; + conditions = [ + "[STATUS] == 200" + "[RESPONSE_TIME] < 1000" + ]; + } + ) monitored; in { options.infra.gatus = { enable = mkEnableOption "Gatus status page behind nginx"; @@ -14,12 +31,6 @@ in { description = "Virtual host name serving the Gatus status page"; }; - port = mkOption { - type = types.port; - default = 8081; - description = "Port Gatus listens on locally"; - }; - title = mkOption { type = types.str; default = "Status"; @@ -47,24 +58,16 @@ in { enable = true; settings = mkMerge [ { - web.port = cfg.port; + web.port = port; storage = { type = "sqlite"; path = "/var/lib/gatus/data.db"; }; ui.title = cfg.title; - endpoints = cfg.endpoints; + endpoints = autoEndpoints ++ cfg.endpoints; } cfg.extraSettings ]; }; - - services.nginx = { - enable = true; - virtualHosts.${cfg.hostname}.locations."/".proxyPass = - "http://127.0.0.1:${toString cfg.port}"; - }; - - networking.firewall.allowedTCPPorts = [ 80 443 ]; }; } \ No newline at end of file diff --git a/modules/keycloak.nix b/modules/keycloak.nix index d079d3a..05a696f 100644 --- a/modules/keycloak.nix +++ b/modules/keycloak.nix @@ -1,9 +1,11 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, vhosts ? { }, ... }: with lib; let cfg = config.services.keycloak; + vhost = vhosts.${cfg.hostname} or { }; + port = vhost.ports.http or 8080; in { options.services.keycloak = { enable = mkEnableOption "Keycloak identity provider"; @@ -61,6 +63,7 @@ in { enable = true; settings = { hostname = cfg.hostname; + http-port = port; hostname-strict-backchannel = true; proxy = "edge"; db = mkDefault "postgres"; @@ -71,7 +74,5 @@ in { admin-password-file = cfg.adminPasswordFile; } // cfg.settings; }; - - networking.firewall.allowedTCPPorts = [ 80 443 ]; }; } \ No newline at end of file diff --git a/modules/nginx.nix b/modules/nginx.nix new file mode 100644 index 0000000..4340f85 --- /dev/null +++ b/modules/nginx.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, vhosts ? { }, domains ? [ ], ... }: + +let + served = lib.filterAttrs (k: _: builtins.elem k domains) vhosts; + enabled = served != { }; +in { + config = lib.mkIf enabled { + services.nginx = { + enable = true; + virtualHosts = lib.mapAttrs (name: vhost: + let + port = vhost.ports.http or 80; + in { + enableACME = vhost.enableACME or false; + forceSSL = vhost.enableACME or false; + extraConfig = vhost.extraConfig or ""; + locations."/" = { + proxyPass = "http://localhost:${toString port}"; + proxyWebsockets = vhost.websockets or false; + }; + } + ) served; + }; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + }; +} \ No newline at end of file