#!/bin/bash
#
# Point a set of hostnames at an install's gunicorn, the way that actually
# works on this server.
#
# ── Why this replaces fix-vhost.sh ───────────────────────────────────────────
#
# fix-vhost.sh wrote cPanel userdata files:
#
#   /etc/apache2/conf.d/userdata/{std,ssl}/2_4/<user>/<host>/proxy.conf
#
# The Include line for those files is present in httpd.conf and the files are
# correct, and the site still serves cPanel's default page. Final Target has
# never used that mechanism. Its vhosts are written by hand into
# pre_virtualhost_global.conf, which Apache reads before every cPanel vhost,
# so those blocks win for the hostnames they name.
#
# That is the method proven on this box across finaltarget.com,
# panel.finaltarget.com and www. This script does the same thing, generated
# rather than hand edited, so a new tenant is one command.
#
# ── What it writes ───────────────────────────────────────────────────────────
#
# One managed region per install, delimited by markers:
#
#   # >>> ft-vhost hpglobal >>>
#   ...
#   # <<< ft-vhost hpglobal <<<
#
# Re-running replaces only that region, so two installs never fight and the
# hand written Final Target block at the top of the file is never touched.
#
# Per hostname it writes a :80 and a :443 vhost. /static/ and /media/ are
# excluded from the proxy and served by Apache from the install, everything
# else goes to gunicorn.
#
# ── Usage ────────────────────────────────────────────────────────────────────
#
#   ft-vhost.sh <sysuser> <port> <host> [host ...]
#
#   ft-vhost.sh hpglobal 8070 hpglobal.uk www.hpglobal.uk panel.hpglobal.uk
#
# Dry run, prints the block and changes nothing:
#   APPLY=0 ft-vhost.sh hpglobal 8070 hpglobal.uk
#
set -euo pipefail

APPLY="${APPLY:-1}"
CONF="${CONF:-/etc/apache2/conf.d/includes/pre_virtualhost_global.conf}"
TLS_DIR="${TLS_DIR:-/var/cpanel/ssl/apache_tls}"

USER_="${1:?usage: ft-vhost.sh <sysuser> <port> <host> [host ...]}"
PORT="${2:?usage: ft-vhost.sh <sysuser> <port> <host> [host ...]}"
shift 2
HOSTS=("$@")
[ "${#HOSTS[@]}" -gt 0 ] || { echo "Give at least one hostname."; exit 1; }

BACKEND="/home/${USER_}/public_html/backend"
[ -d "$BACKEND" ] || { echo "No install at $BACKEND"; exit 1; }

IP="$(cat /var/cpanel/mainip 2>/dev/null || true)"
[ -n "$IP" ] || { echo "Could not read /var/cpanel/mainip"; exit 1; }

# Apache refuses to start if ErrorLog points into a directory that does not
# exist. hpglobal had no logs/ at all after install, which is also why
# ft-ping-all.sh reported it.
if [ "$APPLY" = "1" ]; then
    mkdir -p "${BACKEND}/logs"
    chown "${USER_}:${USER_}" "${BACKEND}/logs"
fi

# The account's main domain, used as the fallback certificate.
MAIN_DOMAIN="$(awk '/^main_domain:/ {print $2}' "/var/cpanel/userdata/${USER_}/main" 2>/dev/null || true)"

# The certificate to serve for a hostname.
#
# AutoSSL names each directory after the hostname it covers, but it will only
# ever issue for domains cPanel considers the account's own. A domain added as
# an alias, which hpglobal.uk is, never gets one, and no amount of AutoSSL runs
# will change that.
#
# So fall back to the account's main domain certificate. The name will not
# match the hostname, which is fine and is exactly what Final Target has always
# done: its :443 block serves the finaltarget.net certificate under
# ServerName finaltarget.com. Cloudflare terminates TLS for the visitor with
# its own valid certificate and, on Full rather than Full (strict), does not
# check the origin's name.
#
# If you ever move a site off Cloudflare, give it a real certificate first.
cert_for() {
    if [ -f "${TLS_DIR}/$1/combined" ]; then
        echo "${TLS_DIR}/$1/combined"
    elif [ -n "$MAIN_DOMAIN" ] && [ -f "${TLS_DIR}/${MAIN_DOMAIN}/combined" ]; then
        echo "${TLS_DIR}/${MAIN_DOMAIN}/combined"
    fi
}

# Everything goes to gunicorn. Nothing is carved out.
#
# Apache aliases for /static/ and /media/ look like an optimisation and are a
# trap. WhiteNoise already serves static from inside the app, and media is
# served by Django through config/urls.py. An Alias plus "ProxyPass /media/ !"
# takes those paths away from the routes that work and hands them to Apache,
# which then serves nothing, so the panel loads with no styling and every
# uploaded image 404s.
emit_common() {
    cat <<BLOCK
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:${PORT}/
    ProxyPassReverse / http://127.0.0.1:${PORT}/

    ErrorLog ${BACKEND}/logs/apache_error.log
    CustomLog ${BACKEND}/logs/apache_access.log combined
BLOCK
}

BLOCK_FILE="$(mktemp)"
NO_CERT=()

{
    echo "# >>> ft-vhost ${USER_} >>>"
    echo "# Generated by ft-vhost.sh. Edits inside this region are overwritten."
    for h in "${HOSTS[@]}"; do
        echo
        echo "<VirtualHost ${IP}:80>"
        echo "    ServerName ${h}"
        emit_common
        echo "</VirtualHost>"

        crt="$(cert_for "$h" || true)"
        if [ -n "$crt" ]; then
            echo
            echo "<VirtualHost ${IP}:443>"
            echo "    ServerName ${h}"
            echo "    SSLEngine on"
            echo "    SSLCertificateFile ${crt}"
            echo "    SSLCertificateKeyFile ${crt}"
            # Apache terminates TLS and forwards over plain http. Without this
            # Django sees an insecure request, SECURE_SSL_REDIRECT fires, and
            # the browser gets a 301 back to the address it already asked for.
            echo '    RequestHeader set X-Forwarded-Proto "https"'
            emit_common
            echo "</VirtualHost>"
        else
            NO_CERT+=("$h")
        fi
    done
    echo "# <<< ft-vhost ${USER_} <<<"
} > "$BLOCK_FILE"

if [ "$APPLY" != "1" ]; then
    cat "$BLOCK_FILE"
    echo
    echo "Dry run. Nothing written."
    [ "${#NO_CERT[@]}" -gt 0 ] && echo "No certificate yet: ${NO_CERT[*]}"
    rm -f "$BLOCK_FILE"
    exit 0
fi

STAMP="$(date +%F-%H%M%S)"
cp -a "$CONF" "${CONF}.bak-${STAMP}"
echo "  backup ${CONF}.bak-${STAMP}"

# Drop any previous region for this install, then append the new one. sed
# rather than an in place edit of the whole file, so the hand written block at
# the top survives untouched.
NEW="$(mktemp)"
sed "/^# >>> ft-vhost ${USER_} >>>$/,/^# <<< ft-vhost ${USER_} <<<$/d" "$CONF" > "$NEW"
cat "$BLOCK_FILE" >> "$NEW"
cp "$NEW" "$CONF"
rm -f "$NEW" "$BLOCK_FILE"

if ! apachectl configtest 2>&1 | tail -2; then
    echo "  Syntax failed. Restoring."
    cp -a "${CONF}.bak-${STAMP}" "$CONF"
    exit 1
fi

systemctl restart httpd
sleep 3
echo "  httpd: $(systemctl is-active httpd)"

echo "→ Against the origin, bypassing any CDN"
for h in "${HOSTS[@]}"; do
    code="$(curl -sk -o /dev/null -w '%{http_code}' --resolve "${h}:443:${IP}" "https://${h}/" || echo ---)"
    printf '  %-28s %s\n' "$h" "$code"
done

if [ "${#NO_CERT[@]}" -gt 0 ]; then
    echo
    echo "  No certificate at all, http only: ${NO_CERT[*]}"
fi

echo
echo "  Rollback: cp -a ${CONF}.bak-${STAMP} ${CONF} && systemctl restart httpd"
