From 62c7dcb7ce5478d9922a31701bdd49abb7687e38 Mon Sep 17 00:00:00 2001 From: mulatta <67085791+mulatta@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:53:32 +0900 Subject: [PATCH] multievolve: gate service through private auth Route MULTI-evolve through the tailnet and Authentik after the shared Authentik Terraform resources exist. Keeping this separate lets the broader Terraform migration land before adding the new app surface. --- docs/admin/authentication.md | 1 + docs/admin/terraform.md | 1 + flake.lock | 180 ++++++++++++++++++++++++- flake.nix | 2 + hosts/eta.nix | 13 ++ hosts/psi.nix | 1 + modules/gatus/default.nix | 1 + modules/headscale/default.nix | 5 + modules/multievolve/default.nix | 85 ++++++++++++ terraform/authentik/forward-auth.tf | 7 + terraform/authentik/import-existing.sh | 1 + 11 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 modules/multievolve/default.nix diff --git a/docs/admin/authentication.md b/docs/admin/authentication.md index 4be942f..98fec91 100644 --- a/docs/admin/authentication.md +++ b/docs/admin/authentication.md @@ -14,6 +14,7 @@ | n8n | Forward Auth | nginx에서 인증 후 이메일 헤더 전달 (Headscale ACL + Forward Auth 이중 보호) | | Grafana | Forward Auth | Tailnet에서만 접근 가능한 관리자 대시보드 | | Gatus | 없음 | Tailnet에서만 접근 가능한 공개 상태 페이지 (Authentik dashboard tile만 표시) | +| MULTI-evolve | Forward Auth | 연구자/관리자용 Streamlit UI | | Nixbot | GitHub OAuth | CI/CD 대시보드 접근 | ### RAGFlow UI-only residue cleanup diff --git a/docs/admin/terraform.md b/docs/admin/terraform.md index 250f3d7..0f18172 100644 --- a/docs/admin/terraform.md +++ b/docs/admin/terraform.md @@ -42,6 +42,7 @@ terragrunt plan | `n8n.sjanglab.org` | `sjanglab-admins`, `sjanglab-researchers` | | `status.sjanglab.org` | 인증 없음 (Authentik dashboard tile만 관리) | | `logging.sjanglab.org` | `sjanglab-admins` | +| `multievolve.sjanglab.org` | `sjanglab-admins`, `sjanglab-researchers` | ### Headscale diff --git a/flake.lock b/flake.lock index 2f39763..f1d330f 100644 --- a/flake.lock +++ b/flake.lock @@ -148,6 +148,27 @@ "type": "github" } }, + "multievolve-nix": { + "inputs": { + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix", + "uv2nix-env": "uv2nix-env" + }, + "locked": { + "lastModified": 1782993998, + "narHash": "sha256-lMjTMaU3vB0hhGVHF6Q4kA+Ti5qYuUQbs6dtdq0VEgU=", + "owner": "mulatta", + "repo": "multievolve-nix", + "rev": "618b1f418073d4698c1eb1e4f41a2038dd8bb9ae", + "type": "github" + }, + "original": { + "owner": "mulatta", + "ref": "nixos-module-service", + "repo": "multievolve-nix", + "type": "github" + } + }, "niks3": { "inputs": { "nixpkgs": [ @@ -260,16 +281,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1782847225, - "narHash": "sha256-JC9PjqKYG9ve5U8aDOLQipp3+KLANBHUvGdLZlxzdKI=", + "lastModified": 1782723713, + "narHash": "sha256-oPXCU/SSUokcGaJREHibG1CBX3+s/W7orDWQOZDsEeQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "95ca1e203c0750115fd4a6f17d5a245dfe6b1edd", + "rev": "b5aa0fbd538984f6e3d201be0005b4463d8b09f8", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-26.05", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -290,6 +311,22 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1782847225, + "narHash": "sha256-JC9PjqKYG9ve5U8aDOLQipp3+KLANBHUvGdLZlxzdKI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "95ca1e203c0750115fd4a6f17d5a245dfe6b1edd", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + }, "pyproject-build-systems": { "inputs": { "nixpkgs": [ @@ -319,6 +356,38 @@ "type": "github" } }, + "pyproject-build-systems_2": { + "inputs": { + "nixpkgs": [ + "multievolve-nix", + "uv2nix-env", + "nixpkgs" + ], + "pyproject-nix": [ + "multievolve-nix", + "uv2nix-env", + "pyproject-nix" + ], + "uv2nix": [ + "multievolve-nix", + "uv2nix-env", + "uv2nix" + ] + }, + "locked": { + "lastModified": 1782093830, + "narHash": "sha256-6gmEVe69+KlRkZD4PEEV5xAlB9CB0Y9TiuEgQjDrKTQ=", + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "rev": "430680a19bc85a3bda55f12e4cc1a1aadcf2e478", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "type": "github" + } + }, "pyproject-nix": { "inputs": { "nixpkgs": [ @@ -340,21 +409,44 @@ "type": "github" } }, + "pyproject-nix_2": { + "inputs": { + "nixpkgs": [ + "multievolve-nix", + "uv2nix-env", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1782808548, + "narHash": "sha256-9+XcLOzFo3tXSgH5xpd5g2i65cx4q6zvfNVEMV7hyEs=", + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "rev": "4de7c126c1a3e76e9ec9e3ced900aaa6d8847757", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "type": "github" + } + }, "root": { "inputs": { "authentik-nix": "authentik-nix", "disko": "disko", "fast-nix-gc": "fast-nix-gc", "flake-parts": "flake-parts", + "multievolve-nix": "multievolve-nix", "niks3": "niks3", "nix-index-database": "nix-index-database", "nixbot": "nixbot", "nixos-images": "nixos-images", - "nixpkgs": "nixpkgs", + "nixpkgs": "nixpkgs_2", "nixpkgs-unstable": "nixpkgs-unstable", "sops-nix": "sops-nix", "srvos": "srvos", - "treefmt-nix": "treefmt-nix" + "treefmt-nix": "treefmt-nix_2" } }, "sops-nix": { @@ -413,6 +505,27 @@ } }, "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "multievolve-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_2": { "inputs": { "nixpkgs": [ "nixpkgs" @@ -456,6 +569,61 @@ "repo": "uv2nix", "type": "github" } + }, + "uv2nix-env": { + "inputs": { + "nixpkgs": [ + "multievolve-nix", + "nixpkgs" + ], + "pyproject-build-systems": "pyproject-build-systems_2", + "pyproject-nix": "pyproject-nix_2", + "treefmt-nix": [ + "multievolve-nix", + "treefmt-nix" + ], + "uv2nix": "uv2nix_2" + }, + "locked": { + "lastModified": 1782880978, + "narHash": "sha256-MewckjJGV0DV2X070b6T93FctV0D0DkDAg7aFfGXa8I=", + "owner": "mulatta", + "repo": "uv2nix-env", + "rev": "67a987d1ef13a7313dbbeea364ec71a443c3e8ce", + "type": "github" + }, + "original": { + "owner": "mulatta", + "repo": "uv2nix-env", + "type": "github" + } + }, + "uv2nix_2": { + "inputs": { + "nixpkgs": [ + "multievolve-nix", + "uv2nix-env", + "nixpkgs" + ], + "pyproject-nix": [ + "multievolve-nix", + "uv2nix-env", + "pyproject-nix" + ] + }, + "locked": { + "lastModified": 1782810559, + "narHash": "sha256-loy132LKn8QfiPbFJhdTjm+yZG3zklyOQhVabLyx2l4=", + "owner": "pyproject-nix", + "repo": "uv2nix", + "rev": "2f698e4b5a3c6004edaf051543542f18a36afe77", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "uv2nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 8a5ec6a..4901397 100644 --- a/flake.nix +++ b/flake.nix @@ -62,6 +62,8 @@ }; # Applications. + multievolve-nix.url = "github:mulatta/multievolve-nix/nixos-module-service"; + niks3 = { url = "github:Mic92/niks3"; inputs.nixpkgs.follows = "nixpkgs"; diff --git a/hosts/eta.nix b/hosts/eta.nix index 14f1fa1..259d842 100644 --- a/hosts/eta.nix +++ b/hosts/eta.nix @@ -41,6 +41,12 @@ in remoteUser = "acme-sync-logging"; remoteHost = hosts.rho.wg-admin; } + { + domain = "multievolve.sjanglab.org"; + serviceName = "acme-sync-multievolve-to-psi"; + remoteUser = "acme-sync-multievolve"; + remoteHost = hosts.psi.wg-admin; + } { domain = "vault.sjanglab.org"; serviceName = "acme-sync-vaultwarden-to-tau"; @@ -66,6 +72,13 @@ in group = "acme"; }; + security.acme.certs."multievolve.sjanglab.org" = { + dnsProvider = "cloudflare"; + environmentFile = config.sops.secrets.cloudflare-credentials.path; + webroot = null; + group = "acme"; + }; + security.acme.certs."vault.sjanglab.org" = { dnsProvider = "cloudflare"; environmentFile = config.sops.secrets.cloudflare-credentials.path; diff --git a/hosts/psi.nix b/hosts/psi.nix index b13e5bf..9aa20bd 100644 --- a/hosts/psi.nix +++ b/hosts/psi.nix @@ -15,6 +15,7 @@ ../modules/borgbackup/psi/client.nix ../modules/monitoring/vector ../modules/harmonia + ../modules/multievolve # ../modules/vllm ../modules/db-sync/databases.nix ../modules/docling diff --git a/modules/gatus/default.nix b/modules/gatus/default.nix index 4cfb9ff..5591a2e 100644 --- a/modules/gatus/default.nix +++ b/modules/gatus/default.nix @@ -57,6 +57,7 @@ in (mkExtEndpoint "Nixbot" "ci") (mkExtEndpoint "Nixbot PostgreSQL" "ci") (mkExtEndpoint "Docling" "ai") + (mkExtEndpoint "MULTI-evolve" "ai") # tau (mkExtEndpoint "Nextcloud" "apps") (mkExtEndpoint "n8n" "apps") diff --git a/modules/headscale/default.nix b/modules/headscale/default.nix index eeaa253..9c5464c 100644 --- a/modules/headscale/default.nix +++ b/modules/headscale/default.nix @@ -62,6 +62,11 @@ type = "A"; value = "100.64.0.2"; # rho headscale IP } + { + name = "multievolve.sjanglab.org"; + type = "A"; + value = "100.64.0.1"; # psi headscale IP + } { name = "vault.sjanglab.org"; type = "A"; diff --git a/modules/multievolve/default.nix b/modules/multievolve/default.nix new file mode 100644 index 0000000..7b434aa --- /dev/null +++ b/modules/multievolve/default.nix @@ -0,0 +1,85 @@ +{ + config, + inputs, + lib, + pkgs, + ... +}: +let + inherit (config.networking.sbee) hosts; + authentikAuth = import ../authentik/nginx-locations.nix { inherit hosts; }; + domain = "multievolve.sjanglab.org"; + port = 8501; + certDir = "/var/lib/acme/${domain}"; +in +{ + imports = [ + inputs.multievolve-nix.nixosModules.default + ../acme/sync.nix + ../gatus/check.nix + ]; + + gatusCheck.push = [ + { + name = "MULTI-evolve"; + group = "ai"; + url = "http://127.0.0.1:${toString port}/_stcore/health"; + } + ]; + + acmeSyncer.mkReceiver = [ + { + inherit domain; + user = "acme-sync-multievolve"; + } + ]; + + services.multievolve-streamlit = { + enable = true; + host = "127.0.0.1"; + inherit port; + workingDirectory = "/workspace/multievolve"; + extraGroups = [ + "render" + "video" + ]; + environment = { + CUDA_VISIBLE_DEVICES = "0"; + LD_LIBRARY_PATH = lib.concatStringsSep ":" [ + "/run/opengl-driver/lib" + "${pkgs.cudaPackages.cuda_cudart}/lib" + "${pkgs.cudaPackages.libcublas}/lib" + ]; + }; + }; + + services.nginx = { + enable = true; + recommendedTlsSettings = true; + + virtualHosts.${domain} = { + forceSSL = true; + sslCertificate = "${certDir}/fullchain.pem"; + sslCertificateKey = "${certDir}/key.pem"; + + locations = authentikAuth.locations // { + "/" = { + proxyPass = "http://127.0.0.1:${toString port}"; + proxyWebsockets = true; + extraConfig = authentikAuth.protectLocation + '' + 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; + proxy_buffering off; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + client_max_body_size 1G; + ''; + }; + }; + }; + }; + + networking.firewall.interfaces.tailscale0.allowedTCPPorts = [ 443 ]; +} diff --git a/terraform/authentik/forward-auth.tf b/terraform/authentik/forward-auth.tf index f04691d..ac1fde1 100644 --- a/terraform/authentik/forward-auth.tf +++ b/terraform/authentik/forward-auth.tf @@ -33,6 +33,13 @@ locals { external_host = "https://logging.sjanglab.org" access_policy = "admins" } + multievolve = { + name = "MULTI-evolve" + provider_name = "MULTI-evolve" + slug = "multievolve" + external_host = "https://multievolve.sjanglab.org" + access_policy = "researchers" + } } } diff --git a/terraform/authentik/import-existing.sh b/terraform/authentik/import-existing.sh index 68d5db6..d2f78af 100755 --- a/terraform/authentik/import-existing.sh +++ b/terraform/authentik/import-existing.sh @@ -154,6 +154,7 @@ while IFS='|' read -r app external_host policy_key; do done <<'EOF' n8n|https://n8n.sjanglab.org|researchers logging|https://logging.sjanglab.org|admins +multievolve|https://multievolve.sjanglab.org|researchers EOF while IFS='|' read -r key name; do