diff --git a/.gitignore b/.gitignore index fb0bd15..c5dd419 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ rmm-backend config.yaml beispielbilder/ certs/ +opnsense-plugin/stage/ +opnsense-plugin/*.pkg diff --git a/Makefile b/Makefile index e627976..a92a758 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all backend agent clean certs deploy-backend deploy-agent +.PHONY: all backend agent plugin clean certs deploy-backend deploy-agent BACKEND_BIN = build/rmm-backend AGENT_BIN = build/rmm-agent @@ -19,8 +19,13 @@ agent: cd agent && GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0 go build -o ../$(AGENT_BIN) . @echo "==> $(AGENT_BIN) erstellt" +plugin: + @echo "==> Building OPNsense Plugin Package..." + cd opnsense-plugin && ./build.sh $(VERSION) + @echo "==> Plugin Package erstellt in opnsense-plugin/" + clean: - rm -rf build/ + rm -rf build/ opnsense-plugin/stage/ certs: @echo "==> Generiere self-signed TLS-Zertifikate..." diff --git a/opnsense-plugin/build.sh b/opnsense-plugin/build.sh new file mode 100755 index 0000000..580a38c --- /dev/null +++ b/opnsense-plugin/build.sh @@ -0,0 +1,110 @@ +#!/bin/sh +# +# Build OPNsense RMM Agent Plugin Package +# +# Usage: ./build.sh [version] +# Example: ./build.sh 1.0.0 +# +# Prerequisites: +# - Go 1.24+ (for cross-compilation) +# - FreeBSD pkg tools (runs on the target OPNsense, or use pkg-create on FreeBSD) +# +# This script: +# 1. Cross-compiles the agent binary (freebsd/amd64) +# 2. Assembles the staging directory +# 3. Creates a +MANIFEST for pkg +# 4. Outputs a .tar that can be installed with `pkg add` + +set -e + +VERSION="${1:-1.0.0}" +NAME="os-rmm-agent" +STAGEDIR="$(pwd)/stage" +BUILDDIR="$(pwd)/../build" +PKGFILE="$(pwd)/${NAME}-${VERSION}.pkg" + +echo "==> Building RMM Agent Plugin v${VERSION}" + +# 1. Build agent binary +echo "==> Compiling agent (freebsd/amd64)..." +cd ../agent +GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o "${BUILDDIR}/rmm-agent" . +cd ../opnsense-plugin +echo " Binary: ${BUILDDIR}/rmm-agent ($(ls -lh ${BUILDDIR}/rmm-agent | awk '{print $5}'))" + +# 2. Assemble staging directory +echo "==> Assembling staging directory..." +rm -rf "${STAGEDIR}" +mkdir -p "${STAGEDIR}/usr/local" + +# Plugin files +cp -R src/etc "${STAGEDIR}/usr/local/" +cp -R src/opnsense "${STAGEDIR}/usr/local/" +cp -R src/usr/local/etc "${STAGEDIR}/usr/local/" +mkdir -p "${STAGEDIR}/usr/local/rmm" +cp "${BUILDDIR}/rmm-agent" "${STAGEDIR}/usr/local/rmm/rmm-agent" + +# Set permissions +chmod +x "${STAGEDIR}/usr/local/rmm/rmm-agent" +chmod +x "${STAGEDIR}/usr/local/etc/rc.d/rmm_agent" +chmod +x "${STAGEDIR}/usr/local/opnsense/scripts/rmmagent/setup.sh" +chmod +x "${STAGEDIR}/usr/local/opnsense/scripts/OPNsense/RMMAgent/generate_config.php" + +# 3. Create +MANIFEST +echo "==> Creating package manifest..." +cat > "${STAGEDIR}/+MANIFEST" << EOF +name: ${NAME} +version: ${VERSION} +origin: sysutils/${NAME} +comment: RMM Agent for OPNsense +maintainer: cm@cynfo.com +www: https://git.cynfo.net/christian/rmm +prefix: /usr/local +desc: < RMM Agent. +EOD +categories: [sysutils] +deps: + opnsense: {origin: opnsense/opnsense, version: "24.7"} +scripts: + post-install: | + /usr/local/etc/rc.d/opnsense-configd restart + echo "RMM Agent Plugin installed." + echo "Configure via: Services > RMM Agent" + pre-deinstall: | + /usr/local/etc/rc.d/rmm_agent stop 2>/dev/null || true + /usr/sbin/sysrc -x rmm_agent_enable 2>/dev/null || true + post-deinstall: | + /usr/local/etc/rc.d/opnsense-configd restart + echo "RMM Agent Plugin removed." +EOF + +# 4. Create package (tar format compatible with `pkg add`) +echo "==> Creating package..." +cd "${STAGEDIR}" + +# Generate file list +find usr -type f | sort > /tmp/rmm-pkg-files.txt + +# Create the pkg using tar (FreeBSD pkg format) +tar -czf "${PKGFILE}" +MANIFEST -C "${STAGEDIR}" $(cat /tmp/rmm-pkg-files.txt) + +cd .. +rm -rf "${STAGEDIR}" +rm -f /tmp/rmm-pkg-files.txt + +echo "" +echo "==> Package erstellt: ${PKGFILE}" +echo " Version: ${VERSION}" +echo " Groesse: $(ls -lh ${PKGFILE} | awk '{print $5}')" +echo "" +echo "==> Installation auf OPNsense:" +echo " scp ${PKGFILE} root@opnsense:/tmp/" +echo " ssh root@opnsense 'pkg add /tmp/${NAME}-${VERSION}.pkg'" +echo "" +echo "==> Danach:" +echo " - WebGUI oeffnen → Services → RMM Agent" +echo " - Backend URL und API Key eintragen" +echo " - Aktiviert ankreuzen → Speichern" diff --git a/opnsense-plugin/pkg-descr b/opnsense-plugin/pkg-descr new file mode 100644 index 0000000..65f93f1 --- /dev/null +++ b/opnsense-plugin/pkg-descr @@ -0,0 +1,16 @@ +RMM Agent for OPNsense + +Remote Monitoring & Management Agent for OPNsense firewalls. +Collects system data (CPU, memory, disks, network, services, WireGuard, +DHCP, routing, gateways, certificates, plugins, updates) and reports +to a central RMM backend via HTTPS/WebSocket. + +Features: +- Automatic system data collection (configurable interval) +- WebSocket-based remote command execution +- Session-based TCP tunnels (SSH, WebGUI access) +- Config backup and restore +- WireGuard peer management +- OPNsense update management (normal + major upgrades) + +WWW: https://git.cynfo.net/christian/rmm diff --git a/opnsense-plugin/pkg-plist b/opnsense-plugin/pkg-plist new file mode 100644 index 0000000..af1a311 --- /dev/null +++ b/opnsense-plugin/pkg-plist @@ -0,0 +1,14 @@ +etc/inc/plugins.inc.d/rmmagent.inc +opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api/ServiceController.php +opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api/SettingsController.php +opnsense/mvc/app/controllers/OPNsense/RMMAgent/GeneralController.php +opnsense/mvc/app/controllers/OPNsense/RMMAgent/forms/general.xml +opnsense/mvc/app/models/OPNsense/RMMAgent/Menu/Menu.xml +opnsense/mvc/app/models/OPNsense/RMMAgent/RMMAgent.php +opnsense/mvc/app/models/OPNsense/RMMAgent/RMMAgent.xml +opnsense/mvc/app/views/OPNsense/RMMAgent/general.volt +opnsense/scripts/OPNsense/RMMAgent/generate_config.php +opnsense/scripts/rmmagent/setup.sh +opnsense/service/conf/actions.d/actions_rmmagent.conf +usr/local/etc/rc.d/rmm_agent +usr/local/rmm/rmm-agent diff --git a/opnsense-plugin/src/etc/inc/plugins.inc.d/rmmagent.inc b/opnsense-plugin/src/etc/inc/plugins.inc.d/rmmagent.inc new file mode 100644 index 0000000..6cb377e --- /dev/null +++ b/opnsense-plugin/src/etc/inc/plugins.inc.d/rmmagent.inc @@ -0,0 +1,40 @@ +general->Enabled == '1') { + $services[] = array( + 'description' => gettext('RMM Agent'), + 'configd' => array( + 'restart' => array('rmmagent restart'), + 'start' => array('rmmagent start'), + 'stop' => array('rmmagent stop'), + ), + 'name' => 'rmm_agent', + 'pidfile' => '/var/run/rmm_agent.pid', + ); + } + + return $services; +} + +function rmmagent_xmlrpc_sync() +{ + $result = array(); + $result[] = array( + 'description' => gettext('RMM Agent'), + 'section' => 'OPNsense.rmmagent', + 'id' => 'rmmagent', + 'services' => array('rmm_agent'), + ); + return $result; +} diff --git a/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api/ServiceController.php b/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api/ServiceController.php new file mode 100644 index 0000000..7d5ac3c --- /dev/null +++ b/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api/ServiceController.php @@ -0,0 +1,13 @@ +getBase('general', 'general'); + } + + public function setAction() + { + return $this->setBase('general', 'general'); + } +} diff --git a/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/GeneralController.php b/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/GeneralController.php new file mode 100644 index 0000000..d7eddd3 --- /dev/null +++ b/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/GeneralController.php @@ -0,0 +1,14 @@ +view->generalForm = $this->getForm('general'); + $this->view->pick('OPNsense/RMMAgent/general'); + } +} diff --git a/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/forms/general.xml b/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/forms/general.xml new file mode 100644 index 0000000..da9fb7d --- /dev/null +++ b/opnsense-plugin/src/opnsense/mvc/app/controllers/OPNsense/RMMAgent/forms/general.xml @@ -0,0 +1,38 @@ +
+ + rmmagent.general.Enabled + + checkbox + RMM Agent Dienst aktivieren/deaktivieren + + + rmmagent.general.BackendUrl + + text + URL des RMM Backends (z.B. https://192.168.85.13:8443) + + + rmmagent.general.ApiKey + + password + API-Schluessel fuer die Authentifizierung am Backend + + + rmmagent.general.AgentName + + text + Optionaler Name (Standard: Hostname der Firewall) + + + rmmagent.general.Interval + + text + Intervall fuer Heartbeat/Datensammlung in Sekunden (Standard: 60) + + + rmmagent.general.Insecure + + checkbox + TLS-Zertifikats-Pruefung deaktivieren (fuer self-signed Backend-Zertifikate) + +
diff --git a/opnsense-plugin/src/opnsense/mvc/app/models/OPNsense/RMMAgent/Menu/Menu.xml b/opnsense-plugin/src/opnsense/mvc/app/models/OPNsense/RMMAgent/Menu/Menu.xml new file mode 100644 index 0000000..3fc1c52 --- /dev/null +++ b/opnsense-plugin/src/opnsense/mvc/app/models/OPNsense/RMMAgent/Menu/Menu.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/opnsense-plugin/src/opnsense/mvc/app/models/OPNsense/RMMAgent/RMMAgent.php b/opnsense-plugin/src/opnsense/mvc/app/models/OPNsense/RMMAgent/RMMAgent.php new file mode 100644 index 0000000..7f2bc40 --- /dev/null +++ b/opnsense-plugin/src/opnsense/mvc/app/models/OPNsense/RMMAgent/RMMAgent.php @@ -0,0 +1,9 @@ + + //OPNsense/rmmagent + 1.0.0 + RMM Agent Settings + + + + 0 + Y + + + Y + /^https?:\/\/.+/ + Gueltige URL erforderlich (https://host:port) + + + Y + /^.{8,}$/ + API-Key muss mindestens 8 Zeichen lang sein + + + N + Optionaler Agent-Name + + + 60 + 10 + 3600 + Intervall zwischen 10 und 3600 Sekunden + + + 1 + Y + + + + diff --git a/opnsense-plugin/src/opnsense/mvc/app/views/OPNsense/RMMAgent/general.volt b/opnsense-plugin/src/opnsense/mvc/app/views/OPNsense/RMMAgent/general.volt new file mode 100644 index 0000000..eb5b0fa --- /dev/null +++ b/opnsense-plugin/src/opnsense/mvc/app/views/OPNsense/RMMAgent/general.volt @@ -0,0 +1,51 @@ +{# + # Copyright (c) 2026 cynfo.com + # RMM Agent Settings Page + #} + + + +
+
+

{{ lang._('RMM Agent Einstellungen') }}

+
+
+
+ {{ partial("layout_partials/base_form", ['fields': generalForm, 'id': 'frm_GeneralSettings']) }} +
+
+
+
+ +
+
+ +
+
+

{{ lang._('Dienst-Status') }}

+
+
+ {{ partial("layout_partials/service_control", ['serviceName': 'rmmagent']) }} +
+
diff --git a/opnsense-plugin/src/opnsense/scripts/OPNsense/RMMAgent/generate_config.php b/opnsense-plugin/src/opnsense/scripts/OPNsense/RMMAgent/generate_config.php new file mode 100755 index 0000000..585f9fb --- /dev/null +++ b/opnsense-plugin/src/opnsense/scripts/OPNsense/RMMAgent/generate_config.php @@ -0,0 +1,31 @@ +#!/usr/local/bin/php +general->BackendUrl; +$apiKey = (string)$mdl->general->ApiKey; +$interval = (string)$mdl->general->Interval; +$agentName = (string)$mdl->general->AgentName; +$insecure = (string)$mdl->general->Insecure; + +if (empty($interval)) { + $interval = "60"; +} + +echo "# RMM Agent Configuration — auto-generated\n"; +echo "# Do not edit — changes via OPNsense WebGUI\n\n"; +echo "backend_url: \"{$backendUrl}\"\n"; +echo "api_key: \"{$apiKey}\"\n"; +echo "interval_seconds: {$interval}\n"; + +if (!empty($agentName)) { + echo "agent_name: \"{$agentName}\"\n"; +} + +// insecure is handled via command-line flag, not config diff --git a/opnsense-plugin/src/opnsense/scripts/rmmagent/setup.sh b/opnsense-plugin/src/opnsense/scripts/rmmagent/setup.sh new file mode 100755 index 0000000..0625cba --- /dev/null +++ b/opnsense-plugin/src/opnsense/scripts/rmmagent/setup.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# RMM Agent — generate config and manage service + +CONFIGDIR="/usr/local/rmm" +CONFIGFILE="${CONFIGDIR}/config.yaml" +# Generate config from model +mkdir -p "${CONFIGDIR}" +/usr/local/bin/php /usr/local/opnsense/scripts/OPNsense/RMMAgent/generate_config.php > "${CONFIGFILE}" + +# Check if enabled +ENABLED=$(/usr/local/sbin/pluginctl -g OPNsense.rmmagent.general.Enabled 2>/dev/null) + +# Check insecure flag +INSECURE=$(/usr/local/sbin/pluginctl -g OPNsense.rmmagent.general.Insecure 2>/dev/null) +if [ "${INSECURE}" = "1" ]; then + /usr/sbin/sysrc rmm_agent_insecure="YES" > /dev/null 2>&1 +else + /usr/sbin/sysrc rmm_agent_insecure="NO" > /dev/null 2>&1 +fi + +if [ "${ENABLED}" = "1" ]; then + /usr/sbin/sysrc rmm_agent_enable="YES" > /dev/null 2>&1 + /usr/local/etc/rc.d/rmm_agent restart + echo "RMM Agent started" +else + /usr/local/etc/rc.d/rmm_agent stop 2>/dev/null + /usr/sbin/sysrc rmm_agent_enable="NO" > /dev/null 2>&1 + echo "RMM Agent stopped" +fi diff --git a/opnsense-plugin/src/opnsense/service/conf/actions.d/actions_rmmagent.conf b/opnsense-plugin/src/opnsense/service/conf/actions.d/actions_rmmagent.conf new file mode 100644 index 0000000..f264498 --- /dev/null +++ b/opnsense-plugin/src/opnsense/service/conf/actions.d/actions_rmmagent.conf @@ -0,0 +1,29 @@ +[start] +command:/usr/local/etc/rc.d/rmm_agent start +parameters: +type:script +message:Starting RMM Agent + +[stop] +command:/usr/local/etc/rc.d/rmm_agent stop +parameters: +type:script +message:Stopping RMM Agent + +[restart] +command:/usr/local/etc/rc.d/rmm_agent restart +parameters: +type:script +message:Restarting RMM Agent + +[status] +command:/usr/local/etc/rc.d/rmm_agent status; exit 0 +parameters: +type:script_output +message:Request RMM Agent status + +[reconfigure] +command:/usr/local/opnsense/scripts/rmmagent/setup.sh +parameters: +type:script +message:Reconfiguring RMM Agent diff --git a/opnsense-plugin/src/usr/local/etc/rc.d/rmm_agent b/opnsense-plugin/src/usr/local/etc/rc.d/rmm_agent new file mode 100755 index 0000000..d2b6df5 --- /dev/null +++ b/opnsense-plugin/src/usr/local/etc/rc.d/rmm_agent @@ -0,0 +1,74 @@ +#!/bin/sh +# +# PROVIDE: rmm_agent +# REQUIRE: NETWORKING +# KEYWORD: shutdown +# +# Add the following to /etc/rc.conf to enable: +# rmm_agent_enable="YES" + +. /etc/rc.subr + +name="rmm_agent" +rcvar="rmm_agent_enable" + +load_rc_config ${name} + +: ${rmm_agent_enable:="NO"} +: ${rmm_agent_config:="/usr/local/rmm/config.yaml"} +: ${rmm_agent_logfile:="/var/log/rmm-agent.log"} +: ${rmm_agent_insecure:="YES"} + +pidfile="/var/run/${name}.pid" +command="/usr/local/rmm/rmm-agent" + +start_cmd="${name}_start" +stop_cmd="${name}_stop" +status_cmd="${name}_status" + +rmm_agent_start() +{ + if [ ! -f "${command}" ]; then + echo "rmm-agent binary not found: ${command}" + return 1 + fi + + if [ ! -f "${rmm_agent_config}" ]; then + echo "Config not found: ${rmm_agent_config}" + return 1 + fi + + echo "Starting ${name}." + + INSECURE_FLAG="" + if checkyesno rmm_agent_insecure; then + INSECURE_FLAG="--insecure" + fi + + /usr/sbin/daemon -f -p ${pidfile} -o ${rmm_agent_logfile} \ + ${command} --config ${rmm_agent_config} ${INSECURE_FLAG} +} + +rmm_agent_stop() +{ + if [ -f "${pidfile}" ]; then + echo "Stopping ${name}." + kill $(cat ${pidfile}) 2>/dev/null + rm -f ${pidfile} + else + echo "${name} is not running." + fi +} + +rmm_agent_status() +{ + if [ -f "${pidfile}" ] && kill -0 $(cat ${pidfile}) 2>/dev/null; then + PID=$(cat ${pidfile}) + echo "${name} is running as pid ${PID}." + else + echo "${name} is not running." + return 1 + fi +} + +run_rc_command "$1"