feat: add production deployment configs
- PM2 cluster mode (2 API workers + 1 Next.js) - Nginx reverse proxy with SSL, rate limiting, security headers - Automated backup script with daily/weekly rotation - PostgreSQL production tuning script (300 connections, 4GB shared_buffers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
89
deploy/nginx/horux360.conf
Normal file
89
deploy/nginx/horux360.conf
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# Rate limiting zones
|
||||||
|
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
|
||||||
|
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/s;
|
||||||
|
limit_req_zone $binary_remote_addr zone=webhook:10m rate=10r/s;
|
||||||
|
|
||||||
|
upstream horux_api {
|
||||||
|
server 127.0.0.1:4000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream horux_web {
|
||||||
|
server 127.0.0.1:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect HTTP to HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name horux360.consultoria-as.com;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name horux360.consultoria-as.com;
|
||||||
|
|
||||||
|
# SSL (managed by Certbot)
|
||||||
|
ssl_certificate /etc/letsencrypt/live/horux360.consultoria-as.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/horux360.consultoria-as.com/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
# Max body size for XML uploads
|
||||||
|
client_max_body_size 1G;
|
||||||
|
|
||||||
|
# Auth endpoints (stricter rate limiting)
|
||||||
|
location /api/auth/ {
|
||||||
|
limit_req zone=auth burst=10 nodelay;
|
||||||
|
proxy_pass http://horux_api;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Webhook endpoints (no auth, moderate rate limiting)
|
||||||
|
location /api/webhooks/ {
|
||||||
|
limit_req zone=webhook burst=20 nodelay;
|
||||||
|
proxy_pass http://horux_api;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API routes
|
||||||
|
location /api/ {
|
||||||
|
limit_req zone=api burst=50 nodelay;
|
||||||
|
proxy_pass http://horux_api;
|
||||||
|
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_read_timeout 300s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check (no rate limit)
|
||||||
|
location /health {
|
||||||
|
proxy_pass http://horux_api;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Next.js frontend
|
||||||
|
location / {
|
||||||
|
proxy_pass http://horux_web;
|
||||||
|
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_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,35 +2,33 @@ module.exports = {
|
|||||||
apps: [
|
apps: [
|
||||||
{
|
{
|
||||||
name: 'horux-api',
|
name: 'horux-api',
|
||||||
|
script: 'dist/index.js',
|
||||||
cwd: '/root/Horux/apps/api',
|
cwd: '/root/Horux/apps/api',
|
||||||
script: 'pnpm',
|
instances: 2,
|
||||||
args: 'dev',
|
exec_mode: 'cluster',
|
||||||
interpreter: 'none',
|
|
||||||
watch: false,
|
|
||||||
autorestart: true,
|
autorestart: true,
|
||||||
restart_delay: 10000,
|
max_memory_restart: '1G',
|
||||||
max_restarts: 3,
|
|
||||||
kill_timeout: 5000,
|
kill_timeout: 5000,
|
||||||
|
listen_timeout: 10000,
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: 'development',
|
NODE_ENV: 'production',
|
||||||
PORT: 4000
|
PORT: 4000,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'horux-web',
|
name: 'horux-web',
|
||||||
|
script: 'node_modules/.bin/next',
|
||||||
|
args: 'start',
|
||||||
cwd: '/root/Horux/apps/web',
|
cwd: '/root/Horux/apps/web',
|
||||||
script: 'pnpm',
|
instances: 1,
|
||||||
args: 'dev',
|
exec_mode: 'fork',
|
||||||
interpreter: 'none',
|
|
||||||
watch: false,
|
|
||||||
autorestart: true,
|
autorestart: true,
|
||||||
restart_delay: 10000,
|
max_memory_restart: '512M',
|
||||||
max_restarts: 3,
|
|
||||||
kill_timeout: 5000,
|
kill_timeout: 5000,
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: 'development',
|
NODE_ENV: 'production',
|
||||||
PORT: 3000
|
PORT: 3000,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
71
scripts/backup.sh
Executable file
71
scripts/backup.sh
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Horux360 Database Backup Script
|
||||||
|
# Backs up all databases (central + tenant) with daily/weekly rotation
|
||||||
|
# Requires: .pgpass file at /root/.pgpass with format: localhost:5432:*:postgres:<password>
|
||||||
|
#
|
||||||
|
# Usage: Add to crontab:
|
||||||
|
# 0 1 * * * /root/Horux/scripts/backup.sh >> /var/log/horux-backup.log 2>&1
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BACKUP_DIR="/var/horux/backups"
|
||||||
|
DAILY_DIR="$BACKUP_DIR/daily"
|
||||||
|
WEEKLY_DIR="$BACKUP_DIR/weekly"
|
||||||
|
PG_USER="postgres"
|
||||||
|
PG_HOST="localhost"
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
DAY_OF_WEEK=$(date +%u)
|
||||||
|
|
||||||
|
# Retention
|
||||||
|
DAILY_KEEP=7
|
||||||
|
WEEKLY_KEEP=4
|
||||||
|
|
||||||
|
echo "=== Horux360 Backup Started: $(date) ==="
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
mkdir -p "$DAILY_DIR" "$WEEKLY_DIR"
|
||||||
|
|
||||||
|
# Get list of all horux databases (central + tenant)
|
||||||
|
DATABASES=$(psql -h "$PG_HOST" -U "$PG_USER" -t -c \
|
||||||
|
"SELECT datname FROM pg_database WHERE datname = 'horux360' OR datname LIKE 'horux_%' AND datname NOT LIKE '%_deleted_%'" \
|
||||||
|
| tr -d ' ')
|
||||||
|
|
||||||
|
TOTAL=0
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
for DB in $DATABASES; do
|
||||||
|
echo "Backing up: $DB"
|
||||||
|
DUMP_FILE="$DAILY_DIR/${DB}_${DATE}.sql.gz"
|
||||||
|
|
||||||
|
if pg_dump -h "$PG_HOST" -U "$PG_USER" "$DB" | gzip > "$DUMP_FILE"; then
|
||||||
|
# Verify file is not empty
|
||||||
|
if [ -s "$DUMP_FILE" ]; then
|
||||||
|
TOTAL=$((TOTAL + 1))
|
||||||
|
echo " OK: $(du -h "$DUMP_FILE" | cut -f1)"
|
||||||
|
else
|
||||||
|
echo " WARNING: Empty backup file for $DB"
|
||||||
|
rm -f "$DUMP_FILE"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ERROR: Failed to backup $DB"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Weekly backup on Sundays (day 7)
|
||||||
|
if [ "$DAY_OF_WEEK" -eq 7 ]; then
|
||||||
|
echo "Creating weekly backup..."
|
||||||
|
cp "$DAILY_DIR"/*_"${DATE}".sql.gz "$WEEKLY_DIR/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean old daily backups
|
||||||
|
echo "Cleaning daily backups older than $DAILY_KEEP days..."
|
||||||
|
find "$DAILY_DIR" -name "*.sql.gz" -mtime "+$DAILY_KEEP" -delete
|
||||||
|
|
||||||
|
# Clean old weekly backups
|
||||||
|
echo "Cleaning weekly backups older than $WEEKLY_KEEP weeks..."
|
||||||
|
find "$WEEKLY_DIR" -name "*.sql.gz" -mtime "+$((WEEKLY_KEEP * 7))" -delete
|
||||||
|
|
||||||
|
echo "=== Backup Complete: $TOTAL databases backed up, $ERRORS errors ==="
|
||||||
|
echo "=== Finished: $(date) ==="
|
||||||
27
scripts/tune-postgres.sh
Executable file
27
scripts/tune-postgres.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# PostgreSQL Production Tuning for Horux360
|
||||||
|
# Server: 32GB RAM, 8 cores
|
||||||
|
# Target: 50 tenants, PM2 cluster ×2
|
||||||
|
#
|
||||||
|
# Run once: sudo bash scripts/tune-postgres.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "=== PostgreSQL Production Tuning ==="
|
||||||
|
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET max_connections = 300;"
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET shared_buffers = '4GB';"
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET work_mem = '16MB';"
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET effective_cache_size = '16GB';"
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET maintenance_work_mem = '512MB';"
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET checkpoint_completion_target = 0.9;"
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET wal_buffers = '64MB';"
|
||||||
|
sudo -u postgres psql -c "ALTER SYSTEM SET random_page_cost = 1.1;"
|
||||||
|
|
||||||
|
echo "Settings applied. Restarting PostgreSQL..."
|
||||||
|
sudo systemctl restart postgresql
|
||||||
|
|
||||||
|
echo "Verifying settings..."
|
||||||
|
sudo -u postgres psql -c "SHOW max_connections; SHOW shared_buffers; SHOW work_mem;"
|
||||||
|
|
||||||
|
echo "=== Done ==="
|
||||||
Reference in New Issue
Block a user