| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113 |
- #!/bin/bash
- set -e # Exit on error
- # Colors for output
- RED='\033[0;31m'
- GREEN='\033[0;32m'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- NC='\033[0m' # No Color
- # Script directory
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- cd "$SCRIPT_DIR"
- # Configuration file
- CONFIG_FILE=".env"
- # Log function
- log() {
- echo -e "${BLUE}[INFO]${NC} $1"
- }
- success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
- }
- error() {
- echo -e "${RED}[ERROR]${NC} $1"
- }
- warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
- }
- # Banner
- banner() {
- echo ""
- echo -e "${BLUE}╔════════════════════════════════════════════════════╗${NC}"
- echo -e "${BLUE}║ ║${NC}"
- echo -e "${BLUE}║ ${GREEN}SaaS Platform Deployment Script${BLUE} ║${NC}"
- echo -e "${BLUE}║ ║${NC}"
- echo -e "${BLUE}╚════════════════════════════════════════════════════╝${NC}"
- echo ""
- }
- # Check if running as root
- check_root() {
- if [ "$EUID" -eq 0 ]; then
- error "Please do not run this script as root"
- error "Run without sudo, the script will ask for permissions when needed"
- exit 1
- fi
- }
- # Check system requirements
- check_requirements() {
- log "Checking system requirements..."
- local missing_deps=()
- # Check Docker
- if ! command -v docker &> /dev/null; then
- missing_deps+=("docker")
- fi
- # Check Docker Compose
- if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
- missing_deps+=("docker-compose")
- fi
- # Check dig (for DNS validation)
- if ! command -v dig &> /dev/null; then
- missing_deps+=("dnsutils/bind-tools")
- fi
- # Check curl
- if ! command -v curl &> /dev/null; then
- missing_deps+=("curl")
- fi
- # Check openssl
- if ! command -v openssl &> /dev/null; then
- missing_deps+=("openssl")
- fi
- if [ ${#missing_deps[@]} -ne 0 ]; then
- error "Missing required dependencies: ${missing_deps[*]}"
- echo ""
- log "Please install the missing dependencies:"
- echo ""
- echo "Ubuntu/Debian:"
- echo " sudo apt update"
- echo " sudo apt install -y docker.io docker-compose dnsutils curl openssl"
- echo ""
- echo "CentOS/RHEL:"
- echo " sudo yum install -y docker docker-compose bind-utils curl openssl"
- echo ""
- exit 1
- fi
- success "All requirements met"
- }
- # Validate domain format
- validate_domain_format() {
- local domain=$1
- # Check if domain matches pattern (basic validation)
- if [[ ! "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
- return 1
- fi
- # Check if domain has at least one dot
- if [[ ! "$domain" =~ \. ]]; then
- return 1
- fi
- return 0
- }
- # Check DNS records
- check_dns_records() {
- local domain=$1
- local console_domain="console.$domain"
- local api_domain="api.$domain"
- local wss_domain="wss.$domain"
- log "Checking DNS records for $domain..."
- # Check A record for main domain
- log "Checking A record for $domain..."
- if ! dig +short A "$domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
- error "No A record found for $domain"
- warning "Please add an A record pointing to your server's IP address"
- return 1
- fi
- local domain_ip=$(dig +short A "$domain" | head -n1)
- log "Found A record: $domain → $domain_ip"
- # Check A record for console subdomain
- log "Checking A record for $console_domain..."
- if ! dig +short A "$console_domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
- error "No A record found for $console_domain"
- warning "Please add an A record for $console_domain pointing to your server's IP address"
- return 1
- fi
- local console_ip=$(dig +short A "$console_domain" | head -n1)
- log "Found A record: $console_domain → $console_ip"
- # Check A record for API subdomain
- log "Checking A record for $api_domain..."
- if ! dig +short A "$api_domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
- error "No A record found for $api_domain"
- warning "Please add an A record for $api_domain pointing to your server's IP address"
- return 1
- fi
- local api_ip=$(dig +short A "$api_domain" | head -n1)
- log "Found A record: $api_domain → $api_ip"
- # Check A record for WSS subdomain
- log "Checking A record for $wss_domain..."
- if ! dig +short A "$wss_domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
- error "No A record found for $wss_domain"
- warning "Please add an A record for $wss_domain pointing to your server's IP address"
- return 1
- fi
- local wss_ip=$(dig +short A "$wss_domain" | head -n1)
- log "Found A record: $wss_domain → $wss_ip"
- # Check wildcard record
- log "Checking wildcard DNS record for *.$domain..."
- local test_subdomain="test-$(date +%s).$domain"
- if ! dig +short A "$test_subdomain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
- error "No wildcard (*.$domain) DNS record found"
- warning "Please add a wildcard A record: *.$domain → your server's IP"
- warning "This is required for hosting applications on subdomains"
- return 1
- fi
- local wildcard_ip=$(dig +short A "$test_subdomain" | head -n1)
- log "Found wildcard record: *.$domain → $wildcard_ip"
- # Verify all IPs match
- if [ "$domain_ip" != "$console_ip" ] || [ "$domain_ip" != "$api_ip" ] || [ "$domain_ip" != "$wss_ip" ] || [ "$domain_ip" != "$wildcard_ip" ]; then
- warning "DNS records point to different IP addresses:"
- warning " $domain → $domain_ip"
- warning " $console_domain → $console_ip"
- warning " $api_domain → $api_ip"
- warning " $wss_domain → $wss_ip"
- warning " *.$domain → $wildcard_ip"
- warning "They should all point to the same IP address"
- read -p "Continue anyway? (y/N): " -n 1 -r
- echo
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
- return 1
- fi
- fi
- success "DNS records validated successfully"
- return 0
- }
- # Get user input for domain
- get_domain_input() {
- # Detect server's public IP
- local server_ip=$(curl -s ifconfig.me 2>/dev/null || curl -s icanhazip.com 2>/dev/null || echo "your-server-ip")
- echo ""
- log "Domain Configuration"
- echo ""
- echo "Please enter your domain name (e.g., example.com)"
- echo ""
- log "Detected server IP: ${GREEN}$server_ip${NC}"
- echo ""
- echo "Make sure you have the following DNS records configured:"
- echo " - A record: yourdomain.com → $server_ip"
- echo " - A record: console.yourdomain.com → $server_ip"
- echo " - A record: api.yourdomain.com → $server_ip"
- echo " - A record: wss.yourdomain.com → $server_ip"
- echo " - A record: *.yourdomain.com → $server_ip (wildcard)"
- echo ""
- echo "Replace 'yourdomain.com' with your actual domain name."
- echo ""
- while true; do
- read -p "Domain name: " DOMAIN
- # Trim whitespace
- DOMAIN=$(echo "$DOMAIN" | xargs)
- if [ -z "$DOMAIN" ]; then
- error "Domain cannot be empty"
- continue
- fi
- # Validate domain format
- if ! validate_domain_format "$DOMAIN"; then
- error "Invalid domain format: $DOMAIN"
- error "Please enter a valid domain name (e.g., example.com)"
- continue
- fi
- # Show DNS configuration with actual domain and IP
- echo ""
- log "Required DNS Configuration for: ${GREEN}$DOMAIN${NC}"
- echo ""
- echo " ┌─────────────────────────────────────────────────────────┐"
- echo " │ Configure these DNS A records in your DNS provider: │"
- echo " ├─────────────────────────────────────────────────────────┤"
- echo " │ Record: $DOMAIN"
- echo " │ Type: A"
- echo " │ Value: $server_ip"
- echo " ├─────────────────────────────────────────────────────────┤"
- echo " │ Record: console.$DOMAIN"
- echo " │ Type: A"
- echo " │ Value: $server_ip"
- echo " ├─────────────────────────────────────────────────────────┤"
- echo " │ Record: api.$DOMAIN"
- echo " │ Type: A"
- echo " │ Value: $server_ip"
- echo " ├─────────────────────────────────────────────────────────┤"
- echo " │ Record: wss.$DOMAIN"
- echo " │ Type: A"
- echo " │ Value: $server_ip"
- echo " ├─────────────────────────────────────────────────────────┤"
- echo " │ Record: *.$DOMAIN (wildcard)"
- echo " │ Type: A"
- echo " │ Value: $server_ip"
- echo " └─────────────────────────────────────────────────────────┘"
- echo ""
- # Check DNS records
- if check_dns_records "$DOMAIN"; then
- success "Domain validated: $DOMAIN"
- break
- else
- echo ""
- warning "DNS validation failed for $DOMAIN"
- echo ""
- log "Make sure all 5 DNS records point to: $server_ip"
- log "DNS propagation can take up to 48 hours"
- echo ""
- read -p "Try again with the same domain (wait for DNS)? (Y/n): " -n 1 -r
- echo
- if [[ $REPLY =~ ^[Nn]$ ]]; then
- read -p "Enter a different domain? (Y/n): " -n 1 -r
- echo
- if [[ $REPLY =~ ^[Nn]$ ]]; then
- exit 1
- fi
- else
- # Wait a bit and try again
- log "Waiting 10 seconds for DNS propagation..."
- sleep 10
- fi
- fi
- done
- }
- # Get user email for Let's Encrypt
- get_email_input() {
- echo ""
- log "Let's Encrypt Configuration"
- echo ""
- echo "Let's Encrypt requires an email address for:"
- echo " - Certificate expiration notifications"
- echo " - Important account updates"
- echo ""
- while true; do
- read -p "Email address: " LE_EMAIL
- # Trim whitespace
- LE_EMAIL=$(echo "$LE_EMAIL" | xargs)
- if [ -z "$LE_EMAIL" ]; then
- error "Email cannot be empty"
- continue
- fi
- # Basic email validation
- if [[ ! "$LE_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
- error "Invalid email format"
- continue
- fi
- success "Email validated: $LE_EMAIL"
- break
- done
- }
- # Install Certbot
- install_certbot() {
- log "Checking Certbot installation..."
- if command -v certbot &> /dev/null; then
- success "Certbot is already installed"
- certbot --version
- return 0
- fi
- log "Installing Certbot..."
- # Detect OS
- if [ -f /etc/debian_version ]; then
- # Debian/Ubuntu
- sudo apt update
- sudo apt install -y certbot python3-certbot-nginx
- elif [ -f /etc/redhat-release ]; then
- # CentOS/RHEL
- sudo yum install -y certbot python3-certbot-nginx
- else
- error "Unsupported operating system"
- error "Please install Certbot manually: https://certbot.eff.org/"
- exit 1
- fi
- success "Certbot installed successfully"
- }
- # Generate Let's Encrypt certificates
- generate_letsencrypt_certs() {
- local domain=$1
- local email=$2
- local console_domain="console.$domain"
- local api_domain="api.$domain"
- local wss_domain="wss.$domain"
- log "Generating Let's Encrypt certificates..."
- echo ""
- log "This will request certificates for:"
- log " - $domain"
- log " - $console_domain"
- log " - $api_domain"
- log " - $wss_domain"
- echo ""
- # Stop nginx if running (to free port 80)
- if docker ps | grep saas-gateway &> /dev/null; then
- log "Stopping Nginx container to free port 80..."
- docker stop saas-gateway || true
- fi
- # Request certificate using standalone mode
- log "Requesting certificate (this may take a minute)..."
- sudo certbot certonly \
- --standalone \
- --non-interactive \
- --agree-tos \
- --email "$email" \
- --domains "$domain,$console_domain,$api_domain,$wss_domain" \
- --preferred-challenges http \
- || {
- error "Failed to generate Let's Encrypt certificate"
- error "Common issues:"
- error " 1. Port 80 is not accessible from the internet"
- error " 2. DNS records are not properly configured"
- error " 3. Domain is already registered with Let's Encrypt"
- exit 1
- }
- success "Let's Encrypt certificates generated successfully"
- # Copy certificates to ssl directory
- log "Copying certificates to ssl directory..."
- sudo cp "/etc/letsencrypt/live/$domain/fullchain.pem" "$SCRIPT_DIR/ssl/certificate.crt"
- sudo cp "/etc/letsencrypt/live/$domain/privkey.pem" "$SCRIPT_DIR/ssl/private.key"
- sudo chown $(whoami):$(whoami) "$SCRIPT_DIR/ssl/certificate.crt" "$SCRIPT_DIR/ssl/private.key"
- chmod 644 "$SCRIPT_DIR/ssl/certificate.crt"
- chmod 600 "$SCRIPT_DIR/ssl/private.key"
- success "Certificates copied to $SCRIPT_DIR/ssl/"
- }
- # Generate DH parameters if not exists
- generate_dhparam() {
- if [ -f "$SCRIPT_DIR/ssl/dhparam.pem" ]; then
- log "DH parameters already exist"
- return 0
- fi
- log "Generating DH parameters (this may take several minutes)..."
- openssl dhparam -out "$SCRIPT_DIR/ssl/dhparam.pem" 2048
- chmod 644 "$SCRIPT_DIR/ssl/dhparam.pem"
- success "DH parameters generated"
- }
- # Create environment file
- create_env_file() {
- local domain=$1
- log "Creating environment configuration..."
- # Generate random secrets
- local db_password=$(openssl rand -base64 32)
- local redis_password=$(openssl rand -base64 32)
- local jwt_secret=$(openssl rand -base64 64)
- local minio_access_key=$(openssl rand -hex 16)
- local minio_secret_key=$(openssl rand -base64 32)
- cat > "$CONFIG_FILE" <<EOF
- # Database Configuration
- POSTGRES_DB=saas_platform
- POSTGRES_USER=saas_user
- POSTGRES_PASSWORD=$db_password
- # Redis Configuration
- REDIS_PASSWORD=$redis_password
- # JWT Configuration
- JWT_SECRET=$jwt_secret
- # Domain Configuration
- DOMAIN=$domain
- CONSOLE_DOMAIN=console.$domain
- # MinIO Configuration (S3-compatible storage)
- MINIO_ROOT_USER=$minio_access_key
- MINIO_ROOT_PASSWORD=$minio_secret_key
- # Application Configuration
- NODE_ENV=production
- PORT=3000
- # Let's Encrypt Email
- LETSENCRYPT_EMAIL=$LE_EMAIL
- EOF
- chmod 600 "$CONFIG_FILE"
- success "Environment file created: $CONFIG_FILE"
- }
- # Update Nginx configuration for production
- update_nginx_config() {
- local domain=$1
- local console_domain="console.$domain"
- local api_domain="api.$domain"
- local wss_domain="wss.$domain"
- log "Updating Nginx configuration for production..."
- # Backup existing config
- if [ -f "nginx/conf.d/https.conf" ]; then
- cp "nginx/conf.d/https.conf" "nginx/conf.d/https.conf.bak"
- fi
- # Create production Nginx config
- cat > "nginx/conf.d/production.conf" <<EOF
- # Production HTTPS Configuration
- # Main domain: $domain
- # Console: $console_domain
- # API: $api_domain
- # WebSocket: $wss_domain
- # API subdomain
- server {
- listen 443 ssl http2;
- server_name $api_domain;
- # SSL Configuration
- ssl_certificate /etc/nginx/ssl/certificate.crt;
- ssl_certificate_key /etc/nginx/ssl/private.key;
- ssl_dhparam /etc/nginx/ssl/dhparam.pem;
- # SSL Security Settings
- 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 shared:SSL:10m;
- ssl_session_timeout 10m;
- ssl_session_tickets off;
- # HSTS
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- # Security headers
- add_header X-Frame-Options DENY;
- add_header X-Content-Type-Options nosniff;
- add_header X-XSS-Protection "1; mode=block";
- add_header Referrer-Policy "strict-origin-when-cross-origin";
- # Authentication routes
- location /auth {
- rewrite ^/auth/(.*) /auth/\$1 break;
- proxy_pass http://auth_service;
- 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;
- add_header Access-Control-Allow-Origin * always;
- add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
- add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
- }
- # API routes (root level - no /api prefix)
- location / {
- proxy_pass http://api_service;
- 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;
- add_header Access-Control-Allow-Origin * always;
- add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
- add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
- }
- # Health check
- location /health {
- access_log off;
- return 200 "healthy\n";
- add_header Content-Type text/plain;
- }
- }
- # WebSocket subdomain (wss.domain.com)
- server {
- listen 443 ssl http2;
- server_name $wss_domain;
- # SSL Configuration
- ssl_certificate /etc/nginx/ssl/certificate.crt;
- ssl_certificate_key /etc/nginx/ssl/private.key;
- ssl_dhparam /etc/nginx/ssl/dhparam.pem;
- # SSL Security Settings
- 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 shared:SSL:10m;
- ssl_session_timeout 10m;
- ssl_session_tickets off;
- # HSTS
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- # Security headers
- add_header X-Frame-Options DENY;
- add_header X-Content-Type-Options nosniff;
- add_header X-XSS-Protection "1; mode=block";
- # WebSocket endpoint (root level)
- location / {
- proxy_pass http://realtime_service;
- 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_read_timeout 86400s;
- proxy_send_timeout 86400s;
- proxy_connect_timeout 10s;
- proxy_buffering off;
- add_header Access-Control-Allow-Origin * always;
- add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
- add_header Access-Control-Allow-Headers "Authorization, Content-Type, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol" always;
- }
- }
- # Console subdomain (Dashboard)
- server {
- listen 443 ssl http2;
- server_name $console_domain;
- # SSL Configuration
- ssl_certificate /etc/nginx/ssl/certificate.crt;
- ssl_certificate_key /etc/nginx/ssl/private.key;
- ssl_dhparam /etc/nginx/ssl/dhparam.pem;
- # SSL Security Settings
- 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 shared:SSL:10m;
- ssl_session_timeout 10m;
- ssl_session_tickets off;
- # HSTS
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- # Security headers
- add_header X-Frame-Options DENY;
- add_header X-Content-Type-Options nosniff;
- add_header X-XSS-Protection "1; mode=block";
- add_header Referrer-Policy "strict-origin-when-cross-origin";
- # Storage routes
- location /storage/ {
- client_max_body_size 100M;
- proxy_pass http://storage_service;
- 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;
- }
- # Health check
- location /health {
- access_log off;
- return 200 "healthy\n";
- add_header Content-Type text/plain;
- }
- # Dashboard (default)
- location / {
- proxy_pass http://dashboard_service;
- 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;
- }
- }
- # Main domain (will serve hello world app)
- server {
- listen 443 ssl http2;
- server_name $domain;
- # SSL Configuration
- ssl_certificate /etc/nginx/ssl/certificate.crt;
- ssl_certificate_key /etc/nginx/ssl/private.key;
- ssl_dhparam /etc/nginx/ssl/dhparam.pem;
- # SSL Security Settings
- 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 shared:SSL:10m;
- ssl_session_timeout 10m;
- ssl_session_tickets off;
- # HSTS
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- # Security headers
- add_header X-Frame-Options SAMEORIGIN;
- add_header X-Content-Type-Options nosniff;
- add_header X-XSS-Protection "1; mode=block";
- # Serve hello world application
- location / {
- root /usr/share/nginx/html/hello-world;
- index index.html;
- try_files \$uri \$uri/ /index.html;
- }
- # Health check
- location /health {
- access_log off;
- return 200 "healthy\n";
- add_header Content-Type text/plain;
- }
- }
- # Wildcard subdomain for hosted applications (will be enabled when app deployment is ready)
- server {
- listen 443 ssl http2;
- server_name *.$domain;
- # Exclude reserved subdomains
- if (\$host ~ "^(console|api|wss)\.$domain\$") {
- return 404;
- }
- # SSL Configuration
- ssl_certificate /etc/nginx/ssl/certificate.crt;
- ssl_certificate_key /etc/nginx/ssl/private.key;
- ssl_dhparam /etc/nginx/ssl/dhparam.pem;
- # SSL Security Settings (same as above)
- 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;
- # Security headers
- add_header X-Frame-Options SAMEORIGIN;
- add_header X-Content-Type-Options nosniff;
- # Placeholder for application routing
- location / {
- return 503 "Application deployment feature coming soon";
- add_header Content-Type text/plain;
- }
- }
- # HTTP to HTTPS redirect
- server {
- listen 80;
- server_name $domain $console_domain $api_domain $wss_domain *.$domain;
- # Let's Encrypt ACME challenge
- location /.well-known/acme-challenge/ {
- root /var/www/certbot;
- }
- # Health check (allow over HTTP)
- location /health {
- access_log off;
- return 200 "healthy\n";
- add_header Content-Type text/plain;
- }
- # Redirect all other traffic to HTTPS
- location / {
- return 301 https://\$server_name\$request_uri;
- }
- }
- EOF
- # Disable default and https configs
- if [ -f "nginx/conf.d/default.conf" ]; then
- mv "nginx/conf.d/default.conf" "nginx/conf.d/default.conf.disabled"
- fi
- if [ -f "nginx/conf.d/https.conf" ]; then
- mv "nginx/conf.d/https.conf" "nginx/conf.d/https.conf.disabled"
- fi
- success "Nginx production configuration created"
- }
- # Create hello world application
- create_hello_world_app() {
- local domain=$1
- local server_ip=$(curl -s ifconfig.me || curl -s icanhazip.com || echo "Unknown")
- log "Creating hello world application..."
- mkdir -p "apps/hello-world"
- cat > "apps/hello-world/index.html" <<EOF
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Welcome to $domain</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 20px;
- }
- .container {
- background: white;
- border-radius: 20px;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
- padding: 60px;
- max-width: 600px;
- text-align: center;
- }
- h1 {
- font-size: 3em;
- color: #333;
- margin-bottom: 20px;
- }
- .emoji {
- font-size: 4em;
- margin-bottom: 20px;
- animation: wave 1s ease-in-out infinite;
- }
- @keyframes wave {
- 0%, 100% { transform: rotate(0deg); }
- 25% { transform: rotate(20deg); }
- 75% { transform: rotate(-20deg); }
- }
- p {
- font-size: 1.2em;
- color: #666;
- margin-bottom: 30px;
- line-height: 1.6;
- }
- .links {
- display: flex;
- gap: 15px;
- justify-content: center;
- flex-wrap: wrap;
- }
- a {
- display: inline-block;
- padding: 15px 30px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- text-decoration: none;
- border-radius: 10px;
- font-weight: 600;
- transition: transform 0.2s, box-shadow 0.2s;
- }
- a:hover {
- transform: translateY(-2px);
- box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
- }
- .info {
- margin-top: 40px;
- padding-top: 40px;
- border-top: 2px solid #eee;
- }
- .info-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin: 10px 0;
- padding: 10px;
- background: #f8f9fa;
- border-radius: 8px;
- }
- .info-label {
- font-weight: 600;
- color: #667eea;
- }
- .info-value {
- color: #666;
- font-family: monospace;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="emoji">👋</div>
- <h1>Hello World!</h1>
- <p>Your SaaS Platform is successfully deployed and running!</p>
- <div class="links">
- <a href="https://console.$domain">Open Dashboard</a>
- <a href="https://api.$domain/health">API Health</a>
- </div>
- <div class="info">
- <div class="info-item">
- <span class="info-label">Main Domain:</span>
- <span class="info-value">$domain</span>
- </div>
- <div class="info-item">
- <span class="info-label">Server IP:</span>
- <span class="info-value">$server_ip</span>
- </div>
- <div class="info-item">
- <span class="info-label">Dashboard:</span>
- <span class="info-value">console.$domain</span>
- </div>
- <div class="info-item">
- <span class="info-label">API:</span>
- <span class="info-value">api.$domain</span>
- </div>
- <div class="info-item">
- <span class="info-label">WebSocket:</span>
- <span class="info-value">wss.$domain</span>
- </div>
- <div class="info-item">
- <span class="info-label">Status:</span>
- <span class="info-value">🟢 Running</span>
- </div>
- <div class="info-item">
- <span class="info-label">SSL:</span>
- <span class="info-value">🔒 Let's Encrypt</span>
- </div>
- </div>
- </div>
- </body>
- </html>
- EOF
- success "Hello world application created"
- }
- # Update docker-compose for production
- update_docker_compose() {
- local domain=$1
- log "Updating docker-compose.yml for production..."
- # Update api-gateway to expose port 443 and mount hello-world app
- if ! grep -q "443:443" docker-compose.yml; then
- log "Updating Nginx ports configuration..."
- # This is a simplified approach - in production you'd use yq or similar
- sed -i 's/- "8443:443"/- "443:443"/' docker-compose.yml || true
- fi
- # Add hello-world volume mount if not exists
- if ! grep -q "hello-world" docker-compose.yml; then
- log "Adding hello-world application mount..."
- # Note: This would need proper YAML editing in production
- fi
- success "Docker Compose configuration updated"
- }
- # Start services
- start_services() {
- log "Starting services with Docker Compose..."
- # Pull latest images
- log "Pulling Docker images..."
- docker-compose pull
- # Build services
- log "Building services..."
- docker-compose build
- # Start services
- log "Starting all services..."
- docker-compose up -d
- # Wait for services to be healthy
- log "Waiting for services to be healthy..."
- sleep 10
- # Check service status
- docker-compose ps
- success "All services started successfully"
- }
- # Setup certbot auto-renewal
- setup_cert_renewal() {
- local domain=$1
- log "Setting up automatic certificate renewal..."
- # Create renewal script
- cat > "$SCRIPT_DIR/renew-certs.sh" <<'EOF'
- #!/bin/bash
- # Auto-renewal script for Let's Encrypt certificates
- # Stop nginx
- docker stop saas-gateway
- # Renew certificates
- certbot renew --quiet
- # Copy renewed certificates
- cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem /data/appserver/ssl/certificate.crt
- cp /etc/letsencrypt/live/$DOMAIN/privkey.pem /data/appserver/ssl/private.key
- # Start nginx
- docker start saas-gateway
- # Reload nginx
- docker exec saas-gateway nginx -s reload
- EOF
- chmod +x "$SCRIPT_DIR/renew-certs.sh"
- # Add cron job (runs twice daily)
- (crontab -l 2>/dev/null; echo "0 0,12 * * * $SCRIPT_DIR/renew-certs.sh >> $SCRIPT_DIR/logs/cert-renewal.log 2>&1") | crontab -
- success "Certificate auto-renewal configured"
- }
- # Print deployment summary
- print_summary() {
- local domain=$1
- local console_domain="console.$domain"
- local api_domain="api.$domain"
- local wss_domain="wss.$domain"
- echo ""
- echo -e "${GREEN}╔════════════════════════════════════════════════════╗${NC}"
- echo -e "${GREEN}║ ║${NC}"
- echo -e "${GREEN}║ ${BLUE}Deployment Completed Successfully!${GREEN} ║${NC}"
- echo -e "${GREEN}║ ║${NC}"
- echo -e "${GREEN}╚════════════════════════════════════════════════════╝${NC}"
- echo ""
- echo -e "${BLUE}📍 Your Platform URLs:${NC}"
- echo ""
- echo -e " 🌐 Main Website: ${GREEN}https://$domain${NC}"
- echo -e " 🎛️ Dashboard: ${GREEN}https://$console_domain${NC}"
- echo -e " 🔌 API Endpoint: ${GREEN}https://$api_domain${NC}"
- echo -e " 📡 WebSocket (WSS): ${GREEN}wss://$wss_domain${NC}"
- echo -e " 📊 API Health: ${GREEN}https://$api_domain/health${NC}"
- echo ""
- echo -e "${BLUE}🔐 Security:${NC}"
- echo ""
- echo -e " ✅ SSL/TLS certificates: Let's Encrypt"
- echo -e " ✅ Auto-renewal: Configured (twice daily)"
- echo -e " ✅ HTTPS redirect: Enabled"
- echo -e " ✅ Security headers: Enabled"
- echo ""
- echo -e "${BLUE}📁 Important Files:${NC}"
- echo ""
- echo -e " 📄 Configuration: ${YELLOW}$CONFIG_FILE${NC}"
- echo -e " 🔑 Certificates: ${YELLOW}$SCRIPT_DIR/ssl/${NC}"
- echo -e " 📝 Logs: ${YELLOW}docker-compose logs -f${NC}"
- echo ""
- echo -e "${BLUE}🚀 Next Steps:${NC}"
- echo ""
- echo " 1. Visit https://$console_domain to access the dashboard"
- echo " 2. Create your admin account"
- echo " 3. Check https://$domain to see the hello world app"
- echo " 4. Monitor services: docker-compose ps"
- echo " 5. View logs: docker-compose logs -f [service-name]"
- echo ""
- echo -e "${YELLOW}⚠️ Important:${NC}"
- echo ""
- echo " - Keep your $CONFIG_FILE file secure (contains passwords)"
- echo " - Backup your database regularly"
- echo " - Monitor certificate expiration (auto-renewed)"
- echo ""
- success "Deployment completed! 🎉"
- echo ""
- }
- # Main deployment function
- main() {
- banner
- check_root
- check_requirements
- get_domain_input
- get_email_input
- echo ""
- log "Deployment Summary:"
- echo ""
- echo " Domain: $DOMAIN"
- echo " Console: console.$DOMAIN"
- echo " Email: $LE_EMAIL"
- echo ""
- read -p "Proceed with deployment? (y/N): " -n 1 -r
- echo
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
- warning "Deployment cancelled"
- exit 0
- fi
- install_certbot
- generate_letsencrypt_certs "$DOMAIN" "$LE_EMAIL"
- generate_dhparam
- create_env_file "$DOMAIN"
- update_nginx_config "$DOMAIN"
- create_hello_world_app "$DOMAIN"
- update_docker_compose "$DOMAIN"
- start_services
- setup_cert_renewal "$DOMAIN"
- print_summary "$DOMAIN"
- }
- # Run main function
- main "$@"
|