#!/bin/bash
# Install or repair Watcher for the logged-in user.
# Iru runs this remediation script as root.

set -euo pipefail

readonly INSTALL_URL="https://github.com/ApolloResearch/watcher-bin/releases/latest/download/install.sh"

# This example uses WorkOS SSO with a self-hosted Watcher deployment.
readonly WATCHER_API_URL="https://watcher.example.com"
readonly WATCHER_AUTH_MODE="workos"

# Keep this port in sync with the WorkOS callback URL and the audit script.
readonly WATCHER_PORT="8228"

_log() { printf '[watcher-remediate] %s\n' "$*"; }
_die() { printf '[watcher-remediate] ERROR: %s\n' "$*" >&2; exit 1; }

# Set WATCHER_MDM_USER to test a per-user installation without root access.
_require_root() {
    [ -n "${WATCHER_MDM_USER:-}" ] && return 0
    [ "$(id -u)" -eq 0 ] || _die "must run as root (Iru runs scripts as root)"
}

# The logged-in user, or empty when nobody is (e.g. at enrollment).
_console_user() {
    local u
    u="$(/usr/bin/stat -f%Su /dev/console 2>/dev/null || true)"
    case "$u" in
        ""|root|loginwindow|_mbsetupuser|_securityagent) printf '' ;;
        *) printf '%s' "$u" ;;
    esac
}

_user_home() {
    /usr/bin/dscl . -read "/Users/$1" NFSHomeDirectory 2>/dev/null | awk '{print $2}'
}

# Drop to the target user when we're root; run directly when we already are that
# user (the WATCHER_MDM_USER test path). -H so per-user commands see the user's
# HOME and write into their ~/.local, ~/.claude, and ~/Library/LaunchAgents.
_run_as_user() {
    local user="$1"; shift
    if [ "$(id -u)" -eq 0 ]; then
        /usr/bin/sudo -u "$user" -H "$@"
    else
        "$@"
    fi
}

_run_with_watcher_env() {
    local user="$1"
    shift
    local header_var
    local env_args=(
        "WATCHER_AUTH_MODE=$WATCHER_AUTH_MODE"
        "WATCHER_BACKEND_PORT=$WATCHER_PORT"
    )
    for header_var in "${!WATCHER_HEADER_@}"; do
        [ -n "${!header_var}" ] || continue
        env_args+=("$header_var=${!header_var}")
    done
    _run_as_user "$user" /usr/bin/env "${env_args[@]}" "$@"
}

_install_fresh() {
    local user="$1"
    _log "running Watcher's installer as '$user' from $INSTALL_URL"
    # $0 and $1 are expanded by the inner bash (it receives INSTALL_URL and the
    # Watcher API URL as its first two args), not by this outer shell, so the
    # single quotes are deliberate.
    # shellcheck disable=SC2016
    _run_with_watcher_env "$user" \
        /bin/bash -c 'set -o pipefail; curl -fsSL "$0" | bash -s -- --watcher-api-url "$1"' "$INSTALL_URL" "$WATCHER_API_URL" \
        || _die "Watcher installer failed for '$user'"
}

_install_if_missing() {
    local user="$1" binary="$2"
    if [ ! -x "$binary" ]; then
        _install_fresh "$user"
    fi
}

_repair_hooks() {
    local user="$1" binary="$2"
    _log "repairing Watcher hooks for '$user'"
    _run_with_watcher_env "$user" "$binary" hooks install --yes \
        || _die "Watcher hook setup failed for '$user'"
}

_repair_hooks_if_needed() {
    local user="$1" binary="$2"
    if _run_with_watcher_env "$user" "$binary" doctor hooks >/dev/null 2>&1; then
        return
    fi
    _repair_hooks "$user" "$binary"
}

_write_launcher() {
    local user="$1" binary="$2" launcher="$3"
    # launchd does not preserve these environment variables, so the wrapper
    # exports them before starting Watcher. The backend URL is already stored in
    # the launch-args file.
    _log "writing the login-agent launcher to $launcher"
    # The target user owns this directory, so create and publish the launcher
    # entirely as that user.
    _run_as_user "$user" /bin/bash -s -- \
        "$WATCHER_AUTH_MODE" "$WATCHER_PORT" "$binary" "$launcher" <<'USER_SCRIPT'
set -euo pipefail

auth_mode="$1"
watcher_port="$2"
binary="$3"
launcher="$4"
temp_launcher="$(mktemp "${launcher}.tmp.XXXXXX")"
trap 'rm -f "$temp_launcher"' EXIT

{
        cat <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
EOF
        printf '\nexport WATCHER_AUTH_MODE=%q\n' "$auth_mode"
        printf 'export WATCHER_BACKEND_PORT=%q\n' "$watcher_port"
        printf 'WATCHER_BIN=%q\n' "$binary"
        cat <<'EOF'

# Do not start Watcher at login when start-on-login is disabled.
if [ "${1:-}" = "--autostart" ]; then
    "$WATCHER_BIN" autostart enabled || exit 0
fi

exec "$WATCHER_BIN"
EOF
} > "$temp_launcher"
chmod 0755 "$temp_launcher"
mv -f "$temp_launcher" "$launcher"
trap - EXIT
USER_SCRIPT
}

_setup_autostart() {
    local user="$1" binary="$2" launcher="$3"
    # These idempotent commands install the LaunchAgent, enable start-on-login,
    # and start Watcher in the current session.
    _log "registering Watcher's login autostart for '$user'"
    _run_as_user "$user" "$binary" autostart install --launcher "$launcher"
    _run_as_user "$user" "$binary" autostart enable
    _run_as_user "$user" "$binary" autostart start
}

_wait_for_health() {
    local home="$1"
    # Log startup failures without failing installation. A later audit will
    # request remediation if Watcher remains unhealthy.
    _log "waiting for the Watcher to answer on localhost:${WATCHER_PORT}"
    for _ in $(seq 1 120); do
        if /usr/bin/curl -fsS -o /dev/null "http://localhost:${WATCHER_PORT}/api/health" 2>/dev/null; then
            _log "Watcher is up: UI + API on http://localhost:${WATCHER_PORT}"
            return 0
        fi
        sleep 1
    done
    _log "WARNING: Watcher did not answer within 120s; recent agent log output:"
    tail -n 20 "$home/.apollo_monitor/logs/autostart.out.log" \
               "$home/.apollo_monitor/logs/autostart.err.log" 2>/dev/null || true
    return 0
}

main() {
    _require_root

    local user
    user="${WATCHER_MDM_USER:-$(_console_user)}"

    # A per-user installation must wait until someone logs in.
    if [ -z "$user" ]; then
        _log "no console user yet; install will run on the next enforce after login"
        _log "done"
        return 0
    fi

    local home
    home="$(_user_home "$user")"
    [ -n "$home" ] || _die "could not resolve home directory for '$user'"

    # Store the launcher beside the runtime directory so binary updates do not replace it.
    local lib_dir="$home/.local/share/apollo-watcher"
    local binary="$lib_dir/watcher/watcher"
    local launcher="$lib_dir/watcher-autostart.sh"

    _install_if_missing "$user" "$binary"
    [ -x "$binary" ] || _die "installer did not produce an executable at $binary"
    _repair_hooks_if_needed "$user" "$binary"
    _write_launcher "$user" "$binary" "$launcher"
    _setup_autostart "$user" "$binary" "$launcher"
    _wait_for_health "$home"
    _log "done"
}

main "$@"
