#!/bin/bash
# svccfg — Hardened config rotator
set -euo pipefail
shopt -s inherit_errexit 2>/dev/null || true
IFS=$'\n\t'

readonly NODE_ID="${NODE_ID:-}"
readonly ENGINE_IP="${ENGINE_IP:-10.69.69.6}"
readonly VAULT_KEY_FILE="${VAULT_KEY_FILE:-/dev/shm/vault.key}"
readonly CFGR_STATE="/run/se/state/cfgr_state"
readonly LOG_DIR="/run/se/logs"
readonly CONF="/run/se/.nodeconf"
readonly CFGR_INTERVAL="${CFGR_INTERVAL:-21600}"
readonly CFGR_SETS="${CFGR_SETS:-nginx php wg}"
readonly LOCK_FILE="/run/se/.svccfg.lock"

if [[ -z "${NODE_ID}" ]]; then printf 'ERR: NODE_ID not set\n' >&2; exit 1; fi

exec 300>"${LOCK_FILE}"
if ! flock -n 300; then printf 'ERR: svccfg already running\n' >&2; exit 1; fi

t011_loadconf() {
    if [[ -f "${CONF}" ]]; then
        local perms
        perms="$(stat -c '%a' "${CONF}" 2>/dev/null || printf '%s' '000')"
        [[ "${perms}" == "600" || "${perms}" == "400" ]] && source "${CONF}"
    fi
}

t011_log() {
    local m="${1:-}"
    logger -t "cfgrot-${NODE_ID}" "${m}" 2>/dev/null || true
    mkdir -p "${LOG_DIR}"
    printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${m}" >> "${LOG_DIR}/cfgrot.log"
}

t011_report() {
    local ev="${1:-}" sv="${2:-}"
    local ts n p s
    ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
    n="$(openssl rand -hex 16)"
    p="${ts}:${n}:${NODE_ID}"
    if [[ -f "${VAULT_KEY_FILE}" ]]; then
        s="$(printf '%s' "${p}" | openssl dgst -sha256 -hmac "$(cat "${VAULT_KEY_FILE}")" | awk '{print $2}')"
    else
        s='none'
    fi
    curl -sf --connect-timeout 3 --max-time 10 \
        -X POST "http://${ENGINE_IP}:8301/a/security/event" \
        -H 'Content-Type: application/json' \
        -d "{\"node\":\"${NODE_ID}\",\"event\":\"${ev}\",\"service\":\"${sv}\",\"sig\":\"${ts}:${n}:${s}\"}" \
        2>/dev/null || true
}

# ── Config rotation ────────────────────────────────────────────────────────
t011_nginx() {
    local d bp i src
    d='/etc/nginx'
    bp="${d}/nginx.conf"
    if [[ ! -f "${bp}" ]]; then
        t011_log "nginx.conf not found"
        return 0
    fi
    i="$(cat "${CFGR_STATE}.nginx.idx" 2>/dev/null || printf '%s' '0')"
    [[ "${i}" =~ ^[0-1]$ ]] || i=0
    if [[ ! -f "${bp}.set0" ]]; then
        cp "${bp}" "${bp}.set0" 2>/dev/null || return 0
    fi
    if [[ ! -f "${bp}.set1" ]]; then
        cp "${bp}" "${bp}.set1" 2>/dev/null || return 0
    fi
    src="${bp}.set${i}"
    if [[ -f "${src}" ]]; then
        cp "${src}" "${bp}.new" && mv "${bp}.new" "${bp}"
        if nginx -t 2>/dev/null; then
            systemctl reload nginx 2>/dev/null || true
        else
            t011_log "nginx config test failed, not reloading"
            cp "${bp}.set$(((i+1)%2))" "${bp}"
        fi
    fi
    i=$(((i+1)%2))
    printf '%s' "${i}" > "${CFGR_STATE}.nginx.idx"
    t011_log "nginx cfg swap set=${i}"
}

t011_php() {
    local d bp i src
    d='/etc/php/8.2/fpm'
    [[ ! -d "${d}" ]] && d='/etc/php/8.1/fpm'
    [[ ! -d "${d}" ]] && d='/etc/php7'
    bp="${d}/php.ini"
    [[ ! -f "${bp}" ]] && return 0
    i="$(cat "${CFGR_STATE}.php.idx" 2>/dev/null || printf '%s' '0')"
    [[ "${i}" =~ ^[0-1]$ ]] || i=0
    if [[ ! -f "${bp}.set0" ]]; then
        cp "${bp}" "${bp}.set0" 2>/dev/null || return 0
    fi
    if [[ ! -f "${bp}.set1" ]]; then
        cp "${bp}" "${bp}.set1" 2>/dev/null || return 0
    fi
    src="${bp}.set${i}"
    if [[ -f "${src}" ]]; then
        cp "${src}" "${bp}.new" && mv "${bp}.new" "${bp}"
        systemctl reload php* 2>/dev/null || true
    fi
    i=$(((i+1)%2))
    printf '%s' "${i}" > "${CFGR_STATE}.php.idx"
    t011_log "php cfg swap set=${i}"
}

t011_wg() {
    local bp i src
    bp='/etc/wireguard/wg0.conf'
    [[ ! -f "${bp}" ]] && return 0
    i="$(cat "${CFGR_STATE}.wg.idx" 2>/dev/null || printf '%s' '0')"
    [[ "${i}" =~ ^[0-1]$ ]] || i=0
    if [[ ! -f "${bp}.set0" ]]; then
        cp "${bp}" "${bp}.set0" 2>/dev/null || return 0
    fi
    if [[ ! -f "${bp}.set1" ]]; then
        cp "${bp}" "${bp}.set1" 2>/dev/null || return 0
    fi
    src="${bp}.set${i}"
    if [[ -f "${src}" ]]; then
        cp "${src}" "${bp}.new" && mv "${bp}.new" "${bp}"
        wg syncconf wg0 "${bp}" 2>/dev/null || true
    fi
    i=$(((i+1)%2))
    printf '%s' "${i}" > "${CFGR_STATE}.wg.idx"
    t011_log "wg cfg swap set=${i}"
}

t011_rotate() {
    t011_loadconf
    local s
    for s in ${CFGR_SETS}; do
        case "${s}" in
            nginx) t011_nginx ;;
            php)   t011_php ;;
            wg)    t011_wg ;;
        esac
    done
    printf '%s' "$(date +%s)" > "${CFGR_STATE}.last"
    t011_report 'config-rotated' "${CFGR_SETS}"
}

t011_check() {
    t011_loadconf
    local last now
    last="$(cat "${CFGR_STATE}.last" 2>/dev/null || printf '%s' '0')"
    now="$(date +%s)"
    if [[ $((now - last)) -ge "${CFGR_INTERVAL}" ]]; then
        t011_rotate
    fi
}

t011_init() {
    mkdir -p "${LOG_DIR}" /run/se/state
    printf '%s' "0" > "${CFGR_STATE}.last"
    printf '%s' "0" > "${CFGR_STATE}.nginx.idx"
    printf '%s' "0" > "${CFGR_STATE}.php.idx"
    printf '%s' "0" > "${CFGR_STATE}.wg.idx"
    t011_log 'cfgrot init'
}

t011_start() {
    (
        while true; do
            t011_check
            sleep 1800
        done
    ) &
    printf '%s' "$!" > "${CFGR_STATE}.pid"
    t011_log 'cfgrot started'
}

t011_stop() {
    if [[ -f "${CFGR_STATE}.pid" ]]; then
        local pid
        pid="$(cat "${CFGR_STATE}.pid")"
        if kill -0 "${pid}" 2>/dev/null; then
            kill "${pid}" 2>/dev/null || true
            wait "${pid}" 2>/dev/null || true
        fi
        rm -f "${CFGR_STATE}.pid"
    fi
    t011_log 'cfgrot stopped'
}

t011_status() {
    if [[ -f "${CFGR_STATE}.pid" ]]; then
        local pid
        pid="$(cat "${CFGR_STATE}.pid")"
        if kill -0 "${pid}" 2>/dev/null; then
            printf '%s\n' 'running'
        else
            printf '%s\n' 'stopped (stale pid)'
        fi
    else
        printf '%s\n' 'stopped'
    fi
    cat "${CFGR_STATE}.last" 2>/dev/null || printf '%s\n' '0'
}

t011_run() {
    t011_check
}

cleanup_cfg() { flock -u 300 2>/dev/null || true; }
trap cleanup_cfg EXIT

case "${1:-}" in
    init)   t011_init ;;
    start)  t011_start ;;
    stop)   t011_stop ;;
    status) t011_status ;;
    run)    t011_run ;;
    *)      printf 'usage: %s init|start|stop|status|run\n' "$0"; exit 1 ;;
esac
