diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04ed3fa..2a7b49f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: lint: - runs-on: &default-runner blacksmith-4vcpu-ubuntu-2404 + runs-on: &default-runner ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 99b199d..7030aa3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -13,7 +13,7 @@ concurrency: jobs: docs-scope: - runs-on: &default-runner blacksmith-4vcpu-ubuntu-2404 + runs-on: &default-runner ubuntu-latest outputs: docs_only: ${{ steps.check.outputs.docs_only }} steps: diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 583a16f..4149793 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -146,6 +146,7 @@ Set during `setup.sh` / `baudbot install` via env vars: | Variable | Description | Default | |----------|-------------|---------| | `BAUDBOT_PI_VERSION` | pi package version installed for `baudbot_agent` | `0.52.12` | +| `BAUDBOT_RUNTIME_NODE_VERSION` | embedded Node.js version downloaded to `~/opt/node-v-linux-x64` (with stable symlink `~/opt/node`) | `22.14.0` | | `GIT_USER_NAME` | Git commit author name | `baudbot-agent` | | `GIT_USER_EMAIL` | Git commit author email | `baudbot-agent@users.noreply.github.com` | diff --git a/bin/baudbot b/bin/baudbot index 2b99f4b..73b88a1 100755 --- a/bin/baudbot +++ b/bin/baudbot @@ -29,6 +29,12 @@ if [ -z "${BAUDBOT_ROOT:-}" ]; then fi fi +RUNTIME_NODE_HELPER="$BAUDBOT_ROOT/bin/lib/runtime-node.sh" +if [ -f "$RUNTIME_NODE_HELPER" ]; then + # shellcheck source=bin/lib/runtime-node.sh + source "$RUNTIME_NODE_HELPER" +fi + json_get_string_or_empty() { local file="$1" local key="$2" @@ -193,7 +199,7 @@ resolve_node_bin() { return 0 fi - # 2) Common user-managed Node installs (sudo secure_path may hide these) + # 2) Embedded runtime (sudo secure_path may hide these) local user_home="" if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then user_home="$(resolve_user_home "$SUDO_USER" || true)" @@ -202,20 +208,26 @@ resolve_node_bin() { user_home="${HOME:-}" fi + if [ -n "${RUNTIME_NODE_HELPER:-}" ] && [ -f "$RUNTIME_NODE_HELPER" ]; then + local embedded_node="" + embedded_node="$(bb_resolve_runtime_node_bin "$user_home" || true)" + if [ -n "$embedded_node" ] && [ -x "$embedded_node" ]; then + echo "$embedded_node" + return 0 + fi + + embedded_node="$(bb_resolve_runtime_node_bin "/home/baudbot_agent" || true)" + if [ -n "$embedded_node" ] && [ -x "$embedded_node" ]; then + echo "$embedded_node" + return 0 + fi + fi + + # 3) Common user-managed Node installs local candidate="" for candidate in \ "$user_home/.local/share/mise/shims/node" \ - "$user_home/.local/bin/node" \ - "$user_home/opt/node-v22.14.0-linux-x64/bin/node" \ - /home/baudbot_agent/opt/node-v22.14.0-linux-x64/bin/node \ - "$user_home"/opt/node-v*-linux-x64/bin/node; do - # If the glob didn't expand, skip the literal pattern. - case "$candidate" in - *\**) - continue - ;; - esac - + "$user_home/.local/bin/node"; do if [ -x "$candidate" ]; then echo "$candidate" return 0 @@ -289,11 +301,14 @@ bootstrap_install() { echo "Escalating with $escalator for system setup..." if [ "$escalator" = "sudo" ]; then - sudo --preserve-env=BAUDBOT_PI_VERSION bash "$install_script" "$@" + sudo --preserve-env=BAUDBOT_PI_VERSION,BAUDBOT_RUNTIME_NODE_VERSION bash "$install_script" "$@" else # doas has no portable preserve-env flag; pass explicitly when set. - if [ -n "${BAUDBOT_PI_VERSION:-}" ]; then - doas env BAUDBOT_PI_VERSION="$BAUDBOT_PI_VERSION" bash "$install_script" "$@" + if [ -n "${BAUDBOT_PI_VERSION:-}" ] || [ -n "${BAUDBOT_RUNTIME_NODE_VERSION:-}" ]; then + doas env \ + BAUDBOT_PI_VERSION="${BAUDBOT_PI_VERSION:-}" \ + BAUDBOT_RUNTIME_NODE_VERSION="${BAUDBOT_RUNTIME_NODE_VERSION:-}" \ + bash "$install_script" "$@" else doas bash "$install_script" "$@" fi diff --git a/bin/baudbot.service b/bin/baudbot.service index 56c3e32..2aedb62 100644 --- a/bin/baudbot.service +++ b/bin/baudbot.service @@ -22,7 +22,7 @@ Restart=on-failure RestartSec=10 # Environment -Environment=PATH=/home/baudbot_agent/.varlock/bin:/home/baudbot_agent/opt/node-v22.14.0-linux-x64/bin:/usr/local/bin:/usr/bin:/bin +Environment=PATH=/home/baudbot_agent/.varlock/bin:/home/baudbot_agent/opt/node/bin:/usr/local/bin:/usr/bin:/bin Environment=HOME=/home/baudbot_agent # Security hardening diff --git a/bin/ci/setup-arch.sh b/bin/ci/setup-arch.sh index 03af311..09fdc7e 100755 --- a/bin/ci/setup-arch.sh +++ b/bin/ci/setup-arch.sh @@ -61,14 +61,14 @@ echo "$HELP_OUT" | grep -q "baudbot" # varlock installed for agent user test -x /home/baudbot_agent/.varlock/bin/varlock # Agent can load env (smoke test — varlock validates schema + .env) -sudo -u baudbot_agent bash -c 'export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH" && cd ~ && varlock load --path ~/.config/' +sudo -u baudbot_agent bash -c 'export PATH="$HOME/.varlock/bin:$HOME/opt/node/bin:$PATH" && cd ~ && varlock load --path ~/.config/' echo " ✓ bootstrap + install verification passed" echo "=== Running CLI smoke checks ===" bash /home/baudbot_admin/baudbot/bin/ci/smoke-cli.sh echo "=== Installing test dependencies ===" -export PATH="/home/baudbot_agent/opt/node-v22.14.0-linux-x64/bin:$PATH" +export PATH="/home/baudbot_agent/opt/node/bin:$PATH" cd /home/baudbot_admin/baudbot npm install --ignore-scripts 2>&1 | tail -1 cd slack-bridge && npm install 2>&1 | tail -1 diff --git a/bin/ci/setup-ubuntu.sh b/bin/ci/setup-ubuntu.sh index 3cfd72a..1f9b2df 100755 --- a/bin/ci/setup-ubuntu.sh +++ b/bin/ci/setup-ubuntu.sh @@ -99,14 +99,14 @@ echo "$HELP_OUT" | grep -q "baudbot" # varlock installed for agent user test -x /home/baudbot_agent/.varlock/bin/varlock # Agent can load env (smoke test — varlock validates schema + .env) -sudo -u baudbot_agent bash -c 'export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH" && cd ~ && varlock load --path ~/.config/' +sudo -u baudbot_agent bash -c 'export PATH="$HOME/.varlock/bin:$HOME/opt/node/bin:$PATH" && cd ~ && varlock load --path ~/.config/' echo " ✓ bootstrap + install verification passed" echo "=== Running CLI smoke checks ===" bash /home/baudbot_admin/baudbot/bin/ci/smoke-cli.sh echo "=== Installing test dependencies ===" -export PATH="/home/baudbot_agent/opt/node-v22.14.0-linux-x64/bin:$PATH" +export PATH="/home/baudbot_agent/opt/node/bin:$PATH" cd /home/baudbot_admin/baudbot npm install --ignore-scripts 2>&1 | tail -1 cd slack-bridge && npm install 2>&1 | tail -1 diff --git a/bin/deploy.sh b/bin/deploy.sh index f335794..10de7b5 100755 --- a/bin/deploy.sh +++ b/bin/deploy.sh @@ -82,9 +82,11 @@ if [ "$DRY_RUN" -eq 0 ]; then cp -r --no-preserve=ownership "$BAUDBOT_SRC/pi/skills" "$STAGE_DIR/skills" cp --no-preserve=ownership "$BAUDBOT_SRC/start.sh" "$STAGE_DIR/start.sh" mkdir -p "$STAGE_DIR/bin" + mkdir -p "$STAGE_DIR/bin/lib" for script in harden-permissions.sh redact-logs.sh prune-session-logs.sh; do [ -f "$BAUDBOT_SRC/bin/$script" ] && cp --no-preserve=ownership "$BAUDBOT_SRC/bin/$script" "$STAGE_DIR/bin/$script" done + [ -f "$BAUDBOT_SRC/bin/lib/runtime-node.sh" ] && cp --no-preserve=ownership "$BAUDBOT_SRC/bin/lib/runtime-node.sh" "$STAGE_DIR/bin/lib/runtime-node.sh" [ -f "$BAUDBOT_SRC/pi/settings.json" ] && cp --no-preserve=ownership "$BAUDBOT_SRC/pi/settings.json" "$STAGE_DIR/settings.json" [ -f "$BAUDBOT_SRC/.env.schema" ] && cp --no-preserve=ownership "$BAUDBOT_SRC/.env.schema" "$STAGE_DIR/.env.schema" chmod -R a+rX "$STAGE_DIR" @@ -245,6 +247,7 @@ echo "Deploying runtime scripts..." if [ "$DRY_RUN" -eq 0 ]; then as_agent mkdir -p "$BAUDBOT_HOME/runtime/bin" + as_agent mkdir -p "$BAUDBOT_HOME/runtime/bin/lib" for script in harden-permissions.sh redact-logs.sh prune-session-logs.sh; do if [ -f "$STAGE_DIR/bin/$script" ]; then @@ -254,6 +257,12 @@ if [ "$DRY_RUN" -eq 0 ]; then fi done + if [ -f "$STAGE_DIR/bin/lib/runtime-node.sh" ]; then + as_agent cp "$STAGE_DIR/bin/lib/runtime-node.sh" "$BAUDBOT_HOME/runtime/bin/lib/runtime-node.sh" + as_agent chmod u+r "$BAUDBOT_HOME/runtime/bin/lib/runtime-node.sh" + log "✓ bin/lib/runtime-node.sh" + fi + as_agent cp "$STAGE_DIR/start.sh" "$BAUDBOT_HOME/runtime/start.sh" as_agent chmod u+x "$BAUDBOT_HOME/runtime/start.sh" log "✓ start.sh" diff --git a/bin/doctor.sh b/bin/doctor.sh index 7705fb4..c4fdb67 100755 --- a/bin/doctor.sh +++ b/bin/doctor.sh @@ -11,9 +11,13 @@ source "$SCRIPT_DIR/lib/shell-common.sh" source "$SCRIPT_DIR/lib/paths-common.sh" # shellcheck source=bin/lib/doctor-common.sh source "$SCRIPT_DIR/lib/doctor-common.sh" +# shellcheck source=bin/lib/runtime-node.sh +source "$SCRIPT_DIR/lib/runtime-node.sh" bb_enable_strict_mode bb_init_paths +BAUDBOT_ROOT="${BAUDBOT_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}" + for arg in "$@"; do case "$arg" in -h|--help) @@ -55,21 +59,31 @@ fi echo "" echo "Dependencies:" -NODE_BIN="$BAUDBOT_HOME/opt/node-v22.14.0-linux-x64/bin/node" -if [ -x "$NODE_BIN" ]; then +NODE_BIN="$(bb_resolve_runtime_node_bin "$BAUDBOT_HOME" || true)" +if [ -n "$NODE_BIN" ] && [ -x "$NODE_BIN" ]; then NODE_VER=$("$NODE_BIN" --version 2>/dev/null || echo "unknown") - pass "Node.js $NODE_VER" + pass "Node.js $NODE_VER ($NODE_BIN)" else - fail "Node.js not found at $NODE_BIN" + NODE_BIN="$(bb_runtime_node_bin_dir "$BAUDBOT_HOME")/node" + fail "Node.js not found (expected: $NODE_BIN)" fi -PI_BIN="$BAUDBOT_HOME/opt/node-v22.14.0-linux-x64/bin/pi" +PI_BIN="$(bb_resolve_runtime_node_bin_dir "$BAUDBOT_HOME")/pi" if [ -x "$PI_BIN" ] || [ -L "$PI_BIN" ]; then pass "pi is installed" else fail "pi not found at $PI_BIN" fi +if [ -n "${BAUDBOT_ROOT:-}" ] && command -v rg &>/dev/null; then + NODE_PATH_DRIFT="$(rg -n --glob '!node_modules/**' --glob '!.git/**' 'node-v[0-9]+\.[0-9]+\.[0-9]+-linux-x64' "$BAUDBOT_ROOT" || true)" + if [ -n "$NODE_PATH_DRIFT" ]; then + fail "hardcoded versioned Node paths found (run test: bin/runtime-node-paths.test.sh)" + else + pass "runtime Node path references are centralized" + fi +fi + if command -v varlock &>/dev/null || [ -x "$BAUDBOT_HOME/.varlock/bin/varlock" ]; then pass "varlock is installed" if [ -f "$BAUDBOT_HOME/.varlock/config.json" ] && grep -q '"anonymousId"' "$BAUDBOT_HOME/.varlock/config.json"; then diff --git a/bin/lib/baudbot-runtime.sh b/bin/lib/baudbot-runtime.sh index 4ba3cb8..f45a20f 100644 --- a/bin/lib/baudbot-runtime.sh +++ b/bin/lib/baudbot-runtime.sh @@ -1,6 +1,10 @@ #!/bin/bash # Runtime/status/session helpers for bin/baudbot. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" +# shellcheck source=bin/lib/runtime-node.sh +source "$SCRIPT_DIR/runtime-node.sh" + # Detect systemd has_systemd() { command -v systemctl &>/dev/null && [ -d /run/systemd/system ] @@ -434,7 +438,9 @@ cmd_attach() { echo -e " ${GREEN}Agent keeps running under systemd in the background.${RESET}" echo "" pause_before_attach - exec sudo -u "$AGENT_USER" bash -lc "export PATH='$AGENT_HOME/.varlock/bin:$AGENT_HOME/opt/node-v22.14.0-linux-x64/bin':\$PATH; cd ~; varlock run --path ~/.config/ -- pi --session '$pi_target'" + local node_bin_dir="" + node_bin_dir="$(bb_resolve_runtime_node_bin_dir "$AGENT_HOME")" + exec sudo -u "$AGENT_USER" bash -lc "export PATH='$AGENT_HOME/.varlock/bin:$node_bin_dir':\$PATH; cd ~; varlock run --path ~/.config/ -- pi --session '$pi_target'" } choose_tmux_target() { diff --git a/bin/lib/runtime-node.sh b/bin/lib/runtime-node.sh new file mode 100644 index 0000000..c4df0dc --- /dev/null +++ b/bin/lib/runtime-node.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# Shared embedded Node runtime helpers. + +# Canonical embedded Node version used by setup/install unless overridden. +: "${BAUDBOT_RUNTIME_NODE_VERSION_DEFAULT:=22.14.0}" + +bb_runtime_node_version() { + echo "${BAUDBOT_RUNTIME_NODE_VERSION:-$BAUDBOT_RUNTIME_NODE_VERSION_DEFAULT}" +} + +bb_runtime_node_versioned_dir() { + local home_dir="${1:?home directory required}" + echo "$home_dir/opt/node-v$(bb_runtime_node_version)-linux-x64" +} + +bb_runtime_node_bin_dir() { + local home_dir="${1:?home directory required}" + + if [ -n "${BAUDBOT_RUNTIME_NODE_BIN_DIR:-}" ]; then + echo "$BAUDBOT_RUNTIME_NODE_BIN_DIR" + return 0 + fi + + if [ -n "${BAUDBOT_RUNTIME_NODE_DIR:-}" ]; then + echo "$BAUDBOT_RUNTIME_NODE_DIR/bin" + return 0 + fi + + echo "$home_dir/opt/node/bin" +} + +bb_resolve_runtime_node_bin() { + local home_dir="${1:-${HOME:-}}" + local candidate="" + + [ -n "$home_dir" ] || return 1 + + for candidate in \ + "${BAUDBOT_RUNTIME_NODE_BIN:-}" \ + "$(bb_runtime_node_bin_dir "$home_dir")/node" \ + "$(bb_runtime_node_versioned_dir "$home_dir")/bin/node" \ + "$home_dir/opt/node-v"*-linux-x64/bin/node; do + [ -n "$candidate" ] || continue + + # If the glob didn't expand, skip the literal pattern. + case "$candidate" in + *\**) + continue + ;; + esac + + if [ -x "$candidate" ]; then + echo "$candidate" + return 0 + fi + done + + return 1 +} + +bb_resolve_runtime_node_bin_dir() { + local home_dir="${1:-${HOME:-}}" + local node_bin="" + + if node_bin="$(bb_resolve_runtime_node_bin "$home_dir")"; then + dirname "$node_bin" + return 0 + fi + + bb_runtime_node_bin_dir "$home_dir" +} diff --git a/bin/runtime-node-paths.test.sh b/bin/runtime-node-paths.test.sh new file mode 100644 index 0000000..dfb632d --- /dev/null +++ b/bin/runtime-node-paths.test.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Guardrail: embedded Node versioned paths must not be hardcoded. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +echo "=== runtime node path drift check ===" + +if command -v rg >/dev/null 2>&1; then + matches="$(rg -n --glob '!node_modules/**' --glob '!.git/**' 'node-v[0-9]+\.[0-9]+\.[0-9]+-linux-x64' . || true)" +else + matches="$(grep -RInE --exclude-dir=node_modules --exclude-dir=.git 'node-v[0-9]+\.[0-9]+\.[0-9]+-linux-x64' . || true)" +fi +if [ -n "$matches" ]; then + echo "❌ Found hardcoded versioned Node paths:" + echo "$matches" | sed 's/^/ /' + exit 1 +fi + +echo "✅ No hardcoded versioned Node paths found" diff --git a/bin/security-audit.sh b/bin/security-audit.sh index e414b8a..bbe4cc6 100755 --- a/bin/security-audit.sh +++ b/bin/security-audit.sh @@ -14,6 +14,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 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/runtime-node.sh +source "$SCRIPT_DIR/lib/runtime-node.sh" bb_enable_strict_mode bb_init_paths @@ -625,7 +627,7 @@ done # Deep scan: cross-pattern analysis via Node scanner (deployed copies) if [ "$DEEP" -eq 1 ]; then - NODE_BIN="$BAUDBOT_HOME/opt/node-v22.14.0-linux-x64/bin/node" + NODE_BIN="$(bb_resolve_runtime_node_bin "$BAUDBOT_HOME" || true)" # Try source scanner first, fall back to deployed copy SCANNER="" [ -f "$BAUDBOT_SRC/bin/scan-extensions.mjs" ] && [ -r "$BAUDBOT_SRC/bin/scan-extensions.mjs" ] && SCANNER="$BAUDBOT_SRC/bin/scan-extensions.mjs" diff --git a/install.sh b/install.sh index 91a4260..a37baa1 100755 --- a/install.sh +++ b/install.sh @@ -232,9 +232,11 @@ info "This takes 1–2 minutes." echo "" if [ "$EXPERIMENTAL" -eq 1 ]; then - BAUDBOT_PI_VERSION="${BAUDBOT_PI_VERSION:-}" bash "$REPO_DIR/setup.sh" --experimental "$ADMIN_USER" + BAUDBOT_PI_VERSION="${BAUDBOT_PI_VERSION:-}" BAUDBOT_RUNTIME_NODE_VERSION="${BAUDBOT_RUNTIME_NODE_VERSION:-}" \ + bash "$REPO_DIR/setup.sh" --experimental "$ADMIN_USER" else - BAUDBOT_PI_VERSION="${BAUDBOT_PI_VERSION:-}" bash "$REPO_DIR/setup.sh" "$ADMIN_USER" + BAUDBOT_PI_VERSION="${BAUDBOT_PI_VERSION:-}" BAUDBOT_RUNTIME_NODE_VERSION="${BAUDBOT_RUNTIME_NODE_VERSION:-}" \ + bash "$REPO_DIR/setup.sh" "$ADMIN_USER" fi echo "" diff --git a/pi/skills/control-agent/SKILL.md b/pi/skills/control-agent/SKILL.md index cd4bc3f..352f1ec 100644 --- a/pi/skills/control-agent/SKILL.md +++ b/pi/skills/control-agent/SKILL.md @@ -200,7 +200,7 @@ git worktree add ~/workspace/worktrees/$BRANCH -b $BRANCH origin/main # 2. Launch the agent IN the worktree tmux new-session -d -s $SESSION_NAME \ "cd ~/workspace/worktrees/$BRANCH && \ - export PATH=\$HOME/.varlock/bin:\$HOME/opt/node-v22.14.0-linux-x64/bin:\$PATH && \ + export PATH=\$HOME/.varlock/bin:\$HOME/opt/node/bin:\$PATH && \ export PI_SESSION_NAME=$SESSION_NAME && \ exec varlock run --path ~/.config/ -- pi --session-control --skill ~/.pi/agent/skills/dev-agent --model " ``` @@ -330,7 +330,7 @@ The sentry-agent triages Sentry alerts and investigates critical issues via the | `OPENCODE_ZEN_API_KEY` | `opencode-zen/claude-haiku-4-5` | ```bash -tmux new-session -d -s sentry-agent "export PATH=\$HOME/.varlock/bin:\$HOME/opt/node-v22.14.0-linux-x64/bin:\$PATH && export PI_SESSION_NAME=sentry-agent && varlock run --path ~/.config/ -- pi --session-control --skill ~/.pi/agent/skills/sentry-agent --model " +tmux new-session -d -s sentry-agent "export PATH=\$HOME/.varlock/bin:\$HOME/opt/node/bin:\$PATH && export PI_SESSION_NAME=sentry-agent && varlock run --path ~/.config/ -- pi --session-control --skill ~/.pi/agent/skills/sentry-agent --model " ``` **Model note**: `github-copilot/*` models reject Personal Access Tokens and will fail in non-interactive sessions. diff --git a/pi/skills/control-agent/startup-cleanup.sh b/pi/skills/control-agent/startup-cleanup.sh index 81a456d..664a28d 100755 --- a/pi/skills/control-agent/startup-cleanup.sh +++ b/pi/skills/control-agent/startup-cleanup.sh @@ -133,7 +133,7 @@ echo "Starting slack-bridge ($BRIDGE_SCRIPT) with PI_SESSION_ID=$MY_UUID..." mkdir -p "$BRIDGE_LOG_DIR" ( unset PKG_EXECPATH - export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH" + export PATH="$HOME/.varlock/bin:$HOME/opt/node/bin:$PATH" export PI_SESSION_ID="$MY_UUID" cd /opt/baudbot/current/slack-bridge while true; do diff --git a/setup.sh b/setup.sh index 725572f..9cbad15 100755 --- a/setup.sh +++ b/setup.sh @@ -61,8 +61,12 @@ fi BAUDBOT_HOME="/home/baudbot_agent" # Source repo auto-detected from this script's location (can live anywhere) REPO_DIR="$(cd "$(dirname "$0")" && pwd)" -NODE_VERSION="22.14.0" +# shellcheck source=bin/lib/runtime-node.sh +source "$REPO_DIR/bin/lib/runtime-node.sh" +NODE_VERSION="$(bb_runtime_node_version)" PI_VERSION="${BAUDBOT_PI_VERSION:-0.52.12}" +NODE_VERSIONED_DIR="$BAUDBOT_HOME/opt/node-v$NODE_VERSION-linux-x64" +NODE_BIN_DIR="$(bb_runtime_node_bin_dir "$BAUDBOT_HOME")" # Work from a neutral directory — sudo -u baudbot_agent inherits CWD, and # git/find fail if CWD is a directory the agent can't access (e.g. /root). @@ -110,7 +114,7 @@ else fi echo "=== Installing Node.js $NODE_VERSION ===" -if [ ! -d "$BAUDBOT_HOME/opt/node-v$NODE_VERSION-linux-x64" ]; then +if [ ! -d "$NODE_VERSIONED_DIR" ]; then sudo -u baudbot_agent bash -c " mkdir -p ~/opt curl -fsSL https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz -o /tmp/node.tar.xz @@ -121,9 +125,10 @@ else echo "Node.js already installed, skipping" fi +sudo -u baudbot_agent bash -c "ln -sfn \"node-v$NODE_VERSION-linux-x64\" ~/opt/node" + echo "=== Installing pi $PI_VERSION ===" -NODE_BIN="$BAUDBOT_HOME/opt/node-v$NODE_VERSION-linux-x64/bin" -sudo -u baudbot_agent env PATH="$NODE_BIN:$PATH" \ +sudo -u baudbot_agent env PATH="$NODE_BIN_DIR:$PATH" \ npm install -g "@mariozechner/pi-coding-agent@$PI_VERSION" echo "=== Configuring git identity ===" @@ -156,8 +161,8 @@ for repo in "$BAUDBOT_HOME"/workspace/*/; do done echo "=== Adding PATH to bashrc ===" -if ! grep -q "node-v$NODE_VERSION" "$BAUDBOT_HOME/.bashrc"; then - sudo -u baudbot_agent bash -c "echo 'export PATH=\$HOME/opt/node-v$NODE_VERSION-linux-x64/bin:\$PATH' >> ~/.bashrc" +if ! grep -q "\$HOME/opt/node/bin" "$BAUDBOT_HOME/.bashrc"; then + sudo -u baudbot_agent bash -c "echo 'export PATH=\$HOME/opt/node/bin:\$PATH' >> ~/.bashrc" fi echo "=== Setting up secrets directory ===" @@ -229,8 +234,7 @@ sudo -u baudbot_agent bash -c ' echo "=== Installing extension dependencies ===" # npm install runs in source (admin-owned) then deploy copies to runtime -NODE_BIN="$BAUDBOT_HOME/opt/node-v$NODE_VERSION-linux-x64/bin" -export PATH="$NODE_BIN:$PATH" +export PATH="$NODE_BIN_DIR:$PATH" while IFS= read -r dir; do ext_name="$(basename "$dir")" if [ "$EXPERIMENTAL" -ne 1 ] && { [ "$ext_name" = "agentmail" ] || [ "$ext_name" = "email-monitor" ]; }; then diff --git a/start.sh b/start.sh index a0ad82c..e64558e 100755 --- a/start.sh +++ b/start.sh @@ -11,10 +11,15 @@ # To update, admin edits source and runs deploy.sh. set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=bin/lib/runtime-node.sh +source "$SCRIPT_DIR/bin/lib/runtime-node.sh" cd ~ +NODE_BIN_DIR="$(bb_resolve_runtime_node_bin_dir "$HOME")" + # Set PATH -export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH" +export PATH="$HOME/.varlock/bin:$NODE_BIN_DIR:$PATH" # Work around varlock telemetry config crash by opting out at runtime. # This avoids loading anonymousId from user config and keeps startup deterministic. @@ -102,7 +107,7 @@ if [ -n "$BRIDGE_SCRIPT" ]; then echo "Starting Slack bridge ($BRIDGE_SCRIPT)... logs: $BRIDGE_LOG_FILE" ( - export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH" + export PATH="$HOME/.varlock/bin:$NODE_BIN_DIR:$PATH" cd "$RELEASE_BRIDGE" while true; do varlock run --path ~/.config/ -- node "$BRIDGE_SCRIPT" >>"$BRIDGE_LOG_FILE" 2>&1 diff --git a/test/shell-scripts.test.mjs b/test/shell-scripts.test.mjs index 2081249..03eefd2 100644 --- a/test/shell-scripts.test.mjs +++ b/test/shell-scripts.test.mjs @@ -47,4 +47,8 @@ describe("shell script test suites", () => { expect(() => runScript("bin/baudbot.test.sh")).not.toThrow(); }); + it("runtime node path drift check", () => { + expect(() => runScript("bin/runtime-node-paths.test.sh")).not.toThrow(); + }); + });