Document the PHP api layer and fix what documenting it exposed
Writing down what each endpoint actually guarantees made the places it didn't obvious — shell arguments reaching a crontab or a bash -c unescaped, master.conf written without tmp+rename, and conf edits that could be saved without ever being parsed.
This commit is contained in:
@@ -1,7 +1,94 @@
|
||||
<?php
|
||||
// Background worker: docker pull → compare image ID → rebuild if updated.
|
||||
// Called via: php docker_pull_worker.php <name> <jobFile> <oldId> <image> <rebuild>
|
||||
[$name, $jobFile, $oldId, $image, $rebuild] = array_slice($argv, 1, 5);
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
// PURPOSE
|
||||
// Container update worker. Pulls one image, decides whether it actually changed, and
|
||||
// recreates the container if it did — reporting progress through a job file the docker tab
|
||||
// polls.
|
||||
//
|
||||
// OPERATIONAL MODEL
|
||||
// Not an HTTP endpoint. This runs as a detached CLI process, spawned by docker_action.php,
|
||||
// because a pull can take minutes and no web request should be held open for it. It lives
|
||||
// under api/ because it is part of that endpoint's implementation, not because it is
|
||||
// reachable over HTTP — and it refuses to run if it ever is.
|
||||
//
|
||||
// Progress is a file, not a return value. The parent request returns immediately with a job
|
||||
// id; this process writes the current state to that file as it goes, and the page polls it.
|
||||
// The file is the only channel between the two.
|
||||
//
|
||||
// Called as: php docker_pull_worker.php <name> <jobFile> <oldId> <image> <rebuild>
|
||||
//
|
||||
// DESIGN PRINCIPLES
|
||||
// Compares image ids, not pull output.
|
||||
// `docker pull` reports success whether or not anything changed. The image id before
|
||||
// and after is the only reliable signal, and it is what decides whether the container
|
||||
// is disturbed at all — an up-to-date container is never restarted.
|
||||
//
|
||||
// Prefers Unraid's own rebuild path.
|
||||
// When a rebuild helper is supplied and executable it is used, because recreating a
|
||||
// container correctly means reapplying its full template — ports, mounts, variables.
|
||||
// The stop/start fallback exists for the case where that helper is unavailable, and is
|
||||
// explicitly the lesser option: it picks up a new image only if the container was
|
||||
// already configured to be recreated on start.
|
||||
//
|
||||
// Every exit writes a terminal state.
|
||||
// Both outcomes end with a job-file write, so the poller always converges. A worker that
|
||||
// died without writing would leave the page spinning indefinitely.
|
||||
//
|
||||
// OPERATIONAL SAFEGUARDS
|
||||
// Refuses to run under a web server.
|
||||
// PHP_SAPI is checked first and a non-CLI invocation is answered with a 404 and no
|
||||
// output. Without that, requesting this file over HTTP would evaluate it with no $argv
|
||||
// at all — and it is a script whose entire job is to stop and restart containers.
|
||||
//
|
||||
// Required arguments are checked before anything runs.
|
||||
// Missing name, job file, or image exits non-zero before the first docker call, so a
|
||||
// malformed spawn cannot pull or restart anything.
|
||||
//
|
||||
// Every value interpolated into a shell command is escaped.
|
||||
// Image, container name and the rebuild helper path all go through escapeshellarg(),
|
||||
// even though they originate from docker_action.php rather than from a request. The
|
||||
// escaping is what stays correct if that caller ever changes.
|
||||
//
|
||||
// The rebuild helper is confirmed executable before it is invoked.
|
||||
// is_executable() gates it, so a missing or non-executable helper falls back to
|
||||
// stop/start rather than failing the update with a shell error.
|
||||
//
|
||||
// Failure is reported as failure.
|
||||
// A non-zero rebuild status writes ok:false with an explicit message. The image has
|
||||
// already been pulled at that point, so silently reporting success would leave a
|
||||
// container running an old image that the page claims was updated.
|
||||
//
|
||||
// Docker output is captured, never echoed.
|
||||
// Every call redirects stderr and the output is discarded or kept locally. This process
|
||||
// has no stdout consumer; writing to it would only risk corrupting the job file if the
|
||||
// two were ever pointed at the same place.
|
||||
//
|
||||
// ARGUMENTS
|
||||
// 1 name container name
|
||||
// 2 jobFile path the progress JSON is written to
|
||||
// 3 oldId image id before the pull, for the changed/unchanged comparison
|
||||
// 4 image image reference to pull
|
||||
// 5 rebuild optional path to Unraid's container rebuild helper
|
||||
//
|
||||
// JOB FILE STATES
|
||||
// {"ok":true,"status":"done","updated":false,"message":"Already up to date"}
|
||||
// {"ok":true,"status":"rebuilding"}
|
||||
// {"ok":true,"status":"done","updated":true,"message":"Updated and rebuilt"}
|
||||
// {"ok":false,"status":"done","error":"Rebuild failed after pull"}
|
||||
//
|
||||
// DEPENDS ON
|
||||
// api/docker_action.php spawns this worker and creates the job file path
|
||||
// docker CLI pull, image inspect, stop, start
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// This file stops and starts containers. It is a CLI worker and must never be reachable as a
|
||||
// web request — over HTTP there is no $argv, and every argument below would be undefined.
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(404);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[$name, $jobFile, $oldId, $image, $rebuild] = array_slice($argv, 1, 5) + array_fill(0, 5, '');
|
||||
|
||||
if (!$name || !$jobFile || !$image) exit(1);
|
||||
|
||||
@@ -21,7 +108,7 @@ if ($oldId && $newId && $oldId === $newId) {
|
||||
jw($jobFile, ['ok' => true, 'status' => 'rebuilding']);
|
||||
|
||||
if ($rebuild && is_executable($rebuild)) {
|
||||
exec($rebuild . ' ' . escapeshellarg($name) . ' 2>&1', $out, $rc);
|
||||
exec(escapeshellarg($rebuild) . ' ' . escapeshellarg($name) . ' 2>&1', $out, $rc);
|
||||
} else {
|
||||
exec('docker stop ' . escapeshellarg($name) . ' 2>&1', $o1, $rc1);
|
||||
exec('docker start ' . escapeshellarg($name) . ' 2>&1', $o2, $rc2);
|
||||
|
||||
Reference in New Issue
Block a user