Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions bin/checks/slack-allowed-users.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env node

import fs from "node:fs";

const envPath = process.argv[2] || "";

function countUsers(rawValue) {
if (!rawValue) return 0;
return rawValue
.split(",")
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0).length;
}

if (!envPath) {
process.stdout.write(
JSON.stringify({
ok: "0",
exists: "0",
defined: "0",
raw_non_empty: "0",
count: "0",
error: "missing_path_argument",
}),
);
process.exit(0);
}

if (!fs.existsSync(envPath)) {
process.stdout.write(
JSON.stringify({
ok: "1",
exists: "0",
defined: "0",
raw_non_empty: "0",
count: "0",
error: "",
}),
);
process.exit(0);
}

try {
const lines = fs.readFileSync(envPath, "utf8").split(/\r?\n/);
let value = "";
let defined = false;

for (const line of lines) {
if (!line.startsWith("SLACK_ALLOWED_USERS=")) continue;
value = line.slice("SLACK_ALLOWED_USERS=".length);
defined = true;
}

const count = defined ? countUsers(value) : 0;

process.stdout.write(
JSON.stringify({
ok: "1",
exists: "1",
defined: defined ? "1" : "0",
raw_non_empty: defined && value.length > 0 ? "1" : "0",
count: String(count),
error: "",
}),
);
} catch {
process.stdout.write(
JSON.stringify({
ok: "0",
exists: "1",
defined: "0",
raw_non_empty: "0",
count: "0",
error: "parse_error",
}),
);
}
38 changes: 14 additions & 24 deletions bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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/json-common.sh
source "$SCRIPT_DIR/lib/json-common.sh"
# shellcheck source=bin/lib/runtime-node.sh
source "$SCRIPT_DIR/lib/runtime-node.sh"
bb_enable_strict_mode
Expand Down Expand Up @@ -64,6 +66,8 @@ else
fail "Node.js not found (expected: $NODE_BIN)"
fi

CHECK_NODE_BIN="$(bb_pick_node_bin "${NODE_BIN:-}" || true)"

PI_BIN="$(bb_resolve_runtime_node_bin_dir "$BAUDBOT_HOME")/pi"
if [ -x "$PI_BIN" ] || [ -L "$PI_BIN" ]; then
pass "pi is installed"
Expand Down Expand Up @@ -240,7 +244,10 @@ if [ -f "$ENV_FILE" ]; then
fi
fi

if grep -q '^SLACK_ALLOWED_USERS=.\+' "$ENV_FILE" 2>/dev/null; then
SLACK_ALLOWED_USERS_CHECK_SCRIPT="$BAUDBOT_ROOT/bin/checks/slack-allowed-users.mjs"
slack_allowed_users_payload="$(bb_run_node_check_payload "$CHECK_NODE_BIN" "$SLACK_ALLOWED_USERS_CHECK_SCRIPT" "$ENV_FILE")"
slack_allowed_users_non_empty="$(bb_json_field_or_default "$slack_allowed_users_payload" "raw_non_empty" "0")"
if [ "$slack_allowed_users_non_empty" = "1" ]; then
pass "SLACK_ALLOWED_USERS is set"
else
warn "SLACK_ALLOWED_USERS is not set (all workspace members allowed)"
Expand Down Expand Up @@ -302,31 +309,14 @@ fi

INTEGRITY_STATUS_FILE="$BAUDBOT_INTEGRITY_STATUS_FILE"
INTEGRITY_CHECK_SCRIPT="$BAUDBOT_ROOT/bin/checks/integrity-status.mjs"
INTEGRITY_CHECK_NODE_BIN=""
if [ -n "${NODE_BIN:-}" ] && [ -x "${NODE_BIN:-}" ]; then
INTEGRITY_CHECK_NODE_BIN="$NODE_BIN"
elif command -v node >/dev/null 2>&1; then
INTEGRITY_CHECK_NODE_BIN="$(command -v node)"
fi

if [ -n "$INTEGRITY_CHECK_NODE_BIN" ] && [ -f "$INTEGRITY_CHECK_SCRIPT" ]; then
integrity_payload="$($INTEGRITY_CHECK_NODE_BIN "$INTEGRITY_CHECK_SCRIPT" "$INTEGRITY_STATUS_FILE" 2>/dev/null || true)"
else
integrity_payload=""
fi
integrity_payload="$(bb_run_node_check_payload "$CHECK_NODE_BIN" "$INTEGRITY_CHECK_SCRIPT" "$INTEGRITY_STATUS_FILE")"

if [ -n "$integrity_payload" ]; then
integrity_exists="$(printf '%s' "$integrity_payload" | json_get_string_stdin "exists" 2>/dev/null || true)"
integrity_status="$(printf '%s' "$integrity_payload" | json_get_string_stdin "status" 2>/dev/null || true)"
integrity_checked_at="$(printf '%s' "$integrity_payload" | json_get_string_stdin "checked_at" 2>/dev/null || true)"
integrity_missing="$(printf '%s' "$integrity_payload" | json_get_string_stdin "missing_files" 2>/dev/null || true)"
integrity_mismatches="$(printf '%s' "$integrity_payload" | json_get_string_stdin "hash_mismatches" 2>/dev/null || true)"

[ -n "$integrity_exists" ] || integrity_exists="0"
[ -n "$integrity_status" ] || integrity_status="unknown"
[ -n "$integrity_checked_at" ] || integrity_checked_at="unknown"
[ -n "$integrity_missing" ] || integrity_missing="0"
[ -n "$integrity_mismatches" ] || integrity_mismatches="0"
integrity_exists="$(bb_json_field_or_default "$integrity_payload" "exists" "0")"
integrity_status="$(bb_json_field_or_default "$integrity_payload" "status" "unknown")"
integrity_checked_at="$(bb_json_field_or_default "$integrity_payload" "checked_at" "unknown")"
integrity_missing="$(bb_json_field_or_default "$integrity_payload" "missing_files" "0")"
integrity_mismatches="$(bb_json_field_or_default "$integrity_payload" "hash_mismatches" "0")"

if [ "$integrity_exists" != "1" ]; then
if [ "$IS_ROOT" -ne 1 ] && [ -d "$BAUDBOT_HOME/.pi/agent" ]; then
Expand Down
51 changes: 51 additions & 0 deletions bin/lib/check-report-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,54 @@ bb_summary_print_item() {

printf " %s %-9s %s\n" "$icon" "$label:" "$value"
}

bb_json_field_or_default() {
local payload="$1"
local key="$2"
local fallback="$3"
local value=""

if [ -z "$payload" ]; then
echo "$fallback"
return 0
fi

value="$(printf '%s' "$payload" | json_get_string_stdin "$key" 2>/dev/null || true)"
if [ -n "$value" ]; then
echo "$value"
else
echo "$fallback"
fi
}

bb_pick_node_bin() {
local preferred_bin="${1:-}"

if [ -n "$preferred_bin" ] && [ -x "$preferred_bin" ]; then
echo "$preferred_bin"
return 0
fi

if command -v node >/dev/null 2>&1; then
command -v node
return 0
fi

return 1
}

bb_run_node_check_payload() {
local node_bin="${1:-}"
local script_path="${2:-}"
shift 2 || true

if [ -z "$node_bin" ] || [ ! -x "$node_bin" ]; then
return 0
fi

if [ -z "$script_path" ] || [ ! -f "$script_path" ]; then
return 0
fi

"$node_bin" "$script_path" "$@" 2>/dev/null || true
}
47 changes: 47 additions & 0 deletions bin/lib/check-report-common.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=bin/lib/json-common.sh
source "$SCRIPT_DIR/json-common.sh"
# shellcheck source=bin/lib/check-report-common.sh
source "$SCRIPT_DIR/check-report-common.sh"

Expand Down Expand Up @@ -71,12 +73,57 @@ test_summary_helpers_render_rows() {
)
}

test_json_field_or_default_reads_existing_key() {
(
set -euo pipefail
payload='{"status":"pass"}'
[ "$(bb_json_field_or_default "$payload" status unknown)" = "pass" ]
)
}

test_json_field_or_default_falls_back_when_missing() {
(
set -euo pipefail
payload='{"exists":"1"}'
[ "$(bb_json_field_or_default "$payload" status unknown)" = "unknown" ]
)
}

test_pick_node_bin_uses_preferred() {
(
set -euo pipefail
fake_node="$(command -v node)"
[ -n "$(bb_pick_node_bin "$fake_node")" ]
)
}

test_run_node_check_payload_executes_script() {
(
set -euo pipefail
tmp_script="$(mktemp /tmp/check-report-node-check.XXXXXX.mjs)"
trap 'rm -f "$tmp_script"' EXIT

cat >"$tmp_script" <<'EOF'
#!/usr/bin/env node
process.stdout.write(JSON.stringify({ ok: "1" }));
EOF
chmod +x "$tmp_script"

output="$(bb_run_node_check_payload "$(command -v node)" "$tmp_script")"
[ "$(bb_json_field_or_default "$output" ok 0)" = "1" ]
)
}

echo "=== check-report-common tests ==="
echo ""

run_test "reset many sets counters to zero" test_reset_many_sets_zero
run_test "increment updates named counter" test_inc_increments_named_counter
run_test "summary helpers render rows" test_summary_helpers_render_rows
run_test "json field helper reads key" test_json_field_or_default_reads_existing_key
run_test "json field helper falls back when missing" test_json_field_or_default_falls_back_when_missing
run_test "node picker prefers runtime path" test_pick_node_bin_uses_preferred
run_test "node check runner executes script" test_run_node_check_payload_executes_script

echo ""
echo "=== $PASSED/$TOTAL passed, $FAILED failed ==="
Expand Down
55 changes: 23 additions & 32 deletions bin/security-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -289,30 +289,16 @@ else
"Run deploy.sh to generate"
fi

INTEGRITY_CHECK_SCRIPT="$BAUDBOT_SRC/bin/checks/integrity-status.mjs"
INTEGRITY_CHECK_NODE_BIN="$(bb_resolve_runtime_node_bin "$BAUDBOT_HOME" 2>/dev/null || true)"
if [ -z "$INTEGRITY_CHECK_NODE_BIN" ] && command -v node >/dev/null 2>&1; then
INTEGRITY_CHECK_NODE_BIN="$(command -v node)"
fi

if [ -n "$INTEGRITY_CHECK_NODE_BIN" ] && [ -f "$INTEGRITY_CHECK_SCRIPT" ]; then
integrity_payload="$($INTEGRITY_CHECK_NODE_BIN "$INTEGRITY_CHECK_SCRIPT" "$BAUDBOT_INTEGRITY_STATUS_FILE" 2>/dev/null || true)"
else
integrity_payload=""
fi
CHECK_NODE_BIN="$(bb_pick_node_bin "$(bb_resolve_runtime_node_bin "$BAUDBOT_HOME" 2>/dev/null || true)" || true)"
INTEGRITY_CHECK_SCRIPT="$SCRIPT_DIR/checks/integrity-status.mjs"
integrity_payload="$(bb_run_node_check_payload "$CHECK_NODE_BIN" "$INTEGRITY_CHECK_SCRIPT" "$BAUDBOT_INTEGRITY_STATUS_FILE")"

if [ -n "$integrity_payload" ]; then
status_exists="$(printf '%s' "$integrity_payload" | json_get_string_stdin "exists" 2>/dev/null || true)"
status_value="$(printf '%s' "$integrity_payload" | json_get_string_stdin "status" 2>/dev/null || true)"
status_checked_at="$(printf '%s' "$integrity_payload" | json_get_string_stdin "checked_at" 2>/dev/null || true)"
status_missing="$(printf '%s' "$integrity_payload" | json_get_string_stdin "missing_files" 2>/dev/null || true)"
status_mismatches="$(printf '%s' "$integrity_payload" | json_get_string_stdin "hash_mismatches" 2>/dev/null || true)"

[ -n "$status_exists" ] || status_exists="0"
[ -n "$status_value" ] || status_value="unknown"
[ -n "$status_checked_at" ] || status_checked_at="unknown"
[ -n "$status_missing" ] || status_missing="0"
[ -n "$status_mismatches" ] || status_mismatches="0"
status_exists="$(bb_json_field_or_default "$integrity_payload" "exists" "0")"
status_value="$(bb_json_field_or_default "$integrity_payload" "status" "unknown")"
status_checked_at="$(bb_json_field_or_default "$integrity_payload" "checked_at" "unknown")"
status_missing="$(bb_json_field_or_default "$integrity_payload" "missing_files" "0")"
status_mismatches="$(bb_json_field_or_default "$integrity_payload" "hash_mismatches" "0")"

if [ "$status_exists" != "1" ]; then
finding "WARN" "No startup integrity status found" \
Expand Down Expand Up @@ -731,18 +717,23 @@ echo ""
echo "Bridge Configuration"

# Check SLACK_ALLOWED_USERS mode (without reading the actual value)
if [ -f "$BAUDBOT_HOME/.config/.env" ]; then
if grep -q '^SLACK_ALLOWED_USERS=' "$BAUDBOT_HOME/.config/.env" 2>/dev/null; then
allowed_count=$(grep '^SLACK_ALLOWED_USERS=' "$BAUDBOT_HOME/.config/.env" 2>/dev/null | cut -d= -f2 | tr ',' '\n' | grep -c . || echo 0)
if [ "$allowed_count" -gt 0 ]; then
ok "SLACK_ALLOWED_USERS configured ($allowed_count user(s))"
else
finding "WARN" "SLACK_ALLOWED_USERS is empty" \
"Bridge will allow all workspace members"
fi
else
SLACK_ALLOWED_USERS_CHECK_SCRIPT="$SCRIPT_DIR/checks/slack-allowed-users.mjs"
SLACK_ALLOWED_USERS_ENV_FILE="$BAUDBOT_HOME/.config/.env"
slack_allowed_users_payload="$(bb_run_node_check_payload "$CHECK_NODE_BIN" "$SLACK_ALLOWED_USERS_CHECK_SCRIPT" "$SLACK_ALLOWED_USERS_ENV_FILE")"

slack_allowed_users_exists="$(bb_json_field_or_default "$slack_allowed_users_payload" "exists" "0")"
slack_allowed_users_defined="$(bb_json_field_or_default "$slack_allowed_users_payload" "defined" "0")"
slack_allowed_users_count="$(bb_json_field_or_default "$slack_allowed_users_payload" "count" "0")"

if [ "$slack_allowed_users_exists" = "1" ]; then
if [ "$slack_allowed_users_defined" != "1" ]; then
finding "WARN" "SLACK_ALLOWED_USERS not set in .env" \
"Bridge will allow all workspace members"
elif [ "$slack_allowed_users_count" -gt 0 ]; then
ok "SLACK_ALLOWED_USERS configured ($slack_allowed_users_count user(s))"
else
finding "WARN" "SLACK_ALLOWED_USERS is empty" \
"Bridge will allow all workspace members"
fi
fi
echo ""
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"private": true,
"scripts": {
"test": "vitest run --config vitest.config.mjs",
"test:js": "vitest run --config vitest.config.mjs pi/extensions/heartbeat.test.mjs pi/extensions/memory.test.mjs test/legacy-node-tests.test.mjs test/broker-bridge.integration.test.mjs test/integrity-status-check.test.mjs",
"test:shell": "vitest run --config vitest.config.mjs test/shell-scripts.test.mjs test/security-audit.test.mjs",
"test:coverage": "vitest run --config vitest.config.mjs --coverage pi/extensions/heartbeat.test.mjs pi/extensions/memory.test.mjs test/legacy-node-tests.test.mjs",
"test:js": "vitest run --config vitest.js.config.mjs",
"test:shell": "vitest run --config vitest.shell.config.mjs",
"test:coverage": "vitest run --config vitest.js.config.mjs --coverage",
"lint": "npm run lint:js && npm run lint:shell",
"lint:js": "biome lint",
"lint:shell": "bash bin/lint-shell.sh",
Expand Down
16 changes: 10 additions & 6 deletions pi/skills/control-agent/startup-cleanup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ if [ -r "$RUNTIME_NODE_HELPER" ]; then
source "$RUNTIME_NODE_HELPER"
fi

NODE_BIN_DIR="${NODE_BIN_DIR:-$HOME/opt/node/bin}"
if command -v bb_resolve_runtime_node_bin_dir >/dev/null 2>&1; then
NODE_BIN_DIR="$(bb_resolve_runtime_node_bin_dir "$HOME")"
fi

BAUDBOT_CURRENT_LINK="${BAUDBOT_CURRENT_LINK:-/opt/baudbot/current}"
BAUDBOT_ENV_FILE="${BAUDBOT_ENV_FILE:-$HOME/.config/.env}"

SOCKET_DIR="$HOME/.pi/session-control"

if [ $# -eq 0 ]; then
Expand Down Expand Up @@ -79,7 +87,7 @@ fi

BRIDGE_LOG_DIR="$HOME/.pi/agent/logs"
BRIDGE_LOG_FILE="$BRIDGE_LOG_DIR/slack-bridge.log"
BRIDGE_DIR="/opt/baudbot/current/slack-bridge"
BRIDGE_DIR="$BAUDBOT_CURRENT_LINK/slack-bridge"
BRIDGE_TMUX_SESSION="slack-bridge"

mkdir -p "$BRIDGE_LOG_DIR"
Expand Down Expand Up @@ -147,10 +155,6 @@ fi
# The tmux session stays alive independently of this script (same pattern as
# sentry-agent). If the bridge crashes, the loop restarts it after 5 seconds.
echo "Starting slack-bridge ($BRIDGE_SCRIPT) via tmux..."
NODE_BIN_DIR="${NODE_BIN_DIR:-$HOME/opt/node/bin}"
if command -v bb_resolve_runtime_node_bin_dir >/dev/null 2>&1; then
NODE_BIN_DIR="$(bb_resolve_runtime_node_bin_dir "$HOME")"
fi
if [ ! -d "$NODE_BIN_DIR" ]; then
# Fallback: resolve versioned node dir
NODE_BIN_DIR="$(echo "$HOME"/opt/node-v*-linux-x64/bin | awk '{print $1}')"
Expand All @@ -164,7 +168,7 @@ tmux new-session -d -s "$BRIDGE_TMUX_SESSION" "\
while true; do \
echo \"[\$(date -Is)] bridge: starting $BRIDGE_SCRIPT\" >> $BRIDGE_LOG_FILE; \
for v in \$(env | grep ^SLACK_BROKER_ | cut -d= -f1 || true); do unset \$v; done; \
set -a; source \$HOME/.config/.env; set +a; \
set -a; source $BAUDBOT_ENV_FILE; set +a; \
node $BRIDGE_SCRIPT >> $BRIDGE_LOG_FILE 2>&1; \
exit_code=\$?; \
echo \"[\$(date -Is)] bridge: exited with code \$exit_code, restarting in 5s\" >> $BRIDGE_LOG_FILE; \
Expand Down
File renamed without changes.
File renamed without changes.
Loading