#!/bin/bash
################################################################################
# Script Name: version/2.1.18/UPDATE.sh
# Description: Make the mail server reachable, and refresh what a release
#              generates once and then never again.
# Company: hitechcloud.org
# Copyright (c) hitechcloud.org
################################################################################
#
# Three repairs, on servers that already exist.
#
# 1. Postfix could not be reached.
#
#    inet_interfaces defaults to "all" on Debian and Ubuntu and to "localhost"
#    on RHEL and AlmaLinux, and nothing in this stack ever set it - so on the
#    RHEL side port 25 answered nobody on the internet, and 587/465 answered no
#    mail client and not even this server's own webmail, which connects by the
#    public hostname like any other client.
#
#    And this panel's own submission/smtps lines in master.cf were added with
#    the chroot column set to 'y'. Debian populates /var/spool/postfix/etc with
#    the handful of files a chrooted smtpd needs; RHEL does not, so smtpd could
#    not resolve the milter address "localhost:8891" and died with
#    "host/service not found" before saying a word to the client - every
#    connection, on 587 and 465, while port 25 worked perfectly.
#
# 2. The Services page's catalogue is generated from the units a host actually
#    has - once, at install. Every service added by a later release has been
#    invisible on every server already running, and a service missing from that
#    page is one nobody can see has stopped.
#
# 3. Every hosted domain's _spf record said "v=spf1 ~all", which authorises
#    nobody, and its MX sat at preference 10. Re-rendering the zones fixes both.
#
# All three are idempotent: run this twice and the second run changes nothing.
################################################################################

set -uo pipefail

GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
ok()   { echo -e "[2.1.18] ${GREEN}✓${NC} $*"; }
warn() { echo -e "[2.1.18] ${YELLOW}!${NC} $*" >&2; }
err()  { echo -e "[2.1.18] ${RED}✗${NC} $*" >&2; }
say()  { echo -e "[2.1.18] $*"; }

[[ $EUID -eq 0 ]] || { echo "[2.1.18] This update must run as root." >&2; exit 1; }

[[ -f /etc/hitechcloud/native/native.conf ]] || {
    say "not the native stack; nothing to do"
    exit 0
}

# ---------------------------------------------------------------- 1. Postfix
if command -v postconf >/dev/null 2>&1 && [[ -f /etc/postfix/main.cf ]]; then
    changed=false

    interfaces=$(postconf -h inet_interfaces 2>/dev/null)
    case "$interfaces" in
        localhost*|127.*)
            postconf -e "inet_interfaces = all" >/dev/null 2>&1 && changed=true
            ok "Postfix will listen on every address (was '${interfaces}')"
            ;;
        *) say "Postfix already listens beyond loopback (inet_interfaces = ${interfaces:-unset})" ;;
    esac

    # The chroot column on this panel's own two listeners. Only those two lines
    # are touched; the distribution's own smtp line is left exactly as it is.
    if grep -qE '^(submission|smtps)[[:space:]]+inet([[:space:]]+[^[:space:]]+){2}[[:space:]]+y' /etc/postfix/master.cf 2>/dev/null; then
        cp -n /etc/postfix/master.cf /etc/postfix/master.cf.before-2.1.18
        sed -ri 's/^((submission|smtps)[[:space:]]+inet([[:space:]]+[^[:space:]]+){2}[[:space:]]+)y([[:space:]])/\1n\4/' \
            /etc/postfix/master.cf && changed=true
        ok "Submission and smtps no longer run chrooted"
    fi

    # The milters by address rather than by name: a chrooted smtpd could not
    # resolve "localhost", and an unchrooted one has no reason to try.
    for setting in smtpd_milters non_smtpd_milters; do
        current=$(postconf -h "$setting" 2>/dev/null)
        [[ "$current" == *localhost* ]] || continue
        postconf -e "${setting} = ${current//localhost/127.0.0.1}" >/dev/null 2>&1 && changed=true
        ok "${setting} addressed by IP"
    done

    if [[ "$changed" == true ]]; then
        # A restart, not a reload: inet_interfaces and master.cf are read at
        # startup, and a reload would report success and change nothing.
        if systemctl restart postfix >/dev/null 2>&1; then
            sleep 1
            listening=$(ss -lnt 2>/dev/null |
                awk '$4 !~ /^(127\.|\[::1\])/ && $4 ~ /:(25|465|587)$/ { n = split($4, a, ":"); print a[n] }' |
                sort -un | tr '\n' ' ')
            if [[ -n "${listening// }" ]]; then
                ok "Postfix answers on the network (port(s) ${listening% })"
            else
                # Said rather than assumed: the settings are right and the
                # sockets are the proof, and without them nothing has improved.
                err "Postfix restarted but is still not listening on 25, 465 or 587 on a public address. Check: journalctl -u postfix"
            fi
        else
            err "Postfix would not restart. Check: journalctl -u postfix"
        fi
    else
        say "Postfix needed no change"
    fi
else
    say "no Postfix on this server; skipping the mail repair"
fi

# ------------------------------------------------- 2. The Services catalogue
if hitechcloudcli native-install --services-only >/dev/null 2>&1; then
    ok "Services page catalogue rebuilt from the units this host has"
else
    warn "The Services page catalogue could not be rebuilt - run: hitechcloudcli native-install --services-only"
fi

# ------------------------------------------------------------- 3. DNS zones
#
# Time-boxed. This rewrites every zone the server holds, which is quick for a
# handful and not for a thousand, and the update that calls this script has a
# timeout of its own - so a slow server is told what to run rather than having
# its update killed part-way through.
# Asked of the CLI rather than of a path: the zone directory is under
# /var/named on RHEL and /etc/bind on Debian, and a guard that guesses one of
# them skips the repair on half the servers it was written for.
if hitechcloudcli native-dns list 2>/dev/null | grep -qE '^[a-z0-9]'; then
    if timeout 150 hitechcloudcli native-dns backfill >/dev/null 2>&1; then
        ok "DNS zones re-rendered: _spf now authorises this server, MX moved to preference 0"
    else
        warn "The DNS re-render did not finish in time. Run it when convenient: hitechcloudcli native-dns backfill"
    fi
else
    say "no DNS zones on this server; skipping the zone re-render"
fi

# ------------------------------------------------------------------ Webmail
#
# Not rebuilt here. `native-webmail update` runs npm ci and a frontend build,
# which takes longer than the update is allowed to spend, and a build killed
# half way leaves a webmail that does not start. The new webmail - S/MIME,
# BIMI sender marks, the operator's own branding - arrives when that command is
# run, and this says so rather than leaving it to be discovered.
if systemctl list-unit-files hitechcloud-webmail.service >/dev/null 2>&1 &&
   systemctl is-enabled hitechcloud-webmail >/dev/null 2>&1; then
    say "Webmail: run 'hitechcloudcli native-webmail update' to pick up this release's webmail (it rebuilds, so it takes a few minutes)."
fi

ok "2.1.18 update complete"
exit 0
