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>
This commit is contained in:
Executable
+180
@@ -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
|
||||
Reference in New Issue
Block a user