Quelltext des aktuellen Standes
<?php
// Cron-Script: Verzeichnis-Synchronisation (Entra / LDAP)
//
// Dieses Script sollte regelmaessig per Cron ausgefuehrt werden (z.B. alle 30 Minuten):
// */30 * * * * /usr/bin/php /pfad/zu/bin/sync-directory.php >> /pfad/zu/storage/logs/directory-sync.log 2>&1
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../bootstrap.php';
use OpenNIT\Audit\AuditLogger;
use OpenNIT\Core\Database;
use OpenNIT\Directory\DirectoryConnectionManager;
use OpenNIT\Directory\DirectorySyncRunner;
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
$dotenv->load();
$dbConfig = require __DIR__ . '/../config/database.php';
$runtimeConfig = [];
$runtimeFile = __DIR__ . '/../config/runtime.php';
if (file_exists($runtimeFile)) {
$runtimeConfig = include $runtimeFile;
}
$config = $runtimeConfig;
$db = new Database($dbConfig);
// Zeitzone aus Konfiguration setzen
$timezone = $config['app']['timezone'] ?? 'Europe/Berlin';
date_default_timezone_set($timezone);
// Cron-Lauf tracken (vor dem Source-Check, damit jeder Aufruf sichtbar wird)
$runId = null;
try {
$runId = (int)$db->insert('cronjob_runs', [
'job_name' => 'directory_sync',
'started_at' => date('Y-m-d H:i:s'),
'status' => 'running',
]);
} catch (\Throwable) {}
$appKey = (string)($config['app']['key'] ?? ($_ENV['APP_KEY'] ?? ''));
$manager = new DirectoryConnectionManager($db, $appKey);
$manager->adoptLegacyConfig($config);
$anbindungen = $manager->enabled();
if ($anbindungen === []) {
$msg = 'Keine Verzeichnis-Anbindung eingerichtet oder eingeschaltet – abgebrochen.';
echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
if ($runId) {
try {
$db->update('cronjob_runs', [
'finished_at' => date('Y-m-d H:i:s'),
'status' => 'success',
'message' => $msg,
'items_processed' => 0,
], 'id = ?', [$runId]);
} catch (\Throwable) {}
}
exit(0);
}
echo '[' . date('Y-m-d H:i:s') . '] Starte Verzeichnis-Abgleich über '
. count($anbindungen) . ' Anbindung(en)…' . PHP_EOL;
try {
$lauf = (new DirectorySyncRunner($db, new AuditLogger($db), $config, $appKey))->run();
foreach ($lauf['results'] as $einzeln) {
echo '[' . date('Y-m-d H:i:s') . '] ' . $einzeln['name'] . ': ' . $einzeln['message'] . PHP_EOL;
}
$msg = ($lauf['ok'] ? 'OK: ' : 'FEHLER: ') . $lauf['message'];
echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
if ($runId) {
$db->update('cronjob_runs', [
'finished_at' => date('Y-m-d H:i:s'),
'status' => $lauf['ok'] ? 'success' : 'error',
'message' => mb_substr($msg, 0, 1000),
'items_processed' => $lauf['imported'] + $lauf['updated'],
], 'id = ?', [$runId]);
}
} catch (\Throwable $e) {
$msg = 'Exception: ' . $e->getMessage();
echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
if ($runId) {
$db->update('cronjob_runs', [
'finished_at' => date('Y-m-d H:i:s'),
'status' => 'error',
'message' => $msg,
], 'id = ?', [$runId]);
}
exit(1);
}
// Der Lizenzabgleich hängt an Entra – er läuft, sobald eine Entra-Anbindung dabei ist.
$source = '';
foreach ($anbindungen as $anbindung) {
if ($anbindung['type'] === 'entra') {
$source = 'entra';
break;
}
}
// ────────────────────────────────────────────────────────────────────────────
// Optional: M365-Lizenzen (subscribedSkus) im selben Lauf abgleichen.
// Nur wenn Quelle = entra UND Toggle "licenses.entra_sync_enabled" aktiv.
// Fallback auf den alten Key "directory.sync_licenses" (Bestandsinstalls).
// (Den modules.licenses-Check haben wir bewusst rausgelassen — der Cron
// laedt nur runtime.php, das Modul-Default aus public/index.php fehlt hier
// und fuehrte dazu, dass der Sync wortlos uebersprungen wurde.)
// ────────────────────────────────────────────────────────────────────────────
$licenseSyncToggle = $config['licenses']['entra_sync_enabled']
?? $config['directory']['sync_licenses']
?? false;
$syncLicenses = !empty($licenseSyncToggle) && $source === 'entra';
if ($syncLicenses) {
$licRunId = null;
try {
$licRunId = (int)$db->insert('cronjob_runs', [
'job_name' => 'entra_license_sync',
'started_at' => date('Y-m-d H:i:s'),
'status' => 'running',
]);
} catch (\Throwable) {}
echo '[' . date('Y-m-d H:i:s') . '] Starte Entra-Lizenz-Sync…' . PHP_EOL;
try {
$auditLogger = new \OpenNIT\Audit\AuditLogger($db, null);
$licSync = new \OpenNIT\Directory\EntraLicenseSync($db, $auditLogger, $config);
$licResult = $licSync->run();
$licMsg = empty($licResult['errors'])
? "OK: {$licResult['imported']} neu, {$licResult['updated']} aktualisiert, {$licResult['skipped']} uebersprungen."
: 'FEHLER: ' . implode('; ', $licResult['errors']);
echo '[' . date('Y-m-d H:i:s') . '] Lizenz-Sync ' . $licMsg . PHP_EOL;
if ($licRunId) {
$db->update('cronjob_runs', [
'finished_at' => date('Y-m-d H:i:s'),
'status' => empty($licResult['errors']) ? 'success' : 'error',
'message' => $licMsg,
'items_processed' => $licResult['imported'] + $licResult['updated'],
], 'id = ?', [$licRunId]);
}
} catch (\Throwable $e) {
$licMsg = 'Exception: ' . $e->getMessage();
echo '[' . date('Y-m-d H:i:s') . '] Lizenz-Sync ' . $licMsg . PHP_EOL;
if ($licRunId) {
$db->update('cronjob_runs', [
'finished_at' => date('Y-m-d H:i:s'),
'status' => 'error',
'message' => $licMsg,
], 'id = ?', [$licRunId]);
}
// Lizenz-Sync-Fehler darf den Personen-Sync-Erfolg nicht zerstoeren.
}
}