# Function API ## Overview All functions become available after a single `require`: ```php require_once __DIR__ . "/manage-client/lib/client.php"; ``` The command line (`bin/manage-client.php`) and the UI (`ui/panel.php`) contain **no logic of their own** — they call exactly these functions. A function therefore behaves identically no matter how it's triggered. Errors are thrown as `RuntimeException`. Exceptions to this rule are noted explicitly. ## Status ### `manageClientStatus(): array` Collective call for UIs. **Never throws**: every error ends up in the return value, so a settings page still renders even when the server is unreachable. ```php [ "instance" => "my-project-prod", "server_url" => "https://manage.example.org", "configured" => true, "version" => "v1.2.3", // "" if it can't be determined "php_version" => "8.3.6", "update" => [...], // return value of manageUpdateCheck(), or null "update_error" => null, // error message if the check failed "backups" => [...], // return value of manageBackupList() "last_backup_at" => "2026-08-20T09:21:04+00:00", "pending_migrations" => [...], "errors" => [], // non-fatal warnings ] ``` ### `manageClientVersion(): string` Installed version, for example `"v1.2.3"`. Returns `""` if it can't be determined; that's not an error, just "unknown". ### `manageClientConfigured(): bool` Whether server URL, instance and token are set. ## Update ### `manageUpdateCheck(): array` Fetches the manifest and compares versions. ```php [ "current" => "v1.2.3", "latest" => "v1.3.0", "available" => true, "manifest" => [ "version" => "v1.3.0", "package_url" => "https://…/api/v1/package.php?version=v1.3.0", "sha256" => "…", "size" => 421337, "published_at" => "2026-08-20T09:20:43+00:00", ], ] ``` If the installed version is unknown, `available => true` holds, so an installation without a readable version file isn't permanently blocked. ### `manageUpdateApply(array $options = []): array` Downloads the package, checks size and SHA-256, extracts it, deploys it, and then runs the post-update step. Options: | Option | Meaning | |---|---| | `force` | deploy even when no newer version is available | | `skip_hook` | only deploy files, run neither migrations nor the callback | Return value: ```php [ "deployed" => true, "from_version" => "v1.2.3", "to_version" => "v1.3.0", "copied" => 128, "backed_up" => 126, "skipped" => 2, // protected paths "removed_backups" => 1, "backup_dir" => "/…/data/manage/updates/20260820-092114-v1.3.0", "hook" => [...], // see below ] ``` Throws if **deployment** fails. If only the post-update step fails, the function returns normally and `hook["success"]` is `false` — the files are then already deployed. Callers must distinguish between the two; see [07_POST_UPDATE_HOOKS](07_POST_UPDATE_HOOKS.md). Important: deployment overwrites files while the application is live. There is no maintenance page and no rollback. The overwritten files sit as copies in `backup_dir`, but exclusively for manual restoration. ### `manageUpdatePendingMigrations(): array` Migrations not yet run, in execution order: ```php [["id" => "2026-08-20-01-add-index", "path" => "/…/migrations/2026-08-20-01-add-index.php"]] ``` ### `manageUpdateRunMigrations(array $context = []): array` Runs the pending migrations. **Never throws**, reports instead: ```php [ "success" => false, "applied" => ["2026-08-20-01-add-index"], "failed" => "2026-08-20-02-backfill", "error" => "SQLSTATE[42S22]: …", "pending" => 2, // including the failed one ] ``` ## Backup ### `manageBackupCreate(string $trigger = "manual"): array` Creates a local archive and uploads it, provided `MANAGE_BACKUP_UPLOAD` is active. `$trigger` is free-form; common values are `manual`, `automatic`, `cron`, `update`. Only `automatic` and `cron` count toward the interval check. ```php [ "filename" => "backup-20260820-092104.zip", "created_at" => "2026-08-20T09:21:04+00:00", "trigger" => "cron", "size" => 427, "file_count" => 3, "source_bytes" => 63, "sha256" => "…", "app_version" => "v1.2.3", "database" => null, // or ["tables" => 12, "rows" => 4711, …] "remote_uploads" => [ ["target" => "Manage Server", "type" => "manage", "success" => true, …], ], ] ``` A failed upload does **not** invalidate the local archive: the error sits in `remote_uploads[].error` and in the log; the function doesn't throw. If it does throw, the archive itself never came into being. Concurrent runs are prevented by a lock file; a second run throws immediately with `Es läuft bereits ein Backup.` ("a backup is already running" — the literal, still German, message text). ### `manageBackupCreateAutomaticIfDue(): ?array` Creates a backup once `MANAGE_BACKUP_AUTO_INTERVAL_SECONDS` has passed since the last automatic backup, otherwise `null`. Meant for hosting without cron; belongs on a rarely loaded admin page. ### `manageBackupList(): array` Local archives, newest first. Self-healing: entries without a file are removed, sizes are refreshed from disk. ### `manageBackupUpload(string $archivePath, array $meta = []): array` Uploads an existing archive to the Manage server. Called automatically by `manageBackupCreate()`; only needed on its own for special cases — for example, resending an archive after a server outage. ### `manageBackupPath(string $filename): string` Absolute path of a local archive. Validates the filename strictly, so a download form can't be made to serve an arbitrary path. ## Heartbeat ### `manageHeartbeatSend(): array` Reports version, PHP version, free disk space, pending migrations and the time of the last backup. The response includes the update information as a side effect: ```php ["success" => true, "latest" => "v1.3.0", "update_available" => true, "server_time" => "…"] ``` ### `manageHeartbeatSendQuietly(): ?array` Same as above, but never throws and returns `null` on error. For calls inside the project where an unreachable server should have no consequence. ## Helper functions | Function | Purpose | |---|---| | `manageFormatBytes(int $bytes): string` | `1234567` → `1.18 MB` | | `manageClientLog(string $level, string $message, array $context = []): void` | one line into the client log. Never throws | | `manageRemoteCapabilities(): array` | which configured target types this system supports | ## Example ```php require_once __DIR__ . "/manage-client/lib/client.php"; $check = manageUpdateCheck(); if ($check["available"]) { manageBackupCreate("update"); // back up before the update $result = manageUpdateApply(); if (!$result["hook"]["success"]) { // Files are deployed, the post-update step is not. error_log("Migration failed: " . $result["hook"]["error"]); } } ``` A backup before the update is deliberately **not** built in, but a line in the project — so it stays visible that it happens.