1# for the pds-gatekeeper https://tangled.sh/@isabelroses.com/dotfiles/blob/61ad925dc8b4537b568784971589b137df5cb948/modules/nixos/services/pds.nix
2{
3 config,
4 lib,
5 pkgs,
6 self,
7 ...
8}: let
9 name = "pds";
10 cfg = config.myNixOS.services.${name};
11
12 gk = config.containers.pds.config.services.pds-gatekeeper.settings;
13 gkurl = "http://${gk.GATEKEEPER_HOST}:${toString gk.GATEKEEPER_PORT}";
14
15 network = config.mySnippets.aylac-top;
16 service = network.networkMap.${name};
17
18 pdsHomePage = ''
19 hiii this is an ATProto PDS!! You will find my (ayla) account here!!
20 i should probably put some cool ass art in here or maybe an actual homepage
21 but having this by itself is fun
22
23 most API routes are under /xrpc/
24 '';
25in {
26 options.myNixOS.services.${name} = {
27 enable = lib.mkEnableOption "atproto pds";
28 autoProxy = lib.mkOption {
29 default = true;
30 example = false;
31 description = "${name} auto proxy";
32 type = lib.types.bool;
33 };
34 };
35
36 config = lib.mkIf cfg.enable {
37 services = {
38 cloudflared.tunnels."${network.cloudflareTunnel}".ingress = lib.mkIf cfg.autoProxy {
39 "${service.vHost}" = "http://${service.hostName}";
40 };
41
42 caddy.virtualHosts."http://${service.vHost}".extraConfig = lib.mkIf cfg.autoProxy ''
43 encode zstd gzip
44
45 handle / {
46 respond "${pdsHomePage}"
47 }
48
49 # https://gist.github.com/mary-ext/6e27b24a83838202908808ad528b3318
50 handle /xrpc/app.bsky.unspecced.getAgeAssuranceState {
51 header content-type "application/json"
52 header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy"
53 header access-control-allow-origin "*"
54 respond `{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}` 200
55 }
56
57 # hijack the links for pds-gatekeeper
58 #@gatekeeper {
59 # path /xrpc/com.atproto.server.getSession
60 # path /xrpc/com.atproto.server.updateEmail
61 # path /xrpc/com.atproto.server.createSession
62 # path /@atproto/oauth-provider/~api/sign-in
63 #}
64
65 #handle @gatekeeper {
66 # reverse_proxy ${gkurl}
67 #}
68
69 handle {
70 reverse_proxy ${service.hostName}:${toString service.port}
71 }
72 '';
73 };
74
75 containers.pds = {
76 autoStart = true;
77 bindMounts."${config.age.secrets.pds.path}".isReadOnly = true;
78 config = {
79 imports = [self.inputs.tgirlpkgs.nixosModules.default];
80
81 services = {
82 bluesky-pds = {
83 enable = true;
84 environmentFiles = [config.age.secrets.pds.path];
85 pdsadmin.enable = true;
86 settings = {
87 PDS_HOSTNAME = service.vHost;
88 PDS_PORT = service.port;
89 # PDS_BSKY_APP_VIEW_URL = "https://bsky.zeppelin.social";
90 # PDS_BSKY_APP_VIEW_DID = "did:web:bsky.zeppelin.social";
91
92 # crawlers taken from the following post
93 # <https://bsky.app/profile/billy.wales/post/3lxpd67hnks2e>
94 PDS_CRAWLERS = lib.concatStringsSep "," [
95 "https://bsky.network"
96 "https://relay.cerulea.blue"
97 "https://relay.fire.hose.cam"
98 "https://relay2.fire.hose.cam"
99 "https://relay3.fr.hose.cam"
100 "https://relay.hayescmd.net"
101 ];
102 };
103 };
104
105 pds-gatekeeper = {
106 enable = false;
107 # we need to share a lot of secrets between pds and gatekeeper
108 environmentFiles = [config.age.secrets.pds.path];
109
110 settings = {
111 GATEKEEPER_PORT = 3602;
112 PDS_BASE_URL = "http://${service.hostName}:${toString service.port}";
113 GATEKEEPER_TRUST_PROXY = "true";
114
115 # make an empty file to prevent early errors due to no pds env
116 # it really wants to load this file but with nix we don't really do it that way
117 PDS_ENV_LOCATION = toString (pkgs.writeText "gatekeeper-pds-env" "");
118 };
119 };
120 };
121
122 system.stateVersion = "25.11";
123 };
124 };
125 };
126}