All functions become available after a single require:
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.
manageClientStatus(): arrayCollective 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.
[
"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(): stringInstalled version, for example "v1.2.3". Returns "" if it can't be
determined; that's not an error, just "unknown".
manageClientConfigured(): boolWhether server URL, instance and token are set.
manageUpdateCheck(): arrayFetches the manifest and compares versions.
[
"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 = []): arrayDownloads 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:
[
"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.
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(): arrayMigrations not yet run, in execution order:
[["id" => "2026-08-20-01-add-index", "path" => "/…/migrations/2026-08-20-01-add-index.php"]]
manageUpdateRunMigrations(array $context = []): arrayRuns the pending migrations. Never throws, reports instead:
[
"success" => false,
"applied" => ["2026-08-20-01-add-index"],
"failed" => "2026-08-20-02-backfill",
"error" => "SQLSTATE[42S22]: …",
"pending" => 2, // including the failed one
]
manageBackupCreate(string $trigger = "manual"): arrayCreates 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.
[
"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(): ?arrayCreates 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(): arrayLocal archives, newest first. Self-healing: entries without a file are removed, sizes are refreshed from disk.
manageBackupUpload(string $archivePath, array $meta = []): arrayUploads 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): stringAbsolute path of a local archive. Validates the filename strictly, so a download form can't be made to serve an arbitrary path.
manageHeartbeatSend(): arrayReports 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:
["success" => true, "latest" => "v1.3.0", "update_available" => true, "server_time" => "…"]
manageHeartbeatSendQuietly(): ?arraySame as above, but never throws and returns null on error. For calls
inside the project where an unreachable server should have no consequence.
| 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 |
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.