#!/bin/bash # BaldCanary Diagnostic / Webhook Test Script set -euo pipefail APP_ROOT="/opt/baldcanary" APP_DB="${APP_ROOT}/db/baldcanary.sqlite" DISPATCHER_SERVICE="baldcanary-dispatcher" OPENCANARY_SERVICE="opencanary" COLLECTOR_SERVICE="baldcanary-decoylog" NGINX_SERVICE="nginx" fail() { echo "ERROR: $*" >&2 exit 1 } section() { echo echo "============================================================" echo "$*" echo "============================================================" } run() { echo "+ $*" "$@" } require_root() { if [[ "${EUID}" -ne 0 ]]; then fail "Run this script as root: sudo $0" fi } check_file() { local file="$1" if [[ -e "$file" ]]; then echo "OK: $file exists" ls -lh "$file" else echo "MISSING: $file" fi } sqlite_query() { local query="$1" if [[ ! -f "$APP_DB" ]]; then echo "SQLite DB not found: $APP_DB" return 1 fi sqlite3 -header -column "$APP_DB" "$query" } insert_test_alert() { section "Insert Test Alert" local now now="$(date -u '+%Y-%m-%d %H:%M:%S')" sqlite3 "$APP_DB" " INSERT INTO events(source,severity,event_type,src_ip,path,matched_bait,raw_json) VALUES( 'system', 'high', 'test_alert', '127.0.0.1', 'manual-test', 'manual webhook test', '{\"test\":true,\"created_by\":\"test.sh\",\"created_at\":\"${now}\"}' ); " local event_id event_id="$(sqlite3 "$APP_DB" "SELECT id FROM events ORDER BY id DESC LIMIT 1;")" echo "Inserted test alert event ID: $event_id" echo "Waiting 15 seconds for dispatcher..." sleep 15 section "Alert Log Result For Test Event" sqlite_query " SELECT id, event_id, target_id, attempted_at, ok, response_code, substr(response_body,1,160) AS response_body, substr(error,1,250) AS error FROM alert_log WHERE event_id = ${event_id} ORDER BY id DESC; " } main() { require_root section "BaldCanary Diagnostic Test" echo "Host: $(hostname)" echo "Time: $(date)" echo "User: $(whoami)" section "Required Files" check_file "$APP_DB" check_file "${APP_ROOT}/config/opencanary_event_labels.json" check_file "${APP_ROOT}/scripts/dispatch_alerts.py" check_file "${APP_ROOT}/scripts/opencanary_collector.py" check_file "/usr/local/bin/baldcanary" check_file "/etc/opencanaryd/opencanary.conf" section "Service Status" systemctl --no-pager --full status "$NGINX_SERVICE" || true systemctl --no-pager --full status "$OPENCANARY_SERVICE" || true systemctl --no-pager --full status "$COLLECTOR_SERVICE" || true systemctl --no-pager --full status "$DISPATCHER_SERVICE" || true section "Listening Ports" ss -ltnup || true section "SQLite Integrity" sqlite3 "$APP_DB" "PRAGMA integrity_check;" || true section "BaldCanary Settings" sqlite_query " SELECT key, value, updated_at FROM settings ORDER BY key; " || true section "Webhook Targets" sqlite_query " SELECT id, name, type, enabled, created_at, substr(url,1,80) || CASE WHEN length(url) > 80 THEN '...' ELSE '' END AS url_preview FROM webhook_targets ORDER BY id; " || true section "Recent Events" sqlite_query " SELECT id, event_time, source, severity, event_type, src_ip, path, matched_bait FROM events ORDER BY id DESC LIMIT 15; " || true section "Recent Alert Attempts" sqlite_query " SELECT id, event_id, target_id, attempted_at, ok, response_code, substr(response_body,1,160) AS response_body, substr(error,1,250) AS error FROM alert_log ORDER BY id DESC LIMIT 15; " || true section "Dispatcher Python Syntax Check" if [[ -f "${APP_ROOT}/scripts/dispatch_alerts.py" ]]; then run python3 -m py_compile "${APP_ROOT}/scripts/dispatch_alerts.py" || true fi section "Collector Python Syntax Check" if [[ -f "${APP_ROOT}/scripts/opencanary_collector.py" ]]; then run python3 -m py_compile "${APP_ROOT}/scripts/opencanary_collector.py" || true fi section "Recent Dispatcher Logs" journalctl -u "$DISPATCHER_SERVICE" -n 80 --no-pager || true section "Recent Collector Logs" journalctl -u "$COLLECTOR_SERVICE" -n 60 --no-pager || true section "Recent OpenCanary Logs" journalctl -u "$OPENCANARY_SERVICE" -n 60 --no-pager || true section "Optional Live Webhook Test" echo "This will insert a new high-severity test event and wait for the dispatcher." read -r -p "Insert test alert now? [y/N] " answer case "$answer" in y|Y|yes|YES) insert_test_alert ;; *) echo "Skipped test alert insertion." ;; esac section "Done" echo "If Teams did not receive the alert, check:" echo " 1. webhook_targets has enabled=1" echo " 2. alert_log has ok=1 and a 2xx response_code" echo " 3. baldcanary-dispatcher logs show no Python errors" echo " 4. the Teams/Power Automate workflow accepts the payload schema" } main "$@"