Game sync and live services for independent game developers (targeting itch.io)
Requirements#
Functional Requirements#
1. Storage Management#
- 2GB default quota per user (soft limits at 80%, cumulative tracking)
- Hard limit enforcement when quota exceeded
- Cumulative tracking (never resets)
- Blob count limits (default: 1000)
2. GDPR Compliance#
- Automatic EU country detection via GeoLite2 database
- EU users must store data in EU data centers
- Non-EU users can use default backend
- Country detection failure must crash application
- GeoLite2 database path configurable via
GEOIP_DATABASE_PATHenvironment variable - Block uploads until country is set in profile
- Provide country consent flow with GDPR alerts
3. Multi-Backend Storage#
- Bunny Storage support (default backend)
- DigitalOcean Spaces support (alternative backend)
- Real-time cost calculation from backend APIs
- 1-hour cost cache with automatic background refresh
- Backend selection based on user location (GDPR-compliant)
- Automatic storage backups (Bunny built-in: 7-day retention)
4. Authentication#
- JWT for XRPC endpoints (24-hour expiration)
- Passkey for admin CLI (terminal-only, no web UI)
- Terminal registration via single interactive command with QR code
- HTTP endpoint for credential response (from phone)
- Age encryption for passkeys database (synced to Bunny Storage)
- Session cookies for web UI (7-day expiration)
- Session cookies created after country consent
- HTTP-only, Secure, SameSite=Lax settings for cookies
5. Passkey Management#
- Terminal-only registration (no web UI)
- Single interactive command:
scrtchbk-ctl passkey-register <username> - Display QR code in terminal (ASCII art)
- Wait for HTTP POST
/auth/passkey/responsefrom phone - Store credentials in passkeys database
- Encrypt passkeys database with age
- Sync to Bunny Storage bucket after every registration
- Encryption key loaded from
.env.ageenvironment variable - 5-minute timeout for credential response
6. Cost Management#
- Real-time storage cost per GB (Bunny: $0.01, DO: $0.005)
- Real-time bandwidth cost per GB (Bunny: $0.005, DO: $0.008)
- Combined total cost calculation
- 1-hour cache duration
- Automatic background refresh task (tokio)
- Cached using
cachedproc macro for memoization - Cache cleared every hour to force recalculation
- Individual backend costs cached separately
- Combined total cost cached separately
7. Admin Controls#
- CLI tool named
scrtchbk-ctl - Direct database access (no authentication required)
- System statistics command
- User management commands (list, view, set quota)
- Blob management commands (list, soft delete)
- Cleanup command for expired soft-deleted blobs (14-day retention)
- Passkey commands (register, sync)
- Database query commands
- Static binary (separate build.sr.ht)
8. Web UI#
- Homepage with deployment information and real-time cost estimates
- Protected self-view page (session cookie auth required)
- Country consent flow page
- HTMX for dynamic interactions
- minijinja templates with Object contexts
- 7-day session cookie expiration
- HTTP-only, Secure, SameSite=Lax cookie settings
9. Configuration#
- Priority: Environment variables > config.toml > defaults
- Age decryption on startup (
.env.age) - GeoLite2 database path via
GEOIP_DATABASE_PATHenvironment variable - Session cookie configuration (7-day expiration)
- Separate build.sr.ht for admin CLI
10. Deployment#
- Deployment metadata generated fresh at every startup
- Static binary builds via build.sr.ht
- No CI/CD (manual deployment to Hetzner VPS)
- Deployment info stored in
deployment-info.json
Non-Functional Requirements#
1. Technology Stack#
- Rust 2021 edition
- Axum 0.7 (web framework)
- Diesel 2.1 (ORM)
- SQLite (database via rsky)
- minijinja 2.0 (templates)
- age 0.11 (encryption)
- cached 0.1 (memoization)
- fs-err-tokio 0.2 (error handling)
- webauthn-rs 0.5 (passkey)
- aws-sdk-s3 1.20 (Bunny/DigitalOcean)
- maxminddb 0.24 (GeoIP)
- qrcode 0.14 (QR codes)
- cookie 0.18 (session cookies)
2. Performance#
- Cost calculations cached for 1 hour
- Background refresh task for cache
- Async/await throughout
- Connection pooling for databases
- Real-time API calls to storage backends
3. Security#
- Zero-knowledge (server cannot decrypt user data)
- Age encryption at rest (passkeys in Bunny Storage)
- HTTPS-only for production
- Secure cookies (HTTP-only, Secure flag, SameSite=Lax)
- JWT tokens (24-hour expiration)
- Session tokens (7-day expiration)
- Separate passkeys database for admin credentials
4. Reliability#
- Bunny Storage 99.95% uptime SLA
- DigitalOcean Spaces 99.9% uptime SLA
- Soft deletion with 14-day retention (grace period for recovery)
- Automatic storage backups (Bunny built-in, 7-day retention)
- GeoIP detection with crash on failure (admin must configure)
5. Usability#
- Terminal-only passkey registration with QR codes
- Clear error messages for configuration failures
- GDPR consent flow with storage restrictions explained
- Mobile-responsive HTMX interface
- Real-time cost estimates on homepage
6. Maintainability#
- Separate admin CLI from web server
- Comprehensive documentation
- Diesel migrations for schema changes
- Build.sr.ht for reproducible deployments
- Error handling with miette for detailed diagnostics
7. Scalability#
- Dual storage backends (Bunny + DigitalOcean)
- GDPR-compliant region routing
- Cost cache reduces API calls
- Connection pooling for databases
- Background tasks for non-blocking operations
Constraints#
-
Budget: $80/month maximum
- Hetzner EU VPS: $5.50
- DigitalOcean US VPS: $12 (upgraded)
- Bunny Storage: $11 total (separate EU/US buckets)
- Sentry: $15 (team plan)
- uptime.io: $7
- Elastic Email: $5
- Turso: $10 (for metadata only)
-
Storage backends must be S3-compatible
-
Passkey registration must be terminal-only
-
GeoIP database must be configured before starting
-
Application must crash if GeoIP detection fails
-
Session cookies must expire after 7 days
-
Soft-deleted blobs must be retained for 14 days
-
Cost cache must refresh every hour
-
Passkeys must sync to Bunny Storage after registration
-
Age encryption key must be in
.env.age