#!/bin/bash
# freeipa-server.sh — Interactive FreeIPA server installer
# Collects all configuration, installs the IPA server, and outputs customized
# client enrollment scripts (freeipa-enroll.sh, freeipa-client.sh, answerfile,
# and optionally auto-enroll-ansible.sh) ready for distribution.

set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"

log "Starting FreeIPA server installer..."

# FreeIPA server provisioning is interactive and must run on a fully booted system
# (ipa-server-install needs running DNS/Kerberos/dirsrv services). It cannot run in
# the install chroot or unattended, so bail out cleanly there instead of hanging on
# the configuration prompts below.
if [[ "${MARCHY_UNATTENDED:-0}" == "1" || ! -t 0 ]]; then
    skip "Unattended mode — run the FreeIPA server installer manually after first boot."
    exit 0
fi

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; NC='\033[0m'

log()     { echo -e "${GREEN}[+]${NC} $*"; }
warn()    { echo -e "${YELLOW}[!]${NC} $*"; }
error()   { echo -e "${RED}[✗]${NC} $*" >&2; }
info()    { echo -e "${CYAN}[i]${NC} $*"; }
section() { echo -e "\n${BLUE}━━━ $* ━━━${NC}"; }
ask()     { printf "${MAGENTA}[?]${NC} %s " "$*"; }

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
IPA_BASE="$SCRIPT_DIR/../../FreeipaAnsible"

# ─── Root check ───────────────────────────────────────────────────────────────
[[ $EUID -ne 0 ]] && { error "Must be run as root."; exit 1; }

# ─── OS detection ─────────────────────────────────────────────────────────────
source /etc/os-release
OS_ID="${ID}"; OS_PRETTY="${PRETTY_NAME}"

section "System check"
log "OS: $OS_PRETTY"

case "$OS_ID" in
    rhel|centos|rocky|almalinux)
        PKG_MGR="dnf"
        IPA_SERVER_PKGS="freeipa-server freeipa-server-dns freeipa-server-trust-ad"
        ANSIBLE_PKGS="ansible-core python3-netaddr"
        ;;
    fedora)
        PKG_MGR="dnf"
        IPA_SERVER_PKGS="freeipa-server freeipa-server-dns"
        ANSIBLE_PKGS="ansible-core python3-netaddr"
        ;;
    arch)
        PKG_MGR="pacman"
        IPA_SERVER_PKGS="freeipa"
        ANSIBLE_PKGS="ansible python-netaddr"
        warn "Arch: FreeIPA server support is community-maintained. RHEL/Rocky recommended for production."
        ;;
    *)
        error "FreeIPA server is not supported on $OS_PRETTY."
        error "Supported: RHEL, Rocky, AlmaLinux, CentOS, Fedora, Arch."
        exit 1
        ;;
esac

# ─── Conflict pre-flight ──────────────────────────────────────────────────────
section "Conflict checks"

if [[ -f /etc/ipa/default.conf ]]; then
    if grep -q "mode=production" /etc/ipa/default.conf 2>/dev/null || \
       grep -q "^basedn=" /etc/ipa/default.conf 2>/dev/null; then
        error "/etc/ipa/default.conf exists — this host may already be an IPA server or enrolled client."
        error "Uninstall first: ipa-server-install --uninstall or ipa-client-install --uninstall"
        exit 1
    fi
fi

for svc in named dirsrv krb5kdc; do
    if systemctl is-active "$svc" &>/dev/null; then
        warn "Service '$svc' is already running and may conflict with FreeIPA."
        ask "Continue anyway? [y/N]:"; read -r C
        [[ "${C,,}" != "y"* ]] && exit 1
    fi
done

for port in 389 636 88; do
    if ss -tlnp 2>/dev/null | grep -q ":${port} " || \
       ss -ulnp 2>/dev/null | grep -q ":${port} "; then
        warn "Port $port is already in use. FreeIPA requires this port."
        ask "Continue anyway? [y/N]:"; read -r C
        [[ "${C,,}" != "y"* ]] && exit 1
    fi
done

log "Conflict checks passed."

# ─── Gather configuration ─────────────────────────────────────────────────────
section "Configuration"
info "Leave any field blank to accept the value shown in [brackets]."
echo

CURRENT_FQDN=$(hostname -f 2>/dev/null || hostname)
ask "Server FQDN [$CURRENT_FQDN]:"; read -r I
SERVER_HOSTNAME="${I:-$CURRENT_FQDN}"

GUESSED_DOMAIN="${SERVER_HOSTNAME#*.}"
ask "IPA Domain [$GUESSED_DOMAIN]:"; read -r I
IPA_DOMAIN="${I:-$GUESSED_DOMAIN}"

GUESSED_REALM="${IPA_DOMAIN^^}"
ask "Kerberos Realm [$GUESSED_REALM]:"; read -r I
IPA_REALM="${I:-$GUESSED_REALM}"

GUESSED_IP=$(ip route get 1 2>/dev/null | awk '{print $7; exit}')
ask "Server IP address [$GUESSED_IP]:"; read -r I
SERVER_IP="${I:-$GUESSED_IP}"

while true; do
    ask "IPA admin password (min 8 chars, no echo):"; read -rs ADMIN_PASSWORD; echo
    ask "Confirm admin password:"; read -rs ADMIN_PASSWORD2; echo
    [[ "$ADMIN_PASSWORD" == "$ADMIN_PASSWORD2" && ${#ADMIN_PASSWORD} -ge 8 ]] && break
    warn "Passwords do not match or are too short."
done

while true; do
    ask "Directory Manager password (min 8 chars, no echo):"; read -rs DM_PASSWORD; echo
    ask "Confirm Directory Manager password:"; read -rs DM_PASSWORD2; echo
    [[ "$DM_PASSWORD" == "$DM_PASSWORD2" && ${#DM_PASSWORD} -ge 8 ]] && break
    warn "Passwords do not match or are too short."
done

ask "Set up integrated DNS (bind) with FreeIPA? [Y/n]:"; read -r I
SETUP_DNS=true; [[ "${I,,}" == "n"* ]] && SETUP_DNS=false

DNS_FORWARDER=""
AUTO_REVERSE=true
if [[ "$SETUP_DNS" == true ]]; then
    ask "DNS forwarder IP (blank for no forwarding):"; read -r DNS_FORWARDER
    ask "Auto-create reverse DNS zone? [Y/n]:"; read -r I
    [[ "${I,,}" == "n"* ]] && AUTO_REVERSE=false
fi

ask "NTP server (blank for system default):"; read -r NTP_SERVER

ask "Install KRA (Key Recovery Authority)? [y/N]:"; read -r I
SETUP_KRA=false; [[ "${I,,}" == "y"* ]] && SETUP_KRA=true

echo
ask "Enable Ansible/AWX auto-enrollment integration? [y/N]:"; read -r I
SETUP_ANSIBLE=false; [[ "${I,,}" == "y"* ]] && SETUP_ANSIBLE=true

AWX_URL=""; AWX_TOKEN=""; AWX_INVENTORY=""
if [[ "$SETUP_ANSIBLE" == true ]]; then
    ask "AWX/Controller URL (e.g. https://awx.corp.example.com):"; read -r AWX_URL
    ask "AWX API token:"; read -r AWX_TOKEN
    ask "AWX inventory name:"; read -r AWX_INVENTORY
fi

DEFAULT_OUTDIR="$(eval echo ~"${SUDO_USER:-root}")/freeipa-output"
ask "Output directory for client scripts [$DEFAULT_OUTDIR]:"; read -r I
OUTPUT_DIR="${I:-$DEFAULT_OUTDIR}"

# ─── Confirm ──────────────────────────────────────────────────────────────────
section "Confirm"
echo
printf "  %-22s %s\n" "Hostname:"    "$SERVER_HOSTNAME"
printf "  %-22s %s\n" "Domain:"      "$IPA_DOMAIN"
printf "  %-22s %s\n" "Realm:"       "$IPA_REALM"
printf "  %-22s %s\n" "IP:"          "$SERVER_IP"
printf "  %-22s %s\n" "Integrated DNS:" "$SETUP_DNS"
[[ -n "$DNS_FORWARDER" ]] && printf "  %-22s %s\n" "DNS Forwarder:" "$DNS_FORWARDER"
printf "  %-22s %s\n" "Install KRA:" "$SETUP_KRA"
printf "  %-22s %s\n" "Ansible/AWX:" "$SETUP_ANSIBLE"
[[ "$SETUP_ANSIBLE" == true ]] && printf "  %-22s %s\n" "AWX URL:" "$AWX_URL"
printf "  %-22s %s\n" "Output dir:"  "$OUTPUT_DIR"
echo
ask "Proceed with installation? [y/N]:"; read -r CONFIRM
[[ "${CONFIRM,,}" != "y"* ]] && { echo "Aborted."; exit 0; }

# ─── Hostname ─────────────────────────────────────────────────────────────────
section "Configuring hostname"
hostnamectl set-hostname "$SERVER_HOSTNAME"
SHORT_NAME="${SERVER_HOSTNAME%%.*}"
sed -i "/\b${SERVER_HOSTNAME}\b/d" /etc/hosts
sed -i "/\b${SHORT_NAME}\b/d" /etc/hosts
echo "$SERVER_IP  $SERVER_HOSTNAME  $SHORT_NAME" >> /etc/hosts
log "Hostname: $SERVER_HOSTNAME → $SERVER_IP"

# ─── Packages ─────────────────────────────────────────────────────────────────
section "Installing packages"
case "$PKG_MGR" in
    dnf)
        dnf install -y $IPA_SERVER_PKGS
        [[ "$SETUP_ANSIBLE" == true ]] && dnf install -y $ANSIBLE_PKGS
        ;;
    pacman)
        pacman -Sy --noconfirm $IPA_SERVER_PKGS
        [[ "$SETUP_ANSIBLE" == true ]] && pacman -Sy --noconfirm $ANSIBLE_PKGS
        ;;
esac
log "Packages installed."

# ─── Firewall ────────────────────────────────────────────────────────────────
section "Configuring firewall"
if command -v firewall-cmd &>/dev/null && systemctl is-active firewalld &>/dev/null; then
    for svc in freeipa-ldap freeipa-ldaps kerberos kpasswd https ntp; do
        firewall-cmd --permanent --add-service="$svc"
    done
    [[ "$SETUP_DNS" == true ]] && firewall-cmd --permanent --add-service=dns
    firewall-cmd --reload
    log "firewalld rules applied."
elif command -v ufw &>/dev/null; then
    for rule in 80/tcp 443/tcp 389/tcp 636/tcp 88/tcp 88/udp 464/tcp 464/udp 123/udp; do
        ufw allow "$rule"
    done
    [[ "$SETUP_DNS" == true ]] && { ufw allow 53/tcp; ufw allow 53/udp; }
    log "UFW rules applied."
else
    warn "No firewall manager found — manually open: 80,443,389,636,88,464,123$([ "$SETUP_DNS" == true ] && echo ",53")"
fi

# ─── ipa-server-install ───────────────────────────────────────────────────────
section "Installing FreeIPA server (this takes several minutes)"

IPA_ARGS=(
    --realm="$IPA_REALM"
    --domain="$IPA_DOMAIN"
    --admin-password="$ADMIN_PASSWORD"
    --ds-password="$DM_PASSWORD"
    --hostname="$SERVER_HOSTNAME"
    --ip-address="$SERVER_IP"
    --mkhomedir
    --unattended
)

if [[ "$SETUP_DNS" == true ]]; then
    IPA_ARGS+=(--setup-dns)
    [[ -n "$DNS_FORWARDER" ]] && IPA_ARGS+=(--forwarder="$DNS_FORWARDER") || IPA_ARGS+=(--no-forwarders)
    [[ "$AUTO_REVERSE" == true ]] && IPA_ARGS+=(--auto-reverse) || IPA_ARGS+=(--no-reverse)
fi
[[ -n "$NTP_SERVER" ]] && IPA_ARGS+=(--ntp-server="$NTP_SERVER")
[[ "$SETUP_KRA" == true ]] && IPA_ARGS+=(--setup-kra)

ipa-server-install "${IPA_ARGS[@]}"
log "FreeIPA server installed."

# ─── Post-install ────────────────────────────────────────────────────────────
section "Post-install verification"
if echo "$ADMIN_PASSWORD" | kinit admin &>/dev/null; then
    log "Kerberos: admin ticket obtained."
    kdestroy &>/dev/null
else
    warn "Could not obtain Kerberos ticket — check connectivity."
fi

# ─── Ansible integration ─────────────────────────────────────────────────────
if [[ "$SETUP_ANSIBLE" == true ]]; then
    section "Setting up Ansible auto-enrollment"
    cp "$IPA_BASE/ansible/ansipa-install-packages.sh" /usr/local/bin/
    cp "$IPA_BASE/ansible/auto-add-baseuser.sh"       /usr/local/bin/
    chmod +x /usr/local/bin/ansipa-install-packages.sh /usr/local/bin/auto-add-baseuser.sh

    for f in ansipa-install.service ansipa-install.timer \
              baseuser-sync.path   baseuser-sync.service; do
        cp "$IPA_BASE/ansible/$f" /etc/systemd/system/
    done
    systemctl daemon-reload
    systemctl enable --now ansipa-install.timer
    systemctl enable --now baseuser-sync.path
    log "Ansible auto-enrollment services enabled."
fi

# ─── Generate output scripts ──────────────────────────────────────────────────
section "Generating client scripts → $OUTPUT_DIR"
REAL_OUTDIR="${OUTPUT_DIR/#\~/"$(eval echo ~"${SUDO_USER:-root}")"/}"
mkdir -p "$REAL_OUTDIR"

# 1. Customized freeipa-enroll.sh (server defaults baked in)
sed \
    -e "s|^IPA_DOMAIN=.*|IPA_DOMAIN=\"$IPA_DOMAIN\"|" \
    -e "s|^IPA_REALM=.*|IPA_REALM=\"$IPA_REALM\"|" \
    -e "s|^IPA_SERVER=.*|IPA_SERVER=\"$SERVER_HOSTNAME\"|" \
    "$IPA_BASE/freeipa-enroll.sh" > "$REAL_OUTDIR/freeipa-enroll.sh"
chmod +x "$REAL_OUTDIR/freeipa-enroll.sh"
log "freeipa-enroll.sh"

# 2. Customized freeipa-client.sh (same server defaults)
sed \
    -e "s|^IPA_DOMAIN=.*|IPA_DOMAIN=\"$IPA_DOMAIN\"|" \
    -e "s|^IPA_REALM=.*|IPA_REALM=\"$IPA_REALM\"|" \
    -e "s|^IPA_SERVER=.*|IPA_SERVER=\"$SERVER_HOSTNAME\"|" \
    "$IPA_BASE/freeipa-client.sh" > "$REAL_OUTDIR/freeipa-client.sh"
chmod +x "$REAL_OUTDIR/freeipa-client.sh"
log "freeipa-client.sh"

# 3. JSON answerfile (pre-filled; password intentionally left blank)
cat > "$REAL_OUTDIR/freeipa-client-answerfile.json" <<JSONEOF
{
  "domain":      "$IPA_DOMAIN",
  "realm":       "$IPA_REALM",
  "server":      "$SERVER_HOSTNAME",
  "hostname":    "",
  "principal":   "admin",
  "password":    "",
  "mkhomedir":   true,
  "sudo":        true,
  "dns_update":  true,
  "ntp_server":  "${NTP_SERVER:-}",
  "fido2":       false,
  "fido2_users": []
}
JSONEOF
log "freeipa-client-answerfile.json"

# 4. Customized auto-enroll-ansible.sh (AWX defaults baked in, still overridable)
if [[ "$SETUP_ANSIBLE" == true ]]; then
    {
        printf '#!/usr/bin/env bash\nset -e\n\n'
        printf '# Generated by freeipa-server.sh for server: %s\n' "$SERVER_HOSTNAME"
        printf '# Override via env vars or positional args:\n'
        printf '#   CONTROLLER_URL=... ./auto-enroll-ansible.sh\n'
        printf '#   ./auto-enroll-ansible.sh <url> <token> <inventory>\n\n'
        printf 'CONTROLLER_URL="${CONTROLLER_URL:-${1:-%s}}"\n' "$AWX_URL"
        printf 'API_TOKEN="${API_TOKEN:-${2:-%s}}"\n' "$AWX_TOKEN"
        printf 'INVENTORY_NAME="${INVENTORY_NAME:-${3:-%s}}"\n\n' "$AWX_INVENTORY"
        # Include body of original (skip shebang + old arg block)
        awk '/^INVENTORY_NAME=/{found=1; next} found{print}' \
            "$IPA_BASE/auto-enroll-ansible.sh"
    } > "$REAL_OUTDIR/auto-enroll-ansible.sh"
    chmod +x "$REAL_OUTDIR/auto-enroll-ansible.sh"
    log "auto-enroll-ansible.sh"
fi

# 5. README
cat > "$REAL_OUTDIR/README.txt" <<READMEEOF
FreeIPA Client Scripts — generated $(date)
==========================================
Server:  $SERVER_HOSTNAME
Domain:  $IPA_DOMAIN
Realm:   $IPA_REALM

─── Files ───────────────────────────────────────────────────

freeipa-client-answerfile.json
  JSON answerfile with server defaults pre-filled.
  Fill in "password" (and optionally "hostname") before use.

freeipa-client.sh  [recommended]
  Flexible client enrollment script. Modes:

    # JSON answerfile (fill in password first):
    sudo ./freeipa-client.sh --answerfile freeipa-client-answerfile.json

    # Fully interactive:
    sudo ./freeipa-client.sh --interactive

    # Direct CLI flags (same as freeipa-enroll.sh):
    sudo ./freeipa-client.sh --password <pw>

freeipa-enroll.sh
  Direct enrollment with server defaults baked in.
  Accepts full set of CLI flags for one-liner use:
    sudo ./freeipa-enroll.sh --password <pw>

$([ "$SETUP_ANSIBLE" == "true" ] && cat <<'AWXEOF'
auto-enroll-ansible.sh
  Registers this client in AWX/Controller inventory after enrollment.
  AWX URL and inventory are pre-configured; API token can be overridden:
    ./auto-enroll-ansible.sh
    API_TOKEN=mytoken ./auto-enroll-ansible.sh

AWXEOF
)─── Quick start on a client machine ────────────────────────

  1. Copy this directory to the client (scp, croc, etc.)
  2. Fill in "password" in freeipa-client-answerfile.json
  3. sudo ./freeipa-client.sh --answerfile freeipa-client-answerfile.json
  4. (optional) ./auto-enroll-ansible.sh

─── Manual registration after enrollment ───────────────────

  • List FIDO2 credentials: cat /etc/u2f_mappings
  • Register a FIDO2 key:   pamu2fcfg -u <user> -o pam://\$(hostname -f) >> /etc/u2f_mappings
  • Check SSSD:             sssctl domain-status $IPA_DOMAIN
  • Get Kerberos ticket:    kinit admin@$IPA_REALM

READMEEOF
log "README.txt"

# ─── Summary ─────────────────────────────────────────────────────────────────
section "Done"
cat <<EOF

  FreeIPA Server:  $SERVER_HOSTNAME
  Web UI:          https://$SERVER_HOSTNAME/ipa/ui
  Domain:          $IPA_DOMAIN
  Realm:           $IPA_REALM
  Admin:           admin@$IPA_REALM
  DNS:             $SETUP_DNS
  KRA:             $SETUP_KRA
  Ansible:         $SETUP_ANSIBLE

  Client scripts → $REAL_OUTDIR/
    freeipa-client.sh + freeipa-client-answerfile.json
    freeipa-enroll.sh
$([ "$SETUP_ANSIBLE" == "true" ] && echo "    auto-enroll-ansible.sh")
    README.txt

EOF
