Files
unraid-podman/plugin/podman.plg
T
maggesandClaude Sonnet 5 51b7262b72
Lint / ShellCheck (push) Successful in 10s
Lint / Validate .plg XML (push) Successful in 10s
Lint / EditorConfig (push) Successful in 4s
Fix five real bugs found by actually installing and running the plugin
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>
2026-07-11 23:06:53 +00:00

366 lines
15 KiB
XML

<?xml version="1.0" standalone="yes"?>
<!--
podman.plg — Unraid plugin manifest for unraid-podman.
This file is the single entry point Unraid's Plugin Manager uses to
install, update, and remove the plugin. Everything it does is built on
OFFICIAL Unraid plugin mechanisms only — verified directly against the
real source of two long-established, widely used Unraid plugins
(unraid/community.applications and unraid/unassigned.devices) rather than
guessed:
- A FILE block with a Name attribute, an upgradepkg Run command (using
its install new and reinstall flags together), and a URL/MD5 child
pair is the standard way to download and install a Slackware .txz
package, and the same command works for both a fresh install and an
update in one step (confirmed: both reference plugins use exactly
this pattern rather than separate installpkg/upgradepkg branches).
- Plugin uninstall logic lives in a <FILE Run="/bin/bash" Method="remove">
block — this exact `Method="remove"` attribute is what Unraid's
plugin manager looks for when the user clicks "Remove" (confirmed
against community.applications.plg).
- Boot/shutdown integration uses the official Unraid plugin EVENT
mechanism: any executable at
/usr/local/emhttp/plugins/<name>/event/<eventname> is run automatically
by emhttpd as the corresponding event fires (confirmed against
unassigned.devices, which uses this exact convention for its own
disks_mounted/started/stopping_svcs hooks). This project deliberately
does NOT hand-edit /boot/config/go — see plugin/event/disks_mounted
and plugin/event/stopping for the two hooks used here, and
docs/ARCHITECTURE.md section 6.2 for the full event-ordering rationale.
Structure of this file:
1. DOCTYPE entity block — plugin metadata + one version/file/md5 triple
per package (the seven upstream components plus this project's own
"unraid-podman" scaffolding package, see packages/unraid-podman/).
Entities are rewritten automatically by scripts/release.sh; never
hand-edit a *_txz_version/_file/_md5 entity — see that script.
2. <PLUGIN> body:
a. <CHANGES> — kept in sync with CHANGELOG.md by hand for now.
b. Pre-install architecture sanity check.
c. Eight <FILE> package install/update blocks.
d. Postinstall <FILE Run="/bin/bash"> — directory/config seeding,
install-manifest generation, first start.
e. <FILE Run="/bin/bash" Method="remove"> — uninstall.
-->
<!DOCTYPE PLUGIN [
<!ENTITY name "podman">
<!ENTITY author "unraid-podman contributors">
<!ENTITY version "0.0.0">
<!-- "Podman" (no parent) — Podman.page declares Menu="Podman", making it
its own top-level nav tab next to Docker/VMs, not nested under
Settings — see webui/plugins/podman/Podman.page. -->
<!ENTITY launch "Podman">
<!ENTITY github "OWNER/unraid-podman">
<!ENTITY gitURL "https://raw.githubusercontent.com/&github;/main">
<!ENTITY pluginURL "&gitURL;/plugin/podman.plg">
<!ENTITY supportURL "https://github.com/&github;/discussions">
<!-- Release asset base — matches scripts/release.sh's RELEASE_BASE_URL
exactly; both must agree since release.sh is what publishes the
packages this URL is expected to find. -->
<!ENTITY baseURL "https://github.com/&github;/releases/download/v&version;">
<!-- Slackware package naming components — must match versions.env's
PKG_ARCH/PKG_BUILD/PKG_TAG (see that file). Kept as entities here so
the eight removepkg calls in the Method="remove" block don't have to
repeat "x86_64-1_unraidpodman" eight times by hand. -->
<!ENTITY pkgArch "x86_64">
<!ENTITY pkgBuild "1">
<!ENTITY pkgTag "_unraidpodman">
<!-- One version/file/md5 entity triple per package, filled in by
scripts/release.sh from dist/*.txz.md5 at release time (see that
script's "Update per-package entities" step). Until a release has been
cut, these are placeholders and the FILE blocks below will correctly
fail to download anything — that is intentional; there is nothing to
install before the first tagged release. -->
<!ENTITY podman_txz_version "0.0.0">
<!ENTITY podman_txz_file "podman-&podman_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY podman_txz_md5 "00000000000000000000000000000000">
<!ENTITY conmon_txz_version "0.0.0">
<!ENTITY conmon_txz_file "conmon-&conmon_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY conmon_txz_md5 "00000000000000000000000000000000">
<!ENTITY crun_txz_version "0.0.0">
<!ENTITY crun_txz_file "crun-&crun_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY crun_txz_md5 "00000000000000000000000000000000">
<!ENTITY netavark_txz_version "0.0.0">
<!ENTITY netavark_txz_file "netavark-&netavark_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY netavark_txz_md5 "00000000000000000000000000000000">
<!ENTITY aardvark_dns_txz_version "0.0.0">
<!ENTITY aardvark_dns_txz_file "aardvark-dns-&aardvark_dns_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY aardvark_dns_txz_md5 "00000000000000000000000000000000">
<!ENTITY passt_txz_version "0.0.0">
<!ENTITY passt_txz_file "passt-&passt_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY passt_txz_md5 "00000000000000000000000000000000">
<!ENTITY fuse_overlayfs_txz_version "0.0.0">
<!ENTITY fuse_overlayfs_txz_file "fuse-overlayfs-&fuse_overlayfs_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY fuse_overlayfs_txz_md5 "00000000000000000000000000000000">
<!-- unraid-podman is this project's OWN scaffolding package (rc.podman,
sbin/ scripts, event/ hooks, config templates — see
packages/unraid-podman/README.md), not an upstream component. Its
version always equals the plugin's own &version; — see
packages/unraid-podman/unraid-podman.SlackBuild, which reads it
straight out of this very file rather than tracking it twice. -->
<!ENTITY unraid_podman_txz_version "&version;">
<!ENTITY unraid_podman_txz_file "unraid-podman-&unraid_podman_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
<!ENTITY unraid_podman_txz_md5 "00000000000000000000000000000000">
]>
<PLUGIN name="&name;"
author="&author;"
version="&version;"
launch="&launch;"
pluginURL="&pluginURL;"
support="&supportURL;"
icon="cubes"
min="6.12.0">
<!--
min="6.12.0": Unraid 6.12 introduced folder-based (non-loopback) Docker
storage and is a reasonably conservative baseline for the cgroup v2 /
kernel networking features netavark, aardvark-dns, and passt assume. See
docs/ARCHITECTURE.md section 21 (open question — revisit if real-world
testing shows an earlier or later minimum is actually required).
-->
<CHANGES>
##podman
###0.0.0
- Initial scaffolding. Not a functional release — see CHANGELOG.md for the
authoritative, up-to-date history; this block is kept in sync with it by
hand at release time (scripts/release.sh does not touch this section).
</CHANGES>
<!--
Pre-install sanity check: this project only builds/ships x86_64 packages
(see versions.env's PKG_ARCH) — fail with a clear message on any other
architecture rather than letting eight package downloads 404 one by one.
-->
<FILE Run="/bin/bash">
<INLINE>
if [ "$(uname -m)" != "x86_64" ]; then
echo "unraid-podman only supports x86_64 (detected: $(uname -m)) - aborting install."
exit 1
fi
</INLINE>
</FILE>
<!--
The seven upstream component packages. Each is downloaded straight into
its backup slot under /boot/config/plugins/&name;/backup/packages/&version;/
(grouped by PLUGIN version, not each component's own version — a rollback
targets "this plugin release" as one unit, see
plugin/sbin/podman-backup.sh's header comment) and installed from there
in place via upgradepkg's install new / reinstall flags, which handle
both a fresh install and an update to a newer version identically (see
file header comment). This download-destination-doubles-as-backup approach is
what makes plugin/sbin/podman-backup.sh's restore-packages command work
with no separate copy step.
-->
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&podman_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&podman_txz_file;
</URL>
<MD5>
&podman_txz_md5;
</MD5>
</FILE>
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&conmon_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&conmon_txz_file;
</URL>
<MD5>
&conmon_txz_md5;
</MD5>
</FILE>
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&crun_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&crun_txz_file;
</URL>
<MD5>
&crun_txz_md5;
</MD5>
</FILE>
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&netavark_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&netavark_txz_file;
</URL>
<MD5>
&netavark_txz_md5;
</MD5>
</FILE>
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&aardvark_dns_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&aardvark_dns_txz_file;
</URL>
<MD5>
&aardvark_dns_txz_md5;
</MD5>
</FILE>
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&passt_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&passt_txz_file;
</URL>
<MD5>
&passt_txz_md5;
</MD5>
</FILE>
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&fuse_overlayfs_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&fuse_overlayfs_txz_file;
</URL>
<MD5>
&fuse_overlayfs_txz_md5;
</MD5>
</FILE>
<!--
The plugin's own scaffolding package — rc.podman, sbin/ helper scripts,
the event/ hooks below, and default config templates. See
packages/unraid-podman/README.md.
-->
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&unraid_podman_txz_file;" Run="upgradepkg --install-new --reinstall">
<URL>
&baseURL;/&unraid_podman_txz_file;
</URL>
<MD5>
&unraid_podman_txz_md5;
</MD5>
</FILE>
<!--
Postinstall: everything that has to happen AFTER the eight packages above
are on disk, but isn't itself package content — directory/config
seeding, the install-manifest that plugin/sbin/podman-verify-packages.sh
and podman-update-packages.sh read, and the first start. Runs on both
fresh install and every update (Unraid re-runs the whole .plg either way)
— every step here is written to be idempotent, per
docs/ARCHITECTURE.md section 7.
-->
<!--
IMPORTANT for anyone editing this block: it is deliberately NOT wrapped
in <![CDATA[ ]]>. CDATA sections suppress XML entity expansion entirely,
which would leave literal, unexpanded text like "&version;" in the
generated installed-versions.env instead of the real version number —
confirmed by direct testing against this exact file. This is also why
real Unraid plugins (community.applications, unassigned.devices) never
wrap their entity-referencing INLINE scripts in CDATA either. The
practical consequence: avoid literal '<' in this script (heredocs
included) — a sequence of individual "echo >>" lines is used below
instead for exactly that reason. A lone '>' is fine unescaped in XML
content (only '<' and '&' are not) — verified empirically, not assumed.
-->
<FILE Run="/bin/bash">
<INLINE>
set -u
echo "Setting permissions..."
chmod 0755 /etc/rc.d/rc.podman
chmod 0755 /usr/local/sbin/*.sh
chmod 0755 /usr/local/emhttp/plugins/podman/event/disks_mounted
chmod 0755 /usr/local/emhttp/plugins/podman/event/stopping
echo "Writing installed-package manifest..."
mkdir -p /usr/local/share/unraid-podman
MANIFEST=/usr/local/share/unraid-podman/installed-versions.env
echo "# Generated by podman.plg postinstall - do not edit by hand." > "$MANIFEST"
echo "# Read by podman-verify-packages.sh and podman-update-packages.sh." >> "$MANIFEST"
echo "PLUGIN_VERSION=\"&version;\"" >> "$MANIFEST"
echo "PODMAN_INSTALLED_VERSION=\"&podman_txz_version;\"" >> "$MANIFEST"
echo "CONMON_INSTALLED_VERSION=\"&conmon_txz_version;\"" >> "$MANIFEST"
echo "CRUN_INSTALLED_VERSION=\"&crun_txz_version;\"" >> "$MANIFEST"
echo "NETAVARK_INSTALLED_VERSION=\"&netavark_txz_version;\"" >> "$MANIFEST"
echo "AARDVARK_DNS_INSTALLED_VERSION=\"&aardvark_dns_txz_version;\"" >> "$MANIFEST"
echo "PASST_INSTALLED_VERSION=\"&passt_txz_version;\"" >> "$MANIFEST"
echo "FUSE_OVERLAYFS_INSTALLED_VERSION=\"&fuse_overlayfs_txz_version;\"" >> "$MANIFEST"
echo "UNRAID_PODMAN_INSTALLED_VERSION=\"&unraid_podman_txz_version;\"" >> "$MANIFEST"
echo "Seeding /boot/config/plugins/podman/ configuration (existing files left untouched)..."
/usr/local/sbin/podman-config.sh seed
echo "Verifying package installation..."
if /usr/local/sbin/podman-verify-packages.sh --quiet; then
echo "Package verification OK."
else
echo "WARNING: package verification reported problems - see /boot/config/plugins/podman/plugin.log"
fi
echo "Starting podman..."
if /etc/rc.d/rc.podman start; then
echo ""
echo "----------------------------------------------------"
echo " unraid-podman &version; has been installed and started."
echo " CLI: podman --url unix:///var/run/podman/podman.sock ..."
echo " Docs: https://github.com/&github;"
echo "----------------------------------------------------"
echo ""
else
echo ""
echo "----------------------------------------------------"
echo " unraid-podman &version; was installed but did not start cleanly."
echo " It will be retried automatically the next time the array starts"
echo " (see /usr/local/emhttp/plugins/podman/event/disks_mounted)."
echo " Check /boot/config/plugins/podman/plugin.log for details."
echo "----------------------------------------------------"
echo ""
fi
</INLINE>
</FILE>
<!--
Uninstall. Triggered by Unraid's Plugin Manager when the user clicks
"Remove" — the Method="remove" attribute is what makes this block
(rather than any of the install blocks above) run in that case.
Order matters: podman-uninstall-cleanup.sh runs FIRST, while the
unraid-podman package (which contains it, at /usr/local/sbin/) is still
on disk — removepkg-ing it out from under itself mid-script would be a
mistake. See that script's own header comment for exactly what is and
is not removed by default (user data is preserved unless the purge-data
flag is passed, which this normal removal flow deliberately never does).
-->
<!-- Not CDATA-wrapped — see the comment above the postinstall block for why: this content needs its &..._txz_file; entity references expanded, and CDATA would suppress that. Nothing here needs an unescaped '<'. -->
<FILE Run="/bin/bash" Method="remove">
<INLINE>
echo "Running unraid-podman cleanup..."
/usr/local/sbin/podman-uninstall-cleanup.sh
echo "Removing packages..."
removepkg &podman_txz_file;
removepkg &conmon_txz_file;
removepkg &crun_txz_file;
removepkg &netavark_txz_file;
removepkg &aardvark_dns_txz_file;
removepkg &passt_txz_file;
removepkg &fuse_overlayfs_txz_file;
removepkg &unraid_podman_txz_file;
echo ""
echo "unraid-podman removed. Your configuration, backups, and container"
echo "storage under /boot/config/plugins/podman/ and the configured"
echo "STORAGE_PATH were left in place - see docs/INSTALL.md if you want"
echo "to remove them too."
echo ""
</INLINE>
</FILE>
</PLUGIN>