- 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?;
889 lines
21 KiB
Markdown
889 lines
21 KiB
Markdown
# 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.
|