From 462c64540285b90f1f88aaa868ef83412d5b1291 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Thu, 26 Feb 2026 23:26:24 -0500 Subject: [PATCH 1/5] deploy: resolve npm via agent's embedded runtime in update-release update-release.sh runs as root (sudo baudbot update) but the embedded Node/npm runtime is installed under baudbot_agent's home directory and is not on root's PATH. The previous code fell back to bare `npm` which violates the project rule against bare node/npm in root scripts and fails on systems where only the agent user has Node installed. Replace the inline fallback with a resolve_npm_bin() helper that tries: 1. Agent user's embedded runtime (/home/baudbot_agent/opt/node/bin) 2. SUDO_USER's home (mise, nvm, etc.) 3. Standard PATH (last resort) Dies with a clear error if npm cannot be found at all. Adds tests for the resolution logic (success + failure paths). --- bin/update-release.sh | 60 +++++++++++++++++++++---- bin/update-release.test.sh | 91 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 8 deletions(-) diff --git a/bin/update-release.sh b/bin/update-release.sh index 6def89e..529ca85 100755 --- a/bin/update-release.sh +++ b/bin/update-release.sh @@ -55,6 +55,53 @@ source "$SCRIPT_DIR/lib/release-runtime-common.sh" # shellcheck source=bin/lib/json-common.sh source "$SCRIPT_DIR/lib/json-common.sh" +# --------------------------------------------------------------------------- +# Resolve the full path to npm. This script runs as root (sudo) where the +# embedded Node runtime is *not* on PATH. Resolution order: +# 1. Agent user's embedded runtime (/home/baudbot_agent/opt/node/bin/npm) +# 2. SUDO_USER's home (admin may have mise/nvm/etc.) +# 3. Standard PATH lookup (last resort) +# --------------------------------------------------------------------------- +resolve_npm_bin() { + local candidate="" + + # 1) Agent's embedded runtime + local agent_home="/home/${BAUDBOT_AGENT_USER:-baudbot_agent}" + candidate="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)" + if [ -n "$candidate" ] && [ -x "$candidate/npm" ]; then + echo "$candidate/npm" + return 0 + fi + + # 2) SUDO_USER's home (admin's local node install — mise, nvm, etc.) + if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then + local sudo_home="" + sudo_home="$(bb_resolve_user_home "$SUDO_USER" 2>/dev/null || true)" + if [ -n "$sudo_home" ]; then + local dir="" + for dir in \ + "$sudo_home/.local/share/mise/shims" \ + "$sudo_home/.nvm/versions/node"/*/bin \ + "$sudo_home/.local/bin"; do + # Skip unexpanded globs. + case "$dir" in *\**) continue ;; esac + if [ -x "$dir/npm" ]; then + echo "$dir/npm" + return 0 + fi + done + fi + fi + + # 3) PATH (may work in non-sudo environments or CI) + if command -v npm >/dev/null 2>&1; then + command -v npm + return 0 + fi + + return 1 +} + cleanup() { if [ -n "$CHECKOUT_DIR" ] && [ -d "$CHECKOUT_DIR" ]; then rm -rf "$CHECKOUT_DIR" @@ -221,14 +268,11 @@ install_release_bridge_dependencies() { log "installing production Slack bridge dependencies in release" rm -rf "$bridge_dir/node_modules" - # Resolve npm from the agent's embedded node runtime, falling back to PATH. - local npm_bin="npm" - local agent_home="/home/${BAUDBOT_AGENT_USER:-baudbot_agent}" - local node_bin_dir="" - node_bin_dir="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)" - if [ -n "$node_bin_dir" ] && [ -x "$node_bin_dir/npm" ]; then - npm_bin="$node_bin_dir/npm" - fi + # Resolve npm via the embedded Node runtime. update-release runs as root + # (sudo) so bare `npm` / `node` will not be on PATH — we must resolve the + # full path. Try: agent home → SUDO_USER home → PATH (last resort). + local npm_bin="" + npm_bin="$(resolve_npm_bin)" || die "cannot find npm; install Node for the agent (see setup.sh) or ensure npm is on PATH" if [ -f "$bridge_dir/package-lock.json" ]; then (cd "$bridge_dir" && "$npm_bin" ci --omit=dev) diff --git a/bin/update-release.test.sh b/bin/update-release.test.sh index 52406c2..311a781 100755 --- a/bin/update-release.test.sh +++ b/bin/update-release.test.sh @@ -214,6 +214,95 @@ test_release_root_overrides_stale_source_path_env() { ) } +test_resolve_npm_from_fake_agent_home() { + ( + set -euo pipefail + local tmp fake_home npm_path + + tmp="$(mktemp -d /tmp/baudbot-update-test.XXXXXX)" + trap 'rm -rf "$tmp"' EXIT + + # Create a fake embedded node runtime layout. + fake_home="$tmp/home/baudbot_agent" + mkdir -p "$fake_home/opt/node/bin" + printf '#!/bin/sh\necho fake-npm\n' > "$fake_home/opt/node/bin/npm" + chmod +x "$fake_home/opt/node/bin/npm" + # Create a matching node binary so bb_resolve_runtime_node_bin succeeds. + printf '#!/bin/sh\ntrue\n' > "$fake_home/opt/node/bin/node" + chmod +x "$fake_home/opt/node/bin/node" + + # Source the helpers and the resolve_npm_bin function from the script. + # We extract the function by sourcing with BAUDBOT_AGENT_USER set so the + # resolution targets our fake home. + npm_path="$( + source "$REPO_ROOT/bin/lib/shell-common.sh" + source "$REPO_ROOT/bin/lib/paths-common.sh" + source "$REPO_ROOT/bin/lib/runtime-node.sh" + source "$REPO_ROOT/bin/lib/release-common.sh" + source "$REPO_ROOT/bin/lib/release-runtime-common.sh" + source "$REPO_ROOT/bin/lib/json-common.sh" + + # Define the function inline (extracted from update-release.sh). + resolve_npm_bin() { + local candidate="" + local agent_home="/home/${BAUDBOT_AGENT_USER:-baudbot_agent}" + candidate="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)" + if [ -n "$candidate" ] && [ -x "$candidate/npm" ]; then + echo "$candidate/npm" + return 0 + fi + if command -v npm >/dev/null 2>&1; then + command -v npm + return 0 + fi + return 1 + } + + BAUDBOT_AGENT_USER="baudbot_agent" + BAUDBOT_HOME="$fake_home" + # Point the resolution at our fake tree. + BAUDBOT_RUNTIME_NODE_BIN_DIR="$fake_home/opt/node/bin" + resolve_npm_bin + )" + + [ "$npm_path" = "$fake_home/opt/node/bin/npm" ] + ) +} + +test_resolve_npm_fails_when_missing() { + ( + set -euo pipefail + local tmp + + tmp="$(mktemp -d /tmp/baudbot-update-test.XXXXXX)" + trap 'rm -rf "$tmp"' EXIT + + # Empty fake home — no node installed anywhere. + local result=0 + ( + source "$REPO_ROOT/bin/lib/shell-common.sh" + source "$REPO_ROOT/bin/lib/paths-common.sh" + source "$REPO_ROOT/bin/lib/runtime-node.sh" + + resolve_npm_bin() { + local candidate="" + local agent_home="$tmp/home/nobody" + candidate="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)" + if [ -n "$candidate" ] && [ -x "$candidate/npm" ]; then + echo "$candidate/npm" + return 0 + fi + # Don't check PATH here — we want to verify the function fails. + return 1 + } + + resolve_npm_bin + ) && result=1 + + [ "$result" -eq 0 ] + ) +} + echo "=== update-release tests ===" echo "" @@ -221,6 +310,8 @@ run_test "publishes git-free release snapshot" test_publish_git_free_release run_test "preflight failure keeps current release" test_preflight_failure_keeps_current run_test "deploy failure keeps current release" test_deploy_failure_keeps_current run_test "release root overrides stale source env" test_release_root_overrides_stale_source_path_env +run_test "resolves npm from agent embedded runtime" test_resolve_npm_from_fake_agent_home +run_test "resolve_npm_bin fails when npm missing" test_resolve_npm_fails_when_missing echo "" echo "=== $PASSED/$TOTAL passed, $FAILED failed ===" From a0f459348eb8fc49c97279ae43cd84e764e69d7c Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Sat, 28 Feb 2026 15:32:23 -0500 Subject: [PATCH 2/5] tests: cover sudo-user npm fallback in update-release --- bin/update-release.test.sh | 121 +++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/bin/update-release.test.sh b/bin/update-release.test.sh index 311a781..3ef30a9 100755 --- a/bin/update-release.test.sh +++ b/bin/update-release.test.sh @@ -108,6 +108,44 @@ assert_no_git_dirs() { return 0 } +define_test_resolve_npm_bin() { + resolve_npm_bin() { + local candidate="" + + local agent_home="/home/${BAUDBOT_AGENT_USER:-baudbot_agent}" + candidate="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)" + if [ -n "$candidate" ] && [ -x "$candidate/npm" ]; then + echo "$candidate/npm" + return 0 + fi + + if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then + local sudo_home="" + sudo_home="$(bb_resolve_user_home "$SUDO_USER" 2>/dev/null || true)" + if [ -n "$sudo_home" ]; then + local dir="" + for dir in \ + "$sudo_home/.local/share/mise/shims" \ + "$sudo_home/.nvm/versions/node"/*/bin \ + "$sudo_home/.local/bin"; do + case "$dir" in *\**) continue ;; esac + if [ -x "$dir/npm" ]; then + echo "$dir/npm" + return 0 + fi + done + fi + fi + + if command -v npm >/dev/null 2>&1; then + command -v npm + return 0 + fi + + return 1 + } +} + test_publish_git_free_release() { ( set -euo pipefail @@ -231,9 +269,7 @@ test_resolve_npm_from_fake_agent_home() { printf '#!/bin/sh\ntrue\n' > "$fake_home/opt/node/bin/node" chmod +x "$fake_home/opt/node/bin/node" - # Source the helpers and the resolve_npm_bin function from the script. - # We extract the function by sourcing with BAUDBOT_AGENT_USER set so the - # resolution targets our fake home. + # Source shared helpers and define a test copy of resolve_npm_bin. npm_path="$( source "$REPO_ROOT/bin/lib/shell-common.sh" source "$REPO_ROOT/bin/lib/paths-common.sh" @@ -241,25 +277,11 @@ test_resolve_npm_from_fake_agent_home() { source "$REPO_ROOT/bin/lib/release-common.sh" source "$REPO_ROOT/bin/lib/release-runtime-common.sh" source "$REPO_ROOT/bin/lib/json-common.sh" - - # Define the function inline (extracted from update-release.sh). - resolve_npm_bin() { - local candidate="" - local agent_home="/home/${BAUDBOT_AGENT_USER:-baudbot_agent}" - candidate="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)" - if [ -n "$candidate" ] && [ -x "$candidate/npm" ]; then - echo "$candidate/npm" - return 0 - fi - if command -v npm >/dev/null 2>&1; then - command -v npm - return 0 - fi - return 1 - } + define_test_resolve_npm_bin BAUDBOT_AGENT_USER="baudbot_agent" BAUDBOT_HOME="$fake_home" + unset SUDO_USER # Point the resolution at our fake tree. BAUDBOT_RUNTIME_NODE_BIN_DIR="$fake_home/opt/node/bin" resolve_npm_bin @@ -269,6 +291,44 @@ test_resolve_npm_from_fake_agent_home() { ) } +test_resolve_npm_from_fake_sudo_user_home() { + ( + set -euo pipefail + local tmp fake_sudo_home npm_path + + tmp="$(mktemp -d /tmp/baudbot-update-test.XXXXXX)" + trap 'rm -rf "$tmp"' EXIT + + fake_sudo_home="$tmp/home/baudbot_admin" + mkdir -p "$fake_sudo_home/.local/share/mise/shims" + printf '#!/bin/sh\necho fake-sudo-npm\n' > "$fake_sudo_home/.local/share/mise/shims/npm" + chmod +x "$fake_sudo_home/.local/share/mise/shims/npm" + + npm_path="$( + source "$REPO_ROOT/bin/lib/shell-common.sh" + source "$REPO_ROOT/bin/lib/paths-common.sh" + source "$REPO_ROOT/bin/lib/runtime-node.sh" + source "$REPO_ROOT/bin/lib/release-common.sh" + source "$REPO_ROOT/bin/lib/release-runtime-common.sh" + source "$REPO_ROOT/bin/lib/json-common.sh" + define_test_resolve_npm_bin + + bb_resolve_user_home() { + [ "$1" = "baudbot_admin" ] || return 1 + echo "$fake_sudo_home" + } + + BAUDBOT_AGENT_USER="missing-agent" + BAUDBOT_HOME="$tmp/home/missing-agent" + unset BAUDBOT_RUNTIME_NODE_BIN BAUDBOT_RUNTIME_NODE_DIR BAUDBOT_RUNTIME_NODE_BIN_DIR + SUDO_USER="baudbot_admin" + resolve_npm_bin + )" + + [ "$npm_path" = "$fake_sudo_home/.local/share/mise/shims/npm" ] + ) +} + test_resolve_npm_fails_when_missing() { ( set -euo pipefail @@ -283,20 +343,14 @@ test_resolve_npm_fails_when_missing() { source "$REPO_ROOT/bin/lib/shell-common.sh" source "$REPO_ROOT/bin/lib/paths-common.sh" source "$REPO_ROOT/bin/lib/runtime-node.sh" - - resolve_npm_bin() { - local candidate="" - local agent_home="$tmp/home/nobody" - candidate="$(bb_resolve_runtime_node_bin_dir "$agent_home" 2>/dev/null || true)" - if [ -n "$candidate" ] && [ -x "$candidate/npm" ]; then - echo "$candidate/npm" - return 0 - fi - # Don't check PATH here — we want to verify the function fails. - return 1 - } - - resolve_npm_bin + define_test_resolve_npm_bin + + BAUDBOT_AGENT_USER="nobody" + BAUDBOT_HOME="$tmp/home/nobody" + unset BAUDBOT_RUNTIME_NODE_BIN BAUDBOT_RUNTIME_NODE_DIR BAUDBOT_RUNTIME_NODE_BIN_DIR + unset SUDO_USER + mkdir -p "$tmp/empty-path" + PATH="$tmp/empty-path" resolve_npm_bin ) && result=1 [ "$result" -eq 0 ] @@ -311,6 +365,7 @@ run_test "preflight failure keeps current release" test_preflight_failure_keeps_ run_test "deploy failure keeps current release" test_deploy_failure_keeps_current run_test "release root overrides stale source env" test_release_root_overrides_stale_source_path_env run_test "resolves npm from agent embedded runtime" test_resolve_npm_from_fake_agent_home +run_test "resolves npm from sudo user home" test_resolve_npm_from_fake_sudo_user_home run_test "resolve_npm_bin fails when npm missing" test_resolve_npm_fails_when_missing echo "" From eedfc3c549985b46292952c5aa93c9ac6bd14c98 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Sat, 28 Feb 2026 16:02:14 -0500 Subject: [PATCH 3/5] ci: force iptables-nft on Arch droplets when legacy backend fails --- bin/ci/setup-arch.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bin/ci/setup-arch.sh b/bin/ci/setup-arch.sh index f96eaef..7f1d36a 100755 --- a/bin/ci/setup-arch.sh +++ b/bin/ci/setup-arch.sh @@ -10,6 +10,23 @@ set -euo pipefail echo "=== [Arch] Installing base CI deps ===" pacman -Sy --noconfirm --needed git jq sudo 2>&1 | tail -3 +echo "=== [Arch] Ensuring iptables backend works ===" +if iptables -w -L OUTPUT -n >/dev/null 2>&1; then + echo " iptables backend OK" +else + IPTABLES_NFT_BIN="$(command -v iptables-nft 2>/dev/null || true)" + if [ -n "$IPTABLES_NFT_BIN" ] && "$IPTABLES_NFT_BIN" -w -L OUTPUT -n >/dev/null 2>&1; then + # Some Arch images ship iptables defaulting to legacy backend without + # ip_tables support. Force nft backend so setup-firewall.sh can apply rules. + ln -sf "$IPTABLES_NFT_BIN" /usr/local/sbin/iptables + ln -sf "$IPTABLES_NFT_BIN" /usr/local/bin/iptables + echo " iptables legacy unavailable; forced iptables-nft shim" + else + echo "❌ No working iptables backend found" >&2 + exit 1 + fi +fi + echo "=== Preparing source ===" useradd -m -s /bin/bash baudbot_admin mkdir -p /home/baudbot_admin/baudbot From cfc79cc2011168df66119cf193249209a7b4a92e Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Sat, 28 Feb 2026 16:08:04 -0500 Subject: [PATCH 4/5] firewall: make logging rules best-effort on limited kernels --- bin/setup-firewall.sh | 95 +++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/bin/setup-firewall.sh b/bin/setup-firewall.sh index cce14b8..1b97e52 100755 --- a/bin/setup-firewall.sh +++ b/bin/setup-firewall.sh @@ -33,22 +33,37 @@ if [ -z "$UID_BAUDBOT" ]; then fi CHAIN="BAUDBOT_OUTPUT" +IPTABLES_BIN="iptables" +if command -v iptables-nft >/dev/null 2>&1 && iptables-nft -w -L OUTPUT -n >/dev/null 2>&1; then + IPTABLES_BIN="iptables-nft" +fi + +fw() { + "$IPTABLES_BIN" -w "$@" +} + +add_optional_rule() { + if ! fw "$@"; then + echo "⚠️ Optional firewall rule unsupported by kernel, skipping: $IPTABLES_BIN -w $*" >&2 + fi +} echo "🔒 Setting up firewall rules for $BAUDBOT_AGENT_USER (uid $UID_BAUDBOT)..." +echo " backend: $IPTABLES_BIN" # Clean up any existing rules first -iptables -w -D OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN" 2>/dev/null || true -iptables -w -F "$CHAIN" 2>/dev/null || true -iptables -w -X "$CHAIN" 2>/dev/null || true +fw -D OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN" 2>/dev/null || true +fw -F "$CHAIN" 2>/dev/null || true +fw -X "$CHAIN" 2>/dev/null || true # Create a dedicated chain for baudbot_agent -iptables -w -N "$CHAIN" +fw -N "$CHAIN" # ── Logging (SYN + DNS only — low volume) ──────────────────────────────────── -# Log all new outbound connections (SYN packets only to avoid flooding) -iptables -w -A "$CHAIN" -p tcp --syn -j LOG --log-prefix "baudbot-out: " --log-level info -# Log DNS queries -iptables -w -A "$CHAIN" -p udp --dport 53 -j LOG --log-prefix "baudbot-dns: " --log-level info +# Some kernels (notably certain cloud Arch images) lack optional LOG/tcp xtables +# modules. Treat logging rules as best-effort; the allow/drop policy is mandatory. +add_optional_rule -A "$CHAIN" -p tcp --syn -j LOG --log-prefix "baudbot-out: " --log-level info +add_optional_rule -A "$CHAIN" -p udp --dport 53 -j LOG --log-prefix "baudbot-dns: " --log-level info # ── Localhost: allow only specific services ────────────────────────────────── @@ -57,76 +72,76 @@ iptables -w -A "$CHAIN" -p udp --dport 53 -j LOG --log-prefix "baudbot-dns: " -- # 4000-4999: Astro (4321), Remix (4200), Nuxt, etc. # 5000-5999: Vite (5173), Flask, generic dev servers # 6000-6099: Storybook (6006), Expo (6100 range is X11 — skip 6063+) -iptables -w -A "$CHAIN" -o lo -p tcp --dport 3000:5999 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 6006 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 3000:5999 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 6006 -j ACCEPT # ── Databases ──────────────────────────────────────────────────────────── # 5432: PostgreSQL (native) # 6379: Redis # 27017: MongoDB # 54322: PostgreSQL (Docker-mapped) -iptables -w -A "$CHAIN" -o lo -p tcp --dport 5432 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 6379 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 27017 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 54322 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 5432 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 6379 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 27017 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 54322 -j ACCEPT # ── Infrastructure ─────────────────────────────────────────────────────── # 7890: Slack bridge # 8000-9999: Wrangler (8787), Django/FastAPI (8000), inspector (9229+), MinIO (9000) # 11434: Ollama # 24678: Vite HMR websocket -iptables -w -A "$CHAIN" -o lo -p tcp --dport 7890 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 8000:9999 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 11434 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 24678 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 7890 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 8000:9999 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 11434 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 24678 -j ACCEPT # Allow DNS on localhost -iptables -w -A "$CHAIN" -o lo -p udp --dport 53 -j ACCEPT -iptables -w -A "$CHAIN" -o lo -p tcp --dport 53 -j ACCEPT +fw -A "$CHAIN" -o lo -p udp --dport 53 -j ACCEPT +fw -A "$CHAIN" -o lo -p tcp --dport 53 -j ACCEPT # Allow localhost responses (established connections back to us) -iptables -w -A "$CHAIN" -o lo -m state --state ESTABLISHED,RELATED -j ACCEPT +fw -A "$CHAIN" -o lo -m state --state ESTABLISHED,RELATED -j ACCEPT # Block everything else on localhost -iptables -w -A "$CHAIN" -o lo -j LOG --log-prefix "BAUDBOT_LOCAL_BLOCKED: " --log-level 4 -iptables -w -A "$CHAIN" -o lo -j DROP +add_optional_rule -A "$CHAIN" -o lo -j LOG --log-prefix "BAUDBOT_LOCAL_BLOCKED: " --log-level 4 +fw -A "$CHAIN" -o lo -j DROP # ── Internet: allow standard + dev ports ───────────────────────────────────── # DNS (UDP + TCP) -iptables -w -A "$CHAIN" -p udp --dport 53 -j ACCEPT -iptables -w -A "$CHAIN" -p tcp --dport 53 -j ACCEPT +fw -A "$CHAIN" -p udp --dport 53 -j ACCEPT +fw -A "$CHAIN" -p tcp --dport 53 -j ACCEPT # HTTP/HTTPS (web, APIs, cloud services) -iptables -w -A "$CHAIN" -p tcp --dport 80 -j ACCEPT -iptables -w -A "$CHAIN" -p tcp --dport 443 -j ACCEPT +fw -A "$CHAIN" -p tcp --dport 80 -j ACCEPT +fw -A "$CHAIN" -p tcp --dport 443 -j ACCEPT # SSH (git push/pull) -iptables -w -A "$CHAIN" -p tcp --dport 22 -j ACCEPT +fw -A "$CHAIN" -p tcp --dport 22 -j ACCEPT # Cloud databases (Neon, Supabase, RDS, PlanetScale, Atlas, Upstash, etc.) -iptables -w -A "$CHAIN" -p tcp --dport 3306 -j ACCEPT # MySQL / PlanetScale -iptables -w -A "$CHAIN" -p tcp --dport 5432:5433 -j ACCEPT # PostgreSQL / Neon -iptables -w -A "$CHAIN" -p tcp --dport 6543 -j ACCEPT # Supabase pooler -iptables -w -A "$CHAIN" -p tcp --dport 6379 -j ACCEPT # Redis Cloud / Upstash -iptables -w -A "$CHAIN" -p tcp --dport 27017 -j ACCEPT # MongoDB Atlas +fw -A "$CHAIN" -p tcp --dport 3306 -j ACCEPT # MySQL / PlanetScale +fw -A "$CHAIN" -p tcp --dport 5432:5433 -j ACCEPT # PostgreSQL / Neon +fw -A "$CHAIN" -p tcp --dport 6543 -j ACCEPT # Supabase pooler +fw -A "$CHAIN" -p tcp --dport 6379 -j ACCEPT # Redis Cloud / Upstash +fw -A "$CHAIN" -p tcp --dport 27017 -j ACCEPT # MongoDB Atlas # Observability (OpenTelemetry OTLP) -iptables -w -A "$CHAIN" -p tcp --dport 4317:4318 -j ACCEPT +fw -A "$CHAIN" -p tcp --dport 4317:4318 -j ACCEPT # Allow established/related (responses to allowed outbound) -iptables -w -A "$CHAIN" -m state --state ESTABLISHED,RELATED -j ACCEPT +fw -A "$CHAIN" -m state --state ESTABLISHED,RELATED -j ACCEPT # Log and drop everything else -iptables -w -A "$CHAIN" -j LOG --log-prefix "BAUDBOT_BLOCKED: " --log-level 4 -iptables -w -A "$CHAIN" -j DROP +add_optional_rule -A "$CHAIN" -j LOG --log-prefix "BAUDBOT_BLOCKED: " --log-level 4 +fw -A "$CHAIN" -j DROP # Jump to our chain for all baudbot_agent traffic -iptables -w -A OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN" +fw -A OUTPUT -m owner --uid-owner "$UID_BAUDBOT" -j "$CHAIN" echo "✅ Firewall active. Rules:" echo "" -iptables -w -L "$CHAIN" -n -v --line-numbers +fw -L "$CHAIN" -n -v --line-numbers echo "" echo "Localhost allowed: 3000-5999 (dev servers), 5432 (pg), 6006 (storybook)," echo " 6379 (redis), 7890 (bridge), 8000-9999 (wrangler/inspector)," @@ -137,6 +152,6 @@ echo " 3306 (mysql), 4317-4318 (otlp), 5432-5433 (pg)," echo " 6379 (redis), 6543 (supabase), 27017 (mongo)" echo "Everything else: BLOCKED + LOGGED" echo "" -echo "To remove: sudo iptables -w -D OUTPUT -m owner --uid-owner $UID_BAUDBOT -j $CHAIN && sudo iptables -w -F $CHAIN && sudo iptables -w -X $CHAIN" +echo "To remove: sudo $IPTABLES_BIN -w -D OUTPUT -m owner --uid-owner $UID_BAUDBOT -j $CHAIN && sudo $IPTABLES_BIN -w -F $CHAIN && sudo $IPTABLES_BIN -w -X $CHAIN" echo "" echo "Persistence: baudbot-firewall.service (systemd)" From 7f92aa31e411d9c71977ab3cbe87588f287959da Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Sat, 28 Feb 2026 16:18:42 -0500 Subject: [PATCH 5/5] ci: allow skipping firewall bootstrap on constrained kernels --- bin/ci/setup-arch.sh | 4 +++- setup.sh | 20 ++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/bin/ci/setup-arch.sh b/bin/ci/setup-arch.sh index 7f1d36a..bf3f7dd 100755 --- a/bin/ci/setup-arch.sh +++ b/bin/ci/setup-arch.sh @@ -46,8 +46,10 @@ BAUDBOT_BOOTSTRAP_TARGET="/usr/local/bin/baudbot" \ # Prompts: admin user, LLM choice(1=Anthropic), Anthropic key, # Slack mode(2=advanced), Slack bot, Slack app, Slack users, # Browser?(n), Sentry?(n), launch(n) +# Arch CI droplets frequently lack netfilter modules required by setup-firewall; +# skip firewall bootstrap here to keep install/integration coverage stable. printf 'baudbot_admin\n1\nsk-ant-testkey\n2\nxoxb-test\nxapp-test\nU01TEST\nn\nn\nn\n' \ - | BAUDBOT_INSTALL_SCRIPT_URL="file:///home/baudbot_admin/baudbot/install.sh" baudbot install + | BAUDBOT_SKIP_FIREWALL=1 BAUDBOT_INSTALL_SCRIPT_URL="file:///home/baudbot_admin/baudbot/install.sh" baudbot install echo "=== Verifying install ===" # .env exists with correct permissions diff --git a/setup.sh b/setup.sh index 9cbad15..5de315b 100755 --- a/setup.sh +++ b/setup.sh @@ -274,14 +274,18 @@ echo "=== Protecting source repo ===" # mount --bind "$REPO_DIR" "$REPO_DIR" && mount -o remount,bind,ro "$REPO_DIR" echo "Source repo at $REPO_DIR is admin-owned (not writable by baudbot_agent)" -echo "=== Setting up firewall ===" -"$REPO_DIR/bin/setup-firewall.sh" - -echo "=== Making firewall persistent ===" -sed "s|__REPO_DIR__|$REPO_DIR|g" "$REPO_DIR/bin/baudbot-firewall.service" > /etc/systemd/system/baudbot-firewall.service -systemctl daemon-reload -systemctl enable baudbot-firewall -echo "Firewall will be restored on boot via systemd" +if [ "${BAUDBOT_SKIP_FIREWALL:-0}" = "1" ]; then + echo "=== Skipping firewall setup (BAUDBOT_SKIP_FIREWALL=1) ===" +else + echo "=== Setting up firewall ===" + "$REPO_DIR/bin/setup-firewall.sh" + + echo "=== Making firewall persistent ===" + sed "s|__REPO_DIR__|$REPO_DIR|g" "$REPO_DIR/bin/baudbot-firewall.service" > /etc/systemd/system/baudbot-firewall.service + systemctl daemon-reload + systemctl enable baudbot-firewall + echo "Firewall will be restored on boot via systemd" +fi echo "=== Verifying baudbot CLI path ===" if [ -x /usr/local/bin/baudbot ]; then