Rename to tranquil PDS, sounds better than bullshit PDS

This commit is contained in:
lewis
2025-12-18 23:17:51 +02:00
parent 95958bb119
commit 80a3e04ec6
62 changed files with 744 additions and 590 deletions

View File

@@ -30,7 +30,7 @@ AWS_SECRET_ACCESS_KEY=minioadmin
# Security Secrets
# =============================================================================
# These MUST be set in production (minimum 32 characters each)
# In development, set BSPDS_ALLOW_INSECURE_SECRETS=1 to use defaults
# In development, set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 to use defaults
# Server-wide secret for OAuth token signing (HS256)
# JWT_SECRET=your-secure-random-string-at-least-32-chars
# Secret for DPoP proof validation
@@ -38,7 +38,7 @@ AWS_SECRET_ACCESS_KEY=minioadmin
# Key for encrypting user signing keys at rest (AES-256-GCM)
# MASTER_KEY=your-secure-random-string-at-least-32-chars
# Set this ONLY in development to allow default/weak secrets
# BSPDS_ALLOW_INSECURE_SECRETS=1
# TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1
# =============================================================================
# PLC Directory
# =============================================================================

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT takedown_ref FROM users WHERE did = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "takedown_ref",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true
]
},
"hash": "1add22e111d5eff8beadbd832b4b8146d95da0a0ce8ce31dc9a2f930a26cc9ce"
}

122
Cargo.lock generated
View File

@@ -929,67 +929,6 @@ dependencies = [
"cfg_aliases",
]
[[package]]
name = "bspds"
version = "0.1.0"
dependencies = [
"aes-gcm",
"anyhow",
"async-trait",
"aws-config",
"aws-sdk-s3",
"axum",
"base32",
"base64 0.22.1",
"bcrypt",
"bytes",
"chrono",
"cid",
"ctor",
"dotenvy",
"ed25519-dalek",
"futures",
"governor",
"hickory-resolver",
"hkdf",
"hmac",
"image",
"ipld-core",
"iroh-car",
"jacquard",
"jacquard-axum",
"jacquard-repo",
"jsonwebtoken",
"k256",
"metrics",
"metrics-exporter-prometheus",
"multibase",
"multihash",
"p256 0.13.2",
"p384",
"rand 0.8.5",
"redis",
"reqwest",
"serde",
"serde_bytes",
"serde_ipld_dagcbor",
"serde_json",
"sha2",
"sqlx",
"subtle",
"testcontainers",
"testcontainers-modules",
"thiserror 2.0.17",
"tokio",
"tokio-tungstenite",
"tower-http",
"tracing",
"tracing-subscriber",
"urlencoding",
"uuid",
"wiremock",
]
[[package]]
name = "btree-range-map"
version = "0.7.2"
@@ -6223,6 +6162,67 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "tranquil-pds"
version = "0.1.0"
dependencies = [
"aes-gcm",
"anyhow",
"async-trait",
"aws-config",
"aws-sdk-s3",
"axum",
"base32",
"base64 0.22.1",
"bcrypt",
"bytes",
"chrono",
"cid",
"ctor",
"dotenvy",
"ed25519-dalek",
"futures",
"governor",
"hickory-resolver",
"hkdf",
"hmac",
"image",
"ipld-core",
"iroh-car",
"jacquard",
"jacquard-axum",
"jacquard-repo",
"jsonwebtoken",
"k256",
"metrics",
"metrics-exporter-prometheus",
"multibase",
"multihash",
"p256 0.13.2",
"p384",
"rand 0.8.5",
"redis",
"reqwest",
"serde",
"serde_bytes",
"serde_ipld_dagcbor",
"serde_json",
"sha2",
"sqlx",
"subtle",
"testcontainers",
"testcontainers-modules",
"thiserror 2.0.17",
"tokio",
"tokio-tungstenite",
"tower-http",
"tracing",
"tracing-subscriber",
"urlencoding",
"uuid",
"wiremock",
]
[[package]]
name = "triomphe"
version = "0.1.15"

View File

@@ -1,5 +1,5 @@
[package]
name = "bspds"
name = "tranquil-pds"
version = "0.1.0"
edition = "2024"
[dependencies]

View File

@@ -16,7 +16,7 @@ COPY .sqlx ./.sqlx
RUN touch src/main.rs && cargo build --release
# Stage 3: Final image
FROM alpine:3.23
COPY --from=builder /app/target/release/bspds /usr/local/bin/bspds
COPY --from=builder /app/target/release/tranquil-pds /usr/local/bin/tranquil-pds
COPY --from=builder /app/migrations /app/migrations
COPY --from=frontend-builder /frontend/dist /app/frontend/dist
WORKDIR /app
@@ -24,4 +24,4 @@ ENV SERVER_HOST=0.0.0.0
ENV SERVER_PORT=3000
ENV FRONTEND_DIR=/app/frontend/dist
EXPOSE 3000
CMD ["bspds"]
CMD ["tranquil-pds"]

View File

@@ -1,4 +1,4 @@
# BSPDS
# Tranquil PDS
A production-grade Personal Data Server (PDS) for the AT Protocol. Drop-in replacement for Bluesky's reference PDS, written in rust with postgres and s3-compatible blob storage.

View File

@@ -1,20 +0,0 @@
[Unit]
Description=BSPDS postgres database
[Container]
ContainerName=bspds-db
Image=docker.io/library/postgres:18-alpine
Pod=bspds.pod
Environment=POSTGRES_USER=bspds
Environment=POSTGRES_DB=pds
Secret=bspds-db-password,type=env,target=POSTGRES_PASSWORD
Volume=/srv/bspds/postgres:/var/lib/postgresql/data:Z
HealthCmd=pg_isready -U bspds -d pds
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5
HealthStartPeriod=10s
[Service]
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

View File

@@ -1,15 +0,0 @@
[Unit]
Description=BSPDS nginx reverse proxy
After=bspds-app.service
[Container]
ContainerName=bspds-nginx
Image=docker.io/library/nginx:1.28-alpine
Pod=bspds.pod
Volume=/srv/bspds/config/nginx.conf:/etc/nginx/nginx.conf:ro,Z
Volume=/srv/bspds/certs:/etc/nginx/certs:ro,Z
Volume=/srv/bspds/acme:/var/www/acme:ro,Z
[Service]
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

View File

@@ -1,11 +1,11 @@
[Unit]
Description=BSPDS AT Protocol PDS
After=bspds-db.service bspds-minio.service bspds-valkey.service
Description=Tranquil PDS AT Protocol PDS
After=tranquil-pds-db.service tranquil-pds-minio.service tranquil-pds-valkey.service
[Container]
ContainerName=bspds-app
Image=localhost/bspds:latest
Pod=bspds.pod
EnvironmentFile=/srv/bspds/config/bspds.env
ContainerName=tranquil-pds-app
Image=localhost/tranquil-pds:latest
Pod=tranquil-pds.pod
EnvironmentFile=/srv/tranquil-pds/config/tranquil-pds.env
Environment=SERVER_HOST=0.0.0.0
Environment=SERVER_PORT=3000
Environment=S3_ENDPOINT=http://localhost:9000

View File

@@ -0,0 +1,20 @@
[Unit]
Description=Tranquil PDS postgres database
[Container]
ContainerName=tranquil-pds-db
Image=docker.io/library/postgres:18-alpine
Pod=tranquil-pds.pod
Environment=POSTGRES_USER=tranquil_pds
Environment=POSTGRES_DB=pds
Secret=tranquil-pds-db-password,type=env,target=POSTGRES_PASSWORD
Volume=/srv/tranquil-pds/postgres:/var/lib/postgresql/data:Z
HealthCmd=pg_isready -U tranquil_pds -d pds
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5
HealthStartPeriod=10s
[Service]
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

View File

@@ -1,12 +1,12 @@
[Unit]
Description=BSPDS minio object storage
Description=Tranquil PDS minio object storage
[Container]
ContainerName=bspds-minio
ContainerName=tranquil-pds-minio
Image=docker.io/minio/minio:RELEASE.2025-10-15T17-29-55Z
Pod=bspds.pod
Pod=tranquil-pds.pod
Environment=MINIO_ROOT_USER=minioadmin
Secret=bspds-minio-password,type=env,target=MINIO_ROOT_PASSWORD
Volume=/srv/bspds/minio:/data:Z
Secret=tranquil-pds-minio-password,type=env,target=MINIO_ROOT_PASSWORD
Volume=/srv/tranquil-pds/minio:/data:Z
Exec=server /data --console-address :9001
HealthCmd=curl -f http://localhost:9000/minio/health/live || exit 1
HealthInterval=30s

View File

@@ -0,0 +1,15 @@
[Unit]
Description=Tranquil PDS nginx reverse proxy
After=tranquil-pds-app.service
[Container]
ContainerName=tranquil-pds-nginx
Image=docker.io/library/nginx:1.28-alpine
Pod=tranquil-pds.pod
Volume=/srv/tranquil-pds/config/nginx.conf:/etc/nginx/nginx.conf:ro,Z
Volume=/srv/tranquil-pds/certs:/etc/nginx/certs:ro,Z
Volume=/srv/tranquil-pds/acme:/var/www/acme:ro,Z
[Service]
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

View File

@@ -1,10 +1,10 @@
[Unit]
Description=BSPDS valkey cache
Description=Tranquil PDS valkey cache
[Container]
ContainerName=bspds-valkey
ContainerName=tranquil-pds-valkey
Image=docker.io/valkey/valkey:9-alpine
Pod=bspds.pod
Volume=/srv/bspds/valkey:/data:Z
Pod=tranquil-pds.pod
Volume=/srv/tranquil-pds/valkey:/data:Z
Exec=valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
HealthCmd=valkey-cli ping
HealthInterval=10s

View File

@@ -1,5 +1,5 @@
[Pod]
PodName=bspds
PodName=tranquil-pds
PublishPort=80:80
PublishPort=443:443
[Install]

View File

@@ -1,9 +1,9 @@
services:
bspds:
tranquil-pds:
build:
context: .
dockerfile: Dockerfile
image: bspds:latest
image: tranquil-pds:latest
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
@@ -11,7 +11,7 @@ services:
SERVER_HOST: "0.0.0.0"
SERVER_PORT: "3000"
PDS_HOSTNAME: "${PDS_HOSTNAME:?PDS_HOSTNAME is required}"
DATABASE_URL: "postgres://bspds:${DB_PASSWORD:?DB_PASSWORD is required}@db:5432/pds"
DATABASE_URL: "postgres://tranquil_pds:${DB_PASSWORD:?DB_PASSWORD is required}@db:5432/pds"
S3_ENDPOINT: "http://minio:9000"
AWS_REGION: "us-east-1"
S3_BUCKET: "pds-blobs"
@@ -46,13 +46,13 @@ services:
image: postgres:18-alpine
restart: unless-stopped
environment:
POSTGRES_USER: bspds
POSTGRES_USER: tranquil_pds
POSTGRES_PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required}"
POSTGRES_DB: pds
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bspds -d pds"]
test: ["CMD-SHELL", "pg_isready -U tranquil_pds -d pds"]
interval: 10s
timeout: 5s
retries: 5
@@ -128,7 +128,7 @@ services:
- ./certs:/etc/nginx/certs:ro
- acme_challenge:/var/www/acme:ro
depends_on:
- bspds
- tranquil-pds
healthcheck:
test: ["CMD", "nginx", "-t"]
interval: 30s

View File

@@ -3,7 +3,7 @@ services:
build:
context: .
dockerfile: Dockerfile
image: bspds
image: tranquil-pds
ports:
- "3000:3000"
env_file:

View File

@@ -1,7 +1,7 @@
# BSPDS Production Installation on Alpine Linux
# Tranquil PDS Production Installation on Alpine Linux
> **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.
This guide covers installing BSPDS on Alpine Linux 3.23 (current stable as of December 2025).
This guide covers installing Tranquil PDS on Alpine Linux 3.23.
## Prerequisites
- A VPS with at least 2GB RAM and 20GB disk
@@ -20,17 +20,16 @@ rustup-init -y
source ~/.cargo/env
rustup default stable
```
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.
This installs the latest stable Rust. Alpine also ships Rust via `apk add rust cargo` if you prefer system packages.
## 3. Install postgres
Alpine 3.23 includes PostgreSQL 18:
```sh
apk add postgresql postgresql-contrib
rc-update add postgresql
/etc/init.d/postgresql setup
rc-service postgresql start
psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';"
psql -U postgres -c "CREATE DATABASE pds OWNER bspds;"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;"
psql -U postgres -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';"
psql -U postgres -c "CREATE DATABASE pds OWNER tranquil_pds;"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;"
```
## 4. Install minio
```sh
@@ -78,7 +77,6 @@ mc alias set local http://localhost:9000 minioadmin your-minio-password
mc mb local/pds-blobs
```
## 5. Install valkey
Alpine 3.23 includes Valkey 9:
```sh
apk add valkey
rc-update add valkey
@@ -90,11 +88,11 @@ curl -fsSL https://deno.land/install.sh | sh
export PATH="$HOME/.deno/bin:$PATH"
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile
```
## 7. Clone and Build BSPDS
## 7. Clone and Build Tranquil PDS
```sh
mkdir -p /opt && cd /opt
git clone https://tangled.org/lewis.moe/bspds-sandbox bspds
cd bspds
git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds
cd tranquil-pds
cd frontend
deno task build
cd ..
@@ -103,56 +101,55 @@ cargo build --release
## 8. Install sqlx-cli and Run Migrations
```sh
cargo install sqlx-cli --no-default-features --features postgres
export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds"
export DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds"
sqlx migrate run
```
## 9. Configure BSPDS
## 9. Configure Tranquil PDS
```sh
mkdir -p /etc/bspds
cp /opt/bspds/.env.example /etc/bspds/bspds.env
chmod 600 /etc/bspds/bspds.env
mkdir -p /etc/tranquil-pds
cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.env
chmod 600 /etc/tranquil-pds/tranquil-pds.env
```
Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with:
Edit `/etc/tranquil-pds/tranquil-pds.env` and fill in your values. Generate secrets with:
```sh
openssl rand -base64 48
```
## 10. Create OpenRC Service
```sh
adduser -D -H -s /sbin/nologin bspds
cp /opt/bspds/target/release/bspds /usr/local/bin/
mkdir -p /var/lib/bspds
cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend
chown -R bspds:bspds /var/lib/bspds
cat > /etc/init.d/bspds << 'EOF'
adduser -D -H -s /sbin/nologin tranquil-pds
cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/
mkdir -p /var/lib/tranquil-pds
cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend
chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds
cat > /etc/init.d/tranquil-pds << 'EOF'
#!/sbin/openrc-run
name="bspds"
description="BSPDS - AT Protocol PDS"
command="/usr/local/bin/bspds"
command_user="bspds"
name="tranquil-pds"
description="Tranquil PDS - AT Protocol PDS"
command="/usr/local/bin/tranquil-pds"
command_user="tranquil-pds"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"
output_log="/var/log/bspds.log"
error_log="/var/log/bspds.log"
output_log="/var/log/tranquil-pds.log"
error_log="/var/log/tranquil-pds.log"
depend() {
need net postgresql minio
}
start_pre() {
export FRONTEND_DIR=/var/lib/bspds/frontend
. /etc/bspds/bspds.env
export FRONTEND_DIR=/var/lib/tranquil-pds/frontend
. /etc/tranquil-pds/tranquil-pds.env
export SERVER_HOST SERVER_PORT PDS_HOSTNAME DATABASE_URL
export S3_ENDPOINT AWS_REGION S3_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
export VALKEY_URL JWT_SECRET DPOP_SECRET MASTER_KEY CRAWLERS
}
EOF
chmod +x /etc/init.d/bspds
rc-update add bspds
rc-service bspds start
chmod +x /etc/init.d/tranquil-pds
rc-update add tranquil-pds
rc-service tranquil-pds start
```
## 11. Install and Configure nginx
Alpine 3.23 includes nginx 1.28:
```sh
apk add nginx certbot certbot-nginx
cat > /etc/nginx/http.d/bspds.conf << 'EOF'
cat > /etc/nginx/http.d/tranquil-pds.conf << 'EOF'
server {
listen 80;
listen [::]:80;
@@ -217,26 +214,26 @@ rc-update add ip6tables
```
## 14. Verify Installation
```sh
rc-service bspds status
rc-service tranquil-pds status
curl -s https://pds.example.com/xrpc/_health
curl -s https://pds.example.com/.well-known/atproto-did
```
## Maintenance
View logs:
```sh
tail -f /var/log/bspds.log
tail -f /var/log/tranquil-pds.log
```
Update BSPDS:
Update Tranquil PDS:
```sh
cd /opt/bspds
cd /opt/tranquil-pds
git pull
cd frontend && deno task build && cd ..
cargo build --release
rc-service bspds stop
cp target/release/bspds /usr/local/bin/
cp -r frontend/dist /var/lib/bspds/frontend
DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run
rc-service bspds start
rc-service tranquil-pds stop
cp target/release/tranquil-pds /usr/local/bin/
cp -r frontend/dist /var/lib/tranquil-pds/frontend
DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" sqlx migrate run
rc-service tranquil-pds start
```
Backup database:
```sh

View File

@@ -1,6 +1,6 @@
# BSPDS Containerized Production Deployment
# Tranquil PDS Containerized Production Deployment
> **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.
This guide covers deploying BSPDS using containers with podman.
This guide covers deploying Tranquil PDS using containers with podman.
- **Debian 13+**: Uses systemd quadlets (modern, declarative container management)
- **Alpine 3.23+**: Uses OpenRC service script with podman-compose
## Prerequisites
@@ -39,14 +39,14 @@ apt install -y podman
## 2. Create Directory Structure
```bash
mkdir -p /etc/containers/systemd
mkdir -p /srv/bspds/{postgres,minio,valkey,certs,acme,config}
mkdir -p /srv/tranquil-pds/{postgres,minio,valkey,certs,acme,config}
```
## 3. Create Environment File
```bash
cp /opt/bspds/.env.example /srv/bspds/config/bspds.env
chmod 600 /srv/bspds/config/bspds.env
cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env
chmod 600 /srv/tranquil-pds/config/tranquil-pds.env
```
Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with:
Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with:
```bash
openssl rand -base64 48
```
@@ -54,37 +54,37 @@ For quadlets, also add `DATABASE_URL` with the full connection string (systemd d
## 4. Install Quadlet Definitions
Copy the quadlet files from the repository:
```bash
cp /opt/bspds/deploy/quadlets/*.pod /etc/containers/systemd/
cp /opt/bspds/deploy/quadlets/*.container /etc/containers/systemd/
cp /opt/tranquil-pds/deploy/quadlets/*.pod /etc/containers/systemd/
cp /opt/tranquil-pds/deploy/quadlets/*.container /etc/containers/systemd/
```
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.
## 5. Create nginx Configuration
```bash
cp /opt/bspds/deploy/nginx/nginx-quadlet.conf /srv/bspds/config/nginx.conf
cp /opt/tranquil-pds/deploy/nginx/nginx-quadlet.conf /srv/tranquil-pds/config/nginx.conf
```
## 6. Build BSPDS Image
## 6. Build Tranquil PDS Image
```bash
cd /opt
git clone https://tangled.org/lewis.moe/bspds-sandbox bspds
cd bspds
podman build -t bspds:latest .
git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds
cd tranquil-pds
podman build -t tranquil-pds:latest .
```
## 7. Create Podman Secrets
```bash
source /srv/bspds/config/bspds.env
echo "$DB_PASSWORD" | podman secret create bspds-db-password -
echo "$MINIO_ROOT_PASSWORD" | podman secret create bspds-minio-password -
source /srv/tranquil-pds/config/tranquil-pds.env
echo "$DB_PASSWORD" | podman secret create tranquil-pds-db-password -
echo "$MINIO_ROOT_PASSWORD" | podman secret create tranquil-pds-minio-password -
```
## 8. Start Services and Initialize
```bash
systemctl daemon-reload
systemctl start bspds-db bspds-minio bspds-valkey
systemctl start tranquil-pds-db tranquil-pds-minio tranquil-pds-valkey
sleep 10
```
Create the minio bucket:
```bash
podman run --rm --pod bspds \
podman run --rm --pod tranquil-pds \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=your-minio-password \
docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \
@@ -94,7 +94,7 @@ podman run --rm --pod bspds \
Run migrations:
```bash
cargo install sqlx-cli --no-default-features --features postgres
DATABASE_URL="postgres://bspds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/bspds/migrations
DATABASE_URL="postgres://tranquil_pds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/tranquil-pds/migrations
```
## 9. Obtain Wildcard SSL Certificate
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,16 +102,16 @@ User handles are served as subdomains (e.g., `alice.pds.example.com`), so you ne
Create temporary self-signed cert to start services:
```bash
openssl req -x509 -nodes -days 1 -newkey rsa:2048 \
-keyout /srv/bspds/certs/privkey.pem \
-out /srv/bspds/certs/fullchain.pem \
-keyout /srv/tranquil-pds/certs/privkey.pem \
-out /srv/tranquil-pds/certs/fullchain.pem \
-subj "/CN=pds.example.com"
systemctl start bspds-app bspds-nginx
systemctl start tranquil-pds-app tranquil-pds-nginx
```
Get a wildcard certificate using DNS validation:
```bash
podman run --rm -it \
-v /srv/bspds/certs:/etc/letsencrypt:Z \
-v /srv/tranquil-pds/certs:/etc/letsencrypt:Z \
docker.io/certbot/certbot:v5.2.2 certonly \
--manual --preferred-challenges dns \
-d pds.example.com -d '*.pds.example.com' \
@@ -123,13 +123,13 @@ For automated renewal, use a DNS provider plugin (e.g., cloudflare, route53).
Link certificates and restart:
```bash
ln -sf /srv/bspds/certs/live/pds.example.com/fullchain.pem /srv/bspds/certs/fullchain.pem
ln -sf /srv/bspds/certs/live/pds.example.com/privkey.pem /srv/bspds/certs/privkey.pem
systemctl restart bspds-nginx
ln -sf /srv/tranquil-pds/certs/live/pds.example.com/fullchain.pem /srv/tranquil-pds/certs/fullchain.pem
ln -sf /srv/tranquil-pds/certs/live/pds.example.com/privkey.pem /srv/tranquil-pds/certs/privkey.pem
systemctl restart tranquil-pds-nginx
```
## 10. Enable All Services
```bash
systemctl enable bspds-db bspds-minio bspds-valkey bspds-app bspds-nginx
systemctl enable tranquil-pds-db tranquil-pds-minio tranquil-pds-valkey tranquil-pds-app tranquil-pds-nginx
```
## 11. Configure Firewall
```bash
@@ -142,7 +142,7 @@ ufw enable
## 12. Certificate Renewal
Add to root's crontab (`crontab -e`):
```
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
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
```
---
# Alpine 3.23+ with OpenRC
@@ -161,79 +161,79 @@ rc-service podman start
```
## 2. Create Directory Structure
```sh
mkdir -p /srv/bspds/{data,config}
mkdir -p /srv/bspds/data/{postgres,minio,valkey,certs,acme}
mkdir -p /srv/tranquil-pds/{data,config}
mkdir -p /srv/tranquil-pds/data/{postgres,minio,valkey,certs,acme}
```
## 3. Clone Repository and Build
```sh
cd /opt
git clone https://tangled.org/lewis.moe/bspds-sandbox bspds
cd bspds
podman build -t bspds:latest .
git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds
cd tranquil-pds
podman build -t tranquil-pds:latest .
```
## 4. Create Environment File
```sh
cp /opt/bspds/.env.example /srv/bspds/config/bspds.env
chmod 600 /srv/bspds/config/bspds.env
cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env
chmod 600 /srv/tranquil-pds/config/tranquil-pds.env
```
Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with:
Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with:
```sh
openssl rand -base64 48
```
## 5. Set Up Compose and nginx
Copy the production compose and nginx configs:
```sh
cp /opt/bspds/docker-compose.prod.yml /srv/bspds/docker-compose.yml
cp /opt/bspds/nginx.prod.conf /srv/bspds/config/nginx.conf
cp /opt/tranquil-pds/docker-compose.prod.yml /srv/tranquil-pds/docker-compose.yml
cp /opt/tranquil-pds/nginx.prod.conf /srv/tranquil-pds/config/nginx.conf
```
Edit `/srv/bspds/docker-compose.yml` to adjust paths if needed:
- Update volume mounts to use `/srv/bspds/data/` paths
- Update nginx cert paths to match `/srv/bspds/data/certs/`
Edit `/srv/bspds/config/nginx.conf` to update cert paths:
Edit `/srv/tranquil-pds/docker-compose.yml` to adjust paths if needed:
- Update volume mounts to use `/srv/tranquil-pds/data/` paths
- Update nginx cert paths to match `/srv/tranquil-pds/data/certs/`
Edit `/srv/tranquil-pds/config/nginx.conf` to update cert paths:
- Change `/etc/nginx/certs/live/${PDS_HOSTNAME}/` to `/etc/nginx/certs/`
## 6. Create OpenRC Service
```sh
cat > /etc/init.d/bspds << 'EOF'
cat > /etc/init.d/tranquil-pds << 'EOF'
#!/sbin/openrc-run
name="bspds"
description="BSPDS AT Protocol PDS (containerized)"
name="tranquil-pds"
description="Tranquil PDS AT Protocol PDS (containerized)"
command="/usr/bin/podman-compose"
command_args="-f /srv/bspds/docker-compose.yml up"
command_args="-f /srv/tranquil-pds/docker-compose.yml up"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"
directory="/srv/bspds"
directory="/srv/tranquil-pds"
depend() {
need net podman
after firewall
}
start_pre() {
set -a
. /srv/bspds/config/bspds.env
. /srv/tranquil-pds/config/tranquil-pds.env
set +a
}
stop() {
ebegin "Stopping ${name}"
cd /srv/bspds
cd /srv/tranquil-pds
set -a
. /srv/bspds/config/bspds.env
. /srv/tranquil-pds/config/tranquil-pds.env
set +a
podman-compose -f /srv/bspds/docker-compose.yml down
podman-compose -f /srv/tranquil-pds/docker-compose.yml down
eend $?
}
EOF
chmod +x /etc/init.d/bspds
chmod +x /etc/init.d/tranquil-pds
```
## 7. Initialize Services
Start services:
```sh
rc-service bspds start
rc-service tranquil-pds start
sleep 15
```
Create the minio bucket:
```sh
source /srv/bspds/config/bspds.env
podman run --rm --network bspds_default \
source /srv/tranquil-pds/config/tranquil-pds.env
podman run --rm --network tranquil-pds_default \
-e MINIO_ROOT_USER="$MINIO_ROOT_USER" \
-e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \
docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \
@@ -246,8 +246,8 @@ apk add rustup
rustup-init -y
source ~/.cargo/env
cargo install sqlx-cli --no-default-features --features postgres
DB_IP=$(podman inspect bspds-db-1 --format '{{.NetworkSettings.Networks.bspds_default.IPAddress}}')
DATABASE_URL="postgres://bspds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/bspds/migrations
DB_IP=$(podman inspect tranquil-pds-db-1 --format '{{.NetworkSettings.Networks.tranquil-pds_default.IPAddress}}')
DATABASE_URL="postgres://tranquil_pds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/tranquil-pds/migrations
```
## 8. Obtain Wildcard SSL Certificate
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,16 +255,16 @@ User handles are served as subdomains (e.g., `alice.pds.example.com`), so you ne
Create temporary self-signed cert to start services:
```sh
openssl req -x509 -nodes -days 1 -newkey rsa:2048 \
-keyout /srv/bspds/data/certs/privkey.pem \
-out /srv/bspds/data/certs/fullchain.pem \
-keyout /srv/tranquil-pds/data/certs/privkey.pem \
-out /srv/tranquil-pds/data/certs/fullchain.pem \
-subj "/CN=pds.example.com"
rc-service bspds restart
rc-service tranquil-pds restart
```
Get a wildcard certificate using DNS validation:
```sh
podman run --rm -it \
-v /srv/bspds/data/certs:/etc/letsencrypt \
-v /srv/tranquil-pds/data/certs:/etc/letsencrypt \
docker.io/certbot/certbot:v5.2.2 certonly \
--manual --preferred-challenges dns \
-d pds.example.com -d '*.pds.example.com' \
@@ -274,13 +274,13 @@ Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't aut
Link certificates and restart:
```sh
ln -sf /srv/bspds/data/certs/live/pds.example.com/fullchain.pem /srv/bspds/data/certs/fullchain.pem
ln -sf /srv/bspds/data/certs/live/pds.example.com/privkey.pem /srv/bspds/data/certs/privkey.pem
rc-service bspds restart
ln -sf /srv/tranquil-pds/data/certs/live/pds.example.com/fullchain.pem /srv/tranquil-pds/data/certs/fullchain.pem
ln -sf /srv/tranquil-pds/data/certs/live/pds.example.com/privkey.pem /srv/tranquil-pds/data/certs/privkey.pem
rc-service tranquil-pds restart
```
## 9. Enable Service at Boot
```sh
rc-update add bspds
rc-update add tranquil-pds
```
## 10. Configure Firewall
```sh
@@ -305,7 +305,7 @@ rc-update add ip6tables
## 11. Certificate Renewal
Add to root's crontab (`crontab -e`):
```
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
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
```
---
# Verification and Maintenance
@@ -317,36 +317,36 @@ curl -s https://pds.example.com/.well-known/atproto-did
## View Logs
**Debian:**
```bash
journalctl -u bspds-app -f
podman logs -f bspds-app
journalctl -u tranquil-pds-app -f
podman logs -f tranquil-pds-app
```
**Alpine:**
```sh
podman-compose -f /srv/bspds/docker-compose.yml logs -f
podman logs -f bspds-bspds-1
podman-compose -f /srv/tranquil-pds/docker-compose.yml logs -f
podman logs -f tranquil-pds-tranquil-pds-1
```
## Update BSPDS
## Update Tranquil PDS
```sh
cd /opt/bspds
cd /opt/tranquil-pds
git pull
podman build -t bspds:latest .
podman build -t tranquil-pds:latest .
```
Debian:
```bash
systemctl restart bspds-app
systemctl restart tranquil-pds-app
```
Alpine:
```sh
rc-service bspds restart
rc-service tranquil-pds restart
```
## Backup Database
**Debian:**
```bash
podman exec bspds-db pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql
podman exec tranquil-pds-db pg_dump -U tranquil_pds pds > /var/backups/pds-$(date +%Y%m%d).sql
```
**Alpine:**
```sh
podman exec bspds-db-1 pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql
podman exec tranquil-pds-db-1 pg_dump -U tranquil_pds pds > /var/backups/pds-$(date +%Y%m%d).sql
```

View File

@@ -1,7 +1,7 @@
# BSPDS Production Installation on Debian
# Tranquil PDS Production Installation on Debian
> **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.
This guide covers installing BSPDS on Debian 13 "Trixie" (current stable as of December 2025).
This guide covers installing Tranquil PDS on Debian 13 "Trixie".
## Prerequisites
- A VPS with at least 2GB RAM and 20GB disk
@@ -19,16 +19,15 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
rustup default stable
```
This installs the latest stable Rust (1.92+ as of December 2025).
This installs the latest stable Rust.
## 3. Install postgres
Debian 13 includes PostgreSQL 17:
```bash
apt install -y postgresql postgresql-contrib
systemctl enable postgresql
systemctl start postgresql
sudo -u postgres psql -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';"
sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;"
sudo -u postgres psql -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';"
sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;"
```
## 4. Install minio
```bash
@@ -71,7 +70,6 @@ mc alias set local http://localhost:9000 minioadmin your-minio-password
mc mb local/pds-blobs
```
## 5. Install valkey
Debian 13 includes Valkey 8:
```bash
apt install -y valkey
systemctl enable valkey-server
@@ -83,11 +81,11 @@ curl -fsSL https://deno.land/install.sh | sh
export PATH="$HOME/.deno/bin:$PATH"
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
```
## 7. Clone and Build BSPDS
## 7. Clone and Build Tranquil PDS
```bash
cd /opt
git clone https://tangled.org/lewis.moe/bspds-sandbox bspds
cd bspds
git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds
cd tranquil-pds
cd frontend
deno task build
cd ..
@@ -96,51 +94,50 @@ cargo build --release
## 8. Install sqlx-cli and Run Migrations
```bash
cargo install sqlx-cli --no-default-features --features postgres
export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds"
export DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds"
sqlx migrate run
```
## 9. Configure BSPDS
## 9. Configure Tranquil PDS
```bash
mkdir -p /etc/bspds
cp /opt/bspds/.env.example /etc/bspds/bspds.env
chmod 600 /etc/bspds/bspds.env
mkdir -p /etc/tranquil-pds
cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.env
chmod 600 /etc/tranquil-pds/tranquil-pds.env
```
Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with:
Edit `/etc/tranquil-pds/tranquil-pds.env` and fill in your values. Generate secrets with:
```bash
openssl rand -base64 48
```
## 10. Create Systemd Service
```bash
useradd -r -s /sbin/nologin bspds
cp /opt/bspds/target/release/bspds /usr/local/bin/
mkdir -p /var/lib/bspds
cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend
chown -R bspds:bspds /var/lib/bspds
cat > /etc/systemd/system/bspds.service << 'EOF'
useradd -r -s /sbin/nologin tranquil-pds
cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/
mkdir -p /var/lib/tranquil-pds
cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend
chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds
cat > /etc/systemd/system/tranquil-pds.service << 'EOF'
[Unit]
Description=BSPDS - AT Protocol PDS
Description=Tranquil PDS - AT Protocol PDS
After=network.target postgresql.service minio.service
[Service]
Type=simple
User=bspds
Group=bspds
EnvironmentFile=/etc/bspds/bspds.env
Environment=FRONTEND_DIR=/var/lib/bspds/frontend
ExecStart=/usr/local/bin/bspds
User=tranquil-pds
Group=tranquil-pds
EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env
Environment=FRONTEND_DIR=/var/lib/tranquil-pds/frontend
ExecStart=/usr/local/bin/tranquil-pds
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable bspds
systemctl start bspds
systemctl enable tranquil-pds
systemctl start tranquil-pds
```
## 11. Install and Configure nginx
Debian 13 includes nginx 1.26:
```bash
apt install -y nginx certbot python3-certbot-nginx
cat > /etc/nginx/sites-available/bspds << 'EOF'
cat > /etc/nginx/sites-available/tranquil-pds << 'EOF'
server {
listen 80;
listen [::]:80;
@@ -158,7 +155,7 @@ server {
}
}
EOF
ln -s /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
@@ -192,26 +189,26 @@ ufw enable
```
## 14. Verify Installation
```bash
systemctl status bspds
systemctl status tranquil-pds
curl -s https://pds.example.com/xrpc/_health | jq
curl -s https://pds.example.com/.well-known/atproto-did
```
## Maintenance
View logs:
```bash
journalctl -u bspds -f
journalctl -u tranquil-pds -f
```
Update BSPDS:
Update Tranquil PDS:
```bash
cd /opt/bspds
cd /opt/tranquil-pds
git pull
cd frontend && deno task build && cd ..
cargo build --release
systemctl stop bspds
cp target/release/bspds /usr/local/bin/
cp -r frontend/dist /var/lib/bspds/frontend
DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run
systemctl start bspds
systemctl stop tranquil-pds
cp target/release/tranquil-pds /usr/local/bin/
cp -r frontend/dist /var/lib/tranquil-pds/frontend
DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" sqlx migrate run
systemctl start tranquil-pds
```
Backup database:
```bash

View File

@@ -1,4 +1,4 @@
# BSPDS on Kubernetes
# Tranquil PDS on Kubernetes
If you're reaching for kubernetes for this app, you're experienced enough to know how to spin up:

View File

@@ -1,6 +1,6 @@
# BSPDS Production Installation on OpenBSD
# Tranquil PDS Production Installation on OpenBSD
> **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.
This guide covers installing BSPDS on OpenBSD 7.8 (current release as of December 2025).
This guide covers installing Tranquil PDS on OpenBSD 7.8.
## Prerequisites
- A VPS with at least 2GB RAM and 20GB disk
- A domain name pointing to your server's IP
@@ -16,7 +16,7 @@ pkg_add curl git
```sh
pkg_add rust
```
OpenBSD 7.8 ships Rust 1.82+. For the latest stable (1.92+), use rustup:
OpenBSD ships Rust in ports. For the latest stable, use rustup:
```sh
pkg_add rustup
rustup-init -y
@@ -24,7 +24,6 @@ source ~/.cargo/env
rustup default stable
```
## 3. Install postgres
OpenBSD 7.8 includes PostgreSQL 17 (PostgreSQL 18 may not yet be in ports):
```sh
pkg_add postgresql-server postgresql-client
mkdir -p /var/postgresql/data
@@ -32,9 +31,9 @@ chown _postgresql:_postgresql /var/postgresql/data
su - _postgresql -c "initdb -D /var/postgresql/data -U postgres -A scram-sha-256"
rcctl enable postgresql
rcctl start postgresql
psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';"
psql -U postgres -c "CREATE DATABASE pds OWNER bspds;"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;"
psql -U postgres -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';"
psql -U postgres -c "CREATE DATABASE pds OWNER tranquil_pds;"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;"
```
## 4. Install minio
OpenBSD doesn't have a minio package. Options:
@@ -93,11 +92,11 @@ curl -fsSL https://deno.land/install.sh | sh
export PATH="$HOME/.deno/bin:$PATH"
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile
```
## 7. Clone and Build BSPDS
## 7. Clone and Build Tranquil PDS
```sh
mkdir -p /opt && cd /opt
git clone https://tangled.org/lewis.moe/bspds-sandbox bspds
cd bspds
git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds
cd tranquil-pds
cd frontend
deno task build
cd ..
@@ -106,46 +105,46 @@ cargo build --release
## 8. Install sqlx-cli and Run Migrations
```sh
cargo install sqlx-cli --no-default-features --features postgres
export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds"
export DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds"
sqlx migrate run
```
## 9. Configure BSPDS
## 9. Configure Tranquil PDS
```sh
mkdir -p /etc/bspds
cp /opt/bspds/.env.example /etc/bspds/bspds.conf
chmod 600 /etc/bspds/bspds.conf
mkdir -p /etc/tranquil-pds
cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.conf
chmod 600 /etc/tranquil-pds/tranquil-pds.conf
```
Edit `/etc/bspds/bspds.conf` and fill in your values. Generate secrets with:
Edit `/etc/tranquil-pds/tranquil-pds.conf` and fill in your values. Generate secrets with:
```sh
openssl rand -base64 48
```
## 10. Create rc.d Service
```sh
useradd -d /var/empty -s /sbin/nologin _bspds
cp /opt/bspds/target/release/bspds /usr/local/bin/
mkdir -p /var/bspds
cp -r /opt/bspds/frontend/dist /var/bspds/frontend
chown -R _bspds:_bspds /var/bspds
cat > /etc/rc.d/bspds << 'EOF'
useradd -d /var/empty -s /sbin/nologin _tranquil_pds
cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/
mkdir -p /var/tranquil-pds
cp -r /opt/tranquil-pds/frontend/dist /var/tranquil-pds/frontend
chown -R _tranquil_pds:_tranquil_pds /var/tranquil-pds
cat > /etc/rc.d/tranquil_pds << 'EOF'
#!/bin/ksh
daemon="/usr/local/bin/bspds"
daemon_user="_bspds"
daemon="/usr/local/bin/tranquil-pds"
daemon_user="_tranquil_pds"
daemon_logger="daemon.info"
. /etc/rc.d/rc.subr
rc_pre() {
export FRONTEND_DIR=/var/bspds/frontend
export FRONTEND_DIR=/var/tranquil-pds/frontend
while IFS='=' read -r key value; do
case "$key" in
\#*|"") continue ;;
esac
export "$key=$value"
done < /etc/bspds/bspds.conf
done < /etc/tranquil-pds/tranquil-pds.conf
}
rc_cmd $1
EOF
chmod +x /etc/rc.d/bspds
rcctl enable bspds
rcctl start bspds
chmod +x /etc/rc.d/tranquil_pds
rcctl enable tranquil_pds
rcctl start tranquil_pds
```
## 11. Install and Configure nginx
```sh
@@ -227,7 +226,7 @@ pfctl -f /etc/pf.conf
```
## 14. Verify Installation
```sh
rcctl check bspds
rcctl check tranquil_pds
ftp -o - https://pds.example.com/xrpc/_health
ftp -o - https://pds.example.com/.well-known/atproto-did
```
@@ -236,17 +235,17 @@ View logs:
```sh
tail -f /var/log/daemon
```
Update BSPDS:
Update Tranquil PDS:
```sh
cd /opt/bspds
cd /opt/tranquil-pds
git pull
cd frontend && deno task build && cd ..
cargo build --release
rcctl stop bspds
cp target/release/bspds /usr/local/bin/
cp -r frontend/dist /var/bspds/frontend
DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run
rcctl start bspds
rcctl stop tranquil_pds
cp target/release/tranquil-pds /usr/local/bin/
cp -r frontend/dist /var/tranquil-pds/frontend
DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" sqlx migrate run
rcctl start tranquil_pds
```
Backup database:
```sh

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BSPDS</title>
<title>Tranquil PDS</title>
<style>
html { background: #fafafa; }
@media (prefers-color-scheme: dark) { html { background: #1a1a1a; } }

View File

@@ -1,5 +1,5 @@
{
"name": "bspds-frontend",
"name": "tranquil-pds-frontend",
"private": true,
"version": "0.0.0",
"type": "module",

View File

@@ -255,7 +255,7 @@ export const api = {
signalNumber: string | null
signalVerified: boolean
}> {
return xrpc('com.bspds.account.getNotificationPrefs', { token })
return xrpc('com.tranquil.account.getNotificationPrefs', { token })
},
async updateNotificationPrefs(token: string, prefs: {
@@ -264,7 +264,7 @@ export const api = {
telegramUsername?: string
signalNumber?: string
}): Promise<{ success: boolean }> {
return xrpc('com.bspds.account.updateNotificationPrefs', {
return xrpc('com.tranquil.account.updateNotificationPrefs', {
method: 'POST',
token,
body: prefs,
@@ -272,7 +272,7 @@ export const api = {
},
async confirmChannelVerification(token: string, channel: string, code: string): Promise<{ success: boolean }> {
return xrpc('com.bspds.account.confirmChannelVerification', {
return xrpc('com.tranquil.account.confirmChannelVerification', {
method: 'POST',
token,
body: { channel, code },
@@ -289,7 +289,7 @@ export const api = {
body: string
}>
}> {
return xrpc('com.bspds.account.getNotificationHistory', { token })
return xrpc('com.tranquil.account.getNotificationHistory', { token })
},
async getServerStats(token: string): Promise<{
@@ -298,11 +298,11 @@ export const api = {
recordCount: number
blobStorageBytes: number
}> {
return xrpc('com.bspds.admin.getServerStats', { token })
return xrpc('com.tranquil.admin.getServerStats', { token })
},
async changePassword(token: string, currentPassword: string, newPassword: string): Promise<void> {
await xrpc('com.bspds.account.changePassword', {
await xrpc('com.tranquil.account.changePassword', {
method: 'POST',
token,
body: { currentPassword, newPassword },
@@ -317,11 +317,11 @@ export const api = {
isCurrent: boolean
}>
}> {
return xrpc('com.bspds.account.listSessions', { token })
return xrpc('com.tranquil.account.listSessions', { token })
},
async revokeSession(token: string, sessionId: string): Promise<void> {
await xrpc('com.bspds.account.revokeSession', {
await xrpc('com.tranquil.account.revokeSession', {
method: 'POST',
token,
body: { sessionId },

View File

@@ -1,8 +1,8 @@
import { api, type Session, type CreateAccountParams, type CreateAccountResult, ApiError } from './api'
import { startOAuthLogin, handleOAuthCallback, checkForOAuthCallback, clearOAuthCallbackParams, refreshOAuthToken } from './oauth'
const STORAGE_KEY = 'bspds_session'
const ACCOUNTS_KEY = 'bspds_accounts'
const STORAGE_KEY = 'tranquil_pds_session'
const ACCOUNTS_KEY = 'tranquil_pds_accounts'
export interface SavedAccount {
did: string

View File

@@ -1,5 +1,5 @@
const OAUTH_STATE_KEY = 'bspds_oauth_state'
const OAUTH_VERIFIER_KEY = 'bspds_oauth_verifier'
const OAUTH_STATE_KEY = 'tranquil_pds_oauth_state'
const OAUTH_VERIFIER_KEY = 'tranquil_pds_oauth_verifier'
interface OAuthState {
state: string

View File

@@ -3,7 +3,7 @@
import { navigate } from '../lib/router.svelte'
import { api, ApiError, type VerificationChannel } from '../lib/api'
const STORAGE_KEY = 'bspds_pending_verification'
const STORAGE_KEY = 'tranquil_pds_pending_verification'
let handle = $state('')
let email = $state('')

View File

@@ -2,7 +2,7 @@
import { confirmSignup, resendVerification, getAuthState } from '../lib/auth.svelte'
import { navigate } from '../lib/router.svelte'
const STORAGE_KEY = 'bspds_pending_verification'
const STORAGE_KEY = 'tranquil_pds_pending_verification'
interface PendingVerification {
did: string

View File

@@ -10,7 +10,7 @@ import {
setupAuthenticatedUser,
setupUnauthenticatedUser,
} from './mocks'
const STORAGE_KEY = 'bspds_session'
const STORAGE_KEY = 'tranquil_pds_session'
describe('Dashboard', () => {
beforeEach(() => {
clearMocks()
@@ -38,8 +38,8 @@ describe('Dashboard', () => {
await waitFor(() => {
expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument()
expect(screen.getByRole('heading', { name: /account overview/i })).toBeInTheDocument()
expect(screen.getByText(/@testuser\.test\.bspds\.dev/)).toBeInTheDocument()
expect(screen.getByText(/did:web:test\.bspds\.dev:u:testuser/)).toBeInTheDocument()
expect(screen.getByText(/@testuser\.test\.tranquil\.dev/)).toBeInTheDocument()
expect(screen.getByText(/did:web:test\.tranquil\.dev:u:testuser/)).toBeInTheDocument()
expect(screen.getByText('test@example.com')).toBeInTheDocument()
expect(screen.getByText('Verified')).toBeInTheDocument()
expect(screen.getByText('Verified')).toHaveClass('badge', 'success')

View File

@@ -95,7 +95,7 @@ describe('Login', () => {
json: async () => ({
error: 'AccountNotVerified',
message: 'Account not verified',
did: 'did:web:test.bspds.dev:u:testuser',
did: 'did:web:test.tranquil.dev:u:testuser',
}),
}))
render(Login)
@@ -116,7 +116,7 @@ describe('Login', () => {
json: async () => ({
error: 'AccountNotVerified',
message: 'Account not verified',
did: 'did:web:test.bspds.dev:u:testuser',
did: 'did:web:test.tranquil.dev:u:testuser',
}),
}))
render(Login)

View File

@@ -28,7 +28,7 @@ describe('Notifications', () => {
describe('page structure', () => {
beforeEach(() => {
setupAuthenticatedUser()
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
})
@@ -48,7 +48,7 @@ describe('Notifications', () => {
setupAuthenticatedUser()
})
it('shows loading text while fetching preferences', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', async () => {
mockEndpoint('com.tranquil.account.getNotificationPrefs', async () => {
await new Promise(resolve => setTimeout(resolve, 100))
return jsonResponse(mockData.notificationPrefs())
})
@@ -61,7 +61,7 @@ describe('Notifications', () => {
setupAuthenticatedUser()
})
it('displays all four channel options', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
render(Notifications)
@@ -73,7 +73,7 @@ describe('Notifications', () => {
})
})
it('email channel is always selectable', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
render(Notifications)
@@ -83,7 +83,7 @@ describe('Notifications', () => {
})
})
it('discord channel is disabled when not configured', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs({ discordId: null }))
)
render(Notifications)
@@ -93,7 +93,7 @@ describe('Notifications', () => {
})
})
it('discord channel is enabled when configured', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs({ discordId: '123456789' }))
)
render(Notifications)
@@ -103,7 +103,7 @@ describe('Notifications', () => {
})
})
it('shows hint for disabled channels', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
render(Notifications)
@@ -112,7 +112,7 @@ describe('Notifications', () => {
})
})
it('selects current preferred channel', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs({ preferredChannel: 'email' }))
)
render(Notifications)
@@ -127,7 +127,7 @@ describe('Notifications', () => {
setupAuthenticatedUser()
})
it('displays email as readonly with current value', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
render(Notifications)
@@ -138,7 +138,7 @@ describe('Notifications', () => {
})
})
it('displays all channel inputs with current values', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs({
discordId: '123456789',
telegramUsername: 'testuser',
@@ -158,7 +158,7 @@ describe('Notifications', () => {
setupAuthenticatedUser()
})
it('shows Primary badge for email', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
render(Notifications)
@@ -167,7 +167,7 @@ describe('Notifications', () => {
})
})
it('shows Verified badge for verified discord', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs({
discordId: '123456789',
discordVerified: true,
@@ -180,7 +180,7 @@ describe('Notifications', () => {
})
})
it('shows Not verified badge for unverified discord', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs({
discordId: '123456789',
discordVerified: false,
@@ -192,7 +192,7 @@ describe('Notifications', () => {
})
})
it('does not show badge when channel not configured', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
render(Notifications)
@@ -208,10 +208,10 @@ describe('Notifications', () => {
})
it('calls updateNotificationPrefs with correct data', async () => {
let capturedBody: Record<string, unknown> | null = null
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
mockEndpoint('com.bspds.account.updateNotificationPrefs', (_url, options) => {
mockEndpoint('com.tranquil.account.updateNotificationPrefs', (_url, options) => {
capturedBody = JSON.parse((options?.body as string) || '{}')
return jsonResponse({ success: true })
})
@@ -228,10 +228,10 @@ describe('Notifications', () => {
})
})
it('shows loading state while saving', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
mockEndpoint('com.bspds.account.updateNotificationPrefs', async () => {
mockEndpoint('com.tranquil.account.updateNotificationPrefs', async () => {
await new Promise(resolve => setTimeout(resolve, 100))
return jsonResponse({ success: true })
})
@@ -244,10 +244,10 @@ describe('Notifications', () => {
expect(screen.getByRole('button', { name: /saving/i })).toBeDisabled()
})
it('shows success message after saving', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
mockEndpoint('com.bspds.account.updateNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.updateNotificationPrefs', () =>
jsonResponse({ success: true })
)
render(Notifications)
@@ -260,10 +260,10 @@ describe('Notifications', () => {
})
})
it('shows error when save fails', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
mockEndpoint('com.bspds.account.updateNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.updateNotificationPrefs', () =>
errorResponse('InvalidRequest', 'Invalid channel configuration', 400)
)
render(Notifications)
@@ -278,11 +278,11 @@ describe('Notifications', () => {
})
it('reloads preferences after successful save', async () => {
let loadCount = 0
mockEndpoint('com.bspds.account.getNotificationPrefs', () => {
mockEndpoint('com.tranquil.account.getNotificationPrefs', () => {
loadCount++
return jsonResponse(mockData.notificationPrefs())
})
mockEndpoint('com.bspds.account.updateNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.updateNotificationPrefs', () =>
jsonResponse({ success: true })
)
render(Notifications)
@@ -301,7 +301,7 @@ describe('Notifications', () => {
setupAuthenticatedUser()
})
it('enables discord channel after entering discord ID', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
render(Notifications)
@@ -314,7 +314,7 @@ describe('Notifications', () => {
})
})
it('allows selecting a configured channel', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs({
discordId: '123456789',
discordVerified: true,
@@ -334,7 +334,7 @@ describe('Notifications', () => {
setupAuthenticatedUser()
})
it('shows error when loading preferences fails', async () => {
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
errorResponse('InternalError', 'Database connection failed', 500)
)
render(Notifications)

View File

@@ -176,7 +176,7 @@ describe('Settings', () => {
it('displays current handle', async () => {
render(Settings)
await waitFor(() => {
expect(screen.getByText(/current: @testuser\.test\.bspds\.dev/i)).toBeInTheDocument()
expect(screen.getByText(/current: @testuser\.test\.tranquil\.dev/i)).toBeInTheDocument()
})
})
it('calls updateHandle with new handle', async () => {
@@ -314,7 +314,7 @@ describe('Settings', () => {
await waitFor(() => {
expect(capturedBody?.token).toBe('DEL123')
expect(capturedBody?.password).toBe('mypassword')
expect(capturedBody?.did).toBe('did:web:test.bspds.dev:u:testuser')
expect(capturedBody?.did).toBe('did:web:test.tranquil.dev:u:testuser')
})
})
it('navigates to login after successful deletion', async () => {

View File

@@ -85,8 +85,8 @@ export function errorResponse(error: string, message: string, status = 400): Moc
}
export const mockData = {
session: (overrides?: Partial<Session>): Session => ({
did: 'did:web:test.bspds.dev:u:testuser',
handle: 'testuser.test.bspds.dev',
did: 'did:web:test.tranquil.dev:u:testuser',
handle: 'testuser.test.tranquil.dev',
email: 'test@example.com',
emailConfirmed: true,
accessJwt: 'mock-access-jwt-token',
@@ -102,8 +102,8 @@ export const mockData = {
code: 'test-invite-123',
available: 1,
disabled: false,
forAccount: 'did:web:test.bspds.dev:u:testuser',
createdBy: 'did:web:test.bspds.dev:u:testuser',
forAccount: 'did:web:test.tranquil.dev:u:testuser',
createdBy: 'did:web:test.tranquil.dev:u:testuser',
createdAt: new Date().toISOString(),
uses: [],
...overrides,
@@ -120,7 +120,7 @@ export const mockData = {
...overrides,
}),
describeServer: () => ({
availableUserDomains: ['test.bspds.dev'],
availableUserDomains: ['test.tranquil.dev'],
inviteCodeRequired: false,
links: {
privacyPolicy: 'https://example.com/privacy',
@@ -128,7 +128,7 @@ export const mockData = {
},
}),
describeRepo: (did: string) => ({
handle: 'testuser.test.bspds.dev',
handle: 'testuser.test.tranquil.dev',
did,
didDoc: {},
collections: ['app.bsky.feed.post', 'app.bsky.feed.like', 'app.bsky.graph.follow'],
@@ -173,10 +173,10 @@ export function setupDefaultMocks(): void {
mockEndpoint('com.atproto.server.createInviteCode', () =>
jsonResponse({ code: 'new-invite-' + Date.now() })
)
mockEndpoint('com.bspds.account.getNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.getNotificationPrefs', () =>
jsonResponse(mockData.notificationPrefs())
)
mockEndpoint('com.bspds.account.updateNotificationPrefs', () =>
mockEndpoint('com.tranquil.account.updateNotificationPrefs', () =>
jsonResponse({ success: true })
)
mockEndpoint('com.atproto.server.requestEmailUpdate', () =>

View File

@@ -25,7 +25,7 @@ test-file file:
./scripts/run-tests.sh --test {{file}}
# Run tests with testcontainers (slower, no shared infra)
test-standalone:
BSPDS_ALLOW_INSECURE_SECRETS=1 cargo test
TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 cargo test
# Manually manage test infrastructure (for debugging)
test-infra-start:
./scripts/test-infra.sh start

View File

@@ -34,8 +34,8 @@ http {
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
upstream bspds {
server bspds:3000;
upstream tranquil-pds {
server tranquil-pds:3000;
keepalive 32;
}
server {
@@ -57,7 +57,7 @@ http {
ssl_certificate_key /etc/nginx/certs/live/${PDS_HOSTNAME}/privkey.pem;
client_max_body_size 100M;
location / {
proxy_pass http://bspds;
proxy_pass http://tranquil-pds;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
@@ -71,7 +71,7 @@ http {
proxy_request_buffering off;
}
location /xrpc/com.atproto.sync.subscribeRepos {
proxy_pass http://bspds;
proxy_pass http://tranquil-pds;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

View File

@@ -7,7 +7,7 @@ scrape_configs:
static_configs:
- targets: ['localhost:9090']
- job_name: 'bspds'
- job_name: 'tranquil-pds'
static_configs:
- targets: ['app:3000']
metrics_path: /metrics

View File

@@ -24,25 +24,25 @@ fi
nuke_installation() {
log_warn "NUKING EXISTING INSTALLATION"
log_info "Stopping services..."
systemctl stop bspds 2>/dev/null || true
systemctl disable bspds 2>/dev/null || true
systemctl stop tranquil-pds 2>/dev/null || true
systemctl disable tranquil-pds 2>/dev/null || true
log_info "Removing BSPDS files..."
rm -rf /opt/bspds
rm -rf /var/lib/bspds
rm -f /usr/local/bin/bspds
rm -f /usr/local/bin/bspds-sendmail
rm -f /usr/local/bin/bspds-mailq
rm -rf /var/spool/bspds-mail
rm -f /etc/systemd/system/bspds.service
log_info "Removing Tranquil PDS files..."
rm -rf /opt/tranquil-pds
rm -rf /var/lib/tranquil-pds
rm -f /usr/local/bin/tranquil-pds
rm -f /usr/local/bin/tranquil-pds-sendmail
rm -f /usr/local/bin/tranquil-pds-mailq
rm -rf /var/spool/tranquil-pds-mail
rm -f /etc/systemd/system/tranquil-pds.service
systemctl daemon-reload
log_info "Removing BSPDS configuration..."
rm -rf /etc/bspds
log_info "Removing Tranquil PDS configuration..."
rm -rf /etc/tranquil-pds
log_info "Dropping postgres database and user..."
sudo -u postgres psql -c "DROP DATABASE IF EXISTS pds;" 2>/dev/null || true
sudo -u postgres psql -c "DROP USER IF EXISTS bspds;" 2>/dev/null || true
sudo -u postgres psql -c "DROP USER IF EXISTS tranquil_pds;" 2>/dev/null || true
log_info "Removing minio bucket..."
if command -v mc &>/dev/null; then
@@ -54,14 +54,14 @@ nuke_installation() {
rm -f /etc/default/minio 2>/dev/null || true
log_info "Removing nginx config..."
rm -f /etc/nginx/sites-enabled/bspds
rm -f /etc/nginx/sites-available/bspds
rm -f /etc/nginx/sites-enabled/tranquil-pds
rm -f /etc/nginx/sites-available/tranquil-pds
systemctl reload nginx 2>/dev/null || true
log_success "Previous installation nuked"
}
if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/bspds ]]; then
if [[ -f /etc/tranquil-pds/tranquil-pds.env ]] || [[ -d /opt/tranquil-pds ]] || [[ -f /usr/local/bin/tranquil-pds ]]; then
log_warn "Existing installation detected"
echo ""
echo "Options:"
@@ -76,8 +76,8 @@ if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/
echo ""
log_warn "This will DELETE:"
echo " - PostgreSQL database 'pds' and all data"
echo " - All BSPDS configuration and credentials"
echo " - All source code in /opt/bspds"
echo " - All Tranquil PDS configuration and credentials"
echo " - All source code in /opt/tranquil-pds"
echo " - MinIO bucket 'pds-blobs' and all blobs"
echo ""
read -p "Type 'NUKE' to confirm: " CONFIRM_NUKE
@@ -102,7 +102,7 @@ if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/
fi
echo ""
log_info "BSPDS Installation Script for Debian"
log_info "Tranquil PDS Installation Script for Debian"
echo ""
get_public_ips() {
@@ -142,7 +142,7 @@ if [[ ! "$DNS_CONFIRMED" =~ ^[Yy]$ ]]; then
exit 0
fi
CREDENTIALS_FILE="/etc/bspds/.credentials"
CREDENTIALS_FILE="/etc/tranquil-pds/.credentials"
if [[ -f "$CREDENTIALS_FILE" ]]; then
log_info "Loading existing credentials..."
source "$CREDENTIALS_FILE"
@@ -154,7 +154,7 @@ else
DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
MINIO_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
mkdir -p /etc/bspds
mkdir -p /etc/tranquil-pds
cat > "$CREDENTIALS_FILE" << EOF
JWT_SECRET="$JWT_SECRET"
DPOP_SECRET="$DPOP_SECRET"
@@ -196,10 +196,10 @@ log_info "Installing postgres..."
apt install -y postgresql postgresql-contrib
systemctl enable postgresql
systemctl start postgresql
sudo -u postgres psql -c "CREATE USER bspds WITH PASSWORD '${DB_PASSWORD}';" 2>/dev/null || \
sudo -u postgres psql -c "ALTER USER bspds WITH PASSWORD '${DB_PASSWORD}';"
sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;" 2>/dev/null || true
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;"
sudo -u postgres psql -c "CREATE USER tranquil_pds WITH PASSWORD '${DB_PASSWORD}';" 2>/dev/null || \
sudo -u postgres psql -c "ALTER USER tranquil_pds WITH PASSWORD '${DB_PASSWORD}';"
sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;" 2>/dev/null || true
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;"
log_success "postgres configured"
log_info "Installing valkey..."
@@ -292,19 +292,19 @@ if ! command -v deno &>/dev/null && [[ ! -f "$HOME/.deno/bin/deno" ]]; then
grep -q 'deno/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
fi
log_info "Cloning BSPDS..."
if [[ ! -d /opt/bspds ]]; then
git clone https://tangled.org/lewis.moe/bspds-sandbox /opt/bspds
log_info "Cloning Tranquil PDS..."
if [[ ! -d /opt/tranquil-pds ]]; then
git clone https://tangled.org/lewis.moe/bspds-sandbox /opt/tranquil-pds
else
cd /opt/bspds && git pull
cd /opt/tranquil-pds && git pull
fi
cd /opt/bspds
cd /opt/tranquil-pds
log_info "Building frontend..."
"$HOME/.deno/bin/deno" task build --filter=frontend
log_success "Frontend built"
log_info "Building BSPDS (this takes a while)..."
log_info "Building Tranquil PDS (this takes a while)..."
source "$HOME/.cargo/env"
if [[ $TOTAL_MEM_KB -lt 4000000 ]]; then
log_info "Low memory - limiting parallel jobs"
@@ -312,39 +312,39 @@ if [[ $TOTAL_MEM_KB -lt 4000000 ]]; then
else
cargo build --release
fi
log_success "BSPDS built"
log_success "Tranquil PDS built"
log_info "Running migrations..."
cargo install sqlx-cli --no-default-features --features postgres
export DATABASE_URL="postgres://bspds:${DB_PASSWORD}@localhost:5432/pds"
export DATABASE_URL="postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds"
"$HOME/.cargo/bin/sqlx" migrate run
log_success "Migrations complete"
log_info "Setting up mail trap..."
mkdir -p /var/spool/bspds-mail
chmod 1777 /var/spool/bspds-mail
mkdir -p /var/spool/tranquil-pds-mail
chmod 1777 /var/spool/tranquil-pds-mail
cat > /usr/local/bin/bspds-sendmail << 'SENDMAIL_EOF'
cat > /usr/local/bin/tranquil-pds-sendmail << 'SENDMAIL_EOF'
#!/bin/bash
MAIL_DIR="/var/spool/bspds-mail"
MAIL_DIR="/var/spool/tranquil-pds-mail"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
RANDOM_ID=$(head -c 4 /dev/urandom | xxd -p)
MAIL_FILE="${MAIL_DIR}/${TIMESTAMP}-${RANDOM_ID}.eml"
mkdir -p "$MAIL_DIR"
{
echo "X-BSPDS-Received: $(date -Iseconds)"
echo "X-BSPDS-Args: $*"
echo "X-Tranquil-PDS-Received: $(date -Iseconds)"
echo "X-Tranquil-PDS-Args: $*"
echo ""
cat
} > "$MAIL_FILE"
chmod 644 "$MAIL_FILE"
exit 0
SENDMAIL_EOF
chmod +x /usr/local/bin/bspds-sendmail
chmod +x /usr/local/bin/tranquil-pds-sendmail
cat > /usr/local/bin/bspds-mailq << 'MAILQ_EOF'
cat > /usr/local/bin/tranquil-pds-mailq << 'MAILQ_EOF'
#!/bin/bash
MAIL_DIR="/var/spool/bspds-mail"
MAIL_DIR="/var/spool/tranquil-pds-mail"
case "${1:-list}" in
list)
ls -lt "$MAIL_DIR"/*.eml 2>/dev/null | head -20 || echo "No emails"
@@ -365,18 +365,18 @@ case "${1:-list}" in
[[ -f "$f" ]] && cat "$f" || echo "Not found"
;;
*)
[[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: bspds-mailq [list|latest|clear|count|N]"
[[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: tranquil-pds-mailq [list|latest|clear|count|N]"
;;
esac
MAILQ_EOF
chmod +x /usr/local/bin/bspds-mailq
chmod +x /usr/local/bin/tranquil-pds-mailq
log_info "Creating BSPDS configuration..."
cat > /etc/bspds/bspds.env << EOF
log_info "Creating Tranquil PDS configuration..."
cat > /etc/tranquil-pds/tranquil-pds.env << EOF
SERVER_HOST=127.0.0.1
SERVER_PORT=3000
PDS_HOSTNAME=${PDS_DOMAIN}
DATABASE_URL=postgres://bspds:${DB_PASSWORD}@localhost:5432/pds
DATABASE_URL=postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds
DATABASE_MAX_CONNECTIONS=100
DATABASE_MIN_CONNECTIONS=10
S3_ENDPOINT=http://localhost:9000
@@ -392,30 +392,30 @@ PLC_DIRECTORY_URL=https://plc.directory
CRAWLERS=https://bsky.network
AVAILABLE_USER_DOMAINS=${PDS_DOMAIN}
MAIL_FROM_ADDRESS=noreply@${PDS_DOMAIN}
MAIL_FROM_NAME=BSPDS
SENDMAIL_PATH=/usr/local/bin/bspds-sendmail
MAIL_FROM_NAME=Tranquil PDS
SENDMAIL_PATH=/usr/local/bin/tranquil-pds-sendmail
EOF
chmod 600 /etc/bspds/bspds.env
chmod 600 /etc/tranquil-pds/tranquil-pds.env
log_info "Installing BSPDS..."
id -u bspds &>/dev/null || useradd -r -s /sbin/nologin bspds
cp /opt/bspds/target/release/bspds /usr/local/bin/
mkdir -p /var/lib/bspds
cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend
chown -R bspds:bspds /var/lib/bspds
log_info "Installing Tranquil PDS..."
id -u tranquil-pds &>/dev/null || useradd -r -s /sbin/nologin tranquil-pds
cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/
mkdir -p /var/lib/tranquil-pds
cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend
chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds
cat > /etc/systemd/system/bspds.service << 'EOF'
cat > /etc/systemd/system/tranquil-pds.service << 'EOF'
[Unit]
Description=BSPDS - AT Protocol PDS
Description=Tranquil PDS - AT Protocol PDS
After=network.target postgresql.service minio.service
[Service]
Type=simple
User=bspds
Group=bspds
EnvironmentFile=/etc/bspds/bspds.env
Environment=FRONTEND_DIR=/var/lib/bspds/frontend
ExecStart=/usr/local/bin/bspds
User=tranquil-pds
Group=tranquil-pds
EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env
Environment=FRONTEND_DIR=/var/lib/tranquil-pds/frontend
ExecStart=/usr/local/bin/tranquil-pds
Restart=always
RestartSec=5
@@ -424,13 +424,13 @@ WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable bspds
systemctl start bspds
log_success "BSPDS service started"
systemctl enable tranquil-pds
systemctl start tranquil-pds
log_success "Tranquil PDS service started"
log_info "Installing nginx..."
apt install -y nginx
cat > /etc/nginx/sites-available/bspds << EOF
cat > /etc/nginx/sites-available/tranquil-pds << EOF
server {
listen 80;
listen [::]:80;
@@ -456,7 +456,7 @@ server {
}
EOF
ln -sf /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/
ln -sf /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
@@ -496,7 +496,7 @@ if [[ "$CERT_READY" =~ ^[Yy]$ ]]; then
-d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" \
--email "${CERTBOT_EMAIL}" --agree-tos; then
cat > /etc/nginx/sites-available/bspds << EOF
cat > /etc/nginx/sites-available/tranquil-pds << EOF
server {
listen 80;
listen [::]:80;
@@ -564,9 +564,9 @@ fi
log_info "Verifying installation..."
sleep 3
if curl -s "http://localhost:3000/xrpc/_health" | grep -q "version"; then
log_success "BSPDS is responding"
log_success "Tranquil PDS is responding"
else
log_warn "BSPDS may still be starting. Check: journalctl -u bspds -f"
log_warn "Tranquil PDS may still be starting. Check: journalctl -u tranquil-pds -f"
fi
echo ""
@@ -574,12 +574,12 @@ log_success "Installation complete"
echo ""
echo "PDS: https://${PDS_DOMAIN}"
echo ""
echo "Credentials (also in /etc/bspds/.credentials):"
echo "Credentials (also in /etc/tranquil-pds/.credentials):"
echo " DB password: ${DB_PASSWORD}"
echo " MinIO password: ${MINIO_PASSWORD}"
echo ""
echo "Commands:"
echo " journalctl -u bspds -f # logs"
echo " systemctl restart bspds # restart"
echo " bspds-mailq # view trapped emails"
echo " journalctl -u tranquil-pds -f # logs"
echo " systemctl restart tranquil-pds # restart"
echo " tranquil-pds-mailq # view trapped emails"
echo ""

View File

@@ -10,7 +10,7 @@ cleanup() {
}
trap cleanup EXIT
"$INFRA_SCRIPT" start
source "${TMPDIR:-/tmp}/bspds_test_infra.env"
source "${TMPDIR:-/tmp}/tranquil_pds_test_infra.env"
echo ""
echo "Running database migrations..."
sqlx database create 2>/dev/null || true

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
INFRA_FILE="${TMPDIR:-/tmp}/bspds_test_infra.env"
CONTAINER_PREFIX="bspds-test"
INFRA_FILE="${TMPDIR:-/tmp}/tranquil_pds_test_infra.env"
CONTAINER_PREFIX="tranquil-pds-test"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
@@ -40,7 +40,7 @@ start_infra() {
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=postgres \
-P \
--label bspds_test=true \
--label tranquil_pds_test=true \
postgres:18-alpine >/dev/null
echo "Starting MinIO..."
$CONTAINER_CMD run -d \
@@ -48,13 +48,13 @@ start_infra() {
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
-P \
--label bspds_test=true \
--label tranquil_pds_test=true \
minio/minio:latest server /data >/dev/null
echo "Starting Valkey..."
$CONTAINER_CMD run -d \
--name "${CONTAINER_PREFIX}-valkey" \
-P \
--label bspds_test=true \
--label tranquil_pds_test=true \
valkey/valkey:8-alpine >/dev/null
echo "Waiting for services to be ready..."
sleep 2
@@ -95,8 +95,8 @@ export AWS_ACCESS_KEY_ID="minioadmin"
export AWS_SECRET_ACCESS_KEY="minioadmin"
export AWS_REGION="us-east-1"
export VALKEY_URL="redis://127.0.0.1:${VALKEY_PORT}"
export BSPDS_TEST_INFRA_READY="1"
export BSPDS_ALLOW_INSECURE_SECRETS="1"
export TRANQUIL_PDS_TEST_INFRA_READY="1"
export TRANQUIL_PDS_ALLOW_INSECURE_SECRETS="1"
export SKIP_IMPORT_VERIFICATION="true"
export DISABLE_RATE_LIMITING="1"
EOF
@@ -125,7 +125,7 @@ status_infra() {
fi
echo ""
echo "Containers:"
$CONTAINER_CMD ps -a --filter "label=bspds_test=true" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo " (none)"
$CONTAINER_CMD ps -a --filter "label=tranquil_pds_test=true" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo " (none)"
}
case "${1:-}" in
start)

View File

@@ -157,9 +157,20 @@ pub async fn activate_account(
.await;
match result {
Ok(_) => {
if let Some(h) = handle {
if let Some(ref h) = handle {
let _ = state.cache.delete(&format!("handle:{}", h)).await;
}
if let Err(e) =
crate::api::repo::record::sequence_account_event(&state, &did, true, None).await
{
warn!("Failed to sequence account activation event: {}", e);
}
if let Err(e) =
crate::api::repo::record::sequence_identity_event(&state, &did, handle.as_deref())
.await
{
warn!("Failed to sequence identity event for activation: {}", e);
}
(StatusCode::OK, Json(json!({}))).into_response()
}
Err(e) => {
@@ -222,9 +233,14 @@ pub async fn deactivate_account(
.await;
match result {
Ok(_) => {
if let Some(h) = handle {
if let Some(ref h) = handle {
let _ = state.cache.delete(&format!("handle:{}", h)).await;
}
if let Err(e) =
crate::api::repo::record::sequence_account_event(&state, &did, false, Some("deactivated")).await
{
warn!("Failed to sequence account deactivation event: {}", e);
}
(StatusCode::OK, Json(json!({}))).into_response()
}
Err(e) => {

View File

@@ -10,6 +10,28 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
use tracing::error;
const HOUR_SECS: i64 = 3600;
const MINUTE_SECS: i64 = 60;
const PROTECTED_METHODS: &[&str] = &[
"com.atproto.admin.sendEmail",
"com.atproto.identity.requestPlcOperationSignature",
"com.atproto.identity.signPlcOperation",
"com.atproto.identity.updateHandle",
"com.atproto.server.activateAccount",
"com.atproto.server.confirmEmail",
"com.atproto.server.createAppPassword",
"com.atproto.server.deactivateAccount",
"com.atproto.server.getAccountInviteCodes",
"com.atproto.server.getSession",
"com.atproto.server.listAppPasswords",
"com.atproto.server.requestAccountDelete",
"com.atproto.server.requestEmailConfirmation",
"com.atproto.server.requestEmailUpdate",
"com.atproto.server.revokeAppPassword",
"com.atproto.server.updateEmail",
];
#[derive(Deserialize)]
pub struct GetServiceAuthParams {
pub aud: String,
@@ -33,7 +55,7 @@ pub async fn get_service_auth(
Some(t) => t,
None => return ApiError::AuthenticationRequired.into_response(),
};
let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await {
let auth_user = match crate::auth::validate_bearer_token_for_service_auth(&state.db, &token).await {
Ok(user) => user,
Err(e) => return ApiError::from(e).into_response(),
};
@@ -46,9 +68,86 @@ pub async fn get_service_auth(
.into_response();
}
};
let lxm = params.lxm.as_deref().unwrap_or("*");
let lxm = params.lxm.as_deref();
let lxm_for_token = lxm.unwrap_or("*");
let user_status = sqlx::query!(
"SELECT takedown_ref FROM users WHERE did = $1",
auth_user.did
)
.fetch_optional(&state.db)
.await;
let is_takendown = match user_status {
Ok(Some(row)) => row.takedown_ref.is_some(),
_ => false,
};
if is_takendown && lxm != Some("com.atproto.server.createAccount") {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "InvalidToken",
"message": "Bad token scope"
})),
)
.into_response();
}
if let Some(method) = lxm {
if PROTECTED_METHODS.contains(&method) {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "InvalidRequest",
"message": format!("cannot request a service auth token for the following protected method: {}", method)
})),
)
.into_response();
}
}
if let Some(exp) = params.exp {
let now = chrono::Utc::now().timestamp();
let diff = exp - now;
if diff < 0 {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "BadExpiration",
"message": "expiration is in past"
})),
)
.into_response();
}
if diff > HOUR_SECS {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "BadExpiration",
"message": "cannot request a token with an expiration more than an hour in the future"
})),
)
.into_response();
}
if lxm.is_none() && diff > MINUTE_SECS {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "BadExpiration",
"message": "cannot request a method-less token with an expiration more than a minute in the future"
})),
)
.into_response();
}
}
let service_token =
match crate::auth::create_service_token(&auth_user.did, &params.aud, lxm, &key_bytes) {
match crate::auth::create_service_token(&auth_user.did, &params.aud, lxm_for_token, &key_bytes) {
Ok(t) => t,
Err(e) => {
error!("Failed to create service token: {:?}", e);

View File

@@ -59,14 +59,14 @@ pub async fn validate_bearer_token(
db: &PgPool,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, None, token, false).await
validate_bearer_token_with_options_internal(db, None, token, false, false).await
}
pub async fn validate_bearer_token_allow_deactivated(
db: &PgPool,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, None, token, true).await
validate_bearer_token_with_options_internal(db, None, token, true, false).await
}
pub async fn validate_bearer_token_cached(
@@ -74,7 +74,7 @@ pub async fn validate_bearer_token_cached(
cache: &Arc<dyn Cache>,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, Some(cache), token, false).await
validate_bearer_token_with_options_internal(db, Some(cache), token, false, false).await
}
pub async fn validate_bearer_token_cached_allow_deactivated(
@@ -82,7 +82,14 @@ pub async fn validate_bearer_token_cached_allow_deactivated(
cache: &Arc<dyn Cache>,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, Some(cache), token, true).await
validate_bearer_token_with_options_internal(db, Some(cache), token, true, false).await
}
pub async fn validate_bearer_token_for_service_auth(
db: &PgPool,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, None, token, true, true).await
}
async fn validate_bearer_token_with_options_internal(
@@ -90,6 +97,7 @@ async fn validate_bearer_token_with_options_internal(
cache: Option<&Arc<dyn Cache>>,
token: &str,
allow_deactivated: bool,
allow_takendown: bool,
) -> Result<AuthenticatedUser, TokenValidationError> {
let did_from_token = get_did_from_token(token).ok();
@@ -155,7 +163,7 @@ async fn validate_bearer_token_with_options_internal(
return Err(TokenValidationError::AccountDeactivated);
}
if takedown_ref.is_some() {
if !allow_takendown && takedown_ref.is_some() {
return Err(TokenValidationError::AccountTakedown);
}

View File

@@ -87,7 +87,7 @@ impl EmailSender {
pub fn from_env() -> Option<Self> {
let from_address = std::env::var("MAIL_FROM_ADDRESS").ok()?;
let from_name = std::env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "BSPDS".to_string());
let from_name = std::env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "Tranquil PDS".to_string());
Some(Self::new(from_address, from_name))
}
@@ -168,7 +168,7 @@ impl CommsSender for DiscordSender {
let content = format!("**{}**\n\n{}", subject, notification.body);
let payload = json!({
"content": content,
"username": "BSPDS"
"username": "Tranquil PDS"
});
let mut last_error = None;
for attempt in 0..MAX_RETRIES {

View File

@@ -25,32 +25,32 @@ impl AuthConfig {
pub fn init() -> &'static Self {
CONFIG.get_or_init(|| {
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| {
if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() {
"test-jwt-secret-not-for-production".to_string()
} else {
panic!(
"JWT_SECRET environment variable must be set in production. \
Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
);
}
});
let dpop_secret = std::env::var("DPOP_SECRET").unwrap_or_else(|_| {
if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() {
"test-dpop-secret-not-for-production".to_string()
} else {
panic!(
"DPOP_SECRET environment variable must be set in production. \
Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
);
}
});
if jwt_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
if jwt_secret.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() {
panic!("JWT_SECRET must be at least 32 characters");
}
if dpop_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
if dpop_secret.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() {
panic!("DPOP_SECRET must be at least 32 characters");
}
@@ -87,23 +87,23 @@ impl AuthConfig {
let signing_key_id = URL_SAFE_NO_PAD.encode(&kid_hash[..8]);
let master_key = std::env::var("MASTER_KEY").unwrap_or_else(|_| {
if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() {
"test-master-key-not-for-production".to_string()
} else {
panic!(
"MASTER_KEY environment variable must be set in production. \
Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
);
}
});
if master_key.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
if master_key.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() {
panic!("MASTER_KEY must be at least 32 characters");
}
let hk = Hkdf::<Sha256>::new(None, master_key.as_bytes());
let mut key_encryption_key = [0u8; 32];
hk.expand(b"bspds-user-key-encryption", &mut key_encryption_key)
hk.expand(b"tranquil-pds-user-key-encryption", &mut key_encryption_key)
.expect("HKDF expansion failed");
AuthConfig {

View File

@@ -52,11 +52,11 @@ pub fn app(state: AppState) -> Router {
get(api::server::get_session),
)
.route(
"/xrpc/com.bspds.account.listSessions",
"/xrpc/com.tranquil.account.listSessions",
get(api::server::list_sessions),
)
.route(
"/xrpc/com.bspds.account.revokeSession",
"/xrpc/com.tranquil.account.revokeSession",
post(api::server::revoke_session),
)
.route(
@@ -199,7 +199,7 @@ pub fn app(state: AppState) -> Router {
post(api::server::reset_password),
)
.route(
"/xrpc/com.bspds.account.changePassword",
"/xrpc/com.tranquil.account.changePassword",
post(api::server::change_password),
)
.route(
@@ -283,7 +283,7 @@ pub fn app(state: AppState) -> Router {
get(api::admin::get_invite_codes),
)
.route(
"/xrpc/com.bspds.admin.getServerStats",
"/xrpc/com.tranquil.admin.getServerStats",
get(api::admin::get_server_stats),
)
.route(
@@ -370,19 +370,19 @@ pub fn app(state: AppState) -> Router {
get(api::temp::check_signup_queue),
)
.route(
"/xrpc/com.bspds.account.getNotificationPrefs",
"/xrpc/com.tranquil.account.getNotificationPrefs",
get(api::notification_prefs::get_notification_prefs),
)
.route(
"/xrpc/com.bspds.account.updateNotificationPrefs",
"/xrpc/com.tranquil.account.updateNotificationPrefs",
post(api::notification_prefs::update_notification_prefs),
)
.route(
"/xrpc/com.bspds.account.getNotificationHistory",
"/xrpc/com.tranquil.account.getNotificationHistory",
get(api::notification_prefs::get_notification_history),
)
.route(
"/xrpc/com.bspds.account.confirmChannelVerification",
"/xrpc/com.tranquil.account.confirmChannelVerification",
post(api::verification::confirm_channel_verification),
)
.route("/xrpc/{*method}", any(api::proxy::proxy_handler))

View File

@@ -1,6 +1,6 @@
use bspds::comms::{CommsService, DiscordSender, EmailSender, SignalSender, TelegramSender};
use bspds::crawlers::{Crawlers, start_crawlers_service};
use bspds::state::AppState;
use tranquil_pds::comms::{CommsService, DiscordSender, EmailSender, SignalSender, TelegramSender};
use tranquil_pds::crawlers::{Crawlers, start_crawlers_service};
use tranquil_pds::state::AppState;
use std::net::SocketAddr;
use std::process::ExitCode;
use std::sync::Arc;
@@ -11,7 +11,7 @@ use tracing::{error, info, warn};
async fn main() -> ExitCode {
dotenvy::dotenv().ok();
tracing_subscriber::fmt::init();
bspds::metrics::init_metrics();
tranquil_pds::metrics::init_metrics();
match run().await {
Ok(()) => ExitCode::SUCCESS,
@@ -62,7 +62,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
.map_err(|e| format!("Failed to run migrations: {}", e))?;
let state = AppState::new(pool.clone()).await;
bspds::sync::listener::start_sequencer_listener(state.clone()).await;
tranquil_pds::sync::listener::start_sequencer_listener(state.clone()).await;
let (shutdown_tx, shutdown_rx) = watch::channel(false);
@@ -108,7 +108,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
None
};
let app = bspds::app(state);
let app = tranquil_pds::app(state);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
info!("listening on {}", addr);

View File

@@ -24,46 +24,46 @@ pub fn init_metrics() -> PrometheusHandle {
}
fn describe_metrics() {
metrics::describe_counter!("bspds_http_requests_total", "Total number of HTTP requests");
metrics::describe_counter!("tranquil_pds_http_requests_total", "Total number of HTTP requests");
metrics::describe_histogram!(
"bspds_http_request_duration_seconds",
"tranquil_pds_http_request_duration_seconds",
"HTTP request duration in seconds"
);
metrics::describe_counter!(
"bspds_auth_cache_hits_total",
"tranquil_pds_auth_cache_hits_total",
"Total number of authentication cache hits"
);
metrics::describe_counter!(
"bspds_auth_cache_misses_total",
"tranquil_pds_auth_cache_misses_total",
"Total number of authentication cache misses"
);
metrics::describe_gauge!(
"bspds_firehose_subscribers",
"tranquil_pds_firehose_subscribers",
"Number of active firehose WebSocket subscribers"
);
metrics::describe_counter!(
"bspds_firehose_events_total",
"tranquil_pds_firehose_events_total",
"Total number of firehose events published"
);
metrics::describe_counter!(
"bspds_block_operations_total",
"tranquil_pds_block_operations_total",
"Total number of block store operations"
);
metrics::describe_counter!(
"bspds_s3_operations_total",
"tranquil_pds_s3_operations_total",
"Total number of S3/blob storage operations"
);
metrics::describe_gauge!(
"bspds_comms_queue_size",
"tranquil_pds_comms_queue_size",
"Current size of the comms queue"
);
metrics::describe_counter!(
"bspds_rate_limit_rejections_total",
"tranquil_pds_rate_limit_rejections_total",
"Total number of rate limit rejections"
);
metrics::describe_counter!("bspds_db_queries_total", "Total number of database queries");
metrics::describe_counter!("tranquil_pds_db_queries_total", "Total number of database queries");
metrics::describe_histogram!(
"bspds_db_query_duration_seconds",
"tranquil_pds_db_query_duration_seconds",
"Database query duration in seconds"
);
}
@@ -97,7 +97,7 @@ pub async fn metrics_middleware(request: Request<Body>, next: Next) -> Response
let status = response.status().as_u16().to_string();
counter!(
"bspds_http_requests_total",
"tranquil_pds_http_requests_total",
"method" => method.clone(),
"path" => path.clone(),
"status" => status.clone()
@@ -105,7 +105,7 @@ pub async fn metrics_middleware(request: Request<Body>, next: Next) -> Response
.increment(1);
histogram!(
"bspds_http_request_duration_seconds",
"tranquil_pds_http_request_duration_seconds",
"method" => method,
"path" => path
)
@@ -135,32 +135,32 @@ fn normalize_path(path: &str) -> String {
}
pub fn record_auth_cache_hit(cache_type: &str) {
counter!("bspds_auth_cache_hits_total", "cache_type" => cache_type.to_string()).increment(1);
counter!("tranquil_pds_auth_cache_hits_total", "cache_type" => cache_type.to_string()).increment(1);
}
pub fn record_auth_cache_miss(cache_type: &str) {
counter!("bspds_auth_cache_misses_total", "cache_type" => cache_type.to_string()).increment(1);
counter!("tranquil_pds_auth_cache_misses_total", "cache_type" => cache_type.to_string()).increment(1);
}
pub fn set_firehose_subscribers(count: usize) {
gauge!("bspds_firehose_subscribers").set(count as f64);
gauge!("tranquil_pds_firehose_subscribers").set(count as f64);
}
pub fn increment_firehose_subscribers() {
counter!("bspds_firehose_events_total").increment(1);
counter!("tranquil_pds_firehose_events_total").increment(1);
}
pub fn record_firehose_event() {
counter!("bspds_firehose_events_total").increment(1);
counter!("tranquil_pds_firehose_events_total").increment(1);
}
pub fn record_block_operation(op_type: &str) {
counter!("bspds_block_operations_total", "op_type" => op_type.to_string()).increment(1);
counter!("tranquil_pds_block_operations_total", "op_type" => op_type.to_string()).increment(1);
}
pub fn record_s3_operation(op_type: &str, status: &str) {
counter!(
"bspds_s3_operations_total",
"tranquil_pds_s3_operations_total",
"op_type" => op_type.to_string(),
"status" => status.to_string()
)
@@ -168,17 +168,17 @@ pub fn record_s3_operation(op_type: &str, status: &str) {
}
pub fn set_comms_queue_size(size: usize) {
gauge!("bspds_comms_queue_size").set(size as f64);
gauge!("tranquil_pds_comms_queue_size").set(size as f64);
}
pub fn record_rate_limit_rejection(limiter: &str) {
counter!("bspds_rate_limit_rejections_total", "limiter" => limiter.to_string()).increment(1);
counter!("tranquil_pds_rate_limit_rejections_total", "limiter" => limiter.to_string()).increment(1);
}
pub fn record_db_query(query_type: &str, duration_seconds: f64) {
counter!("bspds_db_queries_total", "query_type" => query_type.to_string()).increment(1);
counter!("tranquil_pds_db_queries_total", "query_type" => query_type.to_string()).increment(1);
histogram!(
"bspds_db_query_duration_seconds",
"tranquil_pds_db_query_duration_seconds",
"query_type" => query_type.to_string()
)
.record(duration_seconds);

View File

@@ -1,6 +1,6 @@
mod common;
use common::{base_url, client, create_account_and_login, get_db_connection_string};
use bspds::comms::{NewComms, CommsType, enqueue_comms};
use tranquil_pds::comms::{NewComms, CommsType, enqueue_comms};
use serde_json::{Value, json};
use sqlx::PgPool;
@@ -37,7 +37,7 @@ async fn test_get_notification_history() {
}
let resp = client
.get(format!("{}/xrpc/com.bspds.account.getNotificationHistory", base))
.get(format!("{}/xrpc/com.tranquil.account.getNotificationHistory", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
@@ -63,7 +63,7 @@ async fn test_verify_channel_discord() {
"discordId": "123456789"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base))
.post(format!("{}/xrpc/com.tranquil.account.updateNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
@@ -92,7 +92,7 @@ async fn test_verify_channel_discord() {
"code": code
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
@@ -101,7 +101,7 @@ async fn test_verify_channel_discord() {
assert_eq!(resp.status(), 200);
let resp = client
.get(format!("{}/xrpc/com.bspds.account.getNotificationPrefs", base))
.get(format!("{}/xrpc/com.tranquil.account.getNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
@@ -121,7 +121,7 @@ async fn test_verify_channel_invalid_code() {
"telegramUsername": "testuser"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base))
.post(format!("{}/xrpc/com.tranquil.account.updateNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
@@ -134,7 +134,7 @@ async fn test_verify_channel_invalid_code() {
"code": "000000"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
@@ -154,7 +154,7 @@ async fn test_verify_channel_not_set() {
"code": "123456"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
@@ -175,7 +175,7 @@ async fn test_update_email_via_notification_prefs() {
"email": unique_email
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base))
.post(format!("{}/xrpc/com.tranquil.account.updateNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
@@ -203,7 +203,7 @@ async fn test_update_email_via_notification_prefs() {
"code": code
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.post(format!("{}/xrpc/com.tranquil.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
@@ -212,7 +212,7 @@ async fn test_update_email_via_notification_prefs() {
assert_eq!(resp.status(), 200);
let resp = client
.get(format!("{}/xrpc/com.bspds.account.getNotificationPrefs", base))
.get(format!("{}/xrpc/com.tranquil.account.getNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await

View File

@@ -11,7 +11,7 @@ async fn test_get_server_stats() {
let (_, _) = create_admin_account_and_login(&client).await;
let resp = client
.get(format!("{}/xrpc/com.bspds.admin.getServerStats", base))
.get(format!("{}/xrpc/com.tranquil.admin.getServerStats", base))
.header("Authorization", format!("Bearer {}", token1))
.send()
.await
@@ -33,7 +33,7 @@ async fn test_get_server_stats_no_auth() {
let client = client();
let base = base_url().await;
let resp = client
.get(format!("{}/xrpc/com.bspds.admin.getServerStats", base))
.get(format!("{}/xrpc/com.tranquil.admin.getServerStats", base))
.send()
.await
.unwrap();

View File

@@ -33,7 +33,7 @@ async fn test_change_password_success() {
let jwt = verify_new_account(&client, did).await;
let change_res = client
.post(format!(
"{}/xrpc/com.bspds.account.changePassword",
"{}/xrpc/com.tranquil.account.changePassword",
base_url().await
))
.bearer_auth(&jwt)
@@ -79,7 +79,7 @@ async fn test_change_password_wrong_current() {
let (_, jwt) = setup_new_user("change-pw-wrong").await;
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.changePassword",
"{}/xrpc/com.tranquil.account.changePassword",
base_url().await
))
.bearer_auth(&jwt)
@@ -122,7 +122,7 @@ async fn test_change_password_too_short() {
let jwt = verify_new_account(&client, did).await;
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.changePassword",
"{}/xrpc/com.tranquil.account.changePassword",
base_url().await
))
.bearer_auth(&jwt)
@@ -144,7 +144,7 @@ async fn test_change_password_empty_current() {
let (_, jwt) = setup_new_user("change-pw-empty").await;
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.changePassword",
"{}/xrpc/com.tranquil.account.changePassword",
base_url().await
))
.bearer_auth(&jwt)
@@ -164,7 +164,7 @@ async fn test_change_password_empty_new() {
let (_, jwt) = setup_new_user("change-pw-emptynew").await;
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.changePassword",
"{}/xrpc/com.tranquil.account.changePassword",
base_url().await
))
.bearer_auth(&jwt)
@@ -183,7 +183,7 @@ async fn test_change_password_requires_auth() {
let client = client();
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.changePassword",
"{}/xrpc/com.tranquil.account.changePassword",
base_url().await
))
.json(&json!({

View File

@@ -1,7 +1,7 @@
use aws_config::BehaviorVersion;
use aws_sdk_s3::Client as S3Client;
use aws_sdk_s3::config::Credentials;
use bspds::state::AppState;
use tranquil_pds::state::AppState;
use chrono::Utc;
use reqwest::{Client, StatusCode, header};
use serde_json::{Value, json};
@@ -40,7 +40,7 @@ pub const AUTH_DID: &str = "did:plc:fake";
pub const TARGET_DID: &str = "did:plc:target";
fn has_external_infra() -> bool {
std::env::var("BSPDS_TEST_INFRA_READY").is_ok()
std::env::var("TRANQUIL_PDS_TEST_INFRA_READY").is_ok()
|| (std::env::var("DATABASE_URL").is_ok() && std::env::var("S3_ENDPOINT").is_ok())
}
#[cfg(test)]
@@ -51,7 +51,7 @@ fn cleanup() {
}
if std::env::var("XDG_RUNTIME_DIR").is_ok() {
let _ = std::process::Command::new("podman")
.args(&["rm", "-f", "--filter", "label=bspds_test=true"])
.args(&["rm", "-f", "--filter", "label=tranquil_pds_test=true"])
.output();
}
let _ = std::process::Command::new("docker")
@@ -60,7 +60,7 @@ fn cleanup() {
"prune",
"-f",
"--filter",
"label=bspds_test=true",
"label=tranquil_pds_test=true",
])
.output();
}
@@ -80,7 +80,7 @@ pub async fn base_url() -> &'static str {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
unsafe {
std::env::set_var("BSPDS_ALLOW_INSECURE_SECRETS", "1");
std::env::set_var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS", "1");
}
if std::env::var("DOCKER_HOST").is_err() {
if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") {
@@ -152,7 +152,7 @@ async fn setup_with_testcontainers() -> String {
.with_env_var("MINIO_ROOT_USER", "minioadmin")
.with_env_var("MINIO_ROOT_PASSWORD", "minioadmin")
.with_cmd(vec!["server".to_string(), "/data".to_string()])
.with_label("bspds_test", "true")
.with_label("tranquil_pds_test", "true")
.start()
.await
.expect("Failed to start MinIO");
@@ -195,7 +195,7 @@ async fn setup_with_testcontainers() -> String {
S3_CONTAINER.set(s3_container).ok();
let container = Postgres::default()
.with_tag("18-alpine")
.with_label("bspds_test", "true")
.with_label("tranquil_pds_test", "true")
.start()
.await
.expect("Failed to start Postgres");
@@ -236,7 +236,7 @@ async fn setup_mock_appview(_mock_server: &MockServer) {
}
async fn spawn_app(database_url: String) -> String {
use bspds::rate_limit::RateLimiters;
use tranquil_pds::rate_limit::RateLimiters;
let pool = PgPoolOptions::new()
.max_connections(50)
.connect(&database_url)
@@ -260,8 +260,8 @@ async fn spawn_app(database_url: String) -> String {
.with_oauth_authorize_limit(10000)
.with_oauth_token_limit(10000);
let state = AppState::new(pool).await.with_rate_limiters(rate_limiters);
bspds::sync::listener::start_sequencer_listener(state.clone()).await;
let app = bspds::app(state);
tranquil_pds::sync::listener::start_sequencer_listener(state.clone()).await;
let app = tranquil_pds::app(state);
tokio::spawn(async move {
axum::serve(listener, app).await.unwrap();
});

View File

@@ -1,4 +1,4 @@
use bspds::image::{
use tranquil_pds::image::{
DEFAULT_MAX_FILE_SIZE, ImageError, ImageProcessor, OutputFormat, THUMB_SIZE_FEED,
THUMB_SIZE_FULL,
};

View File

@@ -194,7 +194,7 @@ async fn get_user_signing_key(did: &str) -> Option<Vec<u8>> {
.fetch_optional(&pool)
.await
.ok()??;
bspds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok()
tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok()
}
#[tokio::test]
#[ignore = "requires exclusive env var access; run with: cargo test test_import_with_valid_signature_and_mock_plc -- --ignored --test-threads=1"]

View File

@@ -1,7 +1,7 @@
#![allow(unused_imports)]
mod common;
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use bspds::auth::{
use tranquil_pds::auth::{
self, SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED, SCOPE_REFRESH,
TOKEN_TYPE_ACCESS, TOKEN_TYPE_REFRESH, TOKEN_TYPE_SERVICE, create_access_token,
create_refresh_token, create_service_token, get_did_from_token, get_jti_from_token,
@@ -409,7 +409,7 @@ async fn test_session_lifecycle_security() {
}
#[tokio::test]
async fn test_deactivated_account_rejected() {
async fn test_deactivated_account_behavior() {
let url = base_url().await;
let http_client = client();
let (access_jwt, _did) = create_account_and_login(&http_client).await;
@@ -423,9 +423,25 @@ async fn test_deactivated_account_rejected() {
let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url))
.header("Authorization", format!("Bearer {}", access_jwt))
.send().await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.unwrap();
assert_eq!(body["error"], "AccountDeactivated");
assert_eq!(body["active"], false);
let post_res = http_client.post(format!("{}/xrpc/com.atproto.repo.createRecord", url))
.header("Authorization", format!("Bearer {}", access_jwt))
.json(&json!({
"repo": _did,
"collection": "app.bsky.feed.post",
"record": {
"$type": "app.bsky.feed.post",
"text": "test",
"createdAt": "2024-01-01T00:00:00Z"
}
}))
.send().await.unwrap();
assert_eq!(post_res.status(), StatusCode::UNAUTHORIZED);
let post_body: Value = post_res.json().await.unwrap();
assert_eq!(post_body["error"], "AccountDeactivated");
}
#[tokio::test]

View File

@@ -1,5 +1,5 @@
mod common;
use bspds::comms::{
use tranquil_pds::comms::{
CommsChannel, CommsStatus, CommsType, NewComms, enqueue_comms, enqueue_welcome,
};
use sqlx::PgPool;

View File

@@ -2,7 +2,7 @@
mod common;
mod helpers;
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use bspds::oauth::dpop::{DPoPJwk, DPoPVerifier, compute_jwk_thumbprint};
use tranquil_pds::oauth::dpop::{DPoPJwk, DPoPVerifier, compute_jwk_thumbprint};
use chrono::Utc;
use common::{base_url, client};
use helpers::verify_new_account;

View File

@@ -50,7 +50,7 @@ async fn get_user_signing_key(did: &str) -> Option<Vec<u8>> {
.fetch_optional(&pool)
.await
.ok()??;
bspds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok()
tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version).ok()
}
async fn get_plc_token_from_db(did: &str) -> Option<String> {

View File

@@ -1,4 +1,4 @@
use bspds::plc::{
use tranquil_pds::plc::{
PlcError, PlcOperation, PlcService, PlcValidationContext, cid_for_cbor, sign_operation,
signing_key_to_did_key, validate_plc_operation, validate_plc_operation_for_submission,
verify_operation_signature,

View File

@@ -162,7 +162,7 @@ async fn test_distributed_rate_limiter_directly() {
println!("VALKEY_URL not set, skipping distributed rate limiter test");
return;
}
use bspds::cache::{DistributedRateLimiter, RedisRateLimiter};
use tranquil_pds::cache::{DistributedRateLimiter, RedisRateLimiter};
let valkey_url = std::env::var("VALKEY_URL").unwrap();
let client = redis::Client::open(valkey_url.as_str()).expect("Failed to create Redis client");
let conn = client

View File

@@ -1,4 +1,4 @@
use bspds::validation::{
use tranquil_pds::validation::{
RecordValidator, ValidationError, ValidationStatus, validate_collection_nsid,
validate_record_key,
};

View File

@@ -1,7 +1,7 @@
mod common;
use bspds::image::{ImageError, ImageProcessor};
use bspds::comms::{SendError, is_valid_phone_number, sanitize_header_value};
use bspds::oauth::templates::{error_page, login_page, success_page};
use tranquil_pds::image::{ImageError, ImageProcessor};
use tranquil_pds::comms::{SendError, is_valid_phone_number, sanitize_header_value};
use tranquil_pds::oauth::templates::{error_page, login_page, success_page};
#[test]
fn test_header_injection_sanitization() {

View File

@@ -11,7 +11,7 @@ async fn test_list_sessions_returns_current_session() {
let (did, jwt) = setup_new_user("list-sessions").await;
let res = client
.get(format!(
"{}/xrpc/com.bspds.account.listSessions",
"{}/xrpc/com.tranquil.account.listSessions",
base_url().await
))
.bearer_auth(&jwt)
@@ -74,7 +74,7 @@ async fn test_list_sessions_multiple_sessions() {
let jwt2 = login_body["accessJwt"].as_str().unwrap();
let list_res = client
.get(format!(
"{}/xrpc/com.bspds.account.listSessions",
"{}/xrpc/com.tranquil.account.listSessions",
base_url().await
))
.bearer_auth(jwt2)
@@ -93,7 +93,7 @@ async fn test_list_sessions_requires_auth() {
let client = client();
let res = client
.get(format!(
"{}/xrpc/com.bspds.account.listSessions",
"{}/xrpc/com.tranquil.account.listSessions",
base_url().await
))
.send()
@@ -145,7 +145,7 @@ async fn test_revoke_session_success() {
let jwt2 = login_body["accessJwt"].as_str().unwrap();
let list_res = client
.get(format!(
"{}/xrpc/com.bspds.account.listSessions",
"{}/xrpc/com.tranquil.account.listSessions",
base_url().await
))
.bearer_auth(jwt2)
@@ -159,7 +159,7 @@ async fn test_revoke_session_success() {
let session_id = other_session.unwrap()["id"].as_str().unwrap();
let revoke_res = client
.post(format!(
"{}/xrpc/com.bspds.account.revokeSession",
"{}/xrpc/com.tranquil.account.revokeSession",
base_url().await
))
.bearer_auth(jwt2)
@@ -170,7 +170,7 @@ async fn test_revoke_session_success() {
assert_eq!(revoke_res.status(), StatusCode::OK);
let list_after_res = client
.get(format!(
"{}/xrpc/com.bspds.account.listSessions",
"{}/xrpc/com.tranquil.account.listSessions",
base_url().await
))
.bearer_auth(jwt2)
@@ -190,7 +190,7 @@ async fn test_revoke_session_invalid_id() {
let (_, jwt) = setup_new_user("revoke-invalid").await;
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.revokeSession",
"{}/xrpc/com.tranquil.account.revokeSession",
base_url().await
))
.bearer_auth(&jwt)
@@ -207,7 +207,7 @@ async fn test_revoke_session_not_found() {
let (_, jwt) = setup_new_user("revoke-notfound").await;
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.revokeSession",
"{}/xrpc/com.tranquil.account.revokeSession",
base_url().await
))
.bearer_auth(&jwt)
@@ -223,7 +223,7 @@ async fn test_revoke_session_requires_auth() {
let client = client();
let res = client
.post(format!(
"{}/xrpc/com.bspds.account.revokeSession",
"{}/xrpc/com.tranquil.account.revokeSession",
base_url().await
))
.json(&json!({"sessionId": "1"}))