Add reproducible build system, native Unraid plugin, and WebUI
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

- 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:
2026-07-11 10:51:14 +00:00
co-authored by Claude Sonnet 5
parent 58ffc0c226
commit e2fefcdf9c
124 changed files with 9611 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
#!/bin/bash
# =============================================================================
# plugin/sbin/podman-storage.sh
#
# "Container Storage erstellen" — creates, mounts, and unmounts the
# podman.img loopback filesystem that backs Podman's overlay storage graph
# (images, containers, named volumes). See docs/ARCHITECTURE.md section 4.3
# for why this needs to be a real mounted filesystem (XFS with ftype=1, on a
# cache pool or dedicated disk) rather than a directory under /mnt/user —
# the FUSE (shfs) layer behind /mnt/user does not reliably support the
# overlay storage driver's filesystem requirements (d_type, etc).
#
# Usage:
# podman-storage.sh create # create podman.img if it doesn't exist yet
# podman-storage.sh mount # mount it at $PODMAN_GRAPHROOT (idempotent)
# podman-storage.sh unmount # cleanly unmount
# podman-storage.sh status # report existence/mount/usage
# =============================================================================
set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./podman-common.sh
. "$SCRIPT_DIR/podman-common.sh"
podman_load_cfg
# -----------------------------------------------------------------------------
# cmd_create
#
# Creates podman.img at the configured size if it doesn't already exist,
# and formats it XFS with ftype=1 (required for the overlay storage driver
# to see correct directory entry types — without it, podman's overlay
# backend silently falls back to a slower/less-capable mode or fails
# outright, depending on version). Does nothing (and exits 0) if the image
# already exists — this script is meant to be safe to call on every boot.
# -----------------------------------------------------------------------------
cmd_create() {
if ! podman_storage_path_is_safe "$STORAGE_PATH"; then
return 1
fi
if [ -f "$PODMAN_STORAGE_IMAGE" ]; then
podman_log "storage: $PODMAN_STORAGE_IMAGE already exists, not recreating"
return 0
fi
if [ ! -d "$STORAGE_PATH" ]; then
podman_log_error "storage: $STORAGE_PATH does not exist or is not mounted."
podman_log_error "storage: check that the configured cache pool/disk is present before starting podman."
return 1
fi
# Free space check: refuse to create an image bigger than what's actually
# available, with a small safety margin, rather than letting truncate
# silently create a sparse file that will fail unpredictably later once
# it's actually written to (see docs/ARCHITECTURE.md section 16.2).
local available_kb required_kb
available_kb=$(df --output=avail -k "$STORAGE_PATH" | tail -n1 | tr -d '[:space:]')
required_kb=$((STORAGE_IMAGE_SIZE_GB * 1024 * 1024))
if [ "$available_kb" -lt "$required_kb" ]; then
podman_log_error "storage: not enough free space on $STORAGE_PATH (need ${STORAGE_IMAGE_SIZE_GB}G, have $((available_kb / 1024 / 1024))G)"
podman_notify "Podman storage creation failed" \
"Not enough free space on $STORAGE_PATH for a ${STORAGE_IMAGE_SIZE_GB}G podman.img." \
"alert"
return 1
fi
podman_log "storage: creating ${STORAGE_IMAGE_SIZE_GB}G image at $PODMAN_STORAGE_IMAGE"
mkdir -p "$STORAGE_PATH"
# Sparse file: only actually consumes disk space as data is written,
# matching Docker-for-Unraid's docker.img behavior that users already
# understand.
truncate -s "${STORAGE_IMAGE_SIZE_GB}G" "$PODMAN_STORAGE_IMAGE"
podman_require_command mkfs.xfs
# -n ftype=1 is the whole reason this has to be a purpose-made image
# rather than a plain directory — see file header comment.
mkfs.xfs -n ftype=1 -q "$PODMAN_STORAGE_IMAGE"
podman_log "storage: created and formatted $PODMAN_STORAGE_IMAGE"
}
# -----------------------------------------------------------------------------
# cmd_mount
#
# Idempotent: does nothing if $PODMAN_GRAPHROOT is already mounted from
# podman.img. Runs a read-only integrity check (xfs_repair -n) before
# mounting and refuses to proceed if it reports corruption — per
# docs/ARCHITECTURE.md section 16.1, this project does not auto-repair
# storage without the user's explicit action, to avoid silent data loss.
# -----------------------------------------------------------------------------
cmd_mount() {
if mountpoint -q "$PODMAN_GRAPHROOT" 2> /dev/null; then
podman_log "storage: $PODMAN_GRAPHROOT already mounted"
return 0
fi
if [ ! -f "$PODMAN_STORAGE_IMAGE" ]; then
podman_log_error "storage: $PODMAN_STORAGE_IMAGE does not exist — run 'podman-storage.sh create' first"
return 1
fi
podman_require_command xfs_repair
podman_log "storage: checking filesystem integrity of $PODMAN_STORAGE_IMAGE"
if ! xfs_repair -n "$PODMAN_STORAGE_IMAGE" > /tmp/podman-xfs-repair.log 2>&1; then
podman_log_error "storage: filesystem check failed for $PODMAN_STORAGE_IMAGE — refusing to mount."
podman_log_error "storage: see /tmp/podman-xfs-repair.log. Run 'xfs_repair $PODMAN_STORAGE_IMAGE' manually to attempt repair, or restore from backup."
podman_notify "Podman storage corruption detected" \
"$PODMAN_STORAGE_IMAGE failed an integrity check and was not mounted. Manual recovery required — see plugin.log." \
"alert"
return 1
fi
mkdir -p "$PODMAN_GRAPHROOT"
podman_log "storage: mounting $PODMAN_STORAGE_IMAGE at $PODMAN_GRAPHROOT"
mount -o loop "$PODMAN_STORAGE_IMAGE" "$PODMAN_GRAPHROOT"
# Runtime state (runroot) is tmpfs-backed and fine to live on RAM-root —
# only the persistent graphroot needs the loopback filesystem.
mkdir -p /var/run/containers/storage
podman_log "storage: mounted"
}
# -----------------------------------------------------------------------------
# cmd_unmount
#
# Unmounts cleanly. Retries briefly if the mount is momentarily busy (a
# container process exiting can hold a reference for a few hundred ms),
# rather than immediately failing rc.podman's stop sequence.
# -----------------------------------------------------------------------------
cmd_unmount() {
if ! mountpoint -q "$PODMAN_GRAPHROOT" 2> /dev/null; then
podman_log "storage: $PODMAN_GRAPHROOT not mounted, nothing to do"
return 0
fi
podman_log "storage: unmounting $PODMAN_GRAPHROOT"
local attempt
for attempt in 1 2 3 4 5; do
if umount "$PODMAN_GRAPHROOT" 2> /dev/null; then
podman_log "storage: unmounted"
return 0
fi
sleep 1
done
podman_log_error "storage: failed to unmount $PODMAN_GRAPHROOT after 5 attempts (still busy?)"
return 1
}
cmd_status() {
echo "storage image: $PODMAN_STORAGE_IMAGE"
if [ -f "$PODMAN_STORAGE_IMAGE" ]; then
echo " exists: yes ($(du -h "$PODMAN_STORAGE_IMAGE" | cut -f1) allocated / ${STORAGE_IMAGE_SIZE_GB}G nominal)"
else
echo " exists: no"
fi
echo "graphroot: $PODMAN_GRAPHROOT"
if mountpoint -q "$PODMAN_GRAPHROOT" 2> /dev/null; then
echo " mounted: yes"
df -h "$PODMAN_GRAPHROOT" | tail -n1 | awk '{print " usage: " $3 " used / " $2 " total (" $5 " full)"}'
else
echo " mounted: no"
fi
}
case "${1:-}" in
create) cmd_create ;;
mount) cmd_mount ;;
unmount) cmd_unmount ;;
status) cmd_status ;;
*)
echo "usage: $0 {create|mount|unmount|status}" >&2
exit 1
;;
esac