Add SelfFreezoneClient wrapper for Self components
- Created SelfFreezoneClient in Self components
- Wraps SDK FreezoneScriptClient for Self-specific operations
- Implements send_verification_email method
- Uses Rhai script template for email verification
- Includes template variable substitution
- Added serde-wasm-bindgen dependency
Usage:
let client = SelfFreezoneClient::builder()
.supervisor_url("http://localhost:8080")
.secret("my-secret")
.build()?;
client.send_verification_email(
"user@example.com",
"123456",
"https://verify.com/abc"
).await?;
This commit is contained in:
888
docs/deployment.md
Normal file
888
docs/deployment.md
Normal file
@@ -0,0 +1,888 @@
|
||||
# Production Deployment Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers deploying Self in production environments, including infrastructure setup, security hardening, monitoring, and maintenance procedures.
|
||||
|
||||
## Infrastructure Requirements
|
||||
|
||||
### Minimum System Requirements
|
||||
|
||||
#### Server Requirements
|
||||
- **CPU**: 2 vCPUs (4+ recommended)
|
||||
- **Memory**: 2GB RAM (4GB+ recommended)
|
||||
- **Storage**: 20GB SSD (50GB+ recommended)
|
||||
- **Network**: 1Gbps connection
|
||||
- **OS**: Ubuntu 20.04 LTS or newer
|
||||
|
||||
#### Database Requirements (Production)
|
||||
- **PostgreSQL**: 12+ or MySQL 8.0+
|
||||
- **Memory**: 4GB RAM dedicated
|
||||
- **Storage**: 100GB+ SSD with backup
|
||||
- **Connections**: 100+ concurrent connections
|
||||
|
||||
#### Load Balancer (High Availability)
|
||||
- **Nginx**: 1.18+ or HAProxy 2.0+
|
||||
- **SSL Termination**: TLS 1.3 support
|
||||
- **Health Checks**: HTTP/HTTPS monitoring
|
||||
- **Rate Limiting**: Request throttling
|
||||
|
||||
### Cloud Deployment Options
|
||||
|
||||
#### AWS Deployment
|
||||
```yaml
|
||||
# docker-compose.aws.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
self-server:
|
||||
image: self-identity:latest
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://user:pass@rds-endpoint/selfdb
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- SMTP_HOST=email-smtp.us-east-1.amazonaws.com
|
||||
deploy:
|
||||
replicas: 3
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
reservations:
|
||||
memory: 512M
|
||||
```
|
||||
|
||||
#### Google Cloud Platform
|
||||
```yaml
|
||||
# gcp-deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: self-identity
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: self-identity
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: self-identity
|
||||
spec:
|
||||
containers:
|
||||
- name: self-server
|
||||
image: gcr.io/project-id/self-identity:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: db-secret
|
||||
key: url
|
||||
```
|
||||
|
||||
#### Azure Container Instances
|
||||
```yaml
|
||||
# azure-container.yaml
|
||||
apiVersion: 2019-12-01
|
||||
location: eastus
|
||||
name: self-identity
|
||||
properties:
|
||||
containers:
|
||||
- name: self-server
|
||||
properties:
|
||||
image: selfidentity.azurecr.io/self-identity:latest
|
||||
resources:
|
||||
requests:
|
||||
cpu: 1
|
||||
memoryInGb: 2
|
||||
ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
environmentVariables:
|
||||
- name: DATABASE_URL
|
||||
secureValue: postgresql://...
|
||||
```
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
### Production Dockerfile
|
||||
|
||||
```dockerfile
|
||||
# Multi-stage build for optimized production image
|
||||
FROM rust:1.75 as builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY server/ ./server/
|
||||
COPY components/ ./components/
|
||||
|
||||
# Build optimized release binary
|
||||
RUN cargo build --release --bin server
|
||||
|
||||
# Runtime image
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
libssl3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -r -s /bin/false selfuser
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/target/release/server ./
|
||||
COPY --chown=selfuser:selfuser static/ ./static/
|
||||
|
||||
USER selfuser
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/health || exit 1
|
||||
|
||||
CMD ["./server"]
|
||||
```
|
||||
|
||||
### Docker Compose Production Setup
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
self-server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.prod
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://selfuser:${DB_PASSWORD}@postgres:5432/selfdb
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- SMTP_HOST=${SMTP_HOST}
|
||||
- SMTP_USERNAME=${SMTP_USERNAME}
|
||||
- SMTP_PASSWORD=${SMTP_PASSWORD}
|
||||
- RUST_LOG=info
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- self-network
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=selfdb
|
||||
- POSTGRES_USER=selfuser
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
networks:
|
||||
- self-network
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- self-network
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./ssl:/etc/nginx/ssl
|
||||
- ./static:/usr/share/nginx/html
|
||||
depends_on:
|
||||
- self-server
|
||||
networks:
|
||||
- self-network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
|
||||
networks:
|
||||
self-network:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
## Database Setup
|
||||
|
||||
### PostgreSQL Schema
|
||||
|
||||
```sql
|
||||
-- init.sql
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Users table
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
public_key TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
is_active BOOLEAN DEFAULT TRUE
|
||||
);
|
||||
|
||||
-- Email verifications table
|
||||
CREATE TABLE email_verifications (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
email VARCHAR(255) NOT NULL,
|
||||
token VARCHAR(255) UNIQUE NOT NULL,
|
||||
verified BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
expires_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '24 hours'
|
||||
);
|
||||
|
||||
-- Authentication sessions table
|
||||
CREATE TABLE auth_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash VARCHAR(255) NOT NULL,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
last_used TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Audit log table
|
||||
CREATE TABLE audit_logs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
action VARCHAR(100) NOT NULL,
|
||||
resource VARCHAR(100),
|
||||
details JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
CREATE INDEX idx_users_public_key ON users(public_key);
|
||||
CREATE INDEX idx_email_verifications_token ON email_verifications(token);
|
||||
CREATE INDEX idx_auth_sessions_token_hash ON auth_sessions(token_hash);
|
||||
CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
|
||||
-- Update trigger for updated_at
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
```
|
||||
|
||||
### Database Migration System
|
||||
|
||||
```rust
|
||||
// migrations/mod.rs
|
||||
use sqlx::{PgPool, migrate::MigrateDatabase};
|
||||
|
||||
pub async fn run_migrations(database_url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create database if it doesn't exist
|
||||
if !sqlx::Postgres::database_exists(database_url).await? {
|
||||
sqlx::Postgres::create_database(database_url).await?;
|
||||
}
|
||||
|
||||
let pool = PgPool::connect(database_url).await?;
|
||||
|
||||
// Run migrations
|
||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Nginx Configuration
|
||||
|
||||
### Production Nginx Config
|
||||
|
||||
```nginx
|
||||
# nginx.conf
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
# Rate limiting
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/s;
|
||||
|
||||
# Upstream servers
|
||||
upstream self_backend {
|
||||
server self-server:8080;
|
||||
# Add more servers for load balancing
|
||||
# server self-server-2:8080;
|
||||
# server self-server-3:8080;
|
||||
}
|
||||
|
||||
# HTTP to HTTPS redirect
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your-domain.com;
|
||||
|
||||
# SSL configuration
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# Security headers
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'";
|
||||
|
||||
# Static files
|
||||
location /static/ {
|
||||
alias /usr/share/nginx/html/;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# API endpoints with rate limiting
|
||||
location /api/ {
|
||||
limit_req zone=api burst=20 nodelay;
|
||||
proxy_pass http://self_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# OAuth endpoints with stricter rate limiting
|
||||
location /oauth/ {
|
||||
limit_req zone=auth burst=10 nodelay;
|
||||
proxy_pass http://self_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
proxy_pass http://self_backend;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Default location
|
||||
location / {
|
||||
proxy_pass http://self_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## SSL/TLS Configuration
|
||||
|
||||
### Let's Encrypt Setup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# setup-ssl.sh
|
||||
|
||||
# Install certbot
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y certbot python3-certbot-nginx
|
||||
|
||||
# Obtain certificate
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
|
||||
# Setup auto-renewal
|
||||
echo "0 12 * * * /usr/bin/certbot renew --quiet" | sudo crontab -
|
||||
```
|
||||
|
||||
### Manual Certificate Setup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# manual-ssl.sh
|
||||
|
||||
# Generate private key
|
||||
openssl genrsa -out key.pem 2048
|
||||
|
||||
# Generate certificate signing request
|
||||
openssl req -new -key key.pem -out cert.csr
|
||||
|
||||
# Generate self-signed certificate (for testing)
|
||||
openssl x509 -req -days 365 -in cert.csr -signkey key.pem -out cert.pem
|
||||
|
||||
# Set proper permissions
|
||||
chmod 600 key.pem
|
||||
chmod 644 cert.pem
|
||||
```
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### Production Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.production
|
||||
# Database
|
||||
DATABASE_URL=postgresql://selfuser:secure_password@localhost:5432/selfdb
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=your-super-secure-jwt-secret-key-here
|
||||
JWT_EXPIRATION=3600
|
||||
|
||||
# SMTP Configuration
|
||||
SMTP_HOST=smtp.your-provider.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USERNAME=your-smtp-username
|
||||
SMTP_PASSWORD=your-smtp-password
|
||||
SMTP_FROM=noreply@your-domain.com
|
||||
|
||||
# Server Configuration
|
||||
SERVER_PORT=8080
|
||||
SERVER_HOST=0.0.0.0
|
||||
BASE_URL=https://your-domain.com
|
||||
|
||||
# Redis Configuration (for sessions)
|
||||
REDIS_URL=redis://localhost:6379
|
||||
REDIS_PASSWORD=your-redis-password
|
||||
|
||||
# Logging
|
||||
RUST_LOG=info
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Security
|
||||
CORS_ORIGINS=https://your-frontend-domain.com
|
||||
RATE_LIMIT_REQUESTS=100
|
||||
RATE_LIMIT_WINDOW=60
|
||||
|
||||
# Monitoring
|
||||
METRICS_ENABLED=true
|
||||
HEALTH_CHECK_ENABLED=true
|
||||
```
|
||||
|
||||
### Configuration Management
|
||||
|
||||
```rust
|
||||
// config.rs
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub database_url: String,
|
||||
pub jwt_secret: String,
|
||||
pub jwt_expiration: u64,
|
||||
pub smtp: SmtpConfig,
|
||||
pub server: ServerConfig,
|
||||
pub redis_url: Option<String>,
|
||||
pub cors_origins: Vec<String>,
|
||||
pub rate_limit: RateLimitConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SmtpConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub from: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RateLimitConfig {
|
||||
pub requests: u32,
|
||||
pub window: u64,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_env() -> Result<Self, config::ConfigError> {
|
||||
let mut cfg = config::Config::builder();
|
||||
|
||||
// Load from environment variables
|
||||
cfg = cfg.add_source(config::Environment::with_prefix("SELF"));
|
||||
|
||||
// Load from config file if exists
|
||||
if let Ok(config_path) = env::var("CONFIG_PATH") {
|
||||
cfg = cfg.add_source(config::File::with_name(&config_path));
|
||||
}
|
||||
|
||||
cfg.build()?.try_deserialize()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring and Logging
|
||||
|
||||
### Prometheus Metrics
|
||||
|
||||
```rust
|
||||
// metrics.rs
|
||||
use prometheus::{Counter, Histogram, Gauge, Registry};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Metrics {
|
||||
pub registry: Registry,
|
||||
pub http_requests_total: Counter,
|
||||
pub http_request_duration: Histogram,
|
||||
pub active_connections: Gauge,
|
||||
pub auth_attempts_total: Counter,
|
||||
pub auth_failures_total: Counter,
|
||||
}
|
||||
|
||||
impl Metrics {
|
||||
pub fn new() -> Arc<Self> {
|
||||
let registry = Registry::new();
|
||||
|
||||
let http_requests_total = Counter::new(
|
||||
"http_requests_total",
|
||||
"Total HTTP requests"
|
||||
).unwrap();
|
||||
|
||||
let http_request_duration = Histogram::new(
|
||||
"http_request_duration_seconds",
|
||||
"HTTP request duration"
|
||||
).unwrap();
|
||||
|
||||
let active_connections = Gauge::new(
|
||||
"active_connections",
|
||||
"Active connections"
|
||||
).unwrap();
|
||||
|
||||
let auth_attempts_total = Counter::new(
|
||||
"auth_attempts_total",
|
||||
"Total authentication attempts"
|
||||
).unwrap();
|
||||
|
||||
let auth_failures_total = Counter::new(
|
||||
"auth_failures_total",
|
||||
"Total authentication failures"
|
||||
).unwrap();
|
||||
|
||||
registry.register(Box::new(http_requests_total.clone())).unwrap();
|
||||
registry.register(Box::new(http_request_duration.clone())).unwrap();
|
||||
registry.register(Box::new(active_connections.clone())).unwrap();
|
||||
registry.register(Box::new(auth_attempts_total.clone())).unwrap();
|
||||
registry.register(Box::new(auth_failures_total.clone())).unwrap();
|
||||
|
||||
Arc::new(Metrics {
|
||||
registry,
|
||||
http_requests_total,
|
||||
http_request_duration,
|
||||
active_connections,
|
||||
auth_attempts_total,
|
||||
auth_failures_total,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Structured Logging
|
||||
|
||||
```rust
|
||||
// logging.rs
|
||||
use tracing::{info, warn, error};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
pub fn init_logging() {
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::new(
|
||||
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
|
||||
))
|
||||
.with(tracing_subscriber::fmt::layer().json())
|
||||
.init();
|
||||
}
|
||||
|
||||
pub fn log_security_event(event_type: &str, details: serde_json::Value) {
|
||||
info!(
|
||||
event_type = event_type,
|
||||
details = %details,
|
||||
timestamp = %chrono::Utc::now(),
|
||||
"Security event logged"
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Backup and Recovery
|
||||
|
||||
### Database Backup Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup-db.sh
|
||||
|
||||
set -e
|
||||
|
||||
DB_NAME="selfdb"
|
||||
DB_USER="selfuser"
|
||||
BACKUP_DIR="/backups"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/selfdb_backup_$DATE.sql"
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Create database backup
|
||||
pg_dump -h localhost -U $DB_USER -d $DB_NAME > $BACKUP_FILE
|
||||
|
||||
# Compress backup
|
||||
gzip $BACKUP_FILE
|
||||
|
||||
# Upload to cloud storage (AWS S3 example)
|
||||
aws s3 cp $BACKUP_FILE.gz s3://your-backup-bucket/database/
|
||||
|
||||
# Clean up old backups (keep last 30 days)
|
||||
find $BACKUP_DIR -name "selfdb_backup_*.sql.gz" -mtime +30 -delete
|
||||
|
||||
echo "Backup completed: $BACKUP_FILE.gz"
|
||||
```
|
||||
|
||||
### Automated Backup Cron Job
|
||||
|
||||
```bash
|
||||
# Add to crontab: crontab -e
|
||||
# Run backup every day at 2 AM
|
||||
0 2 * * * /path/to/backup-db.sh >> /var/log/backup.log 2>&1
|
||||
```
|
||||
|
||||
## Health Checks and Monitoring
|
||||
|
||||
### Health Check Endpoint
|
||||
|
||||
```rust
|
||||
// health.rs
|
||||
use axum::{Json, response::IntoResponse};
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub async fn health_check(pool: &PgPool) -> impl IntoResponse {
|
||||
let mut status = "healthy";
|
||||
let mut checks = serde_json::Map::new();
|
||||
|
||||
// Database health check
|
||||
match sqlx::query("SELECT 1").fetch_one(pool).await {
|
||||
Ok(_) => {
|
||||
checks.insert("database".to_string(), json!("healthy"));
|
||||
}
|
||||
Err(_) => {
|
||||
status = "unhealthy";
|
||||
checks.insert("database".to_string(), json!("unhealthy"));
|
||||
}
|
||||
}
|
||||
|
||||
// Memory usage check
|
||||
let memory_usage = get_memory_usage();
|
||||
if memory_usage < 90.0 {
|
||||
checks.insert("memory".to_string(), json!("healthy"));
|
||||
} else {
|
||||
status = "degraded";
|
||||
checks.insert("memory".to_string(), json!("high"));
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
"status": status,
|
||||
"timestamp": chrono::Utc::now(),
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
"checks": checks
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
### Monitoring Dashboard
|
||||
|
||||
```yaml
|
||||
# docker-compose.monitoring.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards
|
||||
- ./grafana/datasources:/etc/grafana/provisioning/datasources
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
```
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### Server Hardening Checklist
|
||||
|
||||
1. **System Updates**
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install unattended-upgrades
|
||||
```
|
||||
|
||||
2. **Firewall Configuration**
|
||||
```bash
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow ssh
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
3. **SSH Hardening**
|
||||
```bash
|
||||
# /etc/ssh/sshd_config
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
Port 2222 # Change default port
|
||||
```
|
||||
|
||||
4. **Fail2Ban Setup**
|
||||
```bash
|
||||
sudo apt install fail2ban
|
||||
sudo systemctl enable fail2ban
|
||||
sudo systemctl start fail2ban
|
||||
```
|
||||
|
||||
### Application Security
|
||||
|
||||
```rust
|
||||
// security middleware
|
||||
use axum::{
|
||||
middleware::{self, Next},
|
||||
http::{Request, HeaderMap, HeaderValue},
|
||||
response::Response,
|
||||
};
|
||||
|
||||
pub async fn security_headers<B>(
|
||||
request: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Response {
|
||||
let mut response = next.run(request).await;
|
||||
|
||||
let headers = response.headers_mut();
|
||||
headers.insert("X-Frame-Options", HeaderValue::from_static("DENY"));
|
||||
headers.insert("X-Content-Type-Options", HeaderValue::from_static("nosniff"));
|
||||
headers.insert("X-XSS-Protection", HeaderValue::from_static("1; mode=block"));
|
||||
headers.insert(
|
||||
"Strict-Transport-Security",
|
||||
HeaderValue::from_static("max-age=31536000; includeSubDomains")
|
||||
);
|
||||
|
||||
response
|
||||
}
|
||||
```
|
||||
|
||||
## Deployment Scripts
|
||||
|
||||
### Deployment Automation
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# deploy.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting deployment..."
|
||||
|
||||
# Pull latest code
|
||||
git pull origin main
|
||||
|
||||
# Build Docker image
|
||||
docker build -t self-identity:latest .
|
||||
|
||||
# Run database migrations
|
||||
docker-compose exec postgres psql -U selfuser -d selfdb -f /migrations/latest.sql
|
||||
|
||||
# Update services with zero downtime
|
||||
docker-compose up -d --no-deps self-server
|
||||
|
||||
# Wait for health check
|
||||
echo "Waiting for service to be healthy..."
|
||||
for i in {1..30}; do
|
||||
if curl -f http://localhost:8080/health; then
|
||||
echo "Service is healthy!"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Clean up old images
|
||||
docker image prune -f
|
||||
|
||||
echo "Deployment completed successfully!"
|
||||
```
|
||||
|
||||
### Rollback Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# rollback.sh
|
||||
|
||||
set -e
|
||||
|
||||
PREVIOUS_VERSION=${1:-"previous"}
|
||||
|
||||
echo "Rolling back to version: $PREVIOUS_VERSION"
|
||||
|
||||
# Pull previous image
|
||||
docker pull self-identity:$PREVIOUS_VERSION
|
||||
|
||||
# Update docker-compose to use previous version
|
||||
sed -i "s/self-identity:latest/self-identity:$PREVIOUS_VERSION/g" docker-compose.yml
|
||||
|
||||
# Restart services
|
||||
docker-compose up -d --no-deps self-server
|
||||
|
||||
echo "Rollback completed!"
|
||||
```
|
||||
|
||||
This comprehensive deployment guide covers all aspects of running Self in production, from infrastructure setup to monitoring and security. The configuration is designed for scalability, security, and maintainability.
|
||||
Reference in New Issue
Block a user