diff --git a/AGENTS.md b/AGENTS.md index c420faf..02cddc6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,7 @@ bin/ security & operations scripts setup-arch.sh Arch Linux droplet: prereqs + setup + tests lib/ shared shell helpers sourced by CLI/release scripts shell-common.sh strict mode + shared logging/error/root-check helpers + paths-common.sh shared path constants (bb_init_paths, bb_refresh_release_paths) release-common.sh shared update/rollback helpers deploy-common.sh deploy/runtime helper functions doctor-common.sh doctor status/check formatting helpers @@ -181,7 +182,7 @@ Add new test files to `vitest.config.mjs` (and shell wrappers under `test/` as n - Security functions must be pure, testable modules (no side effects, no env vars at module scope). - All security code must have tests before merging. - Run `bin/security-audit.sh --deep` after any security-relevant changes. -- Keep shell CLIs thin: move reusable logic to `bin/lib/*.sh`, and source shared helpers (`shell-common.sh`, `release-common.sh`, `deploy-common.sh`, `doctor-common.sh`) instead of duplicating logging/error/root-check patterns. +- Keep shell CLIs thin: move reusable logic to `bin/lib/*.sh`, and source shared helpers (`shell-common.sh`, `paths-common.sh`, `release-common.sh`, `deploy-common.sh`, `doctor-common.sh`) instead of duplicating logging/error/root-check patterns. - For shell scripts, standardize on `bb_enable_strict_mode` and shared helper functions (`bb_log`, `bb_die`, etc.) rather than ad-hoc wrappers. - Protected files (`tool-guard.ts`, `security.mjs`, their tests) are deployed read-only. The agent cannot modify them at runtime. - New integrations get their own subdirectory (e.g. `discord-bridge/`). diff --git a/bin/deploy.sh b/bin/deploy.sh index 4c31dc7..bd4f5bc 100755 --- a/bin/deploy.sh +++ b/bin/deploy.sh @@ -15,18 +15,21 @@ # Auto-detect source repo from this script's location BAUDBOT_SRC="${BAUDBOT_SRC:-$(cd "$(dirname "$0")/.." && pwd)}" -BAUDBOT_HOME="${BAUDBOT_HOME:-/home/baudbot_agent}" -AGENT_USER="baudbot_agent" DRY_RUN=0 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck source=bin/lib/shell-common.sh source "$SCRIPT_DIR/lib/shell-common.sh" +# shellcheck source=bin/lib/paths-common.sh +source "$SCRIPT_DIR/lib/paths-common.sh" # shellcheck source=bin/lib/json-common.sh source "$SCRIPT_DIR/lib/json-common.sh" # shellcheck source=bin/lib/deploy-common.sh source "$SCRIPT_DIR/lib/deploy-common.sh" bb_enable_strict_mode +bb_init_paths + +AGENT_USER="${AGENT_USER:-$BAUDBOT_AGENT_USER}" # Helper: run a command as baudbot_agent as_agent() { diff --git a/bin/doctor.sh b/bin/doctor.sh index a8a0dee..50473a6 100755 --- a/bin/doctor.sh +++ b/bin/doctor.sh @@ -7,9 +7,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck source=bin/lib/shell-common.sh source "$SCRIPT_DIR/lib/shell-common.sh" +# shellcheck source=bin/lib/paths-common.sh +source "$SCRIPT_DIR/lib/paths-common.sh" # shellcheck source=bin/lib/doctor-common.sh source "$SCRIPT_DIR/lib/doctor-common.sh" bb_enable_strict_mode +bb_init_paths for arg in "$@"; do case "$arg" in @@ -20,7 +23,6 @@ for arg in "$@"; do esac done -BAUDBOT_HOME="/home/baudbot_agent" doctor_init_counters IS_ROOT=0 if [ "$(id -u)" -eq 0 ]; then @@ -42,10 +44,10 @@ fi # ── User ───────────────────────────────────────────────────────────────────── echo "User:" -if id baudbot_agent &>/dev/null; then - pass "baudbot_agent user exists" +if id "$BAUDBOT_AGENT_USER" &>/dev/null; then + pass "$BAUDBOT_AGENT_USER user exists" else - fail "baudbot_agent user does not exist (run: baudbot setup)" + fail "$BAUDBOT_AGENT_USER user does not exist (run: baudbot setup)" fi # ── Dependencies ───────────────────────────────────────────────────────────── @@ -90,10 +92,10 @@ else fi if command -v gh &>/dev/null; then - if sudo -u baudbot_agent gh auth status &>/dev/null; then + if sudo -u "$BAUDBOT_AGENT_USER" gh auth status &>/dev/null; then pass "gh cli authenticated" else - warn "gh cli installed but not authenticated (run: sudo -u baudbot_agent gh auth login)" + warn "gh cli installed but not authenticated (run: sudo -u $BAUDBOT_AGENT_USER gh auth login)" fi else fail "gh cli not found" @@ -136,10 +138,10 @@ if [ -f "$ENV_FILE" ]; then else fail ".env has $PERMS permissions (should be 600)" fi - if [ "$OWNER" = "baudbot_agent" ]; then - pass ".env owned by baudbot_agent" + if [ "$OWNER" = "$BAUDBOT_AGENT_USER" ]; then + pass ".env owned by $BAUDBOT_AGENT_USER" else - fail ".env owned by $OWNER (should be baudbot_agent)" + fail ".env owned by $OWNER (should be $BAUDBOT_AGENT_USER)" fi # LLM key validation: require at least one valid key, and flag malformed configured keys. @@ -369,7 +371,7 @@ if bb_has_systemd; then fi else # No systemd — check for pi process - if pgrep -u baudbot_agent -f "pi --session-control" &>/dev/null; then + if pgrep -u "$BAUDBOT_AGENT_USER" -f "pi --session-control" &>/dev/null; then pass "agent is running (direct mode)" else warn "agent is not running" diff --git a/bin/lib/paths-common.sh b/bin/lib/paths-common.sh new file mode 100644 index 0000000..92afa54 --- /dev/null +++ b/bin/lib/paths-common.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# Shared path constants for baudbot shell scripts. + +# shellcheck disable=SC2120 # Optional args are used by callers in other scripts. +bb_refresh_release_paths() { + local release_root="${BAUDBOT_RELEASE_ROOT:-/opt/baudbot}" + local force="0" + + if [ "$#" -ge 1 ]; then + release_root="$1" + fi + if [ "$#" -ge 2 ]; then + force="$2" + fi + + BAUDBOT_RELEASE_ROOT="$release_root" + + if [ "$force" = "1" ]; then + BAUDBOT_RELEASES_DIR="$BAUDBOT_RELEASE_ROOT/releases" + BAUDBOT_CURRENT_LINK="$BAUDBOT_RELEASE_ROOT/current" + BAUDBOT_PREVIOUS_LINK="$BAUDBOT_RELEASE_ROOT/previous" + BAUDBOT_SOURCE_URL_FILE="$BAUDBOT_RELEASE_ROOT/source.url" + BAUDBOT_SOURCE_BRANCH_FILE="$BAUDBOT_RELEASE_ROOT/source.branch" + else + : "${BAUDBOT_RELEASES_DIR:=$BAUDBOT_RELEASE_ROOT/releases}" + : "${BAUDBOT_CURRENT_LINK:=$BAUDBOT_RELEASE_ROOT/current}" + : "${BAUDBOT_PREVIOUS_LINK:=$BAUDBOT_RELEASE_ROOT/previous}" + : "${BAUDBOT_SOURCE_URL_FILE:=$BAUDBOT_RELEASE_ROOT/source.url}" + : "${BAUDBOT_SOURCE_BRANCH_FILE:=$BAUDBOT_RELEASE_ROOT/source.branch}" + fi +} + +bb_init_paths() { + : "${BAUDBOT_AGENT_USER:=baudbot_agent}" + + if [ -n "${BAUDBOT_HOME:-}" ] && [ -z "${BAUDBOT_AGENT_HOME:-}" ]; then + BAUDBOT_AGENT_HOME="$BAUDBOT_HOME" + fi + + if [ -z "${BAUDBOT_AGENT_HOME:-}" ]; then + BAUDBOT_AGENT_HOME="$(bb_resolve_user_home "$BAUDBOT_AGENT_USER" 2>/dev/null || true)" + fi + : "${BAUDBOT_AGENT_HOME:=/home/$BAUDBOT_AGENT_USER}" + + if [ -z "${BAUDBOT_HOME:-}" ]; then + BAUDBOT_HOME="$BAUDBOT_AGENT_HOME" + fi + + : "${BAUDBOT_RUNTIME_DIR:=$BAUDBOT_AGENT_HOME/runtime}" + : "${BAUDBOT_PI_DIR:=$BAUDBOT_AGENT_HOME/.pi}" + : "${BAUDBOT_AGENT_DIR:=$BAUDBOT_PI_DIR/agent}" + : "${BAUDBOT_AGENT_EXT_DIR:=$BAUDBOT_AGENT_DIR/extensions}" + : "${BAUDBOT_AGENT_SKILLS_DIR:=$BAUDBOT_AGENT_DIR/skills}" + : "${BAUDBOT_AGENT_SETTINGS_FILE:=$BAUDBOT_AGENT_DIR/settings.json}" + : "${BAUDBOT_VERSION_FILE:=$BAUDBOT_AGENT_DIR/baudbot-version.json}" + : "${BAUDBOT_MANIFEST_FILE:=$BAUDBOT_AGENT_DIR/baudbot-manifest.json}" + : "${BAUDBOT_ENV_FILE:=$BAUDBOT_AGENT_HOME/.config/.env}" + + bb_refresh_release_paths + + export BAUDBOT_AGENT_USER BAUDBOT_AGENT_HOME BAUDBOT_HOME + export BAUDBOT_RUNTIME_DIR BAUDBOT_PI_DIR BAUDBOT_AGENT_DIR + export BAUDBOT_AGENT_EXT_DIR BAUDBOT_AGENT_SKILLS_DIR BAUDBOT_AGENT_SETTINGS_FILE + export BAUDBOT_VERSION_FILE BAUDBOT_MANIFEST_FILE BAUDBOT_ENV_FILE + export BAUDBOT_RELEASE_ROOT BAUDBOT_RELEASES_DIR BAUDBOT_CURRENT_LINK BAUDBOT_PREVIOUS_LINK + export BAUDBOT_SOURCE_URL_FILE BAUDBOT_SOURCE_BRANCH_FILE +} diff --git a/bin/rollback-release.sh b/bin/rollback-release.sh index ba743c0..64f6c6d 100755 --- a/bin/rollback-release.sh +++ b/bin/rollback-release.sh @@ -8,12 +8,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=bin/lib/shell-common.sh source "$SCRIPT_DIR/lib/shell-common.sh" +# shellcheck source=bin/lib/paths-common.sh +source "$SCRIPT_DIR/lib/paths-common.sh" bb_enable_strict_mode -BAUDBOT_RELEASE_ROOT="${BAUDBOT_RELEASE_ROOT:-/opt/baudbot}" -BAUDBOT_RELEASES_DIR="${BAUDBOT_RELEASES_DIR:-$BAUDBOT_RELEASE_ROOT/releases}" -BAUDBOT_CURRENT_LINK="${BAUDBOT_CURRENT_LINK:-$BAUDBOT_RELEASE_ROOT/current}" -BAUDBOT_PREVIOUS_LINK="${BAUDBOT_PREVIOUS_LINK:-$BAUDBOT_RELEASE_ROOT/previous}" +bb_init_paths BAUDBOT_ROLLBACK_DEPLOY_CMD="${BAUDBOT_ROLLBACK_DEPLOY_CMD:-}" BAUDBOT_ROLLBACK_RESTART_CMD="${BAUDBOT_ROLLBACK_RESTART_CMD:-}" @@ -24,9 +23,6 @@ BAUDBOT_ROLLBACK_SKIP_VERSION_CHECK="${BAUDBOT_ROLLBACK_SKIP_VERSION_CHECK:-0}" BAUDBOT_ROLLBACK_SKIP_CLI_LINK="${BAUDBOT_ROLLBACK_SKIP_CLI_LINK:-0}" BAUDBOT_ROLLBACK_ALLOW_NON_ROOT="${BAUDBOT_ROLLBACK_ALLOW_NON_ROOT:-0}" -BAUDBOT_AGENT_USER="${BAUDBOT_AGENT_USER:-baudbot_agent}" -BAUDBOT_AGENT_HOME="${BAUDBOT_AGENT_HOME:-/home/baudbot_agent}" - log() { bb_log "$1"; } die() { bb_die "$1"; } @@ -50,10 +46,7 @@ while [ "$#" -gt 0 ]; do case "$1" in --release-root) [ "$#" -ge 2 ] || die "--release-root requires a value" - BAUDBOT_RELEASE_ROOT="$2" - BAUDBOT_RELEASES_DIR="$BAUDBOT_RELEASE_ROOT/releases" - BAUDBOT_CURRENT_LINK="$BAUDBOT_RELEASE_ROOT/current" - BAUDBOT_PREVIOUS_LINK="$BAUDBOT_RELEASE_ROOT/previous" + bb_refresh_release_paths "$2" 1 shift 2 ;; --skip-restart) diff --git a/bin/scan-extensions.test.mjs b/bin/scan-extensions.test.mjs index 83d3d24..e11e055 100644 --- a/bin/scan-extensions.test.mjs +++ b/bin/scan-extensions.test.mjs @@ -316,7 +316,7 @@ describe("scan-extensions: new rules", () => { await writeFile(join(dir, "bad.js"), ` const mod = require(userInput); `); - const { output, exitCode } = runScanner(dir); + const { output } = runScanner(dir); // info severity = exit 0, but should appear in output assert.ok(output.includes("dynamic") || output.includes("Dynamic") || output.includes("require"), output); }); diff --git a/bin/security-audit.sh b/bin/security-audit.sh index 8a459bc..dfa583b 100755 --- a/bin/security-audit.sh +++ b/bin/security-audit.sh @@ -12,9 +12,11 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck source=bin/lib/shell-common.sh source "$SCRIPT_DIR/lib/shell-common.sh" +# shellcheck source=bin/lib/paths-common.sh +source "$SCRIPT_DIR/lib/paths-common.sh" bb_enable_strict_mode +bb_init_paths -BAUDBOT_HOME="${BAUDBOT_HOME:-/home/baudbot_agent}" # Source repo — auto-detect from this script's location, or use env override BAUDBOT_SRC="${BAUDBOT_SRC:-$(cd "$SCRIPT_DIR/.." && pwd)}" @@ -97,12 +99,12 @@ echo "" # ── Docker group ───────────────────────────────────────────────────────────── echo "Docker Access" -if id baudbot_agent 2>/dev/null | grep -q '(docker)'; then - finding "CRITICAL" "baudbot_agent is in docker group" \ +if id "$BAUDBOT_AGENT_USER" 2>/dev/null | grep -q '(docker)'; then + finding "CRITICAL" "$BAUDBOT_AGENT_USER is in docker group" \ "Can bypass baudbot-docker wrapper via /usr/bin/docker directly" - fix_skip "Remove from docker group" "Requires root: sudo gpasswd -d baudbot_agent docker" + fix_skip "Remove from docker group" "Requires root: sudo gpasswd -d $BAUDBOT_AGENT_USER docker" else - ok "baudbot_agent not in docker group" + ok "$BAUDBOT_AGENT_USER not in docker group" fi if [ -f /usr/local/bin/baudbot-docker ]; then @@ -191,7 +193,7 @@ echo "Source Isolation & Integrity" # Source repo lives outside agent's home — agent should not be able to read it if [ -r "$BAUDBOT_SRC/setup.sh" ] 2>/dev/null; then # If we're running as admin, this is expected — check agent can't - agent_can_read=$(sudo -u baudbot_agent test -r "$BAUDBOT_SRC/setup.sh" 2>/dev/null && echo "yes" || echo "no") + agent_can_read=$(sudo -u "$BAUDBOT_AGENT_USER" test -r "$BAUDBOT_SRC/setup.sh" 2>/dev/null && echo "yes" || echo "no") if [ "$agent_can_read" = "yes" ]; then finding "WARN" "Agent can read source repo at $BAUDBOT_SRC" \ "Ensure admin home is 700: chmod 700 $(dirname "$BAUDBOT_SRC")" @@ -566,10 +568,10 @@ echo "" echo "Extension & Skill Safety" # Check pi extensions for suspicious patterns (deployed copies only) -AGENT_USER="${BAUDBOT_AGENT_USER:-baudbot_agent}" +AGENT_USER="$BAUDBOT_AGENT_USER" suspicious_extension_patterns="(eval\s*\(|new\s+Function\s*\(|child_process|execSync|execFile|spawn\s*\(|writeFileSync.*\/etc|writeFileSync.*\/home\/(?!${AGENT_USER}))" ext_dirs=( - "$BAUDBOT_HOME/.pi/agent/extensions" + "$BAUDBOT_AGENT_EXT_DIR" ) ext_findings=0 for ext_dir in "${ext_dirs[@]}"; do @@ -588,7 +590,7 @@ fi # Check skills for dangerous tool instructions (deployed copies only) skill_dirs=( - "$BAUDBOT_HOME/.pi/agent/skills" + "$BAUDBOT_AGENT_SKILLS_DIR" ) skill_findings=0 for skill_dir in "${skill_dirs[@]}"; do diff --git a/bin/setup-firewall.sh b/bin/setup-firewall.sh index 43782dc..cce14b8 100755 --- a/bin/setup-firewall.sh +++ b/bin/setup-firewall.sh @@ -16,22 +16,25 @@ # - Bind to ports (no inbound listeners/backdoors) # - Do DNS tunneling over non-53 UDP -set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=bin/lib/shell-common.sh +source "$SCRIPT_DIR/lib/shell-common.sh" +# shellcheck source=bin/lib/paths-common.sh +source "$SCRIPT_DIR/lib/paths-common.sh" +bb_enable_strict_mode +bb_init_paths -if [ "$(id -u)" -ne 0 ]; then - echo "❌ Must run as root (sudo $0)" - exit 1 -fi +bb_require_root "setup-firewall" -UID_BAUDBOT=$(id -u baudbot_agent 2>/dev/null) +UID_BAUDBOT=$(id -u "$BAUDBOT_AGENT_USER" 2>/dev/null) if [ -z "$UID_BAUDBOT" ]; then - echo "❌ baudbot_agent user not found" + echo "❌ $BAUDBOT_AGENT_USER user not found" exit 1 fi CHAIN="BAUDBOT_OUTPUT" -echo "🔒 Setting up firewall rules for baudbot_agent (uid $UID_BAUDBOT)..." +echo "🔒 Setting up firewall rules for $BAUDBOT_AGENT_USER (uid $UID_BAUDBOT)..." # Clean up any existing rules first iptables -w -D OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN" 2>/dev/null || true diff --git a/bin/uninstall.sh b/bin/uninstall.sh index ce7ac5c..75d1c9b 100755 --- a/bin/uninstall.sh +++ b/bin/uninstall.sh @@ -4,14 +4,24 @@ # Run as root: sudo ~/baudbot/bin/uninstall.sh # # Flags: -# --keep-home Remove user but preserve /home/baudbot_agent +# --keep-home Remove user but preserve agent home directory # --dry-run Print what would be done without doing it # --yes Skip confirmation prompt # # ⚠️ Keep this in sync with setup.sh — if you add something to setup, # add the reverse here. -set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=bin/lib/shell-common.sh +source "$SCRIPT_DIR/lib/shell-common.sh" +# shellcheck source=bin/lib/paths-common.sh +source "$SCRIPT_DIR/lib/paths-common.sh" +bb_enable_strict_mode +bb_init_paths + +AGENT_USER="$BAUDBOT_AGENT_USER" +AGENT_HOME="$BAUDBOT_AGENT_HOME" +RELEASE_ROOT="$BAUDBOT_RELEASE_ROOT" KEEP_HOME=false DRY_RUN=false @@ -25,7 +35,7 @@ for arg in "$@"; do -h|--help) echo "Usage: sudo $0 [--keep-home] [--dry-run] [--yes]" echo "" - echo " --keep-home Remove user but preserve /home/baudbot_agent" + echo " --keep-home Remove user but preserve $AGENT_HOME" echo " --dry-run Print what would be done without doing it" echo " --yes Skip confirmation prompt" exit 0 @@ -37,12 +47,9 @@ for arg in "$@"; do esac done -if [ "$(id -u)" -ne 0 ]; then - echo "❌ Must run as root (sudo $0)" - exit 1 -fi +bb_require_root "uninstall" -REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" REMOVED=() SKIPPED=() @@ -60,11 +67,11 @@ skipped() { SKIPPED+=("$1"); } # ── Confirmation ───────────────────────────────────────────────────────────── if ! $AUTO_YES && ! $DRY_RUN; then - echo "⚠️ This will remove the baudbot_agent user and all system-level changes." + echo "⚠️ This will remove the $AGENT_USER user and all system-level changes." if $KEEP_HOME; then - echo " (--keep-home: /home/baudbot_agent will be preserved)" + echo " (--keep-home: $AGENT_HOME will be preserved)" else - echo " ⚠️ /home/baudbot_agent will be DELETED (use --keep-home to preserve)" + echo " ⚠️ $AGENT_HOME will be DELETED (use --keep-home to preserve)" fi echo "" read -rp "Continue? [y/N] " confirm @@ -80,18 +87,18 @@ if $DRY_RUN; then echo "" fi -# ── 1. Kill all baudbot_agent processes ─────────────────────────────────────── +# ── 1. Kill all agent processes ───────────────────────────────────────────── -echo "=== Killing baudbot_agent processes ===" -if id baudbot_agent &>/dev/null; then - if pgrep -u baudbot_agent &>/dev/null; then - run pkill -u baudbot_agent || true +echo "=== Killing $AGENT_USER processes ===" +if id "$AGENT_USER" &>/dev/null; then + if pgrep -u "$AGENT_USER" &>/dev/null; then + run pkill -u "$AGENT_USER" || true sleep 1 # Force kill stragglers - if pgrep -u baudbot_agent &>/dev/null; then - run pkill -9 -u baudbot_agent || true + if pgrep -u "$AGENT_USER" &>/dev/null; then + run pkill -9 -u "$AGENT_USER" || true fi - removed "baudbot_agent processes" + removed "$AGENT_USER processes" else skipped "processes (none running)" fi @@ -121,8 +128,8 @@ fi # ── 3. Flush iptables rules ───────────────────────────────────────────────── echo "=== Removing iptables rules ===" -if id baudbot_agent &>/dev/null; then - UID_BAUDBOT=$(id -u baudbot_agent) +if id "$AGENT_USER" &>/dev/null; then + UID_BAUDBOT=$(id -u "$AGENT_USER") if iptables -w -L BAUDBOT_OUTPUT -n &>/dev/null 2>&1; then run iptables -w -D OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j BAUDBOT_OUTPUT 2>/dev/null || true run iptables -w -F BAUDBOT_OUTPUT @@ -196,12 +203,12 @@ else skipped "/usr/local/bin/baudbot (not found)" fi -echo "=== Removing /opt release snapshots ===" -if [ -d /opt/baudbot ]; then - run rm -rf /opt/baudbot - removed "/opt/baudbot releases" +echo "=== Removing release snapshots ===" +if [ -d "$RELEASE_ROOT" ]; then + run rm -rf "$RELEASE_ROOT" + removed "$RELEASE_ROOT releases" else - skipped "/opt/baudbot (not found)" + skipped "$RELEASE_ROOT (not found)" fi echo "=== Removing sudoers ===" @@ -228,7 +235,7 @@ done echo "=== Removing procview group ===" if getent group procview &>/dev/null; then - # Check if anyone else is in the group besides baudbot_agent + # Check if anyone else is in the group besides the agent user members=$(getent group procview | cut -d: -f4) if [ -n "$members" ]; then echo " ⚠️ procview group has members: $members" @@ -256,19 +263,19 @@ else skipped "pre-commit hook (not found)" fi -# ── 9. Remove baudbot_agent user + home ─────────────────────────────────────── +# ── 9. Remove agent user + home ───────────────────────────────────────────── -echo "=== Removing baudbot_agent user ===" -if id baudbot_agent &>/dev/null; then +echo "=== Removing $AGENT_USER user ===" +if id "$AGENT_USER" &>/dev/null; then if $KEEP_HOME; then - run userdel baudbot_agent - removed "baudbot_agent user (home preserved)" + run userdel "$AGENT_USER" + removed "$AGENT_USER user (home preserved)" else - run userdel -r baudbot_agent - removed "baudbot_agent user + /home/baudbot_agent" + run userdel -r "$AGENT_USER" + removed "$AGENT_USER user + $AGENT_HOME" fi else - skipped "baudbot_agent user (doesn't exist)" + skipped "$AGENT_USER user (doesn't exist)" fi # ── Summary ────────────────────────────────────────────────────────────────── @@ -301,11 +308,11 @@ if $DRY_RUN; then else echo "✅ Uninstall complete." if $KEEP_HOME; then - echo " /home/baudbot_agent was preserved. Remove manually when ready." + echo " $AGENT_HOME was preserved. Remove manually when ready." fi echo "" echo "Remaining manual steps:" - echo " - Remove admin user from baudbot_agent group: gpasswd -d baudbot_agent" + echo " - Remove admin user from $AGENT_USER group: gpasswd -d $AGENT_USER" echo " (or log out and back in — group was deleted)" echo " - The source repo ($REPO_DIR) was not removed." fi diff --git a/bin/update-release.sh b/bin/update-release.sh index 8a6a917..7c30ab8 100755 --- a/bin/update-release.sh +++ b/bin/update-release.sh @@ -13,17 +13,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=bin/lib/shell-common.sh source "$SCRIPT_DIR/lib/shell-common.sh" +# shellcheck source=bin/lib/paths-common.sh +source "$SCRIPT_DIR/lib/paths-common.sh" bb_enable_strict_mode BAUDBOT_ROOT="${BAUDBOT_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}" +bb_init_paths -BAUDBOT_RELEASE_ROOT="${BAUDBOT_RELEASE_ROOT:-/opt/baudbot}" -BAUDBOT_RELEASES_DIR="${BAUDBOT_RELEASES_DIR:-$BAUDBOT_RELEASE_ROOT/releases}" -BAUDBOT_CURRENT_LINK="${BAUDBOT_CURRENT_LINK:-$BAUDBOT_RELEASE_ROOT/current}" -BAUDBOT_PREVIOUS_LINK="${BAUDBOT_PREVIOUS_LINK:-$BAUDBOT_RELEASE_ROOT/previous}" - -SOURCE_URL_FILE="$BAUDBOT_RELEASE_ROOT/source.url" -SOURCE_BRANCH_FILE="$BAUDBOT_RELEASE_ROOT/source.branch" +SOURCE_URL_FILE="$BAUDBOT_SOURCE_URL_FILE" +SOURCE_BRANCH_FILE="$BAUDBOT_SOURCE_BRANCH_FILE" BAUDBOT_UPDATE_TMP_PARENT="${BAUDBOT_UPDATE_TMP_PARENT:-/tmp}" BAUDBOT_UPDATE_REPO="${BAUDBOT_UPDATE_REPO:-}" @@ -41,9 +39,6 @@ BAUDBOT_UPDATE_SKIP_VERSION_CHECK="${BAUDBOT_UPDATE_SKIP_VERSION_CHECK:-0}" BAUDBOT_UPDATE_SKIP_CLI_LINK="${BAUDBOT_UPDATE_SKIP_CLI_LINK:-0}" BAUDBOT_UPDATE_ALLOW_NON_ROOT="${BAUDBOT_UPDATE_ALLOW_NON_ROOT:-0}" -BAUDBOT_AGENT_USER="${BAUDBOT_AGENT_USER:-baudbot_agent}" -BAUDBOT_AGENT_HOME="${BAUDBOT_AGENT_HOME:-/home/baudbot_agent}" - CHECKOUT_DIR="" STAGING_DIR="" TARGET_SHA="" @@ -103,12 +98,9 @@ while [ "$#" -gt 0 ]; do ;; --release-root) [ "$#" -ge 2 ] || die "--release-root requires a value" - BAUDBOT_RELEASE_ROOT="$2" - BAUDBOT_RELEASES_DIR="$BAUDBOT_RELEASE_ROOT/releases" - BAUDBOT_CURRENT_LINK="$BAUDBOT_RELEASE_ROOT/current" - BAUDBOT_PREVIOUS_LINK="$BAUDBOT_RELEASE_ROOT/previous" - SOURCE_URL_FILE="$BAUDBOT_RELEASE_ROOT/source.url" - SOURCE_BRANCH_FILE="$BAUDBOT_RELEASE_ROOT/source.branch" + bb_refresh_release_paths "$2" 1 + SOURCE_URL_FILE="$BAUDBOT_SOURCE_URL_FILE" + SOURCE_BRANCH_FILE="$BAUDBOT_SOURCE_BRANCH_FILE" shift 2 ;; --skip-preflight) diff --git a/pi/extensions/heartbeat.test.mjs b/pi/extensions/heartbeat.test.mjs index 789bb7c..157100b 100644 --- a/pi/extensions/heartbeat.test.mjs +++ b/pi/extensions/heartbeat.test.mjs @@ -60,7 +60,7 @@ function readHeartbeatFile(filepath) { function computeBackoffMs(consecutiveErrors, baseInterval) { if (consecutiveErrors <= 0) return baseInterval; - const backoff = baseInterval * Math.pow(BACKOFF_MULTIPLIER, consecutiveErrors); + const backoff = baseInterval * BACKOFF_MULTIPLIER ** consecutiveErrors; return Math.min(backoff, MAX_BACKOFF_MS); } diff --git a/pi/extensions/heartbeat.ts b/pi/extensions/heartbeat.ts index c9b5316..5be613e 100644 --- a/pi/extensions/heartbeat.ts +++ b/pi/extensions/heartbeat.ts @@ -18,7 +18,7 @@ * checklist that the agent evaluates on each tick. */ -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { StringEnum } from "@mariozechner/pi-ai"; import { existsSync, readFileSync } from "node:fs"; @@ -31,8 +31,7 @@ const DEFAULT_HEARTBEAT_FILE = join(homedir(), ".pi", "agent", "HEARTBEAT.md"); // Minimum interval to prevent accidental token burn (2 minutes) const MIN_INTERVAL_MS = 2 * 60 * 1000; -// Maximum consecutive errors before backing off -const MAX_CONSECUTIVE_ERRORS = 5; +// Exponential backoff after errors const BACKOFF_MULTIPLIER = 2; const MAX_BACKOFF_MS = 60 * 60 * 1000; // 1 hour @@ -84,13 +83,13 @@ function readHeartbeatFile(filepath: string): string | null { function computeBackoffMs(consecutiveErrors: number, baseInterval: number): number { if (consecutiveErrors <= 0) return baseInterval; - const backoff = baseInterval * Math.pow(BACKOFF_MULTIPLIER, consecutiveErrors); + const backoff = baseInterval * BACKOFF_MULTIPLIER ** consecutiveErrors; return Math.min(backoff, MAX_BACKOFF_MS); } export default function heartbeatExtension(pi: ExtensionAPI): void { let timer: ReturnType | null = null; - let state: HeartbeatState = { + const state: HeartbeatState = { enabled: true, intervalMs: DEFAULT_INTERVAL_MS, heartbeatFile: DEFAULT_HEARTBEAT_FILE, @@ -160,7 +159,7 @@ export default function heartbeatExtension(pi: ExtensionAPI): void { // Success — reset error counter state.consecutiveErrors = 0; saveState(); - } catch (err) { + } catch { // Increment error counter for backoff — never let the heartbeat die state.consecutiveErrors += 1; try { diff --git a/pi/extensions/memory.test.mjs b/pi/extensions/memory.test.mjs index 0a1fee7..b944d9c 100644 --- a/pi/extensions/memory.test.mjs +++ b/pi/extensions/memory.test.mjs @@ -13,7 +13,6 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; -import { execSync } from "node:child_process"; // ── Paths ─────────────────────────────────────────────────────────────────── diff --git a/slack-bridge/broker-bridge.mjs b/slack-bridge/broker-bridge.mjs index bff9f13..41f8f2a 100755 --- a/slack-bridge/broker-bridge.mjs +++ b/slack-bridge/broker-bridge.mjs @@ -23,7 +23,6 @@ import { createRateLimiter, } from "./security.mjs"; import { - stableStringify, canonicalizeEnvelope, canonicalizeOutbound, canonicalizeSendRequest, @@ -534,7 +533,7 @@ async function say(channel, text, threadTs) { } } -async function react(channel, threadTs, emoji) { +async function _react(channel, threadTs, emoji) { if (outboundMode === "direct") { const params = { channel,