#!/usr/bin/env bash
# =============================================================================
# onx-apache-write-conf — Atomic write of /etc/httpd/conf.d/onox-custom.conf
#                          with apachectl configtest gate + auto rollback.
#
# Input (stdin JSON):
#   { "content": "...conf body...", "target": "/etc/httpd/conf.d/onox-custom.conf" }
#
# Output (stdout JSON):
#   { "ok": true, "path": "...", "backup": "...", "uptime": "..." }
#
# Exit codes:
#   0  — success
#   1  — invalid input
#   2  — preflight fail (apachectl missing)
#   3  — execution fail
#   4  — execution fail + rollback succeeded (configtest failed)
#   5  — execution fail + rollback FAILED (critical)
#
# Sudoers:
#   apache ALL=(root) NOPASSWD: /usr/local/onoxsoft/bin/onx-apache-write-conf
#
# Deployed to: /usr/local/onoxsoft/bin/onx-apache-write-conf
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

# ── Preflight ────────────────────────────────────────────────────────────────
command -v apachectl >/dev/null 2>&1 || onx_die 2 "apachectl not found"

onx_json_input

CONTENT=$(echo "$INPUT" | jq -r '.content // empty')
TARGET=$(onx_json_field 'target' '/etc/httpd/conf.d/onox-custom.conf')

[[ -n "$CONTENT" ]] || onx_die 1 "content is required"

# ── Target whitelist (anti-traversal + lock to /etc/httpd/conf.d) ────────────
case "$TARGET" in
    /etc/httpd/conf.d/onox-*.conf) : ;;
    /etc/httpd/conf.modules.d/onox-*.conf) : ;;
    *) onx_die 1 "target_not_allowed: $TARGET (must be /etc/httpd/conf.d/onox-*.conf)" ;;
esac

[[ "$TARGET" == *".."* ]] && onx_die 1 "target_traversal_detected"

# ── Backup current file (if exists) ──────────────────────────────────────────
BACKUP=""
if [[ -f "$TARGET" ]]; then
    BACKUP="${TARGET}.bak-$(date +%s)"
    cp -p "$TARGET" "$BACKUP" || onx_die 3 "backup_failed"
    onx_log "Backup created: $BACKUP"
    onx_rollback_register "mv -f '$BACKUP' '$TARGET'"
else
    # Remove freshly created file on rollback so configtest with an empty
    # file does not leave dangling cruft.
    onx_rollback_register "rm -f '$TARGET'"
fi

trap 'onx_rollback_run' ERR

# ── Write file atomically via temp + mv (so half-writes are impossible) ──────
TMPF=$(mktemp -t onox-apache-XXXXXX.conf)
chmod 0644 "$TMPF"
printf '%s\n' "$CONTENT" > "$TMPF"
mv -f "$TMPF" "$TARGET"
chmod 0644 "$TARGET"
chown root:root "$TARGET" 2>/dev/null || true

onx_log "Wrote config: $TARGET ($(wc -c <"$TARGET") bytes)"

# ── apachectl configtest gate ────────────────────────────────────────────────
if ! TEST_OUT=$(apachectl configtest 2>&1); then
    onx_log "configtest FAILED: $TEST_OUT"
    # ERR trap kicks in via the explicit `false` below → rollback executes.
    false
fi

# Disable trap before optional reload — reload errors should surface as exec
# failures, not rollback (config is good, reload may still hiccup).
trap - ERR

# ── Reload ───────────────────────────────────────────────────────────────────
if command -v systemctl >/dev/null 2>&1; then
    if ! systemctl reload httpd 2>/dev/null; then
        # Try apachectl graceful as a fallback
        apachectl graceful || onx_die 3 "reload_failed"
    fi
else
    apachectl graceful || onx_die 3 "reload_failed"
fi

# ── Uptime probe ─────────────────────────────────────────────────────────────
UPTIME=""
if command -v systemctl >/dev/null 2>&1; then
    UPTIME=$(systemctl show httpd --property=ActiveEnterTimestamp --value 2>/dev/null || echo "")
fi

jq -nc \
    --argjson ok true \
    --arg path "$TARGET" \
    --arg backup "${BACKUP:-}" \
    --arg uptime "$UPTIME" \
    '{ok: $ok, path: $path, backup: $backup, uptime: $uptime}'

exit 0
