fix(installer): avoid staged and installed capture class collision

This commit is contained in:
ENELIX Agent
2026-10-03 07:34:44 +00:00
parent fe3f46ba0e
commit 6d9aba74b0
2 changed files with 105 additions and 0 deletions
@@ -0,0 +1,71 @@
--- a/install.php
+++ b/install.php
@@ -19,6 +19,8 @@
}
$before=json_decode(IPS_GetConfiguration($manager),true,128,JSON_THROW_ON_ERROR);
$report['files']=v4StageInstall(__DIR__,'/var/lib/symcon/modules/Enelix-EMS');
+ require_once __DIR__.'/installed_capture_bootstrap.php';
+ $report['classLoading']=v4ApplicationLoadInstalledCapture('/var/lib/symcon/modules/Enelix-EMS',__DIR__.'/MANIFEST.json');
$controls=[];
foreach (IPS_GetModuleList() as $mid) if ((IPS_GetModule($mid)['Prefix']??'')==='MC') $controls=array_merge($controls,IPS_GetInstanceListByModuleID($mid));
if (count($controls)!==1||!function_exists('MC_ReloadModule')) throw new RuntimeException('Module Control nicht eindeutig.');
@@ -38,7 +40,7 @@
$marker=$dir.'/observer-import.json';
if (!is_file($marker)&&!IPS_GetProperty($manager,'NetzfahrplanV4MessdatenAktiv')) {
if (glob($dir.'/raw-*.jsonl')!==[]) throw new RuntimeException('Messdaten ohne Importabschluss vorhanden; nicht doppelt importieren.');
- require_once __DIR__.'/source/libs/NetzfahrplanV4Messaufnahme.php';
+ // Reuse the hash-checked installed class; never load the staged copy.
$count=0;$last=0;$cutoff=time()-172800;
$files=glob('/srv/agent/netplan-v4-separated-observer-stage/data/raw-*.jsonl');sort($files,SORT_STRING);
foreach ($files as $file) {
--- /dev/null
+++ b/installed_capture_bootstrap.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/** Installer-only bootstrap. Load the same files the installed Manager uses.
+ * No IPS calls, source edits, state changes, or autoload of an unknown class.
+ */
+function v4ApplicationLoadInstalledCapture(string $target, string $manifestFile): array
+{
+ if (is_link($target) || realpath($target) !== $target || !is_dir($target)) {
+ throw new RuntimeException('Unerwartetes Modulverzeichnis.');
+ }
+ $manifest = json_decode((string) file_get_contents($manifestFile), true, 64, JSON_THROW_ON_ERROR);
+ if (($manifest['mode'] ?? null) !== 'data_application_only') {
+ throw new RuntimeException('Unerwarteter Installationsumfang.');
+ }
+ $classes = [
+ 'Belevo\\EnelixEMS\\NetzfahrplanV4Bilanzierung' => 'libs/NetzfahrplanV4Bilanzierung.php',
+ 'Belevo\\EnelixEMS\\NetzfahrplanV4Messaufnahme' => 'libs/NetzfahrplanV4Messaufnahme.php',
+ ];
+ // Check BOTH origins before loading either file, including the transitive dependency.
+ foreach ($classes as $class => $relative) {
+ $path = $target . '/' . $relative;
+ $expected = $manifest['files'][$relative]['after'] ?? null;
+ if (!is_string($expected) || !preg_match('/^[a-f0-9]{64}$/D', $expected)
+ || is_link($path) || !is_file($path) || realpath($path) !== $path
+ || hash_file('sha256', $path) !== $expected) {
+ throw new RuntimeException('Installierte Messbibliothek weicht vom geprueften Paket ab: ' . $relative);
+ }
+ if (class_exists($class, false)) {
+ $origin = (new ReflectionClass($class))->getFileName();
+ if ($origin !== $path) {
+ throw new RuntimeException('Messbibliothek bereits aus anderem Pfad geladen. Installationsaufruf separat ausfuehren, ohne weitere Sammler-Includes.');
+ }
+ }
+ }
+ foreach ($classes as $class => $relative) {
+ $path = $target . '/' . $relative;
+ if (!class_exists($class, false)) {
+ require_once $path;
+ }
+ if (!class_exists($class, false) || (new ReflectionClass($class))->getFileName() !== $path) {
+ throw new RuntimeException('Installierte Messbibliothek konnte nicht eindeutig geladen werden.');
+ }
+ }
+ return ['source' => 'installed_module_only', 'stagedClassesLoaded' => false,
+ 'files' => array_values($classes)];
+}