#!/bin/bash
# /usr/lib/keel/overlays/anubis/state.d/50keel-web enabled|disabled
#
# Keel Web's default site, with or without Anubis in front of it (handbook
# decisions 0030 and 0041, step 7). keel spec apply runs it when the anubis
# overlay is turned on or off: after anubis@keel.service starts, and
# before it stops (keel's docs/apply.md, "State hooks").
#
# enabled: replaces the link sites-enabled/keel-default (the default site
# of a simple installation, which the package's postinst enabled) with
# sites-enabled/default-anubis, the same pages behind Anubis: its servers
# take the default server on port 80, which serves the same as 443 while
# the certificate is self-signed and redirects to HTTPS when a CA's is in
# place (http-mode, decision 0079), and on port 443, which serves HTTPS with the machine's certificate (refused,
# changing nothing, while it is not there) and sends every request through
# /etc/nginx/snippets/keel-anubis.conf of keel-overlay-anubis, and what
# Anubis allows comes back on unix:/run/nginx/keel-app.sock, where the
# pages are served. The front only ever takes the default site's place,
# and only while that link points to keel-web's file: a link of the
# operator's or a file there is refused, and with no keel-default link the
# default server is the operator's, so nothing is changed and the run says
# so (decision 0042, "Debian's default site", has keel's sites take the
# same care). Debian's own default site is nothing to this hook: the
# postinst took its link away and the postrm gives it back.
#
# disabled: removes the front, and links the default site again where
# nothing stands, when its file is there (the package is installed).
#
# Either way the result is tested with `nginx -t` and Nginx reloaded when
# it runs; a refused test puts the links back as they were and fails, and
# a failed reload after enabled does the same and reloads again. The two
# sites are never enabled together.
#
# Exit codes: 0 done or unchanged, 1 refused or rolled back, 2 usage.
#
# The KEEL_* variables exist for tests/anubis-front.bats; on a machine the
# defaults are the paths of trixie's nginx.
set -euo pipefail

NGINX_DIR="${KEEL_NGINX_DIR:-/etc/nginx}"
PID_FILE="${KEEL_NGINX_PID:-/run/nginx.pid}"

FRONT_SITE="$NGINX_DIR/sites-available/default-anubis"
FRONT_LINK="$NGINX_DIR/sites-enabled/default-anubis"
DEFAULT_SITE="$NGINX_DIR/sites-available/keel-default"
DEFAULT_LINK="$NGINX_DIR/sites-enabled/keel-default"
# the machine's certificate, which the front serves HTTPS with
CERT="${KEEL_WEB_CERT:-/usr/local/share/ca-certificates/cert.crt}"
KEY="${KEEL_WEB_KEY:-/etc/ssl/private/cert.key}"

say() {
    echo "keel-web: $*"
}

fail() {
    say "$*" >&2
    exit 1
}

running() {
    local pid
    pid="$(cat "$PID_FILE" 2>/dev/null)" || return 1
    [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
}

# a file of the operator's where a link of this hook goes is never replaced
refuse_file() {
    if [ -e "$1" ] && [ ! -L "$1" ]; then
        fail "$1 is not a link; it is left alone, move it away first"
    fi
}

# The front serves HTTPS with the machine's certificate (keel-host-keys
# makes it at the first boot); without it nginx -t would refuse the front.
# Said here, before anything changes.
has_certificate() {
    local file
    for file in "$CERT" "$KEY"; do
        [ -s "$file" ] || fail "$file, the machine's certificate or its key, is not there: the front serves HTTPS with it, so Anubis is not put in front of the default site (keel-host-keys makes it at the first boot)"
    done
}

default_site_linked() {
    [ "$(readlink "$DEFAULT_LINK" 2>/dev/null || true)" = "$DEFAULT_SITE" ]
}

enable() {
    if [ "$(readlink "$FRONT_LINK" || true)" = "$FRONT_SITE" ]; then
        say "unchanged (enabled)"
        return 0
    fi
    refuse_file "$FRONT_LINK"
    # The front only ever takes the default site's place. Without its link
    # the default server is whatever the operator made it, and the front's
    # default_server would take it from them.
    if [ ! -e "$DEFAULT_LINK" ] && [ ! -L "$DEFAULT_LINK" ]; then
        say "keel-web's default site is not enabled, so the default server is the operator's: it is left alone, and Anubis is put in front of no site"
        return 0
    fi
    refuse_file "$DEFAULT_LINK"
    local target
    target="$(readlink "$DEFAULT_LINK")"
    [ "$target" = "$DEFAULT_SITE" ] || fail "$DEFAULT_LINK points to $target, not keel-web's default site; it is left alone and Anubis is not put in front of it"
    has_certificate
    rm -f "$DEFAULT_LINK"
    ln -sfn "$FRONT_SITE" "$FRONT_LINK"
    if ! nginx -t; then
        undo_enable
        fail "nginx -t refused the configuration with the front; rolled back"
    fi
    if ! running; then
        say "enabled: the default site goes through Anubis (nginx is not running: it reads it at its start)"
        return 0
    fi
    if ! nginx -s reload; then
        undo_enable
        nginx -s reload || :
        fail "nginx -s reload failed; rolled back"
    fi
    say "enabled: the default site goes through Anubis"
}

undo_enable() {
    rm -f "$FRONT_LINK"
    ln -sfn "$DEFAULT_SITE" "$DEFAULT_LINK"
}

disable() {
    refuse_file "$FRONT_LINK"
    if [ ! -L "$FRONT_LINK" ]; then
        say "unchanged (disabled)"
        return 0
    fi
    local front relinked=no
    front="$(readlink "$FRONT_LINK")"
    rm -f "$FRONT_LINK"
    if [ ! -e "$DEFAULT_LINK" ] && [ ! -L "$DEFAULT_LINK" ] && [ -f "$DEFAULT_SITE" ]; then
        ln -s "$DEFAULT_SITE" "$DEFAULT_LINK"
        relinked=yes
    fi
    if ! nginx -t; then
        [ "$relinked" = no ] || rm -f "$DEFAULT_LINK"
        ln -s "$front" "$FRONT_LINK"
        fail "nginx -t refused the configuration without the front; the links are put back"
    fi
    local state="the default site is keel-web's"
    if ! default_site_linked; then
        state="no default site is enabled ($DEFAULT_SITE is not there, or the link is the operator's)"
    fi
    if ! running; then
        say "disabled: $state (nginx is not running)"
        return 0
    fi
    nginx -s reload || fail "disabled, but nginx -s reload failed: the running workers keep the front until Nginx restarts"
    say "disabled: $state"
}

case "${1:-}" in
    enabled) enable ;;
    disabled) disable ;;
    *)
        echo "usage: anubis-front enabled|disabled" >&2
        exit 2
        ;;
esac
