#!/usr/bin/env bash
#
# create-agent-user.sh
# -----------------------------------------------------------------------------
# Provision a local `agent-user` account on a Linux host so an AI agent can be
# handed full (sudo) access to the machine over SSH.
#
# What it does, in order:
#   1. Refuses to run unless invoked as root and an SSH server is running.
#   2. Detects the distro family (Debian/Ubuntu, Fedora/RHEL, Arch/CachyOS).
#   3. Creates `agent-user` as a normal user for that distro, with a fixed UID
#      and the SAME supplementary groups as the default uid=1000 user.
#   4. Grants passwordless sudo (default) via /etc/sudoers.d.
#   5. Sets a long word!word!word passphrase (from the system dict) or, if no
#      dict is available, a 13-char unambiguous random password.
#   6. Hides the account from every installed graphical greeter (GDM/SDDM/
#      LightDM) while keeping it fully usable for VNC / X2Go / forwarded-X
#      desktop sessions.
#   7. Installs lightweight agent + hardware-inspection tooling (best effort).
#   8. Writes a system-info file into the account's home dir.
#   9. Prints a short block of credentials + instructions to paste into an
#      AI agent prompt.
#
# Run it AS ROOT, directly on the target host:
#     sudo ./create-agent-user.sh [OPTIONS]
#
# It is NOT meant to be piped from curl. Download it, read it, then run it.
# -----------------------------------------------------------------------------

set -uo pipefail   # deliberately NOT -e: many steps are best-effort and checked
                   # explicitly with die() where failure must actually stop us.

SCRIPT_VERSION="1.2.0"

# =============================================================================
# CONFIG / DEFAULTS  -- everything below is overridable via CLI flags.
# Edit these to change the baked-in defaults for your fleet.
# =============================================================================

AGENT_USER="agent-user"          # account name to create
AGENT_UID="60123"                # fixed UID. Sits in the reserved 60001-61183
                                 #   gap: above the human range (<=60000) and
                                 #   below the systemd DynamicUser floor (61184).
                                 #   Well clear of SSSD AD idmap (200000+).
                                 #   Validated as free at runtime regardless.
AGENT_GECOS="AI agent access account"
REF_UID="1000"                   # the "default user" whose groups we clone

PASSPHRASE_WORDS=6               # words in the generated passphrase
PASSPHRASE_SEP='!'               # separator between words
RANDOM_PW_LEN=13                 # length of the fallback random password
MIN_WORD_LEN=4                   # dict-word length filter (inclusive)
MAX_WORD_LEN=8

SUDO_MODE="nopasswd"             # "nopasswd" | "password"
DO_INSTALL=1                     # 1 = install tooling, 0 = skip (--no-install)
DICT_PROMPT=1                    # 1 = offer to install a dict if missing
SSHD_CHECK=1                     # 1 = require a running sshd before proceeding
ROTATE=0                         # 1 = rotate creds/groups if user exists
NONINTERACTIVE=0                 # 1 = never prompt; take safe fallbacks
PW_MODE="auto"                   # "auto" | "passphrase" | "random"
UNIQUE_KEYPAIR=0                 # 1 = tell the agent to mint a per-host keypair
                                 #   (--unique-keypair); default just says "use key
                                 #   auth", since most agents already have one.

AGENT_TARGET_HOST=""             # override reachable address in the output
SSH_PORT=""                      # override advertised SSH port (else detected)
INFO_FILE_NAME="AGENT_INFO.md"   # written into the account's home dir
PROMPT_FILE_NAME="AGENT_PROMPT.txt"  # copy of the final agent-prompt block,
                                 #   also written into the account's home dir

# Dictionaries we look for, in order of preference:
DICT_CANDIDATES=(
    /usr/share/dict/words
    /usr/share/dict/american-english
    /usr/share/dict/british-english
    /usr/dict/words
)

# =============================================================================
# Logging helpers
# =============================================================================
if [ -t 2 ]; then
    C_RED=$'\033[31m'; C_YLW=$'\033[33m'; C_GRN=$'\033[32m'
    C_BLU=$'\033[34m'; C_RST=$'\033[0m'
else
    C_RED=""; C_YLW=""; C_GRN=""; C_BLU=""; C_RST=""
fi
info() { printf '%s[*]%s %s\n'  "$C_BLU" "$C_RST" "$*" >&2; }
ok()   { printf '%s[+]%s %s\n'  "$C_GRN" "$C_RST" "$*" >&2; }
warn() { printf '%s[!]%s %s\n'  "$C_YLW" "$C_RST" "$*" >&2; }
die()  { printf '%s[x]%s %s\n'  "$C_RED" "$C_RST" "$*" >&2; exit 1; }

usage() {
    cat >&2 <<EOF
create-agent-user.sh v$SCRIPT_VERSION

Usage: sudo ./create-agent-user.sh [OPTIONS]

  --rotate                 Rotate creds/groups if $AGENT_USER already exists
                           (default: refuse if the account exists).
  --sudo-password          Require a password for sudo (default: NOPASSWD).
  --no-install             Skip all package installation.
  --agent-target-host H    Address to advertise in the output block.
  --uid N                  Override the fixed UID (default: $AGENT_UID).
  --username NAME          Override the account name (default: $AGENT_USER).
  --ssh-port N             Advertise this SSH port (default: auto-detect).
  --passphrase             Force a dictionary passphrase.
  --random-password        Force a random password.
  --words N                Passphrase word count (default: $PASSPHRASE_WORDS).
  --no-sshd-check          Do not require a running SSH server.
  --non-interactive        Never prompt; take safe fallbacks.
  --unique-keypair         Agent prompt tells the AI to mint a NEW per-host SSH
                           keypair for hardening (default: tells it to just use
                           whatever key auth it already has).
  -h, --help               Show this help.
EOF
    exit "${1:-0}"
}

# =============================================================================
# Argument parsing
# =============================================================================
while [ $# -gt 0 ]; do
    case "$1" in
        --rotate)            ROTATE=1 ;;
        --sudo-password)     SUDO_MODE="password" ;;
        --no-install)        DO_INSTALL=0 ;;
        --agent-target-host) AGENT_TARGET_HOST="${2:?}"; shift ;;
        --uid)               AGENT_UID="${2:?}"; shift ;;
        --username)          AGENT_USER="${2:?}"; shift ;;
        --ssh-port)          SSH_PORT="${2:?}"; shift ;;
        --passphrase)        PW_MODE="passphrase" ;;
        --random-password)   PW_MODE="random" ;;
        --words)             PASSPHRASE_WORDS="${2:?}"; shift ;;
        --no-sshd-check)     SSHD_CHECK=0 ;;
        --non-interactive)   NONINTERACTIVE=1 ;;
        --unique-keypair)    UNIQUE_KEYPAIR=1 ;;
        -h|--help)           usage 0 ;;
        *) warn "Unknown option: $1"; usage 1 ;;
    esac
    shift
done

# No controlling terminal on stdin -> we cannot prompt.
[ -t 0 ] || NONINTERACTIVE=1

ask_yes_no() {
    # $1 = prompt, $2 = default (y/n). Honors --non-interactive.
    local prompt="$1" def="${2:-n}" reply
    if [ "$NONINTERACTIVE" -eq 1 ]; then
        [ "$def" = "y" ] && return 0 || return 1
    fi
    local hint="[y/N]"; [ "$def" = "y" ] && hint="[Y/n]"
    read -r -p "$prompt $hint " reply || reply=""
    reply="${reply:-$def}"
    case "$reply" in [Yy]*) return 0 ;; *) return 1 ;; esac
}

# =============================================================================
# Preconditions
# =============================================================================
[ "$(id -u)" -eq 0 ] || die "Must run as root (use sudo)."

for bin in getent useradd usermod chpasswd visudo shuf install; do
    command -v "$bin" >/dev/null 2>&1 || die "Required tool missing: $bin"
done

ssh_running() {
    pgrep -x sshd >/dev/null 2>&1 && return 0
    systemctl is-active --quiet sshd 2>/dev/null && return 0
    systemctl is-active --quiet ssh   2>/dev/null && return 0
    command -v ss >/dev/null 2>&1 && ss -tlnH 2>/dev/null | grep -qE ':22\b' && return 0
    return 1
}
if [ "$SSHD_CHECK" -eq 1 ]; then
    ssh_running || die "No running SSH server detected. Start sshd, or pass --no-sshd-check."
    ok "SSH server detected."
fi

# =============================================================================
# Distro detection -> PKG_FAMILY + package name arrays
# =============================================================================
PKG_FAMILY=""
DNF="dnf"
detect_distro() {
    local id="" like=""
    if [ -r /etc/os-release ]; then
        # Source in a SUBSHELL so os-release vars (VERSION, ID, NAME, ...) never
        # leak into and clobber this script's own globals.
        # shellcheck disable=SC1091
        id="$( . /etc/os-release; printf '%s' "${ID:-}" )"
        like="$( . /etc/os-release; printf '%s' "${ID_LIKE:-}" )"
    fi
    case " $id $like " in
        *" debian "*|*" ubuntu "*) PKG_FAMILY="debian" ;;
        *" fedora "*|*" rhel "*|*" centos "*) PKG_FAMILY="rhel" ;;
        *" arch "*) PKG_FAMILY="arch" ;;
        *)
            # Fall back on whichever package manager exists.
            if   command -v apt-get >/dev/null; then PKG_FAMILY="debian"
            elif command -v dnf     >/dev/null; then PKG_FAMILY="rhel"
            elif command -v yum     >/dev/null; then PKG_FAMILY="rhel"; DNF="yum"
            elif command -v pacman  >/dev/null; then PKG_FAMILY="arch"
            else die "Unsupported distro: could not find apt/dnf/pacman."
            fi ;;
    esac
    command -v dnf >/dev/null 2>&1 || DNF="yum"

    case "$PKG_FAMILY" in
        debian)
            SUDO_GROUP="sudo"
            PKGS_AGENT=(git jq curl rsync tmux ca-certificates python3 python3-venv python3-pip pipx)
            PKGS_HW=(lshw lsscsi hdparm inxi pciutils usbutils dmidecode smartmontools)
            PKG_DICT="wamerican" ;;
        rhel)
            SUDO_GROUP="wheel"
            PKGS_AGENT=(git jq curl rsync tmux ca-certificates python3 python3-pip pipx)
            PKGS_HW=(lshw lsscsi hdparm inxi pciutils usbutils dmidecode smartmontools)
            PKG_DICT="words" ;;
        arch)
            SUDO_GROUP="wheel"
            PKGS_AGENT=(git jq curl rsync tmux ca-certificates python python-pip python-pipx)
            PKGS_HW=(lshw lsscsi hdparm inxi pciutils usbutils dmidecode smartmontools)
            PKG_DICT="words" ;;
    esac
}
detect_distro
ok "Distro family: $PKG_FAMILY (sudo group: $SUDO_GROUP)"

# =============================================================================
# Package installation helpers (best-effort)
# =============================================================================
PKG_REFRESHED=0
pkg_refresh() {
    [ "$PKG_REFRESHED" -eq 1 ] && return 0
    PKG_REFRESHED=1
    case "$PKG_FAMILY" in
        debian) DEBIAN_FRONTEND=noninteractive apt-get update -qq >/dev/null 2>&1 || true ;;
        arch)   pacman -Sy --noconfirm >/dev/null 2>&1 || true ;;
        rhel)   : ;;  # dnf resolves metadata on demand
    esac
}
pkg_install_one() {
    local p="$1"
    case "$PKG_FAMILY" in
        debian) DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$p" >/dev/null 2>&1 ;;
        rhel)   $DNF install -y "$p" >/dev/null 2>&1 ;;
        arch)   pacman -S --noconfirm --needed "$p" >/dev/null 2>&1 ;;
    esac
}
INSTALLED_OK=()
INSTALLED_FAIL=()
pkg_install_list() {
    # Best-effort: never aborts the script. Records per-package outcome.
    pkg_refresh
    local p
    for p in "$@"; do
        if pkg_install_one "$p"; then
            INSTALLED_OK+=("$p")
        else
            INSTALLED_FAIL+=("$p")
        fi
    done
}

# =============================================================================
# Reference user (uid=1000): clone its supplementary groups + login shell
# =============================================================================
REF_USER="$(getent passwd "$REF_UID" | cut -d: -f1)"
SUPP_GROUPS=()
REF_SHELL="/bin/bash"
if [ -n "$REF_USER" ]; then
    info "Cloning groups from reference user: $REF_USER (uid $REF_UID)"
    ref_primary="$(id -gn "$REF_USER" 2>/dev/null || echo)"
    ref_shell="$(getent passwd "$REF_USER" | cut -d: -f7)"
    [ -n "$ref_shell" ] && [ -x "$ref_shell" ] && REF_SHELL="$ref_shell"
    for g in $(id -nG "$REF_USER" 2>/dev/null); do
        [ "$g" = "$ref_primary" ] && continue
        [ "$g" = "$REF_USER" ] && continue
        SUPP_GROUPS+=("$g")
    done
else
    warn "No uid=$REF_UID user found; falling back to '$SUDO_GROUP' group only."
    getent group "$SUDO_GROUP" >/dev/null && SUPP_GROUPS+=("$SUDO_GROUP")
fi
SUPP_CSV="$(IFS=,; echo "${SUPP_GROUPS[*]:-}")"
info "Supplementary groups: ${SUPP_CSV:-<none>}"
info "Login shell: $REF_SHELL"

# =============================================================================
# Password generation
# =============================================================================
find_dict() {
    local d
    for d in "${DICT_CANDIDATES[@]}"; do
        if [ -r "$d" ] && [ "$(grep -cE "^[a-z]{${MIN_WORD_LEN},${MAX_WORD_LEN}}\$" "$d" 2>/dev/null)" -ge 100 ]; then
            echo "$d"; return 0
        fi
    done
    return 1
}
gen_passphrase() {
    local dict="$1" picked out="" w
    mapfile -t picked < <(grep -E "^[a-z]{${MIN_WORD_LEN},${MAX_WORD_LEN}}\$" "$dict" | shuf -n "$PASSPHRASE_WORDS")
    [ "${#picked[@]}" -lt "$PASSPHRASE_WORDS" ] && return 1
    for w in "${picked[@]}"; do
        out+="${w^}${PASSPHRASE_SEP}"        # Capitalize each word
    done
    out+="$(( (RANDOM % 90) + 10 ))"          # trailing 2-digit for complexity
    printf '%s' "$out"
}
gen_random_pw() {
    # Unambiguous set: no I l O 0 1 o.
    local U='ABCDEFGHJKMNPQRSTUVWXYZ' L='abcdefghijkmnpqrstuvwxyz' \
          D='23456789' S='!@#%^*-_=+' all pw="" i
    all="$U$L$D$S"
    pw+="${U:RANDOM%${#U}:1}"
    pw+="${L:RANDOM%${#L}:1}"
    pw+="${D:RANDOM%${#D}:1}"
    pw+="${S:RANDOM%${#S}:1}"
    for ((i=${#pw}; i<RANDOM_PW_LEN; i++)); do pw+="${all:RANDOM%${#all}:1}"; done
    printf '%s' "$pw" | fold -w1 | shuf | tr -d '\n'
}

AGENT_PASSWORD=""
PW_KIND=""
make_password() {
    local dict=""
    case "$PW_MODE" in
        random)
            AGENT_PASSWORD="$(gen_random_pw)"; PW_KIND="random-${RANDOM_PW_LEN}ch" ;;
        passphrase)
            dict="$(find_dict)" || die "--passphrase requested but no usable dictionary found."
            AGENT_PASSWORD="$(gen_passphrase "$dict")" || die "Passphrase generation failed."
            PW_KIND="passphrase-${PASSPHRASE_WORDS}w" ;;
        auto)
            if dict="$(find_dict)"; then
                AGENT_PASSWORD="$(gen_passphrase "$dict")" && PW_KIND="passphrase-${PASSPHRASE_WORDS}w"
            fi
            if [ -z "$AGENT_PASSWORD" ]; then
                # No dict. Offer to install one, else random.
                if [ "$DO_INSTALL" -eq 1 ] && [ "$DICT_PROMPT" -eq 1 ] \
                   && ask_yes_no "No word list found. Install '$PKG_DICT' for a passphrase?" y; then
                    pkg_install_list "$PKG_DICT"
                    if dict="$(find_dict)"; then
                        AGENT_PASSWORD="$(gen_passphrase "$dict")" && PW_KIND="passphrase-${PASSPHRASE_WORDS}w"
                    fi
                fi
            fi
            if [ -z "$AGENT_PASSWORD" ]; then
                warn "Using random password (no dictionary available)."
                AGENT_PASSWORD="$(gen_random_pw)"; PW_KIND="random-${RANDOM_PW_LEN}ch"
            fi ;;
    esac
    [ -n "$AGENT_PASSWORD" ] || die "Failed to generate a password."
}

# =============================================================================
# Create (or rotate) the account
# =============================================================================
USER_EXISTS=0
getent passwd "$AGENT_USER" >/dev/null && USER_EXISTS=1

if [ "$USER_EXISTS" -eq 1 ] && [ "$ROTATE" -eq 0 ]; then
    die "Account '$AGENT_USER' already exists. Re-run with --rotate to rotate its credentials."
fi

if [ "$USER_EXISTS" -eq 0 ]; then
    # UID collision check (real users, winbind mappings, anything).
    uid_owner="$(getent passwd "$AGENT_UID" | cut -d: -f1)"
    if [ -n "$uid_owner" ]; then
        die "UID $AGENT_UID already in use by '$uid_owner'. Re-run with --uid <N>."
    fi
    info "Creating account '$AGENT_USER' (uid $AGENT_UID)"
    useradd --create-home --uid "$AGENT_UID" --shell "$REF_SHELL" \
            --comment "$AGENT_GECOS" "$AGENT_USER" \
        || die "useradd failed."
    ok "Account created."
else
    existing_uid="$(id -u "$AGENT_USER")"
    warn "Rotating existing account '$AGENT_USER' (uid $existing_uid); UID left unchanged."
    AGENT_UID="$existing_uid"
fi

# Apply supplementary groups (idempotent).
if [ -n "$SUPP_CSV" ]; then
    usermod -aG "$SUPP_CSV" "$AGENT_USER" || warn "usermod -aG partially failed."
fi

# Set the password.
make_password
printf '%s:%s\n' "$AGENT_USER" "$AGENT_PASSWORD" | chpasswd \
    || die "Failed to set password."
ok "Password set ($PW_KIND)."

AGENT_HOME="$(getent passwd "$AGENT_USER" | cut -d: -f6)"
[ -n "$AGENT_HOME" ] || die "Could not determine home directory."

# Pre-create ~/.ssh so the agent can drop in a key cleanly.
install -d -m 700 -o "$AGENT_USER" -g "$AGENT_USER" "$AGENT_HOME/.ssh"

# =============================================================================
# sudo configuration
# =============================================================================
SUDOERS_FILE="/etc/sudoers.d/$AGENT_USER"
if [ "$SUDO_MODE" = "nopasswd" ]; then
    SUDO_RULE="$AGENT_USER ALL=(ALL:ALL) NOPASSWD:ALL"
else
    SUDO_RULE="$AGENT_USER ALL=(ALL:ALL) ALL"
fi
printf '# Managed by create-agent-user.sh\n%s\n' "$SUDO_RULE" > "$SUDOERS_FILE"
chmod 0440 "$SUDOERS_FILE"
if ! visudo -cf "$SUDOERS_FILE" >/dev/null 2>&1; then
    rm -f "$SUDOERS_FILE"
    die "Generated sudoers file failed validation; removed it."
fi
ok "sudo configured ($SUDO_MODE)."

# =============================================================================
# Hide from graphical greeters (GDM / SDDM / LightDM) via AccountsService
# plus greeter-specific excludes for whichever are installed.
# =============================================================================
greeter_present() {
    case "$1" in
        gdm)     command -v gdm >/dev/null || command -v gdm3 >/dev/null || [ -d /etc/gdm ] || [ -d /etc/gdm3 ] ;;
        sddm)    command -v sddm >/dev/null || [ -f /etc/sddm.conf ] || [ -d /etc/sddm.conf.d ] ;;
        lightdm) command -v lightdm >/dev/null || [ -d /etc/lightdm ] ;;
    esac
}

# AccountsService: honored by GDM and, in modern setups, by SDDM/LightDM too.
install -d -m 775 /var/lib/AccountsService/users 2>/dev/null || true
cat > "/var/lib/AccountsService/users/$AGENT_USER" <<EOF
[User]
SystemAccount=true
EOF

# SDDM drop-in.
if greeter_present sddm; then
    install -d -m 755 /etc/sddm.conf.d
    cat > "/etc/sddm.conf.d/10-hide-$AGENT_USER.conf" <<EOF
[Users]
HideUsers=$AGENT_USER
EOF
    info "SDDM hide rule written."
fi

# LightDM users.conf.
if greeter_present lightdm; then
    LDM=/etc/lightdm/users.conf
    if [ -f "$LDM" ] && grep -qE '^\s*hidden-users=' "$LDM"; then
        grep -qE "hidden-users=.*(^|[ =])$AGENT_USER($|[ ])" "$LDM" \
            || sed -i "s/^\(\s*hidden-users=.*\)/\1 $AGENT_USER/" "$LDM"
    else
        install -d -m 755 /etc/lightdm
        printf '[UserList]\nhidden-users=nobody nobody4 noaccess %s\n' "$AGENT_USER" >> "$LDM"
    fi
    info "LightDM hide rule written."
fi
ok "Account hidden from installed greeters."

# =============================================================================
# Install tooling (best-effort)
# =============================================================================
if [ "$DO_INSTALL" -eq 1 ]; then
    info "Installing agent tooling (best-effort): ${PKGS_AGENT[*]}"
    info "Installing hardware-inspection tooling (best-effort): ${PKGS_HW[*]}"
    pkg_install_list "${PKGS_AGENT[@]}" "${PKGS_HW[@]}"
    ok "Installed (${#INSTALLED_OK[@]}): ${INSTALLED_OK[*]:-<none>}"
    [ "${#INSTALLED_FAIL[@]}" -gt 0 ] && warn "Not installed (${#INSTALLED_FAIL[@]}): ${INSTALLED_FAIL[*]}"
else
    info "Skipping package installation (--no-install)."
fi

# =============================================================================
# Determine reachable host + SSH port for the output block
#
# Preference order: FQDN, then a static IP, then the short hostname (works via
# mDNS/local resolution on many LANs), then a plain DHCP-assigned IP as last
# resort. ADDR_KIND records which one was picked so the output can flag the
# weaker choices (short hostname / DHCP IP) instead of presenting them as if
# they were as durable as an FQDN or static IP.
# =============================================================================
PRIMARY_IFACE="$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')"

is_static_ip() {
    # Best-effort: returns 0=static, 1=dhcp, 2=unknown. Checked in order of
    # how common each network stack is; the first one that gives a definite
    # answer wins.
    local iface="$1" conn method ifidx
    [ -n "$iface" ] || return 2

    if command -v nmcli >/dev/null 2>&1; then
        conn="$(nmcli -t -f GENERAL.CONNECTION device show "$iface" 2>/dev/null | cut -d: -f2-)"
        if [ -n "$conn" ] && [ "$conn" != "--" ]; then
            method="$(nmcli -t -f ipv4.method connection show "$conn" 2>/dev/null | cut -d: -f2-)"
            case "$method" in
                manual) return 0 ;;
                auto)   return 1 ;;
            esac
        fi
    fi

    ifidx="$(cat "/sys/class/net/$iface/ifindex" 2>/dev/null)"
    [ -n "$ifidx" ] && [ -f "/run/systemd/netif/leases/$ifidx" ] && return 1

    ls /var/lib/dhcp/dhclient*"$iface"*.lease* >/dev/null 2>&1 && return 1
    ls /var/lib/dhcpcd*/*.lease >/dev/null 2>&1 && return 1

    if [ -f /etc/network/interfaces ] \
       && grep -qE "iface[[:space:]]+${iface}[[:space:]]+inet[[:space:]]+static" /etc/network/interfaces 2>/dev/null; then
        return 0
    fi

    return 2
}

# NOTE: detect_host sets OUT_HOST/ADDR_KIND directly (globals) rather than
# echoing a return value, because it must run in THIS shell, not a
# command-substitution subshell — otherwise ADDR_KIND would be lost the
# moment the function returns.
ADDR_KIND=""   # override | fqdn | static-ip | short-hostname | dhcp-ip
OUT_HOST=""
detect_host() {
    if [ -n "$AGENT_TARGET_HOST" ]; then
        ADDR_KIND="override"; OUT_HOST="$AGENT_TARGET_HOST"; return
    fi

    local ip fqdn short
    ip="$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')"
    [ -z "$ip" ] && ip="$(hostname -I 2>/dev/null | awk '{print $1}')"

    fqdn="$(hostname -A 2>/dev/null | awk '{print $1}')"
    if [ -n "$fqdn" ] && [[ "$fqdn" == *.* ]]; then
        ADDR_KIND="fqdn"; OUT_HOST="$fqdn"; return
    fi

    if [ -n "$ip" ]; then
        is_static_ip "$PRIMARY_IFACE"
        if [ "$?" -eq 0 ]; then
            ADDR_KIND="static-ip"; OUT_HOST="$ip"; return
        fi
    fi

    short="$(hostname 2>/dev/null)"
    if [ -n "$short" ] && [ "$short" != "localhost" ]; then
        ADDR_KIND="short-hostname"; OUT_HOST="$short"; return
    fi

    ADDR_KIND="dhcp-ip"; OUT_HOST="$ip"
}
detect_port() {
    [ -n "$SSH_PORT" ] && { echo "$SSH_PORT"; return; }
    local p
    p="$(sshd -T 2>/dev/null | awk '/^port /{print $2; exit}')"
    [ -z "$p" ] && p=22
    echo "$p"
}
detect_host
OUT_PORT="$(detect_port)"
HOSTNAME_FQDN="$(hostname -f 2>/dev/null || hostname)"

ADDR_WARNING=""
case "$ADDR_KIND" in
    short-hostname)
        ADDR_WARNING="No FQDN or static IP was found; advertising the short hostname '$OUT_HOST'. This may only resolve on the local network/mDNS domain — confirm it resolves before relying on it, or re-run with --agent-target-host." ;;
    dhcp-ip)
        ADDR_WARNING="No FQDN was found and the IP looks DHCP-assigned; advertising $OUT_HOST as-is may go stale if the lease changes. Prefer an FQDN if one exists, or re-run with --agent-target-host." ;;
esac

# Representative package-manager invocation + human-readable distro name, so
# the agent knows immediately how to install things without waiting to read
# AGENT_INFO.md.
case "$PKG_FAMILY" in
    debian) PKG_MGR_CMD="apt-get install -y <pkg>" ;;
    rhel)   PKG_MGR_CMD="$DNF install -y <pkg>" ;;
    arch)   PKG_MGR_CMD="pacman -S --noconfirm <pkg>" ;;
esac
DISTRO_PRETTY="$( [ -r /etc/os-release ] && . /etc/os-release; printf '%s' "${PRETTY_NAME:-$PKG_FAMILY}" )"

# =============================================================================
# x86-64 microarchitecture level (nice to know; explains SIGILL-on-old-CPU)
# =============================================================================
x86_64_level() {
    [ "$(uname -m)" = "x86_64" ] || { echo "n/a ($(uname -m))"; return; }
    local f lvl=1
    f=" $(grep -m1 '^flags' /proc/cpuinfo | cut -d: -f2) "
    has() { [[ $f == *" $1 "* ]]; }
    if has cx16 && has lahf_lm && has popcnt && has sse4_1 && has sse4_2 && has ssse3; then lvl=2; fi
    if [ "$lvl" -ge 2 ] && has avx && has avx2 && has bmi1 && has bmi2 && has f16c && has fma && has movbe && has abm; then lvl=3; fi
    if [ "$lvl" -ge 3 ] && has avx512f && has avx512bw && has avx512cd && has avx512dq && has avx512vl; then lvl=4; fi
    echo "x86-64-v$lvl"
}

# =============================================================================
# Write the system-info file into the account's home directory
# =============================================================================
INFO_PATH="$AGENT_HOME/$INFO_FILE_NAME"
try() { "$@" 2>/dev/null || true; }
{
    echo "# Agent host reference — $HOSTNAME_FQDN"
    echo
    echo "> Written by create-agent-user.sh v$SCRIPT_VERSION. This file is here for the"
    echo "> agent to read after logging in. It contains no credentials."
    echo
    echo "## Identity"
    echo '```'
    echo "hostname : $HOSTNAME_FQDN"
    echo "account  : $AGENT_USER (uid $AGENT_UID)"
    echo "sudo     : $SUDO_MODE"
    echo "shell    : $REF_SHELL"
    echo "groups   : ${SUPP_CSV:-<none>}"
    echo '```'
    echo
    echo "## OS & kernel"
    echo '```'
    try grep -E '^(PRETTY_NAME|VERSION|ID)=' /etc/os-release
    echo "kernel   : $(uname -r)"
    echo "arch     : $(uname -m)"
    echo '```'
    echo
    echo "## CPU"
    echo '```'
    try grep -m1 'model name' /proc/cpuinfo
    echo "cores    : $(nproc 2>/dev/null || echo '?')"
    echo "x86 level: $(x86_64_level)"
    echo '```'
    echo
    echo "## Memory"
    echo '```'
    try free -h
    echo '```'
    echo
    echo "## Block devices"
    echo '```'
    if command -v lsblk >/dev/null; then try lsblk -o NAME,SIZE,TYPE,MODEL,MOUNTPOINT; fi
    if command -v lsscsi >/dev/null; then echo; try lsscsi; fi
    echo '```'
    echo
    echo "## GPU / display"
    echo '```'
    if command -v lspci >/dev/null; then try lspci | grep -iE 'vga|3d|display'; fi
    echo '```'
    echo
    echo "## Network"
    echo '```'
    if command -v ip >/dev/null; then try ip -brief addr; else try hostname -I; fi
    echo '```'
    echo
    echo "## Installed agent tooling"
    echo '```'
    for t in git python3 python pip pip3 pipx jq curl rsync tmux; do
        if command -v "$t" >/dev/null 2>&1; then
            printf '%-8s %s\n' "$t" "$("$t" --version 2>&1 | head -n1)"
        fi
    done
    echo '```'
    echo
    echo "## Notes"
    echo "- Provisioned for AI agent access. Add host-specific notes below."
    echo
} > "$INFO_PATH"
chown "$AGENT_USER:$AGENT_USER" "$INFO_PATH"
chmod 640 "$INFO_PATH"
ok "System info written to $INFO_PATH"

# =============================================================================
# Final output: short block to paste into an AI agent prompt
# =============================================================================
SUDO_NOTE="nopasswd"; [ "$SUDO_MODE" = "password" ] && SUDO_NOTE="password"
TODAY="$(date +%Y-%m-%d 2>/dev/null || echo unknown)"

PROMPT_BLOCK="$(cat <<EOF

$(printf '%s' "$C_GRN")============================================================
 DONE — copy everything between the lines into your AI agent
============================================================$(printf '%s' "$C_RST")

You have been granted an account with full sudo on a Linux host. Here are the
credentials:

     host:        $OUT_HOST
     port:        $OUT_PORT
     username:    $AGENT_USER
     uid:         $AGENT_UID
     password:    "$AGENT_PASSWORD"
     sudo:        $SUDO_NOTE
     hostname:    $HOSTNAME_FQDN
     distro:      $DISTRO_PRETTY ($PKG_FAMILY; install with: $PKG_MGR_CMD)
     provisioned: $TODAY
$( [ -n "$ADDR_WARNING" ] && printf '\nNOTE: %s\n' "$ADDR_WARNING" )
Do this:

1. Store the credential where this machine keeps its secrets, and record a
   pointer to it somewhere every future session will see (e.g. a global
   CLAUDE.md / AGENTS.md), so this access is discoverable later — not just for
   this one session. Never inline the password into a doc that gets loaded into
   context or committed to a repo.
     - PREFERRED, if this machine uses Bitwarden Secrets Manager (\`bws\` on
       PATH, token in the keyring): store it as a secret, e.g.
         BWS_ACCESS_TOKEN="\$(secret-tool lookup service bws account default)" \\
           bws secret create ${AGENT_USER}-${HOSTNAME_FQDN%%.*} "<password>" <project-id> \\
           --note "host=$OUT_HOST user=$AGENT_USER uid=$AGENT_UID sudo=$SUDO_NOTE"
       and record the returned secret id in your pointer. Fetch later with
       \`bws secret get <id> | jq -r .value\`.
     - FALLBACK, otherwise: write it to ~/.config/agent-hosts/${HOSTNAME_FQDN}.yaml
       and \`chmod 600\` it (install a YAML parser — yq, or python3 + PyYAML —
       if none is present). Judge which fits this machine.

2. SSH in and read ~/$INFO_FILE_NAME on the host — it has the machine's
   OS / CPU / memory / disk / GPU inventory and notes. Keep the useful parts
   alongside your stored credential / pointer. (This exact prompt is also
   saved at ~/$PROMPT_FILE_NAME on the host, in case it's needed again later.)

3. Verify access end-to-end and report results:
     - SSH login succeeds
     - \`sudo -n true\` succeeds (sudo works)
     - an SFTP/SCP round-trip of a small test file succeeds

4. Harden:$( [ "$UNIQUE_KEYPAIR" -eq 1 ] && cat <<KEYEOF
 generate a NEW per-host SSH keypair (do not reuse an
   existing default key), e.g.
     ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_${AGENT_USER}-${HOSTNAME_FQDN%%.*} -N ""
   append its public key to ${AGENT_HOME}/.ssh/authorized_keys, and confirm
   key-based login works. Tools like ssh-copy-id default to "the newest key in
   ~/.ssh" when no -i is given, so back-date this new keypair's mtime to just
   before your existing default key's mtime (e.g. \`touch -d '<time>'
   ~/.ssh/id_ed25519_${AGENT_USER}-${HOSTNAME_FQDN%%.*}{,.pub}\`) so it never
   becomes an implicit default. Then prefer this key over the password.
KEYEOF
)$( [ "$UNIQUE_KEYPAIR" -eq 0 ] && cat <<KEYEOF
 append your existing SSH public key (e.g.
   ~/.ssh/id_ed25519.pub — generate one first if you don't have one) to
   ${AGENT_HOME}/.ssh/authorized_keys, confirm key-based login works, and
   prefer the key over the password from then on.
KEYEOF
)

============================================================

EOF
)"

printf '%s\n' "$PROMPT_BLOCK"

# Also drop a plain-text (no ANSI) copy next to AGENT_INFO.md, so the prompt
# survives a lost terminal/scrollback — it's not otherwise persisted anywhere.
# Contains the password: keep it as locked-down as the account's own auth.
PROMPT_PATH="$AGENT_HOME/$PROMPT_FILE_NAME"
printf '%s\n' "$PROMPT_BLOCK" | sed -E 's/\x1b\[[0-9;]*m//g' > "$PROMPT_PATH"
chown "$AGENT_USER:$AGENT_USER" "$PROMPT_PATH"
chmod 600 "$PROMPT_PATH"
ok "Agent prompt saved to $PROMPT_PATH"

ok "Provisioning complete."
