#!/usr/bin/env python3 # ============================================================================== # NGINX CONFIG & SSL DEPLOYMENT SCRIPT (Python Version) # # This script follows a robust, idempotent deployment strategy. # 1. It first ensures all required SSL certificates exist using Certbot. # 2. It then deploys the perfected, local Nginx configurations. # This prevents Certbot from ever creating a broken Nginx configuration. # # USAGE: # 1. Ensure your local `sites-available` files are correct and complete. # 2. Run the script: `python3 deploy_nginx.py` # ============================================================================== import os import subprocess import sys from pathlib import Path # --- Configuration --- REMOTE_USER = "ubuntu" REMOTE_HOST = "3.9.182.122" CERTBOT_EMAIL = "azeem.fidahusein@gmail.com" # --- File & Path Definitions --- # Using pathlib for robust path handling KEY_FILE = Path.home() / "repos" / "azeem-macbookair.pem" SOURCE_NGINX_CONF = Path("nginx.conf") SOURCE_SITES_DIR = Path("sites-available") DEST_NGINX_PATH = "/etc/nginx/" DEST_SITES_PATH = "/etc/nginx/sites-available/" # --- Helper Functions --- def print_header(message): """Prints a formatted header.""" print("\n" + "=" * 60) print(message) print("=" * 60) def print_success(message): """Prints a success message.""" print(f"✅ {message}") def print_error(message): """Prints an error message and exits.""" print(f"❌ ERROR: {message}") sys.exit(1) def run_command(command, shell=False): """Runs a command, checks for errors, and returns the result.""" try: # Using shell=True for commands with pipes or redirects result = subprocess.run( command, check=True, capture_output=True, text=True, shell=shell ) return result except subprocess.CalledProcessError as e: print_error(f"Command failed with exit code {e.returncode}") print(f"STDOUT: {e.stdout}") print(f"STDERR: {e.stderr}") def run_remote_command(ssh_command): """Runs a command on the remote server via SSH.""" base_command = ["ssh", "-i", str(KEY_FILE), f"{REMOTE_USER}@{REMOTE_HOST}"] full_command = base_command + [ssh_command] print(f" -> Running on remote: {' '.join(full_command)}") run_command(full_command) # --- Main Logic --- def main(): """Main function to run the deployment process.""" print_header(f"🚀 Starting NGINX & SSL deployment to {REMOTE_HOST}") # --- Pre-flight Checks --- print("-> Performing pre-flight checks...") if not KEY_FILE.is_file(): print_error(f"SSH key not found at {KEY_FILE}") if not SOURCE_NGINX_CONF.is_file(): print_error(f"Source file '{SOURCE_NGINX_CONF}' not found.") if not SOURCE_SITES_DIR.is_dir(): print_error(f"Source directory '{SOURCE_SITES_DIR}' not found.") print_success("Pre-flight checks passed.") # --- Local Operations --- print("-> Collecting local configuration details...") config_files = [f.name for f in SOURCE_SITES_DIR.iterdir() if f.is_file()] if not config_files: print_error("No configuration files found in 'sites-available' directory.") print(f" -> Found site config files: {', '.join(config_files)}") # Find all unique domains for Certbot all_domains_str = run_command( f"grep -h -r 'server_name' {SOURCE_SITES_DIR} | sed 's/.*server_name\s*//' | sed 's/;//' | xargs -n1 | sort -u | tr '\\n' ' '", shell=True ).stdout.strip() if not all_domains_str: print("⚠️ WARNING: No domains found. Skipping Certbot step later.") all_domains_found = False else: print_success(f"Found domains for certificate check: {all_domains_str}") all_domains_found = True # --- Remote Operations --- # Step 1: Ensure certificates exist BEFORE deploying configs if all_domains_found: print_header("Step 1: Running Certbot to Get/Renew Certificates") certbot_domains = " ".join([f"-d {domain}" for domain in all_domains_str.split()]) certbot_command = ( f"sudo certbot certonly --nginx --non-interactive --agree-tos " f"--email {CERTBOT_EMAIL} --expand {certbot_domains}" ) run_remote_command(certbot_command) print_success("Certificate check complete.") # Step 2: Deploy the perfected configuration files print_header("Step 2: Deploying Local Configurations") remote_temp_dir = f"/home/{REMOTE_USER}/deploy_temp" run_remote_command(f"mkdir -p {remote_temp_dir}") print(" -> Transferring files via scp...") scp_command = [ "scp", "-i", str(KEY_FILE), "-r", str(SOURCE_NGINX_CONF), str(SOURCE_SITES_DIR), f"{REMOTE_USER}@{REMOTE_HOST}:{remote_temp_dir}/" ] run_command(scp_command) print_success("File transfer complete.") # Step 3: Move files, enable sites, and test print_header("Step 3: Activating New Configuration on Server") config_files_str = " ".join(config_files) # Using a multi-line f-string for the remote script remote_script = f""" echo " -> Moving files into place..."; sudo mv {remote_temp_dir}/{SOURCE_NGINX_CONF.name} {DEST_NGINX_PATH}; sudo mv {remote_temp_dir}/{SOURCE_SITES_DIR.name}/* {DEST_SITES_PATH}; echo " -> Checking and creating symbolic links..."; for config_file in {config_files_str}; do source_file="{DEST_SITES_PATH}${{config_file}}"; link_file="/etc/nginx/sites-enabled/${{config_file}}"; if [ ! -L "$link_file" ]; then sudo ln -s "$source_file" "$link_file"; echo " -> Link created for ${{config_file}}."; else echo " -> Link for ${{config_file}} already exists."; fi; done; rm -rf {remote_temp_dir}; echo " -> Verifying final Nginx configuration..."; sudo nginx -t; """ run_remote_command(remote_script) print_success("Nginx configuration test passed.") # Step 4: Reload Nginx print_header("Step 4: Reloading Nginx") run_remote_command("sudo systemctl reload nginx") print_success("Nginx reloaded successfully.") print_header("🎉 Deployment complete!") if __name__ == "__main__": main()