release: r019 idempotent batch container updates
This commit is contained in:
+1
-1
@@ -104,7 +104,7 @@ cat > "${BUILD_DIR}/install/slack-desc" << DESCEOF
|
||||
|
|
||||
|${PKG_NAME} - Mikes Unraid Agent
|
||||
|MCP over HTTP (Streamable HTTP) Server für Unraid.
|
||||
|33 Tools: Docker, Community Applications, Netzwerk und Unraid-System.
|
||||
|34 Tools: Docker, Community Applications, Netzwerk und Unraid-System.
|
||||
|Port: 3002, Endpunkt: /mcp
|
||||
|
|
||||
|Runtime: TypeScript (Bun Runtime, kompiliertes Binary)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* create <template_name>
|
||||
* modify <container> <field> <value>
|
||||
* update <container>
|
||||
* update-verified-batch <container1|container2|...>
|
||||
* rebuild <container>
|
||||
* ca-install <template_url> <container_name> <overrides_json> <start:true|false>
|
||||
*
|
||||
@@ -116,6 +117,109 @@ function rebuild_container(string $name, bool $pull_image = false, bool $force_s
|
||||
out("Done: $Name");
|
||||
}
|
||||
|
||||
function docker_inspect_value(string $format, string $name): string {
|
||||
return docker_exec('inspect --format ' . escapeshellarg($format) . ' ' . escapeshellarg($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull and update several exact containers without trusting Unraid's cached
|
||||
* update flag. The immutable image ID is the authority. A stale cache can
|
||||
* therefore never cause a needless container recreation.
|
||||
*/
|
||||
function update_verified_batch(string $raw_names): void {
|
||||
$names = array_values(array_unique(array_filter(array_map('trim', explode('|', $raw_names)))));
|
||||
if (count($names) < 1 || count($names) > 25) {
|
||||
fail('Expected 1-25 container names separated by |');
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($names as $name) {
|
||||
if (!preg_match('/^[a-zA-Z0-9._-]{1,128}$/', $name)) {
|
||||
$results[] = ['container' => $name, 'result' => 'error', 'error' => 'invalid container name'];
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$tmpl = get_template_path($name);
|
||||
[, $template_name, $repository] = xmlToCommand($tmpl);
|
||||
if ($template_name !== $name) {
|
||||
throw new RuntimeException("Template name mismatch: $template_name");
|
||||
}
|
||||
|
||||
$before_id = docker_inspect_value('{{.Id}}', $name);
|
||||
$before_image_id = docker_inspect_value('{{.Image}}', $name);
|
||||
$before_state = docker_inspect_value('{{.State.Status}}', $name);
|
||||
$was_running = docker_inspect_value('{{.State.Running}}', $name) === 'true';
|
||||
|
||||
$pull_output = docker_exec('pull ' . escapeshellarg($repository));
|
||||
if (preg_match('/(?:^|\n)(?:Error response|error:|failed)/i', $pull_output)) {
|
||||
throw new RuntimeException('Image pull failed: ' . substr($pull_output, 0, 500));
|
||||
}
|
||||
$pulled_image_id = docker_exec('image inspect --format ' . escapeshellarg('{{.Id}}') . ' ' . escapeshellarg($repository));
|
||||
if ($pulled_image_id === '') {
|
||||
throw new RuntimeException('Pulled image ID could not be resolved');
|
||||
}
|
||||
|
||||
if ($before_image_id === $pulled_image_id) {
|
||||
$results[] = [
|
||||
'container' => $name,
|
||||
'result' => 'already-current',
|
||||
'recreated' => false,
|
||||
'original_state' => $before_state,
|
||||
'final_state' => $before_state,
|
||||
'container_id_changed' => false,
|
||||
'image_id_changed' => false,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
rebuild_container($name, false, false);
|
||||
ob_end_clean();
|
||||
|
||||
$after_id = docker_inspect_value('{{.Id}}', $name);
|
||||
$after_image_id = docker_inspect_value('{{.Image}}', $name);
|
||||
$after_state = docker_inspect_value('{{.State.Status}}', $name);
|
||||
$restart_count = (int)docker_inspect_value('{{.RestartCount}}', $name);
|
||||
$health = docker_inspect_value('{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}', $name);
|
||||
$state_preserved = $was_running ? $after_state === 'running' : $after_state !== 'running';
|
||||
|
||||
$results[] = [
|
||||
'container' => $name,
|
||||
'result' => ($after_image_id === $pulled_image_id && $state_preserved) ? 'updated' : 'verification-failed',
|
||||
'recreated' => true,
|
||||
'original_state' => $before_state,
|
||||
'final_state' => $after_state,
|
||||
'state_preserved' => $state_preserved,
|
||||
'container_id_changed' => $before_id !== $after_id,
|
||||
'image_id_changed' => $before_image_id !== $after_image_id,
|
||||
'restart_count' => $restart_count,
|
||||
'health' => $health,
|
||||
];
|
||||
} catch (Throwable $error) {
|
||||
if (ob_get_level() > 0) ob_end_clean();
|
||||
$results[] = [
|
||||
'container' => $name,
|
||||
'result' => 'error',
|
||||
'error' => substr($error->getMessage(), 0, 600),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach ($results as $result) {
|
||||
$key = (string)$result['result'];
|
||||
$counts[$key] = ($counts[$key] ?? 0) + 1;
|
||||
}
|
||||
echo json_encode([
|
||||
'schema_version' => '1.0',
|
||||
'requested_count' => count($names),
|
||||
'result_counts' => $counts,
|
||||
'all_verified' => !isset($counts['error']) && !isset($counts['verification-failed']),
|
||||
'containers' => $results,
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text content of a DOM element.
|
||||
* This is the actual value Unraid uses (not the Default attribute).
|
||||
@@ -354,6 +458,11 @@ switch ($action) {
|
||||
rebuild_container($arg1, true);
|
||||
break;
|
||||
|
||||
case 'update-verified-batch':
|
||||
if (!$arg1) fail('Missing container names');
|
||||
update_verified_batch($arg1);
|
||||
break;
|
||||
|
||||
case 'rebuild':
|
||||
if (!$arg1) fail("Missing container name");
|
||||
out("Rebuilding container: $arg1");
|
||||
@@ -366,5 +475,5 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
default:
|
||||
fail("Unknown action: $action. Usage: create|modify|update|rebuild|ca-install");
|
||||
fail("Unknown action: $action. Usage: create|modify|update|update-verified-batch|rebuild|ca-install");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user