- podman-common.sh: file-wide shellcheck disable=SC2034 for its constants, which are intentionally defined for other scripts to source, not used within the file itself (ShellCheck can't see cross-file usage in that direction). Confirmed as the actual cause of the failing ShellCheck job via the real Gitea Actions log. - podman-storage.sh: use the retry counter in cmd_unmount's log message instead of leaving it genuinely unused (real SC2034 hit, not a false positive). - lint.yml: replace the editorconfig-checker/action-* marketplace action with a pinned, checksum-verified direct binary download — more robust than relying on third-party action resolution on self-hosted Gitea Actions runners — and disable its IndentSize check, which false-positives on Markdown's nested-list indentation and shell heredoc bodies. scripts/dev/lint.sh mirrors the same check for local use. All three lint.yml jobs verified passing locally. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
182 lines
7.2 KiB
Bash
Executable File
182 lines
7.2 KiB
Bash
Executable File
#!/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
|
|
podman_log "storage: unmount busy, retrying (attempt $attempt/5)"
|
|
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
|