Created
February 5, 2026 04:15
-
-
Save vck/dfddce29b1d4d74aa83681ee1e9aa02c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| echo "===========================================" | |
| echo "EC2 Ubuntu Bootstrap Script (No Docker)" | |
| echo "Optimized for 1GB RAM" | |
| echo "===========================================" | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' # No Color | |
| # Logging functions | |
| log() { | |
| echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | |
| } | |
| info() { | |
| echo -e "${BLUE}[INFO]${NC} $1" | |
| } | |
| warn() { | |
| echo -e "${YELLOW}[WARN]${NC} $1" | |
| } | |
| error() { | |
| echo -e "${RED}[ERROR]${NC} $1" | |
| exit 1 | |
| } | |
| # Check if running as root | |
| if [ "$EUID" -ne 0 ]; then | |
| warn "Not running as root, using sudo where needed" | |
| SUDO="sudo" | |
| else | |
| SUDO="" | |
| fi | |
| # ==================== SYSTEM INFO ==================== | |
| log "Checking system information..." | |
| CPU_CORES=$(nproc --all) | |
| TOTAL_RAM=$(free -m | awk '/^Mem:/{print $2}') | |
| log "System: $(lsb_release -d | cut -f2)" | |
| log "CPU Cores: $CPU_CORES" | |
| log "Total RAM: ${TOTAL_RAM}MB" | |
| if [ "$TOTAL_RAM" -lt 900 ]; then | |
| warn "Low RAM detected (${TOTAL_RAM}MB)! Enabling aggressive optimizations." | |
| AGGRESSIVE_OPTIMIZATION=true | |
| fi | |
| # ==================== PHASE 1: SYSTEM OPTIMIZATION ==================== | |
| log "Phase 1: System Optimization" | |
| # Update system (minimal) | |
| log "Updating package lists..." | |
| $SUDO apt-get update -y | |
| # Install ONLY essentials | |
| log "Installing essential packages..." | |
| $SUDO apt-get install -y \ | |
| curl \ | |
| wget \ | |
| git \ | |
| rsync \ | |
| htop \ | |
| nload \ | |
| ncdu \ | |
| fail2ban \ | |
| unattended-upgrades \ | |
| ufw \ | |
| cron \ | |
| software-properties-common \ | |
| build-essential \ | |
| libssl-dev \ | |
| ca-certificates | |
| # Remove unnecessary packages | |
| log "Removing unnecessary packages..." | |
| $SUDO apt-get remove -y --purge \ | |
| snapd \ | |
| lxd \ | |
| lxcfs \ | |
| popularity-contest \ | |
| ubuntu-advantage-tools \ | |
| command-not-found \ | |
| mlocate | |
| $SUDO apt-get autoremove -y | |
| $SUDO apt-get clean | |
| # ==================== PHASE 2: MEMORY OPTIMIZATION ==================== | |
| log "Phase 2: Memory Optimization" | |
| # Create optimal swap file based on RAM | |
| SWAP_SIZE="1G" | |
| if [ "$AGGRESSIVE_OPTIMIZATION" = true ]; then | |
| SWAP_SIZE="2G" # More swap for low RAM | |
| fi | |
| log "Setting up ${SWAP_SIZE} swap file..." | |
| if swapon --show | grep -q "."; then | |
| log "Swap already exists:" | |
| swapon --show | |
| info "Keeping existing swap" | |
| else | |
| # Create swap file | |
| $SUDO fallocate -l $SWAP_SIZE /swapfile | |
| $SUDO chmod 600 /swapfile | |
| $SUDO mkswap /swapfile | |
| $SUDO swapon /swapfile | |
| # Make permanent | |
| echo '/swapfile none swap sw 0 0' | $SUDO tee -a /etc/fstab | |
| # Optimize swappiness (lower = less swap usage) | |
| if [ "$AGGRESSIVE_OPTIMIZATION" = true ]; then | |
| SWAPPINESS=5 | |
| else | |
| SWAPPINESS=10 | |
| fi | |
| echo "vm.swappiness=$SWAPPINESS" | $SUDO tee -a /etc/sysctl.conf | |
| echo "vm.vfs_cache_pressure=50" | $SUDO tee -a /etc/sysctl.conf | |
| $SUDO sysctl -p | |
| fi | |
| # Disable memory-hungry services | |
| log "Disabling memory-intensive services..." | |
| $SUDO systemctl disable --now \ | |
| apport \ | |
| whoopsie \ | |
| systemd-timesyncd \ | |
| ModemManager \ | |
| pppd-dns \ | |
| cups-browsed \ | |
| cups | |
| # Disable automatic updates service (we'll use cron) | |
| $SUDO systemctl disable --now apt-daily{,-upgrade}.{timer,service} | |
| # ==================== PHASE 3: SECURITY ==================== | |
| log "Phase 3: Security Configuration" | |
| # Setup UFW (Uncomplicated Firewall) | |
| log "Configuring firewall..." | |
| $SUDO ufw default deny incoming | |
| $SUDO ufw default allow outgoing | |
| $SUDO ufw allow ssh | |
| $SUDO ufw allow 80/tcp # HTTP | |
| $SUDO ufw allow 443/tcp # HTTPS | |
| $SUDO ufw --force enable | |
| # Configure fail2ban | |
| log "Setting up fail2ban..." | |
| $SUDO cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local | |
| $SUDO systemctl enable fail2ban | |
| $SUDO systemctl start fail2ban | |
| # SSH hardening | |
| log "Hardening SSH configuration..." | |
| SSHD_CONFIG="/etc/ssh/sshd_config" | |
| $SUDO sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/' $SSHD_CONFIG | |
| $SUDO sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' $SSHD_CONFIG | |
| $SUDO sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' $SSHD_CONFIG | |
| $SUDO sed -i 's/#ClientAliveInterval 0/ClientAliveInterval 300/' $SSHD_CONFIG | |
| $SUDO sed -i 's/#ClientAliveCountMax 3/ClientAliveCountMax 2/' $SSHD_CONFIG | |
| echo "AllowUsers ubuntu" | $SUDO tee -a $SSHD_CONFIG | |
| $SUDO systemctl restart sshd | |
| # Automatic security updates (lightweight) | |
| log "Configuring automatic security updates..." | |
| cat << EOF | $SUDO tee /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null | |
| Unattended-Upgrade::Allowed-Origins { | |
| "\${distro_id}:\${distro_codename}-security"; | |
| }; | |
| Unattended-Upgrade::AutoFixInterruptedDpkg "true"; | |
| Unattended-Upgrade::MinimalSteps "true"; | |
| Unattended-Upgrade::Remove-Unused-Dependencies "true"; | |
| Unattended-Upgrade::Automatic-Reboot "false"; | |
| EOF | |
| # ==================== PHASE 4: APPLICATION ENVIRONMENT ==================== | |
| log "Phase 4: Setting up Application Environment" | |
| # Create directory structure | |
| log "Creating directory structure..." | |
| $SUDO mkdir -p /var/www/app/{releases,shared} | |
| $SUDO mkdir -p /var/log/app | |
| $SUDO mkdir -p /opt/{scripts,backups,monitoring} | |
| $SUDO mkdir -p /home/ubuntu/.ssh | |
| # Set proper permissions | |
| $SUDO chown -R ubuntu:ubuntu /var/www/app | |
| $SUDO chown -R ubuntu:ubuntu /var/log/app | |
| $SUDO chown -R ubuntu:ubuntu /opt/scripts | |
| $SUDO chown -R ubuntu:ubuntu /opt/backups | |
| $SUDO chown -R ubuntu:ubuntu /opt/monitoring | |
| $SUDO chown -R ubuntu:ubuntu /home/ubuntu/.ssh | |
| # ==================== PHASE 5: NODE.JS SETUP (Optional) ==================== | |
| if [ "$INSTALL_NODE" = "true" ]; then | |
| log "Installing Node.js via NodeSource..." | |
| # Use Node.js 18.x (LTS) | |
| curl -fsSL https://deb.nodesource.com/setup_18.x | $SUDO -E bash - | |
| $SUDO apt-get install -y nodejs | |
| # Install PM2 for process management | |
| log "Installing PM2 process manager..." | |
| $SUDO npm install -g pm2 | |
| pm2 startup ubuntu --hp /home/ubuntu | |
| $SUDO env PATH=$PATH:/usr/bin pm2 startup ubuntu -u ubuntu --hp /home/ubuntu | |
| # Configure npm for low RAM | |
| log "Configuring npm for low memory..." | |
| npm config set maxsockets 1 | |
| npm config set fetch-retries 2 | |
| npm config set fetch-retry-mintimeout 20000 | |
| npm config set fetch-retry-maxtimeout 120000 | |
| npm config set cache-min 9999999 | |
| # Clean npm cache | |
| npm cache clean --force | |
| fi | |
| # ==================== PHASE 6: DEPLOYMENT SCRIPTS ==================== | |
| log "Phase 6: Creating Deployment Scripts" | |
| # Main lightweight deploy script | |
| log "Creating deploy script..." | |
| cat << 'EOF' | $SUDO tee /opt/scripts/deploy.sh > /dev/null | |
| #!/bin/bash | |
| set -e | |
| # Configuration | |
| APP_NAME="myapp" | |
| APP_DIR="/var/www/app" | |
| CURRENT_DIR="$APP_DIR/current" | |
| RELEASES_DIR="$APP_DIR/releases" | |
| SHARED_DIR="$APP_DIR/shared" | |
| LOG_DIR="/var/log/app" | |
| MAX_RAM_MB=800 | |
| # Colors | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' | |
| log() { | |
| echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_DIR/deploy.log" | |
| } | |
| error() { | |
| echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_DIR/deploy.log" | |
| exit 1 | |
| } | |
| warn() { | |
| echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_DIR/deploy.log" | |
| } | |
| info() { | |
| echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_DIR/deploy.log" | |
| } | |
| # Check system resources | |
| check_resources() { | |
| log "Checking system resources..." | |
| # Check memory | |
| MEM_FREE=$(free -m | awk '/^Mem:/{print $4}') | |
| SWAP_FREE=$(free -m | awk '/^Swap:/{print $4}') | |
| info "Memory: ${MEM_FREE}MB free, Swap: ${SWAP_FREE}MB free" | |
| if [ "$MEM_FREE" -lt 50 ] && [ "$SWAP_FREE" -lt 100 ]; then | |
| warn "Low memory detected! Attempting cleanup..." | |
| # Clear caches | |
| sync | |
| echo 1 | sudo tee /proc/sys/vm/drop_caches > /dev/null | |
| sleep 2 | |
| MEM_FREE=$(free -m | awk '/^Mem:/{print $4}') | |
| if [ "$MEM_FREE" -lt 30 ]; then | |
| error "Insufficient memory to deploy (only ${MEM_FREE}MB free)" | |
| fi | |
| fi | |
| # Check disk space | |
| DISK_FREE=$(df -m / | awk 'NR==2 {print $4}') | |
| if [ "$DISK_FREE" -lt 100 ]; then | |
| warn "Low disk space: ${DISK_FREE}MB free" | |
| /opt/scripts/cleanup.sh | |
| fi | |
| } | |
| # Git operations with retry | |
| git_clone_or_pull() { | |
| local repo_url="$1" | |
| local target_dir="$2" | |
| if [ -d "$target_dir/.git" ]; then | |
| log "Updating existing repository..." | |
| cd "$target_dir" | |
| git fetch --all --prune | |
| git reset --hard origin/$BRANCH | |
| git clean -fd | |
| else | |
| log "Cloning repository (shallow clone)..." | |
| git clone --depth 1 --branch $BRANCH "$repo_url" "$target_dir" | |
| fi | |
| } | |
| # Deploy application | |
| deploy() { | |
| check_resources | |
| TIMESTAMP=$(date +%Y%m%d%H%M%S) | |
| RELEASE_DIR="$RELEASES_DIR/$TIMESTAMP" | |
| log "Starting deployment #$TIMESTAMP..." | |
| # Create release directory | |
| mkdir -p "$RELEASE_DIR" | |
| # Clone/update code | |
| git_clone_or_pull "$REPO_URL" "$RELEASE_DIR" | |
| cd "$RELEASE_DIR" | |
| # Install dependencies if package.json exists | |
| if [ -f "package.json" ]; then | |
| log "Installing dependencies with low-memory settings..." | |
| # Set memory limit for npm | |
| export NODE_OPTIONS="--max-old-space-size=256" | |
| # Clean npm cache first | |
| npm cache clean --force | |
| # Install production dependencies only | |
| npm ci --omit=dev --ignore-scripts --no-audit --no-fund | |
| # Run build if build script exists | |
| if [ -f "package.json" ] && grep -q '"build"' package.json; then | |
| log "Building application..." | |
| npm run build | |
| fi | |
| fi | |
| # Link shared files | |
| if [ -d "$SHARED_DIR" ]; then | |
| log "Linking shared files..." | |
| for item in $(ls -A "$SHARED_DIR/"); do | |
| if [ -e "$RELEASE_DIR/$item" ]; then | |
| rm -rf "$RELEASE_DIR/$item" | |
| fi | |
| ln -sf "$SHARED_DIR/$item" "$RELEASE_DIR/$item" | |
| done | |
| fi | |
| # Stop application | |
| log "Stopping application..." | |
| sudo systemctl stop "$APP_NAME" 2>/dev/null || true | |
| pm2 stop "$APP_NAME" 2>/dev/null || true | |
| # Switch current release | |
| log "Activating release..." | |
| rm -f "$CURRENT_DIR" | |
| ln -sf "$RELEASE_DIR" "$CURRENT_DIR" | |
| # Start application | |
| log "Starting application..." | |
| # Try systemd first, then pm2, then direct | |
| if [ -f "$CURRENT_DIR/$APP_NAME.service" ]; then | |
| sudo cp "$CURRENT_DIR/$APP_NAME.service" /etc/systemd/system/ | |
| sudo systemctl daemon-reload | |
| sudo systemctl enable "$APP_NAME" | |
| sudo systemctl start "$APP_NAME" | |
| elif command -v pm2 &> /dev/null && [ -f "$CURRENT_DIR/ecosystem.config.js" ]; then | |
| cd "$CURRENT_DIR" | |
| pm2 start ecosystem.config.js | |
| pm2 save | |
| elif [ -f "$CURRENT_DIR/package.json" ]; then | |
| cd "$CURRENT_DIR" | |
| NODE_OPTIONS="--max-old-space-size=400" nohup npm start > "$LOG_DIR/app.log" 2>&1 & | |
| fi | |
| # Clean old releases (keep last 5) | |
| log "Cleaning old releases..." | |
| ls -dt "$RELEASES_DIR"/*/ | tail -n +6 | xargs rm -rf 2>/dev/null || true | |
| log "Deployment completed successfully!" | |
| # Show deployment info | |
| info "Current release: $TIMESTAMP" | |
| info "App directory: $CURRENT_DIR" | |
| # Run health check | |
| /opt/scripts/health-check.sh | |
| } | |
| # Parse arguments | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| --repo) | |
| REPO_URL="$2" | |
| shift 2 | |
| ;; | |
| --branch) | |
| BRANCH="$2" | |
| shift 2 | |
| ;; | |
| --app-name) | |
| APP_NAME="$2" | |
| shift 2 | |
| ;; | |
| *) | |
| echo "Unknown option: $1" | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| BRANCH=${BRANCH:-"main"} | |
| APP_NAME=${APP_NAME:-"myapp"} | |
| if [ -z "$REPO_URL" ]; then | |
| echo "Usage: $0 --repo <git-repo-url> [--branch <branch>] [--app-name <name>]" | |
| exit 1 | |
| fi | |
| deploy | |
| EOF | |
| $SUDO chmod +x /opt/scripts/deploy.sh | |
| # Health check script | |
| log "Creating health check script..." | |
| cat << 'EOF' | $SUDO tee /opt/scripts/health-check.sh > /dev/null | |
| #!/bin/bash | |
| # Configuration | |
| APP_NAME="myapp" | |
| LOG_FILE="/var/log/app/health.log" | |
| ALERT_THRESHOLD=80 | |
| CRITICAL_THRESHOLD=90 | |
| # Colors | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| RED='\033[0;31m' | |
| NC='\033[0m' | |
| # Check system health | |
| check_system() { | |
| echo "=== System Health Check $(date '+%Y-%m-%d %H:%M:%S') ===" | |
| # Memory usage | |
| MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}') | |
| MEM_USED=$(free -m | awk '/^Mem:/{print $3}') | |
| MEM_PERCENT=$((MEM_USED * 100 / MEM_TOTAL)) | |
| if [ "$MEM_PERCENT" -ge "$CRITICAL_THRESHOLD" ]; then | |
| echo -e "${RED}✗ Memory: ${MEM_PERCENT}% (${MEM_USED}/${MEM_TOTAL}MB) - CRITICAL${NC}" | |
| return 1 | |
| elif [ "$MEM_PERCENT" -ge "$ALERT_THRESHOLD" ]; then | |
| echo -e "${YELLOW}⚠ Memory: ${MEM_PERCENT}% (${MEM_USED}/${MEM_TOTAL}MB) - WARNING${NC}" | |
| return 2 | |
| else | |
| echo -e "${GREEN}✓ Memory: ${MEM_PERCENT}% (${MEM_USED}/${MEM_TOTAL}MB)${NC}" | |
| fi | |
| # Swap usage | |
| SWAP_TOTAL=$(free -m | awk '/^Swap:/{print $2}') | |
| if [ "$SWAP_TOTAL" -gt 0 ]; then | |
| SWAP_USED=$(free -m | awk '/^Swap:/{print $3}') | |
| SWAP_PERCENT=$((SWAP_USED * 100 / SWAP_TOTAL)) | |
| if [ "$SWAP_PERCENT" -gt 50 ]; then | |
| echo -e "${YELLOW}⚠ Swap: ${SWAP_PERCENT}% used${NC}" | |
| fi | |
| fi | |
| # Disk usage | |
| DISK_PERCENT=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//') | |
| if [ "$DISK_PERCENT" -ge "$CRITICAL_THRESHOLD" ]; then | |
| echo -e "${RED}✗ Disk: ${DISK_PERCENT}% used - CRITICAL${NC}" | |
| return 1 | |
| elif [ "$DISK_PERCENT" -ge "$ALERT_THRESHOLD" ]; then | |
| echo -e "${YELLOW}⚠ Disk: ${DISK_PERCENT}% used - WARNING${NC}" | |
| return 2 | |
| else | |
| echo -e "${GREEN}✓ Disk: ${DISK_PERCENT}% used${NC}" | |
| fi | |
| # Load average | |
| LOAD=$(uptime | awk -F'load average:' '{print $2}') | |
| CPU_CORES=$(nproc) | |
| LOAD1=$(echo $LOAD | cut -d, -f1 | tr -d ' ') | |
| if (( $(echo "$LOAD1 > $CPU_CORES" | bc -l) )); then | |
| echo -e "${YELLOW}⚠ Load: $LOAD (1 min)${NC}" | |
| else | |
| echo -e "${GREEN}✓ Load: $LOAD${NC}" | |
| fi | |
| return 0 | |
| } | |
| # Check application | |
| check_app() { | |
| echo -e "\n=== Application Check ===" | |
| # Check if app is running | |
| if systemctl is-active --quiet "$APP_NAME"; then | |
| echo -e "${GREEN}✓ Service: $APP_NAME is running${NC}" | |
| # Check service status | |
| SERVICE_STATUS=$(systemctl status "$APP_NAME" | grep "Active:" | cut -d':' -f2-) | |
| echo " Status:$SERVICE_STATUS" | |
| # Check recent logs for errors | |
| ERROR_COUNT=$(sudo journalctl -u "$APP_NAME" --since "5 minutes ago" | grep -i "error\|fail\|exception" | wc -l) | |
| if [ "$ERROR_COUNT" -gt 0 ]; then | |
| echo -e "${YELLOW}⚠ Found $ERROR_COUNT errors in recent logs${NC}" | |
| fi | |
| elif command -v pm2 &> /dev/null && pm2 list | grep -q "$APP_NAME"; then | |
| echo -e "${GREEN}✓ PM2: $APP_NAME is running${NC}" | |
| pm2 info "$APP_NAME" | grep -E "status|uptime|memory" | |
| else | |
| echo -e "${RED}✗ Application $APP_NAME is not running${NC}" | |
| return 1 | |
| fi | |
| # Check if app responds (optional - customize port) | |
| if command -v curl &> /dev/null; then | |
| if curl -s -f http://localhost:3000/health > /dev/null 2>&1; then | |
| echo -e "${GREEN}✓ Health endpoint: Responding${NC}" | |
| elif curl -s -f http://localhost:3000 > /dev/null 2>&1; then | |
| echo -e "${GREEN}✓ Application: Responding on port 3000${NC}" | |
| fi | |
| fi | |
| return 0 | |
| } | |
| # Main | |
| main() { | |
| EXIT_CODE=0 | |
| # Run checks | |
| check_system || EXIT_CODE=1 | |
| check_app || EXIT_CODE=1 | |
| # Log to file | |
| { | |
| echo "=== Health Check $(date '+%Y-%m-%d %H:%M:%S') ===" | |
| free -m | |
| df -h / | |
| systemctl status "$APP_NAME" --no-pager | |
| } >> "$LOG_FILE" | |
| echo -e "\n=== Summary ===" | |
| if [ $EXIT_CODE -eq 0 ]; then | |
| echo -e "${GREEN}✅ All systems operational${NC}" | |
| else | |
| echo -e "${RED}❌ Issues detected${NC}" | |
| fi | |
| # Keep log file manageable | |
| tail -n 1000 "$LOG_FILE" > "$LOG_FILE.tmp" && mv "$LOG_FILE.tmp" "$LOG_FILE" | |
| exit $EXIT_CODE | |
| } | |
| main | |
| EOF | |
| $SUDO chmod +x /opt/scripts/health-check.sh | |
| # Cleanup script | |
| log "Creating cleanup script..." | |
| cat << 'EOF' | $SUDO tee /opt/scripts/cleanup.sh > /dev/null | |
| #!/bin/bash | |
| # Colors | |
| GREEN='\033[0;32m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' | |
| log() { | |
| echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | |
| } | |
| info() { | |
| echo -e "${BLUE}[INFO]${NC} $1" | |
| } | |
| log "Starting system cleanup..." | |
| # Clear package cache | |
| sudo apt-get clean | |
| sudo apt-get autoremove -y | |
| # Clear temporary files (older than 1 day) | |
| log "Cleaning temporary files..." | |
| find /tmp -type f -atime +1 -delete 2>/dev/null || true | |
| find /var/tmp -type f -atime +1 -delete 2>/dev/null || true | |
| # Clear old logs (keep last 3 days) | |
| log "Rotating logs..." | |
| find /var/log -name "*.log" -type f -mtime +3 -delete 2>/dev/null || true | |
| find /var/log -name "*.gz" -type f -delete 2>/dev/null || true | |
| # Clear journal logs (keep last 1 day) | |
| sudo journalctl --vacuum-time=1d 2>/dev/null || true | |
| # Clear npm cache (if Node.js is installed) | |
| if command -v npm &> /dev/null; then | |
| log "Cleaning npm cache..." | |
| npm cache clean --force 2>/dev/null || true | |
| rm -rf ~/.npm/_logs/* 2>/dev/null || true | |
| fi | |
| # Clear old app releases (keep last 3) | |
| if [ -d "/var/www/app/releases" ]; then | |
| log "Cleaning old releases..." | |
| ls -dt /var/www/app/releases/*/ 2>/dev/null | tail -n +4 | xargs rm -rf 2>/dev/null || true | |
| fi | |
| # Clear old backups (keep last 7 days) | |
| if [ -d "/opt/backups" ]; then | |
| log "Cleaning old backups..." | |
| find /opt/backups -type f -mtime +7 -delete 2>/dev/null || true | |
| fi | |
| # Clear browser-like caches | |
| log "Cleaning user caches..." | |
| rm -rf ~/.cache/* 2>/dev/null || true | |
| # Clear APT lists (keep current) | |
| log "Cleaning APT lists..." | |
| rm -rf /var/lib/apt/lists/* 2>/dev/null || true | |
| sudo apt-get update | |
| # Drop caches (careful with production!) | |
| log "Optimizing memory..." | |
| sync | |
| echo 1 | sudo tee /proc/sys/vm/drop_caches > /dev/null 2>&1 || true | |
| # Check disk usage | |
| log "Disk usage after cleanup:" | |
| df -h / | |
| log "Cleanup completed!" | |
| EOF | |
| $SUDO chmod +x /opt/scripts/cleanup.sh | |
| # ==================== PHASE 7: SYSTEMD SERVICE ==================== | |
| log "Phase 7: Creating Systemd Service" | |
| cat << 'EOF' | $SUDO tee /etc/systemd/system/myapp.service > /dev/null | |
| [Unit] | |
| Description=My Application | |
| After=network.target | |
| StartLimitIntervalSec=0 | |
| [Service] | |
| Type=simple | |
| User=ubuntu | |
| Group=ubuntu | |
| WorkingDirectory=/var/www/app/current | |
| # Memory limits - CRITICAL for 1GB RAM | |
| MemoryMax=700M | |
| MemorySwapMax=300M | |
| LimitNOFILE=4096 | |
| LimitNPROC=50 | |
| # Environment | |
| Environment="NODE_ENV=production" | |
| Environment="PORT=3000" | |
| Environment="NODE_OPTIONS=--max-old-space-size=400" | |
| # Start command | |
| ExecStart=/usr/bin/npm start | |
| # Alternative if using direct Node: | |
| # ExecStart=/usr/bin/node server.js | |
| # Restart policy | |
| Restart=always | |
| RestartSec=10 | |
| # Security | |
| NoNewPrivileges=true | |
| PrivateTmp=true | |
| ProtectSystem=full | |
| ReadWritePaths=/var/log/app /var/www/app/current | |
| # Logging | |
| StandardOutput=journal | |
| StandardError=journal | |
| SyslogIdentifier=myapp | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| # ==================== PHASE 8: MONITORING & CRON ==================== | |
| log "Phase 8: Setting up Monitoring" | |
| # Simple monitoring script | |
| cat << 'EOF' | $SUDO tee /opt/monitoring/monitor.sh > /dev/null | |
| #!/bin/bash | |
| # Lightweight monitoring script | |
| LOG_FILE="/var/log/app/monitor.log" | |
| MAX_LOG_SIZE=10485760 # 10MB | |
| # Rotate log if too large | |
| if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE") -gt $MAX_LOG_SIZE ]; then | |
| mv "$LOG_FILE" "${LOG_FILE}.old" | |
| fi | |
| { | |
| echo "=== System Monitor $(date '+%Y-%m-%d %H:%M:%S') ===" | |
| echo "Uptime: $(uptime -p)" | |
| echo "Load: $(uptime | awk -F'load average:' '{print $2}')" | |
| # Memory | |
| free -m | awk 'NR==2{printf "Memory: Used=%dMB (%.1f%%), Free=%dMB\n", $3, $3*100/$2, $4}' | |
| # Disk | |
| df -h / | awk 'NR==2{printf "Disk: Used=%s (%s), Free=%s\n", $3, $5, $4}' | |
| # Top 5 processes by memory | |
| echo -e "\nTop 5 processes by memory:" | |
| ps aux --sort=-%mem | head -6 | |
| # Service status | |
| echo -e "\nService status:" | |
| systemctl is-active myapp && echo "myapp: ACTIVE" || echo "myapp: INACTIVE" | |
| echo "=====================================" | |
| } >> "$LOG_FILE" | |
| EOF | |
| $SUDO chmod +x /opt/monitoring/monitor.sh | |
| # Setup cron jobs | |
| log "Setting up cron jobs..." | |
| # Create crontab for ubuntu user | |
| CRON_FILE="/home/ubuntu/crontab" | |
| cat << 'EOF' > "$CRON_FILE" | |
| # Run health check every 5 minutes | |
| */5 * * * * /opt/scripts/health-check.sh >/dev/null 2>&1 | |
| # Run cleanup daily at 3 AM | |
| 0 3 * * * /opt/scripts/cleanup.sh >/dev/null 2>&1 | |
| # Run monitoring every hour | |
| 0 * * * * /opt/monitoring/monitor.sh >/dev/null 2>&1 | |
| # Update package lists weekly | |
| 0 2 * * 1 apt-get update >/dev/null 2>&1 | |
| # Security updates daily at 4 AM | |
| 0 4 * * * unattended-upgrade -v >/dev/null 2>&1 | |
| # Log rotation | |
| 0 0 * * * logrotate /etc/logrotate.d/myapp >/dev/null 2>&1 | |
| EOF | |
| # Install crontab | |
| crontab "$CRON_FILE" | |
| rm "$CRON_FILE" | |
| # Log rotation config | |
| cat << 'EOF' | $SUDO tee /etc/logrotate.d/myapp > /dev/null | |
| /var/log/app/*.log { | |
| daily | |
| missingok | |
| rotate 7 | |
| compress | |
| delaycompress | |
| notifempty | |
| create 0640 ubuntu ubuntu | |
| sharedscripts | |
| postrotate | |
| systemctl kill -s USR1 myapp.service 2>/dev/null || true | |
| endscript | |
| } | |
| EOF | |
| # ==================== PHASE 9: KERNEL OPTIMIZATIONS ==================== | |
| log "Phase 9: Kernel Optimizations" | |
| cat << 'EOF' | $SUDO tee -a /etc/sysctl.conf > /dev/null | |
| # Memory optimizations | |
| vm.overcommit_memory = 1 | |
| vm.overcommit_ratio = 50 | |
| vm.dirty_background_ratio = 5 | |
| vm.dirty_ratio = 10 | |
| vm.vfs_cache_pressure = 50 | |
| vm.swappiness = 10 | |
| # Network optimizations for fewer connections | |
| net.core.somaxconn = 1024 | |
| net.ipv4.tcp_max_syn_backlog = 2048 | |
| net.core.netdev_max_backlog = 1000 | |
| # TCP optimizations | |
| net.ipv4.tcp_keepalive_time = 300 | |
| net.ipv4.tcp_keepalive_probes = 3 | |
| net.ipv4.tcp_keepalive_intvl = 30 | |
| # Security | |
| net.ipv4.tcp_syncookies = 1 | |
| net.ipv4.conf.all.rp_filter = 1 | |
| EOF | |
| $SUDO sysctl -p | |
| # ==================== PHASE 10: FINAL SETUP ==================== | |
| log "Phase 10: Final Configuration" | |
| # Create README | |
| cat << 'EOF' | tee /home/ubuntu/README.md > /dev/null | |
| # EC2 Instance - Lightweight Setup (No Docker) | |
| ## Quick Start Guide | |
| ### Application Structure: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment