Http::clientIp()]); Http::fail( 401, 'unauthorized', 'Migration nur fuer angemeldete Administratoren oder mit gueltigem Shared Key.' ); } // --- Migrationen ausfuehren --- $result = Migrator::migrate($db); if ($result['failed'] !== null) { Logger::error('Migration abgebrochen', $result['failed']); $payload = [ 'applied' => $result['applied'], 'skipped' => $result['skipped'], 'failed' => Config::isDebug() ? $result['failed'] : ['version' => $result['failed']['version'], 'message' => 'Details stehen im Log unter var/log/.'], ]; if ($isCli) { fwrite(STDERR, "Migration fehlgeschlagen:\n" . json_encode($payload, JSON_PRETTY_PRINT) . "\n"); exit(1); } Http::fail(500, 'migration_failed', 'Die Migration wurde abgebrochen.', null, $payload); } // --- Administrator nur anlegen, wenn noch keiner existiert --- $adminNotice = null; $userCount = (int)$db->query('SELECT COUNT(*) FROM dc_users')->fetchColumn(); if ($userCount === 0) { $username = 'admin'; $password = generateInitialPassword(); $stmt = $db->prepare(' INSERT INTO dc_users (username, password_hash, created_at) VALUES (:username, :hash, UTC_TIMESTAMP()) '); $stmt->execute([':username' => $username, ':hash' => password_hash($password, PASSWORD_DEFAULT)]); Logger::info('Initialer Administrator angelegt', ['username' => $username]); $adminNotice = [ 'username' => $username, 'password' => $password, 'warning' => 'Dieses Passwort wird nur ein einziges Mal angezeigt. Bitte sofort notieren und nach der ersten Anmeldung aendern.', ]; } else { $adminNotice = [ 'message' => sprintf( '%d Benutzerkonto(en) vorhanden - es wurde keines angelegt und keines veraendert.', $userCount ), ]; } // --- Standard-Endpunkte hinterlegen --- $endpoints = json_encode([ 'validate' => '/api/license/v1/validate', 'deactivate' => '/api/license/v1/deactivate', ], JSON_UNESCAPED_SLASHES); $db->prepare(' INSERT INTO dc_settings (skey, svalue) VALUES ("endpoints", :value) ON DUPLICATE KEY UPDATE svalue = VALUES(svalue) ')->execute([':value' => $endpoints]); $tables = $db->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN) ?: []; $response = [ 'applied' => $result['applied'], 'skipped' => $result['skipped'], 'tables' => $tables, 'admin' => $adminNotice, 'next_steps' => [ 'Cron einrichten: * * * * * curl -fsS -H "Authorization: Bearer " ' . Http::baseUrl() . '/api/watchdog/v1/evaluate > /dev/null', 'Master-Token im WebUI unter "Token-Verwaltung" erzeugen und an die Agenten verteilen.', ], 'timestamp_utc' => gmdate('c'), ]; if ($isCli) { echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), "\n"; exit(0); } Http::ok($response); /** * Erzeugt ein gut lesbares, ausreichend starkes Initialpasswort. * Zeichen, die sich leicht verwechseln lassen, sind ausgeschlossen. */ function generateInitialPassword(int $length = 20): string { $alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'; $max = strlen($alphabet) - 1; $password = ''; for ($i = 0; $i < $length; $i++) { $password .= $alphabet[random_int(0, $max)]; } return $password; }