| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- #!/usr/bin/env php
- <?php
- declare(strict_types=1);
- // Command line interface for the manage client.
- //
- // Contains no logic of its own: every command calls the same public functions
- // the GUI panel and the host application use, so a feature behaves identically
- // however it is triggered.
- //
- // Usage:
- // php manage-client/bin/manage-client.php status
- // check
- // update [--force] [--yes] [--skip-hook]
- // migrate [--dry-run]
- // backup [--trigger=cron]
- // heartbeat
- //
- // Exit codes:
- // 0 success
- // 1 error (including a failed post-update step after a successful deploy)
- // 2 update available (check only)
- if (PHP_SAPI !== "cli") {
- http_response_code(403);
- exit("This script must be run from the command line.\n");
- }
- require_once dirname(__DIR__) . "/lib/client.php";
- $argv = $_SERVER["argv"] ?? [];
- array_shift($argv);
- $command = "";
- $flags = [];
- foreach ($argv as $argument) {
- if (str_starts_with($argument, "--")) {
- $parts = explode("=", substr($argument, 2), 2);
- $flags[$parts[0]] = $parts[1] ?? true;
- } elseif ($command === "") {
- $command = $argument;
- }
- }
- $quiet = isset($flags["quiet"]);
- function manageCliOut(string $line): void
- {
- global $quiet;
- if (!$quiet) {
- fwrite(STDOUT, $line . PHP_EOL);
- }
- }
- // Errors always print, even with --quiet, so a cron job still mails a failure.
- function manageCliError(string $line): void
- {
- fwrite(STDERR, $line . PHP_EOL);
- }
- function manageCliUsage(): void
- {
- fwrite(STDOUT, <<<TEXT
- Manage client
- status Übersicht: Version, Update, Backups, Migrationen
- check Prüft auf ein neues Release (Exit 2 = Update verfügbar)
- update [--force] [--yes] Spielt das aktuelle Release ein
- [--skip-hook] Nur Dateien ausrollen, ohne Migrationen/Hook
- migrate [--dry-run] Führt offene Migrationen aus
- backup [--trigger=cron] Erstellt ein Backup und lädt es hoch
- heartbeat Meldet den Status an den Manage-Server
- Optionen: --quiet unterdrückt die normale Ausgabe (Fehler weiterhin auf STDERR).
- TEXT);
- }
- function manageCliConfirm(string $question): bool
- {
- global $flags;
- if (isset($flags["yes"])) {
- return true;
- }
- fwrite(STDOUT, $question . " [j/N]: ");
- $answer = trim((string) fgets(STDIN));
- return in_array(strtolower($answer), ["j", "ja", "y", "yes"], true);
- }
- // Renders the migration/hook part of an update result. Shared by update and
- // migrate so both report a failure the same way.
- function manageCliReportHook(?array $hook): bool
- {
- if ($hook === null) {
- return true;
- }
- if (!empty($hook["skipped"])) {
- $pending = (int) ($hook["migrations"]["pending"] ?? 0);
- manageCliOut(" Post-Update übersprungen (--skip-hook)." .
- ($pending > 0 ? " Offene Migrationen: " . $pending : ""));
- return true;
- }
- $migrations = $hook["migrations"] ?? [];
- $applied = $migrations["applied"] ?? [];
- if ($applied !== []) {
- manageCliOut(" Migrationen ausgeführt: " . implode(", ", $applied));
- }
- if (!empty($hook["success"])) {
- if (!empty($hook["hook"]["configured"])) {
- manageCliOut(" Post-Update-Hook ausgeführt.");
- }
- return true;
- }
- if (!empty($hook["failed_migration"])) {
- manageCliError(" FEHLER in Migration " . $hook["failed_migration"] . ": " . (string) $hook["error"]);
- manageCliError(" Verbleibende Migrationen wurden NICHT ausgeführt.");
- manageCliError(" Ursache beheben und danach erneut ausführen: manage-client.php migrate");
- } else {
- manageCliError(" FEHLER im Post-Update-Hook: " . (string) ($hook["error"] ?? "unbekannt"));
- }
- return false;
- }
- try {
- switch ($command) {
- case "status":
- $status = manageClientStatus();
- manageCliOut("Instanz: " . ($status["instance"] !== "" ? $status["instance"] : "(nicht gesetzt)"));
- manageCliOut("Server: " . ($status["server_url"] !== "" ? $status["server_url"] : "(nicht gesetzt)"));
- manageCliOut("Konfiguriert: " . ($status["configured"] ? "ja" : "NEIN"));
- manageCliOut("Installierte Ver.: " . ($status["version"] !== "" ? $status["version"] : "unbekannt"));
- manageCliOut("PHP: " . $status["php_version"]);
- if ($status["update"] !== null) {
- manageCliOut("Aktuelles Release: " . $status["update"]["latest"]);
- manageCliOut("Update verfügbar: " . ($status["update"]["available"] ? "JA" : "nein"));
- } elseif ($status["update_error"] !== null) {
- manageCliOut("Update-Prüfung: fehlgeschlagen (" . $status["update_error"] . ")");
- }
- manageCliOut("Lokale Backups: " . count($status["backups"]));
- manageCliOut("Letztes Backup: " . ($status["last_backup_at"] ?? "nie"));
- manageCliOut("Offene Migrationen: " . count($status["pending_migrations"]));
- foreach ($status["pending_migrations"] as $migration) {
- manageCliOut(" - " . $migration["id"]);
- }
- foreach ($status["errors"] as $error) {
- manageCliError("Warnung: " . $error);
- }
- exit(0);
- case "check":
- $check = manageUpdateCheck();
- manageCliOut("Installiert: " . ($check["current"] !== "" ? $check["current"] : "unbekannt"));
- manageCliOut("Verfügbar: " . $check["latest"]);
- if ($check["available"]) {
- manageCliOut("Ein Update ist verfügbar.");
- exit(2);
- }
- manageCliOut("Die Installation ist aktuell.");
- exit(0);
- case "update":
- $check = manageUpdateCheck();
- $force = isset($flags["force"]);
- if (!$check["available"] && !$force) {
- manageCliOut("Kein Update verfügbar. Mit --force kann dasselbe Paket erneut ausgerollt werden.");
- exit(0);
- }
- if (!manageCliConfirm(
- "Version " . $check["latest"] . " jetzt ausrollen" .
- ($check["current"] !== "" ? " (installiert: " . $check["current"] . ")" : "") . "?"
- )) {
- manageCliOut("Abgebrochen.");
- exit(0);
- }
- $result = manageUpdateApply([
- "force" => $force,
- "skip_hook" => isset($flags["skip-hook"]),
- ]);
- manageCliOut("Update ausgerollt: " . $result["from_version"] . " -> " . $result["to_version"]);
- manageCliOut(" Dateien kopiert: " . $result["copied"]);
- manageCliOut(" Dateien gesichert: " . $result["backed_up"]);
- manageCliOut(" Geschützt übersprungen: " . $result["skipped"]);
- manageCliOut(" Sicherungsverzeichnis: " . $result["backup_dir"]);
- $hookOk = manageCliReportHook($result["hook"]);
- manageHeartbeatSendQuietly();
- // The deployment succeeded either way; the exit code reports the
- // post-update step so a cron job notices a failed migration.
- exit($hookOk ? 0 : 1);
- case "migrate":
- $pending = manageUpdatePendingMigrations();
- if ($pending === []) {
- manageCliOut("Keine offenen Migrationen.");
- exit(0);
- }
- manageCliOut("Offene Migrationen: " . count($pending));
- foreach ($pending as $migration) {
- manageCliOut(" - " . $migration["id"]);
- }
- if (isset($flags["dry-run"])) {
- manageCliOut("--dry-run: nichts ausgeführt.");
- exit(0);
- }
- if (!manageCliConfirm("Diese Migrationen jetzt ausführen?")) {
- manageCliOut("Abgebrochen.");
- exit(0);
- }
- $report = manageUpdateRunMigrations();
- if ($report["applied"] !== []) {
- manageCliOut("Ausgeführt: " . implode(", ", $report["applied"]));
- }
- if (!$report["success"]) {
- manageCliError("FEHLER in Migration " . (string) $report["failed"] . ": " . (string) $report["error"]);
- manageCliError("Verbleibende Migrationen: " . ($report["pending"] - 1));
- exit(1);
- }
- manageCliOut("Alle Migrationen abgeschlossen.");
- manageHeartbeatSendQuietly();
- exit(0);
- case "backup":
- $trigger = is_string($flags["trigger"] ?? null) ? (string) $flags["trigger"] : "manual";
- $record = manageBackupCreate($trigger);
- manageCliOut("Backup erstellt: " . $record["filename"]);
- manageCliOut(" Dateien: " . $record["file_count"]);
- manageCliOut(" Größe: " . manageFormatBytes((int) $record["size"]));
- manageCliOut(" SHA-256: " . $record["sha256"]);
- if (is_array($record["database"] ?? null)) {
- manageCliOut(" Datenbank: " . $record["database"]["tables"] . " Tabellen, " .
- $record["database"]["rows"] . " Zeilen");
- }
- $failed = false;
- foreach ($record["remote_uploads"] as $upload) {
- if (!empty($upload["success"])) {
- manageCliOut(" Upload " . $upload["target"] . ": OK");
- } else {
- $failed = true;
- manageCliError(" Upload " . $upload["target"] . " FEHLGESCHLAGEN: " .
- (string) ($upload["error"] ?? "unbekannt"));
- }
- }
- manageHeartbeatSendQuietly();
- // The local archive exists regardless, but a failed upload must be
- // visible to cron.
- exit($failed ? 1 : 0);
- case "heartbeat":
- $result = manageHeartbeatSend();
- manageCliOut("Heartbeat gesendet.");
- manageCliOut(" Aktuelles Release: " . ($result["latest"] !== "" ? $result["latest"] : "keines"));
- manageCliOut(" Update verfügbar: " . ($result["update_available"] ? "JA" : "nein"));
- exit(0);
- case "":
- case "help":
- case "-h":
- case "--help":
- manageCliUsage();
- exit(0);
- default:
- manageCliError("Unbekannter Befehl: " . $command);
- manageCliUsage();
- exit(1);
- }
- } catch (Throwable $exception) {
- manageCliError("Fehler: " . $exception->getMessage());
- exit(1);
- }
|