First live end-to-end install on real Unraid hardware (all 8 built
packages installed via upgradepkg, rc.podman started, containers
pulled/run/networked/port-mapped) — surfaced five genuine bugs no
amount of container-based CI testing could have caught, since none of
them exist inside the vbatts/slackware:15.0 build container:
1. rc.podman never created $PODMAN_LOG_DIR before redirecting the
podman system service's output into it, so the service failed to
even start ("No such file or directory"). Added it alongside the
existing PODMAN_RUN_DIR mkdir.
2. config/storage.conf hardcoded a [storage] table, and
podman-config.sh's `sync` step appended a second one at boot with
the real graphroot/runroot — TOML forbids defining the same table
twice. Removed the template's [storage] entirely; sync already
generates the whole thing.
3. config/policy.json had a "_comment" pseudo-field for
documentation, but containers/image's policy parser rejects any
unknown top-level key outright. JSON has no comment syntax; moved
the rationale into docs/ARCHITECTURE.md instead.
4. netavark >= 2.0 dropped its iptables firewall driver entirely
(verified: passing "iptables" is flatly rejected) — nftables or
firewalld are the only remaining options, and firewalld needs
systemd/dbus, which Unraid has neither of. Set firewall_driver =
"nftables" explicitly and documented that Unraid OS doesn't ship
the `nft` binary this needs (a slackware64 nftables package works;
not yet wired into the build/install pipeline — see follow-up).
5. Every container failed with "crun: pivot_root: Invalid argument".
Root cause: Unraid's / is permanently the kernel's initial "rootfs"
pseudo-filesystem (Unraid never pivots to a real one at boot — the
whole OS runs from RAM), and pivot_root(2) unconditionally rejects
that as the old root. This is not new: Docker/runc hits the exact
same kernel restriction on this exact host and silently falls back
to an MS_MOVE-based chroot; crun has no such fallback, only a
--no-pivot flag with no config-file equivalent. Added
plugin/sbin/crun-no-pivot.sh, a thin wrapper that scans crun's full
argument list (podman puts global flags before the subcommand, so
the subcommand isn't reliably $1) and injects --no-pivot right
after create/run, and pointed containers.conf's crun runtime at it.
Also fixed the podman.plg postinstall's chmod glob
(`podman-*.sh` -> `*.sh`), which would have skipped this new
non-podman-prefixed sbin script.
Verified end-to-end on the real host: pull, run, real network
connectivity (wget through the container's bridge), and a published
port actually serving HTTP (curl through -p 8099:80 to nginx) all
work. --no-pivot's security tradeoff (disabling one particular
container-escape mitigation) was explicitly discussed with and
approved by the user before committing, given it must be the default
for any container to start at all on this platform.
Follow-up not yet done: nftables (needed for #4) is not yet a
packages/ component in the reproducible build pipeline — it was only
installed manually on the test host for this verification run.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
245 lines
9.2 KiB
Bash
Executable File
245 lines
9.2 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_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
|