#!/usr/bin/env bash
# install.sh — Install dugnad-agent on Linux, Termux, or a VPS.
#
# Usage:
#   curl -sSL install.dugnad.cloud | bash -s -- --token <device-token>
#   curl -sSL install.dugnad.cloud | bash -s -- --update
#
# Flags:
#   --token <token>         Device registration token (first install only).
#   --coordinator <url>     Coordinator URL (default: https://api.dugnad.cloud).
#   --label <name>          Human-readable node name (default: hostname).
#   --update                Upgrade binary only. Skip token + config checks.
#   --with-container-runtime  Opt-in: install Docker + enable container apps (non-Termux, kernel >= 6).
#   --manifest-url <url>    Signed bridge-allowlist manifest URL (#138). Defaults to the
#                           canonical manifest on a fresh install (#149); pass to override
#                           (e.g. a staging manifest). The agent re-fetches + verifies +
#                           full-replaces allowed_bridge_node_ids on every startup —
#                           required for NAT'd-node bridge-flip eligibility.
#   --refresh-bridges       Restart the agent so it re-fetches + verifies the bridge allowlist
#                           (read at startup only). Operates on an existing install, then exits.
#   --set-manifest-url <url>  Activate manifest consumption on an EXISTING node (config-only;
#                           the binary is NOT re-downloaded). Stops, sets manifest_url, restarts, verifies.
#   --unset-manifest-url    Deactivate + clean rollback on an existing node (removes manifest_url,
#                           resets allowed_bridge_node_ids=[], clears the anti-replay floor), then exits.
#   --require-bridges       Fail if the bridge allowlist is empty after (re)start (strict).
#   --dry-run               Print actions without executing.
#   --help                  Show this message.
#
# Re-running without flags upgrades the binary, keeps the keypair and config.

set -euo pipefail

COORDINATOR="https://api.dugnad.cloud"
DOWNLOAD_BASE="https://downloads.sptk.be/dugnad-agent/latest"
COSIGN_DOWNLOAD_BASE="https://github.com/sigstore/cosign/releases/latest/download"
# Security profiles are arch-independent and live in a flat directory.
PROFILES_DOWNLOAD_BASE="$DOWNLOAD_BASE/profiles"
PROFILES_INSTALL_DIR="/usr/local/share/dugnad/profiles"
TOKEN=""
LABEL=""
UPDATE_ONLY=false
WITH_CONTAINER_RUNTIME=false
# #149: canonical signed bridge-allowlist manifest. Defaulted onto every fresh
# provision (config is written first-install-only, see below) so the standard
# --token flow activates manifest consumption without a per-node --manifest-url
# flag — closes the coop-runner#533 fleet-scale gate. Deliberately NOT mirrored
# as a compile-time default in agent/src/config.rs: that would retrofit every
# live node on its next binary upgrade, which the 2026-06-19 decision
# (fresh-provision-only activation, see #148) avoids for the trusted-core
# backbone.
DEFAULT_MANIFEST_URL="https://downloads.sptk.be/dugnad-agent/bridge-manifest.json"
MANIFEST_URL=""
REFRESH_BRIDGES=false
REQUIRE_BRIDGES=false
# #148: config-only activate/deactivate of manifest consumption on an EXISTING
# node (terminal sub-commands; binary NOT re-downloaded).
SET_MANIFEST_URL=""
UNSET_MANIFEST=false
DRY_RUN=false

log()   { printf '\033[1;34m[install]\033[0m %s\n' "$*"; }
warn()  { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
fail()  { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
run()   { if $DRY_RUN; then printf '\033[2m$ %s\033[0m\n' "$*"; else eval "$*"; fi; }

usage() { sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; }

# ─── Parse args ────────────────────────────────────────────────────────────
while [ $# -gt 0 ]; do
    case "$1" in
        --token)       TOKEN="${2:?--token requires a value}"; shift 2 ;;
        --coordinator) COORDINATOR="${2:?--coordinator requires a value}"; shift 2 ;;
        --label)       LABEL="${2:?--label requires a value}"; shift 2 ;;
        --update)      UPDATE_ONLY=true; shift ;;
        --with-container-runtime) WITH_CONTAINER_RUNTIME=true; shift ;;
        --manifest-url) MANIFEST_URL="${2:?--manifest-url requires a value}"; shift 2 ;;
        --refresh-bridges) REFRESH_BRIDGES=true; shift ;;
        --set-manifest-url) SET_MANIFEST_URL="${2:?--set-manifest-url requires a value}"; shift 2 ;;
        --unset-manifest-url) UNSET_MANIFEST=true; shift ;;
        --require-bridges) REQUIRE_BRIDGES=true; shift ;;
        --dry-run)     DRY_RUN=true; shift ;;
        --help|-h)     usage 0 ;;
        *)             warn "unknown flag: $1"; usage 1 ;;
    esac
done

# #149: default the manifest URL when not explicitly supplied, so the standard
# `--token`-only flow seeds manifest_url on a fresh provision. An explicit
# --manifest-url (e.g. a staging manifest) still overrides. Existing nodes are
# unaffected — config is written first-install-only (see "Write config" below).
[ -z "$MANIFEST_URL" ] && MANIFEST_URL="$DEFAULT_MANIFEST_URL"

# ─── Detect platform ───────────────────────────────────────────────────────
IS_TERMUX=false
if [ -n "${TERMUX_VERSION:-}" ] || [ -d /data/data/com.termux/files/usr ]; then
    IS_TERMUX=true
fi

MACHINE="$(uname -m)"
case "$MACHINE" in
    x86_64|amd64)
        ARCH_DIR="x86_64"
        COSIGN_ASSET="cosign-linux-amd64"
        $IS_TERMUX && fail "x86_64 on Termux is not supported (no Android x86_64 build)"
        ;;
    aarch64|arm64)
        if $IS_TERMUX; then
            ARCH_DIR="aarch64"         # Android NDK / bionic build
            COSIGN_ASSET=""
        else
            ARCH_DIR="aarch64-linux"   # aarch64-unknown-linux-musl (Raspberry Pi, ARM VPS, Graviton)
            COSIGN_ASSET="cosign-linux-arm64"
        fi
        ;;
    armv7l|armv7|arm)
        ARCH_DIR="armv7"
        COSIGN_ASSET=""
        $IS_TERMUX || fail "armv7 Linux is not supported yet — only armv7 Android/Termux binaries exist."
        ;;
    *)
        fail "unsupported architecture: $MACHINE"
        ;;
esac

BINARY_URL="$DOWNLOAD_BASE/$ARCH_DIR/dugnad-agent"
CHECKSUM_URL="$BINARY_URL.sha256"

# ─── Choose install mode ───────────────────────────────────────────────────
# termux           → runit under ~/.dugnad/sv
# root + systemd   → system service
# user + systemd   → user service (~/.config/systemd/user)
MODE=""
if $IS_TERMUX; then
    MODE="termux"
elif [ "$(id -u)" = "0" ]; then
    command -v systemctl >/dev/null || fail "root install requires systemd (systemctl not found)"
    MODE="system"
else
    command -v systemctl >/dev/null || fail "user install requires systemd (systemctl not found). Re-run as root, or install Termux for Android."
    systemctl --user show-environment >/dev/null 2>&1 \
        || fail "user systemd is not available in this session. Re-run as root, or enable lingering with: sudo loginctl enable-linger $(id -un)"
    MODE="user"
fi

# ─── Paths ─────────────────────────────────────────────────────────────────
case "$MODE" in
    termux)
        BIN_DIR="${PREFIX:-/data/data/com.termux/files/usr}/bin"
        CONFIG_DIR="$HOME/.dugnad"
        LOG_DIR="$CONFIG_DIR/logs"
        SVDIR="${PREFIX:-/data/data/com.termux/files/usr}/var/service"
        SERVICE_DIR="$SVDIR/dugnad-agent"
        ;;
    system)
        BIN_DIR="/usr/local/bin"
        CONFIG_DIR="/etc/dugnad-agent"
        LOG_DIR="/var/log/dugnad-agent"
        SERVICE_FILE="/etc/systemd/system/dugnad-agent.service"
        ;;
    user)
        BIN_DIR="$HOME/.local/bin"
        CONFIG_DIR="$HOME/.dugnad"
        LOG_DIR="$CONFIG_DIR/logs"
        SERVICE_FILE="$HOME/.config/systemd/user/dugnad-agent.service"
        ;;
esac
BINARY="$BIN_DIR/dugnad-agent"
COSIGN_BINARY="$BIN_DIR/cosign"
CONFIG_FILE="$CONFIG_DIR/config.toml"

# ─── Bridge-allowlist helpers (#138) ───────────────────────────────────────
# The agent fetches + VERIFIES (binary-embedded ed25519 key) + full-replaces the
# allowlist on startup. install.sh only sets manifest_url and triggers a restart;
# it deliberately does NOT verify (a key in a TLS-fetched script is theater — the
# agent's signature verify is the authoritative gate).
bridge_count_from_config() {
    [ -f "$CONFIG_FILE" ] || { echo 0; return; }
    local line
    line=$(grep -E '^[[:space:]]*allowed_bridge_node_ids[[:space:]]*=' "$CONFIG_FILE" 2>/dev/null | head -1)
    [ -n "$line" ] || { echo 0; return; }
    printf '%s' "$line" | grep -oE '"[^"]+"' | wc -l | tr -d ' '
}

config_has_manifest_url() {
    [ -f "$CONFIG_FILE" ] && grep -qE '^[[:space:]]*manifest_url[[:space:]]*=' "$CONFIG_FILE"
}

restart_agent() {
    case "$MODE" in
        termux) run "echo -n t > '$SERVICE_DIR/supervise/control' 2>/dev/null || true" ;;
        system) run "systemctl restart dugnad-agent" ;;
        user)   run "systemctl --user restart dugnad-agent" ;;
    esac
}

# Stop / start the agent (#148): a config mutation via the `manifest` subcommand
# must run while the agent is DOWN so its own startup config-rewrite cannot race
# the atomic swap (panel-review concurrency finding). runit: 'd' = down, 'u' = up.
stop_agent() {
    case "$MODE" in
        termux) run "echo -n d > '$SERVICE_DIR/supervise/control' 2>/dev/null || true" ;;
        system) run "systemctl stop dugnad-agent" ;;
        user)   run "systemctl --user stop dugnad-agent" ;;
    esac
}
start_agent() {
    case "$MODE" in
        termux) run "echo -n u > '$SERVICE_DIR/supervise/control' 2>/dev/null || true" ;;
        system) run "systemctl start dugnad-agent" ;;
        user)   run "systemctl --user start dugnad-agent" ;;
    esac
}

# Version-gate (#148): the `manifest` subcommand only exists in builds that have
# it. Probe by a recognizable usage sentinel — an OLDER binary treats "manifest"
# as a config-path positional and errors differently (no such sentinel). We never
# silently download /latest to obtain it (that would defeat "binary untouched").
manifest_subcommand_supported() {
    [ -x "$BINARY" ] || return 1
    "$BINARY" manifest 2>&1 | grep -q 'manifest <set|unset>'
}

# Poll config.toml for a non-empty allowlist after the agent's verified refresh.
wait_for_bridges() {
    local timeout_secs="${1:-25}" start; start=$(date +%s)
    while :; do
        [ "$(bridge_count_from_config)" -gt 0 ] && return 0
        [ $(( $(date +%s) - start )) -ge "$timeout_secs" ] && return 1
        sleep 3
    done
}

# --refresh-bridges is a terminal sub-command on an existing install.
if $REFRESH_BRIDGES; then
    [ -f "$CONFIG_FILE" ] || fail "--refresh-bridges: no config at $CONFIG_FILE (run a full install first)"
    config_has_manifest_url \
        || fail "--refresh-bridges: no manifest_url in $CONFIG_FILE — set one via --manifest-url on install"
    log "refreshing bridge allowlist: restarting agent (allowlist is read at startup; the agent re-fetches + verifies the signed manifest)"
    restart_agent
    if $DRY_RUN; then log "(dry-run) would wait for the verified allowlist to apply"; exit 0; fi
    if wait_for_bridges 25; then
        log "bridge allowlist refreshed: $(bridge_count_from_config) authorized bridge(s)"
    elif $REQUIRE_BRIDGES; then
        fail "--require-bridges: allowlist still empty after refresh — check the agent log + manifest reachability"
    else
        warn "bridge allowlist empty after refresh — node NOT bridge-flip eligible. Check the agent log + manifest publish."
    fi
    exit 0
fi

# ─── #148: config-only manifest activate/deactivate (terminal sub-commands) ──
# Mutate ONLY the node-local config via the already-deployed binary's
# `manifest set|unset` subcommand — the binary is NOT re-downloaded (no /latest
# fetch), so a deliberately-pinned release stays put. The service is stopped
# before the mutation and restarted after, so the agent's own startup
# config-rewrite cannot race the atomic swap.
if [ -n "$SET_MANIFEST_URL" ] || $UNSET_MANIFEST; then
    # Config-only: must never combine with provisioning / upgrade flags (which
    # would write a fresh-install config or download a new binary).
    if [ -n "$TOKEN" ] || $UPDATE_ONLY || $REFRESH_BRIDGES; then
        fail "--set-manifest-url / --unset-manifest-url are config-only sub-commands; do not combine with --token / --update / --refresh-bridges"
    fi
    if [ -n "$SET_MANIFEST_URL" ] && $UNSET_MANIFEST; then
        fail "choose either --set-manifest-url or --unset-manifest-url, not both"
    fi
    [ -f "$CONFIG_FILE" ] || fail "no config at $CONFIG_FILE (run a full install first)"
    if ! $DRY_RUN && ! manifest_subcommand_supported; then
        fail "the installed dugnad-agent ($BINARY) does not support the 'manifest' subcommand — upgrade to a build with #148 first. This command does NOT download a new binary."
    fi

    stop_agent
    if [ -n "$SET_MANIFEST_URL" ]; then
        log "activating manifest consumption: manifest set --url $SET_MANIFEST_URL"
        run "'$BINARY' manifest set --url '$SET_MANIFEST_URL' --config '$CONFIG_FILE'" \
            || { start_agent; fail "manifest set failed — service restarted, config unchanged"; }
    else
        log "deactivating manifest consumption: manifest unset (clean rollback)"
        run "'$BINARY' manifest unset --config '$CONFIG_FILE'" \
            || { start_agent; fail "manifest unset failed — service restarted"; }
    fi
    start_agent

    if $DRY_RUN; then log "(dry-run) would verify the end state"; exit 0; fi

    if [ -n "$SET_MANIFEST_URL" ]; then
        # set verification: the verified refresh should populate a NON-EMPTY
        # allowlist; an empty one means fetch/verify failed → loud signal.
        if wait_for_bridges 25; then
            log "manifest consumption active: $(bridge_count_from_config) authorized bridge(s)"
        elif $REQUIRE_BRIDGES; then
            fail "--require-bridges: allowlist still empty after activation — check the agent log + manifest reachability (roll back with --unset-manifest-url)"
        else
            warn "allowlist still empty after activation — node NOT bridge-flip eligible yet. Check the agent log + manifest publish (roll back with --unset-manifest-url)."
        fi
    else
        # unset verification: the INERT end-state is the goal, so do NOT use
        # wait_for_bridges (an empty allowlist would false-fail it). The mutation
        # already committed synchronously before restart, and a manifest_url-less
        # config triggers no startup rewrite — so assert the clean state directly.
        if config_has_manifest_url; then
            warn "unset: manifest_url still present in $CONFIG_FILE — check the agent log; re-run --unset-manifest-url"
        elif [ "$(bridge_count_from_config)" -ne 0 ]; then
            warn "unset: allowed_bridge_node_ids non-empty after rollback — check the agent log"
        else
            log "manifest consumption deactivated: manifest_url removed, allowlist empty, anti-replay floor cleared"
        fi
    fi
    exit 0
fi

ensure_cosign() {
    if $IS_TERMUX; then
        log "skipping cosign install in Termux mode"
        return 0
    fi
    if [ -z "$COSIGN_ASSET" ]; then
        warn "no cosign binary mapping for architecture $MACHINE — container verification may be unavailable"
        return 0
    fi
    if [ -x "$COSIGN_BINARY" ]; then
        log "keeping existing cosign at $COSIGN_BINARY"
        return 0
    fi

    local tmp_cosign="$CONFIG_DIR/.cosign.new"
    local cosign_url="$COSIGN_DOWNLOAD_BASE/$COSIGN_ASSET"
    log "installing cosign verifier helper"
    if $DRY_RUN; then
        run "curl -fSL --retry 3 --max-time 300 '$cosign_url' -o '$tmp_cosign'"
        run "chmod +x '$tmp_cosign'"
        run "mv '$tmp_cosign' '$COSIGN_BINARY'"
    else
        curl -fSL --retry 3 --max-time 300 "$cosign_url" -o "$tmp_cosign" \
            || fail "cosign download failed from $cosign_url"
        chmod +x "$tmp_cosign"
        mv "$tmp_cosign" "$COSIGN_BINARY"
        log "cosign installed: $($COSIGN_BINARY version 2>/dev/null | head -1 || echo installed)"
    fi
}

log "platform: $MACHINE ($ARCH_DIR) — mode: $MODE"
log "install paths: binary=$BINARY config=$CONFIG_FILE"

# ─── Verify token state ────────────────────────────────────────────────────
if ! $UPDATE_ONLY; then
    if [ -z "$TOKEN" ] && [ ! -f "$CONFIG_FILE" ]; then
        fail "--token is required on first install. Create one in the dashboard and re-run."
    fi
    if [ -n "$TOKEN" ] && [ -f "$CONFIG_FILE" ]; then
        warn "existing config found — ignoring --token (already registered)"
        TOKEN=""
    fi
fi

# ─── Ensure dirs ───────────────────────────────────────────────────────────
run "mkdir -p '$BIN_DIR' '$CONFIG_DIR' '$LOG_DIR'"
[ "$MODE" = "user" ] && run "mkdir -p '$(dirname "$SERVICE_FILE")'"
ensure_cosign

# ─── Download binary ───────────────────────────────────────────────────────
TMP_BINARY="$CONFIG_DIR/.dugnad-agent.new"
TMP_CHECKSUM="$CONFIG_DIR/.dugnad-agent.sha256"
log "downloading $BINARY_URL"
if $DRY_RUN; then
    run "curl -fSL --retry 3 --max-time 300 '$BINARY_URL' -o '$TMP_BINARY'"
    run "curl -fSL --retry 3 --max-time 60 '$CHECKSUM_URL' -o '$TMP_CHECKSUM'"
else
    curl -fSL --retry 3 --max-time 300 "$BINARY_URL" -o "$TMP_BINARY" \
        || fail "download failed from $BINARY_URL"
    curl -fSL --retry 3 --max-time 60 "$CHECKSUM_URL" -o "$TMP_CHECKSUM" \
        || fail "checksum download failed from $CHECKSUM_URL"
    command -v sha256sum >/dev/null \
        || fail "sha256sum is required for checksum verification (install coreutils)"
    EXPECTED_SHA=$(awk '{print $1}' "$TMP_CHECKSUM")
    [ -n "$EXPECTED_SHA" ] || fail "checksum file is empty or malformed: $CHECKSUM_URL"
    ACTUAL_SHA=$(sha256sum "$TMP_BINARY" | awk '{print $1}')
    if [ "$EXPECTED_SHA" != "$ACTUAL_SHA" ]; then
        rm -f "$TMP_BINARY" "$TMP_CHECKSUM"
        fail "checksum mismatch — binary may be tampered. expected=$EXPECTED_SHA got=$ACTUAL_SHA"
    fi
    rm -f "$TMP_CHECKSUM"
    log "checksum verified: $EXPECTED_SHA"
    chmod +x "$TMP_BINARY"
fi

# ─── Stop service before replacing binary (avoid ETXTBSY) ──────────────────
SERVICE_WAS_RUNNING=false
case "$MODE" in
    termux)
        if [ -d "$SERVICE_DIR" ]; then
            SERVICE_WAS_RUNNING=true
            log "stopping dugnad-agent (runit)"
            run "echo -n t > '$SERVICE_DIR/supervise/control' 2>/dev/null || true"
            sleep 1
        fi
        ;;
    system)
        if systemctl is-active --quiet dugnad-agent 2>/dev/null; then
            SERVICE_WAS_RUNNING=true
            log "stopping dugnad-agent (systemd)"
            run "systemctl stop dugnad-agent"
        fi
        ;;
    user)
        if systemctl --user is-active --quiet dugnad-agent 2>/dev/null; then
            SERVICE_WAS_RUNNING=true
            log "stopping dugnad-agent (systemd --user)"
            run "systemctl --user stop dugnad-agent"
        fi
        ;;
esac

run "mv '$TMP_BINARY' '$BINARY'"
log "binary installed: $(${DRY_RUN} && echo '<dry-run>' || "$BINARY" --version 2>/dev/null || echo 'installed')"

# ─── Install security profiles (system mode only; requires root for apparmor_parser) ─
# Profiles are installed to PROFILES_INSTALL_DIR so the agent can load AppArmor
# profiles via `apparmor_parser --replace` and resolve seccomp JSON paths at
# container-start time.  Termux (Android) and user-mode installs skip this —
# AppArmor profile loading requires root and is not available on Android.
install_security_profiles() {
    # Bundled profile filenames.  Extend this list when new per-app profiles are added.
    local profiles="vaultwarden.apparmor vaultwarden-seccomp.json"

    if $DRY_RUN; then
        run "mkdir -p '$PROFILES_INSTALL_DIR'"
        for f in $profiles; do
            run "curl -fSL --retry 3 --max-time 60 '$PROFILES_DOWNLOAD_BASE/$f' -o '$PROFILES_INSTALL_DIR/$f'"
        done
        return 0
    fi

    mkdir -p "$PROFILES_INSTALL_DIR" \
        || { warn "could not create profiles dir $PROFILES_INSTALL_DIR — skipping profile install"; return 0; }

    local failed=0
    for f in $profiles; do
        local dest="$PROFILES_INSTALL_DIR/$f"
        local tmp="$PROFILES_INSTALL_DIR/.${f}.new"
        if curl -fSL --retry 3 --max-time 60 "$PROFILES_DOWNLOAD_BASE/$f" -o "$tmp" 2>/dev/null; then
            mv "$tmp" "$dest"
            log "profile installed: $dest"
        else
            rm -f "$tmp"
            warn "failed to download security profile $f from $PROFILES_DOWNLOAD_BASE — \
container deployments requiring this profile will fail until it is installed"
            failed=1
        fi
    done
    return $failed
}

case "$MODE" in
    termux)
        log "skipping security profile install (not applicable on Android/Termux)"
        ;;
    system|user)
        install_security_profiles || warn "one or more security profiles could not be installed"
        ;;
esac

# ─── Write config (first install only) ─────────────────────────────────────
if [ ! -f "$CONFIG_FILE" ]; then
    NODE_LABEL="${LABEL:-$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo dugnad)}"
    log "writing $CONFIG_FILE"
    if $DRY_RUN; then
        echo "  # config content would include: coordinator_url, node_name=$NODE_LABEL, registration_token, log_dir"
    else
        # #138: seed bridge provisioning. manifest_url (when given) makes the
        # agent fetch + verify + full-replace the allowlist on every startup;
        # allowed_bridge_node_ids starts empty (fail-closed) and the agent
        # populates it from the signed manifest on first start.
        bridge_cfg="allowed_bridge_node_ids = []"
        [ -n "$MANIFEST_URL" ] && bridge_cfg="manifest_url = \"$MANIFEST_URL\"
allowed_bridge_node_ids = []"
        cat > "$CONFIG_FILE" <<EOF
[agent]
transport = "http"
coordinator_url = "$COORDINATOR"
heartbeat_interval_secs = 30
node_name = "$NODE_LABEL"
log_dir = "$LOG_DIR"
registration_token = "$TOKEN"
$bridge_cfg

[capabilities.health]
enabled = true
interval_secs = 60
EOF
        chmod 600 "$CONFIG_FILE"
    fi
else
    log "keeping existing config at $CONFIG_FILE"
fi

# ─── Write service unit ────────────────────────────────────────────────────
case "$MODE" in
    termux)
        log "installing runit service at $SERVICE_DIR"
        run "mkdir -p '$SERVICE_DIR' '$SERVICE_DIR/log'"
        if ! $DRY_RUN; then
            cat > "$SERVICE_DIR/run" <<EOF
#!/data/data/com.termux/files/usr/bin/sh
exec 2>&1
exec $BINARY $CONFIG_FILE
EOF
            chmod +x "$SERVICE_DIR/run"
            cat > "$SERVICE_DIR/log/run" <<EOF
#!/data/data/com.termux/files/usr/bin/sh
exec svlogd -tt $LOG_DIR
EOF
            chmod +x "$SERVICE_DIR/log/run"
        fi
        ;;
    system)
        log "installing systemd unit at $SERVICE_FILE"
        if ! $DRY_RUN; then
            cat > "$SERVICE_FILE" <<EOF
[Unit]
Description=Dugnad Agent — P2P job execution
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=$BINARY $CONFIG_FILE
Restart=always
RestartSec=5
Environment=RUST_LOG=info
Environment=DUGNAD_AGENT_COSIGN_BIN=$COSIGN_BINARY

[Install]
WantedBy=multi-user.target
EOF
        fi
        run "systemctl daemon-reload"
        run "systemctl enable dugnad-agent"
        ;;
    user)
        log "installing user systemd unit at $SERVICE_FILE"
        if ! $DRY_RUN; then
            cat > "$SERVICE_FILE" <<EOF
[Unit]
Description=Dugnad Agent — P2P job execution
After=network-online.target

[Service]
Type=simple
ExecStart=$BINARY $CONFIG_FILE
Restart=always
RestartSec=5
Environment=RUST_LOG=info
Environment=DUGNAD_AGENT_COSIGN_BIN=$COSIGN_BINARY

[Install]
WantedBy=default.target
EOF
        fi
        run "systemctl --user daemon-reload"
        run "systemctl --user enable dugnad-agent"
        if ! systemctl --user show-environment 2>/dev/null | grep -q XDG_RUNTIME_DIR; then
            warn "systemd --user may stop when you log out. Enable lingering with: sudo loginctl enable-linger $(id -un)"
        fi
        ;;
esac

# ─── Container runtime (opt-in via --with-container-runtime) ────────────────
# Installs Docker and wires the agent to reach it so container/stack apps become
# eligible on community-owned hardware. Detection (capabilities.rs) stays the
# source of truth — this flag is the consensual fast path, never a default.
# Runs BEFORE first agent start so the agent inherits the docker group on launch
# (the group-ordering gotcha — see dugnad-agent#118). NOT for Termux (Android has
# no Docker) and NOT for kernel < 6 (rootless userns unsupported, mirroring
# detect_docker_rootless_capable in capabilities.rs).
setup_container_runtime() {
    if [ "$MODE" = "termux" ]; then
        fail "--with-container-runtime is not supported on Termux/Android (no Docker)"
    fi

    # apt + daemon + usermod need root; non-root (user mode) goes through sudo.
    local SUDO=""
    if [ "$(id -u)" != "0" ]; then
        command -v sudo >/dev/null \
            || fail "--with-container-runtime needs root for Docker setup. Re-run as root or install sudo."
        SUDO="sudo"
    fi

    # Kernel gate — mirror detect_docker_rootless_capable (major >= 6).
    local kmajor; kmajor="$(uname -r | cut -d. -f1)"
    if [ "${kmajor:-0}" -lt 6 ]; then
        warn "kernel ${kmajor}.x < 6 — rootless container userns unsupported; skipping Docker setup."
        warn "  The agent continues without container capability."
        return 0
    fi

    if command -v docker >/dev/null 2>&1 && $SUDO docker info >/dev/null 2>&1; then
        log "docker already installed and reachable — skipping install"
    else
        command -v apt-get >/dev/null \
            || fail "--with-container-runtime supports apt-based distros only (needs docker.io). Install Docker manually, then re-run without the flag."
        log "installing docker.io (distro package)..."
        run "$SUDO apt-get update -qq"
        run "$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker.io"
        run "$SUDO systemctl enable --now docker"
    fi

    # Group dance is only meaningful in user mode. In system mode the agent runs
    # as root, which reaches the docker socket without group membership.
    if [ "$MODE" = "user" ]; then
        local agent_user agent_uid
        agent_user="$(id -un)"; agent_uid="$(id -u)"
        log "adding $agent_user to the docker group"
        run "$SUDO usermod -aG docker '$agent_user'"
        # The running user manager (user@UID.service) holds the pre-usermod group
        # set; a plain `systemctl --user restart` re-spawns the agent with STALE
        # groups and detection still fails. Restart the manager so the agent
        # inherits the docker group on its (re)start below (dugnad-agent#118).
        log "restarting user systemd manager so the new group is picked up"
        run "$SUDO systemctl restart user@$agent_uid.service"
        $DRY_RUN || sleep 2
    fi

    log "container runtime ready — container/stack apps are now eligible"
}

if $WITH_CONTAINER_RUNTIME; then
    setup_container_runtime
fi

# ─── Start service ─────────────────────────────────────────────────────────
case "$MODE" in
    termux)
        # runsvdir picks up new services within 5s of SVDIR scan
        log "starting dugnad-agent (runit will pick it up within 5s)"
        # Nudge runsvdir if not running
        if ! pgrep -x runsvdir >/dev/null 2>&1; then
            warn "runsvdir is not running — starting it"
            run "setsid runsvdir -P '$SVDIR' >/dev/null 2>&1 &"
        fi
        ;;
    system)
        run "systemctl start dugnad-agent"
        ;;
    user)
        run "systemctl --user start dugnad-agent"
        ;;
esac

# ─── Wait for first successful binding (first install only) ───────────────
# If the user passed --token, poll the agent's log for the `[registration] bound
# to user …` marker. Absence after N heartbeats means token invalid/expired/used.
wait_for_binding() {
    local timeout_secs="${1:-90}"
    local start; start=$(date +%s)
    local line=""
    while :; do
        case "$MODE" in
            termux)
                if [ -f "$LOG_DIR/current" ]; then
                    line=$(grep -F '[registration]' "$LOG_DIR/current" 2>/dev/null | tail -1)
                fi
                ;;
            system)
                line=$(journalctl -u dugnad-agent --since "2 minutes ago" --no-pager -o cat 2>/dev/null \
                        | grep -F '[registration]' | tail -1)
                ;;
            user)
                line=$(journalctl --user -u dugnad-agent --since "2 minutes ago" --no-pager -o cat 2>/dev/null \
                        | grep -F '[registration]' | tail -1)
                ;;
        esac
        if [ -n "$line" ]; then
            printf '%s\n' "$line"
            return 0
        fi
        local now; now=$(date +%s)
        if [ $((now - start)) -ge "$timeout_secs" ]; then
            return 1
        fi
        sleep 3
    done
}

if [ -n "$TOKEN" ] && ! $DRY_RUN; then
    log "waiting up to 90s for first heartbeat + registration..."
    if line=$(wait_for_binding 90); then
        if printf '%s' "$line" | grep -qF 'bound to user'; then
            log "registration: SUCCESS — $(printf '%s' "$line" | sed -n 's/.*\(\[registration\].*\)$/\1/p')"
        else
            warn "registration: $line"
        fi
    else
        warn "registration: no binding signal after 90s. Token may be invalid, expired, or already used."
        warn "  Check logs: $([ "$MODE" = "termux" ] && echo "tail -f $LOG_DIR/current" || \
                              [ "$MODE" = "system" ] && echo "journalctl -u dugnad-agent -f" || \
                              echo "journalctl --user -u dugnad-agent -f")"
        warn "  Re-run with a fresh token from the dashboard if needed."
    fi
fi

# ─── Verify bridge-allowlist provisioning (#138) ───────────────────────────
if { [ -n "$MANIFEST_URL" ] || config_has_manifest_url; } && ! $DRY_RUN && ! $UPDATE_ONLY; then
    log "verifying bridge-allowlist provisioning (agent fetches + verifies the signed manifest on start)..."
    if wait_for_bridges 25; then
        log "bridge allowlist: $(bridge_count_from_config) authorized bridge(s) — node is bridge-flip eligible"
    elif $REQUIRE_BRIDGES; then
        fail "--require-bridges: bridge allowlist empty after start — manifest unreachable or signature rejected. Check the agent log."
    else
        warn "bridge allowlist empty after start — node not yet bridge-flip eligible (manifest may be unpublished). The agent retries on every restart."
    fi
fi

# ─── Post-install summary ──────────────────────────────────────────────────
log "done. dugnad-agent is installed and starting."
case "$MODE" in
    termux) echo "    check status: sv status dugnad-agent"
            echo "    tail logs:    tail -f $LOG_DIR/current" ;;
    system) echo "    check status: systemctl status dugnad-agent"
            echo "    tail logs:    journalctl -u dugnad-agent -f" ;;
    user)   echo "    check status: systemctl --user status dugnad-agent"
            echo "    tail logs:    journalctl --user -u dugnad-agent -f" ;;
esac
echo "    coordinator:  $COORDINATOR"
