forked from
lass.pds.lassul.us/superconfig
This repository has no description
4.9 kB
149 lines
1{
2 self,
3 config,
4 pkgs,
5 ...
6}:
7let
8 domain = "pds.lassul.us";
9 cfg = config.services.tranquil-pds;
10 package = self.packages.${pkgs.stdenv.hostPlatform.system}.tranquil-pds;
11 zitadelEnv = config.lass.zitadel.projects.pds.apps.web.envFile.path;
12in
13{
14 # ATProto PDS (Tranquil). Accounts get handles <name>.pds.lassul.us and
15 # log in via id.lassul.us. Accounts are created only by the SSO signup
16 # (no invite code), nginx rejects all other account-creation routes.
17 clan.core.vars.generators.tranquil-pds = {
18 files."secrets.env" = { };
19 runtimeInputs = [ pkgs.openssl ];
20 script = ''
21 for var in JWT_SECRET DPOP_SECRET MASTER_KEY; do
22 echo "$var=$(openssl rand -hex 32)"
23 done > "$out/secrets.env"
24 '';
25 };
26
27 # send-only mailbox on this host's mailserver (configs/mailserver.nix) for
28 # email verification and password resets
29 mailserver.loginAccounts."pds@lassul.us" = {
30 hashedPasswordFile = config.clan.core.vars.generators.mailserver-pds.files."password-hash".path;
31 sendOnly = true;
32 };
33
34 # smarthost.env is an environmentFile of tranquil-pds
35 clan.core.vars.generators.mailserver-pds = {
36 files."smarthost.env" = { };
37 files."password-hash" = { };
38 runtimeInputs = with pkgs; [
39 coreutils
40 mkpasswd
41 pwgen
42 ];
43 script = ''
44 password=$(pwgen -s 32 1)
45 echo "MAIL_SMARTHOST_PASSWORD=$password" > "$out/smarthost.env"
46 mkpasswd -sm bcrypt <<< "$password" > "$out/password-hash"
47 '';
48 };
49
50 services.tranquil-pds = {
51 enable = true;
52 inherit package;
53 database.createLocally = true;
54 environmentFiles = [
55 config.clan.core.vars.generators.tranquil-pds.files."secrets.env".path
56 config.clan.core.vars.generators.mailserver-pds.files."smarthost.env".path
57 zitadelEnv
58 ];
59 settings = {
60 server = {
61 hostname = domain;
62 invite_code_required = false;
63 # certs come from nginx, not caddy
64 enable_caddy_on_demand_tls = false;
65 };
66 frontend.dir = package.frontend;
67 # owner's PLC recovery key (public part)
68 secrets.plc_rotation_key = "did:key:zDnaepKZiYEbndTH9NNqoZUa7mvbhdiF3M9g2MCjtnxhijtUS";
69 email = {
70 from_address = "pds@lassul.us";
71 from_name = "lassul.us PDS";
72 smarthost = {
73 host = "mail.lassul.us";
74 port = 465;
75 tls = "implicit";
76 };
77 };
78 sso.oidc = {
79 issuer = "https://id.lassul.us";
80 display_name = "lassul.us ID";
81 };
82 };
83 };
84
85 systemd.services.tranquil-pds = {
86 wants = [ "zitadel-reconcile.service" ];
87 after = [ "zitadel-reconcile.service" ];
88 # not in settings: the config file is validated at build time, without
89 # the secrets from environmentFiles that these require
90 environment = {
91 MAIL_SMARTHOST_USERNAME = "pds@lassul.us";
92 SSO_OIDC_ENABLED = "true";
93 };
94 };
95
96 # only the users listed here get a project grant and can log in
97 lass.zitadel.projects.pds = {
98 access = "declarative";
99 users = [ "lass@lassul.us" ];
100 apps.web = {
101 redirectUris = [ "https://${domain}/oauth/sso/callback" ];
102 authMethod = "post";
103 envFile = {
104 clientIdVariable = "SSO_OIDC_CLIENT_ID";
105 clientSecretVariable = "SSO_OIDC_CLIENT_SECRET";
106 };
107 };
108 };
109
110 # repos, accounts and keys in postgres, blobs on disk
111 lass.backup.state.tranquil-pds = {
112 folders = [ cfg.dataDir ];
113 preBackup = ''
114 mkdir -p /var/backup/postgresql
115 ${pkgs.sudo}/bin/sudo -u postgres ${config.services.postgresql.package}/bin/pg_dump \
116 tranquil-pds | ${pkgs.gzip}/bin/gzip > /var/backup/postgresql/tranquil-pds.sql.gz
117 '';
118 };
119
120 # handles are served /.well-known/atproto-did on <handle>.pds.lassul.us;
121 # DNS-01 against our own knot, see _acme-challenge.pds.lassul.us in
122 # configs/dns/knot.nix
123 security.acme.certs.${domain} = {
124 extraDomainNames = [ "*.${domain}" ];
125 dnsProvider = "rfc2136";
126 environmentFile = config.clan.core.vars.generators.knot-acme-review.files."lego.env".path;
127 group = config.services.nginx.group;
128 };
129
130 services.nginx.virtualHosts.${domain} = {
131 serverAliases = [ "*.${domain}" ];
132 useACMEHost = domain;
133 forceSSL = true;
134 locations."/" = {
135 proxyPass = "http://${cfg.settings.server.host}:${toString cfg.settings.server.port}";
136 recommendedProxySettings = true;
137 # com.atproto.sync.subscribeRepos
138 proxyWebsockets = true;
139 extraConfig = ''
140 proxy_read_timeout 1d;
141 client_max_body_size 10G;
142 '';
143 };
144 # password, passkey, delegated and migrated (service-auth) accounts
145 locations."= /xrpc/com.atproto.server.createAccount".return = "403";
146 locations."= /xrpc/_account.createPasskeyAccount".return = "403";
147 locations."= /xrpc/_delegation.createDelegatedAccount".return = "403";
148 };
149}