#!/usr/bin/env bash # bAgents Desktop — instalador del runner. # # Instala el cliente que ejecuta en ESTE host los comandos que bAgents aprobó. # No decide nada: la política (allow-list deny-by-default + aprobación humana # para lo destructivo) vive en el servidor. # # SOPORTE POR SISTEMA # Linux (systemd: Ubuntu/Debian/RHEL…) → usuario de servicio dedicado + systemd. # Requiere sudo. # macOS → LaunchAgent bajo TU usuario (no root). # NO uses sudo. # Windows → ver README (tarea programada). Este # script no lo cubre. # # curl -fsSL https://bagents.buckler.pro/api/desktop/install.sh \ # | BAGENTS_RUNNER_TOKEN=rk_... sudo -E bash # Linux # curl -fsSL .../install.sh | BAGENTS_RUNNER_TOKEN=rk_... bash # macOS set -euo pipefail BAGENTS_URL="${BAGENTS_URL:-https://bagents.buckler.pro}" OS="$(uname -s)" # El token es OPCIONAL: sin él, el runner arranca el flujo de conexión (RFC # 8628) y pide que un humano autorice el equipo en el portal. El secreto nunca # pasa por el portapapeles ni por el historial del shell. command -v python3 >/dev/null || { echo "ERROR: se requiere python3." >&2; exit 1; } # ───────────────────────────────────────────────────────────────────────── # Linux — usuario de servicio dedicado + systemd # ───────────────────────────────────────────────────────────────────────── install_linux() { local RUNNER_USER="bagents-runner" local INSTALL_DIR="/opt/bagents" local CONF_DIR="/etc/bagents" if [[ "${EUID}" -ne 0 ]]; then echo "En Linux este instalador necesita sudo (crea un usuario de servicio y una unidad systemd)." >&2 echo "Ejecutalo con: ... | BAGENTS_RUNNER_TOKEN=rk_... sudo -E bash" >&2 exit 1 fi echo "→ usuario de servicio de MÍNIMO PRIVILEGIO (no root, sin shell de login)" id -u "${RUNNER_USER}" >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin "${RUNNER_USER}" echo "→ instalando el runner en ${INSTALL_DIR}" mkdir -p "${INSTALL_DIR}" "${CONF_DIR}" curl -fsSL "${BAGENTS_URL}/api/desktop/runner.py" -o "${INSTALL_DIR}/bagents_runner.py" chown -R "${RUNNER_USER}" "${INSTALL_DIR}" echo "→ credencial del host (solo legible por el runner)" if [[ -n "${BAGENTS_RUNNER_TOKEN:-}" ]]; then printf 'BAGENTS_URL=%s\nBAGENTS_RUNNER_TOKEN=%s\n' "${BAGENTS_URL}" "${BAGENTS_RUNNER_TOKEN}" > "${CONF_DIR}/runner.env" else printf 'BAGENTS_URL=%s\nBAGENTS_TOKEN_FILE=%s/token\n' "${BAGENTS_URL}" "${CONF_DIR}" > "${CONF_DIR}/runner.env" # Conexión INTERACTIVA acá, antes del servicio: si la hiciera el servicio, # el código saldría por el journal y nadie lo vería. Además el servicio # corre con /etc en solo-lectura, así que no podría guardar el token. echo "→ conectando este equipo con bAgents" if ! BAGENTS_URL="${BAGENTS_URL}" BAGENTS_TOKEN_FILE="${CONF_DIR}/token" \ python3 "${INSTALL_DIR}/bagents_runner.py" --connect; then echo "ERROR: no se completó la conexión. Volvé a correr el instalador." >&2 exit 1 fi chmod 600 "${CONF_DIR}/token" chown "${RUNNER_USER}" "${CONF_DIR}/token" fi chmod 600 "${CONF_DIR}/runner.env" chown "${RUNNER_USER}" "${CONF_DIR}/runner.env" if ! command -v systemctl >/dev/null; then echo echo "✔ Runner instalado en ${INSTALL_DIR} (este Linux no usa systemd)." echo " Correlo con:" echo " sudo -u ${RUNNER_USER} env \$(cat ${CONF_DIR}/runner.env | xargs) python3 ${INSTALL_DIR}/bagents_runner.py" return fi echo "→ servicio systemd (endurecido)" cat > /etc/systemd/system/bagents-runner.service <&2 echo "Ejecutalo con: ... | BAGENTS_RUNNER_TOKEN=rk_... bash" >&2 exit 1 fi local INSTALL_DIR="${HOME}/.bagents" local PLIST="${HOME}/Library/LaunchAgents/pro.buckler.bagents-runner.plist" local PY PY="$(command -v python3)" echo "→ instalando el runner en ${INSTALL_DIR}" mkdir -p "${INSTALL_DIR}" "${HOME}/Library/LaunchAgents" curl -fsSL "${BAGENTS_URL}/api/desktop/runner.py" -o "${INSTALL_DIR}/bagents_runner.py" echo "→ credencial del host (solo legible por vos)" printf 'BAGENTS_URL=%s\nBAGENTS_RUNNER_TOKEN=%s\n' "${BAGENTS_URL}" "${BAGENTS_RUNNER_TOKEN}" > "${INSTALL_DIR}/runner.env" chmod 600 "${INSTALL_DIR}/runner.env" echo "→ LaunchAgent (arranca con tu sesión)" cat > "${PLIST}" < Labelpro.buckler.bagents-runner ProgramArguments ${PY} ${INSTALL_DIR}/bagents_runner.py EnvironmentVariables BAGENTS_URL${BAGENTS_URL} BAGENTS_RUNNER_TOKEN${BAGENTS_RUNNER_TOKEN} RunAtLoad KeepAlive StandardOutPath${INSTALL_DIR}/runner.log StandardErrorPath${INSTALL_DIR}/runner.err PLIST chmod 600 "${PLIST}" launchctl unload "${PLIST}" 2>/dev/null || true launchctl load "${PLIST}" echo echo "✔ bAgents Desktop instalado y corriendo (launchd, bajo tu usuario)." echo " Logs: tail -f ${INSTALL_DIR}/runner.log" echo " Parar: launchctl unload ${PLIST}" } case "${OS}" in Linux) install_linux ;; Darwin) install_macos ;; *) echo "ERROR: sistema no soportado por este instalador: ${OS}" >&2 echo "Windows: ver la sección de Windows en el README (tarea programada)." >&2 exit 1 ;; esac