#!/bin/sh # ansipa-opnsense-checkmk-install.sh — wire an OPNsense firewall into CheckMK. # # Run as root ON THE FIREWALL. POSIX sh only: OPNsense is FreeBSD and has no # bash in the base system. # # Usage: # ansipa-opnsense-checkmk-install.sh [HOST_FQDN] [CMK_USER] # # Copy this script plus ansipa-opnsense-monitor.sh to the firewall first, e.g. # scp ansipa-opnsense-*.sh root@fw:/root/ # # ── Transport: why SSH and not the usual agent port ────────────────────────── # The normal CheckMK agent listens on TCP 6556 via inetd. On OPNsense that is a # poor fit: /etc/inetd.conf, /etc/services and /etc/hosts.allow are GENERATED # from config.xml, so the firewall silently reverts those edits on the next # reboot or config apply, and monitoring dies quietly weeks later. # # So this installs the agent as a plain script and leaves the transport to # CheckMK's "individual program call" datasource — CheckMK SSHes in and runs # the agent on demand. Nothing listens, nothing to punch through the firewall # rules, and nothing for OPNsense to overwrite. The script prints the exact # rule to create at the end. # # It also registers the host in CheckMK via the REST API, in folder # /ansipa/infra alongside the PVE/PBS hosts. set -eu LOG_TAG="ansipa-opnsense-checkmk" log() { echo "[$LOG_TAG] $*"; } warn() { echo "[$LOG_TAG][WARN] $*" >&2; } die() { echo "[$LOG_TAG][ERROR] $*" >&2; exit 1; } [ "$(id -u)" -eq 0 ] || die "Run as root." [ $# -ge 3 ] || die "Usage: $0 [HOST_FQDN] [CMK_USER]" CMK_URL=$(echo "$1" | sed 's|/$||') CMK_SITE="$2" CMK_SECRET="$3" HOST_FQDN="${4:-$(hostname)}" CMK_USER="${5:-automation}" CMK_API="${CMK_URL}/${CMK_SITE}/check_mk/api/1.0" SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) AGENT_BIN=/usr/local/bin/check_mk_agent LOCAL_DIR=/usr/local/lib/check_mk_agent/local command -v opnsense-version >/dev/null 2>&1 \ || die "opnsense-version not found — this does not look like an OPNsense host." log "OPNsense detected: $(opnsense-version 2>/dev/null | head -1)" # ── Install the FreeBSD agent ──────────────────────────────────────────────── # Pulled from the monitoring server so agent and server versions always match. if [ ! -x "$AGENT_BIN" ]; then log "Downloading the FreeBSD agent from ${CMK_URL}..." fetch -q -o "$AGENT_BIN" \ --user-agent="ansipa" \ "${CMK_URL}/${CMK_SITE}/check_mk/agents/check_mk_agent.freebsd" 2>/dev/null \ || curl -sf -u "${CMK_USER}:${CMK_SECRET}" \ "${CMK_URL}/${CMK_SITE}/check_mk/agents/check_mk_agent.freebsd" \ -o "$AGENT_BIN" 2>/dev/null \ || die "Could not download check_mk_agent.freebsd — check CMK_URL/site/secret." chmod 755 "$AGENT_BIN" log "Agent installed at $AGENT_BIN" else log "Agent already present at $AGENT_BIN" fi # ── Install the ansipa local check ─────────────────────────────────────────── mkdir -p "$LOCAL_DIR" if [ -f "$SCRIPT_DIR/ansipa-opnsense-monitor.sh" ]; then install -m 755 "$SCRIPT_DIR/ansipa-opnsense-monitor.sh" "$LOCAL_DIR/ansipa_opnsense" log "Installed local check: $LOCAL_DIR/ansipa_opnsense" else warn "ansipa-opnsense-monitor.sh not found next to this script — local check NOT installed." fi # ── Keep the firmware status cache warm ────────────────────────────────────── # The local check reads a cached update count rather than hitting the mirrors on # every poll. /usr/local/etc/rc.syshook.d survives reboots and firmware updates, # unlike /etc/crontab which OPNsense regenerates. SYSHOOK_DIR=/usr/local/etc/rc.syshook.d/start mkdir -p "$SYSHOOK_DIR" cat > "$SYSHOOK_DIR/99-ansipa-cmk" <<'HOOK' #!/bin/sh # ansipa: refresh the firmware status cache once a day so the CheckMK local # check can report pending updates without going to the network itself. ( while true; do /usr/local/sbin/configctl firmware check >/dev/null 2>&1 || true sleep 86400 done ) & HOOK chmod 755 "$SYSHOOK_DIR/99-ansipa-cmk" log "Installed firmware-cache refresher: $SYSHOOK_DIR/99-ansipa-cmk" "$SYSHOOK_DIR/99-ansipa-cmk" 2>/dev/null || true # ── Register the host in CheckMK ───────────────────────────────────────────── # Same folder the PVE/PBS appliances land in. 422 means it already exists. _folder_http=$(curl -sf -o /dev/null -w '%{http_code}' \ -X POST "${CMK_API}/domain-types/folder_config/collections/all" \ -H "Authorization: Bearer ${CMK_USER} ${CMK_SECRET}" \ -H "Content-Type: application/json" -H "Accept: application/json" \ -d '{"name":"infra","title":"Ansipa Infra (PVE/PBS)","parent":"/ansipa"}' \ 2>/dev/null || echo "000") case "$_folder_http" in 2*|422) ;; *) warn "Unexpected response creating /ansipa/infra: HTTP $_folder_http" ;; esac _HOST_IP=$(ifconfig 2>/dev/null | awk '/inet /{if($2!="127.0.0.1"){print $2; exit}}') if curl -sf -o /dev/null \ -H "Authorization: Bearer ${CMK_USER} ${CMK_SECRET}" \ "${CMK_API}/objects/host_config/${HOST_FQDN}" 2>/dev/null; then log "${HOST_FQDN} already registered in CheckMK" else _body="{\"host_name\":\"${HOST_FQDN}\",\"folder\":\"/ansipa/infra\"" [ -n "$_HOST_IP" ] && _body="${_body},\"attributes\":{\"ipaddress\":\"${_HOST_IP}\",\"tag_role\":\"opnsense\"}" _body="${_body}}" _http=$(curl -sf -o /dev/null -w '%{http_code}' \ -X POST "${CMK_API}/domain-types/host_config/collections/all" \ -H "Authorization: Bearer ${CMK_USER} ${CMK_SECRET}" \ -H "Content-Type: application/json" -H "Accept: application/json" \ -d "$_body" 2>/dev/null || echo "000") case "$_http" in 2*) log "Registered ${HOST_FQDN} in CheckMK (/ansipa/infra)" ;; *) warn "Host registration failed (HTTP ${_http}) — add it in the UI." ;; esac fi cat <