QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.
···1+# Git
2+.git
3+.gitignore
4+5+# Documentation
6+*.md
7+docs/
8+LICENSE
9+10+# Development files
11+.vscode/
12+.env
13+.env.local
14+*.log
15+16+# Build artifacts
17+target/
18+Dockerfile
19+.dockerignore
20+21+# Test files
22+tests/
23+benches/
24+25+# Scripts (except the ones we need)
26+*.sh
27+28+# SQLite databases
29+*.db
30+*.db-*
31+32+# OS files
33+.DS_Store
34+Thumbs.db
35+36+# Keep the www directory for static files
37+!www/
+12-8
.env.example
···1# QuickDID Environment Configuration Template
2# Copy this file to .env and customize for your deployment
3-#
4-# IMPORTANT: Never commit .env files with real SERVICE_KEY values
56# ============================================================================
7# REQUIRED CONFIGURATION
···13# - quickdid.example.com:8080
14# - localhost:3007
15HTTP_EXTERNAL=quickdid.example.com
16-17-# Private key for service identity (REQUIRED)
18-# SECURITY: Generate a new key for each environment
19-# NEVER commit real keys to version control
20-SERVICE_KEY=did:key:YOUR_PRIVATE_KEY_HERE
2122# ============================================================================
23# NETWORK CONFIGURATION
···98QUEUE_BUFFER_SIZE=1000
99100# ============================================================================
000000000000101# LOGGING
102# ============================================================================
103···112# ============================================================================
113114# HTTP_EXTERNAL=localhost:3007
115-# SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK
116# RUST_LOG=debug
117# CACHE_TTL_MEMORY=60
118# CACHE_TTL_REDIS=300
···21cargo build
2223# Run in debug mode (requires environment variables)
24-HTTP_EXTERNAL=localhost:3007 SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK cargo run
2526# Run tests
27cargo test
···714. **HTTP Server** (`src/http/`)
72 - XRPC endpoints for AT Protocol compatibility
73 - Health check endpoint
74- - DID document serving via .well-known
075 - CORS headers support for cross-origin requests
76 - Cache-Control headers with configurable max-age and stale directives
77 - ETag support with configurable seed for cache invalidation
···107108### Required
109- `HTTP_EXTERNAL`: External hostname for service endpoints (e.g., `localhost:3007`)
110-- `SERVICE_KEY`: Private key for service identity (DID format)
111112### Optional - Core Configuration
113- `HTTP_PORT`: Server port (default: 8080)
114- `PLC_HOSTNAME`: PLC directory hostname (default: plc.directory)
115- `RUST_LOG`: Logging level (e.g., debug, info)
0116117### Optional - Caching
118- `REDIS_URL`: Redis connection URL for caching
···21cargo build
2223# Run in debug mode (requires environment variables)
24+HTTP_EXTERNAL=localhost:3007 cargo run
2526# Run tests
27cargo test
···714. **HTTP Server** (`src/http/`)
72 - XRPC endpoints for AT Protocol compatibility
73 - Health check endpoint
74+ - Static file serving from configurable directory (default: www)
75+ - Serves .well-known files as static content
76 - CORS headers support for cross-origin requests
77 - Cache-Control headers with configurable max-age and stale directives
78 - ETag support with configurable seed for cache invalidation
···108109### Required
110- `HTTP_EXTERNAL`: External hostname for service endpoints (e.g., `localhost:3007`)
0111112### Optional - Core Configuration
113- `HTTP_PORT`: Server port (default: 8080)
114- `PLC_HOSTNAME`: PLC directory hostname (default: plc.directory)
115- `RUST_LOG`: Logging level (e.g., debug, info)
116+- `STATIC_FILES_DIR`: Directory for serving static files (default: www)
117118### Optional - Caching
119- `REDIS_URL`: Redis connection URL for caching
···8384## Minimum Configuration
8586-QuickDID requires the following environment variables to run. Configuration is validated at startup, and the service will exit with specific error codes if validation fails.
8788### Required
8990- `HTTP_EXTERNAL`: External hostname for service endpoints (e.g., `localhost:3007`)
91-- `SERVICE_KEY`: Private key for service identity in DID format (e.g., `did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK`)
9293### Example Minimal Setup
9495```bash
96-HTTP_EXTERNAL=localhost:3007 \
97-SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK \
98-cargo run
000000000099```
100101This will start QuickDID with:
···155- `PROACTIVE_REFRESH_ENABLED`: Enable proactive cache refreshing (default: false)
156- `PROACTIVE_REFRESH_THRESHOLD`: Refresh when TTL remaining is below this threshold (0.0-1.0, default: 0.8)
157000158#### Logging
159- `RUST_LOG`: Logging level (e.g., debug, info, warn, error)
160···163#### Redis-based with Metrics (Multi-instance/HA)
164```bash
165HTTP_EXTERNAL=quickdid.example.com \
166-SERVICE_KEY=did:key:yourkeyhere \
167HTTP_PORT=3000 \
168REDIS_URL=redis://localhost:6379 \
169CACHE_TTL_REDIS=86400 \
···183#### SQLite-based (Single-instance)
184```bash
185HTTP_EXTERNAL=quickdid.example.com \
186-SERVICE_KEY=did:key:yourkeyhere \
187HTTP_PORT=3000 \
188SQLITE_URL=sqlite:./quickdid.db \
189CACHE_TTL_SQLITE=86400 \
···8384## Minimum Configuration
8586+QuickDID requires minimal configuration to run. Configuration is validated at startup, and the service will exit with specific error codes if validation fails.
8788### Required
8990- `HTTP_EXTERNAL`: External hostname for service endpoints (e.g., `localhost:3007`)
09192### Example Minimal Setup
9394```bash
95+HTTP_EXTERNAL=localhost:3007 cargo run
96+```
97+98+### Static Files
99+100+QuickDID serves static files from the `www` directory by default. This includes:
101+- Landing page (`index.html`)
102+- AT Protocol well-known files (`.well-known/atproto-did` and `.well-known/did.json`)
103+104+Generate the `.well-known` files for your deployment:
105+106+```bash
107+HTTP_EXTERNAL=your-domain.com ./generate-wellknown.sh
108```
109110This will start QuickDID with:
···164- `PROACTIVE_REFRESH_ENABLED`: Enable proactive cache refreshing (default: false)
165- `PROACTIVE_REFRESH_THRESHOLD`: Refresh when TTL remaining is below this threshold (0.0-1.0, default: 0.8)
166167+#### Static Files
168+- `STATIC_FILES_DIR`: Directory for serving static files (default: www)
169+170#### Logging
171- `RUST_LOG`: Logging level (e.g., debug, info, warn, error)
172···175#### Redis-based with Metrics (Multi-instance/HA)
176```bash
177HTTP_EXTERNAL=quickdid.example.com \
0178HTTP_PORT=3000 \
179REDIS_URL=redis://localhost:6379 \
180CACHE_TTL_REDIS=86400 \
···194#### SQLite-based (Single-instance)
195```bash
196HTTP_EXTERNAL=quickdid.example.com \
0197HTTP_PORT=3000 \
198SQLITE_URL=sqlite:./quickdid.db \
199CACHE_TTL_SQLITE=86400 \
···40**Constraints**:
41- Must be a valid hostname or hostname:port combination
42- Port (if specified) must be between 1-65535
43-- Used to generate service DID (did:web:{HTTP_EXTERNAL})
44-45-### `SERVICE_KEY`
46-47-**Required**: Yes
48-**Type**: String
49-**Format**: DID private key
50-**Security**: SENSITIVE - Never commit to version control
51-52-The private key for the service's AT Protocol identity. This key is used to sign responses and authenticate the service.
53-54-**Examples**:
55-```bash
56-# did:key format (Ed25519)
57-SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK
58-59-# did:plc format
60-SERVICE_KEY=did:plc:xyz123abc456def789
61-```
62-63-**Constraints**:
64-- Must be a valid DID format
65-- Must include the private key component
66-- Should be stored securely (e.g., secrets manager, encrypted storage)
6768## Network Configuration
69···785- TTL=3600s (1 hour), threshold=0.8: Refresh after 48 minutes
786- TTL=86400s (1 day), threshold=0.8: Refresh after 19.2 hours
7870000000000000000000000000000000000000000000000000000788## HTTP Caching Configuration
789790### `CACHE_MAX_AGE`
···958```bash
959# .env.development
960HTTP_EXTERNAL=localhost:3007
961-SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK
962RUST_LOG=debug
963```
964···968# .env.production.redis
969# Required
970HTTP_EXTERNAL=quickdid.example.com
971-SERVICE_KEY=${SECRET_SERVICE_KEY} # From secrets manager
972973# Network
974HTTP_PORT=8080
···1015# .env.production.sqlite
1016# Required
1017HTTP_EXTERNAL=quickdid.example.com
1018-SERVICE_KEY=${SECRET_SERVICE_KEY} # From secrets manager
10191020# Network
1021HTTP_PORT=8080
···1050# .env.ha.redis
1051# Required
1052HTTP_EXTERNAL=quickdid.example.com
1053-SERVICE_KEY=${SECRET_SERVICE_KEY}
10541055# Network
1056HTTP_PORT=8080
···1097# .env.hybrid
1098# Required
1099HTTP_EXTERNAL=quickdid.example.com
1100-SERVICE_KEY=${SECRET_SERVICE_KEY}
11011102# Network
1103HTTP_PORT=8080
···1128 image: quickdid:latest
1129 environment:
1130 HTTP_EXTERNAL: quickdid.example.com
1131- SERVICE_KEY: ${SERVICE_KEY}
1132 HTTP_PORT: 8080
1133 REDIS_URL: redis://redis:6379/0
1134 CACHE_TTL_MEMORY: 600
···1157 image: quickdid:latest
1158 environment:
1159 HTTP_EXTERNAL: quickdid.example.com
1160- SERVICE_KEY: ${SERVICE_KEY}
1161 HTTP_PORT: 8080
1162 SQLITE_URL: sqlite:/data/quickdid.db
1163 CACHE_TTL_MEMORY: 600
···1183### Required Fields
118411851. **HTTP_EXTERNAL**: Must be provided
1186-2. **SERVICE_KEY**: Must be provided
11871188### Value Constraints
1189···12281229```bash
1230# Validate configuration
1231-HTTP_EXTERNAL=test SERVICE_KEY=test quickdid --help
12321233# Test with specific values
1234CACHE_TTL_MEMORY=0 quickdid --help # Will fail validation
12351236# Check parsed configuration (with debug logging)
1237-RUST_LOG=debug HTTP_EXTERNAL=test SERVICE_KEY=test quickdid
1238```
12391240## Best Practices
12411242### Security
12431244-1. **Never commit SERVICE_KEY** to version control
1245-2. Use environment-specific key management (Vault, AWS Secrets, etc.)
1246-3. Rotate SERVICE_KEY regularly
1247-4. Use TLS for Redis connections in production (`rediss://`)
12485. Implement network segmentation for Redis access
12491250### Performance
···1280### Deployment
128112821. Use `.env` files for local development
1283-2. Use secrets management for production SERVICE_KEY
12843. Set resource limits in container orchestration
12854. Use health checks to monitor service availability
12865. Implement gradual rollouts with feature flags
···40**Constraints**:
41- Must be a valid hostname or hostname:port combination
42- Port (if specified) must be between 1-65535
0000000000000000000000004344## Network Configuration
45···761- TTL=3600s (1 hour), threshold=0.8: Refresh after 48 minutes
762- TTL=86400s (1 day), threshold=0.8: Refresh after 19.2 hours
763764+## Static Files Configuration
765+766+### `STATIC_FILES_DIR`
767+768+**Required**: No
769+**Type**: String (directory path)
770+**Default**: `www`
771+772+Directory path for serving static files. This directory should contain the landing page and AT Protocol well-known files.
773+774+**Directory Structure**:
775+```
776+www/
777+├── index.html # Landing page
778+├── .well-known/
779+│ ├── atproto-did # Service DID identifier
780+│ └── did.json # DID document
781+└── (other static assets)
782+```
783+784+**Examples**:
785+```bash
786+# Default (relative to working directory)
787+STATIC_FILES_DIR=www
788+789+# Absolute path
790+STATIC_FILES_DIR=/var/www/quickdid
791+792+# Docker container path
793+STATIC_FILES_DIR=/app/www
794+795+# Custom directory
796+STATIC_FILES_DIR=./public
797+```
798+799+**Docker Volume Mounting**:
800+```yaml
801+volumes:
802+ # Mount entire custom directory
803+ - ./custom-www:/app/www:ro
804+805+ # Mount specific files
806+ - ./custom-index.html:/app/www/index.html:ro
807+ - ./well-known:/app/www/.well-known:ro
808+```
809+810+**Generating Well-Known Files**:
811+```bash
812+# Generate .well-known files for your domain
813+HTTP_EXTERNAL=your-domain.com ./generate-wellknown.sh
814+```
815+816## HTTP Caching Configuration
817818### `CACHE_MAX_AGE`
···986```bash
987# .env.development
988HTTP_EXTERNAL=localhost:3007
0989RUST_LOG=debug
990```
991···995# .env.production.redis
996# Required
997HTTP_EXTERNAL=quickdid.example.com
0998999# Network
1000HTTP_PORT=8080
···1041# .env.production.sqlite
1042# Required
1043HTTP_EXTERNAL=quickdid.example.com
010441045# Network
1046HTTP_PORT=8080
···1075# .env.ha.redis
1076# Required
1077HTTP_EXTERNAL=quickdid.example.com
010781079# Network
1080HTTP_PORT=8080
···1121# .env.hybrid
1122# Required
1123HTTP_EXTERNAL=quickdid.example.com
011241125# Network
1126HTTP_PORT=8080
···1151 image: quickdid:latest
1152 environment:
1153 HTTP_EXTERNAL: quickdid.example.com
01154 HTTP_PORT: 8080
1155 REDIS_URL: redis://redis:6379/0
1156 CACHE_TTL_MEMORY: 600
···1179 image: quickdid:latest
1180 environment:
1181 HTTP_EXTERNAL: quickdid.example.com
01182 HTTP_PORT: 8080
1183 SQLITE_URL: sqlite:/data/quickdid.db
1184 CACHE_TTL_MEMORY: 600
···1204### Required Fields
120512061. **HTTP_EXTERNAL**: Must be provided
1207+2. **HTTP_EXTERNAL**: Must be provided
12081209### Value Constraints
1210···12491250```bash
1251# Validate configuration
1252+HTTP_EXTERNAL=test quickdid --help
12531254# Test with specific values
1255CACHE_TTL_MEMORY=0 quickdid --help # Will fail validation
12561257# Check parsed configuration (with debug logging)
1258+RUST_LOG=debug HTTP_EXTERNAL=test quickdid
1259```
12601261## Best Practices
12621263### Security
12641265+1. Use environment-specific configuration management
1266+2. Use TLS for Redis connections in production (`rediss://`)
1267+3. Never commit sensitive configuration to version control
012685. Implement network segmentation for Redis access
12691270### Performance
···1300### Deployment
130113021. Use `.env` files for local development
1303+2. Use secrets management for production configurations
13043. Set resource limits in container orchestration
13054. Use health checks to monitor service availability
13065. Implement gradual rollouts with feature flags
+18-14
docs/production-deployment.md
···42# - localhost:3007 (for testing only)
43HTTP_EXTERNAL=quickdid.example.com
4445-# Private key for service identity (DID format)
46-# Generate a new key for production using atproto-identity tools
47-# SECURITY: Keep this key secure and never commit to version control
48-# Example formats:
49-# - did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK
50-# - did:plc:xyz123abc456
51-SERVICE_KEY=did:key:YOUR_PRODUCTION_KEY_HERE
52-53# ----------------------------------------------------------------------------
54# NETWORK CONFIGURATION
55# ----------------------------------------------------------------------------
···304PROACTIVE_REFRESH_THRESHOLD=0.8
305306# ----------------------------------------------------------------------------
0000000000000307# PERFORMANCE TUNING
308# ----------------------------------------------------------------------------
309···512 container_name: quickdid-sqlite
513 environment:
514 HTTP_EXTERNAL: quickdid.example.com
515- SERVICE_KEY: ${SERVICE_KEY}
516 HTTP_PORT: 8080
517 SQLITE_URL: sqlite:/data/quickdid.db
518 CACHE_TTL_MEMORY: 600
···713714### 1. Service Key Protection
715716-- **Never commit** the `SERVICE_KEY` to version control
717- Store keys in a secure secret management system (e.g., HashiCorp Vault, AWS Secrets Manager)
718- Rotate keys regularly
719- Use different keys for different environments
···758docker logs quickdid
759760# Verify environment variables
761-docker exec quickdid env | grep -E "HTTP_EXTERNAL|SERVICE_KEY"
762763# Test Redis connectivity
764docker exec quickdid redis-cli -h redis ping
···943### Required Fields
944945- **HTTP_EXTERNAL**: Must be provided
946-- **SERVICE_KEY**: Must be provided
947948### Value Constraints
949···982983```bash
984# Validate configuration without starting service
985-HTTP_EXTERNAL=test SERVICE_KEY=test quickdid --help
986987# Test with specific values (will fail validation)
988CACHE_TTL_MEMORY=0 quickdid --help
989990# Debug configuration parsing
991-RUST_LOG=debug HTTP_EXTERNAL=test SERVICE_KEY=test quickdid
992```
993994## Support and Resources
···42# - localhost:3007 (for testing only)
43HTTP_EXTERNAL=quickdid.example.com
440000000045# ----------------------------------------------------------------------------
46# NETWORK CONFIGURATION
47# ----------------------------------------------------------------------------
···296PROACTIVE_REFRESH_THRESHOLD=0.8
297298# ----------------------------------------------------------------------------
299+# STATIC FILES CONFIGURATION
300+# ----------------------------------------------------------------------------
301+302+# Directory path for serving static files (default: www)
303+# This directory should contain:
304+# - index.html (landing page)
305+# - .well-known/atproto-did (service DID identifier)
306+# - .well-known/did.json (DID document)
307+# In Docker, this defaults to /app/www
308+# You can mount custom files via Docker volumes
309+STATIC_FILES_DIR=/app/www
310+311+# ----------------------------------------------------------------------------
312# PERFORMANCE TUNING
313# ----------------------------------------------------------------------------
314···517 container_name: quickdid-sqlite
518 environment:
519 HTTP_EXTERNAL: quickdid.example.com
0520 HTTP_PORT: 8080
521 SQLITE_URL: sqlite:/data/quickdid.db
522 CACHE_TTL_MEMORY: 600
···717718### 1. Service Key Protection
719720+- **Never commit** sensitive configuration to version control
721- Store keys in a secure secret management system (e.g., HashiCorp Vault, AWS Secrets Manager)
722- Rotate keys regularly
723- Use different keys for different environments
···762docker logs quickdid
763764# Verify environment variables
765+docker exec quickdid env | grep -E "HTTP_EXTERNAL|HTTP_PORT"
766767# Test Redis connectivity
768docker exec quickdid redis-cli -h redis ping
···947### Required Fields
948949- **HTTP_EXTERNAL**: Must be provided
950+- **HTTP_EXTERNAL**: Must be provided
951952### Value Constraints
953···986987```bash
988# Validate configuration without starting service
989+HTTP_EXTERNAL=test quickdid --help
990991# Test with specific values (will fail validation)
992CACHE_TTL_MEMORY=0 quickdid --help
993994# Debug configuration parsing
995+RUST_LOG=debug HTTP_EXTERNAL=test quickdid
996```
997998## Support and Resources
···1+#!/bin/bash
2+3+# Script to generate .well-known static files based on QuickDID configuration
4+# Usage: HTTP_EXTERNAL=quickdid.smokesignal.tools ./generate-wellknown.sh
5+#
6+# Note: Since we no longer process SERVICE_KEY, you'll need to manually
7+# add the public key to the did.json file if you need DID document support.
8+9+set -e
10+11+# Check required environment variables
12+if [ -z "$HTTP_EXTERNAL" ]; then
13+ echo "Error: HTTP_EXTERNAL environment variable is required"
14+ echo "Usage: HTTP_EXTERNAL=example.com ./generate-wellknown.sh"
15+ exit 1
16+fi
17+18+# Ensure www/.well-known directory exists
19+mkdir -p www/.well-known
20+21+# Generate service DID from HTTP_EXTERNAL
22+if [[ "$HTTP_EXTERNAL" == *":"* ]]; then
23+ # Contains port - URL encode the colon
24+ SERVICE_DID="did:web:${HTTP_EXTERNAL//:/%3A}"
25+else
26+ SERVICE_DID="did:web:$HTTP_EXTERNAL"
27+fi
28+29+echo "Generating .well-known files for $SERVICE_DID"
30+31+# Write atproto-did file
32+echo "$SERVICE_DID" > www/.well-known/atproto-did
33+echo "Created: www/.well-known/atproto-did"
34+35+# Create a basic did.json template
36+# Note: You'll need to manually add the publicKeyMultibase if you need DID document support
37+38+cat > www/.well-known/did.json <<EOF
39+{
40+ "@context": [
41+ "https://www.w3.org/ns/did/v1",
42+ "https://w3id.org/security/multikey/v1"
43+ ],
44+ "id": "$SERVICE_DID",
45+ "verificationMethod": [],
46+ "service": [
47+ {
48+ "id": "${SERVICE_DID}#quickdid",
49+ "type": "QuickDIDService",
50+ "serviceEndpoint": "https://${HTTP_EXTERNAL}"
51+ }
52+ ]
53+}
54+EOF
55+56+echo "Created: www/.well-known/did.json"
57+echo ""
58+echo "Note: The did.json file is a basic template. If you need DID document support,"
59+echo "you'll need to manually add the verificationMethod with your public key."
+1-24
src/bin/quickdid.rs
···1use anyhow::Result;
2use atproto_identity::{
3 config::{CertificateBundles, DnsNameservers},
4- key::{identify_key, to_public},
5 resolve::HickoryDnsResolver,
6};
7use quickdid::{
···23 sqlite_schema::create_sqlite_pool,
24 task_manager::spawn_cancellable_task,
25};
26-use serde_json::json;
27use std::sync::Arc;
28use tokio::signal;
29use tokio_util::{sync::CancellationToken, task::TaskTracker};
···79 println!(" -V, --version Print version information");
80 println!();
81 println!("ENVIRONMENT VARIABLES:");
82- println!(" SERVICE_KEY Private key for service identity (required)");
83 println!(
84 " HTTP_EXTERNAL External hostname for service endpoints (required)"
85 );
···191 config.validate()?;
192193 tracing::info!("Starting QuickDID service on port {}", config.http_port);
194- tracing::info!("Service DID: {}", config.service_did);
195 tracing::info!(
196 "Cache TTL - Memory: {}s, Redis: {}s, SQLite: {}s",
197 config.cache_ttl_memory,
···225226 // Create DNS resolver
227 let dns_resolver = HickoryDnsResolver::create_resolver(dns_nameservers.as_ref());
228-229- // Process service key
230- let private_service_key_data = identify_key(&config.service_key)?;
231- let public_service_key_data = to_public(&private_service_key_data)?;
232- let public_service_key = public_service_key_data.to_string();
233-234- // Create service DID document
235- let service_document = json!({
236- "@context": vec!["https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1"],
237- "id": config.service_did.clone(),
238- "verificationMethod": [{
239- "id": format!("{}#atproto", config.service_did),
240- "type": "Multikey",
241- "controller": config.service_did.clone(),
242- "publicKeyMultibase": public_service_key
243- }],
244- "service": []
245- });
246247 // Create DNS resolver Arc for sharing
248 let dns_resolver_arc = Arc::new(dns_resolver);
···543544 // Create app context with the queue adapter
545 let app_context = AppContext::new(
546- service_document,
547- config.service_did.clone(),
548 handle_resolver.clone(),
549 handle_queue,
550 metrics_publisher,
551 config.etag_seed.clone(),
552 config.cache_control_header.clone(),
0553 );
554555 // Create router
···1use anyhow::Result;
2use atproto_identity::{
3 config::{CertificateBundles, DnsNameservers},
04 resolve::HickoryDnsResolver,
5};
6use quickdid::{
···22 sqlite_schema::create_sqlite_pool,
23 task_manager::spawn_cancellable_task,
24};
025use std::sync::Arc;
26use tokio::signal;
27use tokio_util::{sync::CancellationToken, task::TaskTracker};
···77 println!(" -V, --version Print version information");
78 println!();
79 println!("ENVIRONMENT VARIABLES:");
080 println!(
81 " HTTP_EXTERNAL External hostname for service endpoints (required)"
82 );
···188 config.validate()?;
189190 tracing::info!("Starting QuickDID service on port {}", config.http_port);
0191 tracing::info!(
192 "Cache TTL - Memory: {}s, Redis: {}s, SQLite: {}s",
193 config.cache_ttl_memory,
···221222 // Create DNS resolver
223 let dns_resolver = HickoryDnsResolver::create_resolver(dns_nameservers.as_ref());
000000000000000000224225 // Create DNS resolver Arc for sharing
226 let dns_resolver_arc = Arc::new(dns_resolver);
···521522 // Create app context with the queue adapter
523 let app_context = AppContext::new(
00524 handle_resolver.clone(),
525 handle_queue,
526 metrics_publisher,
527 config.etag_seed.clone(),
528 config.cache_control_header.clone(),
529+ config.static_files_dir.clone(),
530 );
531532 // Create router
+9-32
src/config.rs
···13//! ```bash
14//! # Minimal configuration
15//! HTTP_EXTERNAL=quickdid.example.com \
16-//! SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK \
17//! quickdid
18//!
19//! # Full configuration with Redis and custom settings
20//! HTTP_EXTERNAL=quickdid.example.com \
21-//! SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK \
22//! HTTP_PORT=3000 \
23//! REDIS_URL=redis://localhost:6379 \
24//! CACHE_TTL_MEMORY=300 \
···38pub enum ConfigError {
39 /// Missing required environment variable or command-line argument
40 ///
41- /// Example: When SERVICE_KEY or HTTP_EXTERNAL are not provided
42 #[error("error-quickdid-config-1 Missing required environment variable: {0}")]
43 MissingRequired(String),
44···97/// config.validate()?;
98///
99/// println!("Service running at: {}", config.http_external);
100-/// println!("Service DID: {}", config.service_did);
101/// # Ok(())
102/// # }
103/// ```
···112 /// External hostname for service endpoints (e.g., "quickdid.example.com")
113 pub http_external: String,
114115- /// Private key for service identity (e.g., "did:key:z42tm...")
116- pub service_key: String,
117-118 /// HTTP User-Agent for outgoing requests (e.g., "quickdid/1.0.0 (+https://...)")
119 pub user_agent: String,
120-121- /// Derived service DID (e.g., "did:web:quickdid.example.com")
122- /// Automatically generated from http_external with proper encoding
123- pub service_did: String,
124125 /// Custom DNS nameservers, comma-separated (e.g., "8.8.8.8,8.8.4.4")
126 pub dns_nameservers: Option<String>,
···250 /// For example, 0.8 means refresh when an entry has lived for 80% of its TTL.
251 /// Default: 0.8 (80%)
252 pub proactive_refresh_threshold: f64,
00000253}
254255impl Config {
···257 ///
258 /// This method:
259 /// 1. Reads configuration from environment variables
260- /// 2. Validates required fields (HTTP_EXTERNAL and SERVICE_KEY)
261- /// 3. Generates derived values (service_did from http_external)
262- /// 4. Applies defaults where appropriate
263 ///
264 /// ## Example
265 ///
···270 /// // Parse from environment variables
271 /// let config = Config::from_env()?;
272 ///
273- /// // The service DID is automatically generated from HTTP_EXTERNAL
274- /// assert!(config.service_did.starts_with("did:web:"));
275 /// # Ok(())
276 /// # }
277 /// ```
···280 ///
281 /// Returns `ConfigError::MissingRequired` if:
282 /// - HTTP_EXTERNAL is not provided
283- /// - SERVICE_KEY is not provided
284 pub fn from_env() -> Result<Self, ConfigError> {
285 // Required fields
286 let http_external = env::var("HTTP_EXTERNAL")
···288 .filter(|s| !s.is_empty())
289 .ok_or_else(|| ConfigError::MissingRequired("HTTP_EXTERNAL".to_string()))?;
290291- let service_key = env::var("SERVICE_KEY")
292- .ok()
293- .filter(|s| !s.is_empty())
294- .ok_or_else(|| ConfigError::MissingRequired("SERVICE_KEY".to_string()))?;
295-296 // Generate default user agent
297 let default_user_agent = format!(
298 "quickdid/{} (+https://github.com/smokesignal.events/quickdid)",
299 env!("CARGO_PKG_VERSION")
300 );
301302- // Generate service DID from http_external
303- let service_did = if http_external.contains(':') {
304- let encoded_external = http_external.replace(':', "%3A");
305- format!("did:web:{}", encoded_external)
306- } else {
307- format!("did:web:{}", http_external)
308- };
309-310 let mut config = Config {
311 http_port: get_env_or_default("HTTP_PORT", Some("8080")).unwrap(),
312 plc_hostname: get_env_or_default("PLC_HOSTNAME", Some("plc.directory")).unwrap(),
313 http_external,
314- service_key,
315 user_agent: get_env_or_default("USER_AGENT", None).unwrap_or(default_user_agent),
316- service_did,
317 dns_nameservers: get_env_or_default("DNS_NAMESERVERS", None),
318 certificate_bundles: get_env_or_default("CERTIFICATE_BUNDLES", None),
319 redis_url: get_env_or_default("REDIS_URL", None),
···350 metrics_tags: get_env_or_default("METRICS_TAGS", None),
351 proactive_refresh_enabled: parse_env("PROACTIVE_REFRESH_ENABLED", false)?,
352 proactive_refresh_threshold: parse_env("PROACTIVE_REFRESH_THRESHOLD", 0.8)?,
0353 };
354355 // Calculate the Cache-Control header value if enabled
···13//! ```bash
14//! # Minimal configuration
15//! HTTP_EXTERNAL=quickdid.example.com \
016//! quickdid
17//!
18//! # Full configuration with Redis and custom settings
19//! HTTP_EXTERNAL=quickdid.example.com \
020//! HTTP_PORT=3000 \
21//! REDIS_URL=redis://localhost:6379 \
22//! CACHE_TTL_MEMORY=300 \
···36pub enum ConfigError {
37 /// Missing required environment variable or command-line argument
38 ///
39+ /// Example: When HTTP_EXTERNAL is not provided
40 #[error("error-quickdid-config-1 Missing required environment variable: {0}")]
41 MissingRequired(String),
42···95/// config.validate()?;
96///
97/// println!("Service running at: {}", config.http_external);
098/// # Ok(())
99/// # }
100/// ```
···109 /// External hostname for service endpoints (e.g., "quickdid.example.com")
110 pub http_external: String,
111000112 /// HTTP User-Agent for outgoing requests (e.g., "quickdid/1.0.0 (+https://...)")
113 pub user_agent: String,
0000114115 /// Custom DNS nameservers, comma-separated (e.g., "8.8.8.8,8.8.4.4")
116 pub dns_nameservers: Option<String>,
···240 /// For example, 0.8 means refresh when an entry has lived for 80% of its TTL.
241 /// Default: 0.8 (80%)
242 pub proactive_refresh_threshold: f64,
243+244+ /// Directory path for serving static files.
245+ /// When set, the root handler will serve files from this directory.
246+ /// Default: "www" (relative to working directory)
247+ pub static_files_dir: String,
248}
249250impl Config {
···252 ///
253 /// This method:
254 /// 1. Reads configuration from environment variables
255+ /// 2. Validates required fields (HTTP_EXTERNAL)
256+ /// 3. Applies defaults where appropriate
0257 ///
258 /// ## Example
259 ///
···264 /// // Parse from environment variables
265 /// let config = Config::from_env()?;
266 ///
00267 /// # Ok(())
268 /// # }
269 /// ```
···272 ///
273 /// Returns `ConfigError::MissingRequired` if:
274 /// - HTTP_EXTERNAL is not provided
0275 pub fn from_env() -> Result<Self, ConfigError> {
276 // Required fields
277 let http_external = env::var("HTTP_EXTERNAL")
···279 .filter(|s| !s.is_empty())
280 .ok_or_else(|| ConfigError::MissingRequired("HTTP_EXTERNAL".to_string()))?;
28100000282 // Generate default user agent
283 let default_user_agent = format!(
284 "quickdid/{} (+https://github.com/smokesignal.events/quickdid)",
285 env!("CARGO_PKG_VERSION")
286 );
28700000000288 let mut config = Config {
289 http_port: get_env_or_default("HTTP_PORT", Some("8080")).unwrap(),
290 plc_hostname: get_env_or_default("PLC_HOSTNAME", Some("plc.directory")).unwrap(),
291 http_external,
0292 user_agent: get_env_or_default("USER_AGENT", None).unwrap_or(default_user_agent),
0293 dns_nameservers: get_env_or_default("DNS_NAMESERVERS", None),
294 certificate_bundles: get_env_or_default("CERTIFICATE_BUNDLES", None),
295 redis_url: get_env_or_default("REDIS_URL", None),
···326 metrics_tags: get_env_or_default("METRICS_TAGS", None),
327 proactive_refresh_enabled: parse_env("PROACTIVE_REFRESH_ENABLED", false)?,
328 proactive_refresh_threshold: parse_env("PROACTIVE_REFRESH_THRESHOLD", 0.8)?,
329+ static_files_dir: get_env_or_default("STATIC_FILES_DIR", Some("www")).unwrap(),
330 };
331332 // Calculate the Cache-Control header value if enabled
···1+# QuickDID Static Files Directory
2+3+This directory contains static files that are served by QuickDID. By default, QuickDID serves files from the `www` directory, but this can be configured using the `STATIC_FILES_DIR` environment variable.
4+5+## Directory Structure
6+7+```
8+www/
9+├── .well-known/
10+│ ├── atproto-did # AT Protocol DID identifier
11+│ └── did.json # DID document
12+├── index.html # Landing page
13+└── README.md # This file
14+```
15+16+## Files
17+18+### `.well-known/atproto-did`
19+Contains the service's DID identifier (e.g., `did:web:example.com`). This file is used by AT Protocol clients to discover the service's DID.
20+21+### `.well-known/did.json`
22+Contains the DID document with verification methods and service endpoints. This is a JSON-LD document following the W3C DID specification.
23+24+### `index.html`
25+The landing page shown when users visit the root URL. This provides information about the service and available endpoints.
26+27+## Customization
28+29+### Using the Generation Script
30+31+You can generate the `.well-known` files for your deployment using the provided script:
32+33+```bash
34+HTTP_EXTERNAL=your-domain.com ./generate-wellknown.sh
35+```
36+37+This will create the appropriate files based on your domain.
38+39+### Manual Customization
40+41+1. **Update `.well-known/atproto-did`**: Replace with your service's DID
42+2. **Update `.well-known/did.json`**: Add your public key to the `verificationMethod` array if needed
43+3. **Customize `index.html`**: Modify the landing page to match your branding
44+45+### Docker Deployment
46+47+When using Docker, you can mount custom static files:
48+49+```yaml
50+volumes:
51+ - ./custom-www:/app/www:ro
52+```
53+54+Or just override specific files:
55+56+```yaml
57+volumes:
58+ - ./custom-index.html:/app/www/index.html:ro
59+ - ./custom-wellknown:/app/www/.well-known:ro
60+```
61+62+### Environment Variable
63+64+You can change the static files directory using:
65+66+```bash
67+STATIC_FILES_DIR=/path/to/custom/www
68+```
69+70+## Security Notes
71+72+- Static files are served with automatic MIME type detection
73+- The `.well-known` files are crucial for AT Protocol compatibility
74+- Ensure proper permissions on mounted volumes in production