#!/bin/bash
# Corehost Pulse - private probe installer (systemd).
#
# Usage (from the Probes page):
#   curl -fsSL https://pulse.corehost.io/install-probe.sh | \
#     sudo PULSE_PROBE_TOKEN=plsp_xxx bash
#
# (The pre-rename URL /install-runner.sh serves this same script.)
#
# The install is only finished when the probe has checked in, so the last step
# waits for that and, if it does not happen, prints the probe's own reason
# (missing token, rejected token, unreachable API) and exits non-zero instead
# of claiming success.
#
# Re-running upgrades the binary in place. If PULSE_PROBE_TOKEN is provided and
# differs from the configured one, credentials are replaced as well. A TTY run
# asks what to do (upgrade / replace credentials / abort) with a 15 second
# timeout that defaults to upgrade.
#
# Naming and back-compat: new installs create the service `pulse-probe`
# (/usr/local/bin/pulse-probe, /etc/pulse-probe.env). A box that already runs
# the pre-rename `pulse-runner` service keeps that name: the installer detects
# it and upgrades binary/credentials/unit under the old paths, so nothing
# breaks and no re-enrolment is needed. The closing block says which name was
# used.
#
# ICMP (ping) monitors: the service unit carries AmbientCapabilities=
# CAP_NET_RAW so the unprivileged service user may open ICMP sockets;
# re-running the installer adds those lines to a pre-existing unit. For hosts
# where the capability is not enough (some containers and hardened kernels),
# PULSE_PING_GROUP_RANGE=1 additionally opens net.ipv4.ping_group_range to
# all gids via a sysctl drop-in, letting ping use unprivileged datagram ICMP.
#
# Env:
#   PULSE_PROBE_TOKEN    required on first install (from the panel, shown once)
#   PULSE_API_URL        optional, defaults to the Pulse API
#   PULSE_DOWNLOAD_BASE  optional, defaults to the Pulse CDN
#   PULSE_PING_GROUP_RANGE=1  opt-in: write /etc/sysctl.d/99-<service>-ping.conf
#                        setting net.ipv4.ping_group_range = 0 2147483647
#                        (unprivileged ICMP fallback, see above)
#   PULSE_DRY_RUN=1      print the plan and exit without touching anything
#
# (PULSE_INSTALL_ROOT / PULSE_SKIP_SYSTEMCTL exist for test harnesses only.)

set -euo pipefail

# Compatibility: older copy-boxes used PULSE_TOKEN; accept it as a fallback.
PULSE_PROBE_TOKEN="${PULSE_PROBE_TOKEN:-${PULSE_TOKEN:-}}"

API_URL_DEFAULT="https://api.pulse.corehost.io"
DOWNLOAD_BASE="${PULSE_DOWNLOAD_BASE:-https://pulse.corehost.io}"
PANEL_URL="https://pulse.corehost.io/app/probes"
ROOT="${PULSE_INSTALL_ROOT:-}"
DRY_RUN="${PULSE_DRY_RUN:-}"

# Service-name detection: any trace of a pre-rename install keeps the old name
# family; everything else installs as pulse-probe. Paths follow the name.
SERVICE_NAME="pulse-probe"
if [ -f "${ROOT}/etc/systemd/system/pulse-runner.service" ] \
    || [ -e "${ROOT}/usr/local/bin/pulse-runner" ] \
    || [ -f "${ROOT}/etc/pulse-runner.env" ]; then
    SERVICE_NAME="pulse-runner"
fi

BIN_PATH="${ROOT}/usr/local/bin/${SERVICE_NAME}"
ENV_PATH="${ROOT}/etc/${SERVICE_NAME}.env"
UNIT_PATH="${ROOT}/etc/systemd/system/${SERVICE_NAME}.service"

# ---------------------------------------------------------------------------
# Presentation: animated steps on a color TTY, plain "ok" lines otherwise.
# ---------------------------------------------------------------------------
FANCY=0
INTERACTIVE=0
[ -t 0 ] && INTERACTIVE=1
if [ -t 1 ]; then
    COLORS="$(tput colors 2>/dev/null || echo 0)"
    if [ "${COLORS:-0}" -ge 8 ]; then
        FANCY=1
    fi
fi

if [ "$FANCY" = 1 ]; then
    if [ "${COLORS:-0}" -ge 256 ]; then
        C_OK=$'\033[38;5;118m'      # lime green
        C_WARN=$'\033[38;5;220m'
        C_ERR=$'\033[38;5;196m'
        C_DIM=$'\033[38;5;245m'
    else
        C_OK=$'\033[32m'
        C_WARN=$'\033[33m'
        C_ERR=$'\033[31m'
        C_DIM=$'\033[2m'
    fi
    C_BOLD=$'\033[1m'
    C_RESET=$'\033[0m'
else
    C_OK=""; C_WARN=""; C_ERR=""; C_DIM=""; C_BOLD=""; C_RESET=""
fi

SPIN_PID=""
STEP_LABEL=""

cleanup() {
    if [ -n "$SPIN_PID" ]; then
        kill "$SPIN_PID" 2>/dev/null || true
        wait "$SPIN_PID" 2>/dev/null || true
        SPIN_PID=""
    fi
    if [ "$FANCY" = 1 ]; then
        printf '\033[?25h'          # show cursor
    fi
}
trap cleanup EXIT INT TERM

spin_stop() {
    if [ -n "$SPIN_PID" ]; then
        kill "$SPIN_PID" 2>/dev/null || true
        wait "$SPIN_PID" 2>/dev/null || true
        SPIN_PID=""
    fi
    if [ "$FANCY" = 1 ]; then
        printf '\033[?25h'          # show cursor between steps (prompts, bars)
    fi
}

step_begin() {
    STEP_LABEL="$1"
    if [ "$FANCY" = 1 ]; then
        printf '\033[?25l'
        (
            frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
            i=0
            while :; do
                printf '\r  %s%s%s %s' "$C_DIM" "${frames:i:1}" "$C_RESET" "$STEP_LABEL"
                i=$(( (i + 1) % 10 ))
                sleep 0.1
            done
        ) &
        SPIN_PID=$!
    else
        printf '%s ... ' "$STEP_LABEL"
    fi
}

step_done() {          # step_done [note]
    local note="${1:-}"
    spin_stop
    if [ "$FANCY" = 1 ]; then
        printf '\r\033[K  %s✓%s %s%s\n' "$C_OK" "$C_RESET" "$STEP_LABEL" \
            "${note:+ ${C_DIM}(${note})${C_RESET}}"
    else
        printf 'ok%s\n' "${note:+ (${note})}"
    fi
}

step_warn() {          # step_warn [note]
    local note="${1:-}"
    spin_stop
    if [ "$FANCY" = 1 ]; then
        printf '\r\033[K  %s!%s %s%s\n' "$C_WARN" "$C_RESET" "$STEP_LABEL" \
            "${note:+ ${C_DIM}(${note})${C_RESET}}"
    else
        printf 'warn%s\n' "${note:+ (${note})}"
    fi
}

note() {               # indented detail line under a step
    printf '      %s\n' "$*"
}

die() {
    spin_stop
    if [ "$FANCY" = 1 ]; then
        [ -n "$STEP_LABEL" ] && printf '\r\033[K  %s✗%s %s\n' "$C_ERR" "$C_RESET" "$STEP_LABEL"
        printf '  %serror:%s %s\n' "$C_ERR" "$C_RESET" "$*" >&2
    else
        printf 'failed\n' 2>/dev/null || true
        printf '[pulse-probe] error: %s\n' "$*" >&2
    fi
    exit 1
}

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
detect_arch() {
    case "$(uname -m)" in
        x86_64)  echo "amd64" ;;
        aarch64) echo "arm64" ;;
        *)       return 1 ;;
    esac
}

env_get() {            # env_get KEY -> value from the probe env file
    # Tolerate an unreadable file (root-owned 0600 during an unprivileged
    # PULSE_DRY_RUN): report nothing rather than crashing under set -e.
    [ -f "$ENV_PATH" ] || return 0
    [ -r "$ENV_PATH" ] || return 0
    sed -n "s/^${1}=//p" "$ENV_PATH" 2>/dev/null | head -n 1 || true
}

mask_token() {
    local t="$1"
    [ -n "$t" ] || { echo "(none)"; return; }
    printf '%.10s...\n' "$t"
}

write_unit() {
    cat > "$1" <<EOF
[Unit]
Description=Corehost Pulse probe (${SERVICE_NAME})
Documentation=https://pulse.corehost.io
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/${SERVICE_NAME}
EnvironmentFile=/etc/${SERVICE_NAME}.env
DynamicUser=yes
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
RestrictSUIDSGID=yes
# ICMP (ping) monitors: raw ICMP sockets without root. AmbientCapabilities
# hands the running process the capability; the bounding set keeps it as the
# only one available.
AmbientCapabilities=CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_RAW
Restart=always
RestartSec=5
# 78 is the probe saying it is misconfigured (no token, or a token the panel
# does not know). Restarting cannot fix that, and a crash loop would bury the
# one message that says what to do; the unit stops in failed state instead, so
# "systemctl status" shows the reason.
RestartPreventExitStatus=78

[Install]
WantedBy=multi-user.target
EOF
}

# ---------------------------------------------------------------------------
# Main flow
# ---------------------------------------------------------------------------
ARCH=""
MODE=""                # fresh | upgrade | replace
EXISTING=0
CUR_VERSION="unknown"
CUR_STATE="unknown"
CUR_API=""
CUR_TOKEN=""
SERVICE_ACTION=""
CONNECT_NOTE="not seen yet; check the panel"
HAVE_SYSTEMCTL=0
# The install is not finished when the files are in place: it is finished when
# the probe has checked in. CHECKIN_OK carries that answer to the closing
# block, which reports the reason instead of congratulating the operator.
CHECKIN_OK=0
CHECKIN_WAIT=25
INVOCATION=""

banner() {
    if [ "$FANCY" = 1 ]; then
        printf '\n  %sCorehost Pulse%s %sprobe installer%s\n\n' "$C_BOLD" "$C_RESET" "$C_DIM" "$C_RESET"
    else
        echo "Corehost Pulse probe installer"
    fi
    if [ -n "$DRY_RUN" ]; then
        note "dry run: no changes will be made"
    fi
}

step_detect_system() {
    step_begin "Detecting system"
    ARCH="$(detect_arch)" || die "unsupported architecture: $(uname -m) (linux amd64/arm64 only)"
    command -v systemctl >/dev/null 2>&1 && HAVE_SYSTEMCTL=1
    if [ "$HAVE_SYSTEMCTL" = 0 ] && [ -z "${PULSE_SKIP_SYSTEMCTL:-}" ] && [ -z "$DRY_RUN" ]; then
        die "systemctl not found; use install-probe-openrc.sh on OpenRC systems"
    fi
    if [ -z "$ROOT" ] && [ -z "$DRY_RUN" ] && [ "$(id -u)" -ne 0 ]; then
        die "must run as root (sudo)"
    fi
    step_done "linux/${ARCH}, systemd"
    if [ "$SERVICE_NAME" = "pulse-runner" ]; then
        note "found a pre-rename pulse-runner install; keeping that service name"
    fi
}

step_check_existing() {
    step_begin "Checking existing install"
    if [ -e "$BIN_PATH" ] || [ -f "$UNIT_PATH" ]; then
        EXISTING=1
    fi

    if [ "$EXISTING" = 0 ]; then
        # Fresh install: a token (or a leftover env file) is required. A
        # leftover env file's credentials are read now so they are never
        # clobbered with an empty token below.
        if [ -f "$ENV_PATH" ]; then
            CUR_API="$(env_get PULSE_API_URL)"
            CUR_TOKEN="$(env_get PULSE_PROBE_TOKEN)"
        fi
        if [ -z "$PULSE_PROBE_TOKEN" ] && [ ! -f "$ENV_PATH" ]; then
            if [ -n "$DRY_RUN" ]; then
                MODE="fresh"
                step_done "none found"
                note "note: PULSE_PROBE_TOKEN not set; a real run would stop here"
                return
            fi
            die "PULSE_PROBE_TOKEN not set and no existing ${ENV_PATH}; get a token from ${PANEL_URL}"
        fi
        MODE="fresh"
        if [ -f "$ENV_PATH" ]; then
            step_done "no service found; reusing ${ENV_PATH}"
        else
            step_done "none found"
        fi
        return
    fi

    # Gather status of the existing install.
    if [ -x "$BIN_PATH" ]; then
        CUR_VERSION="$("$BIN_PATH" -version 2>/dev/null || echo "unknown")"
    fi
    if [ "$HAVE_SYSTEMCTL" = 1 ] && [ -z "${PULSE_SKIP_SYSTEMCTL:-}" ]; then
        CUR_STATE="$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)"
        [ -n "$CUR_STATE" ] || CUR_STATE="unknown"
    fi
    CUR_API="$(env_get PULSE_API_URL)"
    CUR_TOKEN="$(env_get PULSE_PROBE_TOKEN)"
    step_done "existing install found (${SERVICE_NAME})"
    note "Installed version : ${CUR_VERSION}"
    note "Service state     : ${CUR_STATE}"
    note "API URL           : ${CUR_API:-(none)}"
    note "Token             : $(mask_token "$CUR_TOKEN")"

    # Decide what to do with it.
    if [ "$INTERACTIVE" = 1 ] && [ -z "$DRY_RUN" ]; then
        printf '      [U]pgrade binary / [R]eplace credentials + upgrade / [A]bort  (default U in 15s): '
        local ans=""
        read -r -t 15 ans || ans=""
        echo ""
        case "$ans" in
            [Rr]*)
                [ -n "$PULSE_PROBE_TOKEN" ] || die "replace chosen but PULSE_PROBE_TOKEN is not set; re-run with a token from ${PANEL_URL}"
                MODE="replace"
                ;;
            [Aa]*)
                echo "      Aborted; nothing changed."
                exit 0
                ;;
            *)
                MODE="upgrade"
                ;;
        esac
    else
        if [ -n "$PULSE_PROBE_TOKEN" ] && [ "$PULSE_PROBE_TOKEN" != "$CUR_TOKEN" ]; then
            MODE="replace"
        else
            MODE="upgrade"
        fi
    fi

    if [ "$MODE" = "replace" ]; then
        note "Plan: replacing credentials and upgrading the binary."
    else
        note "Plan: upgrading the binary in place; keeping existing credentials."
    fi

    # Upgrade with nothing to keep is really a broken install.
    if [ "$MODE" = "upgrade" ] && [ ! -f "$ENV_PATH" ] && [ -z "$PULSE_PROBE_TOKEN" ] && [ -z "$DRY_RUN" ]; then
        die "no credentials configured and PULSE_PROBE_TOKEN not set; get a token from ${PANEL_URL}"
    fi
}

print_dry_run_plan() {
    echo ""
    echo "Dry run plan (PULSE_DRY_RUN=1, nothing was touched):"
    echo "  mode        : ${MODE}"
    echo "  service     : ${SERVICE_NAME}$([ "$SERVICE_NAME" = "pulse-runner" ] && echo ' (pre-rename install, name kept)')"
    echo "  binary      : would download ${DOWNLOAD_BASE}/probe/pulse-probe-linux-${ARCH}"
    echo "                (falling back to ${DOWNLOAD_BASE}/runner/pulse-runner-linux-${ARCH})"
    echo "                and install it to ${BIN_PATH}"
    case "$MODE" in
        fresh)
            if [ -z "$PULSE_PROBE_TOKEN" ] && [ -f "$ENV_PATH" ]; then
                echo "  credentials : would keep existing ${ENV_PATH} (token $(mask_token "$CUR_TOKEN"))"
            else
                echo "  credentials : would write ${ENV_PATH} (token $(mask_token "$PULSE_PROBE_TOKEN"))"
            fi ;;
        replace)
            echo "  credentials : would replace token in ${ENV_PATH} (new token $(mask_token "$PULSE_PROBE_TOKEN"))" ;;
        upgrade)
            echo "  credentials : would keep existing ${ENV_PATH}" ;;
    esac
    echo "  unit        : would write ${UNIT_PATH} (AmbientCapabilities=CAP_NET_RAW +"
    echo "                CapabilityBoundingSet=CAP_NET_RAW for ICMP monitors; added to a"
    echo "                pre-existing unit too, plus RestartPreventExitStatus=78 so a"
    echo "                misconfigured probe stops with its message instead of looping),"
    echo "                daemon-reload, enable and start/restart ${SERVICE_NAME}"
    if [ -n "${PULSE_PING_GROUP_RANGE:-}" ]; then
        echo "  sysctl      : would write /etc/sysctl.d/99-${SERVICE_NAME}-ping.conf"
        echo "                (net.ipv4.ping_group_range = 0 2147483647) and apply it"
    fi
    echo "  check-in    : would wait up to ${CHECKIN_WAIT}s for the 'probe mode:' journal line,"
    echo "                and report the probe's own reason if it never arrives"
    echo "  panel       : ${PANEL_URL}"
}

download_binary() {    # download_binary URL TMPFILE -> 0 on success
    local url="$1" tmp="$2"
    if [ "$FANCY" = 1 ]; then
        # Real progress bar on a TTY; the bar line is replaced by the check.
        curl -f --progress-bar --retry 3 -o "$tmp" "$url"
    else
        curl -fsSL --retry 3 -o "$tmp" "$url"
    fi
}

step_download() {
    local url legacy_url tmp
    url="${DOWNLOAD_BASE}/probe/pulse-probe-linux-${ARCH}"
    legacy_url="${DOWNLOAD_BASE}/runner/pulse-runner-linux-${ARCH}"
    tmp="$(mktemp)"
    if [ "$FANCY" = 1 ]; then
        STEP_LABEL="Downloading probe"
    else
        step_begin "Downloading probe"
    fi
    # Same binary under two CDN names; the pre-rename name is the fallback so
    # the installer works against a CDN that has not republished yet.
    if ! download_binary "$url" "$tmp"; then
        if ! download_binary "$legacy_url" "$tmp"; then
            rm -f "$tmp"
            die "download failed: $url (and fallback $legacy_url)"
        fi
    fi
    if [ "$FANCY" = 1 ]; then
        printf '\r\033[K'
    fi
    step_done "linux/${ARCH}"
    DOWNLOADED_TMP="$tmp"
}

step_install_binary() {
    step_begin "Installing binary"
    mkdir -p "$(dirname "$BIN_PATH")"
    chmod 755 "$DOWNLOADED_TMP"
    mv -f "$DOWNLOADED_TMP" "$BIN_PATH"
    NEW_VERSION="$("$BIN_PATH" -version 2>/dev/null || echo "pulse-probe unknown")"
    step_done "$NEW_VERSION"
}

step_credentials() {
    step_begin "Writing credentials"
    mkdir -p "$(dirname "$ENV_PATH")"
    # Keep the env file untouched when upgrading, and also on a "fresh" run
    # that found only a leftover env file and no new token (service/binary
    # were removed but credentials remain valid; never blank the token).
    if [ -f "$ENV_PATH" ]; then
        if [ "$MODE" = "upgrade" ] || { [ "$MODE" = "fresh" ] && [ -z "$PULSE_PROBE_TOKEN" ]; }; then
            step_done "kept existing"
            return
        fi
    fi
    (
        umask 077
        cat > "$ENV_PATH" <<EOF
PULSE_API_URL=${PULSE_API_URL:-${CUR_API:-$API_URL_DEFAULT}}
PULSE_PROBE_TOKEN=${PULSE_PROBE_TOKEN:-${CUR_TOKEN}}
EOF
    )
    chmod 600 "$ENV_PATH"
    if [ "$MODE" = "replace" ]; then
        step_done "replaced"
    else
        step_done "written"
    fi
}

step_service() {
    step_begin "Starting service"
    mkdir -p "$(dirname "$UNIT_PATH")"
    # The unit is rewritten on every run, so upgrades of pre-CAP_NET_RAW
    # installs pick the capability lines up here; say so when that happens.
    local unit_note=""
    if [ -f "$UNIT_PATH" ] && ! grep -q '^AmbientCapabilities=CAP_NET_RAW' "$UNIT_PATH"; then
        unit_note="unit updated: CAP_NET_RAW added for ICMP (ping) monitors"
    fi
    write_unit "$UNIT_PATH"
    if [ -n "${PULSE_SKIP_SYSTEMCTL:-}" ]; then
        SERVICE_ACTION="skipped (test mode)"
        step_done "$SERVICE_ACTION"
        if [ -n "$unit_note" ]; then note "$unit_note"; fi
        return
    fi
    systemctl daemon-reload
    systemctl enable "$SERVICE_NAME" >/dev/null 2>&1 || true
    if systemctl is-active --quiet "$SERVICE_NAME"; then
        systemctl restart "$SERVICE_NAME"
        SERVICE_ACTION="restarted"
    else
        systemctl start "$SERVICE_NAME"
        SERVICE_ACTION="started"
    fi
    step_done "$SERVICE_ACTION"
    if [ -n "$unit_note" ]; then note "$unit_note"; fi
}

step_ping_sysctl() {
    # Opt-in fallback for ICMP monitors (PULSE_PING_GROUP_RANGE=1): some
    # hosts (containers, hardened kernels) refuse raw ICMP sockets even with
    # CAP_NET_RAW. iputils then falls back to unprivileged datagram ICMP,
    # which works only when the service's gid sits inside
    # net.ipv4.ping_group_range; this opens the range to all gids via a
    # sysctl drop-in and applies it immediately.
    [ -n "${PULSE_PING_GROUP_RANGE:-}" ] || return 0
    step_begin "Enabling unprivileged ICMP (sysctl)"
    local conf="${ROOT}/etc/sysctl.d/99-${SERVICE_NAME}-ping.conf"
    mkdir -p "$(dirname "$conf")"
    cat > "$conf" <<EOF
# Corehost Pulse probe: allow unprivileged (datagram) ICMP echo for all
# gids, the fallback for ping monitors on hosts where CAP_NET_RAW is not
# enough. Written by the installer on request (PULSE_PING_GROUP_RANGE=1);
# remove this file and run "sysctl --system" to revert.
net.ipv4.ping_group_range = 0 2147483647
EOF
    if [ -z "${PULSE_SKIP_SYSTEMCTL:-}" ]; then
        sysctl -q -w "net.ipv4.ping_group_range=0 2147483647" \
            || { step_warn "drop-in written; live apply failed (takes effect after reboot or sysctl --system)"; return 0; }
    fi
    step_done "net.ipv4.ping_group_range = 0 2147483647"
}

# service_log prints what this service has said since the restart above. The
# probe's own failure messages are already specific (they name the missing
# variable, the rejected token, the unreachable API), so the installer quotes
# them rather than writing a second, vaguer diagnosis of its own.
service_log() {
    if [ -n "$INVOCATION" ]; then
        journalctl -q _SYSTEMD_INVOCATION_ID="$INVOCATION" -o cat --no-pager 2>/dev/null || true
    else
        journalctl -q -u "$SERVICE_NAME" -n 100 -o cat --no-pager 2>/dev/null || true
    fi
}

# diagnose reads the service log and sets CONNECT_NOTE to the specific reason
# the probe is not checking in. Every branch is a thing the probe prints about
# itself; the final fallback is the honest "it did not say".
diagnose() {           # diagnose LOGTEXT
    local log="$1"
    if printf '%s\n' "$log" | grep -q 'PULSE_PROBE_TOKEN is not set'; then
        CONNECT_NOTE="no token configured; put PULSE_PROBE_TOKEN in ${ENV_PATH}"
    elif printf '%s\n' "$log" | grep -q 'PULSE_PROBE_TOKEN was rejected'; then
        CONNECT_NOTE="token rejected; get a fresh one from ${PANEL_URL}"
    elif printf '%s\n' "$log" | grep -q 'Cannot reach the Pulse API'; then
        CONNECT_NOTE="cannot reach the Pulse API from this host (DNS, egress or proxy)"
    elif [ -z "$log" ]; then
        CONNECT_NOTE="the service logged nothing; see journalctl -u ${SERVICE_NAME} -n 50"
    else
        CONNECT_NOTE="started but no check-in in ${CHECKIN_WAIT}s; see journalctl -u ${SERVICE_NAME} -n 50"
    fi
}

# quote_log prints the tail of the service log under the failed step, so the
# reason is on screen instead of behind another command.
quote_log() {          # quote_log LOGTEXT
    local log="$1"
    [ -n "$log" ] || return 0
    printf '%s\n' "$log" | tail -n 14 | while IFS= read -r l; do
        printf '      %s%s%s\n' "$C_DIM" "$l" "$C_RESET"
    done
}

step_wait_checkin() {
    step_begin "Verifying the probe is checking in"
    if [ -n "${PULSE_SKIP_SYSTEMCTL:-}" ]; then
        CHECKIN_OK=1
        CONNECT_NOTE="skipped (test mode)"
        step_done "$CONNECT_NOTE"
        return
    fi

    INVOCATION="$(systemctl show -p InvocationID --value "$SERVICE_NAME" 2>/dev/null || true)"

    # A check-in is the config pull the backend counts: new binaries log it as
    # "probe mode:", pre-rename ones as "runner mode:". Watch for that line,
    # and for the service giving up, whichever comes first.
    local deadline log line label state
    deadline=$(( $(date +%s) + CHECKIN_WAIT ))
    while :; do
        log="$(service_log)"
        line="$(printf '%s\n' "$log" | grep -m1 -E 'probe mode:|runner mode:' || true)"
        if [ -n "$line" ]; then
            label="$(printf '%s\n' "$line" | sed -n 's/.*region "\([^"]*\)".*/\1/p')"
            CHECKIN_OK=1
            if [ -n "$label" ]; then
                CONNECT_NOTE="checking in as ${label}"
            else
                CONNECT_NOTE="checking in"
            fi
            step_done "$CONNECT_NOTE"
            return
        fi
        state="$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)"
        if [ "$state" = "failed" ] || [ "$state" = "inactive" ]; then
            break                      # it stopped; waiting longer proves nothing
        fi
        [ "$(date +%s)" -lt "$deadline" ] || break
        sleep 1
    done

    diagnose "$log"
    step_warn "$CONNECT_NOTE"
    quote_log "$log"
}

closing_block() {
    local ver headline lines uninstall
    ver="$("$BIN_PATH" -version 2>/dev/null | awk '{print $2}' || true)"
    [ -n "$ver" ] || ver="unknown"
    case "$MODE" in
        fresh)   headline="Installed ${SERVICE_NAME} ${ver}" ;;
        replace) headline="Upgraded ${SERVICE_NAME} to ${ver} (credentials replaced)" ;;
        *)       headline="Upgraded ${SERVICE_NAME} to ${ver} (credentials kept)" ;;
    esac
    if [ "$CHECKIN_OK" != 1 ]; then
        headline="${headline}, but it is not checking in"
    fi
    uninstall="sudo systemctl disable --now ${SERVICE_NAME}; sudo rm -f /usr/local/bin/${SERVICE_NAME} /etc/${SERVICE_NAME}.env /etc/systemd/system/${SERVICE_NAME}.service; sudo systemctl daemon-reload"

    lines=(
        "$headline"
        ""
        "Service : ${SERVICE_NAME}.service"
        "Logs    : journalctl -u ${SERVICE_NAME} -f"
        "Status  : ${CONNECT_NOTE}"
        ""
    )
    if [ "$CHECKIN_OK" = 1 ]; then
        lines+=("Open ${PANEL_URL} - your probe shows Online within a minute.")
    else
        lines+=(
            "The files are installed; the probe has not reached the control plane yet."
            "Fix the reason above and run: sudo systemctl restart ${SERVICE_NAME}"
            "Then watch it: journalctl -u ${SERVICE_NAME} -f"
        )
    fi
    if [ "$SERVICE_NAME" = "pulse-runner" ]; then
        lines+=("(pre-rename install: service name pulse-runner kept for compatibility)")
    fi

    echo ""
    local width=0 l cols
    for l in "${lines[@]}"; do
        [ "${#l}" -gt "$width" ] && width="${#l}"
    done
    cols="$(tput cols 2>/dev/null || echo 0)"
    if [ "$FANCY" = 1 ] && [ "${cols:-0}" -ge $((width + 6)) ]; then
        local border=""
        border="$(printf '%*s' $((width + 2)) '' | sed 's/ /─/g')"
        printf '  %s┌%s┐%s\n' "$C_DIM" "$border" "$C_RESET"
        for l in "${lines[@]}"; do
            printf '  %s│%s %-*s %s│%s\n' "$C_DIM" "$C_RESET" "$width" "$l" "$C_DIM" "$C_RESET"
        done
        printf '  %s└%s┘%s\n' "$C_DIM" "$border" "$C_RESET"
        printf '  %sUninstall:%s %s\n' "$C_DIM" "$C_RESET" "$uninstall"
    else
        local rule=""
        rule="$(printf '%*s' $((width + 2)) '' | tr ' ' '-')"
        printf '+%s+\n' "$rule"
        for l in "${lines[@]}"; do
            printf '| %-*s |\n' "$width" "$l"
        done
        printf '+%s+\n' "$rule"
        printf 'Uninstall: %s\n' "$uninstall"
    fi
    echo ""
}

main() {
    banner
    step_detect_system
    step_check_existing

    if [ -n "$DRY_RUN" ]; then
        print_dry_run_plan
        exit 0
    fi

    step_download
    step_install_binary
    step_credentials
    step_ping_sysctl
    step_service
    step_wait_checkin
    closing_block
    # Non-zero when the probe is not checking in, so an automated install
    # (config management, an image build) fails where a human would have read
    # the warning.
    [ "$CHECKIN_OK" = 1 ] || exit 1
}

main "$@"
