Files
goodgo-platform/infra/server-setup.sh
Ho Ngoc Hai e5f7acf7da
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 58s
Deploy / Build Web Image (push) Failing after 14s
Deploy / Rollback Production (push) Has been skipped
CI / E2E Tests (push) Has been skipped
Deploy / Build API Image (push) Failing after 3m8s
Deploy / Build AI Services Image (push) Failing after 10s
E2E Tests / Playwright E2E (push) Failing after 1m21s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
feat: production infra — nginx configs, deploy script, security hardening
- Add Nginx reverse-proxy configs for api.goodgo.vn and platform.goodgo.vn
  with SSL, gzip, rate limiting, security headers, and WebSocket support
- Add Cloudflare DNS setup script for A/AAAA/CNAME records
- Add server-setup.sh for Ubuntu provisioning (Docker, fail2ban, UFW,
  swap, unattended-upgrades)
- Add deploy-production.sh for manual production deployments
- Add env.production.example with all required environment variables
- Bind container ports to 127.0.0.1 in docker-compose.prod.yml
  (security: prevent direct access bypassing Nginx)
- Fix deploy workflow: add -T flag to exec, sync Nginx configs,
  copy pgbouncer and backup configs to server

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 14:11:25 +07:00

265 lines
9.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# ==============================================================================
# GoodGo Platform — VPS Server Setup Script
# Target: VelikSV01 (185.225.232.65) — Ubuntu 22.04+
#
# Usage:
# scp infra/server-setup.sh ubuntu@185.225.232.65:~/
# ssh ubuntu@185.225.232.65 'chmod +x ~/server-setup.sh && sudo ~/server-setup.sh'
# ==============================================================================
set -euo pipefail
# ── Colors ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[SETUP]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# ── Pre-flight checks ────────────────────────────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
err "This script must be run as root (sudo)."
exit 1
fi
DEPLOY_USER="${DEPLOY_USER:-ubuntu}"
DEPLOY_DIR="/home/${DEPLOY_USER}/goodgo"
log "Starting GoodGo Platform server setup..."
log "Deploy user: ${DEPLOY_USER}"
log "Deploy dir: ${DEPLOY_DIR}"
# ── 1. System Updates ─────────────────────────────────────────────────────────
log "Updating system packages..."
apt-get update -qq
apt-get upgrade -y -qq
# ── 2. Install Essential Packages ─────────────────────────────────────────────
log "Installing essential packages..."
apt-get install -y -qq \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release \
software-properties-common \
git \
jq \
htop \
unzip \
fail2ban \
ufw \
logrotate
# ── 3. Install Docker Engine ─────────────────────────────────────────────────
if command -v docker &>/dev/null; then
log "Docker already installed: $(docker --version)"
else
log "Installing Docker Engine..."
curl -fsSL https://get.docker.com | sh
# Add deploy user to docker group
usermod -aG docker "${DEPLOY_USER}"
log "Docker installed: $(docker --version)"
fi
# Ensure Docker starts on boot
systemctl enable docker
systemctl start docker
# ── 4. Install Docker Compose v2 (plugin) ────────────────────────────────────
if docker compose version &>/dev/null; then
log "Docker Compose already installed: $(docker compose version --short)"
else
log "Installing Docker Compose v2 plugin..."
DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name)
mkdir -p /usr/local/lib/docker/cli-plugins
curl -fsSL "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-$(uname -m)" \
-o /usr/local/lib/docker/cli-plugins/docker-compose
chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
log "Docker Compose installed: $(docker compose version --short)"
fi
# ── 5. Install Nginx ──────────────────────────────────────────────────────────
if command -v nginx &>/dev/null; then
log "Nginx already installed: $(nginx -v 2>&1)"
else
log "Installing Nginx..."
apt-get install -y -qq nginx
log "Nginx installed: $(nginx -v 2>&1)"
fi
systemctl enable nginx
systemctl start nginx
# ── 6. Configure Firewall (ufw) ──────────────────────────────────────────────
log "Configuring firewall (ufw)..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP — redirect to HTTPS'
ufw allow 443/tcp comment 'HTTPS'
ufw --force enable
log "Firewall configured. Active rules:"
ufw status verbose
# ── 7. Configure fail2ban ─────────────────────────────────────────────────────
log "Configuring fail2ban..."
cat > /etc/fail2ban/jail.local << 'F2B_CONF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
[nginx-http-auth]
enabled = true
[nginx-botsearch]
enabled = true
F2B_CONF
systemctl enable fail2ban
systemctl restart fail2ban
log "fail2ban configured and running."
# ── 8. Create Directory Structure ────────────────────────────────────────────
log "Creating deployment directory structure..."
mkdir -p "${DEPLOY_DIR}"/{monitoring/{prometheus,grafana/{provisioning,dashboards},loki,promtail,alertmanager},scripts/backup,infra/pgbouncer}
chown -R "${DEPLOY_USER}:${DEPLOY_USER}" "${DEPLOY_DIR}"
log "Directory structure created at ${DEPLOY_DIR}"
# ── 9. Create SSL Directory ──────────────────────────────────────────────────
log "Creating SSL certificate directory..."
mkdir -p /etc/ssl/goodgo
chmod 700 /etc/ssl/goodgo
log "SSL directory: /etc/ssl/goodgo (place origin.pem + origin-key.pem here)"
# ── 10. Nginx Base Configuration ─────────────────────────────────────────────
log "Creating Nginx base configuration..."
# Create sites-available/sites-enabled structure if not exists
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
# Check if nginx.conf already includes sites-enabled
if ! grep -q 'sites-enabled' /etc/nginx/nginx.conf; then
warn "Adding 'include sites-enabled' to nginx.conf"
sed -i '/http {/a \ include /etc/nginx/sites-enabled/*;' /etc/nginx/nginx.conf
fi
# Remove default site
rm -f /etc/nginx/sites-enabled/default
# Nginx performance tuning
cat > /etc/nginx/conf.d/performance.conf << 'NGINX_PERF'
# GoodGo Platform — Nginx Performance Tuning
# Rate limiting zone (used by API vhost)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
# Proxy settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffers 32 8k;
# Security
server_tokens off;
NGINX_PERF
nginx -t && systemctl reload nginx
log "Nginx base configuration ready."
# ── 11. Docker Log Rotation ──────────────────────────────────────────────────
log "Configuring Docker log rotation..."
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'DOCKER_LOG'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
},
"storage-driver": "overlay2"
}
DOCKER_LOG
systemctl restart docker
log "Docker log rotation configured."
# ── 12. Swap (if <4GB RAM) ───────────────────────────────────────────────────
TOTAL_MEM=$(free -m | awk '/^Mem:/{print $2}')
if [ "$TOTAL_MEM" -lt 4096 ]; then
if [ ! -f /swapfile ]; then
log "Low RAM (${TOTAL_MEM}MB). Creating 4GB swap..."
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
log "Swap created and enabled."
else
log "Swap already exists."
fi
else
log "RAM: ${TOTAL_MEM}MB — swap not needed."
fi
# ── 13. Kernel Tuning (for Docker/PostgreSQL) ────────────────────────────────
log "Applying kernel tuning..."
cat >> /etc/sysctl.conf << 'SYSCTL'
# GoodGo — Docker + PostgreSQL tuning
vm.overcommit_memory = 1
vm.swappiness = 10
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
fs.file-max = 2097152
SYSCTL
sysctl -p >/dev/null 2>&1 || true
log "Kernel parameters tuned."
# ── Summary ──────────────────────────────────────────────────────────────────
echo ""
log "=========================================="
log " Server setup complete!"
log "=========================================="
log ""
log " Next steps:"
log " 1. Place Cloudflare Origin Certificate:"
log " /etc/ssl/goodgo/origin.pem"
log " /etc/ssl/goodgo/origin-key.pem"
log ""
log " 2. Copy Nginx vhost configs to:"
log " /etc/nginx/sites-available/"
log " Then symlink to /etc/nginx/sites-enabled/"
log ""
log " 3. Create .env file at:"
log " ${DEPLOY_DIR}/.env"
log ""
log " 4. Login to GHCR:"
log " docker login ghcr.io"
log ""
log " 5. Deploy:"
log " cd ${DEPLOY_DIR}"
log " docker compose -f docker-compose.prod.yml up -d"
log "=========================================="