Fix five real bugs found by actually installing and running the plugin
Lint / ShellCheck (push) Successful in 10s
Lint / Validate .plg XML (push) Successful in 10s
Lint / EditorConfig (push) Successful in 4s

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>
This commit is contained in:
2026-07-11 23:06:53 +00:00
co-authored by Claude Sonnet 5
parent 6cd541de23
commit 51b7262b72
7 changed files with 84 additions and 13 deletions
+26 -2
View File
@@ -18,6 +18,30 @@
# TODO: static_dir / volume_path / runroot overrides pointing at the cache-pool
# backed storage location instead of RAM-root defaults.
[engine.runtimes]
# Unraid's / is the kernel's initial 'rootfs' pseudo-filesystem — Unraid
# never pivots to a real one during boot, the whole OS runs from RAM — and
# pivot_root(2) unconditionally rejects that as the "old root" (EINVAL).
# runc (what Docker uses) silently falls back to an MS_MOVE-based chroot
# in that situation; crun has no such fallback, only a --no-pivot flag on
# `create`/`run` with no config-file equivalent — so podman is pointed at
# a thin wrapper (installed by the unraid-podman package, see
# plugin/sbin/crun-no-pivot.sh) that injects it, instead of crun directly.
# Verified live: without this, every container fails with
# "crun: pivot_root: Invalid argument: OCI runtime error".
crun = ["/usr/local/sbin/crun-no-pivot.sh"]
[network]
# TODO: default network backend (netavark), default subnet range distinct from
# Docker's docker0 range — see docs/ARCHITECTURE.md section 8.
# TODO: default subnet range distinct from Docker's docker0 range — see
# docs/ARCHITECTURE.md section 8.
#
# netavark >= 2.0 dropped its iptables firewall driver entirely — only
# nftables and firewalld remain (verified against the actual netavark
# binary; "iptables" is rejected with "Must provide a valid firewall
# backend"). firewalld needs systemd/dbus, which Unraid has neither of, so
# nftables (netavark's own default — explicit here so that stays true even
# if netavark's default ever changes) is the only viable driver. Unraid OS
# does not ship the `nft` binary this needs — see docs/ARCHITECTURE.md
# section 8 for how it's provisioned.
network_backend = "netavark"
firewall_driver = "nftables"
-1
View File
@@ -1,5 +1,4 @@
{
"_comment": "Default container image signature verification policy. Placeholder: accepts all images without signature verification, matching Docker's default trust model on Unraid today. See docs/ARCHITECTURE.md section 10 (Images). Revisit before a 1.0 release if signed-image verification becomes a goal.",
"default": [
{ "type": "insecureAcceptAnything" }
],
+7 -8
View File
@@ -7,14 +7,13 @@
#
# Full reference: https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md
[storage]
driver = "overlay"
# runroot and graphroot are set at runtime by rc.podman based on
# /boot/config/plugins/podman/podman.cfg (configurable storage location),
# not hardcoded here. TODO: document the exact substitution mechanism once
# rc.podman is implemented.
# graphroot = "/var/lib/containers/storage"
# runroot = "/var/run/containers/storage"
# The [storage] table itself (driver, graphroot, runroot) is intentionally
# NOT defined here — podman-config.sh's `sync` command appends it in full
# at sync time, since graphroot depends on STORAGE_PATH from
# /boot/config/plugins/podman/podman.cfg (configurable storage location) and
# can't be known statically. A second [storage] table here would be a TOML
# duplicate-key error once sync appends its own — see podman-config.sh's
# cmd_sync for the generated content.
[storage.options]
# TODO: overlay-specific mount options once the loopback filesystem