Files
laca-website/vps-deploy.sh
PhongPham b146127513 feat: Update deployment script to support root user execution
- Modified vps-deploy.sh to run as both root and non-root user
- Added dynamic sudo command handling (SUDO_CMD variable)
- Set appropriate service user (www-data for root, current user for non-root)
- Updated all system commands to use dynamic sudo
- Enhanced PostgreSQL setup for root execution
- Updated PM2 configuration with proper user permissions
- Added deployment update script with root support
- Improved security and flexibility for VPS deployment
2025-08-12 07:39:58 +07:00

788 lines
20 KiB
Bash
Executable File

#!/bin/bash
# 🚀 Laca City Website - Complete VPS Deployment Script
# This script sets up your entire website on a VPS with domain configuration
# Run this script on your VPS after uploading your project files
# Can be run as root user or regular user with sudo privileges
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Configuration Variables (Modify these before running)
DOMAIN_NAME="" # e.g., "yourdomain.com"
EMAIL="" # Your email for SSL certificate
PROJECT_NAME="laca-city"
DB_NAME="laca_city_db"
DB_USER="laca_admin"
DB_PASSWORD="" # Will be generated if empty
FRONTEND_PORT=3000
BACKEND_PORT=3001
NGINX_CONFIG_NAME="laca-city"
# System Configuration
UBUNTU_VERSION=$(lsb_release -rs 2>/dev/null || echo "unknown")
CURRENT_USER=$(whoami)
PROJECT_DIR="/var/www/$PROJECT_NAME"
BACKUP_DIR="/var/backups/$PROJECT_NAME"
# Function to print colored output
print_header() {
echo -e "\n${PURPLE}========================================${NC}"
echo -e "${PURPLE}$1${NC}"
echo -e "${PURPLE}========================================${NC}\n"
}
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_step() {
echo -e "${CYAN}[STEP]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to prompt for user input
prompt_input() {
local prompt="$1"
local var_name="$2"
local default="$3"
if [ -n "$default" ]; then
read -p "$prompt [$default]: " input
eval "$var_name=\${input:-$default}"
else
read -p "$prompt: " input
eval "$var_name=\$input"
fi
}
# Function to generate random password
generate_password() {
openssl rand -base64 32 | tr -d "=+/" | cut -c1-25
}
# Function to create systemd service
create_systemd_service() {
local service_name="$1"
local description="$2"
local exec_start="$3"
local working_dir="$4"
local user="$5"
$SUDO_CMD tee "/etc/systemd/system/$service_name.service" > /dev/null <<EOF
[Unit]
Description=$description
After=network.target
[Service]
Type=simple
User=$user
WorkingDirectory=$working_dir
ExecStart=$exec_start
Restart=always
RestartSec=10
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
$SUDO_CMD systemctl daemon-reload
$SUDO_CMD systemctl enable "$service_name"
}
# Function to setup firewall
setup_firewall() {
print_step "Setting up UFW firewall..."
$SUDO_CMD ufw --force reset
$SUDO_CMD ufw default deny incoming
$SUDO_CMD ufw default allow outgoing
# Allow SSH
$SUDO_CMD ufw allow ssh
$SUDO_CMD ufw allow 22
# Allow HTTP and HTTPS
$SUDO_CMD ufw allow 80
$SUDO_CMD ufw allow 443
# Allow specific ports for development (optional)
$SUDO_CMD ufw allow $FRONTEND_PORT
$SUDO_CMD ufw allow $BACKEND_PORT
$SUDO_CMD ufw --force enable
print_success "Firewall configured"
}
# Function to setup Nginx
setup_nginx() {
print_step "Setting up Nginx..."
$SUDO_CMD apt update
$SUDO_CMD apt install -y nginx
# Remove default configuration
$SUDO_CMD rm -f /etc/nginx/sites-enabled/default
# Create Nginx configuration
$SUDO_CMD tee "/etc/nginx/sites-available/$NGINX_CONFIG_NAME" > /dev/null <<EOF
# Upstream servers
upstream frontend {
server 127.0.0.1:$FRONTEND_PORT;
}
upstream backend {
server 127.0.0.1:$BACKEND_PORT;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name $DOMAIN_NAME www.$DOMAIN_NAME;
return 301 https://\$server_name\$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl http2;
server_name $DOMAIN_NAME www.$DOMAIN_NAME;
# SSL configuration (will be configured by Certbot)
ssl_certificate /etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN_NAME/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript;
# API routes
location /api/ {
proxy_pass http://backend;
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_cache_bypass \$http_upgrade;
}
# Socket.IO WebSocket
location /socket.io/ {
proxy_pass http://backend;
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;
}
# Static files from Next.js
location /_next/static/ {
proxy_pass http://frontend;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Frontend routes
location / {
proxy_pass http://frontend;
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_cache_bypass \$http_upgrade;
}
# Error pages
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/html;
}
}
EOF
# Enable the site
$SUDO_CMD ln -sf "/etc/nginx/sites-available/$NGINX_CONFIG_NAME" "/etc/nginx/sites-enabled/"
# Test Nginx configuration
$SUDO_CMD nginx -t
print_success "Nginx configured"
}
# Function to setup SSL with Let's Encrypt
setup_ssl() {
print_step "Setting up SSL certificate with Let's Encrypt..."
# Install Certbot
$SUDO_CMD apt install -y certbot python3-certbot-nginx
# Stop Nginx temporarily
$SUDO_CMD systemctl stop nginx
# Obtain SSL certificate
$SUDO_CMD certbot --nginx -d "$DOMAIN_NAME" -d "www.$DOMAIN_NAME" --email "$EMAIL" --agree-tos --non-interactive
# Start Nginx
$SUDO_CMD systemctl start nginx
$SUDO_CMD systemctl enable nginx
# Setup automatic renewal
$SUDO_CMD crontab -l 2>/dev/null | { cat; echo "0 3 * * * /usr/bin/certbot renew --quiet"; } | $SUDO_CMD crontab -
print_success "SSL certificate configured and auto-renewal setup"
}
# Function to setup PostgreSQL
setup_postgresql() {
print_step "Setting up PostgreSQL..."
$SUDO_CMD apt update
$SUDO_CMD apt install -y postgresql postgresql-contrib
# Start and enable PostgreSQL
$SUDO_CMD systemctl start postgresql
$SUDO_CMD systemctl enable postgresql
# Generate database password if not provided
if [ -z "$DB_PASSWORD" ]; then
DB_PASSWORD=$(generate_password)
print_status "Generated database password: $DB_PASSWORD"
fi
# Create database and user
if [ "$CURRENT_USER" = "root" ]; then
su - postgres -c "psql" <<EOF
CREATE DATABASE $DB_NAME;
CREATE USER $DB_USER WITH ENCRYPTED PASSWORD '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
ALTER USER $DB_USER CREATEDB;
\q
EOF
else
sudo -u postgres psql <<EOF
CREATE DATABASE $DB_NAME;
CREATE USER $DB_USER WITH ENCRYPTED PASSWORD '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
ALTER USER $DB_USER CREATEDB;
\q
EOF
fi
print_success "PostgreSQL configured"
}
# Function to setup Redis
setup_redis() {
print_step "Setting up Redis..."
$SUDO_CMD apt update
$SUDO_CMD apt install -y redis-server
# Configure Redis
$SUDO_CMD sed -i 's/^supervised no/supervised systemd/' /etc/redis/redis.conf
$SUDO_CMD systemctl restart redis.service
$SUDO_CMD systemctl enable redis.service
print_success "Redis configured"
}
# Function to install Node.js
install_nodejs() {
print_step "Installing Node.js..."
# Install Node.js 18.x
curl -fsSL https://deb.nodesource.com/setup_18.x | $SUDO_CMD -E bash -
$SUDO_CMD apt-get install -y nodejs
# Install PM2 globally
$SUDO_CMD npm install -g pm2
print_success "Node.js and PM2 installed"
}
# Function to setup project files
setup_project() {
print_step "Setting up project files..."
# Create project directory
$SUDO_CMD mkdir -p "$PROJECT_DIR"
$SUDO_CMD mkdir -p "$BACKUP_DIR"
# Copy files to project directory (assuming current directory has the project)
$SUDO_CMD cp -r ./* "$PROJECT_DIR/" 2>/dev/null || true
$SUDO_CMD chown -R "$SERVICE_USER:$SERVICE_USER" "$PROJECT_DIR"
cd "$PROJECT_DIR"
# Install dependencies
print_status "Installing frontend dependencies..."
cd "$PROJECT_DIR/frontend"
npm ci --production
print_status "Installing backend dependencies..."
cd "$PROJECT_DIR/backend"
npm ci --production
print_success "Project dependencies installed"
}
# Function to create environment files
create_env_files() {
print_step "Creating environment files..."
# Backend environment
cat > "$PROJECT_DIR/backend/.env" <<EOF
NODE_ENV=production
PORT=$BACKEND_PORT
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=$DB_NAME
DB_USERNAME=$DB_USER
DB_PASSWORD=$DB_PASSWORD
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
# JWT Configuration
JWT_SECRET=$(generate_password)
JWT_EXPIRES_IN=7d
# API Configuration
API_PREFIX=api
API_VERSION=v1
# CORS Configuration
CORS_ORIGIN=https://$DOMAIN_NAME
# Rate Limiting
RATE_LIMIT_TTL=60
RATE_LIMIT_LIMIT=100
EOF
# Frontend environment
cat > "$PROJECT_DIR/frontend/.env.production" <<EOF
NODE_ENV=production
NEXT_PUBLIC_API_URL=https://$DOMAIN_NAME/api
NEXT_PUBLIC_SOCKET_URL=https://$DOMAIN_NAME
NEXT_PUBLIC_DOMAIN=$DOMAIN_NAME
EOF
print_success "Environment files created"
}
# Function to build applications
build_applications() {
print_step "Building applications..."
# Build backend
cd "$PROJECT_DIR/backend"
npm run build
# Build frontend
cd "$PROJECT_DIR/frontend"
npm run build
print_success "Applications built successfully"
}
# Function to setup PM2 processes
setup_pm2() {
print_step "Setting up PM2 processes..."
cd "$PROJECT_DIR"
# Create PM2 ecosystem file
cat > ecosystem.config.js <<EOF
module.exports = {
apps: [
{
name: 'laca-city-backend',
script: './backend/dist/main.js',
cwd: './backend',
instances: 1,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: $BACKEND_PORT
},
error_file: '/var/log/pm2/laca-city-backend-error.log',
out_file: '/var/log/pm2/laca-city-backend-out.log',
log_file: '/var/log/pm2/laca-city-backend.log',
max_restarts: 10,
min_uptime: '10s',
uid: '$SERVICE_USER',
gid: '$SERVICE_USER'
},
{
name: 'laca-city-frontend',
script: 'npm',
args: 'start',
cwd: './frontend',
instances: 1,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: $FRONTEND_PORT
},
error_file: '/var/log/pm2/laca-city-frontend-error.log',
out_file: '/var/log/pm2/laca-city-frontend-out.log',
log_file: '/var/log/pm2/laca-city-frontend.log',
max_restarts: 10,
min_uptime: '10s',
uid: '$SERVICE_USER',
gid: '$SERVICE_USER'
}
]
};
EOF
# Create log directory
$SUDO_CMD mkdir -p /var/log/pm2
$SUDO_CMD chown -R "$SERVICE_USER:$SERVICE_USER" /var/log/pm2
# Start applications with PM2
pm2 start ecosystem.config.js
pm2 save
pm2 startup
print_success "PM2 processes configured"
}
# Function to setup monitoring
setup_monitoring() {
print_step "Setting up monitoring..."
# Install htop and other monitoring tools
$SUDO_CMD apt install -y htop iotop nethogs
# Setup logrotate for application logs
$SUDO_CMD tee "/etc/logrotate.d/$PROJECT_NAME" > /dev/null <<EOF
/var/log/pm2/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 $SERVICE_USER $SERVICE_USER
postrotate
pm2 reloadLogs
endscript
}
EOF
print_success "Monitoring tools installed"
}
# Function to create backup script
create_backup_script() {
print_step "Creating backup script..."
$SUDO_CMD tee "/usr/local/bin/backup-$PROJECT_NAME" > /dev/null <<EOF
#!/bin/bash
BACKUP_DATE=\$(date +%Y%m%d_%H%M%S)
BACKUP_PATH="$BACKUP_DIR/backup_\$BACKUP_DATE"
mkdir -p "\$BACKUP_PATH"
# Backup database
pg_dump -h localhost -U $DB_USER -d $DB_NAME > "\$BACKUP_PATH/database.sql"
# Backup application files
cp -r "$PROJECT_DIR" "\$BACKUP_PATH/app"
# Backup Nginx configuration
cp -r /etc/nginx/sites-available/$NGINX_CONFIG_NAME "\$BACKUP_PATH/nginx.conf"
# Compress backup
cd "$BACKUP_DIR"
tar -czf "backup_\$BACKUP_DATE.tar.gz" "backup_\$BACKUP_DATE"
rm -rf "backup_\$BACKUP_DATE"
# Keep only last 7 days of backups
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "Backup completed: backup_\$BACKUP_DATE.tar.gz"
EOF
$SUDO_CMD chmod +x "/usr/local/bin/backup-$PROJECT_NAME"
# Setup daily backup cron job
(crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/backup-$PROJECT_NAME") | crontab -
print_success "Backup script created and scheduled"
}
# Function to setup deployment script
create_deployment_script() {
print_step "Creating deployment script for future updates..."
tee "$PROJECT_DIR/deploy-update.sh" > /dev/null <<EOF
#!/bin/bash
# Update deployment script for Laca City
set -e
PROJECT_DIR="$PROJECT_DIR"
# Check if running as root
if [ "\$(whoami)" = "root" ]; then
SUDO_CMD=""
else
SUDO_CMD="sudo"
fi
echo "🚀 Starting deployment update..."
# Stop applications
pm2 stop all
# Backup current version
/usr/local/bin/backup-$PROJECT_NAME
# Pull latest changes (if using git)
# git pull origin main
# Install dependencies
cd "\$PROJECT_DIR/frontend"
npm ci --production
cd "\$PROJECT_DIR/backend"
npm ci --production
# Build applications
cd "\$PROJECT_DIR/frontend"
npm run build
cd "\$PROJECT_DIR/backend"
npm run build
# Restart applications
pm2 restart all
# Reload Nginx
\$SUDO_CMD systemctl reload nginx
echo "✅ Deployment update completed!"
EOF
EOF
chmod +x "$PROJECT_DIR/deploy-update.sh"
print_success "Deployment script created"
}
# Function to perform final checks
final_checks() {
print_step "Performing final checks..."
# Check if services are running
if systemctl is-active --quiet nginx; then
print_success "✅ Nginx is running"
else
print_error "❌ Nginx is not running"
fi
if systemctl is-active --quiet postgresql; then
print_success "✅ PostgreSQL is running"
else
print_error "❌ PostgreSQL is not running"
fi
if systemctl is-active --quiet redis; then
print_success "✅ Redis is running"
else
print_error "❌ Redis is not running"
fi
# Check PM2 processes
if pm2 list | grep -q "online"; then
print_success "✅ PM2 applications are running"
else
print_error "❌ PM2 applications are not running"
fi
# Check domain accessibility
if curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN_NAME" | grep -q "200\|301\|302"; then
print_success "✅ Website is accessible at https://$DOMAIN_NAME"
else
print_warning "⚠️ Website might not be fully accessible yet. Please check DNS and SSL configuration."
fi
}
# Main deployment function
main() {
print_header "🚀 LACA CITY WEBSITE - VPS DEPLOYMENT"
# Check execution context and setup appropriate commands
if [ "$CURRENT_USER" = "root" ]; then
print_warning "Running as root user. Some operations will be adjusted accordingly."
SUDO_CMD=""
# Set a default non-root user for services
SERVICE_USER="www-data"
else
# Check if sudo is available
if ! sudo -n true 2>/dev/null; then
print_error "This script requires sudo access. Please ensure you can run sudo commands."
exit 1
fi
SUDO_CMD="sudo"
SERVICE_USER="$CURRENT_USER"
fi
# Collect configuration
if [ -z "$DOMAIN_NAME" ]; then
prompt_input "Enter your domain name (e.g., yourdomain.com)" DOMAIN_NAME
fi
if [ -z "$EMAIL" ]; then
prompt_input "Enter your email for SSL certificate" EMAIL
fi
if [ -z "$DB_PASSWORD" ]; then
prompt_input "Enter database password (leave empty to generate)" DB_PASSWORD
fi
print_status "Configuration:"
print_status "Domain: $DOMAIN_NAME"
print_status "Email: $EMAIL"
print_status "Project Directory: $PROJECT_DIR"
print_status "Frontend Port: $FRONTEND_PORT"
print_status "Backend Port: $BACKEND_PORT"
read -p "Continue with deployment? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_status "Deployment cancelled."
exit 0
fi
# Start deployment
print_header "STARTING DEPLOYMENT"
# Update system
print_step "Updating system packages..."
$SUDO_CMD apt update && $SUDO_CMD apt upgrade -y
# Install basic tools
print_step "Installing basic tools..."
$SUDO_CMD apt install -y curl wget git unzip software-properties-common apt-transport-https ca-certificates gnupg lsb-release openssl
# Setup firewall
setup_firewall
# Install Node.js
install_nodejs
# Setup databases
setup_postgresql
setup_redis
# Setup project
setup_project
# Create environment files
create_env_files
# Build applications
build_applications
# Setup Nginx
setup_nginx
# Setup SSL
setup_ssl
# Setup PM2
setup_pm2
# Setup monitoring
setup_monitoring
# Create backup script
create_backup_script
# Create deployment script
create_deployment_script
# Final checks
final_checks
print_header "🎉 DEPLOYMENT COMPLETED SUCCESSFULLY!"
echo -e "\n${GREEN}Your Laca City website is now deployed!${NC}\n"
echo -e "🌐 Website URL: ${CYAN}https://$DOMAIN_NAME${NC}"
echo -e "📊 PM2 Monitor: ${CYAN}pm2 monit${NC}"
echo -e "📝 Logs: ${CYAN}pm2 logs${NC}"
echo -e "🔄 Update: ${CYAN}$PROJECT_DIR/deploy-update.sh${NC}"
echo -e "💾 Backup: ${CYAN}/usr/local/bin/backup-$PROJECT_NAME${NC}"
echo -e "\n${YELLOW}Important Information:${NC}"
echo -e "📧 Database Password: ${RED}$DB_PASSWORD${NC} (save this!)"
echo -e "📁 Project Directory: $PROJECT_DIR"
echo -e "📁 Backup Directory: $BACKUP_DIR"
echo -e "🔧 Nginx Config: /etc/nginx/sites-available/$NGINX_CONFIG_NAME"
echo -e "\n${BLUE}Useful Commands:${NC}"
echo -e " pm2 list # View running processes"
echo -e " pm2 restart all # Restart all applications"
echo -e " pm2 logs # View application logs"
echo -e " $SUDO_CMD systemctl status nginx # Check Nginx status"
echo -e " $SUDO_CMD certbot renew --dry-run # Test SSL renewal"
print_success "Setup completed! Your website should be accessible at https://$DOMAIN_NAME"
}
# Run main function
main "$@"