+2
-2
.env.example
+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
+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
+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
Cargo.toml
+2
-2
Dockerfile
+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
-1
README.md
+6
-6
deploy/quadlets/bspds-app.container
deploy/quadlets/tranquil-pds-app.container
+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
-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
+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
-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
+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
deploy/quadlets/bspds.pod
deploy/quadlets/tranquil-pds.pod
+20
deploy/quadlets/tranquil-pds-db.container
+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
+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
+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
+1
-1
docker-compose.yaml
+42
-45
docs/install-alpine.md
+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
+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
+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
-1
docs/install-kubernetes.md
+36
-37
docs/install-openbsd.md
+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
+1
-1
frontend/index.html
+1
-1
frontend/package.json
+1
-1
frontend/package.json
+8
-8
frontend/src/lib/api.ts
+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
+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
+2
-2
frontend/src/lib/oauth.ts
+1
-1
frontend/src/routes/Register.svelte
+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
+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
+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
+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
+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
+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
+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
+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
+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
+1
-1
observability/prometheus.yml
+75
-75
scripts/install-debian.sh
+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
+1
-1
scripts/run-tests.sh
+8
-8
scripts/test-infra.sh
+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
+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
+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, ¶ms.aud, lxm, &key_bytes) {
150
+
match crate::auth::create_service_token(&auth_user.did, ¶ms.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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
-1
tests/image_processing.rs
+1
-1
tests/import_with_verification.rs
+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
+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
tests/notifications.rs
+1
-1
tests/oauth_security.rs
+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
+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
-1
tests/plc_validation.rs
+1
-1
tests/rate_limit.rs
+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
-1
tests/record_validation.rs
+3
-3
tests/security_fixes.rs
+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
+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"}))