#!/usr/bin/env bash
#
# setup-ssl-wildcard.sh — Install a free Let's Encrypt WILDCARD SSL
# certificate on Amazon Linux 1 + Apache using acme.sh + Route 53 DNS-01.
#
# One certificate covers the apex plus every first-level subdomain, e.g.
#   dev.basetax.co.uk        (apex — must be listed explicitly)
#   *.dev.basetax.co.uk      (mtd.dev, api.dev, bookkeeping.dev, ...)
#
# Wildcards REQUIRE DNS-01 validation (Let's Encrypt will not issue a
# wildcard over HTTP-01), so this script uses acme.sh's `dns_aws` mode to
# create/remove the _acme-challenge TXT record in Route 53 automatically.
# That also means renewals run fully unattended — no Apache downtime.
#
# Usage:
#   sudo bash setup-ssl-wildcard.sh BASE_DOMAIN
#   e.g. sudo bash setup-ssl-wildcard.sh dev.basetax.co.uk
#
#   The script issues for BASE_DOMAIN and *.BASE_DOMAIN. Any extra args are
#   added to the SAN list as-is (for a second wildcard tier, etc.):
#   sudo bash setup-ssl-wildcard.sh dev.basetax.co.uk '*.api.dev.basetax.co.uk'
#
# Prerequisites:
#   - The hosted zone for the domain lives in AWS Route 53.
#   - AWS credentials available to acme.sh, via EITHER:
#       (a) an EC2 instance-profile IAM role (PREFERRED — no keys on disk), OR
#       (b) exported AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars.
#     Required Route 53 permissions: route53:GetChange,
#       route53:ListHostedZones, route53:ListHostedZonesByName,
#       route53:ListResourceRecordSets, route53:ChangeResourceRecordSets.
#   - Apache (httpd) installed. (No port 80/443 challenge needed here, but
#     Apache is what serves the finished certificate.)
#   - Run as root (or with sudo).
#
# Note: DNS-01 does NOT need inbound port 80 for validation, but you still
# want the EC2 Security Group to allow inbound TCP 443 so clients can reach
# the HTTPS site once the cert is installed.
#
set -euo pipefail

# ── Argument handling ────────────────────────────────────────────────
if [[ $# -lt 1 ]]; then
    echo "Usage: sudo bash setup-ssl-wildcard.sh BASE_DOMAIN [EXTRA_SAN ...]"
    echo "  e.g. sudo bash setup-ssl-wildcard.sh dev.basetax.co.uk"
    exit 1
fi

BASE_DOMAIN="$1"       # apex domain (used for cert paths / filenames)
shift

# The SAN list: apex + its wildcard, plus any extra names the caller passed.
ALL_DOMAINS=("$BASE_DOMAIN" "*.$BASE_DOMAIN" "$@")

# ── Configurable behaviour (override via environment variables) ──────
# CERT_DIR          Where the installed cert files are written.
#                   IMPORTANT on production: point this at a NEW directory so
#                   you do NOT overwrite the cert files your live vhosts are
#                   currently serving. e.g. CERT_DIR=/etc/ssl/basetax-wildcard
#
# WRITE_APACHE_CONF When 1 (default) the script writes an Apache SSL vhost and
#                   gracefully reloads httpd. Set to 0 for a CERT-ONLY run that
#                   issues + installs the cert files and touches nothing under
#                   /etc/httpd — use this on production so you can update each
#                   subdomain's vhost by hand, testing after each one.
#
# RELOAD_CMD        Command acme.sh runs after installing/renewing the cert.
#                   Defaults to a graceful httpd reload. For a cert-only
#                   production run set RELOAD_CMD=true so nothing touches httpd
#                   yet; once every vhost points at the new cert, re-run
#                   --install-cert with the real reload (see notes at the end).
CERT_DIR="${CERT_DIR:-/etc/ssl/basetax}"
WRITE_APACHE_CONF="${WRITE_APACHE_CONF:-1}"
RELOAD_CMD="${RELOAD_CMD:-service httpd graceful}"

# ── Pre-flight checks ───────────────────────────────────────────────
if [[ $EUID -ne 0 ]]; then
    echo "ERROR: This script must be run as root (use sudo)."
    exit 1
fi

# acme.sh refuses to run when it detects SUDO_USER. Since we already
# verified we are root, clear the variable so acme.sh cooperates.
unset SUDO_USER

# ── Credential sanity check ─────────────────────────────────────────
# If explicit keys are exported we use them; otherwise we assume an
# instance-profile role is attached and let acme.sh / the AWS SDK pick it up.
if [[ -n "${AWS_ACCESS_KEY_ID:-}" && -n "${AWS_SECRET_ACCESS_KEY:-}" ]]; then
    echo "==> Using AWS credentials from environment variables."
else
    echo "==> No AWS_ACCESS_KEY_ID in environment; assuming an EC2 instance"
    echo "    IAM role provides Route 53 access. (acme.sh reads it automatically.)"
fi

# ── Step 1: Install mod_ssl if missing ───────────────────────────────
# Amazon Linux 1 may have httpd24 (Apache 2.4) instead of httpd (2.2).
# The SSL module package name differs between the two.
if rpm -q httpd24 >/dev/null 2>&1; then
    SSL_PKG="mod24_ssl"
elif rpm -q httpd >/dev/null 2>&1; then
    SSL_PKG="mod_ssl"
else
    echo "ERROR: Neither httpd24 nor httpd appears to be installed."
    exit 1
fi

echo "==> Installing $SSL_PKG (if not already present)..."
yum install -y "$SSL_PKG"

# ── Step 2: Install acme.sh ─────────────────────────────────────────
# acme.sh is a pure-shell ACME client — no Python or Snap dependencies.
# It works reliably on Amazon Linux 1 where certbot often fails.
ACME_HOME="/root/.acme.sh"

if [[ ! -d "$ACME_HOME" ]]; then
    echo "==> Installing acme.sh..."
    curl https://get.acme.sh | sh -s email=admin@"$BASE_DOMAIN"
else
    echo "==> acme.sh already installed, upgrading..."
    "$ACME_HOME"/acme.sh --upgrade
fi

# Source acme.sh so it's available in this session
source "$ACME_HOME"/acme.sh.env

# ── Step 3: Issue the wildcard certificate via Route 53 DNS-01 ───────
# acme.sh's dns_aws plugin creates the _acme-challenge TXT record, waits
# for propagation, lets Let's Encrypt verify, then removes the record.
# No Apache downtime, and the exact same flow runs on every renewal.

echo "==> Issuing wildcard certificate for: ${ALL_DOMAINS[*]} ..."

DOMAIN_ARGS=""
for d in "${ALL_DOMAINS[@]}"; do
    DOMAIN_ARGS="$DOMAIN_ARGS -d $d"
done

"$ACME_HOME"/acme.sh --issue --dns dns_aws $DOMAIN_ARGS --server letsencrypt --force

# ── Step 4: Install the certificate for Apache ──────────────────────
mkdir -p "$CERT_DIR"

echo "==> Installing certificate to $CERT_DIR ..."
echo "    (reload command on install/renew: '$RELOAD_CMD')"

# --install-cert is keyed off the primary domain used in --issue.
"$ACME_HOME"/acme.sh --install-cert -d "$BASE_DOMAIN" \
    --cert-file      "$CERT_DIR/cert.pem" \
    --key-file       "$CERT_DIR/key.pem" \
    --fullchain-file "$CERT_DIR/fullchain.pem" \
    --reloadcmd      "$RELOAD_CMD"

# ── Cert-only mode: stop here without touching any httpd config ──────
# On production we want to install the new cert files and leave every
# existing vhost (still serving the OLD cert) completely untouched, so the
# operator can migrate one subdomain at a time and test after each.
if [[ "$WRITE_APACHE_CONF" != "1" ]]; then
    echo ""
    echo "============================================"
    echo " Cert-only run complete — httpd NOT modified."
    echo "============================================"
    echo ""
    echo " Covered names:"
    for d in "${ALL_DOMAINS[@]}"; do
        echo "   - $d"
    done
    echo ""
    echo " New cert files (existing vhosts were left alone):"
    echo "   SSLCertificateFile     $CERT_DIR/fullchain.pem"
    echo "   SSLCertificateKeyFile  $CERT_DIR/key.pem"
    echo ""
    echo " NEXT: update one subdomain's vhost to point at the paths above,"
    echo " run 'httpd -t' (or 'apachectl -t'), reload, and verify that one"
    echo " subdomain before moving to the next. The others keep the old cert."
    echo ""
    echo " AFTER all vhosts are migrated, set the real renewal reload so"
    echo " future auto-renewals reload httpd:"
    echo "   $ACME_HOME/acme.sh --install-cert -d $BASE_DOMAIN \\"
    echo "       --cert-file $CERT_DIR/cert.pem --key-file $CERT_DIR/key.pem \\"
    echo "       --fullchain-file $CERT_DIR/fullchain.pem \\"
    echo "       --reloadcmd \"service httpd graceful\""
    echo "============================================"
    exit 0
fi

# ── Step 5: Configure a single Apache SSL virtual host ───────────────
# One wildcard cert serves the apex and every first-level subdomain, so a
# single default SSL vhost covers them all. Per-subdomain routing (ProxyPass
# by ServerName) can be added below or in separate conf files as needed.
SSL_CONF="/etc/httpd/conf.d/ssl-wildcard-${BASE_DOMAIN}.conf"
echo "==> Writing wildcard Apache SSL config → $SSL_CONF ..."

cat > "$SSL_CONF" <<APACHE_CONF
<VirtualHost *:443>
    ServerName $BASE_DOMAIN
    ServerAlias *.$BASE_DOMAIN

    DocumentRoot /var/www/html

    SSLEngine on
    SSLCertificateFile      $CERT_DIR/fullchain.pem
    SSLCertificateKeyFile   $CERT_DIR/key.pem

    # Modern TLS settings — disable old protocols and weak ciphers
    SSLProtocol             all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder     on

    # HSTS header (optional but recommended — uncomment when you're confident)
    # Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

    # Per-subdomain routing example — copy into its own vhost/conf as needed:
    # <If "%{HTTP_HOST} == 'api.$BASE_DOMAIN'">
    #     ProxyPreserveHost On
    #     ProxyPass        / http://127.0.0.1:3000/
    #     ProxyPassReverse / http://127.0.0.1:3000/
    # </If>
</VirtualHost>

# Redirect all HTTP to HTTPS for the apex and every subdomain
<VirtualHost *:80>
    ServerName $BASE_DOMAIN
    ServerAlias *.$BASE_DOMAIN
    DocumentRoot /var/www/html

    RewriteEngine On
    RewriteRule ^(.*)\$ https://%{HTTP_HOST}\$1 [R=301,L]
</VirtualHost>
APACHE_CONF

# ── Step 6: Test config and reload ───────────────────────────────────
# Use the correct binary name for httpd24 vs httpd
if command -v httpd >/dev/null 2>&1; then
    HTTPD_BIN="httpd"
elif command -v apachectl >/dev/null 2>&1; then
    HTTPD_BIN="apachectl"
else
    HTTPD_BIN="httpd"
fi

echo "==> Testing Apache configuration..."
if $HTTPD_BIN -t 2>&1; then
    echo "==> Configuration OK. Reloading Apache..."
    service httpd graceful
else
    echo "ERROR: Apache config test failed. Check $SSL_CONF"
    exit 1
fi

# ── Step 7: Verify auto-renewal cron ────────────────────────────────
# acme.sh installs a cron job during installation. Because issuance uses
# the Route 53 DNS API, renewals are fully unattended — no manual TXT edits.
echo "==> Verifying auto-renewal cron..."
if crontab -l 2>/dev/null | grep -q acme.sh; then
    echo "    Auto-renewal cron is active."
else
    echo "    WARNING: No acme.sh cron found. Adding one now..."
    "$ACME_HOME"/acme.sh --install-cronjob
fi

# ── Done ─────────────────────────────────────────────────────────────
echo ""
echo "============================================"
echo " Wildcard SSL setup complete!"
echo "============================================"
echo ""
echo " Covered names:"
for d in "${ALL_DOMAINS[@]}"; do
    echo "   - $d"
done
echo ""
echo " Certificate:  $CERT_DIR/fullchain.pem"
echo " Private key:  $CERT_DIR/key.pem"
echo " Apache conf:  $SSL_CONF"
echo ""
echo " The certificate auto-renews via cron using Route 53 DNS-01."
echo " To test renewal:  $ACME_HOME/acme.sh --renew -d $BASE_DOMAIN --force"
echo " To check cron:    crontab -l | grep acme"
echo ""
echo " IMPORTANT: Ensure the EC2 Security Group allows inbound TCP 443,"
echo " and that the IAM role/keys grant Route 53 access to the hosted zone."
echo "============================================"
