diff --git a/deploy.py b/deploy.py new file mode 100644 index 0000000..bec0b56 --- /dev/null +++ b/deploy.py @@ -0,0 +1,178 @@ +#!/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() +