- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1175 lines
25 KiB
Markdown
1175 lines
25 KiB
Markdown
# Phase 2 Infrastructure Plan
|
|
|
|
**Project**: Tractatus AI Safety Framework Website
|
|
**Phase**: 2 of 3
|
|
**Created**: 2025-10-07
|
|
**Owner**: John Stroh
|
|
**Status**: Planning
|
|
**Target Deployment**: TBD (awaiting Phase 2 approval)
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture Overview](#architecture-overview)
|
|
2. [Server Specifications](#server-specifications)
|
|
3. [Network Architecture](#network-architecture)
|
|
4. [Deployment Procedures](#deployment-procedures)
|
|
5. [Security Hardening](#security-hardening)
|
|
6. [Monitoring & Alerting](#monitoring--alerting)
|
|
7. [Backup & Disaster Recovery](#backup--disaster-recovery)
|
|
8. [DNS & Domain Configuration](#dns--domain-configuration)
|
|
9. [SSL/TLS Configuration](#ssltls-configuration)
|
|
10. [Environment Configuration](#environment-configuration)
|
|
11. [Deployment Checklist](#deployment-checklist)
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Internet │
|
|
└────────────────────┬────────────────────────────────────────┘
|
|
│
|
|
┌────────▼────────┐
|
|
│ Cloudflare │ (Optional CDN + DDoS protection)
|
|
│ DNS + Proxy │
|
|
└────────┬────────┘
|
|
│
|
|
┌────────▼────────┐
|
|
│ OVHCloud VPS │ (Ubuntu 22.04 LTS)
|
|
│ agenticgovernance.digital │
|
|
└────────┬────────┘
|
|
│
|
|
┌────────────┴────────────┐
|
|
│ │
|
|
┌────▼─────┐ ┌──────▼──────┐
|
|
│ Nginx │ │ Fail2ban │
|
|
│ :80/443 │ │ Firewall │
|
|
└────┬─────┘ └─────────────┘
|
|
│
|
|
┌────▼─────┐
|
|
│ Node.js │
|
|
│ Express │
|
|
│ :9000 │
|
|
└────┬─────┘
|
|
│
|
|
┌────▼─────┐
|
|
│ MongoDB │
|
|
│ :27017 │
|
|
└──────────┘
|
|
```
|
|
|
|
### Component Stack
|
|
|
|
| Layer | Component | Version | Purpose |
|
|
|-------|-----------|---------|---------|
|
|
| **CDN** | Cloudflare | Free tier | DDoS protection, CDN (optional) |
|
|
| **DNS** | Cloudflare/OVH | - | Domain nameservers |
|
|
| **Firewall** | UFW + Fail2ban | Latest | Perimeter security |
|
|
| **Web Server** | Nginx | 1.24+ | Reverse proxy, SSL termination |
|
|
| **Application** | Node.js + Express | 18 LTS + 4.x | Tractatus platform |
|
|
| **Database** | MongoDB | 7.x | Document storage |
|
|
| **Process Manager** | systemd | System default | Service management |
|
|
| **SSL/TLS** | Let's Encrypt | Latest | HTTPS certificates |
|
|
| **Email** | ProtonBridge | Latest | SMTP gateway |
|
|
| **Analytics** | Plausible (self-hosted) | Latest | Privacy-respecting analytics |
|
|
| **Monitoring** | Self-hosted scripts | Custom | Uptime, performance |
|
|
|
|
---
|
|
|
|
## Server Specifications
|
|
|
|
### Recommended Configuration (OVHCloud VPS Essential)
|
|
|
|
**Compute**:
|
|
- vCores: 2 (Intel Xeon or AMD EPYC)
|
|
- RAM: 4GB DDR4
|
|
- Storage: 80GB SSD NVMe
|
|
- Architecture: x86_64
|
|
|
|
**Network**:
|
|
- Bandwidth: 500 Mbps
|
|
- Traffic: Unlimited
|
|
- IPv4: 1 dedicated
|
|
- IPv6: 1 dedicated (/64 subnet)
|
|
- Anti-DDoS: Included
|
|
|
|
**Operating System**:
|
|
- Distribution: Ubuntu 22.04 LTS (Jammy Jellyfish)
|
|
- Kernel: 5.15+ (HWE)
|
|
- Init system: systemd
|
|
|
|
**Geographic Location**:
|
|
- Preferred: Singapore or Australia (closest to NZ)
|
|
- Alternative: Europe (if latency acceptable)
|
|
- Avoid: US East/West (regulatory, latency)
|
|
|
|
**Cost**: ~$20-30/month USD
|
|
|
|
---
|
|
|
|
### Server Sizing Rationale
|
|
|
|
**Memory Allocation**:
|
|
- MongoDB: ~1.5GB (production database + WiredTiger cache)
|
|
- Node.js: ~1GB (application + dependencies)
|
|
- Nginx: ~100MB (minimal footprint)
|
|
- System: ~500MB (OS, utilities)
|
|
- Plausible Analytics: ~500MB (if self-hosted)
|
|
- Buffer: ~400MB (peak load)
|
|
- **Total**: ~4GB (fits VPS Essential)
|
|
|
|
**Storage Allocation**:
|
|
- OS + System: ~10GB
|
|
- MongoDB data: ~20GB (estimated Year 1)
|
|
- Application code: ~2GB (node_modules)
|
|
- Logs: ~5GB (1-year retention with rotation)
|
|
- Backups (on-server): ~20GB (7-day retention)
|
|
- Free space: ~23GB (buffer)
|
|
- **Total**: ~80GB (VPS Essential)
|
|
|
|
**CPU Usage**:
|
|
- Typical: <20% (2 vCores)
|
|
- Peak: <60% (during deployments, backups)
|
|
- Acceptable: 2 vCores sufficient for Phase 2 traffic
|
|
|
|
---
|
|
|
|
## Network Architecture
|
|
|
|
### Firewall Rules (UFW)
|
|
|
|
**Allow**:
|
|
```bash
|
|
# SSH (restricted to specific IPs in production)
|
|
ufw allow from <admin_ip> to any port 22 proto tcp comment 'SSH Admin'
|
|
|
|
# HTTP (redirect to HTTPS)
|
|
ufw allow 80/tcp comment 'HTTP'
|
|
|
|
# HTTPS
|
|
ufw allow 443/tcp comment 'HTTPS'
|
|
|
|
# MongoDB (localhost only, no external access)
|
|
ufw allow from 127.0.0.1 to any port 27017 proto tcp comment 'MongoDB local'
|
|
```
|
|
|
|
**Deny** (default):
|
|
```bash
|
|
# Default deny incoming
|
|
ufw default deny incoming
|
|
|
|
# Default allow outgoing (for apt, npm, git)
|
|
ufw default allow outgoing
|
|
```
|
|
|
|
**Activate**:
|
|
```bash
|
|
ufw enable
|
|
ufw status verbose
|
|
```
|
|
|
|
---
|
|
|
|
### Port Configuration
|
|
|
|
| Service | Port | Bind Address | Firewall | Notes |
|
|
|---------|------|--------------|----------|-------|
|
|
| **SSH** | 22 | 0.0.0.0 | Restricted IP | Key-only auth |
|
|
| **HTTP** | 80 | 0.0.0.0 | Allow | Redirect to 443 |
|
|
| **HTTPS** | 443 | 0.0.0.0 | Allow | Nginx reverse proxy |
|
|
| **Node.js** | 9000 | 127.0.0.1 | Deny | Internal only |
|
|
| **MongoDB** | 27017 | 127.0.0.1 | Deny | Internal only |
|
|
|
|
---
|
|
|
|
## Deployment Procedures
|
|
|
|
### Initial Server Setup
|
|
|
|
#### 1. Provision VPS
|
|
|
|
**OVHCloud Control Panel**:
|
|
1. Select VPS Essential tier
|
|
2. Choose Ubuntu 22.04 LTS
|
|
3. Select geographic region (Singapore/Australia)
|
|
4. Generate root password (save securely)
|
|
5. Provision (5-10 minutes)
|
|
|
|
**Verify Access**:
|
|
```bash
|
|
ssh root@<server_ip>
|
|
```
|
|
|
|
---
|
|
|
|
#### 2. Create Non-Root User
|
|
|
|
```bash
|
|
# Create deploy user
|
|
adduser tractatus
|
|
usermod -aG sudo tractatus
|
|
|
|
# Set up SSH key auth
|
|
mkdir -p /home/tractatus/.ssh
|
|
chmod 700 /home/tractatus/.ssh
|
|
```
|
|
|
|
**On local machine**:
|
|
```bash
|
|
# Generate SSH key (if not exists)
|
|
ssh-keygen -t ed25519 -C "tractatus-deploy"
|
|
|
|
# Copy public key to server
|
|
ssh-copy-id tractatus@<server_ip>
|
|
```
|
|
|
|
**Test**:
|
|
```bash
|
|
ssh tractatus@<server_ip>
|
|
```
|
|
|
|
---
|
|
|
|
#### 3. Harden SSH
|
|
|
|
```bash
|
|
# Edit SSH config
|
|
sudo nano /etc/ssh/sshd_config
|
|
```
|
|
|
|
**Changes**:
|
|
```
|
|
PermitRootLogin no
|
|
PasswordAuthentication no
|
|
PubkeyAuthentication yes
|
|
Port 22 # Consider changing to non-standard port (2222)
|
|
AllowUsers tractatus
|
|
```
|
|
|
|
**Restart SSH**:
|
|
```bash
|
|
sudo systemctl restart sshd
|
|
```
|
|
|
|
---
|
|
|
|
#### 4. System Updates
|
|
|
|
```bash
|
|
# Update package lists
|
|
sudo apt update
|
|
|
|
# Upgrade all packages
|
|
sudo apt upgrade -y
|
|
|
|
# Install essential tools
|
|
sudo apt install -y \
|
|
curl \
|
|
wget \
|
|
git \
|
|
ufw \
|
|
fail2ban \
|
|
htop \
|
|
vim \
|
|
certbot \
|
|
python3-certbot-nginx
|
|
```
|
|
|
|
---
|
|
|
|
#### 5. Configure Firewall
|
|
|
|
```bash
|
|
# Allow SSH (before enabling UFW!)
|
|
sudo ufw allow from <your_ip> to any port 22 proto tcp
|
|
|
|
# Allow HTTP/HTTPS
|
|
sudo ufw allow 80/tcp
|
|
sudo ufw allow 443/tcp
|
|
|
|
# Enable firewall
|
|
sudo ufw enable
|
|
sudo ufw status verbose
|
|
```
|
|
|
|
---
|
|
|
|
### Install Application Stack
|
|
|
|
#### 1. Install Node.js
|
|
|
|
```bash
|
|
# Add NodeSource repository (Node 18 LTS)
|
|
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
|
|
|
# Install Node.js
|
|
sudo apt install -y nodejs
|
|
|
|
# Verify
|
|
node --version # v18.x.x
|
|
npm --version # 9.x.x
|
|
```
|
|
|
|
---
|
|
|
|
#### 2. Install MongoDB
|
|
|
|
```bash
|
|
# Import MongoDB public GPG key
|
|
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
|
|
sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/mongodb-server-7.0.gpg
|
|
|
|
# Add MongoDB repository
|
|
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | \
|
|
sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
|
|
|
|
# Update and install
|
|
sudo apt update
|
|
sudo apt install -y mongodb-org
|
|
|
|
# Start MongoDB
|
|
sudo systemctl start mongod
|
|
sudo systemctl enable mongod
|
|
|
|
# Verify
|
|
sudo systemctl status mongod
|
|
mongosh --eval 'db.version()' # 7.0.x
|
|
```
|
|
|
|
---
|
|
|
|
#### 3. Install Nginx
|
|
|
|
```bash
|
|
# Install Nginx
|
|
sudo apt install -y nginx
|
|
|
|
# Start Nginx
|
|
sudo systemctl start nginx
|
|
sudo systemctl enable nginx
|
|
|
|
# Verify
|
|
sudo systemctl status nginx
|
|
nginx -v # nginx/1.24.x
|
|
```
|
|
|
|
---
|
|
|
|
### Deploy Application
|
|
|
|
#### 1. Clone Repository
|
|
|
|
```bash
|
|
# Create application directory
|
|
sudo mkdir -p /var/www/tractatus
|
|
sudo chown tractatus:tractatus /var/www/tractatus
|
|
|
|
# Clone repository
|
|
cd /var/www/tractatus
|
|
git clone https://github.com/your-org/tractatus.git .
|
|
|
|
# Install dependencies
|
|
npm install --production
|
|
```
|
|
|
|
---
|
|
|
|
#### 2. Configure Environment
|
|
|
|
```bash
|
|
# Create production environment file
|
|
cp .env.example .env.production
|
|
|
|
# Edit configuration
|
|
nano .env.production
|
|
```
|
|
|
|
**Production Environment Variables**:
|
|
```bash
|
|
# Application
|
|
NODE_ENV=production
|
|
PORT=9000
|
|
APP_NAME=Tractatus
|
|
|
|
# MongoDB
|
|
MONGODB_URI=mongodb://localhost:27017/tractatus_prod
|
|
MONGODB_PORT=27017
|
|
|
|
# JWT
|
|
JWT_SECRET=<generate_secure_random_string_64_chars>
|
|
JWT_EXPIRY=7d
|
|
JWT_AUDIENCE=tractatus-admin
|
|
JWT_ISSUER=tractatus
|
|
|
|
# Claude API
|
|
CLAUDE_API_KEY=<anthropic_api_key>
|
|
CLAUDE_MODEL=claude-sonnet-4-5-20250929
|
|
CLAUDE_MAX_TOKENS=200000
|
|
|
|
# Email (ProtonBridge)
|
|
SMTP_HOST=127.0.0.1
|
|
SMTP_PORT=1025
|
|
SMTP_USER=contact@agenticgovernance.digital
|
|
SMTP_PASSWORD=<protonbridge_password>
|
|
SMTP_FROM=contact@agenticgovernance.digital
|
|
|
|
# Admin
|
|
ADMIN_EMAIL=john.stroh.nz@pm.me
|
|
|
|
# Logging
|
|
LOG_LEVEL=info
|
|
LOG_FILE=/var/log/tractatus/app.log
|
|
```
|
|
|
|
**Generate JWT Secret**:
|
|
```bash
|
|
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
|
|
```
|
|
|
|
---
|
|
|
|
#### 3. Database Initialization
|
|
|
|
```bash
|
|
# Create production database and admin user
|
|
mongosh tractatus_prod --eval "
|
|
db.createUser({
|
|
user: 'tractatus',
|
|
pwd: '<secure_password>',
|
|
roles: [{ role: 'readWrite', db: 'tractatus_prod' }]
|
|
})
|
|
"
|
|
|
|
# Run migration scripts
|
|
npm run init:db
|
|
|
|
# Seed admin user
|
|
npm run seed:admin
|
|
```
|
|
|
|
---
|
|
|
|
#### 4. Build Assets
|
|
|
|
```bash
|
|
# Build Tailwind CSS for production
|
|
npm run build:css
|
|
|
|
# Verify build
|
|
ls -lh public/css/tailwind.css # Should be ~24KB minified
|
|
```
|
|
|
|
---
|
|
|
|
#### 5. Create Systemd Service
|
|
|
|
```bash
|
|
# Create service file
|
|
sudo nano /etc/systemd/system/tractatus.service
|
|
```
|
|
|
|
**Service Configuration**:
|
|
```ini
|
|
[Unit]
|
|
Description=Tractatus AI Safety Framework
|
|
Documentation=https://agenticgovernance.digital/docs
|
|
After=network.target mongod.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=tractatus
|
|
WorkingDirectory=/var/www/tractatus
|
|
Environment=NODE_ENV=production
|
|
EnvironmentFile=/var/www/tractatus/.env.production
|
|
ExecStart=/usr/bin/node src/server.js
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=tractatus
|
|
|
|
# Security
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
**Enable and Start**:
|
|
```bash
|
|
# Reload systemd
|
|
sudo systemctl daemon-reload
|
|
|
|
# Enable service (start on boot)
|
|
sudo systemctl enable tractatus.service
|
|
|
|
# Start service
|
|
sudo systemctl start tractatus.service
|
|
|
|
# Verify
|
|
sudo systemctl status tractatus.service
|
|
journalctl -u tractatus.service -f
|
|
```
|
|
|
|
---
|
|
|
|
### Configure Nginx
|
|
|
|
#### 1. Create Nginx Configuration
|
|
|
|
```bash
|
|
# Create site configuration
|
|
sudo nano /etc/nginx/sites-available/tractatus
|
|
```
|
|
|
|
**Nginx Configuration**:
|
|
```nginx
|
|
# Upstream Node.js application
|
|
upstream tractatus_app {
|
|
server 127.0.0.1:9000;
|
|
keepalive 64;
|
|
}
|
|
|
|
# HTTP (redirect to HTTPS)
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name agenticgovernance.digital www.agenticgovernance.digital;
|
|
|
|
# Let's Encrypt verification
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/html;
|
|
}
|
|
|
|
# Redirect to HTTPS
|
|
location / {
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
}
|
|
|
|
# HTTPS
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name agenticgovernance.digital www.agenticgovernance.digital;
|
|
|
|
# SSL certificates (Let's Encrypt)
|
|
ssl_certificate /etc/letsencrypt/live/agenticgovernance.digital/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/agenticgovernance.digital/privkey.pem;
|
|
ssl_trusted_certificate /etc/letsencrypt/live/agenticgovernance.digital/chain.pem;
|
|
|
|
# SSL configuration (Mozilla Intermediate)
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
|
ssl_prefer_server_ciphers off;
|
|
|
|
# SSL session cache
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
|
|
# OCSP stapling
|
|
ssl_stapling on;
|
|
ssl_stapling_verify on;
|
|
resolver 1.1.1.1 1.0.0.1 valid=300s;
|
|
resolver_timeout 5s;
|
|
|
|
# Security headers
|
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
|
add_header X-Frame-Options "DENY" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
|
|
|
|
# Logging
|
|
access_log /var/log/nginx/tractatus-access.log;
|
|
error_log /var/log/nginx/tractatus-error.log;
|
|
|
|
# Root and index
|
|
root /var/www/tractatus/public;
|
|
index index.html;
|
|
|
|
# Static files (served directly by Nginx)
|
|
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# API and dynamic routes (proxy to Node.js)
|
|
location /api/ {
|
|
proxy_pass http://tractatus_app;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
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;
|
|
proxy_cache_bypass $http_upgrade;
|
|
proxy_read_timeout 60s;
|
|
}
|
|
|
|
# Try static files first, then proxy to Node.js
|
|
location / {
|
|
try_files $uri $uri/ @nodejs;
|
|
}
|
|
|
|
location @nodejs {
|
|
proxy_pass http://tractatus_app;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
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;
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
|
|
# Security: Deny access to sensitive files
|
|
location ~ /\. {
|
|
deny all;
|
|
}
|
|
|
|
location ~ /\.git {
|
|
deny all;
|
|
}
|
|
|
|
location ~ /node_modules {
|
|
deny all;
|
|
}
|
|
|
|
location ~ /\.env {
|
|
deny all;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Enable Site**:
|
|
```bash
|
|
# Create symlink
|
|
sudo ln -s /etc/nginx/sites-available/tractatus /etc/nginx/sites-enabled/
|
|
|
|
# Remove default site
|
|
sudo rm /etc/nginx/sites-enabled/default
|
|
|
|
# Test configuration
|
|
sudo nginx -t
|
|
|
|
# Reload Nginx
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
---
|
|
|
|
### SSL/TLS Setup (Let's Encrypt)
|
|
|
|
```bash
|
|
# Obtain SSL certificate
|
|
sudo certbot --nginx -d agenticgovernance.digital -d www.agenticgovernance.digital
|
|
|
|
# Follow prompts:
|
|
# - Enter email: john.stroh.nz@pm.me
|
|
# - Agree to terms
|
|
# - Redirect HTTP to HTTPS: Yes
|
|
|
|
# Verify auto-renewal
|
|
sudo certbot renew --dry-run
|
|
|
|
# Auto-renewal is configured via systemd timer
|
|
sudo systemctl list-timers | grep certbot
|
|
```
|
|
|
|
**Certificate Renewal** (automatic):
|
|
```bash
|
|
# Certbot creates a systemd timer for auto-renewal
|
|
# Certificates renew 30 days before expiry
|
|
# No manual intervention needed
|
|
```
|
|
|
|
---
|
|
|
|
## Security Hardening
|
|
|
|
### Fail2ban Configuration
|
|
|
|
```bash
|
|
# Install Fail2ban (if not already)
|
|
sudo apt install -y fail2ban
|
|
|
|
# Create local configuration
|
|
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
|
|
sudo nano /etc/fail2ban/jail.local
|
|
```
|
|
|
|
**Jail Configuration**:
|
|
```ini
|
|
[DEFAULT]
|
|
bantime = 3600 # 1 hour ban
|
|
findtime = 600 # 10 minutes
|
|
maxretry = 5 # 5 attempts before ban
|
|
|
|
[sshd]
|
|
enabled = true
|
|
port = 22
|
|
filter = sshd
|
|
logpath = /var/log/auth.log
|
|
|
|
[nginx-http-auth]
|
|
enabled = true
|
|
port = 80,443
|
|
filter = nginx-http-auth
|
|
logpath = /var/log/nginx/error.log
|
|
|
|
[nginx-limit-req]
|
|
enabled = true
|
|
port = 80,443
|
|
filter = nginx-limit-req
|
|
logpath = /var/log/nginx/error.log
|
|
maxretry = 10
|
|
```
|
|
|
|
**Restart**:
|
|
```bash
|
|
sudo systemctl restart fail2ban
|
|
sudo systemctl enable fail2ban
|
|
|
|
# Verify
|
|
sudo fail2ban-client status
|
|
```
|
|
|
|
---
|
|
|
|
### MongoDB Security
|
|
|
|
```bash
|
|
# Enable authentication
|
|
sudo nano /etc/mongod.conf
|
|
```
|
|
|
|
**Add**:
|
|
```yaml
|
|
security:
|
|
authorization: enabled
|
|
|
|
net:
|
|
bindIp: 127.0.0.1
|
|
port: 27017
|
|
```
|
|
|
|
**Restart MongoDB**:
|
|
```bash
|
|
sudo systemctl restart mongod
|
|
```
|
|
|
|
---
|
|
|
|
### Automatic Security Updates
|
|
|
|
```bash
|
|
# Install unattended-upgrades
|
|
sudo apt install -y unattended-upgrades
|
|
|
|
# Configure
|
|
sudo dpkg-reconfigure -plow unattended-upgrades
|
|
# Select "Yes" to enable
|
|
|
|
# Verify
|
|
sudo cat /etc/apt/apt.conf.d/20auto-upgrades
|
|
```
|
|
|
|
---
|
|
|
|
## Monitoring & Alerting
|
|
|
|
### Log Management
|
|
|
|
```bash
|
|
# Create log directories
|
|
sudo mkdir -p /var/log/tractatus
|
|
sudo chown tractatus:tractatus /var/log/tractatus
|
|
|
|
# Configure logrotate
|
|
sudo nano /etc/logrotate.d/tractatus
|
|
```
|
|
|
|
**Logrotate Configuration**:
|
|
```
|
|
/var/log/tractatus/*.log {
|
|
daily
|
|
rotate 7
|
|
compress
|
|
delaycompress
|
|
missingok
|
|
notifempty
|
|
create 0640 tractatus tractatus
|
|
sharedscripts
|
|
postrotate
|
|
systemctl reload tractatus >/dev/null 2>&1 || true
|
|
endscript
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Uptime Monitoring Script
|
|
|
|
```bash
|
|
# Create monitoring script
|
|
sudo nano /usr/local/bin/tractatus-healthcheck.sh
|
|
```
|
|
|
|
**Script**:
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
# Healthcheck endpoint
|
|
URL="https://agenticgovernance.digital/health"
|
|
|
|
# Check if site is up
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $URL)
|
|
|
|
if [ "$HTTP_CODE" != "200" ]; then
|
|
# Alert (email to admin)
|
|
echo "Tractatus is DOWN! HTTP code: $HTTP_CODE" | \
|
|
mail -s "ALERT: Tractatus Down" john.stroh.nz@pm.me
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|
|
```
|
|
|
|
**Cron Job** (every 5 minutes):
|
|
```bash
|
|
# Make executable
|
|
sudo chmod +x /usr/local/bin/tractatus-healthcheck.sh
|
|
|
|
# Add to crontab
|
|
sudo crontab -e
|
|
```
|
|
|
|
**Add**:
|
|
```
|
|
*/5 * * * * /usr/local/bin/tractatus-healthcheck.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Backup & Disaster Recovery
|
|
|
|
### MongoDB Backup Script
|
|
|
|
```bash
|
|
# Create backup directory
|
|
sudo mkdir -p /var/backups/tractatus/mongodb
|
|
sudo chown tractatus:tractatus /var/backups/tractatus/mongodb
|
|
|
|
# Create backup script
|
|
nano /home/tractatus/backup-mongodb.sh
|
|
```
|
|
|
|
**Script**:
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
# Configuration
|
|
BACKUP_DIR="/var/backups/tractatus/mongodb"
|
|
DB_NAME="tractatus_prod"
|
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|
RETENTION_DAYS=7
|
|
|
|
# Create backup
|
|
mongodump --db $DB_NAME --out $BACKUP_DIR/$DATE
|
|
|
|
# Compress backup
|
|
tar -czf $BACKUP_DIR/tractatus_backup_$DATE.tar.gz -C $BACKUP_DIR $DATE
|
|
rm -rf $BACKUP_DIR/$DATE
|
|
|
|
# Delete old backups
|
|
find $BACKUP_DIR -name "tractatus_backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
|
|
|
|
echo "Backup completed: $BACKUP_DIR/tractatus_backup_$DATE.tar.gz"
|
|
```
|
|
|
|
**Cron Job** (daily at 2am):
|
|
```bash
|
|
chmod +x /home/tractatus/backup-mongodb.sh
|
|
|
|
crontab -e
|
|
```
|
|
|
|
**Add**:
|
|
```
|
|
0 2 * * * /home/tractatus/backup-mongodb.sh >> /var/log/tractatus/backup.log 2>&1
|
|
```
|
|
|
|
---
|
|
|
|
### Disaster Recovery Procedure
|
|
|
|
**Scenario**: Server failure, data loss
|
|
|
|
**Recovery Steps**:
|
|
|
|
1. **Provision New Server** (same specs)
|
|
|
|
2. **Restore Application**:
|
|
```bash
|
|
# Clone repository
|
|
git clone https://github.com/your-org/tractatus.git /var/www/tractatus
|
|
|
|
# Restore environment file
|
|
scp .env.production tractatus@<new_server_ip>:/var/www/tractatus/
|
|
```
|
|
|
|
3. **Restore Database**:
|
|
```bash
|
|
# Copy backup to new server
|
|
scp tractatus_backup_YYYYMMDD.tar.gz tractatus@<new_server_ip>:/tmp/
|
|
|
|
# Extract and restore
|
|
tar -xzf /tmp/tractatus_backup_YYYYMMDD.tar.gz -C /tmp/
|
|
mongorestore --db tractatus_prod /tmp/YYYYMMDD/tractatus_prod
|
|
```
|
|
|
|
4. **Reconfigure DNS** (if IP changed)
|
|
|
|
5. **Verify**:
|
|
```bash
|
|
curl https://agenticgovernance.digital/health
|
|
```
|
|
|
|
**RTO** (Recovery Time Objective): <4 hours
|
|
**RPO** (Recovery Point Objective): 24 hours (daily backups)
|
|
|
|
---
|
|
|
|
## DNS & Domain Configuration
|
|
|
|
### Cloudflare DNS Setup
|
|
|
|
**A Records**:
|
|
```
|
|
Type: A
|
|
Name: @
|
|
Content: <server_ip>
|
|
Proxy status: Proxied (or DNS only)
|
|
TTL: Auto
|
|
```
|
|
|
|
```
|
|
Type: A
|
|
Name: www
|
|
Content: <server_ip>
|
|
Proxy status: Proxied (or DNS only)
|
|
TTL: Auto
|
|
```
|
|
|
|
**AAAA Records** (IPv6):
|
|
```
|
|
Type: AAAA
|
|
Name: @
|
|
Content: <server_ipv6>
|
|
Proxy status: Proxied
|
|
TTL: Auto
|
|
```
|
|
|
|
**MX Records** (if using custom email):
|
|
```
|
|
Type: MX
|
|
Name: @
|
|
Content: mail.protonmail.ch
|
|
Priority: 10
|
|
```
|
|
|
|
**TXT Records** (SPF, DKIM):
|
|
```
|
|
Type: TXT
|
|
Name: @
|
|
Content: v=spf1 include:_spf.protonmail.ch ~all
|
|
```
|
|
|
|
---
|
|
|
|
## Environment Configuration
|
|
|
|
### Production .env File
|
|
|
|
**Template** (already shown above, see Deploy Application > Configure Environment)
|
|
|
|
**Security**:
|
|
```bash
|
|
# Restrict permissions
|
|
chmod 600 /var/www/tractatus/.env.production
|
|
|
|
# Verify
|
|
ls -l /var/www/tractatus/.env.production
|
|
# Should show: -rw------- (owner only)
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment Checklist
|
|
|
|
### Pre-Deployment
|
|
|
|
- [ ] OVHCloud VPS provisioned (Essential tier)
|
|
- [ ] Domain registered (agenticgovernance.digital)
|
|
- [ ] Cloudflare account created (optional)
|
|
- [ ] DNS configured (A/AAAA records pointing to server)
|
|
- [ ] SSH key generated and added to server
|
|
- [ ] John Stroh has admin access
|
|
|
|
---
|
|
|
|
### Server Setup
|
|
|
|
- [ ] Ubuntu 22.04 LTS installed
|
|
- [ ] Non-root user created (tractatus)
|
|
- [ ] SSH hardened (key-only, no root)
|
|
- [ ] Firewall configured (UFW)
|
|
- [ ] Fail2ban installed and configured
|
|
- [ ] Automatic security updates enabled
|
|
|
|
---
|
|
|
|
### Application Stack
|
|
|
|
- [ ] Node.js 18 LTS installed
|
|
- [ ] MongoDB 7.x installed and running
|
|
- [ ] Nginx installed and running
|
|
- [ ] Application repository cloned
|
|
- [ ] npm dependencies installed (`npm install --production`)
|
|
- [ ] Environment file configured (.env.production)
|
|
- [ ] Database initialized (`npm run init:db`)
|
|
- [ ] Admin user created (`npm run seed:admin`)
|
|
- [ ] Tailwind CSS built (`npm run build:css`)
|
|
|
|
---
|
|
|
|
### Service Configuration
|
|
|
|
- [ ] systemd service created (tractatus.service)
|
|
- [ ] Service enabled and started
|
|
- [ ] Service logs verified (`journalctl -u tractatus`)
|
|
- [ ] Nginx configured (sites-available/tractatus)
|
|
- [ ] Nginx configuration tested (`nginx -t`)
|
|
- [ ] SSL certificates obtained (Let's Encrypt)
|
|
- [ ] HTTPS redirect working
|
|
|
|
---
|
|
|
|
### Security
|
|
|
|
- [ ] Firewall rules verified (`ufw status`)
|
|
- [ ] SSH access tested (key-only)
|
|
- [ ] MongoDB authentication enabled
|
|
- [ ] MongoDB bound to localhost only
|
|
- [ ] Application environment secrets secure (chmod 600)
|
|
- [ ] Security headers verified (browser dev tools)
|
|
- [ ] SSL Labs test: A+ rating (https://www.ssllabs.com/ssltest/)
|
|
|
|
---
|
|
|
|
### Monitoring
|
|
|
|
- [ ] Log rotation configured (logrotate)
|
|
- [ ] Uptime monitoring script installed
|
|
- [ ] Backup script configured and tested
|
|
- [ ] Email alerts configured (john.stroh.nz@pm.me)
|
|
- [ ] Plausible Analytics installed (optional, self-hosted)
|
|
|
|
---
|
|
|
|
### Testing
|
|
|
|
- [ ] Homepage loads: https://agenticgovernance.digital/
|
|
- [ ] API health check: https://agenticgovernance.digital/health
|
|
- [ ] Document viewer: https://agenticgovernance.digital/docs-viewer.html
|
|
- [ ] Admin login: https://agenticgovernance.digital/admin/login.html
|
|
- [ ] Static assets loading (CSS, JS)
|
|
- [ ] CSP compliance (no console errors)
|
|
- [ ] Mobile responsiveness (test on phone)
|
|
|
|
---
|
|
|
|
### Post-Deployment
|
|
|
|
- [ ] DNS propagation complete (24-48 hours)
|
|
- [ ] SSL certificate auto-renewal tested (`certbot renew --dry-run`)
|
|
- [ ] Backup restore tested (disaster recovery drill)
|
|
- [ ] Performance baseline recorded (Lighthouse, WebPageTest)
|
|
- [ ] Monitoring alerts tested (trigger fake downtime)
|
|
|
|
---
|
|
|
|
## Appendix: Quick Commands
|
|
|
|
### Service Management
|
|
|
|
```bash
|
|
# Restart application
|
|
sudo systemctl restart tractatus.service
|
|
|
|
# View logs
|
|
sudo journalctl -u tractatus.service -f
|
|
|
|
# Check status
|
|
sudo systemctl status tractatus.service
|
|
```
|
|
|
|
### Nginx
|
|
|
|
```bash
|
|
# Test configuration
|
|
sudo nginx -t
|
|
|
|
# Reload configuration
|
|
sudo systemctl reload nginx
|
|
|
|
# View error log
|
|
sudo tail -f /var/log/nginx/tractatus-error.log
|
|
```
|
|
|
|
### MongoDB
|
|
|
|
```bash
|
|
# Connect to database
|
|
mongosh tractatus_prod
|
|
|
|
# Backup manually
|
|
mongodump --db tractatus_prod --out /tmp/backup
|
|
|
|
# Restore from backup
|
|
mongorestore --db tractatus_prod /tmp/backup/tractatus_prod
|
|
```
|
|
|
|
### SSL
|
|
|
|
```bash
|
|
# Renew certificates manually
|
|
sudo certbot renew
|
|
|
|
# Check certificate expiry
|
|
sudo certbot certificates
|
|
```
|
|
|
|
---
|
|
|
|
## Revision History
|
|
|
|
| Date | Version | Changes |
|
|
|------|---------|---------|
|
|
| 2025-10-07 | 1.0 | Initial infrastructure plan for Phase 2 |
|
|
|
|
---
|
|
|
|
**Document Owner**: John Stroh
|
|
**Last Updated**: 2025-10-07
|
|
**Next Review**: Upon deployment completion
|