personal activity index (bluesky, leaflet, substack) pai.desertthunder.dev
rss bluesky
at main 421 lines 11 kB view raw view rendered
1# Personal Activity Index – Deployment Guide 2 3This guide walks through two common reverse proxy setups for `pai serve`: **nginx** and **Caddy**. 4Both sections include native (host binary) instructions and optional Docker paths if you prefer containerized deployments. 5 6## Table of Contents 7 8- [Prerequisites](#prerequisites) 9- [nginx Deployment](#nginx-deployment) 10 - [Host Setup](#host-setup) 11 - [nginx Config](#nginx-config) 12 - [Optional: nginx via Docker](#optional-nginx-via-docker) 13- [Caddy Deployment](#caddy-deployment) 14 - [Host Setup](#host-setup-1) 15 - [Caddyfile Example](#caddyfile-example) 16 - [Optional: Caddy + Docker Compose](#optional-caddy--docker-compose) 17- [Health Checks & Monitoring](#health-checks--monitoring) 18- [Cloudflare Worker Deployment](#cloudflare-worker-deployment) 19 - [Prerequisites](#prerequisites-1) 20 - [Quick Start](#quick-start) 21 - [Cron Triggers](#cron-triggers) 22 - [API Endpoints](#api-endpoints) 23 - [Local Development](#local-development) 24 - [Monitoring](#monitoring) 25 26## Prerequisites 27 281. Build binary: 29 30 ```sh 31 cargo build --release -p pai 32 ``` 33 34 The binary will live at `target/release/pai`. 35 362. Prepare a configuration + database location. The default locations follow the XDG spec, but you can override them with `-C` (config dir) and `-d` (database path). 373. Run a sync at least once so the database has data: 38 39 ```sh 40 ./target/release/pai sync -C /etc/pai -d /var/lib/pai/pai.db -a 41 ``` 42 434. Start the server (example binds to localhost so the proxy terminates TLS): 44 45 ```sh 46 ./target/release/pai serve -d /var/lib/pai/pai.db -a 127.0.0.1:8080 47 ``` 48 49### CORS Configuration for Self-Hosted Server 50 51The HTTP server supports CORS configuration via `config.toml`. Add a `[cors]` section: 52 53```toml 54[cors] 55# List of allowed origins for cross-origin requests 56allowed_origins = ["https://desertthunder.dev", "http://localhost:4321"] 57 58# Optional development key for local testing 59dev_key = "your-secret-dev-key" 60``` 61 62CORS features: 63 64- **Exact matching**: `http://localhost:4321` only allows that specific origin 65- **Same-root-domain**: `https://desertthunder.dev` also allows `https://pai.desertthunder.dev`, `https://api.desertthunder.dev`, etc. 66- **Dev key**: Requests with `X-Local-Dev-Key` header matching the configured key bypass origin checks 67 68The PAI server handles CORS automatically - no additional proxy configuration needed. See [README.md](./README.md#cors-configuration) for details. 69 70## nginx Deployment 71 72### Host Setup 73 741. Install nginx via your package manager (`apt`, `dnf`, `brew`, etc.). 752. Create a systemd service for `pai` (optional but recommended): 76 77 ```ini 78 [Unit] 79 Description=Personal Activity Index 80 After=network.target 81 82 [Service] 83 ExecStart=/usr/local/bin/pai serve -d /var/lib/pai/pai.db -a 127.0.0.1:8080 84 Restart=on-failure 85 User=pai 86 Group=pai 87 WorkingDirectory=/var/lib/pai 88 89 [Install] 90 WantedBy=multi-user.target 91 ``` 92 933. Enable and start it: 94 95 ```sh 96 sudo systemctl daemon-reload 97 sudo systemctl enable --now pai.service 98 ``` 99 100### nginx Config 101 102Create `/etc/nginx/conf.d/pai.conf`: 103 104```nginx 105server { 106 listen 80; 107 server_name pai.example.com; 108 109 location / { 110 proxy_pass http://127.0.0.1:8080; 111 proxy_set_header Host $host; 112 proxy_set_header X-Real-IP $remote_addr; 113 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 114 proxy_set_header X-Forwarded-Proto $scheme; 115 } 116} 117``` 118 119Reload nginx: `sudo nginx -s reload`. 120 121### Optional: nginx via Docker 122 123Use an `nginx` image + bind-mount config: 124 125```yaml 126services: 127 pai: 128 image: ghcr.io/your-namespace/pai:latest 129 command: ["serve", "-d", "/data/pai.db", "-a", "0.0.0.0:8080"] 130 volumes: 131 - ./data:/data 132 expose: 133 - "8080" 134 135 nginx: 136 image: nginx:1.27 137 volumes: 138 - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro 139 ports: 140 - "80:80" 141 depends_on: 142 - pai 143``` 144 145`nginx.conf` should proxy to `http://pai:8080` instead of localhost. 146 147## Caddy Deployment 148 149### Host Setup 150 1511. Install Caddy (<https://caddyserver.com/docs/install>). 1522. Keep the same `pai` systemd service from above (or run manually). 153 154### Caddyfile Example 155 156Create `/etc/caddy/Caddyfile`: 157 158```caddyfile 159pai.example.com { 160 reverse_proxy 127.0.0.1:8080 161 encode gzip zstd 162 header { 163 Referrer-Policy "no-referrer-when-downgrade" 164 X-Content-Type-Options "nosniff" 165 } 166} 167``` 168 169Caddy automatically provisions TLS certificates with Let’s Encrypt. Reload with `sudo systemctl reload caddy`. 170 171### Optional: Caddy + Docker Compose 172 173```yaml 174services: 175 pai: 176 image: ghcr.io/your-namespace/pai:latest 177 command: ["serve", "-d", "/data/pai.db", "-a", "0.0.0.0:8080"] 178 volumes: 179 - ./data:/data 180 expose: 181 - "8080" 182 183 caddy: 184 image: caddy:2 185 volumes: 186 - ./Caddyfile:/etc/caddy/Caddyfile:ro 187 - caddy_data:/data 188 - caddy_config:/config 189 ports: 190 - "80:80" 191 - "443:443" 192 depends_on: 193 - pai 194 195volumes: 196 caddy_data: 197 caddy_config: 198``` 199 200Use the same `Caddyfile` contents as above, but point `reverse_proxy` to `pai:8080`. 201 202## Health Checks & Monitoring 203 204- `GET /status` – lightweight JSON (`status`, version, uptime, total items, counts per `source_kind`). Ideal for load balancer health probes. 205- `GET /api/feed?limit=1` ensures the server can read from SQLite and return real data. 206- `GET /api/item/{id}` is handy for debugging a specific record. 207- Consider wiring `/status` into nginx/Caddy health checks (`/healthz`) or your platform’s monitoring agents. 208 209## Cloudflare Worker Deployment 210 211The Personal Activity Index can also be deployed as a Cloudflare Worker with D1 database, providing a serverless alternative to self-hosting. 212 213### Prerequisites 214 2151. Cloudflare account with Workers enabled 2162. [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/) installed 217 `npx wrangler` works here as well. 2183. Rust toolchain with `wasm32-unknown-unknown` target 2194. Crate `worker-build` 220 221### Quick Start 222 223#### 1. Generate Scaffolding 224 225Use the `pai cf-init` command to generate Cloudflare Worker configuration: 226 227```sh 228# Dry run to preview files 229pai cf-init --dry-run -o cloudflare-deployment 230 231# Create scaffolding 232pai cf-init -o cloudflare-deployment 233cd cloudflare-deployment 234``` 235 236This creates: 237 238- `wrangler.example.toml` - Worker configuration template 239- `schema.sql` - D1 database schema 240- `README.md` - Deployment instructions 241 242#### 2. Create D1 Database 243 244```sh 245wrangler d1 create personal-activity-db 246``` 247 248Copy the database ID from the output and update `wrangler.example.toml`: 249 250```toml 251[[d1_databases]] 252binding = "DB" 253database_name = "personal-activity-db" 254database_id = "your-database-id-here" # Replace with returned database_id 255``` 256 257Then copy to the active config: 258 259```sh 260cp wrangler.example.toml wrangler.toml 261``` 262 263#### 3. Initialize Database Schema 264 265```sh 266wrangler d1 execute personal-activity-db --remote --file=schema.sql 267``` 268 269Note that you can omit `--remote` for local development. 270 271#### 4. Build and Deploy 272 273```sh 274# Build the worker 275cd .. 276cargo install worker-build 277worker-build --release worker 278``` 279 280#### 5. Patch Generated Code 281 282The worker-build output requires two patches for compatibility with wrangler: 283 284```sh 285# 1. Fix import syntax (remove 'source' keyword) 286sed -i.bak 's/import source wasmModule/import wasmModule/' worker/build/index.js 287 288# 2. Add default export for ES module format (required for D1 bindings) 289echo -e "\nexport default { fetch, scheduled };" >> worker/build/index.js 290``` 291 292On macOS, use `sed -i '' ...` instead of `sed -i.bak ...`. 293 294#### 6. Deploy 295 296```sh 297cd cloudflare-deployment 298wrangler deploy 299``` 300 301### Cron Triggers 302 303The worker includes a scheduled event handler for automatic syncing. Configure the schedule in `wrangler.toml`: 304 305```toml 306[triggers] 307crons = ["0 * * * *"] # Every hour at minute 0 308``` 309 310Common schedules: 311 312- `*/30 * * * *` - Every 30 minutes 313- `0 */6 * * *` - Every 6 hours 314- `0 0 * * *` - Daily at midnight 315 316### Environment Variables 317 318Configure sources in `wrangler.toml` under `[vars]`: 319 320```toml 321[vars] 322# Substack RSS feed URL 323SUBSTACK_URL = "https://patternmatched.substack.com" 324 325# Bluesky handle 326BLUESKY_HANDLE = "desertthunder.dev" 327 328# Leaflet publications (comma-separated id:url pairs) 329LEAFLET_URLS = "desertthunder:https://desertthunder.leaflet.pub,stormlightlabs:https://stormlightlabs.leaflet.pub" 330 331# BearBlog publications (comma-separated id:url pairs) 332BEARBLOG_URLS = "desertthunder:https://desertthunder.bearblog.dev" 333 334# CORS configuration (optional) 335CORS_ALLOWED_ORIGINS = "https://desertthunder.dev,http://localhost:4321" 336CORS_DEV_KEY = "your-secret-dev-key" 337``` 338 339### CORS Configuration 340 341The Worker supports CORS to allow cross-origin requests from your web applications. 342 343#### Environment Variables 344 345Add to `wrangler.toml` under `[vars]`: 346 347- **CORS_ALLOWED_ORIGINS**: Comma-separated list of allowed origins 348 - Supports exact matching: `http://localhost:4321` only allows that exact origin 349 - Supports same-root-domain: `https://desertthunder.dev` also allows `https://pai.desertthunder.dev`, `https://api.desertthunder.dev`, etc. 350 351- **CORS_DEV_KEY**: Optional development key for local testing 352 - When set, requests with the `X-Local-Dev-Key` header matching this value bypass origin checking 353 - Useful for testing from different local ports during development 354 355#### Example Configuration 356 357```toml 358[vars] 359# Allow requests from your main domain and localhost for development 360CORS_ALLOWED_ORIGINS = "https://desertthunder.dev,http://localhost:4321" 361 362# Dev key for local Astro development 363CORS_DEV_KEY = "local-dev-secret-123" 364``` 365 366#### Usage from JavaScript 367 368```javascript 369// Production request from https://desertthunder.dev 370fetch('https://pai.desertthunder.dev/api/feed', { 371 credentials: 'include' 372}) 373 374// Development request from http://localhost:4321 375fetch('http://localhost:8787/api/feed', { 376 headers: { 377 'X-Local-Dev-Key': 'local-dev-secret-123' 378 } 379}) 380``` 381 382#### Same-Root-Domain Support 383 384When you configure `CORS_ALLOWED_ORIGINS = "https://desertthunder.dev"`: 385 386- ✓ `https://desertthunder.dev` (exact match) 387- ✓ `https://pai.desertthunder.dev` (subdomain) 388- ✓ `https://api.desertthunder.dev` (subdomain) 389- ✗ `https://evil.dev` (different root domain) 390 391This allows you to deploy the Worker to `pai.desertthunder.dev` and access it from your main site at `desertthunder.dev` without explicitly listing every subdomain. 392 393### API Endpoints 394 395The Worker exposes the following API: 396 397- `GET /` - API documentation (JSON) 398- `GET /api/feed?source_kind=bluesky&limit=20` - List items with optional filters 399- `GET /api/item/{id}` - Get single item by ID 400- `POST /api/sync` - Manually trigger synchronization from all configured sources 401- `GET /status` - Health check and version info 402 403### Local Development 404 405Test the worker locally before deploying: 406 407```sh 408wrangler dev 409``` 410 411This starts a local server at `http://localhost:8787` with live reload. 412 413### Monitoring 414 415View logs in real-time: 416 417```sh 418wrangler tail 419``` 420 421Or check logs in the [Cloudflare Dashboard](https://dash.cloudflare.com) under Workers & Pages.