Root cause of a fresh-install "cannot reach the Podman API socket" report (a friend's Unraid box, cache pool present and mounted): unlike Docker-for-Unraid's docker.img path, this plugin never auto-created STORAGE_PATH itself — only podman.img inside it. A perfectly normal, already-mounted cache pool still failed preflight/storage-create with "does not exist", just because its own .../system/podman subdirectory had never been created. Fixed by walking up to the nearest existing ancestor and checking whether it's on a different device than / (real mount vs. nothing mounted at all) — see podman-common.sh's new podman_path_has_real_mount_ancestor(), used by both podman-preflight.sh and podman-storage.sh. Settings gets a "Podman Service" card (status chip + Start/Restart, backed by new ajax/settings.php service_status/start/restart actions that just shell out to rc.podman) so a fresh install that failed to start can be diagnosed and retried without SSH/terminal access at all — exactly what was missing when this was first needed live. Also adds "Format a Disk for Podman Storage" (new ajax/disks.php) for a single-disk system with no cache pool at all. Only ever lists disks with literally no existing partition/filesystem/RAID-or-ZFS-membership signature and that aren't Unraid's boot flash — found live, twice, during development: the boot USB (FAT, labeled "UNRAID") passed the initial mounted-only check because this host's /boot is backed by a ZFS dataset rather than a direct partition mount, and active RAID-member cache disks passed a data-vs-blank *warning* rather than a hard exclusion. Both are now excluded outright, not just flagged — see disks.php's device_or_children_labeled_unraid() and the hasData exclusion in list_candidate_disks(). A disk formatted this way is remounted by UUID on every boot via a new plugin/sbin/podman-mount-managed-disk.sh, called from plugin/event/disks_mounted before rc.podman start. Unrelated fix bundled in: scripts/lib/slackbuild-common.sh now sets SOURCE_DATE_EPOCH (derived from the repo's last commit) before calling makepkg, so two separate builds of the same commit produce byte-identical .txz files — makepkg already supports this (`--clamp-mtime` when $SOURCE_DATE_EPOCH is set, confirmed by reading a real host's /sbin/makepkg) but nothing was setting the variable, so release.yml's "rebuild in CI and verify it matches the committed checksums" step was guaranteed to fail on the first package it checked alphabetically (observed live: aardvark-dns). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
211 lines
9.2 KiB
Bash
Executable File
211 lines
9.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# =============================================================================
|
|
# plugin/sbin/podman-common.sh
|
|
#
|
|
# Shared constants and helper functions sourced by every other script under
|
|
# plugin/sbin/ and by plugin/rc.d/rc.podman. Centralizing these here means:
|
|
# - every script agrees on the same paths (no risk of one script writing
|
|
# config to a path another script reads from a slightly different one),
|
|
# - logging/notification behavior is consistent everywhere,
|
|
# - each individual script stays focused on its one job instead of
|
|
# re-implementing "how do I log this" or "where is podman.cfg".
|
|
#
|
|
# This file is NOT meant to be executed directly — it only defines
|
|
# functions/variables for other scripts to source:
|
|
# . "$(dirname "${BASH_SOURCE[0]}")/podman-common.sh"
|
|
#
|
|
# See docs/ARCHITECTURE.md sections 4 (Verzeichnislayout), 7 (Persistenz),
|
|
# 15 (Logging), 16 (Fehlerbehandlung) for the design this implements.
|
|
# =============================================================================
|
|
|
|
# We deliberately do NOT `set -e` in this file: it is sourced by scripts
|
|
# that set their own error-handling mode, and a library changing its
|
|
# caller's shell options would be a surprising action at a distance.
|
|
|
|
# shellcheck disable=SC2034
|
|
# This whole file is a "library" of constants and functions for OTHER
|
|
# scripts to `source` — see the header above. ShellCheck analyzes each file
|
|
# in isolation and has no way to see that e.g. $PODMAN_AUTOSTART_FILE is
|
|
# used in podman-autostart.sh, not here, so it flags every constant below
|
|
# as "appears unused". That is a false positive for this file's actual
|
|
# role; disabling SC2034 file-wide (this directive, placed before any code,
|
|
# applies to the whole file per ShellCheck's own rules) is more honest than
|
|
# scattering 15+ identical per-line suppressions.
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Path constants.
|
|
#
|
|
# PODMAN_BOOT_DIR is the persistent, source-of-truth configuration directory
|
|
# on the Unraid flash device (survives reboots — see ARCHITECTURE.md 4.1).
|
|
# Everything under /etc, /var, /usr is RAM-root and rebuilt from here (or
|
|
# from the array/cache-backed storage dir) on every boot.
|
|
# -----------------------------------------------------------------------------
|
|
PODMAN_BOOT_DIR="/boot/config/plugins/podman"
|
|
PODMAN_CFG_FILE="$PODMAN_BOOT_DIR/podman.cfg"
|
|
PODMAN_AUTOSTART_FILE="$PODMAN_BOOT_DIR/autostart"
|
|
PODMAN_AUTOSTART_DELAY_FILE="$PODMAN_BOOT_DIR/autostart-delay"
|
|
PODMAN_NETWORKS_BOOT_DIR="$PODMAN_BOOT_DIR/networks"
|
|
PODMAN_BACKUP_DIR="$PODMAN_BOOT_DIR/backup"
|
|
PODMAN_PLUGIN_LOG="$PODMAN_BOOT_DIR/plugin.log"
|
|
|
|
# Runtime (RAM-root) locations rebuilt/synced on every rc.podman start.
|
|
PODMAN_ETC_DIR="/etc/containers"
|
|
PODMAN_RUN_DIR="/var/run/podman"
|
|
PODMAN_SOCKET="$PODMAN_RUN_DIR/podman.sock"
|
|
PODMAN_STATUS_FILE="$PODMAN_RUN_DIR/rc.podman.status"
|
|
PODMAN_SERVICE_PID_FILE="$PODMAN_RUN_DIR/podman-service.pid"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# podman_load_cfg
|
|
#
|
|
# Sources /boot/config/plugins/podman/podman.cfg (the user-editable settings
|
|
# file, see config/podman.cfg.example) and applies safe defaults for any
|
|
# setting that file doesn't define — so every other script can simply
|
|
# reference $STORAGE_PATH, $PODMAN_ENABLED, etc. after calling this, without
|
|
# each script having its own copy of the defaults.
|
|
# -----------------------------------------------------------------------------
|
|
podman_load_cfg() {
|
|
# Defaults, applied BEFORE sourcing podman.cfg so the file only needs to
|
|
# override what the user actually wants to change.
|
|
STORAGE_PATH="/mnt/cache/system/podman"
|
|
STORAGE_IMAGE_SIZE_GB="20"
|
|
PODMAN_ENABLED="yes"
|
|
STOP_TIMEOUT="10"
|
|
CONFIG_SCHEMA_VERSION="1"
|
|
|
|
if [ -f "$PODMAN_CFG_FILE" ]; then
|
|
# shellcheck source=/dev/null
|
|
. "$PODMAN_CFG_FILE"
|
|
fi
|
|
|
|
# Values derived from STORAGE_PATH, computed after sourcing so a custom
|
|
# STORAGE_PATH is honored.
|
|
PODMAN_STORAGE_IMAGE="$STORAGE_PATH/podman.img"
|
|
PODMAN_LOG_DIR="$STORAGE_PATH/logs"
|
|
PODMAN_GRAPHROOT="/var/lib/containers/storage"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# podman_log <message>
|
|
#
|
|
# Writes a timestamped line to both stdout (so it shows up in `rc.podman`
|
|
# invocations and CI/manual runs) and to the persistent plugin log on flash
|
|
# (so it survives a reboot — see ARCHITECTURE.md section 15, Logging). Kept
|
|
# deliberately terse (no log levels/rotation logic here) — this is for
|
|
# install/lifecycle events, not container output, which lives under
|
|
# $PODMAN_LOG_DIR instead.
|
|
# -----------------------------------------------------------------------------
|
|
podman_log() {
|
|
local msg="$1"
|
|
local line
|
|
line="$(date -u +'%Y-%m-%dT%H:%M:%SZ') [podman] $msg"
|
|
echo "$line"
|
|
# Best-effort: the boot directory should always exist post-install, but
|
|
# never let logging itself fail a caller that has `set -e`.
|
|
mkdir -p "$PODMAN_BOOT_DIR" 2> /dev/null || true
|
|
echo "$line" >> "$PODMAN_PLUGIN_LOG" 2> /dev/null || true
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# podman_log_error <message>
|
|
#
|
|
# Like podman_log, but also mirrors the message to the system log via
|
|
# `logger`, so it is visible in Unraid's Tools -> System Log GUI without the
|
|
# user having to know where the plugin's own log lives. Reserved for
|
|
# conditions the user should actually notice (see ARCHITECTURE.md 16.2).
|
|
# -----------------------------------------------------------------------------
|
|
podman_log_error() {
|
|
local msg="$1"
|
|
podman_log "ERROR: $msg"
|
|
if command -v logger > /dev/null 2>&1; then
|
|
logger -t podman-plugin "$msg"
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# podman_notify <subject> <description> [importance]
|
|
#
|
|
# Surfaces a message via Unraid's own GUI notification system
|
|
# (/usr/local/emhttp/webGui/scripts/notify) instead of inventing a parallel
|
|
# notification mechanism — see ARCHITECTURE.md section 16.2. Silently
|
|
# no-ops if that script isn't present (e.g. when running outside a real
|
|
# Unraid system, such as in CI/lint contexts).
|
|
#
|
|
# importance: "normal" (default), "warning", or "alert".
|
|
# -----------------------------------------------------------------------------
|
|
podman_notify() {
|
|
local subject="$1"
|
|
local description="$2"
|
|
local importance="${3:-normal}"
|
|
local notify_bin="/usr/local/emhttp/webGui/scripts/notify"
|
|
|
|
podman_log "notify [$importance] $subject: $description"
|
|
|
|
if [ -x "$notify_bin" ]; then
|
|
"$notify_bin" -e "unraid-podman" -s "$subject" -d "$description" -i "$importance" \
|
|
> /dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# podman_storage_path_is_safe
|
|
#
|
|
# Refuses storage paths under /mnt/user (FUSE/shfs) — see
|
|
# ARCHITECTURE.md section 4.3 for why the overlay storage driver must live
|
|
# on a real mounted filesystem (cache pool or a specific disk), not shfs.
|
|
# Returns 0 (safe) or 1 (unsafe) and prints a reason on failure.
|
|
# -----------------------------------------------------------------------------
|
|
podman_storage_path_is_safe() {
|
|
local path="$1"
|
|
case "$path" in
|
|
/mnt/user/*|/mnt/user)
|
|
echo "STORAGE_PATH ($path) is under /mnt/user (FUSE/shfs)." >&2
|
|
echo "The overlay storage driver needs a real mounted filesystem —" >&2
|
|
echo "use a cache pool or a specific disk path instead, e.g. /mnt/cache/system/podman." >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# podman_path_has_real_mount_ancestor <path>
|
|
#
|
|
# True if <path> itself, or its nearest EXISTING ancestor directory, lives
|
|
# on a different filesystem than / (root) — i.e. something is genuinely
|
|
# mounted along this path (a cache pool, a dedicated disk, ...), even if
|
|
# the exact leaf directory doesn't exist yet. False only when nothing real
|
|
# is mounted anywhere along the path (root/RAM all the way up), which is
|
|
# the one case that's actually unsafe to silently `mkdir -p` into.
|
|
#
|
|
# This exists because this project never auto-created $STORAGE_PATH
|
|
# itself (only podman.img inside it) — found live: a perfectly normal,
|
|
# already-mounted cache pool still failed preflight/storage-create with
|
|
# "does not exist", because the pool's own .../system/podman subdirectory
|
|
# had simply never been created. "Does the exact leaf directory exist" was
|
|
# always the wrong question; "is a real filesystem mounted somewhere along
|
|
# this path" is the one that actually matters.
|
|
# -----------------------------------------------------------------------------
|
|
podman_path_has_real_mount_ancestor() {
|
|
local path="$1"
|
|
local parent="$path"
|
|
while [ ! -d "$parent" ] && [ "$parent" != "/" ]; do
|
|
parent="$(dirname "$parent")"
|
|
done
|
|
[ "$parent" != "/" ] && [ "$(stat -c %d "$parent")" != "$(stat -c %d /)" ]
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# podman_require_command <binary>
|
|
#
|
|
# Fails loudly (rather than letting a script continue and fail confusingly
|
|
# three steps later) if a required binary isn't on PATH.
|
|
# -----------------------------------------------------------------------------
|
|
podman_require_command() {
|
|
local bin="$1"
|
|
if ! command -v "$bin" > /dev/null 2>&1; then
|
|
podman_log_error "required command not found: $bin"
|
|
return 1
|
|
fi
|
|
}
|