My nix-darwin and NixOS config
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: PDS Gatekeeper

+109
+9
flake.nix
··· 44 44 url = "github:ewanc26/pkgs"; 45 45 inputs.nixpkgs.follows = "nixpkgs"; 46 46 }; 47 + 48 + # Isabel's packages — provides pds-gatekeeper NixOS module and package 49 + tgirlpkgs = { 50 + url = "github:tgirlcloud/pkgs"; 51 + inputs.nixpkgs.follows = "nixpkgs"; 52 + }; 47 53 }; 48 54 49 55 outputs = ··· 59 65 mac-app-util, 60 66 plasma-manager, 61 67 pkgs-monorepo, 68 + tgirlpkgs, 62 69 ... 63 70 }: 64 71 let ··· 162 169 pkgs-unstable = mkUnstablePkgs "x86_64-linux"; 163 170 }; 164 171 modules = nixosModules ++ [ 172 + tgirlpkgs.nixosModules.default 165 173 ./hosts/server 166 174 { nixpkgs.hostPlatform = "x86_64-linux"; } 167 175 ]; ··· 173 181 pkgs-unstable = mkUnstablePkgs "aarch64-linux"; 174 182 }; 175 183 modules = nixosModules ++ [ 184 + tgirlpkgs.nixosModules.default 176 185 ./hosts/server 177 186 { nixpkgs.hostPlatform = "aarch64-linux"; } 178 187 ];
+2
hosts/server/default.nix
··· 13 13 ../../modules/server/split-dns.nix 14 14 ../../modules/server/cloudflare-tunnel.nix 15 15 ../../modules/server/pds.nix 16 + ../../modules/server/pds-gatekeeper.nix 16 17 ../../modules/server/forgejo.nix 17 18 ../../modules/server/nextcloud.nix 18 19 ../../modules/server/immich.nix ··· 29 30 myConfig.services.immich.enable = true; # Tailnet-only — not in CF tunnel 30 31 myConfig.services.jellyfin.enable = true; # Tailnet-only — not in CF tunnel 31 32 myConfig.services.pds.enable = true; 33 + myConfig.services.pdsGatekeeper.enable = true; 32 34 myConfig.services.cloudflare.enable = true; 33 35 myConfig.services.vaultwarden.enable = true; # Tailnet-only — password manager, never public 34 36 myConfig.services.timemachine.enable = true; # Tailnet-only — Time Machine AFP target
+5
modules/options.nix
··· 408 408 type = bool; 409 409 default = false; 410 410 }; 411 + pdsGatekeeper.enable = mkOption { 412 + type = bool; 413 + default = false; 414 + description = "Enable PDS Gatekeeper (2FA proxy for the ATProto PDS). Requires services.pds.enable = true."; 415 + }; 411 416 nextcloud.enable = mkOption { 412 417 type = bool; 413 418 default = false;
+93
modules/server/pds-gatekeeper.nix
··· 1 + ############################################################################## 2 + # PDS Gatekeeper — 2FA proxy for the ATProto PDS. 3 + # 4 + # By Bailey Townsend; NixOS module and package by Isabel (tgirlcloud/pkgs). 5 + # Sits between Caddy and the PDS for a small set of auth endpoints, 6 + # adding TOTP-based two-factor authentication on top of the standard 7 + # ATProto login flow. 8 + # 9 + # Architecture: 10 + # Caddy (auth routes only) 11 + # ↓ reverse_proxy 12 + # PDS Gatekeeper (127.0.0.1:3602) 13 + # ↓ proxies upstream to 14 + # PDS (127.0.0.1:cfg.pds.port) 15 + # 16 + # Prerequisites: 17 + # - myConfig.services.pds.enable = true (pds.nix must be imported) 18 + # - tgirlpkgs.nixosModules.default (wired in via flake.nix) 19 + # - secrets/pds.env sops secret (shared with bluesky-pds) 20 + ############################################################################## 21 + { 22 + config, 23 + lib, 24 + pkgs, 25 + ... 26 + }: 27 + let 28 + cfg = config.myConfig; 29 + pds = cfg.pds; 30 + pdsPort = toString pds.port; 31 + caddyPort = toString pds.caddyPort; 32 + 33 + gkHost = "127.0.0.1"; 34 + gkPort = 3602; 35 + gkUrl = "http://${gkHost}:${toString gkPort}"; 36 + in 37 + lib.mkIf cfg.services.pdsGatekeeper.enable { 38 + 39 + # ── Permissions ──────────────────────────────────────────────────────────── 40 + # The pds.env sops secret is owned by pds:pds. Widen to group-readable so 41 + # the gatekeeper service (added to the pds group below) can read it. 42 + sops.secrets."pds.env".mode = lib.mkForce "0440"; 43 + 44 + # ── PDS Gatekeeper service ───────────────────────────────────────────────── 45 + services.pds-gatekeeper = { 46 + enable = true; 47 + environmentFiles = [ config.sops.secrets."pds.env".path ]; 48 + settings = { 49 + GATEKEEPER_HOST = gkHost; 50 + GATEKEEPER_PORT = gkPort; 51 + PDS_BASE_URL = "http://127.0.0.1:${pdsPort}"; 52 + GATEKEEPER_TRUST_PROXY = "true"; 53 + # Gatekeeper expects a .env file path; supply an empty nix-store file 54 + # so it doesn't error on startup (secrets come via environmentFiles). 55 + PDS_ENV_LOCATION = toString (pkgs.writeText "gatekeeper-pds-env" ""); 56 + }; 57 + }; 58 + 59 + # ── Systemd tweaks ───────────────────────────────────────────────────────── 60 + systemd.services.pds-gatekeeper = { 61 + after = [ "bluesky-pds.service" ]; 62 + wants = [ "bluesky-pds.service" ]; 63 + serviceConfig = { 64 + # Allow reading the pds:pds group-owned sops secret. 65 + SupplementaryGroups = [ "pds" ]; 66 + Restart = "always"; 67 + RestartSec = cfg.server.servicePolicy.restartSec; 68 + }; 69 + unitConfig = { 70 + StartLimitIntervalSec = cfg.server.servicePolicy.startLimitIntervalSec; 71 + StartLimitBurst = cfg.server.servicePolicy.startLimitBurst; 72 + }; 73 + }; 74 + 75 + # ── Caddy routes ─────────────────────────────────────────────────────────── 76 + # These four endpoints are intercepted by gatekeeper before the catch-all 77 + # reverse_proxy in pds.nix. lib.mkBefore ensures they appear first in the 78 + # merged extraConfig string so Caddy evaluates them with higher priority. 79 + services.caddy.virtualHosts."http://${pds.hostname}:${caddyPort}".extraConfig = lib.mkBefore '' 80 + handle /xrpc/com.atproto.server.createSession { 81 + reverse_proxy ${gkUrl} 82 + } 83 + handle /xrpc/com.atproto.server.getSession { 84 + reverse_proxy ${gkUrl} 85 + } 86 + handle /xrpc/com.atproto.server.updateEmail { 87 + reverse_proxy ${gkUrl} 88 + } 89 + handle /@atproto/oauth-provider/~api/sign-in { 90 + reverse_proxy ${gkUrl} 91 + } 92 + ''; 93 + }