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.

Rename to tranquil PDS, sounds better than bullshit PDS

+744 -590
+2 -2
.env.example
··· 30 30 # Security Secrets 31 31 # ============================================================================= 32 32 # These MUST be set in production (minimum 32 characters each) 33 - # In development, set BSPDS_ALLOW_INSECURE_SECRETS=1 to use defaults 33 + # In development, set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 to use defaults 34 34 # Server-wide secret for OAuth token signing (HS256) 35 35 # JWT_SECRET=your-secure-random-string-at-least-32-chars 36 36 # Secret for DPoP proof validation ··· 38 38 # Key for encrypting user signing keys at rest (AES-256-GCM) 39 39 # MASTER_KEY=your-secure-random-string-at-least-32-chars 40 40 # Set this ONLY in development to allow default/weak secrets 41 - # BSPDS_ALLOW_INSECURE_SECRETS=1 41 + # TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 42 42 # ============================================================================= 43 43 # PLC Directory 44 44 # =============================================================================
+22
.sqlx/query-1add22e111d5eff8beadbd832b4b8146d95da0a0ce8ce31dc9a2f930a26cc9ce.json
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT takedown_ref FROM users WHERE did = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "takedown_ref", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + true 19 + ] 20 + }, 21 + "hash": "1add22e111d5eff8beadbd832b4b8146d95da0a0ce8ce31dc9a2f930a26cc9ce" 22 + }
+61 -61
Cargo.lock
··· 930 930 ] 931 931 932 932 [[package]] 933 - name = "bspds" 934 - version = "0.1.0" 935 - dependencies = [ 936 - "aes-gcm", 937 - "anyhow", 938 - "async-trait", 939 - "aws-config", 940 - "aws-sdk-s3", 941 - "axum", 942 - "base32", 943 - "base64 0.22.1", 944 - "bcrypt", 945 - "bytes", 946 - "chrono", 947 - "cid", 948 - "ctor", 949 - "dotenvy", 950 - "ed25519-dalek", 951 - "futures", 952 - "governor", 953 - "hickory-resolver", 954 - "hkdf", 955 - "hmac", 956 - "image", 957 - "ipld-core", 958 - "iroh-car", 959 - "jacquard", 960 - "jacquard-axum", 961 - "jacquard-repo", 962 - "jsonwebtoken", 963 - "k256", 964 - "metrics", 965 - "metrics-exporter-prometheus", 966 - "multibase", 967 - "multihash", 968 - "p256 0.13.2", 969 - "p384", 970 - "rand 0.8.5", 971 - "redis", 972 - "reqwest", 973 - "serde", 974 - "serde_bytes", 975 - "serde_ipld_dagcbor", 976 - "serde_json", 977 - "sha2", 978 - "sqlx", 979 - "subtle", 980 - "testcontainers", 981 - "testcontainers-modules", 982 - "thiserror 2.0.17", 983 - "tokio", 984 - "tokio-tungstenite", 985 - "tower-http", 986 - "tracing", 987 - "tracing-subscriber", 988 - "urlencoding", 989 - "uuid", 990 - "wiremock", 991 - ] 992 - 993 - [[package]] 994 933 name = "btree-range-map" 995 934 version = "0.7.2" 996 935 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6221 6160 "proc-macro2", 6222 6161 "quote", 6223 6162 "syn 2.0.111", 6163 + ] 6164 + 6165 + [[package]] 6166 + name = "tranquil-pds" 6167 + version = "0.1.0" 6168 + dependencies = [ 6169 + "aes-gcm", 6170 + "anyhow", 6171 + "async-trait", 6172 + "aws-config", 6173 + "aws-sdk-s3", 6174 + "axum", 6175 + "base32", 6176 + "base64 0.22.1", 6177 + "bcrypt", 6178 + "bytes", 6179 + "chrono", 6180 + "cid", 6181 + "ctor", 6182 + "dotenvy", 6183 + "ed25519-dalek", 6184 + "futures", 6185 + "governor", 6186 + "hickory-resolver", 6187 + "hkdf", 6188 + "hmac", 6189 + "image", 6190 + "ipld-core", 6191 + "iroh-car", 6192 + "jacquard", 6193 + "jacquard-axum", 6194 + "jacquard-repo", 6195 + "jsonwebtoken", 6196 + "k256", 6197 + "metrics", 6198 + "metrics-exporter-prometheus", 6199 + "multibase", 6200 + "multihash", 6201 + "p256 0.13.2", 6202 + "p384", 6203 + "rand 0.8.5", 6204 + "redis", 6205 + "reqwest", 6206 + "serde", 6207 + "serde_bytes", 6208 + "serde_ipld_dagcbor", 6209 + "serde_json", 6210 + "sha2", 6211 + "sqlx", 6212 + "subtle", 6213 + "testcontainers", 6214 + "testcontainers-modules", 6215 + "thiserror 2.0.17", 6216 + "tokio", 6217 + "tokio-tungstenite", 6218 + "tower-http", 6219 + "tracing", 6220 + "tracing-subscriber", 6221 + "urlencoding", 6222 + "uuid", 6223 + "wiremock", 6224 6224 ] 6225 6225 6226 6226 [[package]]
+1 -1
Cargo.toml
··· 1 1 [package] 2 - name = "bspds" 2 + name = "tranquil-pds" 3 3 version = "0.1.0" 4 4 edition = "2024" 5 5 [dependencies]
+2 -2
Dockerfile
··· 16 16 RUN touch src/main.rs && cargo build --release 17 17 # Stage 3: Final image 18 18 FROM alpine:3.23 19 - COPY --from=builder /app/target/release/bspds /usr/local/bin/bspds 19 + COPY --from=builder /app/target/release/tranquil-pds /usr/local/bin/tranquil-pds 20 20 COPY --from=builder /app/migrations /app/migrations 21 21 COPY --from=frontend-builder /frontend/dist /app/frontend/dist 22 22 WORKDIR /app ··· 24 24 ENV SERVER_PORT=3000 25 25 ENV FRONTEND_DIR=/app/frontend/dist 26 26 EXPOSE 3000 27 - CMD ["bspds"] 27 + CMD ["tranquil-pds"]
+1 -1
README.md
··· 1 - # BSPDS 1 + # Tranquil PDS 2 2 3 3 A production-grade Personal Data Server (PDS) for the AT Protocol. Drop-in replacement for Bluesky's reference PDS, written in rust with postgres and s3-compatible blob storage. 4 4
+6 -6
deploy/quadlets/bspds-app.container deploy/quadlets/tranquil-pds-app.container
··· 1 1 [Unit] 2 - Description=BSPDS AT Protocol PDS 3 - After=bspds-db.service bspds-minio.service bspds-valkey.service 2 + Description=Tranquil PDS AT Protocol PDS 3 + After=tranquil-pds-db.service tranquil-pds-minio.service tranquil-pds-valkey.service 4 4 [Container] 5 - ContainerName=bspds-app 6 - Image=localhost/bspds:latest 7 - Pod=bspds.pod 8 - EnvironmentFile=/srv/bspds/config/bspds.env 5 + ContainerName=tranquil-pds-app 6 + Image=localhost/tranquil-pds:latest 7 + Pod=tranquil-pds.pod 8 + EnvironmentFile=/srv/tranquil-pds/config/tranquil-pds.env 9 9 Environment=SERVER_HOST=0.0.0.0 10 10 Environment=SERVER_PORT=3000 11 11 Environment=S3_ENDPOINT=http://localhost:9000
-20
deploy/quadlets/bspds-db.container
··· 1 - [Unit] 2 - Description=BSPDS postgres database 3 - [Container] 4 - ContainerName=bspds-db 5 - Image=docker.io/library/postgres:18-alpine 6 - Pod=bspds.pod 7 - Environment=POSTGRES_USER=bspds 8 - Environment=POSTGRES_DB=pds 9 - Secret=bspds-db-password,type=env,target=POSTGRES_PASSWORD 10 - Volume=/srv/bspds/postgres:/var/lib/postgresql/data:Z 11 - HealthCmd=pg_isready -U bspds -d pds 12 - HealthInterval=10s 13 - HealthTimeout=5s 14 - HealthRetries=5 15 - HealthStartPeriod=10s 16 - [Service] 17 - Restart=always 18 - RestartSec=10 19 - [Install] 20 - WantedBy=default.target
+5 -5
deploy/quadlets/bspds-minio.container deploy/quadlets/tranquil-pds-minio.container
··· 1 1 [Unit] 2 - Description=BSPDS minio object storage 2 + Description=Tranquil PDS minio object storage 3 3 [Container] 4 - ContainerName=bspds-minio 4 + ContainerName=tranquil-pds-minio 5 5 Image=docker.io/minio/minio:RELEASE.2025-10-15T17-29-55Z 6 - Pod=bspds.pod 6 + Pod=tranquil-pds.pod 7 7 Environment=MINIO_ROOT_USER=minioadmin 8 - Secret=bspds-minio-password,type=env,target=MINIO_ROOT_PASSWORD 9 - Volume=/srv/bspds/minio:/data:Z 8 + Secret=tranquil-pds-minio-password,type=env,target=MINIO_ROOT_PASSWORD 9 + Volume=/srv/tranquil-pds/minio:/data:Z 10 10 Exec=server /data --console-address :9001 11 11 HealthCmd=curl -f http://localhost:9000/minio/health/live || exit 1 12 12 HealthInterval=30s
-15
deploy/quadlets/bspds-nginx.container
··· 1 - [Unit] 2 - Description=BSPDS nginx reverse proxy 3 - After=bspds-app.service 4 - [Container] 5 - ContainerName=bspds-nginx 6 - Image=docker.io/library/nginx:1.28-alpine 7 - Pod=bspds.pod 8 - Volume=/srv/bspds/config/nginx.conf:/etc/nginx/nginx.conf:ro,Z 9 - Volume=/srv/bspds/certs:/etc/nginx/certs:ro,Z 10 - Volume=/srv/bspds/acme:/var/www/acme:ro,Z 11 - [Service] 12 - Restart=always 13 - RestartSec=10 14 - [Install] 15 - WantedBy=default.target
+4 -4
deploy/quadlets/bspds-valkey.container deploy/quadlets/tranquil-pds-valkey.container
··· 1 1 [Unit] 2 - Description=BSPDS valkey cache 2 + Description=Tranquil PDS valkey cache 3 3 [Container] 4 - ContainerName=bspds-valkey 4 + ContainerName=tranquil-pds-valkey 5 5 Image=docker.io/valkey/valkey:9-alpine 6 - Pod=bspds.pod 7 - Volume=/srv/bspds/valkey:/data:Z 6 + Pod=tranquil-pds.pod 7 + Volume=/srv/tranquil-pds/valkey:/data:Z 8 8 Exec=valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru 9 9 HealthCmd=valkey-cli ping 10 10 HealthInterval=10s
+1 -1
deploy/quadlets/bspds.pod deploy/quadlets/tranquil-pds.pod
··· 1 1 [Pod] 2 - PodName=bspds 2 + PodName=tranquil-pds 3 3 PublishPort=80:80 4 4 PublishPort=443:443 5 5 [Install]
+20
deploy/quadlets/tranquil-pds-db.container
··· 1 + [Unit] 2 + Description=Tranquil PDS postgres database 3 + [Container] 4 + ContainerName=tranquil-pds-db 5 + Image=docker.io/library/postgres:18-alpine 6 + Pod=tranquil-pds.pod 7 + Environment=POSTGRES_USER=tranquil_pds 8 + Environment=POSTGRES_DB=pds 9 + Secret=tranquil-pds-db-password,type=env,target=POSTGRES_PASSWORD 10 + Volume=/srv/tranquil-pds/postgres:/var/lib/postgresql/data:Z 11 + HealthCmd=pg_isready -U tranquil_pds -d pds 12 + HealthInterval=10s 13 + HealthTimeout=5s 14 + HealthRetries=5 15 + HealthStartPeriod=10s 16 + [Service] 17 + Restart=always 18 + RestartSec=10 19 + [Install] 20 + WantedBy=default.target
+15
deploy/quadlets/tranquil-pds-nginx.container
··· 1 + [Unit] 2 + Description=Tranquil PDS nginx reverse proxy 3 + After=tranquil-pds-app.service 4 + [Container] 5 + ContainerName=tranquil-pds-nginx 6 + Image=docker.io/library/nginx:1.28-alpine 7 + Pod=tranquil-pds.pod 8 + Volume=/srv/tranquil-pds/config/nginx.conf:/etc/nginx/nginx.conf:ro,Z 9 + Volume=/srv/tranquil-pds/certs:/etc/nginx/certs:ro,Z 10 + Volume=/srv/tranquil-pds/acme:/var/www/acme:ro,Z 11 + [Service] 12 + Restart=always 13 + RestartSec=10 14 + [Install] 15 + WantedBy=default.target
+6 -6
docker-compose.prod.yml
··· 1 1 services: 2 - bspds: 2 + tranquil-pds: 3 3 build: 4 4 context: . 5 5 dockerfile: Dockerfile 6 - image: bspds:latest 6 + image: tranquil-pds:latest 7 7 restart: unless-stopped 8 8 ports: 9 9 - "127.0.0.1:3000:3000" ··· 11 11 SERVER_HOST: "0.0.0.0" 12 12 SERVER_PORT: "3000" 13 13 PDS_HOSTNAME: "${PDS_HOSTNAME:?PDS_HOSTNAME is required}" 14 - DATABASE_URL: "postgres://bspds:${DB_PASSWORD:?DB_PASSWORD is required}@db:5432/pds" 14 + DATABASE_URL: "postgres://tranquil_pds:${DB_PASSWORD:?DB_PASSWORD is required}@db:5432/pds" 15 15 S3_ENDPOINT: "http://minio:9000" 16 16 AWS_REGION: "us-east-1" 17 17 S3_BUCKET: "pds-blobs" ··· 46 46 image: postgres:18-alpine 47 47 restart: unless-stopped 48 48 environment: 49 - POSTGRES_USER: bspds 49 + POSTGRES_USER: tranquil_pds 50 50 POSTGRES_PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required}" 51 51 POSTGRES_DB: pds 52 52 volumes: 53 53 - postgres_data:/var/lib/postgresql/data 54 54 healthcheck: 55 - test: ["CMD-SHELL", "pg_isready -U bspds -d pds"] 55 + test: ["CMD-SHELL", "pg_isready -U tranquil_pds -d pds"] 56 56 interval: 10s 57 57 timeout: 5s 58 58 retries: 5 ··· 128 128 - ./certs:/etc/nginx/certs:ro 129 129 - acme_challenge:/var/www/acme:ro 130 130 depends_on: 131 - - bspds 131 + - tranquil-pds 132 132 healthcheck: 133 133 test: ["CMD", "nginx", "-t"] 134 134 interval: 30s
+1 -1
docker-compose.yaml
··· 3 3 build: 4 4 context: . 5 5 dockerfile: Dockerfile 6 - image: bspds 6 + image: tranquil-pds 7 7 ports: 8 8 - "3000:3000" 9 9 env_file:
+42 -45
docs/install-alpine.md
··· 1 - # BSPDS Production Installation on Alpine Linux 1 + # Tranquil PDS Production Installation on Alpine Linux 2 2 > **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3 3 4 - This guide covers installing BSPDS on Alpine Linux 3.23 (current stable as of December 2025). 4 + This guide covers installing Tranquil PDS on Alpine Linux 3.23. 5 5 6 6 ## Prerequisites 7 7 - A VPS with at least 2GB RAM and 20GB disk ··· 20 20 source ~/.cargo/env 21 21 rustup default stable 22 22 ``` 23 - This installs the latest stable Rust (1.92+ as of December 2025). Alpine 3.23 also ships Rust 1.91 via `apk add rust cargo` if you prefer system packages. 23 + This installs the latest stable Rust. Alpine also ships Rust via `apk add rust cargo` if you prefer system packages. 24 24 ## 3. Install postgres 25 - Alpine 3.23 includes PostgreSQL 18: 26 25 ```sh 27 26 apk add postgresql postgresql-contrib 28 27 rc-update add postgresql 29 28 /etc/init.d/postgresql setup 30 29 rc-service postgresql start 31 - psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';" 32 - psql -U postgres -c "CREATE DATABASE pds OWNER bspds;" 33 - psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 30 + psql -U postgres -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';" 31 + psql -U postgres -c "CREATE DATABASE pds OWNER tranquil_pds;" 32 + psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" 34 33 ``` 35 34 ## 4. Install minio 36 35 ```sh ··· 78 77 mc mb local/pds-blobs 79 78 ``` 80 79 ## 5. Install valkey 81 - Alpine 3.23 includes Valkey 9: 82 80 ```sh 83 81 apk add valkey 84 82 rc-update add valkey ··· 90 88 export PATH="$HOME/.deno/bin:$PATH" 91 89 echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile 92 90 ``` 93 - ## 7. Clone and Build BSPDS 91 + ## 7. Clone and Build Tranquil PDS 94 92 ```sh 95 93 mkdir -p /opt && cd /opt 96 - git clone https://tangled.org/lewis.moe/bspds-sandbox bspds 97 - cd bspds 94 + git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds 95 + cd tranquil-pds 98 96 cd frontend 99 97 deno task build 100 98 cd .. ··· 103 101 ## 8. Install sqlx-cli and Run Migrations 104 102 ```sh 105 103 cargo install sqlx-cli --no-default-features --features postgres 106 - export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" 104 + export DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" 107 105 sqlx migrate run 108 106 ``` 109 - ## 9. Configure BSPDS 107 + ## 9. Configure Tranquil PDS 110 108 ```sh 111 - mkdir -p /etc/bspds 112 - cp /opt/bspds/.env.example /etc/bspds/bspds.env 113 - chmod 600 /etc/bspds/bspds.env 109 + mkdir -p /etc/tranquil-pds 110 + cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.env 111 + chmod 600 /etc/tranquil-pds/tranquil-pds.env 114 112 ``` 115 - Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with: 113 + Edit `/etc/tranquil-pds/tranquil-pds.env` and fill in your values. Generate secrets with: 116 114 ```sh 117 115 openssl rand -base64 48 118 116 ``` 119 117 ## 10. Create OpenRC Service 120 118 ```sh 121 - adduser -D -H -s /sbin/nologin bspds 122 - cp /opt/bspds/target/release/bspds /usr/local/bin/ 123 - mkdir -p /var/lib/bspds 124 - cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend 125 - chown -R bspds:bspds /var/lib/bspds 126 - cat > /etc/init.d/bspds << 'EOF' 119 + adduser -D -H -s /sbin/nologin tranquil-pds 120 + cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/ 121 + mkdir -p /var/lib/tranquil-pds 122 + cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend 123 + chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds 124 + cat > /etc/init.d/tranquil-pds << 'EOF' 127 125 #!/sbin/openrc-run 128 - name="bspds" 129 - description="BSPDS - AT Protocol PDS" 130 - command="/usr/local/bin/bspds" 131 - command_user="bspds" 126 + name="tranquil-pds" 127 + description="Tranquil PDS - AT Protocol PDS" 128 + command="/usr/local/bin/tranquil-pds" 129 + command_user="tranquil-pds" 132 130 command_background=true 133 131 pidfile="/run/${RC_SVCNAME}.pid" 134 - output_log="/var/log/bspds.log" 135 - error_log="/var/log/bspds.log" 132 + output_log="/var/log/tranquil-pds.log" 133 + error_log="/var/log/tranquil-pds.log" 136 134 depend() { 137 135 need net postgresql minio 138 136 } 139 137 start_pre() { 140 - export FRONTEND_DIR=/var/lib/bspds/frontend 141 - . /etc/bspds/bspds.env 138 + export FRONTEND_DIR=/var/lib/tranquil-pds/frontend 139 + . /etc/tranquil-pds/tranquil-pds.env 142 140 export SERVER_HOST SERVER_PORT PDS_HOSTNAME DATABASE_URL 143 141 export S3_ENDPOINT AWS_REGION S3_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY 144 142 export VALKEY_URL JWT_SECRET DPOP_SECRET MASTER_KEY CRAWLERS 145 143 } 146 144 EOF 147 - chmod +x /etc/init.d/bspds 148 - rc-update add bspds 149 - rc-service bspds start 145 + chmod +x /etc/init.d/tranquil-pds 146 + rc-update add tranquil-pds 147 + rc-service tranquil-pds start 150 148 ``` 151 149 ## 11. Install and Configure nginx 152 - Alpine 3.23 includes nginx 1.28: 153 150 ```sh 154 151 apk add nginx certbot certbot-nginx 155 - cat > /etc/nginx/http.d/bspds.conf << 'EOF' 152 + cat > /etc/nginx/http.d/tranquil-pds.conf << 'EOF' 156 153 server { 157 154 listen 80; 158 155 listen [::]:80; ··· 217 214 ``` 218 215 ## 14. Verify Installation 219 216 ```sh 220 - rc-service bspds status 217 + rc-service tranquil-pds status 221 218 curl -s https://pds.example.com/xrpc/_health 222 219 curl -s https://pds.example.com/.well-known/atproto-did 223 220 ``` 224 221 ## Maintenance 225 222 View logs: 226 223 ```sh 227 - tail -f /var/log/bspds.log 224 + tail -f /var/log/tranquil-pds.log 228 225 ``` 229 - Update BSPDS: 226 + Update Tranquil PDS: 230 227 ```sh 231 - cd /opt/bspds 228 + cd /opt/tranquil-pds 232 229 git pull 233 230 cd frontend && deno task build && cd .. 234 231 cargo build --release 235 - rc-service bspds stop 236 - cp target/release/bspds /usr/local/bin/ 237 - cp -r frontend/dist /var/lib/bspds/frontend 238 - DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run 239 - rc-service bspds start 232 + rc-service tranquil-pds stop 233 + cp target/release/tranquil-pds /usr/local/bin/ 234 + cp -r frontend/dist /var/lib/tranquil-pds/frontend 235 + DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" sqlx migrate run 236 + rc-service tranquil-pds start 240 237 ``` 241 238 Backup database: 242 239 ```sh
+77 -77
docs/install-containers.md
··· 1 - # BSPDS Containerized Production Deployment 1 + # Tranquil PDS Containerized Production Deployment 2 2 > **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3 - This guide covers deploying BSPDS using containers with podman. 3 + This guide covers deploying Tranquil PDS using containers with podman. 4 4 - **Debian 13+**: Uses systemd quadlets (modern, declarative container management) 5 5 - **Alpine 3.23+**: Uses OpenRC service script with podman-compose 6 6 ## Prerequisites ··· 39 39 ## 2. Create Directory Structure 40 40 ```bash 41 41 mkdir -p /etc/containers/systemd 42 - mkdir -p /srv/bspds/{postgres,minio,valkey,certs,acme,config} 42 + mkdir -p /srv/tranquil-pds/{postgres,minio,valkey,certs,acme,config} 43 43 ``` 44 44 ## 3. Create Environment File 45 45 ```bash 46 - cp /opt/bspds/.env.example /srv/bspds/config/bspds.env 47 - chmod 600 /srv/bspds/config/bspds.env 46 + cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env 47 + chmod 600 /srv/tranquil-pds/config/tranquil-pds.env 48 48 ``` 49 - Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with: 49 + Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with: 50 50 ```bash 51 51 openssl rand -base64 48 52 52 ``` ··· 54 54 ## 4. Install Quadlet Definitions 55 55 Copy the quadlet files from the repository: 56 56 ```bash 57 - cp /opt/bspds/deploy/quadlets/*.pod /etc/containers/systemd/ 58 - cp /opt/bspds/deploy/quadlets/*.container /etc/containers/systemd/ 57 + cp /opt/tranquil-pds/deploy/quadlets/*.pod /etc/containers/systemd/ 58 + cp /opt/tranquil-pds/deploy/quadlets/*.container /etc/containers/systemd/ 59 59 ``` 60 60 Note: Systemd doesn't support shell-style variable expansion in `Environment=` lines. The quadlet files expect DATABASE_URL to be set in the environment file. 61 61 ## 5. Create nginx Configuration 62 62 ```bash 63 - cp /opt/bspds/deploy/nginx/nginx-quadlet.conf /srv/bspds/config/nginx.conf 63 + cp /opt/tranquil-pds/deploy/nginx/nginx-quadlet.conf /srv/tranquil-pds/config/nginx.conf 64 64 ``` 65 - ## 6. Build BSPDS Image 65 + ## 6. Build Tranquil PDS Image 66 66 ```bash 67 67 cd /opt 68 - git clone https://tangled.org/lewis.moe/bspds-sandbox bspds 69 - cd bspds 70 - podman build -t bspds:latest . 68 + git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds 69 + cd tranquil-pds 70 + podman build -t tranquil-pds:latest . 71 71 ``` 72 72 ## 7. Create Podman Secrets 73 73 ```bash 74 - source /srv/bspds/config/bspds.env 75 - echo "$DB_PASSWORD" | podman secret create bspds-db-password - 76 - echo "$MINIO_ROOT_PASSWORD" | podman secret create bspds-minio-password - 74 + source /srv/tranquil-pds/config/tranquil-pds.env 75 + echo "$DB_PASSWORD" | podman secret create tranquil-pds-db-password - 76 + echo "$MINIO_ROOT_PASSWORD" | podman secret create tranquil-pds-minio-password - 77 77 ``` 78 78 ## 8. Start Services and Initialize 79 79 ```bash 80 80 systemctl daemon-reload 81 - systemctl start bspds-db bspds-minio bspds-valkey 81 + systemctl start tranquil-pds-db tranquil-pds-minio tranquil-pds-valkey 82 82 sleep 10 83 83 ``` 84 84 85 85 Create the minio bucket: 86 86 ```bash 87 - podman run --rm --pod bspds \ 87 + podman run --rm --pod tranquil-pds \ 88 88 -e MINIO_ROOT_USER=minioadmin \ 89 89 -e MINIO_ROOT_PASSWORD=your-minio-password \ 90 90 docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ ··· 94 94 Run migrations: 95 95 ```bash 96 96 cargo install sqlx-cli --no-default-features --features postgres 97 - DATABASE_URL="postgres://bspds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/bspds/migrations 97 + DATABASE_URL="postgres://tranquil_pds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/tranquil-pds/migrations 98 98 ``` 99 99 ## 9. Obtain Wildcard SSL Certificate 100 100 User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. ··· 102 102 Create temporary self-signed cert to start services: 103 103 ```bash 104 104 openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 105 - -keyout /srv/bspds/certs/privkey.pem \ 106 - -out /srv/bspds/certs/fullchain.pem \ 105 + -keyout /srv/tranquil-pds/certs/privkey.pem \ 106 + -out /srv/tranquil-pds/certs/fullchain.pem \ 107 107 -subj "/CN=pds.example.com" 108 - systemctl start bspds-app bspds-nginx 108 + systemctl start tranquil-pds-app tranquil-pds-nginx 109 109 ``` 110 110 111 111 Get a wildcard certificate using DNS validation: 112 112 ```bash 113 113 podman run --rm -it \ 114 - -v /srv/bspds/certs:/etc/letsencrypt:Z \ 114 + -v /srv/tranquil-pds/certs:/etc/letsencrypt:Z \ 115 115 docker.io/certbot/certbot:v5.2.2 certonly \ 116 116 --manual --preferred-challenges dns \ 117 117 -d pds.example.com -d '*.pds.example.com' \ ··· 123 123 124 124 Link certificates and restart: 125 125 ```bash 126 - ln -sf /srv/bspds/certs/live/pds.example.com/fullchain.pem /srv/bspds/certs/fullchain.pem 127 - ln -sf /srv/bspds/certs/live/pds.example.com/privkey.pem /srv/bspds/certs/privkey.pem 128 - systemctl restart bspds-nginx 126 + ln -sf /srv/tranquil-pds/certs/live/pds.example.com/fullchain.pem /srv/tranquil-pds/certs/fullchain.pem 127 + ln -sf /srv/tranquil-pds/certs/live/pds.example.com/privkey.pem /srv/tranquil-pds/certs/privkey.pem 128 + systemctl restart tranquil-pds-nginx 129 129 ``` 130 130 ## 10. Enable All Services 131 131 ```bash 132 - systemctl enable bspds-db bspds-minio bspds-valkey bspds-app bspds-nginx 132 + systemctl enable tranquil-pds-db tranquil-pds-minio tranquil-pds-valkey tranquil-pds-app tranquil-pds-nginx 133 133 ``` 134 134 ## 11. Configure Firewall 135 135 ```bash ··· 142 142 ## 12. Certificate Renewal 143 143 Add to root's crontab (`crontab -e`): 144 144 ``` 145 - 0 0 * * * podman run --rm -v /srv/bspds/certs:/etc/letsencrypt:Z -v /srv/bspds/acme:/var/www/acme:Z docker.io/certbot/certbot:v5.2.2 renew --quiet && systemctl reload bspds-nginx 145 + 0 0 * * * podman run --rm -v /srv/tranquil-pds/certs:/etc/letsencrypt:Z -v /srv/tranquil-pds/acme:/var/www/acme:Z docker.io/certbot/certbot:v5.2.2 renew --quiet && systemctl reload tranquil-pds-nginx 146 146 ``` 147 147 --- 148 148 # Alpine 3.23+ with OpenRC ··· 161 161 ``` 162 162 ## 2. Create Directory Structure 163 163 ```sh 164 - mkdir -p /srv/bspds/{data,config} 165 - mkdir -p /srv/bspds/data/{postgres,minio,valkey,certs,acme} 164 + mkdir -p /srv/tranquil-pds/{data,config} 165 + mkdir -p /srv/tranquil-pds/data/{postgres,minio,valkey,certs,acme} 166 166 ``` 167 167 ## 3. Clone Repository and Build 168 168 ```sh 169 169 cd /opt 170 - git clone https://tangled.org/lewis.moe/bspds-sandbox bspds 171 - cd bspds 172 - podman build -t bspds:latest . 170 + git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds 171 + cd tranquil-pds 172 + podman build -t tranquil-pds:latest . 173 173 ``` 174 174 ## 4. Create Environment File 175 175 ```sh 176 - cp /opt/bspds/.env.example /srv/bspds/config/bspds.env 177 - chmod 600 /srv/bspds/config/bspds.env 176 + cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env 177 + chmod 600 /srv/tranquil-pds/config/tranquil-pds.env 178 178 ``` 179 - Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with: 179 + Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with: 180 180 ```sh 181 181 openssl rand -base64 48 182 182 ``` 183 183 ## 5. Set Up Compose and nginx 184 184 Copy the production compose and nginx configs: 185 185 ```sh 186 - cp /opt/bspds/docker-compose.prod.yml /srv/bspds/docker-compose.yml 187 - cp /opt/bspds/nginx.prod.conf /srv/bspds/config/nginx.conf 186 + cp /opt/tranquil-pds/docker-compose.prod.yml /srv/tranquil-pds/docker-compose.yml 187 + cp /opt/tranquil-pds/nginx.prod.conf /srv/tranquil-pds/config/nginx.conf 188 188 ``` 189 - Edit `/srv/bspds/docker-compose.yml` to adjust paths if needed: 190 - - Update volume mounts to use `/srv/bspds/data/` paths 191 - - Update nginx cert paths to match `/srv/bspds/data/certs/` 192 - Edit `/srv/bspds/config/nginx.conf` to update cert paths: 189 + Edit `/srv/tranquil-pds/docker-compose.yml` to adjust paths if needed: 190 + - Update volume mounts to use `/srv/tranquil-pds/data/` paths 191 + - Update nginx cert paths to match `/srv/tranquil-pds/data/certs/` 192 + Edit `/srv/tranquil-pds/config/nginx.conf` to update cert paths: 193 193 - Change `/etc/nginx/certs/live/${PDS_HOSTNAME}/` to `/etc/nginx/certs/` 194 194 ## 6. Create OpenRC Service 195 195 ```sh 196 - cat > /etc/init.d/bspds << 'EOF' 196 + cat > /etc/init.d/tranquil-pds << 'EOF' 197 197 #!/sbin/openrc-run 198 - name="bspds" 199 - description="BSPDS AT Protocol PDS (containerized)" 198 + name="tranquil-pds" 199 + description="Tranquil PDS AT Protocol PDS (containerized)" 200 200 command="/usr/bin/podman-compose" 201 - command_args="-f /srv/bspds/docker-compose.yml up" 201 + command_args="-f /srv/tranquil-pds/docker-compose.yml up" 202 202 command_background=true 203 203 pidfile="/run/${RC_SVCNAME}.pid" 204 - directory="/srv/bspds" 204 + directory="/srv/tranquil-pds" 205 205 depend() { 206 206 need net podman 207 207 after firewall 208 208 } 209 209 start_pre() { 210 210 set -a 211 - . /srv/bspds/config/bspds.env 211 + . /srv/tranquil-pds/config/tranquil-pds.env 212 212 set +a 213 213 } 214 214 stop() { 215 215 ebegin "Stopping ${name}" 216 - cd /srv/bspds 216 + cd /srv/tranquil-pds 217 217 set -a 218 - . /srv/bspds/config/bspds.env 218 + . /srv/tranquil-pds/config/tranquil-pds.env 219 219 set +a 220 - podman-compose -f /srv/bspds/docker-compose.yml down 220 + podman-compose -f /srv/tranquil-pds/docker-compose.yml down 221 221 eend $? 222 222 } 223 223 EOF 224 - chmod +x /etc/init.d/bspds 224 + chmod +x /etc/init.d/tranquil-pds 225 225 ``` 226 226 ## 7. Initialize Services 227 227 Start services: 228 228 ```sh 229 - rc-service bspds start 229 + rc-service tranquil-pds start 230 230 sleep 15 231 231 ``` 232 232 233 233 Create the minio bucket: 234 234 ```sh 235 - source /srv/bspds/config/bspds.env 236 - podman run --rm --network bspds_default \ 235 + source /srv/tranquil-pds/config/tranquil-pds.env 236 + podman run --rm --network tranquil-pds_default \ 237 237 -e MINIO_ROOT_USER="$MINIO_ROOT_USER" \ 238 238 -e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \ 239 239 docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ ··· 246 246 rustup-init -y 247 247 source ~/.cargo/env 248 248 cargo install sqlx-cli --no-default-features --features postgres 249 - DB_IP=$(podman inspect bspds-db-1 --format '{{.NetworkSettings.Networks.bspds_default.IPAddress}}') 250 - DATABASE_URL="postgres://bspds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/bspds/migrations 249 + DB_IP=$(podman inspect tranquil-pds-db-1 --format '{{.NetworkSettings.Networks.tranquil-pds_default.IPAddress}}') 250 + DATABASE_URL="postgres://tranquil_pds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/tranquil-pds/migrations 251 251 ``` 252 252 ## 8. Obtain Wildcard SSL Certificate 253 253 User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. ··· 255 255 Create temporary self-signed cert to start services: 256 256 ```sh 257 257 openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 258 - -keyout /srv/bspds/data/certs/privkey.pem \ 259 - -out /srv/bspds/data/certs/fullchain.pem \ 258 + -keyout /srv/tranquil-pds/data/certs/privkey.pem \ 259 + -out /srv/tranquil-pds/data/certs/fullchain.pem \ 260 260 -subj "/CN=pds.example.com" 261 - rc-service bspds restart 261 + rc-service tranquil-pds restart 262 262 ``` 263 263 264 264 Get a wildcard certificate using DNS validation: 265 265 ```sh 266 266 podman run --rm -it \ 267 - -v /srv/bspds/data/certs:/etc/letsencrypt \ 267 + -v /srv/tranquil-pds/data/certs:/etc/letsencrypt \ 268 268 docker.io/certbot/certbot:v5.2.2 certonly \ 269 269 --manual --preferred-challenges dns \ 270 270 -d pds.example.com -d '*.pds.example.com' \ ··· 274 274 275 275 Link certificates and restart: 276 276 ```sh 277 - ln -sf /srv/bspds/data/certs/live/pds.example.com/fullchain.pem /srv/bspds/data/certs/fullchain.pem 278 - ln -sf /srv/bspds/data/certs/live/pds.example.com/privkey.pem /srv/bspds/data/certs/privkey.pem 279 - rc-service bspds restart 277 + ln -sf /srv/tranquil-pds/data/certs/live/pds.example.com/fullchain.pem /srv/tranquil-pds/data/certs/fullchain.pem 278 + ln -sf /srv/tranquil-pds/data/certs/live/pds.example.com/privkey.pem /srv/tranquil-pds/data/certs/privkey.pem 279 + rc-service tranquil-pds restart 280 280 ``` 281 281 ## 9. Enable Service at Boot 282 282 ```sh 283 - rc-update add bspds 283 + rc-update add tranquil-pds 284 284 ``` 285 285 ## 10. Configure Firewall 286 286 ```sh ··· 305 305 ## 11. Certificate Renewal 306 306 Add to root's crontab (`crontab -e`): 307 307 ``` 308 - 0 0 * * * podman run --rm -v /srv/bspds/data/certs:/etc/letsencrypt -v /srv/bspds/data/acme:/var/www/acme docker.io/certbot/certbot:v5.2.2 renew --quiet && rc-service bspds restart 308 + 0 0 * * * podman run --rm -v /srv/tranquil-pds/data/certs:/etc/letsencrypt -v /srv/tranquil-pds/data/acme:/var/www/acme docker.io/certbot/certbot:v5.2.2 renew --quiet && rc-service tranquil-pds restart 309 309 ``` 310 310 --- 311 311 # Verification and Maintenance ··· 317 317 ## View Logs 318 318 **Debian:** 319 319 ```bash 320 - journalctl -u bspds-app -f 321 - podman logs -f bspds-app 320 + journalctl -u tranquil-pds-app -f 321 + podman logs -f tranquil-pds-app 322 322 ``` 323 323 **Alpine:** 324 324 ```sh 325 - podman-compose -f /srv/bspds/docker-compose.yml logs -f 326 - podman logs -f bspds-bspds-1 325 + podman-compose -f /srv/tranquil-pds/docker-compose.yml logs -f 326 + podman logs -f tranquil-pds-tranquil-pds-1 327 327 ``` 328 - ## Update BSPDS 328 + ## Update Tranquil PDS 329 329 ```sh 330 - cd /opt/bspds 330 + cd /opt/tranquil-pds 331 331 git pull 332 - podman build -t bspds:latest . 332 + podman build -t tranquil-pds:latest . 333 333 ``` 334 334 335 335 Debian: 336 336 ```bash 337 - systemctl restart bspds-app 337 + systemctl restart tranquil-pds-app 338 338 ``` 339 339 340 340 Alpine: 341 341 ```sh 342 - rc-service bspds restart 342 + rc-service tranquil-pds restart 343 343 ``` 344 344 ## Backup Database 345 345 **Debian:** 346 346 ```bash 347 - podman exec bspds-db pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql 347 + podman exec tranquil-pds-db pg_dump -U tranquil_pds pds > /var/backups/pds-$(date +%Y%m%d).sql 348 348 ``` 349 349 **Alpine:** 350 350 ```sh 351 - podman exec bspds-db-1 pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql 351 + podman exec tranquil-pds-db-1 pg_dump -U tranquil_pds pds > /var/backups/pds-$(date +%Y%m%d).sql 352 352 ```
+40 -43
docs/install-debian.md
··· 1 - # BSPDS Production Installation on Debian 1 + # Tranquil PDS Production Installation on Debian 2 2 > **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3 3 4 - This guide covers installing BSPDS on Debian 13 "Trixie" (current stable as of December 2025). 4 + This guide covers installing Tranquil PDS on Debian 13 "Trixie". 5 5 6 6 ## Prerequisites 7 7 - A VPS with at least 2GB RAM and 20GB disk ··· 19 19 source ~/.cargo/env 20 20 rustup default stable 21 21 ``` 22 - This installs the latest stable Rust (1.92+ as of December 2025). 22 + This installs the latest stable Rust. 23 23 ## 3. Install postgres 24 - Debian 13 includes PostgreSQL 17: 25 24 ```bash 26 25 apt install -y postgresql postgresql-contrib 27 26 systemctl enable postgresql 28 27 systemctl start postgresql 29 - sudo -u postgres psql -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';" 30 - sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;" 31 - sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 28 + sudo -u postgres psql -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';" 29 + sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;" 30 + sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" 32 31 ``` 33 32 ## 4. Install minio 34 33 ```bash ··· 71 70 mc mb local/pds-blobs 72 71 ``` 73 72 ## 5. Install valkey 74 - Debian 13 includes Valkey 8: 75 73 ```bash 76 74 apt install -y valkey 77 75 systemctl enable valkey-server ··· 83 81 export PATH="$HOME/.deno/bin:$PATH" 84 82 echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc 85 83 ``` 86 - ## 7. Clone and Build BSPDS 84 + ## 7. Clone and Build Tranquil PDS 87 85 ```bash 88 86 cd /opt 89 - git clone https://tangled.org/lewis.moe/bspds-sandbox bspds 90 - cd bspds 87 + git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds 88 + cd tranquil-pds 91 89 cd frontend 92 90 deno task build 93 91 cd .. ··· 96 94 ## 8. Install sqlx-cli and Run Migrations 97 95 ```bash 98 96 cargo install sqlx-cli --no-default-features --features postgres 99 - export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" 97 + export DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" 100 98 sqlx migrate run 101 99 ``` 102 - ## 9. Configure BSPDS 100 + ## 9. Configure Tranquil PDS 103 101 ```bash 104 - mkdir -p /etc/bspds 105 - cp /opt/bspds/.env.example /etc/bspds/bspds.env 106 - chmod 600 /etc/bspds/bspds.env 102 + mkdir -p /etc/tranquil-pds 103 + cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.env 104 + chmod 600 /etc/tranquil-pds/tranquil-pds.env 107 105 ``` 108 - Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with: 106 + Edit `/etc/tranquil-pds/tranquil-pds.env` and fill in your values. Generate secrets with: 109 107 ```bash 110 108 openssl rand -base64 48 111 109 ``` 112 110 ## 10. Create Systemd Service 113 111 ```bash 114 - useradd -r -s /sbin/nologin bspds 115 - cp /opt/bspds/target/release/bspds /usr/local/bin/ 116 - mkdir -p /var/lib/bspds 117 - cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend 118 - chown -R bspds:bspds /var/lib/bspds 119 - cat > /etc/systemd/system/bspds.service << 'EOF' 112 + useradd -r -s /sbin/nologin tranquil-pds 113 + cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/ 114 + mkdir -p /var/lib/tranquil-pds 115 + cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend 116 + chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds 117 + cat > /etc/systemd/system/tranquil-pds.service << 'EOF' 120 118 [Unit] 121 - Description=BSPDS - AT Protocol PDS 119 + Description=Tranquil PDS - AT Protocol PDS 122 120 After=network.target postgresql.service minio.service 123 121 [Service] 124 122 Type=simple 125 - User=bspds 126 - Group=bspds 127 - EnvironmentFile=/etc/bspds/bspds.env 128 - Environment=FRONTEND_DIR=/var/lib/bspds/frontend 129 - ExecStart=/usr/local/bin/bspds 123 + User=tranquil-pds 124 + Group=tranquil-pds 125 + EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env 126 + Environment=FRONTEND_DIR=/var/lib/tranquil-pds/frontend 127 + ExecStart=/usr/local/bin/tranquil-pds 130 128 Restart=always 131 129 RestartSec=5 132 130 [Install] 133 131 WantedBy=multi-user.target 134 132 EOF 135 133 systemctl daemon-reload 136 - systemctl enable bspds 137 - systemctl start bspds 134 + systemctl enable tranquil-pds 135 + systemctl start tranquil-pds 138 136 ``` 139 137 ## 11. Install and Configure nginx 140 - Debian 13 includes nginx 1.26: 141 138 ```bash 142 139 apt install -y nginx certbot python3-certbot-nginx 143 - cat > /etc/nginx/sites-available/bspds << 'EOF' 140 + cat > /etc/nginx/sites-available/tranquil-pds << 'EOF' 144 141 server { 145 142 listen 80; 146 143 listen [::]:80; ··· 158 155 } 159 156 } 160 157 EOF 161 - ln -s /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/ 158 + ln -s /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/ 162 159 rm -f /etc/nginx/sites-enabled/default 163 160 nginx -t 164 161 systemctl reload nginx ··· 192 189 ``` 193 190 ## 14. Verify Installation 194 191 ```bash 195 - systemctl status bspds 192 + systemctl status tranquil-pds 196 193 curl -s https://pds.example.com/xrpc/_health | jq 197 194 curl -s https://pds.example.com/.well-known/atproto-did 198 195 ``` 199 196 ## Maintenance 200 197 View logs: 201 198 ```bash 202 - journalctl -u bspds -f 199 + journalctl -u tranquil-pds -f 203 200 ``` 204 - Update BSPDS: 201 + Update Tranquil PDS: 205 202 ```bash 206 - cd /opt/bspds 203 + cd /opt/tranquil-pds 207 204 git pull 208 205 cd frontend && deno task build && cd .. 209 206 cargo build --release 210 - systemctl stop bspds 211 - cp target/release/bspds /usr/local/bin/ 212 - cp -r frontend/dist /var/lib/bspds/frontend 213 - DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run 214 - systemctl start bspds 207 + systemctl stop tranquil-pds 208 + cp target/release/tranquil-pds /usr/local/bin/ 209 + cp -r frontend/dist /var/lib/tranquil-pds/frontend 210 + DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" sqlx migrate run 211 + systemctl start tranquil-pds 215 212 ``` 216 213 Backup database: 217 214 ```bash
+1 -1
docs/install-kubernetes.md
··· 1 - # BSPDS on Kubernetes 1 + # Tranquil PDS on Kubernetes 2 2 3 3 If you're reaching for kubernetes for this app, you're experienced enough to know how to spin up: 4 4
+36 -37
docs/install-openbsd.md
··· 1 - # BSPDS Production Installation on OpenBSD 1 + # Tranquil PDS Production Installation on OpenBSD 2 2 > **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3 - This guide covers installing BSPDS on OpenBSD 7.8 (current release as of December 2025). 3 + This guide covers installing Tranquil PDS on OpenBSD 7.8. 4 4 ## Prerequisites 5 5 - A VPS with at least 2GB RAM and 20GB disk 6 6 - A domain name pointing to your server's IP ··· 16 16 ```sh 17 17 pkg_add rust 18 18 ``` 19 - OpenBSD 7.8 ships Rust 1.82+. For the latest stable (1.92+), use rustup: 19 + OpenBSD ships Rust in ports. For the latest stable, use rustup: 20 20 ```sh 21 21 pkg_add rustup 22 22 rustup-init -y ··· 24 24 rustup default stable 25 25 ``` 26 26 ## 3. Install postgres 27 - OpenBSD 7.8 includes PostgreSQL 17 (PostgreSQL 18 may not yet be in ports): 28 27 ```sh 29 28 pkg_add postgresql-server postgresql-client 30 29 mkdir -p /var/postgresql/data ··· 32 31 su - _postgresql -c "initdb -D /var/postgresql/data -U postgres -A scram-sha-256" 33 32 rcctl enable postgresql 34 33 rcctl start postgresql 35 - psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';" 36 - psql -U postgres -c "CREATE DATABASE pds OWNER bspds;" 37 - psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 34 + psql -U postgres -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';" 35 + psql -U postgres -c "CREATE DATABASE pds OWNER tranquil_pds;" 36 + psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" 38 37 ``` 39 38 ## 4. Install minio 40 39 OpenBSD doesn't have a minio package. Options: ··· 93 92 export PATH="$HOME/.deno/bin:$PATH" 94 93 echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile 95 94 ``` 96 - ## 7. Clone and Build BSPDS 95 + ## 7. Clone and Build Tranquil PDS 97 96 ```sh 98 97 mkdir -p /opt && cd /opt 99 - git clone https://tangled.org/lewis.moe/bspds-sandbox bspds 100 - cd bspds 98 + git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds 99 + cd tranquil-pds 101 100 cd frontend 102 101 deno task build 103 102 cd .. ··· 106 105 ## 8. Install sqlx-cli and Run Migrations 107 106 ```sh 108 107 cargo install sqlx-cli --no-default-features --features postgres 109 - export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" 108 + export DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" 110 109 sqlx migrate run 111 110 ``` 112 - ## 9. Configure BSPDS 111 + ## 9. Configure Tranquil PDS 113 112 ```sh 114 - mkdir -p /etc/bspds 115 - cp /opt/bspds/.env.example /etc/bspds/bspds.conf 116 - chmod 600 /etc/bspds/bspds.conf 113 + mkdir -p /etc/tranquil-pds 114 + cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.conf 115 + chmod 600 /etc/tranquil-pds/tranquil-pds.conf 117 116 ``` 118 - Edit `/etc/bspds/bspds.conf` and fill in your values. Generate secrets with: 117 + Edit `/etc/tranquil-pds/tranquil-pds.conf` and fill in your values. Generate secrets with: 119 118 ```sh 120 119 openssl rand -base64 48 121 120 ``` 122 121 ## 10. Create rc.d Service 123 122 ```sh 124 - useradd -d /var/empty -s /sbin/nologin _bspds 125 - cp /opt/bspds/target/release/bspds /usr/local/bin/ 126 - mkdir -p /var/bspds 127 - cp -r /opt/bspds/frontend/dist /var/bspds/frontend 128 - chown -R _bspds:_bspds /var/bspds 129 - cat > /etc/rc.d/bspds << 'EOF' 123 + useradd -d /var/empty -s /sbin/nologin _tranquil_pds 124 + cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/ 125 + mkdir -p /var/tranquil-pds 126 + cp -r /opt/tranquil-pds/frontend/dist /var/tranquil-pds/frontend 127 + chown -R _tranquil_pds:_tranquil_pds /var/tranquil-pds 128 + cat > /etc/rc.d/tranquil_pds << 'EOF' 130 129 #!/bin/ksh 131 - daemon="/usr/local/bin/bspds" 132 - daemon_user="_bspds" 130 + daemon="/usr/local/bin/tranquil-pds" 131 + daemon_user="_tranquil_pds" 133 132 daemon_logger="daemon.info" 134 133 . /etc/rc.d/rc.subr 135 134 rc_pre() { 136 - export FRONTEND_DIR=/var/bspds/frontend 135 + export FRONTEND_DIR=/var/tranquil-pds/frontend 137 136 while IFS='=' read -r key value; do 138 137 case "$key" in 139 138 \#*|"") continue ;; 140 139 esac 141 140 export "$key=$value" 142 - done < /etc/bspds/bspds.conf 141 + done < /etc/tranquil-pds/tranquil-pds.conf 143 142 } 144 143 rc_cmd $1 145 144 EOF 146 - chmod +x /etc/rc.d/bspds 147 - rcctl enable bspds 148 - rcctl start bspds 145 + chmod +x /etc/rc.d/tranquil_pds 146 + rcctl enable tranquil_pds 147 + rcctl start tranquil_pds 149 148 ``` 150 149 ## 11. Install and Configure nginx 151 150 ```sh ··· 227 226 ``` 228 227 ## 14. Verify Installation 229 228 ```sh 230 - rcctl check bspds 229 + rcctl check tranquil_pds 231 230 ftp -o - https://pds.example.com/xrpc/_health 232 231 ftp -o - https://pds.example.com/.well-known/atproto-did 233 232 ``` ··· 236 235 ```sh 237 236 tail -f /var/log/daemon 238 237 ``` 239 - Update BSPDS: 238 + Update Tranquil PDS: 240 239 ```sh 241 - cd /opt/bspds 240 + cd /opt/tranquil-pds 242 241 git pull 243 242 cd frontend && deno task build && cd .. 244 243 cargo build --release 245 - rcctl stop bspds 246 - cp target/release/bspds /usr/local/bin/ 247 - cp -r frontend/dist /var/bspds/frontend 248 - DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run 249 - rcctl start bspds 244 + rcctl stop tranquil_pds 245 + cp target/release/tranquil-pds /usr/local/bin/ 246 + cp -r frontend/dist /var/tranquil-pds/frontend 247 + DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" sqlx migrate run 248 + rcctl start tranquil_pds 250 249 ``` 251 250 Backup database: 252 251 ```sh
+1 -1
frontend/index.html
··· 3 3 <head> 4 4 <meta charset="UTF-8" /> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>BSPDS</title> 6 + <title>Tranquil PDS</title> 7 7 <style> 8 8 html { background: #fafafa; } 9 9 @media (prefers-color-scheme: dark) { html { background: #1a1a1a; } }
+1 -1
frontend/package.json
··· 1 1 { 2 - "name": "bspds-frontend", 2 + "name": "tranquil-pds-frontend", 3 3 "private": true, 4 4 "version": "0.0.0", 5 5 "type": "module",
+8 -8
frontend/src/lib/api.ts
··· 255 255 signalNumber: string | null 256 256 signalVerified: boolean 257 257 }> { 258 - return xrpc('com.bspds.account.getNotificationPrefs', { token }) 258 + return xrpc('com.tranquil.account.getNotificationPrefs', { token }) 259 259 }, 260 260 261 261 async updateNotificationPrefs(token: string, prefs: { ··· 264 264 telegramUsername?: string 265 265 signalNumber?: string 266 266 }): Promise<{ success: boolean }> { 267 - return xrpc('com.bspds.account.updateNotificationPrefs', { 267 + return xrpc('com.tranquil.account.updateNotificationPrefs', { 268 268 method: 'POST', 269 269 token, 270 270 body: prefs, ··· 272 272 }, 273 273 274 274 async confirmChannelVerification(token: string, channel: string, code: string): Promise<{ success: boolean }> { 275 - return xrpc('com.bspds.account.confirmChannelVerification', { 275 + return xrpc('com.tranquil.account.confirmChannelVerification', { 276 276 method: 'POST', 277 277 token, 278 278 body: { channel, code }, ··· 289 289 body: string 290 290 }> 291 291 }> { 292 - return xrpc('com.bspds.account.getNotificationHistory', { token }) 292 + return xrpc('com.tranquil.account.getNotificationHistory', { token }) 293 293 }, 294 294 295 295 async getServerStats(token: string): Promise<{ ··· 298 298 recordCount: number 299 299 blobStorageBytes: number 300 300 }> { 301 - return xrpc('com.bspds.admin.getServerStats', { token }) 301 + return xrpc('com.tranquil.admin.getServerStats', { token }) 302 302 }, 303 303 304 304 async changePassword(token: string, currentPassword: string, newPassword: string): Promise<void> { 305 - await xrpc('com.bspds.account.changePassword', { 305 + await xrpc('com.tranquil.account.changePassword', { 306 306 method: 'POST', 307 307 token, 308 308 body: { currentPassword, newPassword }, ··· 317 317 isCurrent: boolean 318 318 }> 319 319 }> { 320 - return xrpc('com.bspds.account.listSessions', { token }) 320 + return xrpc('com.tranquil.account.listSessions', { token }) 321 321 }, 322 322 323 323 async revokeSession(token: string, sessionId: string): Promise<void> { 324 - await xrpc('com.bspds.account.revokeSession', { 324 + await xrpc('com.tranquil.account.revokeSession', { 325 325 method: 'POST', 326 326 token, 327 327 body: { sessionId },
+2 -2
frontend/src/lib/auth.svelte.ts
··· 1 1 import { api, type Session, type CreateAccountParams, type CreateAccountResult, ApiError } from './api' 2 2 import { startOAuthLogin, handleOAuthCallback, checkForOAuthCallback, clearOAuthCallbackParams, refreshOAuthToken } from './oauth' 3 3 4 - const STORAGE_KEY = 'bspds_session' 5 - const ACCOUNTS_KEY = 'bspds_accounts' 4 + const STORAGE_KEY = 'tranquil_pds_session' 5 + const ACCOUNTS_KEY = 'tranquil_pds_accounts' 6 6 7 7 export interface SavedAccount { 8 8 did: string
+2 -2
frontend/src/lib/oauth.ts
··· 1 - const OAUTH_STATE_KEY = 'bspds_oauth_state' 2 - const OAUTH_VERIFIER_KEY = 'bspds_oauth_verifier' 1 + const OAUTH_STATE_KEY = 'tranquil_pds_oauth_state' 2 + const OAUTH_VERIFIER_KEY = 'tranquil_pds_oauth_verifier' 3 3 4 4 interface OAuthState { 5 5 state: string
+1 -1
frontend/src/routes/Register.svelte
··· 3 3 import { navigate } from '../lib/router.svelte' 4 4 import { api, ApiError, type VerificationChannel } from '../lib/api' 5 5 6 - const STORAGE_KEY = 'bspds_pending_verification' 6 + const STORAGE_KEY = 'tranquil_pds_pending_verification' 7 7 8 8 let handle = $state('') 9 9 let email = $state('')
+1 -1
frontend/src/routes/Verify.svelte
··· 2 2 import { confirmSignup, resendVerification, getAuthState } from '../lib/auth.svelte' 3 3 import { navigate } from '../lib/router.svelte' 4 4 5 - const STORAGE_KEY = 'bspds_pending_verification' 5 + const STORAGE_KEY = 'tranquil_pds_pending_verification' 6 6 7 7 interface PendingVerification { 8 8 did: string
+3 -3
frontend/src/tests/Dashboard.test.ts
··· 10 10 setupAuthenticatedUser, 11 11 setupUnauthenticatedUser, 12 12 } from './mocks' 13 - const STORAGE_KEY = 'bspds_session' 13 + const STORAGE_KEY = 'tranquil_pds_session' 14 14 describe('Dashboard', () => { 15 15 beforeEach(() => { 16 16 clearMocks() ··· 38 38 await waitFor(() => { 39 39 expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument() 40 40 expect(screen.getByRole('heading', { name: /account overview/i })).toBeInTheDocument() 41 - expect(screen.getByText(/@testuser\.test\.bspds\.dev/)).toBeInTheDocument() 42 - expect(screen.getByText(/did:web:test\.bspds\.dev:u:testuser/)).toBeInTheDocument() 41 + expect(screen.getByText(/@testuser\.test\.tranquil\.dev/)).toBeInTheDocument() 42 + expect(screen.getByText(/did:web:test\.tranquil\.dev:u:testuser/)).toBeInTheDocument() 43 43 expect(screen.getByText('test@example.com')).toBeInTheDocument() 44 44 expect(screen.getByText('Verified')).toBeInTheDocument() 45 45 expect(screen.getByText('Verified')).toHaveClass('badge', 'success')
+2 -2
frontend/src/tests/Login.test.ts
··· 95 95 json: async () => ({ 96 96 error: 'AccountNotVerified', 97 97 message: 'Account not verified', 98 - did: 'did:web:test.bspds.dev:u:testuser', 98 + did: 'did:web:test.tranquil.dev:u:testuser', 99 99 }), 100 100 })) 101 101 render(Login) ··· 116 116 json: async () => ({ 117 117 error: 'AccountNotVerified', 118 118 message: 'Account not verified', 119 - did: 'did:web:test.bspds.dev:u:testuser', 119 + did: 'did:web:test.tranquil.dev:u:testuser', 120 120 }), 121 121 })) 122 122 render(Login)
+27 -27
frontend/src/tests/Notifications.test.ts
··· 28 28 describe('page structure', () => { 29 29 beforeEach(() => { 30 30 setupAuthenticatedUser() 31 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 31 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 32 32 jsonResponse(mockData.notificationPrefs()) 33 33 ) 34 34 }) ··· 48 48 setupAuthenticatedUser() 49 49 }) 50 50 it('shows loading text while fetching preferences', async () => { 51 - mockEndpoint('com.bspds.account.getNotificationPrefs', async () => { 51 + mockEndpoint('com.tranquil.account.getNotificationPrefs', async () => { 52 52 await new Promise(resolve => setTimeout(resolve, 100)) 53 53 return jsonResponse(mockData.notificationPrefs()) 54 54 }) ··· 61 61 setupAuthenticatedUser() 62 62 }) 63 63 it('displays all four channel options', async () => { 64 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 64 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 65 65 jsonResponse(mockData.notificationPrefs()) 66 66 ) 67 67 render(Notifications) ··· 73 73 }) 74 74 }) 75 75 it('email channel is always selectable', async () => { 76 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 76 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 77 77 jsonResponse(mockData.notificationPrefs()) 78 78 ) 79 79 render(Notifications) ··· 83 83 }) 84 84 }) 85 85 it('discord channel is disabled when not configured', async () => { 86 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 86 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 87 87 jsonResponse(mockData.notificationPrefs({ discordId: null })) 88 88 ) 89 89 render(Notifications) ··· 93 93 }) 94 94 }) 95 95 it('discord channel is enabled when configured', async () => { 96 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 96 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 97 97 jsonResponse(mockData.notificationPrefs({ discordId: '123456789' })) 98 98 ) 99 99 render(Notifications) ··· 103 103 }) 104 104 }) 105 105 it('shows hint for disabled channels', async () => { 106 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 106 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 107 107 jsonResponse(mockData.notificationPrefs()) 108 108 ) 109 109 render(Notifications) ··· 112 112 }) 113 113 }) 114 114 it('selects current preferred channel', async () => { 115 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 115 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 116 116 jsonResponse(mockData.notificationPrefs({ preferredChannel: 'email' })) 117 117 ) 118 118 render(Notifications) ··· 127 127 setupAuthenticatedUser() 128 128 }) 129 129 it('displays email as readonly with current value', async () => { 130 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 130 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 131 131 jsonResponse(mockData.notificationPrefs()) 132 132 ) 133 133 render(Notifications) ··· 138 138 }) 139 139 }) 140 140 it('displays all channel inputs with current values', async () => { 141 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 141 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 142 142 jsonResponse(mockData.notificationPrefs({ 143 143 discordId: '123456789', 144 144 telegramUsername: 'testuser', ··· 158 158 setupAuthenticatedUser() 159 159 }) 160 160 it('shows Primary badge for email', async () => { 161 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 161 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 162 162 jsonResponse(mockData.notificationPrefs()) 163 163 ) 164 164 render(Notifications) ··· 167 167 }) 168 168 }) 169 169 it('shows Verified badge for verified discord', async () => { 170 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 170 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 171 171 jsonResponse(mockData.notificationPrefs({ 172 172 discordId: '123456789', 173 173 discordVerified: true, ··· 180 180 }) 181 181 }) 182 182 it('shows Not verified badge for unverified discord', async () => { 183 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 183 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 184 184 jsonResponse(mockData.notificationPrefs({ 185 185 discordId: '123456789', 186 186 discordVerified: false, ··· 192 192 }) 193 193 }) 194 194 it('does not show badge when channel not configured', async () => { 195 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 195 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 196 196 jsonResponse(mockData.notificationPrefs()) 197 197 ) 198 198 render(Notifications) ··· 208 208 }) 209 209 it('calls updateNotificationPrefs with correct data', async () => { 210 210 let capturedBody: Record<string, unknown> | null = null 211 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 211 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 212 212 jsonResponse(mockData.notificationPrefs()) 213 213 ) 214 - mockEndpoint('com.bspds.account.updateNotificationPrefs', (_url, options) => { 214 + mockEndpoint('com.tranquil.account.updateNotificationPrefs', (_url, options) => { 215 215 capturedBody = JSON.parse((options?.body as string) || '{}') 216 216 return jsonResponse({ success: true }) 217 217 }) ··· 228 228 }) 229 229 }) 230 230 it('shows loading state while saving', async () => { 231 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 231 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 232 232 jsonResponse(mockData.notificationPrefs()) 233 233 ) 234 - mockEndpoint('com.bspds.account.updateNotificationPrefs', async () => { 234 + mockEndpoint('com.tranquil.account.updateNotificationPrefs', async () => { 235 235 await new Promise(resolve => setTimeout(resolve, 100)) 236 236 return jsonResponse({ success: true }) 237 237 }) ··· 244 244 expect(screen.getByRole('button', { name: /saving/i })).toBeDisabled() 245 245 }) 246 246 it('shows success message after saving', async () => { 247 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 247 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 248 248 jsonResponse(mockData.notificationPrefs()) 249 249 ) 250 - mockEndpoint('com.bspds.account.updateNotificationPrefs', () => 250 + mockEndpoint('com.tranquil.account.updateNotificationPrefs', () => 251 251 jsonResponse({ success: true }) 252 252 ) 253 253 render(Notifications) ··· 260 260 }) 261 261 }) 262 262 it('shows error when save fails', async () => { 263 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 263 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 264 264 jsonResponse(mockData.notificationPrefs()) 265 265 ) 266 - mockEndpoint('com.bspds.account.updateNotificationPrefs', () => 266 + mockEndpoint('com.tranquil.account.updateNotificationPrefs', () => 267 267 errorResponse('InvalidRequest', 'Invalid channel configuration', 400) 268 268 ) 269 269 render(Notifications) ··· 278 278 }) 279 279 it('reloads preferences after successful save', async () => { 280 280 let loadCount = 0 281 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => { 281 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => { 282 282 loadCount++ 283 283 return jsonResponse(mockData.notificationPrefs()) 284 284 }) 285 - mockEndpoint('com.bspds.account.updateNotificationPrefs', () => 285 + mockEndpoint('com.tranquil.account.updateNotificationPrefs', () => 286 286 jsonResponse({ success: true }) 287 287 ) 288 288 render(Notifications) ··· 301 301 setupAuthenticatedUser() 302 302 }) 303 303 it('enables discord channel after entering discord ID', async () => { 304 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 304 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 305 305 jsonResponse(mockData.notificationPrefs()) 306 306 ) 307 307 render(Notifications) ··· 314 314 }) 315 315 }) 316 316 it('allows selecting a configured channel', async () => { 317 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 317 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 318 318 jsonResponse(mockData.notificationPrefs({ 319 319 discordId: '123456789', 320 320 discordVerified: true, ··· 334 334 setupAuthenticatedUser() 335 335 }) 336 336 it('shows error when loading preferences fails', async () => { 337 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 337 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 338 338 errorResponse('InternalError', 'Database connection failed', 500) 339 339 ) 340 340 render(Notifications)
+2 -2
frontend/src/tests/Settings.test.ts
··· 176 176 it('displays current handle', async () => { 177 177 render(Settings) 178 178 await waitFor(() => { 179 - expect(screen.getByText(/current: @testuser\.test\.bspds\.dev/i)).toBeInTheDocument() 179 + expect(screen.getByText(/current: @testuser\.test\.tranquil\.dev/i)).toBeInTheDocument() 180 180 }) 181 181 }) 182 182 it('calls updateHandle with new handle', async () => { ··· 314 314 await waitFor(() => { 315 315 expect(capturedBody?.token).toBe('DEL123') 316 316 expect(capturedBody?.password).toBe('mypassword') 317 - expect(capturedBody?.did).toBe('did:web:test.bspds.dev:u:testuser') 317 + expect(capturedBody?.did).toBe('did:web:test.tranquil.dev:u:testuser') 318 318 }) 319 319 }) 320 320 it('navigates to login after successful deletion', async () => {
+8 -8
frontend/src/tests/mocks.ts
··· 85 85 } 86 86 export const mockData = { 87 87 session: (overrides?: Partial<Session>): Session => ({ 88 - did: 'did:web:test.bspds.dev:u:testuser', 89 - handle: 'testuser.test.bspds.dev', 88 + did: 'did:web:test.tranquil.dev:u:testuser', 89 + handle: 'testuser.test.tranquil.dev', 90 90 email: 'test@example.com', 91 91 emailConfirmed: true, 92 92 accessJwt: 'mock-access-jwt-token', ··· 102 102 code: 'test-invite-123', 103 103 available: 1, 104 104 disabled: false, 105 - forAccount: 'did:web:test.bspds.dev:u:testuser', 106 - createdBy: 'did:web:test.bspds.dev:u:testuser', 105 + forAccount: 'did:web:test.tranquil.dev:u:testuser', 106 + createdBy: 'did:web:test.tranquil.dev:u:testuser', 107 107 createdAt: new Date().toISOString(), 108 108 uses: [], 109 109 ...overrides, ··· 120 120 ...overrides, 121 121 }), 122 122 describeServer: () => ({ 123 - availableUserDomains: ['test.bspds.dev'], 123 + availableUserDomains: ['test.tranquil.dev'], 124 124 inviteCodeRequired: false, 125 125 links: { 126 126 privacyPolicy: 'https://example.com/privacy', ··· 128 128 }, 129 129 }), 130 130 describeRepo: (did: string) => ({ 131 - handle: 'testuser.test.bspds.dev', 131 + handle: 'testuser.test.tranquil.dev', 132 132 did, 133 133 didDoc: {}, 134 134 collections: ['app.bsky.feed.post', 'app.bsky.feed.like', 'app.bsky.graph.follow'], ··· 173 173 mockEndpoint('com.atproto.server.createInviteCode', () => 174 174 jsonResponse({ code: 'new-invite-' + Date.now() }) 175 175 ) 176 - mockEndpoint('com.bspds.account.getNotificationPrefs', () => 176 + mockEndpoint('com.tranquil.account.getNotificationPrefs', () => 177 177 jsonResponse(mockData.notificationPrefs()) 178 178 ) 179 - mockEndpoint('com.bspds.account.updateNotificationPrefs', () => 179 + mockEndpoint('com.tranquil.account.updateNotificationPrefs', () => 180 180 jsonResponse({ success: true }) 181 181 ) 182 182 mockEndpoint('com.atproto.server.requestEmailUpdate', () =>
+1 -1
justfile
··· 25 25 ./scripts/run-tests.sh --test {{file}} 26 26 # Run tests with testcontainers (slower, no shared infra) 27 27 test-standalone: 28 - BSPDS_ALLOW_INSECURE_SECRETS=1 cargo test 28 + TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 cargo test 29 29 # Manually manage test infrastructure (for debugging) 30 30 test-infra-start: 31 31 ./scripts/test-infra.sh start
+4 -4
nginx.prod.conf
··· 34 34 ssl_session_tickets off; 35 35 ssl_stapling on; 36 36 ssl_stapling_verify on; 37 - upstream bspds { 38 - server bspds:3000; 37 + upstream tranquil-pds { 38 + server tranquil-pds:3000; 39 39 keepalive 32; 40 40 } 41 41 server { ··· 57 57 ssl_certificate_key /etc/nginx/certs/live/${PDS_HOSTNAME}/privkey.pem; 58 58 client_max_body_size 100M; 59 59 location / { 60 - proxy_pass http://bspds; 60 + proxy_pass http://tranquil-pds; 61 61 proxy_http_version 1.1; 62 62 proxy_set_header Upgrade $http_upgrade; 63 63 proxy_set_header Connection "upgrade"; ··· 71 71 proxy_request_buffering off; 72 72 } 73 73 location /xrpc/com.atproto.sync.subscribeRepos { 74 - proxy_pass http://bspds; 74 + proxy_pass http://tranquil-pds; 75 75 proxy_http_version 1.1; 76 76 proxy_set_header Upgrade $http_upgrade; 77 77 proxy_set_header Connection "upgrade";
+1 -1
observability/prometheus.yml
··· 7 7 static_configs: 8 8 - targets: ['localhost:9090'] 9 9 10 - - job_name: 'bspds' 10 + - job_name: 'tranquil-pds' 11 11 static_configs: 12 12 - targets: ['app:3000'] 13 13 metrics_path: /metrics
+75 -75
scripts/install-debian.sh
··· 24 24 nuke_installation() { 25 25 log_warn "NUKING EXISTING INSTALLATION" 26 26 log_info "Stopping services..." 27 - systemctl stop bspds 2>/dev/null || true 28 - systemctl disable bspds 2>/dev/null || true 27 + systemctl stop tranquil-pds 2>/dev/null || true 28 + systemctl disable tranquil-pds 2>/dev/null || true 29 29 30 - log_info "Removing BSPDS files..." 31 - rm -rf /opt/bspds 32 - rm -rf /var/lib/bspds 33 - rm -f /usr/local/bin/bspds 34 - rm -f /usr/local/bin/bspds-sendmail 35 - rm -f /usr/local/bin/bspds-mailq 36 - rm -rf /var/spool/bspds-mail 37 - rm -f /etc/systemd/system/bspds.service 30 + log_info "Removing Tranquil PDS files..." 31 + rm -rf /opt/tranquil-pds 32 + rm -rf /var/lib/tranquil-pds 33 + rm -f /usr/local/bin/tranquil-pds 34 + rm -f /usr/local/bin/tranquil-pds-sendmail 35 + rm -f /usr/local/bin/tranquil-pds-mailq 36 + rm -rf /var/spool/tranquil-pds-mail 37 + rm -f /etc/systemd/system/tranquil-pds.service 38 38 systemctl daemon-reload 39 39 40 - log_info "Removing BSPDS configuration..." 41 - rm -rf /etc/bspds 40 + log_info "Removing Tranquil PDS configuration..." 41 + rm -rf /etc/tranquil-pds 42 42 43 43 log_info "Dropping postgres database and user..." 44 44 sudo -u postgres psql -c "DROP DATABASE IF EXISTS pds;" 2>/dev/null || true 45 - sudo -u postgres psql -c "DROP USER IF EXISTS bspds;" 2>/dev/null || true 45 + sudo -u postgres psql -c "DROP USER IF EXISTS tranquil_pds;" 2>/dev/null || true 46 46 47 47 log_info "Removing minio bucket..." 48 48 if command -v mc &>/dev/null; then ··· 54 54 rm -f /etc/default/minio 2>/dev/null || true 55 55 56 56 log_info "Removing nginx config..." 57 - rm -f /etc/nginx/sites-enabled/bspds 58 - rm -f /etc/nginx/sites-available/bspds 57 + rm -f /etc/nginx/sites-enabled/tranquil-pds 58 + rm -f /etc/nginx/sites-available/tranquil-pds 59 59 systemctl reload nginx 2>/dev/null || true 60 60 61 61 log_success "Previous installation nuked" 62 62 } 63 63 64 - if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/bspds ]]; then 64 + if [[ -f /etc/tranquil-pds/tranquil-pds.env ]] || [[ -d /opt/tranquil-pds ]] || [[ -f /usr/local/bin/tranquil-pds ]]; then 65 65 log_warn "Existing installation detected" 66 66 echo "" 67 67 echo "Options:" ··· 76 76 echo "" 77 77 log_warn "This will DELETE:" 78 78 echo " - PostgreSQL database 'pds' and all data" 79 - echo " - All BSPDS configuration and credentials" 80 - echo " - All source code in /opt/bspds" 79 + echo " - All Tranquil PDS configuration and credentials" 80 + echo " - All source code in /opt/tranquil-pds" 81 81 echo " - MinIO bucket 'pds-blobs' and all blobs" 82 82 echo "" 83 83 read -p "Type 'NUKE' to confirm: " CONFIRM_NUKE ··· 102 102 fi 103 103 104 104 echo "" 105 - log_info "BSPDS Installation Script for Debian" 105 + log_info "Tranquil PDS Installation Script for Debian" 106 106 echo "" 107 107 108 108 get_public_ips() { ··· 142 142 exit 0 143 143 fi 144 144 145 - CREDENTIALS_FILE="/etc/bspds/.credentials" 145 + CREDENTIALS_FILE="/etc/tranquil-pds/.credentials" 146 146 if [[ -f "$CREDENTIALS_FILE" ]]; then 147 147 log_info "Loading existing credentials..." 148 148 source "$CREDENTIALS_FILE" ··· 154 154 DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) 155 155 MINIO_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) 156 156 157 - mkdir -p /etc/bspds 157 + mkdir -p /etc/tranquil-pds 158 158 cat > "$CREDENTIALS_FILE" << EOF 159 159 JWT_SECRET="$JWT_SECRET" 160 160 DPOP_SECRET="$DPOP_SECRET" ··· 196 196 apt install -y postgresql postgresql-contrib 197 197 systemctl enable postgresql 198 198 systemctl start postgresql 199 - sudo -u postgres psql -c "CREATE USER bspds WITH PASSWORD '${DB_PASSWORD}';" 2>/dev/null || \ 200 - sudo -u postgres psql -c "ALTER USER bspds WITH PASSWORD '${DB_PASSWORD}';" 201 - sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;" 2>/dev/null || true 202 - sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 199 + sudo -u postgres psql -c "CREATE USER tranquil_pds WITH PASSWORD '${DB_PASSWORD}';" 2>/dev/null || \ 200 + sudo -u postgres psql -c "ALTER USER tranquil_pds WITH PASSWORD '${DB_PASSWORD}';" 201 + sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;" 2>/dev/null || true 202 + sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" 203 203 log_success "postgres configured" 204 204 205 205 log_info "Installing valkey..." ··· 292 292 grep -q 'deno/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc 293 293 fi 294 294 295 - log_info "Cloning BSPDS..." 296 - if [[ ! -d /opt/bspds ]]; then 297 - git clone https://tangled.org/lewis.moe/bspds-sandbox /opt/bspds 295 + log_info "Cloning Tranquil PDS..." 296 + if [[ ! -d /opt/tranquil-pds ]]; then 297 + git clone https://tangled.org/lewis.moe/bspds-sandbox /opt/tranquil-pds 298 298 else 299 - cd /opt/bspds && git pull 299 + cd /opt/tranquil-pds && git pull 300 300 fi 301 - cd /opt/bspds 301 + cd /opt/tranquil-pds 302 302 303 303 log_info "Building frontend..." 304 304 "$HOME/.deno/bin/deno" task build --filter=frontend 305 305 log_success "Frontend built" 306 306 307 - log_info "Building BSPDS (this takes a while)..." 307 + log_info "Building Tranquil PDS (this takes a while)..." 308 308 source "$HOME/.cargo/env" 309 309 if [[ $TOTAL_MEM_KB -lt 4000000 ]]; then 310 310 log_info "Low memory - limiting parallel jobs" ··· 312 312 else 313 313 cargo build --release 314 314 fi 315 - log_success "BSPDS built" 315 + log_success "Tranquil PDS built" 316 316 317 317 log_info "Running migrations..." 318 318 cargo install sqlx-cli --no-default-features --features postgres 319 - export DATABASE_URL="postgres://bspds:${DB_PASSWORD}@localhost:5432/pds" 319 + export DATABASE_URL="postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds" 320 320 "$HOME/.cargo/bin/sqlx" migrate run 321 321 log_success "Migrations complete" 322 322 323 323 log_info "Setting up mail trap..." 324 - mkdir -p /var/spool/bspds-mail 325 - chmod 1777 /var/spool/bspds-mail 324 + mkdir -p /var/spool/tranquil-pds-mail 325 + chmod 1777 /var/spool/tranquil-pds-mail 326 326 327 - cat > /usr/local/bin/bspds-sendmail << 'SENDMAIL_EOF' 327 + cat > /usr/local/bin/tranquil-pds-sendmail << 'SENDMAIL_EOF' 328 328 #!/bin/bash 329 - MAIL_DIR="/var/spool/bspds-mail" 329 + MAIL_DIR="/var/spool/tranquil-pds-mail" 330 330 TIMESTAMP=$(date +%Y%m%d-%H%M%S) 331 331 RANDOM_ID=$(head -c 4 /dev/urandom | xxd -p) 332 332 MAIL_FILE="${MAIL_DIR}/${TIMESTAMP}-${RANDOM_ID}.eml" 333 333 mkdir -p "$MAIL_DIR" 334 334 { 335 - echo "X-BSPDS-Received: $(date -Iseconds)" 336 - echo "X-BSPDS-Args: $*" 335 + echo "X-Tranquil-PDS-Received: $(date -Iseconds)" 336 + echo "X-Tranquil-PDS-Args: $*" 337 337 echo "" 338 338 cat 339 339 } > "$MAIL_FILE" 340 340 chmod 644 "$MAIL_FILE" 341 341 exit 0 342 342 SENDMAIL_EOF 343 - chmod +x /usr/local/bin/bspds-sendmail 343 + chmod +x /usr/local/bin/tranquil-pds-sendmail 344 344 345 - cat > /usr/local/bin/bspds-mailq << 'MAILQ_EOF' 345 + cat > /usr/local/bin/tranquil-pds-mailq << 'MAILQ_EOF' 346 346 #!/bin/bash 347 - MAIL_DIR="/var/spool/bspds-mail" 347 + MAIL_DIR="/var/spool/tranquil-pds-mail" 348 348 case "${1:-list}" in 349 349 list) 350 350 ls -lt "$MAIL_DIR"/*.eml 2>/dev/null | head -20 || echo "No emails" ··· 365 365 [[ -f "$f" ]] && cat "$f" || echo "Not found" 366 366 ;; 367 367 *) 368 - [[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: bspds-mailq [list|latest|clear|count|N]" 368 + [[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: tranquil-pds-mailq [list|latest|clear|count|N]" 369 369 ;; 370 370 esac 371 371 MAILQ_EOF 372 - chmod +x /usr/local/bin/bspds-mailq 372 + chmod +x /usr/local/bin/tranquil-pds-mailq 373 373 374 - log_info "Creating BSPDS configuration..." 375 - cat > /etc/bspds/bspds.env << EOF 374 + log_info "Creating Tranquil PDS configuration..." 375 + cat > /etc/tranquil-pds/tranquil-pds.env << EOF 376 376 SERVER_HOST=127.0.0.1 377 377 SERVER_PORT=3000 378 378 PDS_HOSTNAME=${PDS_DOMAIN} 379 - DATABASE_URL=postgres://bspds:${DB_PASSWORD}@localhost:5432/pds 379 + DATABASE_URL=postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds 380 380 DATABASE_MAX_CONNECTIONS=100 381 381 DATABASE_MIN_CONNECTIONS=10 382 382 S3_ENDPOINT=http://localhost:9000 ··· 392 392 CRAWLERS=https://bsky.network 393 393 AVAILABLE_USER_DOMAINS=${PDS_DOMAIN} 394 394 MAIL_FROM_ADDRESS=noreply@${PDS_DOMAIN} 395 - MAIL_FROM_NAME=BSPDS 396 - SENDMAIL_PATH=/usr/local/bin/bspds-sendmail 395 + MAIL_FROM_NAME=Tranquil PDS 396 + SENDMAIL_PATH=/usr/local/bin/tranquil-pds-sendmail 397 397 EOF 398 - chmod 600 /etc/bspds/bspds.env 398 + chmod 600 /etc/tranquil-pds/tranquil-pds.env 399 399 400 - log_info "Installing BSPDS..." 401 - id -u bspds &>/dev/null || useradd -r -s /sbin/nologin bspds 402 - cp /opt/bspds/target/release/bspds /usr/local/bin/ 403 - mkdir -p /var/lib/bspds 404 - cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend 405 - chown -R bspds:bspds /var/lib/bspds 400 + log_info "Installing Tranquil PDS..." 401 + id -u tranquil-pds &>/dev/null || useradd -r -s /sbin/nologin tranquil-pds 402 + cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/ 403 + mkdir -p /var/lib/tranquil-pds 404 + cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend 405 + chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds 406 406 407 - cat > /etc/systemd/system/bspds.service << 'EOF' 407 + cat > /etc/systemd/system/tranquil-pds.service << 'EOF' 408 408 [Unit] 409 - Description=BSPDS - AT Protocol PDS 409 + Description=Tranquil PDS - AT Protocol PDS 410 410 After=network.target postgresql.service minio.service 411 411 412 412 [Service] 413 413 Type=simple 414 - User=bspds 415 - Group=bspds 416 - EnvironmentFile=/etc/bspds/bspds.env 417 - Environment=FRONTEND_DIR=/var/lib/bspds/frontend 418 - ExecStart=/usr/local/bin/bspds 414 + User=tranquil-pds 415 + Group=tranquil-pds 416 + EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env 417 + Environment=FRONTEND_DIR=/var/lib/tranquil-pds/frontend 418 + ExecStart=/usr/local/bin/tranquil-pds 419 419 Restart=always 420 420 RestartSec=5 421 421 ··· 424 424 EOF 425 425 426 426 systemctl daemon-reload 427 - systemctl enable bspds 428 - systemctl start bspds 429 - log_success "BSPDS service started" 427 + systemctl enable tranquil-pds 428 + systemctl start tranquil-pds 429 + log_success "Tranquil PDS service started" 430 430 431 431 log_info "Installing nginx..." 432 432 apt install -y nginx 433 - cat > /etc/nginx/sites-available/bspds << EOF 433 + cat > /etc/nginx/sites-available/tranquil-pds << EOF 434 434 server { 435 435 listen 80; 436 436 listen [::]:80; ··· 456 456 } 457 457 EOF 458 458 459 - ln -sf /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/ 459 + ln -sf /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/ 460 460 rm -f /etc/nginx/sites-enabled/default 461 461 nginx -t 462 462 systemctl reload nginx ··· 496 496 -d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" \ 497 497 --email "${CERTBOT_EMAIL}" --agree-tos; then 498 498 499 - cat > /etc/nginx/sites-available/bspds << EOF 499 + cat > /etc/nginx/sites-available/tranquil-pds << EOF 500 500 server { 501 501 listen 80; 502 502 listen [::]:80; ··· 564 564 log_info "Verifying installation..." 565 565 sleep 3 566 566 if curl -s "http://localhost:3000/xrpc/_health" | grep -q "version"; then 567 - log_success "BSPDS is responding" 567 + log_success "Tranquil PDS is responding" 568 568 else 569 - log_warn "BSPDS may still be starting. Check: journalctl -u bspds -f" 569 + log_warn "Tranquil PDS may still be starting. Check: journalctl -u tranquil-pds -f" 570 570 fi 571 571 572 572 echo "" ··· 574 574 echo "" 575 575 echo "PDS: https://${PDS_DOMAIN}" 576 576 echo "" 577 - echo "Credentials (also in /etc/bspds/.credentials):" 577 + echo "Credentials (also in /etc/tranquil-pds/.credentials):" 578 578 echo " DB password: ${DB_PASSWORD}" 579 579 echo " MinIO password: ${MINIO_PASSWORD}" 580 580 echo "" 581 581 echo "Commands:" 582 - echo " journalctl -u bspds -f # logs" 583 - echo " systemctl restart bspds # restart" 584 - echo " bspds-mailq # view trapped emails" 582 + echo " journalctl -u tranquil-pds -f # logs" 583 + echo " systemctl restart tranquil-pds # restart" 584 + echo " tranquil-pds-mailq # view trapped emails" 585 585 echo ""
+1 -1
scripts/run-tests.sh
··· 10 10 } 11 11 trap cleanup EXIT 12 12 "$INFRA_SCRIPT" start 13 - source "${TMPDIR:-/tmp}/bspds_test_infra.env" 13 + source "${TMPDIR:-/tmp}/tranquil_pds_test_infra.env" 14 14 echo "" 15 15 echo "Running database migrations..." 16 16 sqlx database create 2>/dev/null || true
+8 -8
scripts/test-infra.sh
··· 1 1 #!/usr/bin/env bash 2 2 set -euo pipefail 3 - INFRA_FILE="${TMPDIR:-/tmp}/bspds_test_infra.env" 4 - CONTAINER_PREFIX="bspds-test" 3 + INFRA_FILE="${TMPDIR:-/tmp}/tranquil_pds_test_infra.env" 4 + CONTAINER_PREFIX="tranquil-pds-test" 5 5 command_exists() { 6 6 command -v "$1" >/dev/null 2>&1 7 7 } ··· 40 40 -e POSTGRES_USER=postgres \ 41 41 -e POSTGRES_DB=postgres \ 42 42 -P \ 43 - --label bspds_test=true \ 43 + --label tranquil_pds_test=true \ 44 44 postgres:18-alpine >/dev/null 45 45 echo "Starting MinIO..." 46 46 $CONTAINER_CMD run -d \ ··· 48 48 -e MINIO_ROOT_USER=minioadmin \ 49 49 -e MINIO_ROOT_PASSWORD=minioadmin \ 50 50 -P \ 51 - --label bspds_test=true \ 51 + --label tranquil_pds_test=true \ 52 52 minio/minio:latest server /data >/dev/null 53 53 echo "Starting Valkey..." 54 54 $CONTAINER_CMD run -d \ 55 55 --name "${CONTAINER_PREFIX}-valkey" \ 56 56 -P \ 57 - --label bspds_test=true \ 57 + --label tranquil_pds_test=true \ 58 58 valkey/valkey:8-alpine >/dev/null 59 59 echo "Waiting for services to be ready..." 60 60 sleep 2 ··· 95 95 export AWS_SECRET_ACCESS_KEY="minioadmin" 96 96 export AWS_REGION="us-east-1" 97 97 export VALKEY_URL="redis://127.0.0.1:${VALKEY_PORT}" 98 - export BSPDS_TEST_INFRA_READY="1" 99 - export BSPDS_ALLOW_INSECURE_SECRETS="1" 98 + export TRANQUIL_PDS_TEST_INFRA_READY="1" 99 + export TRANQUIL_PDS_ALLOW_INSECURE_SECRETS="1" 100 100 export SKIP_IMPORT_VERIFICATION="true" 101 101 export DISABLE_RATE_LIMITING="1" 102 102 EOF ··· 125 125 fi 126 126 echo "" 127 127 echo "Containers:" 128 - $CONTAINER_CMD ps -a --filter "label=bspds_test=true" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo " (none)" 128 + $CONTAINER_CMD ps -a --filter "label=tranquil_pds_test=true" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo " (none)" 129 129 } 130 130 case "${1:-}" in 131 131 start)
+18 -2
src/api/server/account_status.rs
··· 157 157 .await; 158 158 match result { 159 159 Ok(_) => { 160 - if let Some(h) = handle { 160 + if let Some(ref h) = handle { 161 161 let _ = state.cache.delete(&format!("handle:{}", h)).await; 162 + } 163 + if let Err(e) = 164 + crate::api::repo::record::sequence_account_event(&state, &did, true, None).await 165 + { 166 + warn!("Failed to sequence account activation event: {}", e); 167 + } 168 + if let Err(e) = 169 + crate::api::repo::record::sequence_identity_event(&state, &did, handle.as_deref()) 170 + .await 171 + { 172 + warn!("Failed to sequence identity event for activation: {}", e); 162 173 } 163 174 (StatusCode::OK, Json(json!({}))).into_response() 164 175 } ··· 222 233 .await; 223 234 match result { 224 235 Ok(_) => { 225 - if let Some(h) = handle { 236 + if let Some(ref h) = handle { 226 237 let _ = state.cache.delete(&format!("handle:{}", h)).await; 238 + } 239 + if let Err(e) = 240 + crate::api::repo::record::sequence_account_event(&state, &did, false, Some("deactivated")).await 241 + { 242 + warn!("Failed to sequence account deactivation event: {}", e); 227 243 } 228 244 (StatusCode::OK, Json(json!({}))).into_response() 229 245 }
+102 -3
src/api/server/service_auth.rs
··· 10 10 use serde_json::json; 11 11 use tracing::error; 12 12 13 + const HOUR_SECS: i64 = 3600; 14 + const MINUTE_SECS: i64 = 60; 15 + 16 + const PROTECTED_METHODS: &[&str] = &[ 17 + "com.atproto.admin.sendEmail", 18 + "com.atproto.identity.requestPlcOperationSignature", 19 + "com.atproto.identity.signPlcOperation", 20 + "com.atproto.identity.updateHandle", 21 + "com.atproto.server.activateAccount", 22 + "com.atproto.server.confirmEmail", 23 + "com.atproto.server.createAppPassword", 24 + "com.atproto.server.deactivateAccount", 25 + "com.atproto.server.getAccountInviteCodes", 26 + "com.atproto.server.getSession", 27 + "com.atproto.server.listAppPasswords", 28 + "com.atproto.server.requestAccountDelete", 29 + "com.atproto.server.requestEmailConfirmation", 30 + "com.atproto.server.requestEmailUpdate", 31 + "com.atproto.server.revokeAppPassword", 32 + "com.atproto.server.updateEmail", 33 + ]; 34 + 13 35 #[derive(Deserialize)] 14 36 pub struct GetServiceAuthParams { 15 37 pub aud: String, ··· 33 55 Some(t) => t, 34 56 None => return ApiError::AuthenticationRequired.into_response(), 35 57 }; 36 - let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await { 58 + let auth_user = match crate::auth::validate_bearer_token_for_service_auth(&state.db, &token).await { 37 59 Ok(user) => user, 38 60 Err(e) => return ApiError::from(e).into_response(), 39 61 }; ··· 46 68 .into_response(); 47 69 } 48 70 }; 49 - let lxm = params.lxm.as_deref().unwrap_or("*"); 71 + 72 + let lxm = params.lxm.as_deref(); 73 + let lxm_for_token = lxm.unwrap_or("*"); 74 + 75 + let user_status = sqlx::query!( 76 + "SELECT takedown_ref FROM users WHERE did = $1", 77 + auth_user.did 78 + ) 79 + .fetch_optional(&state.db) 80 + .await; 81 + 82 + let is_takendown = match user_status { 83 + Ok(Some(row)) => row.takedown_ref.is_some(), 84 + _ => false, 85 + }; 86 + 87 + if is_takendown && lxm != Some("com.atproto.server.createAccount") { 88 + return ( 89 + StatusCode::BAD_REQUEST, 90 + Json(json!({ 91 + "error": "InvalidToken", 92 + "message": "Bad token scope" 93 + })), 94 + ) 95 + .into_response(); 96 + } 97 + 98 + if let Some(method) = lxm { 99 + if PROTECTED_METHODS.contains(&method) { 100 + return ( 101 + StatusCode::BAD_REQUEST, 102 + Json(json!({ 103 + "error": "InvalidRequest", 104 + "message": format!("cannot request a service auth token for the following protected method: {}", method) 105 + })), 106 + ) 107 + .into_response(); 108 + } 109 + } 110 + 111 + if let Some(exp) = params.exp { 112 + let now = chrono::Utc::now().timestamp(); 113 + let diff = exp - now; 114 + 115 + if diff < 0 { 116 + return ( 117 + StatusCode::BAD_REQUEST, 118 + Json(json!({ 119 + "error": "BadExpiration", 120 + "message": "expiration is in past" 121 + })), 122 + ) 123 + .into_response(); 124 + } 125 + 126 + if diff > HOUR_SECS { 127 + return ( 128 + StatusCode::BAD_REQUEST, 129 + Json(json!({ 130 + "error": "BadExpiration", 131 + "message": "cannot request a token with an expiration more than an hour in the future" 132 + })), 133 + ) 134 + .into_response(); 135 + } 136 + 137 + if lxm.is_none() && diff > MINUTE_SECS { 138 + return ( 139 + StatusCode::BAD_REQUEST, 140 + Json(json!({ 141 + "error": "BadExpiration", 142 + "message": "cannot request a method-less token with an expiration more than a minute in the future" 143 + })), 144 + ) 145 + .into_response(); 146 + } 147 + } 148 + 50 149 let service_token = 51 - match crate::auth::create_service_token(&auth_user.did, &params.aud, lxm, &key_bytes) { 150 + match crate::auth::create_service_token(&auth_user.did, &params.aud, lxm_for_token, &key_bytes) { 52 151 Ok(t) => t, 53 152 Err(e) => { 54 153 error!("Failed to create service token: {:?}", e);
+13 -5
src/auth/mod.rs
··· 59 59 db: &PgPool, 60 60 token: &str, 61 61 ) -> Result<AuthenticatedUser, TokenValidationError> { 62 - validate_bearer_token_with_options_internal(db, None, token, false).await 62 + validate_bearer_token_with_options_internal(db, None, token, false, false).await 63 63 } 64 64 65 65 pub async fn validate_bearer_token_allow_deactivated( 66 66 db: &PgPool, 67 67 token: &str, 68 68 ) -> Result<AuthenticatedUser, TokenValidationError> { 69 - validate_bearer_token_with_options_internal(db, None, token, true).await 69 + validate_bearer_token_with_options_internal(db, None, token, true, false).await 70 70 } 71 71 72 72 pub async fn validate_bearer_token_cached( ··· 74 74 cache: &Arc<dyn Cache>, 75 75 token: &str, 76 76 ) -> Result<AuthenticatedUser, TokenValidationError> { 77 - validate_bearer_token_with_options_internal(db, Some(cache), token, false).await 77 + validate_bearer_token_with_options_internal(db, Some(cache), token, false, false).await 78 78 } 79 79 80 80 pub async fn validate_bearer_token_cached_allow_deactivated( ··· 82 82 cache: &Arc<dyn Cache>, 83 83 token: &str, 84 84 ) -> Result<AuthenticatedUser, TokenValidationError> { 85 - validate_bearer_token_with_options_internal(db, Some(cache), token, true).await 85 + validate_bearer_token_with_options_internal(db, Some(cache), token, true, false).await 86 + } 87 + 88 + pub async fn validate_bearer_token_for_service_auth( 89 + db: &PgPool, 90 + token: &str, 91 + ) -> Result<AuthenticatedUser, TokenValidationError> { 92 + validate_bearer_token_with_options_internal(db, None, token, true, true).await 86 93 } 87 94 88 95 async fn validate_bearer_token_with_options_internal( ··· 90 97 cache: Option<&Arc<dyn Cache>>, 91 98 token: &str, 92 99 allow_deactivated: bool, 100 + allow_takendown: bool, 93 101 ) -> Result<AuthenticatedUser, TokenValidationError> { 94 102 let did_from_token = get_did_from_token(token).ok(); 95 103 ··· 155 163 return Err(TokenValidationError::AccountDeactivated); 156 164 } 157 165 158 - if takedown_ref.is_some() { 166 + if !allow_takendown && takedown_ref.is_some() { 159 167 return Err(TokenValidationError::AccountTakedown); 160 168 } 161 169
+2 -2
src/comms/sender.rs
··· 87 87 88 88 pub fn from_env() -> Option<Self> { 89 89 let from_address = std::env::var("MAIL_FROM_ADDRESS").ok()?; 90 - let from_name = std::env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "BSPDS".to_string()); 90 + let from_name = std::env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "Tranquil PDS".to_string()); 91 91 Some(Self::new(from_address, from_name)) 92 92 } 93 93 ··· 168 168 let content = format!("**{}**\n\n{}", subject, notification.body); 169 169 let payload = json!({ 170 170 "content": content, 171 - "username": "BSPDS" 171 + "username": "Tranquil PDS" 172 172 }); 173 173 let mut last_error = None; 174 174 for attempt in 0..MAX_RETRIES {
+10 -10
src/config.rs
··· 25 25 pub fn init() -> &'static Self { 26 26 CONFIG.get_or_init(|| { 27 27 let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| { 28 - if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() { 28 + if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() { 29 29 "test-jwt-secret-not-for-production".to_string() 30 30 } else { 31 31 panic!( 32 32 "JWT_SECRET environment variable must be set in production. \ 33 - Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing." 33 + Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing." 34 34 ); 35 35 } 36 36 }); 37 37 38 38 let dpop_secret = std::env::var("DPOP_SECRET").unwrap_or_else(|_| { 39 - if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() { 39 + if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() { 40 40 "test-dpop-secret-not-for-production".to_string() 41 41 } else { 42 42 panic!( 43 43 "DPOP_SECRET environment variable must be set in production. \ 44 - Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing." 44 + Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing." 45 45 ); 46 46 } 47 47 }); 48 48 49 - if jwt_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() { 49 + if jwt_secret.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() { 50 50 panic!("JWT_SECRET must be at least 32 characters"); 51 51 } 52 52 53 - if dpop_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() { 53 + if dpop_secret.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() { 54 54 panic!("DPOP_SECRET must be at least 32 characters"); 55 55 } 56 56 ··· 87 87 let signing_key_id = URL_SAFE_NO_PAD.encode(&kid_hash[..8]); 88 88 89 89 let master_key = std::env::var("MASTER_KEY").unwrap_or_else(|_| { 90 - if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() { 90 + if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() { 91 91 "test-master-key-not-for-production".to_string() 92 92 } else { 93 93 panic!( 94 94 "MASTER_KEY environment variable must be set in production. \ 95 - Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing." 95 + Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing." 96 96 ); 97 97 } 98 98 }); 99 99 100 - if master_key.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() { 100 + if master_key.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() { 101 101 panic!("MASTER_KEY must be at least 32 characters"); 102 102 } 103 103 104 104 let hk = Hkdf::<Sha256>::new(None, master_key.as_bytes()); 105 105 let mut key_encryption_key = [0u8; 32]; 106 - hk.expand(b"bspds-user-key-encryption", &mut key_encryption_key) 106 + hk.expand(b"tranquil-pds-user-key-encryption", &mut key_encryption_key) 107 107 .expect("HKDF expansion failed"); 108 108 109 109 AuthConfig {
+8 -8
src/lib.rs
··· 52 52 get(api::server::get_session), 53 53 ) 54 54 .route( 55 - "/xrpc/com.bspds.account.listSessions", 55 + "/xrpc/com.tranquil.account.listSessions", 56 56 get(api::server::list_sessions), 57 57 ) 58 58 .route( 59 - "/xrpc/com.bspds.account.revokeSession", 59 + "/xrpc/com.tranquil.account.revokeSession", 60 60 post(api::server::revoke_session), 61 61 ) 62 62 .route( ··· 199 199 post(api::server::reset_password), 200 200 ) 201 201 .route( 202 - "/xrpc/com.bspds.account.changePassword", 202 + "/xrpc/com.tranquil.account.changePassword", 203 203 post(api::server::change_password), 204 204 ) 205 205 .route( ··· 283 283 get(api::admin::get_invite_codes), 284 284 ) 285 285 .route( 286 - "/xrpc/com.bspds.admin.getServerStats", 286 + "/xrpc/com.tranquil.admin.getServerStats", 287 287 get(api::admin::get_server_stats), 288 288 ) 289 289 .route( ··· 370 370 get(api::temp::check_signup_queue), 371 371 ) 372 372 .route( 373 - "/xrpc/com.bspds.account.getNotificationPrefs", 373 + "/xrpc/com.tranquil.account.getNotificationPrefs", 374 374 get(api::notification_prefs::get_notification_prefs), 375 375 ) 376 376 .route( 377 - "/xrpc/com.bspds.account.updateNotificationPrefs", 377 + "/xrpc/com.tranquil.account.updateNotificationPrefs", 378 378 post(api::notification_prefs::update_notification_prefs), 379 379 ) 380 380 .route( 381 - "/xrpc/com.bspds.account.getNotificationHistory", 381 + "/xrpc/com.tranquil.account.getNotificationHistory", 382 382 get(api::notification_prefs::get_notification_history), 383 383 ) 384 384 .route( 385 - "/xrpc/com.bspds.account.confirmChannelVerification", 385 + "/xrpc/com.tranquil.account.confirmChannelVerification", 386 386 post(api::verification::confirm_channel_verification), 387 387 ) 388 388 .route("/xrpc/{*method}", any(api::proxy::proxy_handler))
+6 -6
src/main.rs
··· 1 - use bspds::comms::{CommsService, DiscordSender, EmailSender, SignalSender, TelegramSender}; 2 - use bspds::crawlers::{Crawlers, start_crawlers_service}; 3 - use bspds::state::AppState; 1 + use tranquil_pds::comms::{CommsService, DiscordSender, EmailSender, SignalSender, TelegramSender}; 2 + use tranquil_pds::crawlers::{Crawlers, start_crawlers_service}; 3 + use tranquil_pds::state::AppState; 4 4 use std::net::SocketAddr; 5 5 use std::process::ExitCode; 6 6 use std::sync::Arc; ··· 11 11 async fn main() -> ExitCode { 12 12 dotenvy::dotenv().ok(); 13 13 tracing_subscriber::fmt::init(); 14 - bspds::metrics::init_metrics(); 14 + tranquil_pds::metrics::init_metrics(); 15 15 16 16 match run().await { 17 17 Ok(()) => ExitCode::SUCCESS, ··· 62 62 .map_err(|e| format!("Failed to run migrations: {}", e))?; 63 63 64 64 let state = AppState::new(pool.clone()).await; 65 - bspds::sync::listener::start_sequencer_listener(state.clone()).await; 65 + tranquil_pds::sync::listener::start_sequencer_listener(state.clone()).await; 66 66 67 67 let (shutdown_tx, shutdown_rx) = watch::channel(false); 68 68 ··· 108 108 None 109 109 }; 110 110 111 - let app = bspds::app(state); 111 + let app = tranquil_pds::app(state); 112 112 let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 113 113 info!("listening on {}", addr); 114 114
+25 -25
src/metrics.rs
··· 24 24 } 25 25 26 26 fn describe_metrics() { 27 - metrics::describe_counter!("bspds_http_requests_total", "Total number of HTTP requests"); 27 + metrics::describe_counter!("tranquil_pds_http_requests_total", "Total number of HTTP requests"); 28 28 metrics::describe_histogram!( 29 - "bspds_http_request_duration_seconds", 29 + "tranquil_pds_http_request_duration_seconds", 30 30 "HTTP request duration in seconds" 31 31 ); 32 32 metrics::describe_counter!( 33 - "bspds_auth_cache_hits_total", 33 + "tranquil_pds_auth_cache_hits_total", 34 34 "Total number of authentication cache hits" 35 35 ); 36 36 metrics::describe_counter!( 37 - "bspds_auth_cache_misses_total", 37 + "tranquil_pds_auth_cache_misses_total", 38 38 "Total number of authentication cache misses" 39 39 ); 40 40 metrics::describe_gauge!( 41 - "bspds_firehose_subscribers", 41 + "tranquil_pds_firehose_subscribers", 42 42 "Number of active firehose WebSocket subscribers" 43 43 ); 44 44 metrics::describe_counter!( 45 - "bspds_firehose_events_total", 45 + "tranquil_pds_firehose_events_total", 46 46 "Total number of firehose events published" 47 47 ); 48 48 metrics::describe_counter!( 49 - "bspds_block_operations_total", 49 + "tranquil_pds_block_operations_total", 50 50 "Total number of block store operations" 51 51 ); 52 52 metrics::describe_counter!( 53 - "bspds_s3_operations_total", 53 + "tranquil_pds_s3_operations_total", 54 54 "Total number of S3/blob storage operations" 55 55 ); 56 56 metrics::describe_gauge!( 57 - "bspds_comms_queue_size", 57 + "tranquil_pds_comms_queue_size", 58 58 "Current size of the comms queue" 59 59 ); 60 60 metrics::describe_counter!( 61 - "bspds_rate_limit_rejections_total", 61 + "tranquil_pds_rate_limit_rejections_total", 62 62 "Total number of rate limit rejections" 63 63 ); 64 - metrics::describe_counter!("bspds_db_queries_total", "Total number of database queries"); 64 + metrics::describe_counter!("tranquil_pds_db_queries_total", "Total number of database queries"); 65 65 metrics::describe_histogram!( 66 - "bspds_db_query_duration_seconds", 66 + "tranquil_pds_db_query_duration_seconds", 67 67 "Database query duration in seconds" 68 68 ); 69 69 } ··· 97 97 let status = response.status().as_u16().to_string(); 98 98 99 99 counter!( 100 - "bspds_http_requests_total", 100 + "tranquil_pds_http_requests_total", 101 101 "method" => method.clone(), 102 102 "path" => path.clone(), 103 103 "status" => status.clone() ··· 105 105 .increment(1); 106 106 107 107 histogram!( 108 - "bspds_http_request_duration_seconds", 108 + "tranquil_pds_http_request_duration_seconds", 109 109 "method" => method, 110 110 "path" => path 111 111 ) ··· 135 135 } 136 136 137 137 pub fn record_auth_cache_hit(cache_type: &str) { 138 - counter!("bspds_auth_cache_hits_total", "cache_type" => cache_type.to_string()).increment(1); 138 + counter!("tranquil_pds_auth_cache_hits_total", "cache_type" => cache_type.to_string()).increment(1); 139 139 } 140 140 141 141 pub fn record_auth_cache_miss(cache_type: &str) { 142 - counter!("bspds_auth_cache_misses_total", "cache_type" => cache_type.to_string()).increment(1); 142 + counter!("tranquil_pds_auth_cache_misses_total", "cache_type" => cache_type.to_string()).increment(1); 143 143 } 144 144 145 145 pub fn set_firehose_subscribers(count: usize) { 146 - gauge!("bspds_firehose_subscribers").set(count as f64); 146 + gauge!("tranquil_pds_firehose_subscribers").set(count as f64); 147 147 } 148 148 149 149 pub fn increment_firehose_subscribers() { 150 - counter!("bspds_firehose_events_total").increment(1); 150 + counter!("tranquil_pds_firehose_events_total").increment(1); 151 151 } 152 152 153 153 pub fn record_firehose_event() { 154 - counter!("bspds_firehose_events_total").increment(1); 154 + counter!("tranquil_pds_firehose_events_total").increment(1); 155 155 } 156 156 157 157 pub fn record_block_operation(op_type: &str) { 158 - counter!("bspds_block_operations_total", "op_type" => op_type.to_string()).increment(1); 158 + counter!("tranquil_pds_block_operations_total", "op_type" => op_type.to_string()).increment(1); 159 159 } 160 160 161 161 pub fn record_s3_operation(op_type: &str, status: &str) { 162 162 counter!( 163 - "bspds_s3_operations_total", 163 + "tranquil_pds_s3_operations_total", 164 164 "op_type" => op_type.to_string(), 165 165 "status" => status.to_string() 166 166 ) ··· 168 168 } 169 169 170 170 pub fn set_comms_queue_size(size: usize) { 171 - gauge!("bspds_comms_queue_size").set(size as f64); 171 + gauge!("tranquil_pds_comms_queue_size").set(size as f64); 172 172 } 173 173 174 174 pub fn record_rate_limit_rejection(limiter: &str) { 175 - counter!("bspds_rate_limit_rejections_total", "limiter" => limiter.to_string()).increment(1); 175 + counter!("tranquil_pds_rate_limit_rejections_total", "limiter" => limiter.to_string()).increment(1); 176 176 } 177 177 178 178 pub fn record_db_query(query_type: &str, duration_seconds: f64) { 179 - counter!("bspds_db_queries_total", "query_type" => query_type.to_string()).increment(1); 179 + counter!("tranquil_pds_db_queries_total", "query_type" => query_type.to_string()).increment(1); 180 180 histogram!( 181 - "bspds_db_query_duration_seconds", 181 + "tranquil_pds_db_query_duration_seconds", 182 182 "query_type" => query_type.to_string() 183 183 ) 184 184 .record(duration_seconds);
+11 -11
tests/account_notifications.rs
··· 1 1 mod common; 2 2 use common::{base_url, client, create_account_and_login, get_db_connection_string}; 3 - use bspds::comms::{NewComms, CommsType, enqueue_comms}; 3 + use tranquil_pds::comms::{NewComms, CommsType, enqueue_comms}; 4 4 use serde_json::{Value, json}; 5 5 use sqlx::PgPool; 6 6 ··· 37 37 } 38 38 39 39 let resp = client 40 - .get(format!("{}/xrpc/com.bspds.account.getNotificationHistory", base)) 40 + .get(format!("{}/xrpc/com.tranquil.account.getNotificationHistory", base)) 41 41 .header("Authorization", format!("Bearer {}", token)) 42 42 .send() 43 43 .await ··· 63 63 "discordId": "123456789" 64 64 }); 65 65 let resp = client 66 - .post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base)) 66 + .post(format!("{}/xrpc/com.tranquil.account.updateNotificationPrefs", base)) 67 67 .header("Authorization", format!("Bearer {}", token)) 68 68 .json(&prefs) 69 69 .send() ··· 92 92 "code": code 93 93 }); 94 94 let resp = client 95 - .post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base)) 95 + .post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base)) 96 96 .header("Authorization", format!("Bearer {}", token)) 97 97 .json(&input) 98 98 .send() ··· 101 101 assert_eq!(resp.status(), 200); 102 102 103 103 let resp = client 104 - .get(format!("{}/xrpc/com.bspds.account.getNotificationPrefs", base)) 104 + .get(format!("{}/xrpc/com.tranquil.account.getNotificationPrefs", base)) 105 105 .header("Authorization", format!("Bearer {}", token)) 106 106 .send() 107 107 .await ··· 121 121 "telegramUsername": "testuser" 122 122 }); 123 123 let resp = client 124 - .post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base)) 124 + .post(format!("{}/xrpc/com.tranquil.account.updateNotificationPrefs", base)) 125 125 .header("Authorization", format!("Bearer {}", token)) 126 126 .json(&prefs) 127 127 .send() ··· 134 134 "code": "000000" 135 135 }); 136 136 let resp = client 137 - .post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base)) 137 + .post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base)) 138 138 .header("Authorization", format!("Bearer {}", token)) 139 139 .json(&input) 140 140 .send() ··· 154 154 "code": "123456" 155 155 }); 156 156 let resp = client 157 - .post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base)) 157 + .post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base)) 158 158 .header("Authorization", format!("Bearer {}", token)) 159 159 .json(&input) 160 160 .send() ··· 175 175 "email": unique_email 176 176 }); 177 177 let resp = client 178 - .post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base)) 178 + .post(format!("{}/xrpc/com.tranquil.account.updateNotificationPrefs", base)) 179 179 .header("Authorization", format!("Bearer {}", token)) 180 180 .json(&prefs) 181 181 .send() ··· 203 203 "code": code 204 204 }); 205 205 let resp = client 206 - .post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base)) 206 + .post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base)) 207 207 .header("Authorization", format!("Bearer {}", token)) 208 208 .json(&input) 209 209 .send() ··· 212 212 assert_eq!(resp.status(), 200); 213 213 214 214 let resp = client 215 - .get(format!("{}/xrpc/com.bspds.account.getNotificationPrefs", base)) 215 + .get(format!("{}/xrpc/com.tranquil.account.getNotificationPrefs", base)) 216 216 .header("Authorization", format!("Bearer {}", token)) 217 217 .send() 218 218 .await
+2 -2
tests/admin_stats.rs
··· 11 11 let (_, _) = create_admin_account_and_login(&client).await; 12 12 13 13 let resp = client 14 - .get(format!("{}/xrpc/com.bspds.admin.getServerStats", base)) 14 + .get(format!("{}/xrpc/com.tranquil.admin.getServerStats", base)) 15 15 .header("Authorization", format!("Bearer {}", token1)) 16 16 .send() 17 17 .await ··· 33 33 let client = client(); 34 34 let base = base_url().await; 35 35 let resp = client 36 - .get(format!("{}/xrpc/com.bspds.admin.getServerStats", base)) 36 + .get(format!("{}/xrpc/com.tranquil.admin.getServerStats", base)) 37 37 .send() 38 38 .await 39 39 .unwrap();
+6 -6
tests/change_password.rs
··· 33 33 let jwt = verify_new_account(&client, did).await; 34 34 let change_res = client 35 35 .post(format!( 36 - "{}/xrpc/com.bspds.account.changePassword", 36 + "{}/xrpc/com.tranquil.account.changePassword", 37 37 base_url().await 38 38 )) 39 39 .bearer_auth(&jwt) ··· 79 79 let (_, jwt) = setup_new_user("change-pw-wrong").await; 80 80 let res = client 81 81 .post(format!( 82 - "{}/xrpc/com.bspds.account.changePassword", 82 + "{}/xrpc/com.tranquil.account.changePassword", 83 83 base_url().await 84 84 )) 85 85 .bearer_auth(&jwt) ··· 122 122 let jwt = verify_new_account(&client, did).await; 123 123 let res = client 124 124 .post(format!( 125 - "{}/xrpc/com.bspds.account.changePassword", 125 + "{}/xrpc/com.tranquil.account.changePassword", 126 126 base_url().await 127 127 )) 128 128 .bearer_auth(&jwt) ··· 144 144 let (_, jwt) = setup_new_user("change-pw-empty").await; 145 145 let res = client 146 146 .post(format!( 147 - "{}/xrpc/com.bspds.account.changePassword", 147 + "{}/xrpc/com.tranquil.account.changePassword", 148 148 base_url().await 149 149 )) 150 150 .bearer_auth(&jwt) ··· 164 164 let (_, jwt) = setup_new_user("change-pw-emptynew").await; 165 165 let res = client 166 166 .post(format!( 167 - "{}/xrpc/com.bspds.account.changePassword", 167 + "{}/xrpc/com.tranquil.account.changePassword", 168 168 base_url().await 169 169 )) 170 170 .bearer_auth(&jwt) ··· 183 183 let client = client(); 184 184 let res = client 185 185 .post(format!( 186 - "{}/xrpc/com.bspds.account.changePassword", 186 + "{}/xrpc/com.tranquil.account.changePassword", 187 187 base_url().await 188 188 )) 189 189 .json(&json!({
+10 -10
tests/common/mod.rs
··· 1 1 use aws_config::BehaviorVersion; 2 2 use aws_sdk_s3::Client as S3Client; 3 3 use aws_sdk_s3::config::Credentials; 4 - use bspds::state::AppState; 4 + use tranquil_pds::state::AppState; 5 5 use chrono::Utc; 6 6 use reqwest::{Client, StatusCode, header}; 7 7 use serde_json::{Value, json}; ··· 40 40 pub const TARGET_DID: &str = "did:plc:target"; 41 41 42 42 fn has_external_infra() -> bool { 43 - std::env::var("BSPDS_TEST_INFRA_READY").is_ok() 43 + std::env::var("TRANQUIL_PDS_TEST_INFRA_READY").is_ok() 44 44 || (std::env::var("DATABASE_URL").is_ok() && std::env::var("S3_ENDPOINT").is_ok()) 45 45 } 46 46 #[cfg(test)] ··· 51 51 } 52 52 if std::env::var("XDG_RUNTIME_DIR").is_ok() { 53 53 let _ = std::process::Command::new("podman") 54 - .args(&["rm", "-f", "--filter", "label=bspds_test=true"]) 54 + .args(&["rm", "-f", "--filter", "label=tranquil_pds_test=true"]) 55 55 .output(); 56 56 } 57 57 let _ = std::process::Command::new("docker") ··· 60 60 "prune", 61 61 "-f", 62 62 "--filter", 63 - "label=bspds_test=true", 63 + "label=tranquil_pds_test=true", 64 64 ]) 65 65 .output(); 66 66 } ··· 80 80 let (tx, rx) = std::sync::mpsc::channel(); 81 81 std::thread::spawn(move || { 82 82 unsafe { 83 - std::env::set_var("BSPDS_ALLOW_INSECURE_SECRETS", "1"); 83 + std::env::set_var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS", "1"); 84 84 } 85 85 if std::env::var("DOCKER_HOST").is_err() { 86 86 if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") { ··· 152 152 .with_env_var("MINIO_ROOT_USER", "minioadmin") 153 153 .with_env_var("MINIO_ROOT_PASSWORD", "minioadmin") 154 154 .with_cmd(vec!["server".to_string(), "/data".to_string()]) 155 - .with_label("bspds_test", "true") 155 + .with_label("tranquil_pds_test", "true") 156 156 .start() 157 157 .await 158 158 .expect("Failed to start MinIO"); ··· 195 195 S3_CONTAINER.set(s3_container).ok(); 196 196 let container = Postgres::default() 197 197 .with_tag("18-alpine") 198 - .with_label("bspds_test", "true") 198 + .with_label("tranquil_pds_test", "true") 199 199 .start() 200 200 .await 201 201 .expect("Failed to start Postgres"); ··· 236 236 } 237 237 238 238 async fn spawn_app(database_url: String) -> String { 239 - use bspds::rate_limit::RateLimiters; 239 + use tranquil_pds::rate_limit::RateLimiters; 240 240 let pool = PgPoolOptions::new() 241 241 .max_connections(50) 242 242 .connect(&database_url) ··· 260 260 .with_oauth_authorize_limit(10000) 261 261 .with_oauth_token_limit(10000); 262 262 let state = AppState::new(pool).await.with_rate_limiters(rate_limiters); 263 - bspds::sync::listener::start_sequencer_listener(state.clone()).await; 264 - let app = bspds::app(state); 263 + tranquil_pds::sync::listener::start_sequencer_listener(state.clone()).await; 264 + let app = tranquil_pds::app(state); 265 265 tokio::spawn(async move { 266 266 axum::serve(listener, app).await.unwrap(); 267 267 });
+1 -1
tests/image_processing.rs
··· 1 - use bspds::image::{ 1 + use tranquil_pds::image::{ 2 2 DEFAULT_MAX_FILE_SIZE, ImageError, ImageProcessor, OutputFormat, THUMB_SIZE_FEED, 3 3 THUMB_SIZE_FULL, 4 4 };
+1 -1
tests/import_with_verification.rs
··· 194 194 .fetch_optional(&pool) 195 195 .await 196 196 .ok()??; 197 - bspds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok() 197 + tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok() 198 198 } 199 199 #[tokio::test] 200 200 #[ignore = "requires exclusive env var access; run with: cargo test test_import_with_valid_signature_and_mock_plc -- --ignored --test-threads=1"]
+20 -4
tests/jwt_security.rs
··· 1 1 #![allow(unused_imports)] 2 2 mod common; 3 3 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 4 - use bspds::auth::{ 4 + use tranquil_pds::auth::{ 5 5 self, SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED, SCOPE_REFRESH, 6 6 TOKEN_TYPE_ACCESS, TOKEN_TYPE_REFRESH, TOKEN_TYPE_SERVICE, create_access_token, 7 7 create_refresh_token, create_service_token, get_did_from_token, get_jti_from_token, ··· 409 409 } 410 410 411 411 #[tokio::test] 412 - async fn test_deactivated_account_rejected() { 412 + async fn test_deactivated_account_behavior() { 413 413 let url = base_url().await; 414 414 let http_client = client(); 415 415 let (access_jwt, _did) = create_account_and_login(&http_client).await; ··· 423 423 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 424 424 .header("Authorization", format!("Bearer {}", access_jwt)) 425 425 .send().await.unwrap(); 426 - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 426 + assert_eq!(res.status(), StatusCode::OK); 427 427 let body: Value = res.json().await.unwrap(); 428 - assert_eq!(body["error"], "AccountDeactivated"); 428 + assert_eq!(body["active"], false); 429 + 430 + let post_res = http_client.post(format!("{}/xrpc/com.atproto.repo.createRecord", url)) 431 + .header("Authorization", format!("Bearer {}", access_jwt)) 432 + .json(&json!({ 433 + "repo": _did, 434 + "collection": "app.bsky.feed.post", 435 + "record": { 436 + "$type": "app.bsky.feed.post", 437 + "text": "test", 438 + "createdAt": "2024-01-01T00:00:00Z" 439 + } 440 + })) 441 + .send().await.unwrap(); 442 + assert_eq!(post_res.status(), StatusCode::UNAUTHORIZED); 443 + let post_body: Value = post_res.json().await.unwrap(); 444 + assert_eq!(post_body["error"], "AccountDeactivated"); 429 445 } 430 446 431 447 #[tokio::test]
+1 -1
tests/notifications.rs
··· 1 1 mod common; 2 - use bspds::comms::{ 2 + use tranquil_pds::comms::{ 3 3 CommsChannel, CommsStatus, CommsType, NewComms, enqueue_comms, enqueue_welcome, 4 4 }; 5 5 use sqlx::PgPool;
+1 -1
tests/oauth_security.rs
··· 2 2 mod common; 3 3 mod helpers; 4 4 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 5 - use bspds::oauth::dpop::{DPoPJwk, DPoPVerifier, compute_jwk_thumbprint}; 5 + use tranquil_pds::oauth::dpop::{DPoPJwk, DPoPVerifier, compute_jwk_thumbprint}; 6 6 use chrono::Utc; 7 7 use common::{base_url, client}; 8 8 use helpers::verify_new_account;
+1 -1
tests/plc_migration.rs
··· 50 50 .fetch_optional(&pool) 51 51 .await 52 52 .ok()??; 53 - bspds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok() 53 + tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok() 54 54 } 55 55 56 56 async fn get_plc_token_from_db(did: &str) -> Option<String> {
+1 -1
tests/plc_validation.rs
··· 1 - use bspds::plc::{ 1 + use tranquil_pds::plc::{ 2 2 PlcError, PlcOperation, PlcService, PlcValidationContext, cid_for_cbor, sign_operation, 3 3 signing_key_to_did_key, validate_plc_operation, validate_plc_operation_for_submission, 4 4 verify_operation_signature,
+1 -1
tests/rate_limit.rs
··· 162 162 println!("VALKEY_URL not set, skipping distributed rate limiter test"); 163 163 return; 164 164 } 165 - use bspds::cache::{DistributedRateLimiter, RedisRateLimiter}; 165 + use tranquil_pds::cache::{DistributedRateLimiter, RedisRateLimiter}; 166 166 let valkey_url = std::env::var("VALKEY_URL").unwrap(); 167 167 let client = redis::Client::open(valkey_url.as_str()).expect("Failed to create Redis client"); 168 168 let conn = client
+1 -1
tests/record_validation.rs
··· 1 - use bspds::validation::{ 1 + use tranquil_pds::validation::{ 2 2 RecordValidator, ValidationError, ValidationStatus, validate_collection_nsid, 3 3 validate_record_key, 4 4 };
+3 -3
tests/security_fixes.rs
··· 1 1 mod common; 2 - use bspds::image::{ImageError, ImageProcessor}; 3 - use bspds::comms::{SendError, is_valid_phone_number, sanitize_header_value}; 4 - use bspds::oauth::templates::{error_page, login_page, success_page}; 2 + use tranquil_pds::image::{ImageError, ImageProcessor}; 3 + use tranquil_pds::comms::{SendError, is_valid_phone_number, sanitize_header_value}; 4 + use tranquil_pds::oauth::templates::{error_page, login_page, success_page}; 5 5 6 6 #[test] 7 7 fn test_header_injection_sanitization() {
+9 -9
tests/session_management.rs
··· 11 11 let (did, jwt) = setup_new_user("list-sessions").await; 12 12 let res = client 13 13 .get(format!( 14 - "{}/xrpc/com.bspds.account.listSessions", 14 + "{}/xrpc/com.tranquil.account.listSessions", 15 15 base_url().await 16 16 )) 17 17 .bearer_auth(&jwt) ··· 74 74 let jwt2 = login_body["accessJwt"].as_str().unwrap(); 75 75 let list_res = client 76 76 .get(format!( 77 - "{}/xrpc/com.bspds.account.listSessions", 77 + "{}/xrpc/com.tranquil.account.listSessions", 78 78 base_url().await 79 79 )) 80 80 .bearer_auth(jwt2) ··· 93 93 let client = client(); 94 94 let res = client 95 95 .get(format!( 96 - "{}/xrpc/com.bspds.account.listSessions", 96 + "{}/xrpc/com.tranquil.account.listSessions", 97 97 base_url().await 98 98 )) 99 99 .send() ··· 145 145 let jwt2 = login_body["accessJwt"].as_str().unwrap(); 146 146 let list_res = client 147 147 .get(format!( 148 - "{}/xrpc/com.bspds.account.listSessions", 148 + "{}/xrpc/com.tranquil.account.listSessions", 149 149 base_url().await 150 150 )) 151 151 .bearer_auth(jwt2) ··· 159 159 let session_id = other_session.unwrap()["id"].as_str().unwrap(); 160 160 let revoke_res = client 161 161 .post(format!( 162 - "{}/xrpc/com.bspds.account.revokeSession", 162 + "{}/xrpc/com.tranquil.account.revokeSession", 163 163 base_url().await 164 164 )) 165 165 .bearer_auth(jwt2) ··· 170 170 assert_eq!(revoke_res.status(), StatusCode::OK); 171 171 let list_after_res = client 172 172 .get(format!( 173 - "{}/xrpc/com.bspds.account.listSessions", 173 + "{}/xrpc/com.tranquil.account.listSessions", 174 174 base_url().await 175 175 )) 176 176 .bearer_auth(jwt2) ··· 190 190 let (_, jwt) = setup_new_user("revoke-invalid").await; 191 191 let res = client 192 192 .post(format!( 193 - "{}/xrpc/com.bspds.account.revokeSession", 193 + "{}/xrpc/com.tranquil.account.revokeSession", 194 194 base_url().await 195 195 )) 196 196 .bearer_auth(&jwt) ··· 207 207 let (_, jwt) = setup_new_user("revoke-notfound").await; 208 208 let res = client 209 209 .post(format!( 210 - "{}/xrpc/com.bspds.account.revokeSession", 210 + "{}/xrpc/com.tranquil.account.revokeSession", 211 211 base_url().await 212 212 )) 213 213 .bearer_auth(&jwt) ··· 223 223 let client = client(); 224 224 let res = client 225 225 .post(format!( 226 - "{}/xrpc/com.bspds.account.revokeSession", 226 + "{}/xrpc/com.tranquil.account.revokeSession", 227 227 base_url().await 228 228 )) 229 229 .json(&json!({"sessionId": "1"}))