Created
January 11, 2026 20:47
-
-
Save amcchord/38cd18ebfc0f9641541ec3489c8e8654 to your computer and use it in GitHub Desktop.
Unified Apache Virtual Host Setup Script with SSL (Let's Encrypt)
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 | |
| # Unified Apache Virtual Host Setup Script | |
| # Usage: ./setupVHost.sh <domain> [subdomain] | |
| # | |
| # Smart detection: | |
| # - domain.com -> sets up domain.com + www.domain.com | |
| # - sub.domain.com -> sets up sub.domain.com only (detected as subdomain) | |
| # - domain.com api -> sets up api.domain.com only | |
| # - domain.com www -> sets up domain.com + www.domain.com | |
| set -e # Exit on error | |
| # Check if script is run as root | |
| if [[ $EUID -ne 0 ]]; then | |
| echo "This script must be run as root" | |
| exit 1 | |
| fi | |
| # Check if domain name is provided | |
| if [ -z "$1" ]; then | |
| echo "Please provide a domain name as an argument." | |
| echo "Usage: $0 <domain> [subdomain]" | |
| echo "" | |
| echo "Examples:" | |
| echo " $0 example.com # Sets up example.com + www.example.com" | |
| echo " $0 example.com www # Same as above" | |
| echo " $0 example.com api # Sets up api.example.com only" | |
| echo " $0 sub.example.com # Sets up sub.example.com only (auto-detected)" | |
| exit 1 | |
| fi | |
| INPUT_DOMAIN=$1 | |
| SUBDOMAIN=${2:-""} | |
| # Count dots in domain to detect if it's already a subdomain | |
| DOT_COUNT=$(echo "$INPUT_DOMAIN" | tr -cd '.' | wc -c) | |
| # Function to check if a valid SSL certificate exists for given domains | |
| check_ssl_exists() { | |
| local domains_to_check="$1" | |
| if ! command -v certbot &> /dev/null; then | |
| return 1 | |
| fi | |
| # Get list of certificates | |
| local cert_output | |
| cert_output=$(certbot certificates 2>/dev/null) | |
| # Check if all required domains are covered by existing certificates | |
| local all_found=true | |
| for domain in $domains_to_check; do | |
| if ! echo "$cert_output" | grep -q "$domain"; then | |
| all_found=false | |
| break | |
| fi | |
| done | |
| if [ "$all_found" = true ]; then | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Function to ensure certbot is installed | |
| ensure_certbot() { | |
| if ! command -v certbot &> /dev/null; then | |
| echo "Installing certbot..." | |
| apt update | |
| apt install -y certbot python3-certbot-apache | |
| fi | |
| } | |
| # Function to create web root directory | |
| create_web_root() { | |
| local web_root="$1" | |
| local display_name="$2" | |
| if [ ! -d "$web_root" ]; then | |
| echo "Creating web root directory: $web_root" | |
| mkdir -p "$web_root" | |
| chown -R www-data:www-data "$web_root" | |
| chmod -R 755 "$web_root" | |
| echo "<h1>Welcome to $display_name</h1>" > "$web_root/index.html" | |
| chown www-data:www-data "$web_root/index.html" | |
| else | |
| echo "Web root already exists: $web_root" | |
| fi | |
| } | |
| # Function to create Apache config and enable site | |
| create_apache_config() { | |
| local conf_file="$1" | |
| local conf_content="$2" | |
| local site_name="$3" | |
| if [ ! -f "$conf_file" ]; then | |
| echo "Creating Apache configuration: $conf_file" | |
| echo "$conf_content" > "$conf_file" | |
| a2ensite "$site_name" | |
| echo "Restarting Apache..." | |
| systemctl restart apache2 | |
| else | |
| echo "Apache configuration already exists: $conf_file" | |
| fi | |
| } | |
| # Function to clean up on failure | |
| cleanup_on_failure() { | |
| local conf_file="$1" | |
| local site_name="$2" | |
| local web_root="$3" | |
| echo "" | |
| echo "ERROR: Setup failed. Cleaning up..." | |
| # Disable and remove site config if it exists | |
| if [ -f "$conf_file" ]; then | |
| echo "Removing Apache configuration: $conf_file" | |
| a2dissite "$site_name" 2>/dev/null || true | |
| rm -f "$conf_file" | |
| fi | |
| # Remove web root if it's empty (only has our default index.html) | |
| if [ -d "$web_root" ]; then | |
| local file_count | |
| file_count=$(find "$web_root" -type f | wc -l) | |
| if [ "$file_count" -le 1 ]; then | |
| echo "Removing web root: $web_root" | |
| rm -rf "$web_root" | |
| else | |
| echo "Web root has user content, leaving it: $web_root" | |
| fi | |
| fi | |
| # Restart Apache to clean state | |
| echo "Restarting Apache..." | |
| systemctl restart apache2 | |
| echo "Cleanup complete." | |
| } | |
| # Function to run certbot with error handling | |
| run_certbot() { | |
| local certbot_args="$1" | |
| local email="$2" | |
| local conf_file="$3" | |
| local site_name="$4" | |
| local web_root="$5" | |
| echo "Setting up SSL certificate..." | |
| # Run certbot and capture exit code | |
| set +e # Temporarily disable exit on error | |
| certbot --apache $certbot_args --non-interactive --agree-tos --email "$email" --redirect | |
| local certbot_exit=$? | |
| set -e # Re-enable exit on error | |
| if [ $certbot_exit -ne 0 ]; then | |
| echo "" | |
| echo "Certbot failed with exit code: $certbot_exit" | |
| cleanup_on_failure "$conf_file" "$site_name" "$web_root" | |
| exit 1 | |
| fi | |
| } | |
| # Determine setup mode based on domain structure and arguments | |
| # If domain has 2+ dots (e.g., sub.domain.com), treat as standalone subdomain | |
| # If domain has 1 dot (e.g., domain.com), check for subdomain argument | |
| if [ "$DOT_COUNT" -ge 2 ]; then | |
| # Input is already a subdomain (e.g., casana.mcchord.net) | |
| # Set up as standalone, no www | |
| SETUP_MODE="subdomain" | |
| TARGET_DOMAIN="$INPUT_DOMAIN" | |
| # Extract base domain for email (last two parts) | |
| BASE_DOMAIN=$(echo "$INPUT_DOMAIN" | rev | cut -d'.' -f1-2 | rev) | |
| elif [ -z "$SUBDOMAIN" ] || [ "$SUBDOMAIN" = "www" ]; then | |
| # Standard domain with www | |
| SETUP_MODE="www" | |
| TARGET_DOMAIN="$INPUT_DOMAIN" | |
| BASE_DOMAIN="$INPUT_DOMAIN" | |
| else | |
| # Explicit subdomain argument | |
| SETUP_MODE="subdomain" | |
| TARGET_DOMAIN="$SUBDOMAIN.$INPUT_DOMAIN" | |
| BASE_DOMAIN="$INPUT_DOMAIN" | |
| fi | |
| # Execute setup based on mode | |
| if [ "$SETUP_MODE" = "www" ]; then | |
| # WWW + Bare Domain Setup | |
| WEB_ROOT="/var/www/$TARGET_DOMAIN" | |
| WWW_DOMAIN="www.$TARGET_DOMAIN" | |
| CONF_FILE="/etc/apache2/sites-available/$TARGET_DOMAIN.conf" | |
| SITE_NAME="$TARGET_DOMAIN.conf" | |
| CERTBOT_DOMAINS="-d $TARGET_DOMAIN -d $WWW_DOMAIN" | |
| echo "Setting up $TARGET_DOMAIN and $WWW_DOMAIN..." | |
| # Create web root | |
| create_web_root "$WEB_ROOT" "$TARGET_DOMAIN" | |
| # Create Apache virtual host configuration | |
| CONF_CONTENT="<VirtualHost *:80> | |
| ServerName $TARGET_DOMAIN | |
| ServerAlias $WWW_DOMAIN | |
| DocumentRoot $WEB_ROOT | |
| ErrorLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-error.log | |
| CustomLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-access.log combined | |
| </VirtualHost>" | |
| create_apache_config "$CONF_FILE" "$CONF_CONTENT" "$SITE_NAME" | |
| # Ensure certbot is installed | |
| ensure_certbot | |
| # Check if SSL certificate already exists | |
| if check_ssl_exists "$TARGET_DOMAIN $WWW_DOMAIN"; then | |
| echo "Valid SSL certificate already exists for $TARGET_DOMAIN and $WWW_DOMAIN. Skipping certbot." | |
| else | |
| run_certbot "$CERTBOT_DOMAINS" "webmaster@$BASE_DOMAIN" "$CONF_FILE" "$SITE_NAME" "$WEB_ROOT" | |
| fi | |
| echo "" | |
| echo "Setup completed successfully for $TARGET_DOMAIN and $WWW_DOMAIN" | |
| echo "Web root: $WEB_ROOT" | |
| else | |
| # Subdomain Setup (standalone, no www) | |
| WEB_ROOT="/var/www/$TARGET_DOMAIN" | |
| CONF_FILE="/etc/apache2/sites-available/$TARGET_DOMAIN.conf" | |
| SITE_NAME="$TARGET_DOMAIN.conf" | |
| CERTBOT_DOMAINS="-d $TARGET_DOMAIN" | |
| echo "Setting up $TARGET_DOMAIN (standalone, no www)..." | |
| # Create web root | |
| create_web_root "$WEB_ROOT" "$TARGET_DOMAIN" | |
| # Create Apache virtual host configuration | |
| CONF_CONTENT="<VirtualHost *:80> | |
| ServerName $TARGET_DOMAIN | |
| DocumentRoot $WEB_ROOT | |
| ErrorLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-error.log | |
| CustomLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-access.log combined | |
| </VirtualHost>" | |
| create_apache_config "$CONF_FILE" "$CONF_CONTENT" "$SITE_NAME" | |
| # Ensure certbot is installed | |
| ensure_certbot | |
| # Check if SSL certificate already exists | |
| if check_ssl_exists "$TARGET_DOMAIN"; then | |
| echo "Valid SSL certificate already exists for $TARGET_DOMAIN. Skipping certbot." | |
| else | |
| run_certbot "$CERTBOT_DOMAINS" "webmaster@$BASE_DOMAIN" "$CONF_FILE" "$SITE_NAME" "$WEB_ROOT" | |
| fi | |
| echo "" | |
| echo "Setup completed successfully for $TARGET_DOMAIN" | |
| echo "Web root: $WEB_ROOT" | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment