diff --git a/secrets.yaml b/secrets.yaml --- a/secrets.yaml +++ b/secrets.yaml @@ -86,6 +86,9 @@ private_keys: ENC[AES256_GCM,data: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,iv:cWOG/YEJ/vbk41rD+yeVurIb5uFUeU1bc9WA8KuD/CQ=,tag:wXMcN0KOb7PJn0FijBeWvw==,type:str] ownertrust: ENC[AES256_GCM,data:lkCOnk4Fe+DTKeTuBtjrhD4y/OsrJVW9z74ihfGLjRxXfh8aKapruUx0CNyiFGzeCNM9Cy1MeA+nFa5xodUDd7g8ZZrnG+4/s8N84UfDzlzR+2v2mhg/a8Os5R5hYVLyP22Dzn2Qye2eVPtV2VQoCnKwShHlrIA3sUipRKDm2HaZZ8Rv0g7tzsqec2vN99CKXpe60kq5zNaeK26UIdRar3YBqqlM1j2rE4VmjWyauztKOvCgwRZpM0ymlgxetmH/tPng4heL85CIvdOhCrnu+8pRVg==,iv:4LpN7y5Q6uwrt6dS5Pst/gWIPzBICyLyOc/cD9yAGMQ=,tag:3DXppR87QE7E7LqCkxfYdg==,type:str] atproto_app_password: ENC[AES256_GCM,data:3BOPzIRh130dzmSK6CYH24fE2w==,iv:GbpKDv5l/RL5CTmoJqP5FR8dD1b0b9UahgEkkRY9hS0=,tag:wXeclV3wCeZCJ4a/jl5vOw==,type:str] +anubis: + signing_key: ENC[AES256_GCM,data:Lo294/P+RGhZLG03UlIWRDOiBexjPPNSZpwoQKfey8BBa/+h4Ix/JKl4u1ljUgIem7fSc6c0X9iktFBBE0nLQw==,iv:z15zaxxkElC22jiA0yZDX0bTDw0QXhdWNI0kdp2VvgQ=,tag:WU2dSUy8q/09JS+qiP/ocw==,type:str] + valkey_password: ENC[AES256_GCM,data:Z3vkFpbjSTeBpyZYPyvDAb3wH6hFDmeKUULCkoxqFLbNuNxc4jH/teghDuPR/KhyHGcmZjCBSQ84f1yjKuw8FA==,iv:f27IjYaK6weSS4f+qtQQAnGU+TWRShXHIHo62yS+Wxk=,tag:LB/Cs618F+zSI9OLBET3zQ==,type:str] sops: age: - enc: | @@ -124,8 +127,8 @@ w2ToACYVigKMSlqPNG9bKcK75XI/iAONG48DQm1Nse3yB9/q3jwd+Q== -----END AGE ENCRYPTED FILE----- recipient: age1yl52c6aemw9anmfuqayamvsnkvlu6fmy0kk3gzvrraexxd9pr90qvs8pe5 - lastmodified: "2026-07-18T04:03:09Z" - mac: ENC[AES256_GCM,data:VRIAu9Cm9Nn8dG3I91MCTWP1wz47INq2OE2QoKWlkRccqgcHp22CvgNHhS6qSQ2NHNSgCAGI/OZ5RPczn3l2yzu7KDV9IZ4uzBa/Oj6cJpKUTGWzVyROvhnIX1xWBNt/6i6t99SVDE1Udyir9aScJXyj2uIlFD87oY0Ao4cWn0E=,iv:FmOQh04XKRZl9fGKGTxwuDy8y/2+/8n3yZlYQKHFT5E=,tag:7cLLKqApEVvrTXcQHHyLJA==,type:str] + lastmodified: "2026-07-18T18:06:39Z" + mac: ENC[AES256_GCM,data:ceIZ/ACXfBn77DzaYmE9yCkMoU3FMRnCxOh35vgesHinIGHN6l8gzux9tUFWMkiDTtF0XgE0YGaFzzuPSy7cWwYRsoitfDQ8xMgCjQB9jTyvcc+1iey5zeA/zueu9FxQy0j60riNELm6Aylk/sXwt4ICbw65F0k+iiS8Z8QBC80=,iv:ryHAK6fDz0+7vyV3BaAOrLdeQVRgmUBkXjb6+nr03f4=,tag:WPwMWC9soEk74C/E2SzK4w==,type:str] pgp: - created_at: "2026-06-02T01:50:38Z" enc: |- diff --git a/modules/hosts/WIREGUARD.md b/modules/hosts/WIREGUARD.md --- a/modules/hosts/WIREGUARD.md +++ b/modules/hosts/WIREGUARD.md @@ -27,6 +27,7 @@ 3000 Dockhand agent endpoint 3306 MariaDB/MySQL 5432 PostgreSQL +6379 Valkey for shared Anubis state ``` ## Generate Keys diff --git a/modules/profiles/server.nix b/modules/profiles/server.nix --- a/modules/profiles/server.nix +++ b/modules/profiles/server.nix @@ -6,7 +6,13 @@ # keep-sorted end ... }: let - inherit (lib) mkForce; + inherit + (lib) + # keep-sorted start + mkDefault + mkForce + # keep-sorted end + ; in { imports = with self.modules.nixos; [ # Profiles @@ -25,7 +31,7 @@ disko.devices = (self.diskoConfigurations.btrfs config.disko.selectedDisk).disko.devices; - boot.kernel.sysctl."vm.overcommit_memory" = 1; + boot.kernel.sysctl."vm.overcommit_memory" = mkDefault 1; powerManagement.cpuFreqGovernor = "performance"; diff --git a/modules/hosts/euclid/default.nix b/modules/hosts/euclid/default.nix --- a/modules/hosts/euclid/default.nix +++ b/modules/hosts/euclid/default.nix @@ -12,6 +12,7 @@ # Services # keep-sorted start + anubis apprise authentik cloudbeaver @@ -24,6 +25,7 @@ postgres protonWireguard traefik + valkey wireguard # keep-sorted end ]; diff --git a/modules/services/data/valkey.nix b/modules/services/data/valkey.nix new file mode 100644 --- /dev/null +++ b/modules/services/data/valkey.nix @@ -0,0 +1,50 @@ +{ + flake.modules.nixos.valkey = { + # keep-sorted start + config, + pkgs, + # keep-sorted end + ... + }: let + instance = config.services.redis.servers.anubis; + secrets = config.sops.secrets; + in { + sops.secrets."anubis/valkey_server_password" = { + key = "anubis/valkey_password"; + mode = "0400"; + owner = instance.user; + restartUnits = ["redis-anubis.service"]; + }; + + services.redis = { + package = pkgs.valkey; + vmOverCommit = true; + + servers.anubis = { + enable = true; + + appendOnly = true; + + bind = "10.100.0.1"; + port = 6379; + unixSocket = null; + + requirePassFile = secrets."anubis/valkey_server_password".path; + }; + }; + + networking.firewall.interfaces.wg0.allowedTCPPorts = [6379]; + + systemd.services = { + anubis-traefik = { + after = ["redis-anubis.service"]; + wants = ["redis-anubis.service"]; + }; + + redis-anubis = { + after = ["sops-install-secrets.service"]; + wants = ["sops-install-secrets.service"]; + }; + }; + }; +} diff --git a/modules/services/security/anubis.nix b/modules/services/security/anubis.nix new file mode 100644 --- /dev/null +++ b/modules/services/security/anubis.nix @@ -0,0 +1,71 @@ +{ + flake.modules.nixos.anubis = { + # keep-sorted start + config, + vars, + # keep-sorted end + ... + }: let + inherit (builtins) toJSON; + + inherit (vars) groundDomain; + + anubisUser = config.services.anubis.instances.traefik.user; + secrets = config.sops.secrets; + sopsPlaceholder = config.sops.placeholder; + templates = config.sops.templates; + in { + sops = { + secrets = { + "anubis/signing_key" = { + mode = "0400"; + owner = anubisUser; + restartUnits = ["anubis-traefik.service"]; + }; + + "anubis/valkey_password" = {}; + }; + + templates."anubis-traefik-policy.json" = { + mode = "0400"; + owner = anubisUser; + restartUnits = ["anubis-traefik.service"]; + content = toJSON { + bots = [ + {import = "(data)/meta/default-config.yaml";} + ]; + + status_codes = { + CHALLENGE = 200; + DENY = 403; + }; + + store = { + backend = "valkey"; + parameters.url = "redis://:${sopsPlaceholder."anubis/valkey_password"}@euclid.wg:6379/0"; + }; + }; + }; + }; + + services.anubis.instances.traefik.settings = { + BIND = "127.0.0.1:8923"; + BIND_NETWORK = "tcp"; + + COOKIE_DOMAIN = groundDomain; + ED25519_PRIVATE_KEY_HEX_FILE = secrets."anubis/signing_key".path; + POLICY_FNAME = templates."anubis-traefik-policy.json".path; + + PUBLIC_URL = "https://anubis.${groundDomain}"; + REDIRECT_DOMAINS = "${groundDomain},*.${groundDomain}"; + TARGET = " "; + + SERVE_ROBOTS_TXT = true; + }; + + systemd.services.anubis-traefik = { + after = ["sops-install-secrets.service"]; + wants = ["sops-install-secrets.service"]; + }; + }; +} diff --git a/modules/services/server/traefik.nix b/modules/services/server/traefik.nix --- a/modules/services/server/traefik.nix +++ b/modules/services/server/traefik.nix @@ -122,6 +122,11 @@ http = { middlewares = { # keep-sorted start block=yes newline_separated=yes + anubis.forwardAuth = { + address = "http://127.0.0.1:8923/.within.website/x/cmd/anubis/api/check"; + trustForwardHeader = true; + }; + authentik-proxy.forwardAuth = { address = "http://[::1]:9005/outpost.goauthentik.io/auth/traefik"; trustForwardHeader = true; @@ -167,21 +172,32 @@ routers = { # keep-sorted start block=yes newline_separated=yes + anubis = { + entryPoints = ["websecure"]; + rule = "Host(`anubis.${groundDomain}`)"; + service = "anubis"; + }; + apprise = { entryPoints = ["websecure"]; - middlewares = ["authentik@file"]; + middlewares = [ + "anubis@file" + "authentik@file" + ]; rule = "Host(`apprise.${groundDomain}`)"; service = "apprise"; }; authentik = { entryPoints = ["websecure"]; + middlewares = ["anubis@file"]; rule = "Host(`authentik.${groundDomain}`)"; service = "authentik"; }; authentik-outpost = { entryPoints = ["websecure"]; + middlewares = ["anubis@file"]; priority = 15; rule = "Host(`traefik.${groundDomain}`) && PathPrefix(`/outpost.goauthentik.io/`)"; service = "authentik-outpost"; @@ -189,26 +205,34 @@ cloudbeaver = { entryPoints = ["websecure"]; - middlewares = ["authentik@file"]; + middlewares = [ + "anubis@file" + "authentik@file" + ]; rule = "Host(`cloudbeaver.${groundDomain}`)"; service = "cloudbeaver"; }; dockhand = { entryPoints = ["websecure"]; + middlewares = ["anubis@file"]; rule = "Host(`dockhand.${groundDomain}`)"; service = "dockhand"; }; gotify = { entryPoints = ["websecure"]; + middlewares = ["anubis@file"]; rule = "Host(`gotify.${groundDomain}`)"; service = "gotify"; }; traefik-dashboard = { entryPoints = ["websecure"]; - middlewares = ["authentik@file"]; + middlewares = [ + "anubis@file" + "authentik@file" + ]; rule = "Host(`traefik.${groundDomain}`)"; service = "api@internal"; }; @@ -217,6 +241,10 @@ services = { # keep-sorted start block=yes newline_separated=yes + anubis.loadBalancer.servers = [ + {url = "http://127.0.0.1:8923";} + ]; + apprise.loadBalancer.servers = [ {url = "http://127.0.0.1:8000";} ]; @@ -273,8 +301,14 @@ tmpfiles.rules = ["d /var/log/traefik 0750 traefik traefik -"]; services.traefik = { - wants = ["podman.socket"]; - after = ["podman.socket"]; + wants = [ + "anubis-traefik.service" + "podman.socket" + ]; + after = [ + "anubis-traefik.service" + "podman.socket" + ]; serviceConfig.TimeoutStopSec = "60s"; stopIfChanged = false; diff --git a/modules/programs/tui/pi/extensions/speeed.nix b/modules/programs/tui/pi/extensions/speeed.nix deleted file mode 100644 --- a/modules/programs/tui/pi/extensions/speeed.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ - flake.modules.homeManager.pi = {lib, ...}: let - inherit (lib) mkOrder; - in { - # Load after other UI extensions so RunCat owns the working indicator. - programs.pi.coding-agent.extensions = mkOrder 1750 ["npm:pi-speeed@0.4.0"]; - }; -} diff --git a/modules/programs/tui/pi/extensions/tool-renderer.nix b/modules/programs/tui/pi/extensions/tool-renderer.nix --- a/modules/programs/tui/pi/extensions/tool-renderer.nix +++ b/modules/programs/tui/pi/extensions/tool-renderer.nix @@ -48,7 +48,6 @@ renderGitDiffCommandDiffs = true; renderGrepFindTools = false; renderMutationTools = true; - workingIndicator = "default"; # keep-sorted end }; };