rmm2/opnsense-plugin/install.sh

671 lines
20 KiB
Bash
Executable File

#!/bin/sh
#
# RMM Agent Plugin Installer fuer OPNsense
#
# Usage (automatischer Download):
# fetch -o /tmp/install.sh "https://git.cynfo.net/christian/rmm/raw/branch/main/opnsense-plugin/install.sh"
# sh /tmp/install.sh
#
# Oder mit lokalen Binaries:
# scp install.sh rmm-agent rmm-updater-freebsd root@opnsense:/tmp/
# ssh root@opnsense '/bin/sh /tmp/install.sh'
#
set -e
BASEDIR="/tmp"
BINARY="${BASEDIR}/rmm-agent"
UPDATER_BINARY="${BASEDIR}/rmm-updater"
BACKEND_URL="${RMM_BACKEND:-https://dsbmueller.spdns.org:8443}"
API_KEY="${RMM_API_KEY:-01532e5a7c9e70bf2757df77a2f5b9b9}"
echo "========================================"
echo " RMM Agent Plugin Installer"
echo "========================================"
# Binaries herunterladen wenn nicht lokal vorhanden
if [ ! -f "${BINARY}" ]; then
echo "[*] Binaries nicht lokal gefunden — lade vom Backend..."
/usr/bin/fetch --no-verify-peer --no-verify-hostname -o "${BASEDIR}/rmm-bundle.zip" \
"${BACKEND_URL}/api/v1/firmware/download?platform=freebsd&api_key=${API_KEY}"
if [ ! -f "${BASEDIR}/rmm-bundle.zip" ]; then
echo "FEHLER: Download fehlgeschlagen."
echo "Pruefe Backend-Erreichbarkeit: ${BACKEND_URL}"
exit 1
fi
cd "${BASEDIR}"
unzip -o rmm-bundle.zip 2>/dev/null || true
# ZIP enthaelt rmm-agent und rmm-updater-freebsd
if [ -f "${BASEDIR}/rmm-updater-freebsd" ] && [ ! -f "${UPDATER_BINARY}" ]; then
mv "${BASEDIR}/rmm-updater-freebsd" "${UPDATER_BINARY}"
fi
rm -f rmm-bundle.zip
echo " Download OK"
if [ ! -f "${BINARY}" ]; then
echo "FEHLER: ${BINARY} nach Download nicht vorhanden."
echo "Firmware-ZIP muss 'rmm-agent' enthalten."
exit 1
fi
fi
# Alten Agent stoppen falls vorhanden
echo "[1/8] Alte Dienste stoppen..."
/usr/local/etc/rc.d/rmm_updater stop 2>/dev/null || true
/usr/local/etc/rc.d/rmm_agent stop 2>/dev/null || true
pkill -9 -f rmm-agent 2>/dev/null || true
pkill -9 -f rmm-updater 2>/dev/null || true
# Binaries installieren
echo "[2/8] Binaries installieren..."
mkdir -p /usr/local/rmm
cp "${BINARY}" /usr/local/rmm/rmm-agent
chmod +x /usr/local/rmm/rmm-agent
if [ -f "${UPDATER_BINARY}" ]; then
cp "${UPDATER_BINARY}" /usr/local/rmm/rmm-updater
chmod +x /usr/local/rmm/rmm-updater
echo " Updater installiert"
else
echo " Updater-Binary nicht gefunden — uebersprungen"
fi
# rc.d Script
echo "[3/8] rc.d Service installieren..."
cat > /usr/local/etc/rc.d/rmm_agent << 'RCEOF'
#!/bin/sh
#
# PROVIDE: rmm_agent
# REQUIRE: NETWORKING
# KEYWORD: shutdown
. /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
echo "${name} is running as pid $(cat ${pidfile})."
else
echo "${name} is not running."
return 1
fi
}
run_rc_command "$1"
RCEOF
chmod +x /usr/local/etc/rc.d/rmm_agent
# Updater rc.d Service
cat > /usr/local/etc/rc.d/rmm_updater << 'RCEOF2'
#!/bin/sh
#
# PROVIDE: rmm_updater
# REQUIRE: NETWORKING rmm_agent
# KEYWORD: shutdown
. /etc/rc.subr
name="rmm_updater"
rcvar="${name}_enable"
command="/usr/local/rmm/rmm-updater"
pidfile="/var/run/${name}.pid"
logfile="/var/log/rmm-updater.log"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"
rmm_updater_start()
{
if [ ! -f "${command}" ]; then
echo "rmm-updater binary not found: ${command}"
return 1
fi
if [ -f "$pidfile" ] && kill -0 $(cat "$pidfile") 2>/dev/null; then
echo "${name} already running"
return 0
fi
echo "Starting ${name}."
/usr/sbin/daemon -f -p "$pidfile" -o "$logfile" "$command"
}
rmm_updater_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_updater_status()
{
if [ -f "${pidfile}" ] && kill -0 $(cat ${pidfile}) 2>/dev/null; then
echo "${name} is running as pid $(cat ${pidfile})."
else
echo "${name} is not running."
return 1
fi
}
load_rc_config $name
: ${rmm_updater_enable:="NO"}
run_rc_command "$1"
RCEOF2
chmod +x /usr/local/etc/rc.d/rmm_updater
# MVC Plugin installieren
echo "[4/8] OPNsense MVC Plugin installieren..."
# Model
mkdir -p /usr/local/opnsense/mvc/app/models/OPNsense/RMMAgent/Menu
cat > /usr/local/opnsense/mvc/app/models/OPNsense/RMMAgent/RMMAgent.xml << 'XMLEOF'
<model>
<mount>//OPNsense/rmmagent</mount>
<version>1.0.0</version>
<description>RMM Agent Settings</description>
<items>
<general>
<Enabled type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</Enabled>
<BackendUrl type="TextField">
<Required>Y</Required>
<Mask>/^https?:\/\/.+/</Mask>
<ValidationMessage>Gueltige URL erforderlich (https://host:port)</ValidationMessage>
</BackendUrl>
<ApiKey type="TextField">
<Required>Y</Required>
<Mask>/^.{8,}$/</Mask>
<ValidationMessage>API-Key muss mindestens 8 Zeichen lang sein</ValidationMessage>
</ApiKey>
<AgentName type="TextField">
<Required>N</Required>
</AgentName>
<Interval type="IntegerField">
<Default>60</Default>
<MinimumValue>10</MinimumValue>
<MaximumValue>3600</MaximumValue>
<ValidationMessage>Intervall zwischen 10 und 3600 Sekunden</ValidationMessage>
</Interval>
<Insecure type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</Insecure>
</general>
</items>
</model>
XMLEOF
cat > /usr/local/opnsense/mvc/app/models/OPNsense/RMMAgent/RMMAgent.php << 'PHPEOF'
<?php
namespace OPNsense\RMMAgent;
use OPNsense\Base\BaseModel;
class RMMAgent extends BaseModel
{
}
PHPEOF
cat > /usr/local/opnsense/mvc/app/models/OPNsense/RMMAgent/Menu/Menu.xml << 'MENUEOF'
<menu>
<Services>
<RMMAgent VisibleName="RMM Agent" cssClass="fa fa-shield" url="/ui/rmmagent/general" order="99">
</RMMAgent>
</Services>
</menu>
MENUEOF
# Controllers
mkdir -p /usr/local/opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api
mkdir -p /usr/local/opnsense/mvc/app/controllers/OPNsense/RMMAgent/forms
cat > /usr/local/opnsense/mvc/app/controllers/OPNsense/RMMAgent/GeneralController.php << 'PHPEOF'
<?php
namespace OPNsense\RMMAgent;
use OPNsense\Base\IndexController;
class GeneralController extends IndexController
{
public function indexAction()
{
$this->view->generalForm = $this->getForm('general');
$this->view->pick('OPNsense/RMMAgent/general');
}
}
PHPEOF
cat > /usr/local/opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api/SettingsController.php << 'PHPEOF'
<?php
namespace OPNsense\RMMAgent\Api;
use OPNsense\Base\ApiControllerBase;
use OPNsense\RMMAgent\RMMAgent;
use OPNsense\Core\Config;
class SettingsController extends ApiControllerBase
{
public function getAction()
{
$mdl = new RMMAgent();
$result = array('rmmagent' => array('general' => array()));
foreach ($mdl->general->iterateItems() as $key => $node) {
$result['rmmagent']['general'][$key] = (string)$node;
}
return $result;
}
public function setAction()
{
$result = array('result' => 'failed');
if ($this->request->isPost()) {
$mdl = new RMMAgent();
$post = null;
// OPNsense sends data as rmmagent[general][KEY]
$rmmagent = $this->request->getPost('rmmagent');
if (!empty($rmmagent) && isset($rmmagent['general'])) {
$post = $rmmagent['general'];
}
// Fallback: JSON body
if (empty($post)) {
$rawBody = $this->request->getRawBody();
if (!empty($rawBody)) {
$jsonData = json_decode($rawBody, true);
if (isset($jsonData['rmmagent']['general'])) {
$post = $jsonData['rmmagent']['general'];
} elseif (isset($jsonData['general'])) {
$post = $jsonData['general'];
}
}
}
if (!empty($post)) {
foreach ($post as $key => $value) {
try {
$node = $mdl->general->$key;
if ($node !== null) {
$node->setValue($value);
}
} catch (\Exception $e) {
// skip
}
}
}
$valMsgs = $mdl->performValidation();
if ($valMsgs->count() > 0) {
$result['validations'] = array();
foreach ($valMsgs as $msg) {
$result['validations']['rmmagent.general.' . $msg->getField()] = $msg->getMessage();
}
} else {
$mdl->serializeToConfig();
Config::getInstance()->save();
$result['result'] = 'saved';
}
}
return $result;
}
}
PHPEOF
cat > /usr/local/opnsense/mvc/app/controllers/OPNsense/RMMAgent/Api/ServiceController.php << 'PHPEOF'
<?php
namespace OPNsense\RMMAgent\Api;
use OPNsense\Base\ApiControllerBase;
class ServiceController extends ApiControllerBase
{
public function statusAction()
{
$backend = new \OPNsense\Core\Backend();
$response = trim($backend->configdRun('rmmagent status'));
if (strpos($response, 'is running') !== false) {
return array('status' => 'running', 'message' => $response);
}
return array('status' => 'stopped', 'message' => $response);
}
public function startAction()
{
$backend = new \OPNsense\Core\Backend();
return array('status' => 'ok', 'message' => trim($backend->configdRun('rmmagent start')));
}
public function stopAction()
{
$backend = new \OPNsense\Core\Backend();
return array('status' => 'ok', 'message' => trim($backend->configdRun('rmmagent stop')));
}
public function restartAction()
{
$backend = new \OPNsense\Core\Backend();
return array('status' => 'ok', 'message' => trim($backend->configdRun('rmmagent restart')));
}
public function reconfigureAction()
{
$backend = new \OPNsense\Core\Backend();
return array('status' => 'ok', 'message' => trim($backend->configdRun('rmmagent reconfigure')));
}
}
PHPEOF
# Forms
cat > /usr/local/opnsense/mvc/app/controllers/OPNsense/RMMAgent/forms/general.xml << 'FORMEOF'
<form>
<field>
<id>rmmagent.general.Enabled</id>
<label>Aktiviert</label>
<type>checkbox</type>
<help>RMM Agent Dienst aktivieren/deaktivieren</help>
</field>
<field>
<id>rmmagent.general.BackendUrl</id>
<label>Backend URL</label>
<type>text</type>
<help>URL des RMM Backends (z.B. https://192.168.85.13:8443)</help>
</field>
<field>
<id>rmmagent.general.ApiKey</id>
<label>API Key</label>
<type>password</type>
<help>API-Schluessel fuer die Authentifizierung am Backend</help>
</field>
<field>
<id>rmmagent.general.AgentName</id>
<label>Agent Name</label>
<type>text</type>
<help>Optionaler Name (Standard: Hostname der Firewall)</help>
</field>
<field>
<id>rmmagent.general.Interval</id>
<label>Heartbeat Intervall (Sek.)</label>
<type>text</type>
<help>Intervall in Sekunden (Standard: 60)</help>
</field>
<field>
<id>rmmagent.general.Insecure</id>
<label>Self-Signed Zertifikate</label>
<type>checkbox</type>
<help>TLS-Pruefung deaktivieren (fuer self-signed Backend-Zertifikate)</help>
</field>
</form>
FORMEOF
# View
mkdir -p /usr/local/opnsense/mvc/app/views/OPNsense/RMMAgent
cat > /usr/local/opnsense/mvc/app/views/OPNsense/RMMAgent/general.volt << 'VOLTEOF'
<script>
$(document).ready(function () {
var data_get_map = {'frm_GeneralSettings': '/api/rmmagent/settings/get'};
mapDataToFormUI(data_get_map).done(function () {
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
});
ajaxCall('/api/rmmagent/service/status', {}, function (data, status) {
if (data && data['status']) {
var badge = data['status'] === 'running'
? '<span class="label label-success">Running</span>'
: '<span class="label label-danger">Stopped</span>';
$('#service_status_container').html(badge);
}
});
function refreshStatus() {
ajaxCall('/api/rmmagent/service/status', {}, function (data, status) {
if (data && data['status']) {
var badge = data['status'] === 'running'
? '<span class="label label-success">Running</span>'
: '<span class="label label-danger">Stopped</span>';
$('#service_status_container').html(badge);
}
});
}
$('#saveAct').unbind('click').click(function () {
saveFormToEndpoint('/api/rmmagent/settings/set', 'frm_GeneralSettings', function () {
$('#saveAct_progress').addClass('fa fa-spinner fa-pulse');
ajaxCall('/api/rmmagent/service/reconfigure', {}, function (data, status) {
$('#saveAct_progress').removeClass('fa fa-spinner fa-pulse');
setTimeout(refreshStatus, 2000);
});
});
});
$('#restartAct').unbind('click').click(function () {
$('#restartAct_progress').addClass('fa fa-spinner fa-pulse');
ajaxCall('/api/rmmagent/service/restart', {}, function (data, status) {
$('#restartAct_progress').removeClass('fa fa-spinner fa-pulse');
setTimeout(refreshStatus, 2000);
});
});
});
</script>
<div class="content-box" style="padding: 10px;">
<div class="content-box-header">
<h3>{{ lang._('RMM Agent') }} <small id="service_status_container"><span class="label label-default">...</span></small></h3>
</div>
<div class="content-box-main">
<div class="table-responsive">
{{ partial("layout_partials/base_form", ['fields': generalForm, 'id': 'frm_GeneralSettings']) }}
</div>
</div>
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="saveAct" type="button">
<b>{{ lang._('Speichern & Anwenden') }}</b> <i id="saveAct_progress"></i>
</button>
<button class="btn btn-default" id="restartAct" type="button">
<b>{{ lang._('Neustart') }}</b> <i id="restartAct_progress"></i>
</button>
</div>
</div>
VOLTEOF
# configd actions
echo "[5/8] configd Actions installieren..."
cat > /usr/local/opnsense/service/conf/actions.d/actions_rmmagent.conf << 'ACTEOF'
[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
ACTEOF
# Setup-Script
echo "[6/8] Setup-Script installieren..."
mkdir -p /usr/local/opnsense/scripts/rmmagent
cat > /usr/local/opnsense/scripts/rmmagent/setup.sh << 'SETUPEOF'
#!/bin/sh
CONFIGDIR="/usr/local/rmm"
CONFIGFILE="${CONFIGDIR}/config.yaml"
mkdir -p "${CONFIGDIR}"
# Config aus OPNsense Model generieren
BACKEND_URL=$(pluginctl -g OPNsense.rmmagent.general.BackendUrl 2>/dev/null)
API_KEY=$(pluginctl -g OPNsense.rmmagent.general.ApiKey 2>/dev/null)
INTERVAL=$(pluginctl -g OPNsense.rmmagent.general.Interval 2>/dev/null)
AGENT_NAME=$(pluginctl -g OPNsense.rmmagent.general.AgentName 2>/dev/null)
INSECURE=$(pluginctl -g OPNsense.rmmagent.general.Insecure 2>/dev/null)
ENABLED=$(pluginctl -g OPNsense.rmmagent.general.Enabled 2>/dev/null)
if [ -z "${INTERVAL}" ]; then
INTERVAL="60"
fi
cat > "${CONFIGFILE}" << EOF
backend_url: "${BACKEND_URL}"
api_key: "${API_KEY}"
interval_seconds: ${INTERVAL}
EOF
if [ -n "${AGENT_NAME}" ]; then
echo "agent_name: \"${AGENT_NAME}\"" >> "${CONFIGFILE}"
fi
# Insecure Flag
if [ "${INSECURE}" = "1" ]; then
sysrc rmm_agent_insecure="YES" > /dev/null 2>&1
else
sysrc rmm_agent_insecure="NO" > /dev/null 2>&1
fi
# Service steuern
if [ "${ENABLED}" = "1" ]; then
sysrc rmm_agent_enable="YES" > /dev/null 2>&1
/usr/local/etc/rc.d/rmm_agent restart
echo "RMM Agent gestartet"
else
/usr/local/etc/rc.d/rmm_agent stop 2>/dev/null
sysrc rmm_agent_enable="NO" > /dev/null 2>&1
echo "RMM Agent gestoppt"
fi
SETUPEOF
chmod +x /usr/local/opnsense/scripts/rmmagent/setup.sh
# Plugin Hook
echo "[7/8] Plugin-Hook registrieren..."
cat > /usr/local/etc/inc/plugins.inc.d/rmmagent.inc << 'INCEOF'
<?php
use OPNsense\RMMAgent\RMMAgent;
function rmmagent_services()
{
$services = array();
$mdl = new RMMAgent();
if ((string)$mdl->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;
}
INCEOF
# Initialen config.xml Eintrag erstellen (falls noch nicht vorhanden)
echo " Initialen config.xml Eintrag pruefen..."
cat > /tmp/rmm_init_config.php << 'INITEOF'
<?php
require_once("/usr/local/etc/inc/config.inc");
$config = OPNsense\Core\Config::getInstance();
$xml = $config->object();
if (!isset($xml->OPNsense->rmmagent)) {
$rmm = $xml->OPNsense->addChild('rmmagent');
$gen = $rmm->addChild('general');
$gen->addChild('Enabled', '0');
$gen->addChild('BackendUrl', '');
$gen->addChild('ApiKey', '');
$gen->addChild('AgentName', '');
$gen->addChild('Interval', '60');
$gen->addChild('Insecure', '1');
$config->save();
echo " Initialer rmmagent Eintrag in config.xml erstellt\n";
} else {
echo " rmmagent Eintrag bereits vorhanden\n";
}
INITEOF
php /tmp/rmm_init_config.php
rm -f /tmp/rmm_init_config.php
# Cache loeschen und configd neustarten
rm -f /var/lib/php/cache/_usr_local_opnsense_mvc_app_views_opnsense_rmmagent_*.php
service configd restart
# Updater aktivieren
echo "[8/8] Updater aktivieren..."
if [ -f /usr/local/rmm/rmm-updater ]; then
sysrc rmm_updater_enable=YES 2>/dev/null || true
/usr/local/etc/rc.d/rmm_updater start 2>/dev/null || true
echo " Updater aktiviert und gestartet"
else
echo " Kein Updater-Binary — uebersprungen"
fi
echo ""
echo "========================================"
echo " RMM Agent Plugin installiert!"
echo ""
echo " WebGUI: Services > RMM Agent"
echo " - Backend URL + API Key eintragen"
echo " - Aktiviert ankreuzen"
echo " - Speichern & Anwenden"
echo "========================================"