registerFunction($moduleName, 'table', function (string $name): string { $prefix = 'picontrol_'; $sanitized = preg_replace('/[^a-zA-Z0-9_]/', '', $name); return $prefix . $sanitized; }); $mm->registerFunction($moduleName, 'pdo', function () use ($moduleName): \PDO { $settings = modules()->settings($moduleName); $useSeparate = !empty($settings['use_separate_db']); if ($useSeparate) { // Uses module-specific DB config $module = modules()->get($moduleName); $fallback = $module['db_defaults'] ?? []; return modules()->modulePdo($moduleName, $fallback); } $base = app()->basePdo(); if (!$base) { throw new ModuleConfigException( $moduleName, 'Base-DB ist deaktiviert. Bitte Base-DB aktivieren oder eigene Modul-DB konfigurieren.' ); } return $base; }); $mm->registerFunction($moduleName, 'redis', function () use ($moduleName) { $settings = modules()->settings($moduleName); $redis = (array)($settings['redis'] ?? []); $host = (string)($redis['host'] ?? ($settings['redis.host'] ?? getenv('PI_CONTROL_REDIS_HOST') ?: 'redis')); $port = (int)($redis['port'] ?? ($settings['redis.port'] ?? (getenv('PI_CONTROL_REDIS_PORT') !== false ? (int)getenv('PI_CONTROL_REDIS_PORT') : 6379))); $password = (string)($redis['password'] ?? ($settings['redis.password'] ?? getenv('PI_CONTROL_REDIS_PASSWORD') ?: '')); $db = (int)($redis['db'] ?? ($settings['redis.db'] ?? (getenv('PI_CONTROL_REDIS_DB') !== false ? (int)getenv('PI_CONTROL_REDIS_DB') : 0))); return new \App\RedisClient($host, $port, $password !== '' ? $password : null, $db); }); $mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName): void { $pdo = module_fn($moduleName, 'pdo'); $table = fn(string $name) => module_fn($moduleName, 'table', $name); $driver = (string)$pdo->getAttribute(PDO::ATTR_DRIVER_NAME); $hostTable = $table('hosts'); $cmdTable = $table('commands'); $runTable = $table('runs'); $sessionTable = $table('sessions'); if ($driver === 'pgsql') { $pdo->exec("CREATE TABLE IF NOT EXISTS {$hostTable} ( id SERIAL PRIMARY KEY, name VARCHAR(120) NOT NULL, host VARCHAR(255) NOT NULL, port INTEGER NOT NULL DEFAULT 22, username VARCHAR(120) NOT NULL, auth_type VARCHAR(20) NOT NULL DEFAULT 'key', key_path TEXT NULL, password TEXT NULL, image_url TEXT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$cmdTable} ( id SERIAL PRIMARY KEY, label VARCHAR(160) NOT NULL, command TEXT NOT NULL, admin_only BOOLEAN NOT NULL DEFAULT false, timeout_sec INTEGER NULL, sort_order INTEGER NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$runTable} ( id SERIAL PRIMARY KEY, host_id INTEGER NULL, command_id INTEGER NULL, command_text TEXT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', output TEXT NULL, error TEXT NULL, exit_code INTEGER NULL, timeout_sec INTEGER NULL, created_by VARCHAR(120) NULL, started_at TIMESTAMP NULL, finished_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$sessionTable} ( id SERIAL PRIMARY KEY, token VARCHAR(64) NOT NULL UNIQUE, host_id INTEGER NOT NULL, provider VARCHAR(20) NOT NULL DEFAULT 'ttyd', command_text TEXT NULL, created_by VARCHAR(120) NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, last_used_at TIMESTAMP NULL )"); } else { $pdo->exec("CREATE TABLE IF NOT EXISTS {$hostTable} ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(120) NOT NULL, host VARCHAR(255) NOT NULL, port INTEGER NOT NULL DEFAULT 22, username VARCHAR(120) NOT NULL, auth_type VARCHAR(20) NOT NULL DEFAULT 'key', key_path TEXT NULL, password TEXT NULL, image_url TEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$cmdTable} ( id INTEGER PRIMARY KEY AUTOINCREMENT, label VARCHAR(160) NOT NULL, command TEXT NOT NULL, admin_only INTEGER NOT NULL DEFAULT 0, timeout_sec INTEGER NULL, sort_order INTEGER NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$runTable} ( id INTEGER PRIMARY KEY AUTOINCREMENT, host_id INTEGER NULL, command_id INTEGER NULL, command_text TEXT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', output TEXT NULL, error TEXT NULL, exit_code INTEGER NULL, timeout_sec INTEGER NULL, created_by VARCHAR(120) NULL, started_at DATETIME NULL, finished_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$sessionTable} ( id INTEGER PRIMARY KEY AUTOINCREMENT, token VARCHAR(64) NOT NULL UNIQUE, host_id INTEGER NOT NULL, provider VARCHAR(20) NOT NULL DEFAULT 'ttyd', command_text TEXT NULL, created_by VARCHAR(120) NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME NOT NULL, last_used_at DATETIME NULL )"); } // Schema migrations for existing tables if ($driver === 'pgsql') { $pdo->exec("ALTER TABLE {$hostTable} ADD COLUMN IF NOT EXISTS image_url TEXT NULL"); $pdo->exec("ALTER TABLE {$cmdTable} ADD COLUMN IF NOT EXISTS timeout_sec INTEGER NULL"); $pdo->exec("ALTER TABLE {$cmdTable} ADD COLUMN IF NOT EXISTS sort_order INTEGER NULL"); $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS error TEXT NULL"); $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS exit_code INTEGER NULL"); $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS timeout_sec INTEGER NULL"); $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS started_at TIMESTAMP NULL"); $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS finished_at TIMESTAMP NULL"); $pdo->exec("ALTER TABLE {$sessionTable} ADD COLUMN IF NOT EXISTS command_text TEXT NULL"); } else { $columns = []; $stmt = $pdo->query('PRAGMA table_info(' . $hostTable . ')'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { $columns[$col['name']] = true; } if (empty($columns['image_url'])) { $pdo->exec("ALTER TABLE {$hostTable} ADD COLUMN image_url TEXT NULL"); } $columns = []; $stmt = $pdo->query('PRAGMA table_info(' . $cmdTable . ')'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { $columns[$col['name']] = true; } if (empty($columns['timeout_sec'])) { $pdo->exec("ALTER TABLE {$cmdTable} ADD COLUMN timeout_sec INTEGER NULL"); } if (empty($columns['sort_order'])) { $pdo->exec("ALTER TABLE {$cmdTable} ADD COLUMN sort_order INTEGER NULL"); } $columns = []; $stmt = $pdo->query('PRAGMA table_info(' . $runTable . ')'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { $columns[$col['name']] = true; } if (empty($columns['error'])) { $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN error TEXT NULL"); } if (empty($columns['exit_code'])) { $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN exit_code INTEGER NULL"); } if (empty($columns['timeout_sec'])) { $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN timeout_sec INTEGER NULL"); } if (empty($columns['started_at'])) { $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN started_at DATETIME NULL"); } if (empty($columns['finished_at'])) { $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN finished_at DATETIME NULL"); } $columns = []; $stmt = $pdo->query('PRAGMA table_info(' . $sessionTable . ')'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { $columns[$col['name']] = true; } if (empty($columns['command_text'])) { $pdo->exec("ALTER TABLE {$sessionTable} ADD COLUMN command_text TEXT NULL"); } } // Seed default commands (only when empty) $count = (int)$pdo->query('SELECT COUNT(*) FROM ' . $cmdTable)->fetchColumn(); if ($count === 0) { $defaults = [ ['Speicherplatz auf Dateisystem', 'df -T -h', false], ['Netzwerkdaten', 'ip -s addr show', false], ['CPU-Information', 'cat /proc/cpuinfo', false], ['Informationen über USB-Bus', 'lsusb', false], ['Uptime', 'uptime -p', false], ['Scannen Sie einen I2C-Bus nach Geräten', '/usr/sbin/i2cdetect -y 1', true], ['Prozesse (Gib zum Beenden \"q\" ein)', 'top', false], ['SSH status', 'systemctl status ssh', false], ['Konfigurationsprogramm in Raspberry OS', 'sudo raspi-config', true], ['Pinbelegung', 'pinout', false], ['Exportiere GPIO (Für Nutzung vorbereiten)', 'echo 22 > /sys/class/gpio/export', true], ['Lese GPIO-Wert', 'echo in > /sys/class/gpio/gpio22/direction && cat /sys/class/gpio/gpio22/value', true], ['Schreibe \"Low\" GPIO Wert', 'echo out > /sys/class/gpio/gpio22/direction && echo 0 > /sys/class/gpio/gpio22/value', true], ['Schreibe \"High\" GPIO Wert', 'echo out > /sys/class/gpio/gpio22/direction && echo 1 > /sys/class/gpio/gpio22/value', true], ['Export entfernen (Ressource freigeben)', 'echo 22 > /sys/class/gpio/unexport', true], ['Alternative Funktionen von GPIO', 'raspi-gpio funcs', false], ['Gerät herunterfahren', 'sudo systemctl poweroff', true], ['Gerät neu starten', 'sudo systemctl reboot', true], ['Aktualisiere die Betriebssystem-Pakete', 'sudo apt update && sudo apt full-upgrade', true], ['Befehle, die zuvor ausgeführt wurden (Quellsprache)', 'history 30', false], ['Liste der zuletzt angemeldeten Nutzer', 'last -30 -F', false], ['Liste der aktuell angemeldeten Nutzer', 'w', false], ['Aktualisierung', 'sudo apt update && sudo apt upgrade -y && sudo apt-get autoremove --purge && sudo apt-get clean && sudo rm -rf /var/lib/apt/lists/* && sudo apt-get update && sudo apt-get remove texlive-*-doc', true], ['Update OS', 'sudo apt update && sudo apt full-upgrade -y && sudo apt-get autoremove --purge && sudo apt-get clean && sudo rm -rf /var/lib/apt/lists/* && sudo apt-get update && sudo apt-get remove texlive-*-doc', true], ['Paketlisten laden', 'sudo apt-get update; sudo apt-get dist-upgrade -y;exit', true], ['Gerät herunterfahren', 'sudo /sbin/shutdown -h now', true], ['Gerät neu starten', 'sudo /sbin/reboot', true], ]; $stmt = $pdo->prepare( 'INSERT INTO ' . $cmdTable . ' (label, command, admin_only) VALUES (:label, :command, :admin_only)' ); foreach ($defaults as [$label, $command, $adminOnly]) { $stmt->bindValue(':label', $label, PDO::PARAM_STR); $stmt->bindValue(':command', $command, PDO::PARAM_STR); $stmt->bindValue(':admin_only', (bool)$adminOnly, PDO::PARAM_BOOL); $stmt->execute(); } } });