Add reproducible build system, native Unraid plugin, and WebUI
- versions.env pins podman, conmon, crun, netavark, aardvark-dns, passt, and fuse-overlayfs to verified upstream source checksums; SlackBuild recipes, scripts/build-packages.sh, checksums.sh, release.sh, and update-versions.sh implement the reproducible pipeline; GitHub Actions workflows build in a Slackware container and publish releases without committing any binaries. - plugin/podman.plg installs/updates/removes all eight packages (the seven components plus the plugin's own unraid-podman scaffolding package) via upgradepkg, using the official Unraid array-event hook mechanism (event/disks_mounted, event/stopping) instead of editing /boot/config/go. rc.podman and the sbin/ helper scripts implement storage creation, config seeding/sync, preflight checks, autostart with per-container Safe-Mode, and package verify/update/rollback. - webui/plugins/podman implements the Dashboard, Containers, Pods, Images, Volumes, Networks, Logs, Terminal, Compose, and Settings panels against the approved mockup (webui/mockups/prototype.html), talking to podman system service exclusively via PodmanClient.php (libpod REST API over the Unix socket), with two documented exceptions: Terminal's one-shot exec model and Compose's use of the podman compose CLI, since libpod has no REST equivalent for either. - docs/ARCHITECTURE.md and docs/ROADMAP.md record the design decisions and honest current status (syntax-checked, unit- and integration-tested against fake sockets/servers; not yet run against a real Unraid/Podman/Slackware system). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* ajax/settings.php
|
||||
*
|
||||
* Backs the Settings panel. Unlike every other ajax/*.php file, this one
|
||||
* is NOT primarily a PodmanClient consumer — plugin settings (podman.cfg,
|
||||
* the autostart list, installed package versions) are unraid-podman's own
|
||||
* data on /boot, not a libpod-managed resource, so there is no API to call
|
||||
* here in the first place. Writes go straight to the same files
|
||||
* plugin/sbin/podman-config.sh and podman-autostart.sh read, using the
|
||||
* same paths (see include/Config.php, which mirrors podman-common.sh's
|
||||
* path constants).
|
||||
*
|
||||
* Actions (?action=...):
|
||||
* get GET -> current settings + autostart list + package versions
|
||||
* save POST {"storagePath": "...", "storageImageSizeGb": 20,
|
||||
* "enabled": true, "stopTimeoutSeconds": 10}
|
||||
* autostart_save POST {"names": ["postgres", "nextcloud", ...]}
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../include/bootstrap.php';
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'get':
|
||||
podman_json_response(settings_get($podmanConfig));
|
||||
break;
|
||||
|
||||
case 'save':
|
||||
$body = podman_read_json_body();
|
||||
settings_save($podmanConfig, $body);
|
||||
podman_json_response(['status' => 'saved']);
|
||||
break;
|
||||
|
||||
case 'autostart_save':
|
||||
$body = podman_read_json_body();
|
||||
$names = $body['names'] ?? null;
|
||||
if (!is_array($names)) {
|
||||
podman_json_error('Missing names array in request body', 400);
|
||||
}
|
||||
autostart_save($podmanConfig, $names);
|
||||
podman_json_response(['status' => 'saved']);
|
||||
break;
|
||||
|
||||
default:
|
||||
podman_json_error("Unknown action '{$action}'", 400);
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
function settings_get(PodmanConfig $config): array
|
||||
{
|
||||
return [
|
||||
'storagePath' => $config->storagePath,
|
||||
'storageImageSizeGb' => $config->storageImageSizeGb,
|
||||
'enabled' => $config->enabled,
|
||||
'stopTimeoutSeconds' => $config->stopTimeoutSeconds,
|
||||
'autostart' => autostart_read($config),
|
||||
'packageVersions' => installed_package_versions(),
|
||||
];
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $input */
|
||||
function settings_save(PodmanConfig $config, array $input): void
|
||||
{
|
||||
$storagePath = isset($input['storagePath']) ? (string) $input['storagePath'] : $config->storagePath;
|
||||
if (!storage_path_is_safe($storagePath)) {
|
||||
podman_json_error(
|
||||
"storagePath ({$storagePath}) is under /mnt/user (FUSE/shfs). " .
|
||||
'The overlay storage driver needs a real mounted filesystem — use a cache pool or a specific disk path instead.',
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$lines = [
|
||||
'# Rewritten by the unraid-podman WebUI (ajax/settings.php).',
|
||||
'# Applies on the next "rc.podman restart" — see plugin/rc.d/rc.podman.',
|
||||
'STORAGE_PATH="' . $storagePath . '"',
|
||||
'STORAGE_IMAGE_SIZE_GB="' . (int) ($input['storageImageSizeGb'] ?? $config->storageImageSizeGb) . '"',
|
||||
'PODMAN_ENABLED="' . ((bool) ($input['enabled'] ?? $config->enabled) ? 'yes' : 'no') . '"',
|
||||
'STOP_TIMEOUT="' . (int) ($input['stopTimeoutSeconds'] ?? $config->stopTimeoutSeconds) . '"',
|
||||
'CONFIG_SCHEMA_VERSION="1"',
|
||||
'',
|
||||
];
|
||||
|
||||
$target = $config->bootDir . '/podman.cfg';
|
||||
if (file_put_contents($target, implode("\n", $lines), LOCK_EX) === false) {
|
||||
podman_json_error("Could not write {$target} — check permissions on /boot/config/plugins/podman/", 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors plugin/sbin/podman-common.sh's podman_storage_path_is_safe() —
|
||||
* kept in sync deliberately (both reject the same /mnt/user prefix, for
|
||||
* the same reason) rather than shelling out to the bash version, since
|
||||
* this is a one-line string check, not worth a process spawn for.
|
||||
*/
|
||||
function storage_path_is_safe(string $path): bool
|
||||
{
|
||||
return $path !== '/mnt/user' && !str_starts_with($path, '/mnt/user/');
|
||||
}
|
||||
|
||||
/** @return array<int,string> */
|
||||
function autostart_read(PodmanConfig $config): array
|
||||
{
|
||||
if (!is_readable($config->autostartFile)) {
|
||||
return [];
|
||||
}
|
||||
$lines = file($config->autostartFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
$names = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim(preg_replace('/#.*$/', '', $line) ?? '');
|
||||
if ($line !== '') {
|
||||
$names[] = $line;
|
||||
}
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
/** @param array<int,mixed> $names */
|
||||
function autostart_save(PodmanConfig $config, array $names): void
|
||||
{
|
||||
$lines = array_map(static fn($n) => (string) $n, $names);
|
||||
$content = implode("\n", $lines) . (count($lines) > 0 ? "\n" : '');
|
||||
if (file_put_contents($config->autostartFile, $content, LOCK_EX) === false) {
|
||||
podman_json_error("Could not write {$config->autostartFile}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads /usr/local/share/unraid-podman/installed-versions.env — the same
|
||||
* manifest plugin/sbin/podman-verify-packages.sh and
|
||||
* podman-update-packages.sh use (see plugin/podman.plg's postinstall step,
|
||||
* which generates it) — so Settings shows exactly what those tools would
|
||||
* report, not a second, possibly-diverging source of truth.
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
function installed_package_versions(): array
|
||||
{
|
||||
$path = '/usr/local/share/unraid-podman/installed-versions.env';
|
||||
if (!is_readable($path)) {
|
||||
return [];
|
||||
}
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
$out = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || str_starts_with($line, '#')) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^([A-Z_][A-Z0-9_]*)="?([^"]*)"?$/', $line, $m)) {
|
||||
$out[$m[1]] = $m[2];
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
Reference in New Issue
Block a user