Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun

fix: set env vars of paths for sendmail & signal from nix pkg not manual

authored by lewis.moe and committed by nel.pet 8a6c5870 320065ca

+22 -28
+9 -15
module.nix
··· 149 149 }; 150 150 151 151 SENDMAIL_PATH = mkOption { 152 - type = types.nullOr types.path; 153 - default = null; 152 + type = types.path; 153 + default = lib.getExe pkgs.system-sendmail; 154 154 description = "Path to the sendmail executable to use for sending emails."; 155 155 }; 156 156 ··· 161 161 }; 162 162 163 163 SIGNAL_CLI_PATH = mkOption { 164 - type = types.nullOr types.path; 165 - default = null; 164 + type = types.path; 165 + default = lib.getExe pkgs.signal-cli; 166 166 description = "Path to the signal-cli executable to use for sending Signal notifications."; 167 167 }; 168 168 ··· 309 309 }) 310 310 311 311 { 312 - services.tranquil-pds.settings = { 313 - SENDMAIL_PATH = lib.mkDefault ( 314 - if cfg.settings.MAIL_FROM_ADDRESS != null then (lib.getExe pkgs.system-sendmail) else null 315 - ); 316 - 317 - SIGNAL_CLI_PATH = lib.mkDefault ( 318 - if cfg.settings.SIGNAL_SENDER_NUMBER != null then (lib.getExe pkgs.signal-cli) else null 319 - ); 320 - }; 321 - 322 312 users.users.${cfg.user} = { 323 313 isSystemUser = true; 324 314 inherit (cfg) group; ··· 359 349 360 350 EnvironmentFile = cfg.environmentFiles; 361 351 Environment = lib.mapAttrsToList (k: v: "${k}=${if builtins.isInt v then toString v else v}") ( 362 - lib.filterAttrs (_: v: v != null) cfg.settings 352 + lib.filterAttrs (k: v: 353 + if k == "SENDMAIL_PATH" then cfg.settings.MAIL_FROM_ADDRESS != null 354 + else if k == "SIGNAL_CLI_PATH" then cfg.settings.SIGNAL_SENDER_NUMBER != null 355 + else v != null 356 + ) cfg.settings 363 357 ); 364 358 365 359 NoNewPrivileges = true;
+13 -13
test.nix
··· 23 23 }; 24 24 25 25 settings = { 26 - PDS_HOSTNAME = "test.local"; 26 + PDS_HOSTNAME = "pds.test"; 27 27 SERVER_HOST = "0.0.0.0"; 28 28 29 29 DISABLE_RATE_LIMITING = 1; ··· 46 46 server.wait_for_open_port(80) 47 47 48 48 def xrpc(method, endpoint, *, headers=None, data=None, raw_body=None, via="nginx"): 49 - host_header = "-H 'Host: test.local'" if via == "nginx" else "" 49 + host_header = "-H 'Host: pds.test'" if via == "nginx" else "" 50 50 base = "http://localhost" if via == "nginx" else "http://localhost:3000" 51 51 url = f"{base}/xrpc/{endpoint}" 52 52 ··· 66 66 return json.loads(xrpc(method, endpoint, **kwargs)) 67 67 68 68 def xrpc_status(endpoint, *, headers=None, via="nginx"): 69 - host_header = "-H 'Host: test.local'" if via == "nginx" else "" 69 + host_header = "-H 'Host: pds.test'" if via == "nginx" else "" 70 70 base = "http://localhost" if via == "nginx" else "http://localhost:3000" 71 71 url = f"{base}/xrpc/{endpoint}" 72 72 ··· 77 77 78 78 return server.succeed(" ".join(parts)).strip() 79 79 80 - def http_status(path, *, host="test.local", via="nginx"): 80 + def http_status(path, *, host="pds.test", via="nginx"): 81 81 base = "http://localhost" if via == "nginx" else "http://localhost:3000" 82 82 return server.succeed( 83 83 f"curl -s -o /dev/null -w '%{{http_code}}' -H 'Host: {host}' '{base}{path}'" 84 84 ).strip() 85 85 86 - def http_get(path, *, host="test.local"): 86 + def http_get(path, *, host="pds.test"): 87 87 return server.succeed( 88 88 f"curl -sf -H 'Host: {host}' 'http://localhost{path}'" 89 89 ) 90 90 91 - def http_header(path, header, *, host="test.local"): 91 + def http_header(path, header, *, host="pds.test"): 92 92 return server.succeed( 93 93 f"curl -sI -H 'Host: {host}' 'http://localhost{path}'" 94 94 f" | grep -i '^{header}:'" ··· 120 120 assert desc.get("inviteCodeRequired") == False 121 121 122 122 with subtest("nginx serves frontend"): 123 - result = server.succeed("curl -sf -H 'Host: test.local' http://localhost/") 123 + result = server.succeed("curl -sf -H 'Host: pds.test' http://localhost/") 124 124 assert "<html" in result.lower() or "<!" in result 125 125 126 126 with subtest("well-known proxied"): ··· 144 144 assert code != "502" and code != "504", f"oauth proxy broken: {code}" 145 145 146 146 with subtest("subdomain routing works"): 147 - code = http_status("/xrpc/_health", host="alice.test.local") 147 + code = http_status("/xrpc/_health", host="alice.pds.test") 148 148 assert code == "200", f"subdomain routing failed: {code}" 149 149 150 150 with subtest("client-metadata.json served with host substitution"): 151 151 meta_raw = http_get("/oauth/client-metadata.json") 152 152 meta = json.loads(meta_raw) 153 153 assert "client_id" in meta, f"no client_id in client-metadata: {meta}" 154 - assert "test.local" in meta_raw, "host substitution did not apply" 154 + assert "pds.test" in meta_raw, "host substitution did not apply" 155 155 156 156 with subtest("static assets location exists"): 157 157 code = http_status("/assets/nonexistent.js") ··· 169 169 170 170 with subtest("create account"): 171 171 account = xrpc_json("POST", "com.atproto.server.createAccount", data={ 172 - "handle": "alice.test.local", 172 + "handle": "alice", 173 173 "password": "NixOS-Test-Pass-99!", 174 - "email": "alice@test.local", 174 + "email": "alice@pds.test", 175 175 "didType": "web", 176 176 }) 177 177 assert "accessJwt" in account, f"no accessJwt: {account}" ··· 191 191 with subtest("get session"): 192 192 session = xrpc_json("GET", "com.atproto.server.getSession", headers=auth) 193 193 assert session["did"] == did 194 - assert session["handle"] == "alice.test.local" 194 + assert session["handle"] == "alice.pds.test", f"unexpected handle: {session['handle']}" 195 195 196 196 with subtest("create record"): 197 197 created = xrpc_json("POST", "com.atproto.repo.createRecord", headers=auth, data={ ··· 232 232 233 233 with subtest("export repo as car"): 234 234 server.succeed( 235 - f"curl -sf -H 'Host: test.local' " 235 + f"curl -sf -H 'Host: pds.test' " 236 236 f"-o /tmp/repo.car " 237 237 f"'http://localhost/xrpc/com.atproto.sync.getRepo?did={did}'" 238 238 )