Our Personal Data Server from scratch! tranquil.farm
pds rust database fun oauth atproto
238
fork

Configure Feed

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

adding k8s docs and a more generalized doc #12

open opened by dsx.sh targeting main from dsx.sh/tranquil-pds: k8s-doc-updates

I recently went through some pain in getting Tranquil all set up on my k8s cluster. Luckily some of my 'biggest' pain was in the mystery of the email server, which has been resolved in the meantime.

Regardless, I added a bunch more detail on a somewhat generic k8s deployment, with some context for secrets, secret generation, etc.

I also set up a "general configuration" doc that took some of the details and context from all the install-x docs and compiled it together into a more generalized information source with all the env vars I could find centralized into one place.

Sorry if this kind of work is already being done elsewhere, I wanted to get this somewhere so I can point friends to so they do their own PDS.

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:6so2rykrnjmzebbr2zbwbqbx/sh.tangled.repo.pull/3mlcn4ezr6p22
+39 -168
Interdiff #1 #2
-154
docs/general-configuration.md
··· 1 - # Tranquil PDS Configuration Reference 2 - 3 - Every option in `example.toml` is overridable by an environment variable. This document focuses on what each setting *does* and why some of them are critical to get right before you start. The install guides (`install-containers.md`, `install-kubernetes.md`, `install-nix.md`) cover the mechanics of standing up the service; this covers what to put in it. 4 - 5 - ## Configuration methods 6 - 7 - The server accepts config in two forms, applied in this order (later wins): 8 - 9 - 1. A TOML file, defaulting to `/etc/tranquil-pds/config.toml`. Override the path with `TRANQUIL_PDS_CONFIG`. 10 - 2. Environment variables. Every TOML key has a corresponding env var documented in `example.toml`. 11 - 12 - In practice: TOML for static, non-sensitive settings; env vars (injected from secrets) for anything that would be unfortunate in a file you may want to commit. 13 - 14 - --- 15 - 16 - ## Required settings 17 - 18 - These have no defaults and the Tranqil will not start without them. 19 - 20 - ### `PDS_HOSTNAME` 21 - 22 - The public hostname of your PDS. Example: `pds.example.com`. 23 - 24 - This is used in DID documents, OAuth metadata, and the `/.well-known/atproto-did` endpoint. Getting it wrong after you have accounts is painful: DIDs are registered against this hostname in the PLC directory, and changing it requires a PLC rotation operation. Set it correctly before creating any accounts. 25 - 26 - If your PDS hostname is also the apex domain (e.g. `example.com` rather than `pds.example.com`), users can have their handle *be* the apex domain (e.g. `@example.com`) since the PDS serves `/.well-known/atproto-did` directly without needing a DNS TXT record. 27 - 28 - ### `DATABASE_URL` 29 - 30 - PostgreSQL connection string. Example: `postgres://tranquil_pds:password@host:5432/pds` 31 - 32 - Tranquil PDS uses Postgres for all persistent state: accounts, repos, sessions, the comms queue, and (when using the default `postgres` repo backend) the full block store for every account's atproto repository. 33 - 34 - --- 35 - 36 - ## Required secret/sensitive env vars 37 - 38 - These three are the most sensitive values in the entire deployment. They should never appear in a committed file, in any circumstance. 39 - 40 - ### `JWT_SECRET` 41 - 42 - Used to sign and verify all session JWTs issued by the PDS. 43 - 44 - Generate: `openssl rand -base64 48` 45 - 46 - ### `DPOP_SECRET` 47 - 48 - Used to validate DPoP (Demonstrating Proof of Possession) proofs in OAuth flows. DPoP is the binding mechanism that ties an OAuth access token to the client's key pair, preventing token theft from replaying the token with a different client. 49 - 50 - Generate: `openssl rand -base64 48` 51 - 52 - ### `MASTER_KEY` 53 - 54 - Used as input to HKDF key derivation and for encrypting per-account signing keys at rest. Every account's atproto repo signing key is derived from or wrapped with this value. 55 - 56 - Generate: `openssl rand -base64 48` 57 - 58 - --- 59 - 60 - ## Handle domains 61 - 62 - ### `PDS_USER_HANDLE_DOMAINS` 63 - 64 - Comma-separated list of domains under which handles are issued. Defaults to `PDS_HOSTNAME` if unset. 65 - 66 - If your PDS hostname is `pds.example.com` but you want handles like `alice.example.com`, set this to `example.com`. You need a wildcard TLS cert and DNS wildcard record for the domain(s) you list here. 67 - 68 - ### `PDS_BANNED_WORDS` 69 - 70 - Comma-separated list of substring patterns blocked at registration time. Useful to prevent handles that would collide with your infrastructure (e.g. `grafana`, `vault`, `relay`). The server has a built-in exact-match reserved list (`admin`, `api`, `git`, `mail`, `ssh`, etc.); this adds substring matches for anything missing from that list. 71 - 72 - ### `INVITE_CODE_REQUIRED` 73 - 74 - Default `true`. Keep this enabled on any public-facing PDS unless you specifically want open registration. Invite codes are generated via the admin API/UI. 75 - 76 - --- 77 - 78 - ## Blob storage 79 - 80 - ### `BLOB_STORAGE_BACKEND` 81 - 82 - `filesystem` (default) or `s3`. Pretty straight forward, and dependent on your personal infrastructure requirements and availability. 83 - 84 - ### `BLOB_STORAGE_PATH` 85 - 86 - Path on disk for the filesystem backend. Default `/var/lib/tranquil-pds/blobs`. 87 - 88 - For S3: set `S3_BUCKET` and optionally `S3_ENDPOINT` for S3-compatible stores (Tigris, Cloudflare R2, MinIO, etc.). 89 - 90 - --- 91 - 92 - ## Federation 93 - 94 - ### `CRAWLERS` 95 - 96 - Comma-separated list of relay URLs to notify when new events are committed to an account's repo. Often `https://bsky.network` for production, this is what allows your PDS to be federated to on the wider network. 97 - 98 - --- 99 - 100 - ## Email 101 - 102 - Email is sorta optional but required for account verification, password resets, and 2FA backup codes. If `MAIL_FROM_ADDRESS` is not set, email sending is disabled entirely. 103 - 104 - Tranquil supports two delivery modes: 105 - 106 - ### Smarthost (SMTP relay) 107 - 108 - Set `MAIL_SMARTHOST_HOST`, `MAIL_SMARTHOST_PORT`, `MAIL_SMARTHOST_USERNAME`, and `MAIL_SMARTHOST_PASSWORD`. Use this if you're routing through a sending service (Postmark, SES, etc.) or a local MTA. 109 - 110 - TLS mode defaults to `starttls`. Setting `tls = "none"` alongside a password is rejected at startup. 111 - 112 - ### DKIM signing (`email.dkim`) 113 - 114 - Set `MAIL_DKIM_SELECTOR`, `MAIL_DKIM_DOMAIN`, and `MAIL_DKIM_KEY_PATH` to sign outgoing mail. The key file should be PEM-format RSA or Ed25519. The corresponding DNS TXT record at `<selector>._domainkey.<domain>` must be published before mail is sent. 115 - 116 - --- 117 - 118 - ## Notifications 119 - 120 - Beyond email, Tranquil supports Discord, Telegram, and Signal for user notifications. Each is opt-in: 121 - 122 - - **Discord**: Set `DISCORD_BOT_TOKEN`. 123 - - **Telegram**: Set `TELEGRAM_BOT_TOKEN` and `TELEGRAM_WEBHOOK_SECRET`. 124 - - **Signal**: Set `SIGNAL_ENABLED=true` after linking a device via the admin API. State is persisted in `signal_*` tables in Postgres. 125 - 126 - --- 127 - 128 - ## SSO 129 - 130 - OAuth SSO is supported for GitHub, Discord, Google, GitLab, generic OIDC, and Apple. Each provider requires `enabled = true`, a `client_id`, and a `client_secret`. GitLab and OIDC also need an `issuer` URL. 131 - 132 - `client_secret` values should be injected as env vars (`SSO_GITHUB_CLIENT_SECRET`, etc.) and not placed in config files. 133 - 134 - --- 135 - 136 - ## Cache backend 137 - 138 - ### `CACHE_BACKEND` 139 - 140 - `ripple` (default) or `valkey`. Ripple is the a built-in in-process gossip cache. Valkey is an external Redis fork with it's own complications. 141 - 142 - --- 143 - 144 - ## Optional keys worth knowing 145 - 146 - | Env var | Default | Notes | 147 - |---|---|---| 148 - | `PLC_ROTATION_KEY` | — | Operator-level PLC rotation key (DID key). If unset, per-user keys are used. | 149 - | `ENABLE_PDS_HOSTED_DID_WEB` | `false` | Opt-in did:web hosting. Long-term commitment — only enable if you intend to serve DID documents indefinitely. | 150 - | `MAX_BLOB_SIZE` | 10 GiB | Per-blob upload limit in bytes. | 151 - | `DISABLE_RATE_LIMITING` | `false` | Only for testing. Avoid in production. | 152 - | `DATABASE_MAX_CONNECTIONS` | 100 | Connection pool ceiling. Tune against your Postgres `max_connections`. | 153 - | `FIREHOSE_BACKFILL_HOURS` | 72 | How many hours of history relays can replay from cursor. | 154 - | `REPO_BACKEND` | `postgres` | `tranquil-store` is the experimental embedded backend. Careful if attempting to run in production. |
+11 -10
docs/install-kubernetes.md
··· 6 6 - a PersistentVolume for blob storage 7 7 - the app itself (it's just a container with some env vars) 8 8 9 - See [configuration.md](configuration.md) for what each env var does and why the secret ones matter. This guide covers the Kubernetes-specific wiring. 9 + See `example.toml` for what each config option does and why the secret ones matter. This guide covers the Kubernetes-specific wiring. 10 10 11 - Simply, the container image expects: 11 + Minimally, the container image expects: 12 12 - A TOML config file mounted at `/etc/tranquil-pds/config.toml` (or passed via `--config`) 13 13 - `DATABASE_URL` - postgres connection string 14 14 - `BLOB_STORAGE_PATH` - path to blob storage (mount a PV here) ··· 20 20 21 21 ## TLS and DNS 22 22 23 - You need a wildcard TLS certificate covering `*.your-pds-hostname.example.com` — user handles resolve as subdomains, so every user's handle requires a matching cert SAN. 23 + You need a wildcard TLS cert covering `*.your-pds-hostname.example.com`. User handles are subdomains, so every handle needs a matching SAN. 24 24 25 25 An approach using Cert Manager would look something like this: 26 26 ··· 37 37 - "*.pds.example.com" 38 38 ``` 39 39 40 - If your PDS hostname is the apex domain (so handles are issued under it, not under a subdomain), include the apex in `dnsNames` alongside the wildcard. 40 + If you're using the apex domain for handles, include it in `dnsNames` alongside the wildcard. 41 41 42 42 --- 43 43 44 44 ## Secrets 45 45 46 - The three secrets primary key secrets (`JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY`) must never appear in a manifest or config file. Inject them as a Kubernetes Secret, sourced from wherever you manage secrets. 46 + The three primary key secrets (`JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY`) must never appear in a manifest or config file. Inject them as a Kubernetes Secret, sourced from wherever you manage secrets. 47 47 48 48 To create the Secret directly and manage rotation manually: 49 49 ··· 67 67 68 68 ## PostgreSQL 69 69 70 - CloudNativePG is an easy recommendation, an example for the purpose of Tranquil PDS: 70 + CloudNativePG works well here. Example cluster config: 71 71 72 72 ```yaml 73 73 apiVersion: postgresql.cnpg.io/v1 ··· 90 90 91 91 The `postgres-user-secret` Secret needs `username` and `password` keys. The password you put here is what goes into `DATABASE_URL`. 92 92 93 - Any standard Postgres setup works in place of CNPG. Tranquil does not require anything special for a basic installation. 93 + Any standard Postgres setup works fine here. 94 94 95 95 --- 96 96 ··· 179 179 claimName: pds-blobs 180 180 ``` 181 181 182 - `SERVER_HOST: "0.0.0.0"` is required — the default `127.0.0.1` isn't reachable by either the Kubelet for health checks or your ingress controller. 182 + `SERVER_HOST: "0.0.0.0"` is required. The default `127.0.0.1` won't be reachable by the Kubelet or your ingress controller. 183 183 184 184 --- 185 185 186 186 ## Ingress 187 187 188 - The ingress rule must match both the PDS hostname itself and the wildcard for user handles. 188 + The ingress needs rules for both the PDS hostname and the wildcard for user handles. 189 189 190 190 ```yaml 191 191 apiVersion: networking.k8s.io/v1 ··· 226 226 227 227 ## Custom homepage 228 228 229 - Mount a ConfigMap with your `homepage.html` into the container's frontend directory and it becomes your landing page. The account dashboard lives at `/app/` so you won't displace it. 229 + Mount a ConfigMap with your `homepage.html` into the container's frontend directory and it becomes your landing page. Go nuts with it. Account dashboard is at `/app/` so you won't break anything. 230 + 230 231 231 232 ```yaml 232 233 apiVersion: v1
+28 -4
example.toml
··· 1 + # Configuration is loaded in this order: 2 + # 3 + # 1. Environment variables (highest priority, always win) 4 + # 2. A custom config file, either passed with --config or the file 5 + # referenced in the TRANQUIL_PDS_CONFIG environment variable 6 + # 3. /etc/tranquil-pds/config.toml (always loaded as a base, even when 7 + # a custom config path is specified) 8 + # 9 + # Useful commands: 10 + # tranquil-pds validate - validate your config without starting 11 + # tranquil-pds config-template - generate a commented example.toml 12 + 1 13 [server] 2 14 # Public hostname of the PDS, such as `pds.example.com`. 3 15 # ··· 142 154 #acquire_timeout_secs = 10 143 155 144 156 [secrets] 145 - # Secret used for signing JWTs. Must be at least 32 characters in 157 + # Secret used for signing session JWTs. Must be at least 32 characters in 146 158 # production. 147 159 # 148 160 # Can also be specified via environment variable `JWT_SECRET`. ··· 355 367 # Default value: 4 356 368 #max_concurrent_repo_exports = 4 357 369 358 - # List of relay / crawler notification URLs. 370 + # List of relay / crawler notification URLs. Notified when new events are 371 + # committed to an account's repo. 372 + # 373 + # Defaults to [ "https://bsky.network" ] when unset. 359 374 # 360 375 # Can also be specified via environment variable `CRAWLERS`. 361 376 #crawlers = 362 377 363 378 [email] 364 - # Sender email address. When unset, email sending is disabled. 379 + # Sender email address. When unset, email sending is disabled entirely. 380 + # 381 + # Email is optional, but at least one comms method (email, Discord, 382 + # Telegram, or Signal) must be set up for account verification, 383 + # password resets, and 2FA backup codes to work. 365 384 # 366 385 # Can also be specified via environment variable `MAIL_FROM_ADDRESS`. 367 386 #from_address = ··· 469 488 #require_tls = false 470 489 471 490 [email.dkim] 472 - # DKIM selector. When unset, outgoing mail is not signed. 491 + # DKIM signing configuration. 492 + # 493 + # The corresponding DNS TXT record at <selector>._domainkey.<domain> must 494 + # be published before mail is sent. 495 + # 496 + # DKIM selector. When unset, outgoing mail is not DKIM-signed. 473 497 # 474 498 # Can also be specified via environment variable `MAIL_DKIM_SELECTOR`. 475 499 #selector =

History

4 rounds 0 comments
sign up or login to add to the discussion
6 commits
expand
adding k8s docs and a more generalized doc
oops didnt actually mean to take that out
removing gen config altogether, agree with nel
expanding example.toml a tiny bit and reworking k8s a little
rewording, and some sentence simplification
more rewording
merge conflicts detected
expand
expand 0 comments
dsx.sh submitted #2
5 commits
expand
adding k8s docs and a more generalized doc
oops didnt actually mean to take that out
removing gen config altogether, agree with nel
expanding example.toml a tiny bit and reworking k8s a little
rewording, and some sentence simplification
expand 0 comments
2 commits
expand
adding k8s docs and a more generalized doc
oops didnt actually mean to take that out
expand 0 comments
dsx.sh submitted #0
1 commit
expand
adding k8s docs and a more generalized doc
expand 0 comments