Wednesday, March 11, 2026

setup app dan api

 dibuat api.sh, chmod +x, jalankan ./api.sh

 

#!/bin/bash
#===============================================================================
# SCRIPT INSTALLASI VPS DEBIAN 12 - VUE + RUST STACK
# Stack: Nginx + Certbot + Fail2Ban
# Frontend: VueJS (app.mydomain.com)
# Backend: Rust (api.mydomain.com)
# FIX: Rate limiting scope corrected for API config
#===============================================================================

set -e  # Exit on error

# Warna untuk output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# Logging function
log_info()    { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warn()    { echo -e "${YELLOW}[!]${NC} $1"; }
log_error()   { echo -e "${RED}[✗]${NC} $1"; }
log_step()    { echo -e "${CYAN}>>>${NC} $1"; }

# Cek apakah dijalankan sebagai root
if [[ $EUID -ne 0 ]]; then
   log_error "Script ini harus dijalankan sebagai root atau dengan sudo!"
   exit 1
fi

# Cek OS
if [[ ! -f /etc/debian_version ]]; then
    log_error "Script ini hanya untuk Debian!"
    exit 1
fi

echo -e "${GREEN}
╔════════════════════════════════════════════════════════════╗
║     VPS DEBIAN 12 INSTALLER - VUE + RUST STACK            ║
║  Nginx • Certbot • Fail2Ban                               ║
╚════════════════════════════════════════════════════════════╝
${NC}"

#===============================================================================
# BAGIAN 1: INPUT INTERAKTIF
#===============================================================================
log_step "KONFIGURASI DOMAIN & APLIKASI"

#-------------------------------------------------------------------------------
# 1.1 Domain Configuration
#-------------------------------------------------------------------------------
echo -e "\n${YELLOW}🌐 KONFIGURASI DOMAIN${NC}"

# Frontend Domain
while true; do
    read -p "Domain frontend (contoh: app.mydomain.com): " FRONTEND_DOMAIN
    if [[ $FRONTEND_DOMAIN =~ ^[a-zA-Z0-9.-]+\.[a-z]{2,}$ ]]; then
        break
    else
        log_warn "Format domain tidak valid. Coba lagi."
    fi
done

# Backend Domain
while true; do
    read -p "Domain backend API (contoh: api.mydomain.com): " BACKEND_DOMAIN
    if [[ $BACKEND_DOMAIN =~ ^[a-zA-Z0-9.-]+\.[a-z]{2,}$ ]]; then
        break
    else
        log_warn "Format domain tidak valid. Coba lagi."
    fi
done

# Email untuk Let's Encrypt
read -p "Email untuk sertifikat SSL (opsional): " SSL_EMAIL

#-------------------------------------------------------------------------------
# 1.2 Rust Application Configuration
#-------------------------------------------------------------------------------
echo -e "\n${YELLOW}πŸ¦€ KONFIGURASI APLIKASI RUST${NC}"

read -p "Port internal aplikasi Rust [8080]: " RUST_PORT
RUST_PORT=${RUST_PORT:-8080}

read -p "Path binary Rust [/opt/apps/rust-backend/target/release/app]: " RUST_BINARY
RUST_BINARY=${RUST_BINARY:-/opt/apps/rust-backend/target/release/app}

read -p "Working directory Rust [/opt/apps/rust-backend]: " RUST_WORKDIR
RUST_WORKDIR=${RUST_WORKDIR:-/opt/apps/rust-backend}

#-------------------------------------------------------------------------------
# 1.3 Fail2Ban Configuration
#-------------------------------------------------------------------------------
echo -e "\n${YELLOW}πŸ›‘️  KONFIGURASI FAIL2BAN${NC}"

read -p "Max retry sebelum ban [5]: " F2B_MAXRETRY
F2B_MAXRETRY=${F2B_MAXRETRY:-5}

read -p "Ban time (contoh: 1h, 24h, 1w) [1h]: " F2B_BANTIME
F2B_BANTIME=${F2B_BANTIME:-1h}

read -p "Find time window (contoh: 10m, 30m) [10m]: " F2B_FINDTIME
F2B_FINDTIME=${F2B_FINDTIME:-10m}

#-------------------------------------------------------------------------------
# 1.4 Ringkasan & Konfirmasi
#-------------------------------------------------------------------------------
echo -e "\n${YELLOW}πŸ“‹ RINGKASAN KONFIGURASI${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
printf "%-20s : %s\n" "Frontend Domain" "$FRONTEND_DOMAIN"
printf "%-20s : %s\n" "Backend Domain" "$BACKEND_DOMAIN"
printf "%-20s : %s\n" "SSL Email" "${SSL_EMAIL:-'(tidak diisi)'}"
printf "%-20s : %s\n" "Rust Port (internal)" "$RUST_PORT"
printf "%-20s : %s\n" "Fail2Ban Max Retry" "$F2B_MAXRETRY"
printf "%-20s : %s\n" "Fail2Ban Ban Time" "$F2B_BANTIME"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

read -p "✅ Lanjutkan instalasi? (y/n): " CONFIRM
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
    log_info "Instalasi dibatalkan oleh user."
    exit 0
fi

#===============================================================================
# BAGIAN 2: UPDATE SISTEM & INSTALASI PACKAGE
#===============================================================================
log_step "UPDATE SISTEM & INSTALASI PACKAGE"

log_info "Memperbarui package list..."
apt update -qq

log_info "Melakukan upgrade sistem..."
apt upgrade -y -qq

log_info "Menginstal package yang diperlukan..."
apt install -y -qq \
    nginx \
    fail2ban \
    certbot python3-certbot-nginx \
    curl wget \
    build-essential pkg-config libssl-dev

log_success "Semua package terinstal."

#===============================================================================
# BAGIAN 3: BUAT STRUKTUR DIREKTORI
#===============================================================================
log_step "MEMBUAT STRUKTUR DIREKTORI"

mkdir -p /var/www/app
mkdir -p /opt/apps/rust-backend
mkdir -p /etc/nginx/sites-available
mkdir -p /etc/nginx/sites-enabled
mkdir -p /etc/fail2ban/jail.d
mkdir -p /etc/fail2ban/filter.d
mkdir -p /var/log/nginx

# Hapus default nginx
rm -f /etc/nginx/sites-enabled/default

log_success "Struktur direktori siap."

#===============================================================================
# BAGIAN 4: KONFIGURASI NGINX - RATE LIMIT ZONE (FIXED)
#===============================================================================
log_step "KONFIGURASI RATE LIMITING ZONE"

# Fungsi untuk menambahkan limit_req_zone di dalam http block
add_rate_limit_zone() {
    local nginx_conf="/etc/nginx/nginx.conf"
    local zone_directive="    limit_req_zone \$binary_remote_addr zone=api_limit:10m rate=10r/s;"
    
    # Cek apakah sudah ada
    if grep -q "limit_req_zone.*api_limit" "$nginx_conf" 2>/dev/null; then
        log_info "Rate limit zone 'api_limit' sudah terkonfigurasi."
        return 0
    fi
    
    log_info "Menambahkan rate limit zone ke nginx.conf..."
    
    # Backup konfigurasi
    cp "$nginx_conf" "${nginx_conf}.bak.$(date +%Y%m%d%H%M%S)"
    
    # Strategy 1: Inject setelah baris "http {" (paling aman)
    if grep -q "^http[[:space:]]*{" "$nginx_conf"; then
        # Gunakan awk untuk inject setelah "http {"
        awk -v directive="$zone_directive" '
            /^http[[:space:]]*\{/ {
                print
                print directive
                next
            }
            { print }
        ' "$nginx_conf" > "${nginx_conf}.tmp" && mv "${nginx_conf}.tmp" "$nginx_conf"
        log_success "Rate limit zone ditambahkan setelah 'http {'."
        return 0
    fi
    
    # Strategy 2: Inject sebelum "include /etc/nginx/conf.d/*.conf;" (biasanya di dalam http block)
    if grep -q "include /etc/nginx/conf.d/\*.conf;" "$nginx_conf"; then
        sed -i "/include \/etc\/nginx\/conf.d\/\*.conf;/i\\$zone_directive" "$nginx_conf"
        log_success "Rate limit zone ditambahkan sebelum include conf.d."
        return 0
    fi
    
    # Strategy 3: Inject sebelum "include /etc/nginx/sites-enabled/*;"
    if grep -q "include /etc/nginx/sites-enabled/\*;" "$nginx_conf"; then
        sed -i "/include \/etc\/nginx\/sites-enabled\/\*;/i\\$zone_directive" "$nginx_conf"
        log_success "Rate limit zone ditambahkan sebelum include sites-enabled."
        return 0
    fi
    
    # Fallback: Append ke akhir file (kemungkinan besar akan error, tapi sebagai last resort)
    log_warn "Tidak menemukan posisi ideal, mencoba append ke akhir file..."
    echo "" >> "$nginx_conf"
    echo "# Rate limiting zone for API" >> "$nginx_conf"
    echo "limit_req_zone \$binary_remote_addr zone=api_limit:10m rate=10r/s;" >> "$nginx_conf"
    
    return 0
}

# Jalankan fungsi
add_rate_limit_zone

# Verifikasi konfigurasi nginx
log_info "Menguji konfigurasi Nginx..."
if nginx -t 2>&1 | grep -q "syntax is ok"; then
    log_success "✓ Nginx configuration valid dengan rate limit zone."
else
    log_error "✗ Nginx configuration test failed!"
    nginx -t 2>&1 | head -5
    log_warn "Restoring backup nginx.conf..."
    # Restore backup terbaru
    LATEST_BACKUP=$(ls -t /etc/nginx/nginx.conf.bak.* 2>/dev/null | head -1)
    if [[ -n "$LATEST_BACKUP" ]]; then
        cp "$LATEST_BACKUP" /etc/nginx/nginx.conf
        log_info "Backup restored. Silakan cek manual konfigurasi nginx.conf Anda."
    fi
    exit 1
fi

log_success "Rate limit zone configured successfully."

#===============================================================================
# BAGIAN 5: KONFIGURASI NGINX - FRONTEND (VUEJS)
#===============================================================================
log_step "KONFIGURASI NGINX - FRONTEND ($FRONTEND_DOMAIN)"

cat > /etc/nginx/sites-available/${FRONTEND_DOMAIN} << EOF
# HTTP → HTTPS Redirect
server {
    listen 80;
    listen [::]:80;
    server_name ${FRONTEND_DOMAIN};
    return 301 https://\$server_name\$request_uri;
}

# HTTPS Server Block
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name ${FRONTEND_DOMAIN};

    # SSL Certificate (diisi otomatis oleh Certbot)
    ssl_certificate /etc/letsencrypt/live/${FRONTEND_DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${FRONTEND_DOMAIN}/privkey.pem;

    # Modern SSL Configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;

    root /var/www/app;
    index index.html;

    # Logging
    access_log /var/log/nginx/${FRONTEND_DOMAIN}-access.log;
    error_log /var/log/nginx/${FRONTEND_DOMAIN}-error.log;

    # Gzip Compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript image/svg+xml;

    # Vue Router History Mode Support
    location / {
        try_files \$uri \$uri/ /index.html;
    }

    # Cache Static Assets (1 year)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|otf|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # 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;
}
EOF

log_success "Konfigurasi frontend dibuat."

#===============================================================================
# BAGIAN 6: KONFIGURASI NGINX - BACKEND (RUST REVERSE PROXY) [FIXED]
#===============================================================================
log_step "KONFIGURASI NGINX - BACKEND ($BACKEND_DOMAIN)"

cat > /etc/nginx/sites-available/${BACKEND_DOMAIN} << EOF
# HTTP → HTTPS Redirect
server {
    listen 80;
    listen [::]:80;
    server_name ${BACKEND_DOMAIN};
    return 301 https://\$server_name\$request_uri;
}

# HTTPS + Reverse Proxy to Rust App
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name ${BACKEND_DOMAIN};

    # SSL Certificate
    ssl_certificate /etc/letsencrypt/live/${BACKEND_DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${BACKEND_DOMAIN}/privkey.pem;

    # Modern SSL Configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Logging
    access_log /var/log/nginx/${BACKEND_DOMAIN}-access.log;
    error_log /var/log/nginx/${BACKEND_DOMAIN}-error.log;

    # ─────────────────────────────────────────
    # LOCATION: Main API (dengan Rate Limiting)
    # ─────────────────────────────────────────
    location / {
        # Rate Limiting: 10 req/detik, burst 20
        limit_req zone=api_limit burst=20 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:${RUST_PORT};
        proxy_http_version 1.1;
        
        # WebSocket Support
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection "upgrade";
        
        # Standard Proxy Headers
        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_set_header X-Forwarded-Host \$host;
        proxy_set_header X-Forwarded-Port \$server_port;
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Buffering
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;
    }

    # ─────────────────────────────────────────
    # LOCATION: Health Check (TANPA Rate Limiting)
    # ─────────────────────────────────────────
    location /health {
        # Tidak ada limit_req = tidak dibatasi
        proxy_pass http://127.0.0.1:${RUST_PORT}/health;
        access_log off;
        proxy_connect_timeout 5s;
        proxy_read_timeout 5s;
    }

    # ─────────────────────────────────────────
    # LOCATION: Metrics/Prometheus (Opsional, localhost only)
    # ─────────────────────────────────────────
    location /metrics {
        proxy_pass http://127.0.0.1:${RUST_PORT}/metrics;
        allow 127.0.0.1;
        deny all;
        access_log off;
    }

    # Security Headers for API
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
}
EOF

#===============================================================================
# BAGIAN 6.5: DUMMY SSL CERTIFICATES (TEMPORARY FIX)
#===============================================================================
log_step "MEMBUAT DUMMY SSL CERTIFICATES (TEMPORARY)"

create_dummy_cert() {
    local domain=$1
    local live_dir="/etc/letsencrypt/live/${domain}"
    local archive_dir="/etc/letsencrypt/archive/${domain}"
    
    mkdir -p "$live_dir" "$archive_dir"
    
    if [[ ! -f "${live_dir}/privkey.pem" ]]; then
        log_info "Generating dummy private key for ${domain}..."
        openssl genrsa -out "${archive_dir}/privkey1.pem" 2048 2>/dev/null
        chmod 600 "${archive_dir}/privkey1.pem"
        ln -sf "${archive_dir}/privkey1.pem" "${live_dir}/privkey.pem"
    fi
    
    if [[ ! -f "${live_dir}/fullchain.pem" ]]; then
        log_info "Generating dummy certificate for ${domain}..."
        openssl req -new -x509 -key "${archive_dir}/privkey1.pem" \
            -out "${archive_dir}/cert1.pem" \
            -days 1 \
            -subj "/CN=${domain}/O=Dummy/C=ID" \
            2>/dev/null
        ln -sf "${archive_dir}/cert1.pem" "${live_dir}/cert.pem"
        ln -sf "${archive_dir}/cert1.pem" "${live_dir}/fullchain.pem"
        ln -sf "${archive_dir}/cert1.pem" "${live_dir}/chain.pem"
    fi
}

create_dummy_cert "${FRONTEND_DOMAIN}"
create_dummy_cert "${BACKEND_DOMAIN}"
log_success "Dummy SSL certificates ready."

# Aktifkan kedua situs
ln -sf /etc/nginx/sites-available/${FRONTEND_DOMAIN} /etc/nginx/sites-enabled/
ln -sf /etc/nginx/sites-available/${BACKEND_DOMAIN} /etc/nginx/sites-enabled/

# Test konfigurasi nginx
if nginx -t; then
    log_success "Konfigurasi Nginx valid."
else
    log_error "Konfigurasi Nginx gagal! Cek error di atas."
    exit 1
fi

#===============================================================================
# BAGIAN 7: KONFIGURASI FAIL2BAN
#===============================================================================
log_step "KONFIGURASI FAIL2BAN"

#-------------------------------------------------------------------------------
# 7.1 Main Jail Configuration
#-------------------------------------------------------------------------------
log_info "Membuat konfigurasi jail Fail2Ban..."

cat > /etc/fail2ban/jail.d/nginx-custom.conf << EOF
[DEFAULT]
bantime = ${F2B_BANTIME}
findtime = ${F2B_FINDTIME}
maxretry = ${F2B_MAXRETRY}
backend = auto
ignoreip = 127.0.0.1/8 ::1

# Ban yang lebih lama untuk repeat offender
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 3

# 1. HTTP Auth Brute Force (jika ada basic auth di Nginx)
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/*error.log

# 2. Bot Scanning Protection (Frontend & Backend)
[nginx-botsearch]
enabled = true
filter = nginx-botsearch
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 2
bantime = 24h

# 3. Rate Limit Violations (API Abuse)
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
port = http,https
logpath = /var/log/nginx/*error.log
maxretry = 10
bantime = 24h

# 4. API Specific Abuse (401/403 errors)
[nginx-api-abuse]
enabled = true
filter = nginx-api-abuse
port = http,https
logpath = /var/log/nginx/${BACKEND_DOMAIN}-access.log
maxretry = 20
bantime = 24h

# 5. Frontend Specific (Scanner/Probe detection)
[nginx-frontend-probe]
enabled = true
filter = nginx-frontend-probe
port = http,https
logpath = /var/log/nginx/${FRONTEND_DOMAIN}-access.log
maxretry = 5
bantime = 12h
EOF

log_success "Jail configuration dibuat."

#-------------------------------------------------------------------------------
# 7.2 Custom Filter: API Abuse
#-------------------------------------------------------------------------------
log_info "Membuat filter untuk API abuse detection..."

cat > /etc/fail2ban/filter.d/nginx-api-abuse.conf << 'EOF'
[Definition]
# Deteksi status code 401 (Unauthorized) atau 403 (Forbidden) di access log
failregex = ^<HOST> -.*"(GET|POST|PUT|DELETE|PATCH).*" (401|403)
ignoreregex =
EOF

log_success "Filter API abuse dibuat."

#-------------------------------------------------------------------------------
# 7.3 Custom Filter: Frontend Probe
#-------------------------------------------------------------------------------
log_info "Membuat filter untuk frontend probe detection..."

cat > /etc/fail2ban/filter.d/nginx-frontend-probe.conf << 'EOF'
[Definition]
# Deteksi scanning untuk file sensitif (.env, .git, wp-admin, phpmyadmin, dll)
failregex = ^<HOST> -.*"(GET|POST).*" 404.*(\.env|\.git|wp-admin|wp-login|phpmyadmin|\.sql|\.bak|\.config|admin|login|\.php|\.asp|\.aspx)
ignoreregex =
EOF

log_success "Filter frontend probe dibuat."

#-------------------------------------------------------------------------------
# 7.4 Enable & Restart Fail2Ban
#-------------------------------------------------------------------------------
log_info "Mengaktifkan dan merestart Fail2Ban..."

systemctl enable --now fail2ban
systemctl restart fail2ban

# Verifikasi
if systemctl is-active --quiet fail2ban; then
    log_success "Fail2Ban aktif dan berjalan."
    log_info "Jails yang aktif:"
    fail2ban-client status | grep "Jail list" || true
else
    log_warn "Fail2Ban gagal aktif. Cek dengan: systemctl status fail2ban"
fi

#===============================================================================
# BAGIAN 8: SYSTEMD SERVICE UNTUK RUST APPLICATION
#===============================================================================
log_step "MEMBUAT SYSTEMD SERVICE UNTUK RUST APP"

# Buat user khusus untuk aplikasi jika belum ada
if ! id -u rustapp >/dev/null 2>&1; then
    useradd -r -m -d /opt/apps/rust-backend -s /usr/sbin/nologin rustapp 2>/dev/null || true
fi

cat > /etc/systemd/system/rust-api.service << EOF
[Unit]
Description=Rust Backend API Service
After=network.target

[Service]
Type=simple
User=rustapp
Group=rustapp
WorkingDirectory=${RUST_WORKDIR}
ExecStart=${RUST_BINARY}
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

# Environment Variables
Environment="RUST_LOG=info"
Environment="RUST_BACKTRACE=1"
Environment="PORT=${RUST_PORT}"

# Security Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=${RUST_WORKDIR} /var/log/nginx /tmp

# Resource Limits
LimitNOFILE=65535
MemoryMax=1G

[Install]
WantedBy=multi-user.target
EOF

# Set permission untuk binary dan directory
touch "${RUST_BINARY}" 2>/dev/null || true
chmod +x "${RUST_BINARY}" 2>/dev/null || true
chown -R rustapp:rustapp "${RUST_WORKDIR}" 2>/dev/null || true

log_success "Systemd service 'rust-api' dibuat."

#===============================================================================
# BAGIAN 9: CERTBOT / SSL CERTIFICATES
#===============================================================================
log_step "KONFIGURASI SSL DENGAN CERTBOT"

# Dapatkan IP server untuk info DNS
SERVER_IP=$(curl -s ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}' || echo "YOUR_SERVER_IP")

echo -e "\n${YELLOW}πŸ” PENTING: DNS CONFIGURATION${NC}"
echo "Pastikan DNS record berikut sudah mengarah ke IP: ${CYAN}${SERVER_IP}${NC}"
echo "  • ${FRONTEND_DOMAIN}  → A record"
echo "  • ${BACKEND_DOMAIN}   → A record"
echo ""

read -p "Apakah DNS sudah siap dan ingin melanjutkan dengan Certbot? (y/n): " RUN_CERTBOT

if [[ "$RUN_CERTBOT" == "y" || "$RUN_CERTBOT" == "Y" ]]; then
    log_info "Meminta sertifikat SSL dari Let's Encrypt..."
    
    CERTBOT_ARGS="--nginx -d ${FRONTEND_DOMAIN} -d ${BACKEND_DOMAIN} --non-interactive --agree-tos --redirect"
    [[ -n "$SSL_EMAIL" ]] && CERTBOT_ARGS="$CERTBOT_ARGS --email ${SSL_EMAIL}"
    
    if certbot $CERTBOT_ARGS; then
        log_success "✓ Sertifikat SSL berhasil dipasang!"
        
        # Test auto-renew
        log_info "Menguji auto-renew SSL (dry-run)..."
        if certbot renew --dry-run --quiet; then
            log_success "✓ Auto-renew SSL berfungsi."
        else
            log_warn "⚠ Auto-renew test gagal. Cek dengan: certbot renew --dry-run"
        fi
        
        # Reload nginx untuk apply SSL config
        systemctl reload nginx
    else
        log_warn "⚠ Certbot gagal. Anda dapat menjalankannya manual nanti:"
        echo "  certbot --nginx -d ${FRONTEND_DOMAIN} -d ${BACKEND_DOMAIN}"
    fi
else
    log_warn "⚠ Certbot dilewati. Website akan accessible via HTTP saja sampai SSL dipasang manual."
fi

#===============================================================================
# BAGIAN 10: FINALISASI & RESTART SERVICES
#===============================================================================
log_step "FINALISASI INSTALASI"

log_info "Merestart dan enable services..."

systemctl daemon-reload
systemctl restart nginx
systemctl restart fail2ban

# Enable services di boot
systemctl enable nginx fail2ban rust-api 2>/dev/null || true

# Test konfigurasi akhir
log_info "Verifikasi konfigurasi..."
nginx -t >/dev/null 2>&1 && log_success "✓ Nginx config valid"
systemctl is-active --quiet fail2ban && log_success "✓ Fail2Ban running"

#===============================================================================
# BAGIAN 11: OUTPUT AKHIR & INSTRUKSI DEPLOY
#===============================================================================
echo -e "\n${GREEN}
╔════════════════════════════════════════════════════════════╗
║  πŸŽ‰ INSTALASI BERHASIL SELESAI!                           ║
╚════════════════════════════════════════════════════════════╝
${NC}"

echo -e "${YELLOW}πŸ“‹ INFORMASI AKSES${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
printf "%-22s : %s\n" "Frontend URL" "https://${FRONTEND_DOMAIN}"
printf "%-22s : %s\n" "Backend URL" "https://${BACKEND_DOMAIN}"
printf "%-22s : %s\n" "SSH Command" "ssh user@${SERVER_IP}"
printf "%-22s : %s\n" "Rust Service" "systemctl status rust-api"
printf "%-22s : %s\n" "Rust Port (internal)" "${RUST_PORT}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

echo -e "\n${YELLOW}πŸ›‘️  FAIL2BAN STATUS${NC}"
echo "• Max Retry: ${F2B_MAXRETRY}"
echo "• Ban Time: ${F2B_BANTIME}"
echo "• Find Time: ${F2B_FINDTIME}"
echo ""
echo "Commands:"
echo "  ${CYAN}fail2ban-client status${NC}"
echo "  ${CYAN}fail2ban-client status nginx-api-abuse${NC}"
echo "  ${CYAN}fail2ban-client status nginx-botsearch${NC}"
echo "  ${CYAN}fail2ban-client unban <IP>${NC}"

echo -e "\n${YELLOW}πŸš€ LANGKAH DEPLOY APLIKASI${NC}"
echo "
1️⃣  Upload Vue.js build ke frontend:
   ${CYAN}scp -r dist/* root@${SERVER_IP}:/var/www/app/${NC}
   
2️⃣  Upload Rust binary & config ke backend:
   ${CYAN}scp target/release/my_app root@${SERVER_IP}:${RUST_WORKDIR}/${NC}
   
3️⃣  Set permissions:
   ${CYAN}chown -R www-www-data /var/www/app${NC}
   ${CYAN}chown -R rustapp:rustapp ${RUST_WORKDIR}${NC}
   ${CYAN}chmod +x ${RUST_BINARY}${NC}
   
4️⃣  Start Rust application:
   ${CYAN}systemctl start rust-api${NC}
   
5️⃣  Verifikasi:
   ${CYAN}curl -I https://${FRONTEND_DOMAIN}${NC}
   ${CYAN}curl -I https://${BACKEND_DOMAIN}/health${NC}
"

echo -e "${YELLOW}πŸ”§ COMMANDS BERGUNA${NC}"
cat << 'EOF'
# Monitoring
tail -f /var/log/nginx/*-error.log
tail -f /var/log/nginx/*-access.log
journalctl -u rust-api -f

# Fail2Ban
fail2ban-client status
fail2ban-client status nginx-api-abuse
fail2ban-client unban <IP_ADDRESS>
fail2ban-client set nginx-api-abuse unbanip <IP_ADDRESS>

# SSL
certbot certificates
certbot renew --dry-run

# Nginx
nginx -t
systemctl reload nginx
systemctl status nginx

# View banned IPs
grep "Ban" /var/log/fail2ban.log | tail -20

# Test rate limiting
for i in {1..30}; do curl -s -o /dev/null -w "%{http_code}\n" https://${BACKEND_DOMAIN}/api/test; done
EOF

echo -e "\n${GREEN}Selamat! Stack Vue + Rust Anda siap produksi. πŸš€${NC}"

# Exit sukses
exit 0

 

No comments:

Post a Comment