diff --git a/deploy/nginx/horux360.conf b/deploy/nginx/horux360.conf new file mode 100644 index 0000000..ff11a43 --- /dev/null +++ b/deploy/nginx/horux360.conf @@ -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"; + } +} diff --git a/ecosystem.config.js b/ecosystem.config.js index d75ecf4..c5fb6ac 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -2,35 +2,33 @@ module.exports = { apps: [ { name: 'horux-api', + script: 'dist/index.js', cwd: '/root/Horux/apps/api', - script: 'pnpm', - args: 'dev', - interpreter: 'none', - watch: false, + instances: 2, + exec_mode: 'cluster', autorestart: true, - restart_delay: 10000, - max_restarts: 3, + max_memory_restart: '1G', kill_timeout: 5000, + listen_timeout: 10000, env: { - NODE_ENV: 'development', - PORT: 4000 - } + NODE_ENV: 'production', + PORT: 4000, + }, }, { name: 'horux-web', + script: 'node_modules/.bin/next', + args: 'start', cwd: '/root/Horux/apps/web', - script: 'pnpm', - args: 'dev', - interpreter: 'none', - watch: false, + instances: 1, + exec_mode: 'fork', autorestart: true, - restart_delay: 10000, - max_restarts: 3, + max_memory_restart: '512M', kill_timeout: 5000, env: { - NODE_ENV: 'development', - PORT: 3000 - } - } - ] + NODE_ENV: 'production', + PORT: 3000, + }, + }, + ], }; diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..0f65663 --- /dev/null +++ b/scripts/backup.sh @@ -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: +# +# 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) ===" diff --git a/scripts/tune-postgres.sh b/scripts/tune-postgres.sh new file mode 100755 index 0000000..533c6b8 --- /dev/null +++ b/scripts/tune-postgres.sh @@ -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 ==="