Files
unraid-podman/plugin/rc.d/rc.podman
T
maggesandClaude Sonnet 5 e2fefcdf9c
Build Packages / Build .txz packages (push) Failing after 9s
Lint / ShellCheck (push) Failing after 43s
Lint / Validate .plg XML (push) Successful in 10s
Lint / EditorConfig (push) Failing after 6s
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>
2026-07-11 10:51:14 +00:00

245 lines
9.1 KiB
Bash
Executable File

#!/bin/bash
# =============================================================================
# plugin/rc.d/rc.podman
#
# Init script for unraid-podman, in the classic Slackware BSD
# start|stop|restart|status style — Unraid has no systemd, so this script
# alone owns the entire process lifecycle (see docs/ARCHITECTURE.md
# section 6, "Start-/Stop-Skripte", and the top-level "Rahmenbedingungen"
# table on why that's the case).
#
# Staged by plugin/podman.plg to /etc/rc.d/rc.podman. Its boot-time
# invocation is registered (also by podman.plg) in /boot/config/go, gated on
# Unraid's "array started" event rather than running unconditionally at
# boot — the configured storage path (cache pool/disk) is not guaranteed to
# be available before that point. See ARCHITECTURE.md section 6.2.
#
# The individual responsibilities below are deliberately split into small,
# single-purpose scripts under /usr/local/sbin/ (staged from plugin/sbin/)
# rather than inlined here — see each script's own header comment for why
# it's separate:
# podman-preflight.sh startup validation
# podman-config.sh config seeding + sync
# podman-storage.sh podman.img create/mount/unmount
# podman-autostart.sh autostart chain
# podman-backup.sh config/package snapshots for rollback
# podman-verify-packages.sh package integrity checks
# podman-update-packages.sh package reconciliation/update
# podman-uninstall-cleanup.sh called from podman.plg's removepkg block
# =============================================================================
set -u
SBIN_DIR="/usr/local/sbin"
# shellcheck source=../sbin/podman-common.sh
. "$SBIN_DIR/podman-common.sh"
podman_load_cfg
# -----------------------------------------------------------------------------
# podman_start
#
# See file header for the full sequence. Every step is expected to be
# idempotent (safe to re-run `rc.podman start` against an already-running
# instance without breaking anything) — preflight and podman-storage.sh
# both already implement this on their own, but we also short-circuit here
# if the service is clearly already up, to avoid doing redundant work.
# -----------------------------------------------------------------------------
podman_start() {
if [ "${PODMAN_ENABLED:-yes}" != "yes" ]; then
podman_log "start: PODMAN_ENABLED is not 'yes' in podman.cfg, not starting"
return 0
fi
if [ -S "$PODMAN_SOCKET" ] && [ -f "$PODMAN_SERVICE_PID_FILE" ] \
&& kill -0 "$(cat "$PODMAN_SERVICE_PID_FILE" 2> /dev/null)" 2> /dev/null; then
podman_log "start: already running (pid $(cat "$PODMAN_SERVICE_PID_FILE"))"
return 0
fi
podman_log "start: beginning startup sequence"
# 1. Preflight — aborts loudly (and already notified) on failure.
if ! "$SBIN_DIR/podman-preflight.sh"; then
podman_log_error "start: preflight checks failed, aborting"
return 1
fi
# 2. Ensure config exists (no-op if already seeded) and sync it to the
# RAM-root /etc/containers/ — see podman-config.sh.
"$SBIN_DIR/podman-config.sh" seed
if ! "$SBIN_DIR/podman-config.sh" sync; then
podman_log_error "start: config sync failed, aborting"
return 1
fi
# 3. Storage: create the image if this is a first start, then mount it.
if ! "$SBIN_DIR/podman-storage.sh" create; then
podman_log_error "start: storage creation failed, aborting"
return 1
fi
if ! "$SBIN_DIR/podman-storage.sh" mount; then
podman_log_error "start: storage mount failed, aborting"
return 1
fi
# 4. Restore persisted netavark network definitions (see
# docs/ARCHITECTURE.md section 8, Netzwerke) from the boot-persistent
# copy into the RAM-root config directory netavark reads from.
mkdir -p "$PODMAN_ETC_DIR/networks"
if [ -d "$PODMAN_NETWORKS_BOOT_DIR" ] && [ -n "$(ls -A "$PODMAN_NETWORKS_BOOT_DIR" 2> /dev/null)" ]; then
cp -a "$PODMAN_NETWORKS_BOOT_DIR"/. "$PODMAN_ETC_DIR/networks/"
podman_log "start: restored custom network definitions"
fi
# 5. Start the Podman API service, rootful, on a unix socket — see
# docs/ARCHITECTURE.md section 6.1 for why this runs as a persistent
# service rather than being started fresh per CLI/WebUI call:
# a shared process gives consistent state and event streaming.
# --time=0 disables the idle-shutdown timeout (this is a long-running
# daemon under our process management, not an on-demand activation).
mkdir -p "$PODMAN_RUN_DIR"
podman_log "start: starting podman system service on unix://$PODMAN_SOCKET"
nohup podman system service --time=0 "unix://$PODMAN_SOCKET" \
> "$PODMAN_LOG_DIR/podman-service.log" 2>&1 &
local service_pid=$!
echo "$service_pid" > "$PODMAN_SERVICE_PID_FILE"
# Wait for the socket to actually appear rather than assuming the fork
# succeeded instantly — up to 15s, polled every 200ms.
local waited=0
while [ ! -S "$PODMAN_SOCKET" ] && [ "$waited" -lt 15000 ]; do
sleep 0.2
waited=$((waited + 200))
if ! kill -0 "$service_pid" 2> /dev/null; then
podman_log_error "start: podman system service exited immediately — see $PODMAN_LOG_DIR/podman-service.log"
"$SBIN_DIR/podman-storage.sh" unmount || true
podman_notify "Podman failed to start" \
"podman system service exited immediately. See $PODMAN_LOG_DIR/podman-service.log." \
"alert"
return 1
fi
done
if [ ! -S "$PODMAN_SOCKET" ]; then
podman_log_error "start: timed out waiting for $PODMAN_SOCKET to appear"
return 1
fi
podman_log "start: podman system service is up (pid $service_pid)"
# 6. Autostart. A failure here is logged/notified by the script itself
# and does not abort rc.podman start — the service is already usable.
"$SBIN_DIR/podman-autostart.sh" || podman_log_error "start: autostart chain reported errors (see above)"
echo "running" > "$PODMAN_STATUS_FILE"
podman_log "start: startup sequence complete"
return 0
}
# -----------------------------------------------------------------------------
# podman_stop
#
# Stops containers first (each with its own STOP_TIMEOUT grace period),
# then the API service, then unmounts storage — the reverse of start, so
# nothing is torn down out from under something still using it.
# -----------------------------------------------------------------------------
podman_stop() {
if [ ! -S "$PODMAN_SOCKET" ]; then
podman_log "stop: not running (no socket at $PODMAN_SOCKET)"
# Still attempt an unmount in case a prior stop was interrupted after
# the service went down but before the unmount completed.
"$SBIN_DIR/podman-storage.sh" unmount || true
rm -f "$PODMAN_STATUS_FILE"
return 0
fi
podman_log "stop: stopping running containers (timeout ${STOP_TIMEOUT:-10}s each)"
local running_ids
running_ids=$(podman --url "unix://$PODMAN_SOCKET" ps -q 2> /dev/null || true)
if [ -n "$running_ids" ]; then
local id
for id in $running_ids; do
podman --url "unix://$PODMAN_SOCKET" stop -t "${STOP_TIMEOUT:-10}" "$id" \
> /dev/null 2>&1 \
|| podman_log_error "stop: failed to stop container $id within timeout"
done
fi
podman_log "stop: stopping podman system service"
if [ -f "$PODMAN_SERVICE_PID_FILE" ]; then
local pid
pid=$(cat "$PODMAN_SERVICE_PID_FILE")
if kill -0 "$pid" 2> /dev/null; then
kill "$pid" 2> /dev/null || true
local waited=0
while kill -0 "$pid" 2> /dev/null && [ "$waited" -lt 10 ]; do
sleep 1
waited=$((waited + 1))
done
kill -0 "$pid" 2> /dev/null && kill -9 "$pid" 2> /dev/null || true
fi
rm -f "$PODMAN_SERVICE_PID_FILE"
fi
rm -f "$PODMAN_SOCKET"
"$SBIN_DIR/podman-storage.sh" unmount || podman_log_error "stop: storage unmount failed"
rm -f "$PODMAN_STATUS_FILE"
podman_log "stop: stopped"
return 0
}
# -----------------------------------------------------------------------------
# podman_status
#
# Human-readable health summary — see docs/ARCHITECTURE.md section 16.2.
# -----------------------------------------------------------------------------
podman_status() {
echo "PODMAN_ENABLED: ${PODMAN_ENABLED:-yes}"
if [ -S "$PODMAN_SOCKET" ] && [ -f "$PODMAN_SERVICE_PID_FILE" ] \
&& kill -0 "$(cat "$PODMAN_SERVICE_PID_FILE" 2> /dev/null)" 2> /dev/null; then
echo "service: running (pid $(cat "$PODMAN_SERVICE_PID_FILE"), socket $PODMAN_SOCKET)"
else
echo "service: stopped"
fi
echo
"$SBIN_DIR/podman-storage.sh" status
echo
if [ -f "$PODMAN_AUTOSTART_FILE" ]; then
local count
count=$(grep -vcE '^\s*(#|$)' "$PODMAN_AUTOSTART_FILE" 2> /dev/null || echo 0)
echo "autostart entries: $count (see $PODMAN_AUTOSTART_FILE)"
fi
if [ -S "$PODMAN_SOCKET" ]; then
echo
echo "podman info:"
podman --url "unix://$PODMAN_SOCKET" info --format \
' containers: {{.Store.ContainerStore.Number}} images: {{.Store.ImageStore.Number}}' \
2> /dev/null || echo " (failed to query — service may still be initializing)"
fi
}
case "${1:-}" in
start)
podman_start
;;
stop)
podman_stop
;;
restart)
podman_stop
podman_start
;;
status)
podman_status
;;
*)
echo "usage: $0 {start|stop|restart|status}"
exit 1
;;
esac