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
+103
View File
@@ -0,0 +1,103 @@
#!/bin/bash
# =============================================================================
# scripts/build-packages.sh
#
# Orchestrates building all seven Slackware .txz packages this plugin ships
# (podman, conmon, crun, netavark, aardvark-dns, passt, fuse-overlayfs), by
# running each package's <name>.SlackBuild in turn. See
# docs/ARCHITECTURE.md section 5.2 (Build-Strategie).
#
# This script itself does not containerize anything — it assumes it is
# already running inside a Slackware-compatible build environment (see
# .github/workflows/build-packages.yml, which runs it inside a pinned
# Slackware Docker image). Running it on a non-Slackware host will likely
# still compile successfully for most components but the resulting .txz
# should not be trusted for an actual Unraid install — see the container
# image note in the CI workflow.
#
# Usage:
# scripts/build-packages.sh # build all packages
# scripts/build-packages.sh podman conmon # build only the named ones
#
# Output: $REPO_ROOT/dist/<name>-<version>-<arch>-<build><tag>.txz
# plus a .sha256 and .md5 sidecar file per package (see
# scripts/lib/slackbuild-common.sh, sb_make_package).
# Nothing under dist/ is committed to git — see .gitignore.
# =============================================================================
set -eu
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PACKAGES_DIR="$REPO_ROOT/packages"
DIST_DIR="$REPO_ROOT/dist"
# The full, ordered set of packages this project builds automatically.
# containers-common is intentionally excluded for now — see packages/README.md.
# unraid-podman (the plugin's own scaffolding, not an upstream component —
# see packages/unraid-podman/README.md) is built last since it's by far the
# fastest and has nothing useful to report on failure that earlier package
# failures wouldn't already explain.
ALL_PACKAGES=(conmon crun netavark aardvark-dns passt fuse-overlayfs podman unraid-podman)
# Podman is listed second-to-last on purpose: among the seven upstream
# components it is the slowest build and the one most likely to fail on a
# dependency/tag mistake, so faster packages surface problems first during
# local iteration. unraid-podman is genuinely last since it packages this
# repo's own files and has no compile step at all.
requested=("$@")
if [ "${#requested[@]}" -eq 0 ]; then
targets=("${ALL_PACKAGES[@]}")
else
targets=("${requested[@]}")
fi
mkdir -p "$DIST_DIR"
echo "==> Building packages: ${targets[*]}"
echo "==> Output directory: $DIST_DIR"
echo
failed=()
built=()
for name in "${targets[@]}"; do
slackbuild="$PACKAGES_DIR/$name/$name.SlackBuild"
if [ ! -x "$slackbuild" ]; then
echo "!! No SlackBuild found/executable for '$name' ($slackbuild)" >&2
failed+=("$name")
continue
fi
echo "############################################################"
echo "## Building: $name"
echo "############################################################"
# Run each SlackBuild with OUTPUT pointed at the shared dist/ directory,
# so every package's .txz ends up in one place regardless of the
# per-package TMP/PKG scratch dirs it uses internally.
if OUTPUT="$DIST_DIR" "$slackbuild"; then
built+=("$name")
else
echo "!! Build failed: $name" >&2
failed+=("$name")
fi
echo
done
echo "============================================================"
echo "Build summary"
echo "============================================================"
echo "Succeeded (${#built[@]}): ${built[*]:-none}"
echo "Failed (${#failed[@]}): ${failed[*]:-none}"
if [ "${#failed[@]}" -gt 0 ]; then
echo
echo "!! One or more packages failed to build — see log above." >&2
exit 1
fi
echo
echo "All packages built successfully. Artifacts in $DIST_DIR:"
ls -1 "$DIST_DIR"
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
# =============================================================================
# scripts/checksums.sh
#
# Verifies and consolidates checksums for everything in dist/. Each package
# already gets its own <file>.sha256 / <file>.md5 sidecar file from
# sb_make_package (scripts/lib/slackbuild-common.sh) at build time — this
# script:
# 1. Re-verifies every .txz against its own sidecar checksum (defense in
# depth: catches disk corruption or a tampered artifact between the
# build job and the release job in CI).
# 2. Writes a single consolidated CHECKSUMS.sha256 manifest covering all
# built packages, suitable for attaching to a GitHub Release so users
# can verify the whole set with one `sha256sum -c CHECKSUMS.sha256`.
#
# Usage:
# scripts/checksums.sh [dist-dir] # defaults to <repo>/dist
# =============================================================================
set -eu
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIST_DIR="${1:-$REPO_ROOT/dist}"
if [ ! -d "$DIST_DIR" ]; then
echo "!! No such directory: $DIST_DIR (nothing built yet?)" >&2
exit 1
fi
shopt -s nullglob
txz_files=("$DIST_DIR"/*.txz)
shopt -u nullglob
if [ "${#txz_files[@]}" -eq 0 ]; then
echo "!! No .txz files found in $DIST_DIR" >&2
exit 1
fi
echo "==> Verifying per-package checksums"
verify_failed=0
for f in "${txz_files[@]}"; do
base=$(basename "$f")
sidecar="$f.sha256"
if [ ! -f "$sidecar" ]; then
echo "!! Missing $sidecar for $base" >&2
verify_failed=1
continue
fi
if ( cd "$DIST_DIR" && sha256sum -c "$(basename "$sidecar")" > /dev/null 2>&1 ); then
echo "OK $base"
else
echo "FAIL $base" >&2
verify_failed=1
fi
done
if [ "$verify_failed" -ne 0 ]; then
echo "!! Checksum verification failed for one or more packages." >&2
exit 1
fi
echo
echo "==> Writing consolidated manifest: $DIST_DIR/CHECKSUMS.sha256"
( cd "$DIST_DIR" && sha256sum ./*.txz > CHECKSUMS.sha256 )
echo "==> Writing consolidated MD5 manifest: $DIST_DIR/CHECKSUMS.md5"
( cd "$DIST_DIR" && md5sum ./*.txz > CHECKSUMS.md5 )
echo "==> Done."
cat "$DIST_DIR/CHECKSUMS.sha256"
+46
View File
@@ -0,0 +1,46 @@
# =============================================================================
# scripts/ci/buildenv-versions.env
#
# Version pins for the BUILD ENVIRONMENT itself — the toolchains and C
# libraries needed to compile the seven packages in packages/, but which are
# not themselves shipped as part of the plugin. Kept separate from the
# top-level versions.env, which pins only what actually gets packaged and
# installed on an Unraid system (see that file's header comment).
#
# Consumed by scripts/ci/setup-slackware-buildenv.sh.
# =============================================================================
# Go toolchain (builds podman). Official upstream tarball, not a distro
# package — Slackware ships no Go toolchain in a stock install.
GO_VERSION="1.26.5"
GO_SRC_URL="https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz"
# Checksum as published by go.dev itself (`curl -s https://go.dev/dl/?mode=json`)
# — must be re-derived from the same source whenever GO_VERSION changes.
GO_SRC_SHA256="88c162b204e6eefcc32499453b492e80209f4a4c78c33092636901c540fb0d05"
# Rust toolchain (builds netavark, aardvark-dns) — installed via rustup
# rather than a pinned tarball, since rustup itself provides reproducible,
# checksummed component installation. We pin the *channel*, not an exact
# rustc build; per-crate reproducibility comes from each Rust package's
# Cargo.lock (built with `cargo build --locked`, see the netavark/
# aardvark-dns SlackBuilds) rather than from the compiler version.
RUST_CHANNEL="stable"
RUSTUP_INIT_URL="https://sh.rustup.rs"
# --- C library build-time dependencies ---------------------------------------
# These are expected to already be present in the Slackware base image (part
# of a stock "full" Slackware 15.0 install): glib2 (conmon), libcap (crun),
# fuse3 (fuse-overlayfs). setup-slackware-buildenv.sh fails fast with a clear
# message if any of these are missing, rather than silently vendoring them.
#
# libseccomp and yajl are NOT part of a stock Slackware install and are
# built from source by setup-slackware-buildenv.sh if pkg-config doesn't
# find them.
LIBSECCOMP_VERSION="2.6.1"
LIBSECCOMP_SRC_URL="https://github.com/seccomp/libseccomp/archive/refs/tags/v${LIBSECCOMP_VERSION}.tar.gz"
LIBSECCOMP_SRC_SHA256="f9a13e4c633d319a9240189760ca348caa0837c0ebe2a09b17061da8ceaf60f0"
YAJL_VERSION="2.1.0"
YAJL_SRC_URL="https://github.com/lloyd/yajl/archive/refs/tags/${YAJL_VERSION}.tar.gz"
YAJL_SRC_SHA256="3fb73364a5a30efe615046d07e6db9d09fd2b41c763c5f7d3bfb121cd5c5ac5a"
+152
View File
@@ -0,0 +1,152 @@
#!/bin/bash
# =============================================================================
# scripts/ci/setup-slackware-buildenv.sh
#
# Prepares a Slackware container (see .github/workflows/build-packages.yml)
# to build all seven packages under packages/. Idempotent and safe to re-run.
#
# Strategy: detect what's already present (a stock "full" Slackware 15.0
# install already provides gcc/make/autotools/glib2/libcap/fuse3) and only
# bootstrap what's genuinely missing (libseccomp, yajl — neither ships in
# stock Slackware — plus the Go and Rust toolchains, which no Slackware
# install ships). This makes the script tolerant of small differences
# between Slackware base image variants instead of assuming one exact image
# layout, while still failing loudly if something we cannot self-provision
# (a C compiler, basically) is missing.
#
# Exits non-zero with a clear message if a required tool cannot be found or
# provisioned — this script is meant to run early in CI so failures surface
# immediately, not halfway through a 20-minute podman build.
# =============================================================================
set -eu
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# shellcheck source=/dev/null
. "$REPO_ROOT/scripts/ci/buildenv-versions.env"
WORK="/tmp/unraid-podman-buildenv"
mkdir -p "$WORK"
require_binary() {
local bin="$1" hint="$2"
if ! command -v "$bin" > /dev/null 2>&1; then
echo "!! Required tool '$bin' not found in the build image." >&2
echo "!! $hint" >&2
exit 1
fi
echo "==> found $bin: $(command -v "$bin")"
}
require_pkgconfig() {
local module="$1" hint="$2"
if ! pkg-config --exists "$module" 2>/dev/null; then
echo "!! Required library '$module' not found via pkg-config." >&2
echo "!! $hint" >&2
return 1
fi
echo "==> found pkg-config module: $module ($(pkg-config --modversion "$module"))"
return 0
}
# -----------------------------------------------------------------------------
# 1. Baseline toolchain expected to already be present in the base image.
# -----------------------------------------------------------------------------
require_binary gcc "Use a Slackware base image with the 'D' (development) series installed."
require_binary make "Use a Slackware base image with the 'D' (development) series installed."
require_binary autoconf "Needed by crun/fuse-overlayfs; part of Slackware's 'D' series."
require_binary automake "Needed by crun/fuse-overlayfs; part of Slackware's 'D' series."
require_binary libtool "Needed by crun/fuse-overlayfs; part of Slackware's 'D' series."
require_binary pkg-config "Needed to locate C library dependencies."
require_binary git "Needed to fetch crun's git submodules."
require_binary curl "Needed to fetch pinned source tarballs."
require_binary makepkg "Slackware's own packaging tool (pkgtools); should always be present."
require_binary strip "Part of binutils; part of Slackware's 'D' series."
# -----------------------------------------------------------------------------
# 2. C library dependencies expected to already be present.
# -----------------------------------------------------------------------------
require_pkgconfig glib-2.0 "Install Slackware's glib2 package (needed by conmon)."
require_pkgconfig libcap "Install Slackware's libcap package (needed by crun)." || true
require_pkgconfig fuse3 "Install Slackware's fuse3 package (needed by fuse-overlayfs)." || true
# -----------------------------------------------------------------------------
# 3. libseccomp — not part of stock Slackware, build from source if missing.
# -----------------------------------------------------------------------------
if ! pkg-config --exists libseccomp 2>/dev/null; then
echo "==> libseccomp not found, building v$LIBSECCOMP_VERSION from source"
d="$WORK/libseccomp"
mkdir -p "$d"
curl -fL --retry 3 -o "$d/src.tar.gz" "$LIBSECCOMP_SRC_URL"
actual=$(sha256sum "$d/src.tar.gz" | awk '{print $1}')
[ "$actual" = "$LIBSECCOMP_SRC_SHA256" ] || {
echo "!! libseccomp checksum mismatch (expected $LIBSECCOMP_SRC_SHA256, got $actual)" >&2
exit 1
}
mkdir -p "$d/src" && tar -xf "$d/src.tar.gz" -C "$d/src" --strip-components=1
( cd "$d/src" && ./autogen.sh && ./configure --prefix=/usr && make -j"$(nproc)" && make install )
else
echo "==> libseccomp already present, skipping bootstrap build"
fi
# -----------------------------------------------------------------------------
# 4. yajl — not part of stock Slackware, build from source if missing.
# -----------------------------------------------------------------------------
if ! pkg-config --exists yajl 2>/dev/null; then
echo "==> yajl not found, building v$YAJL_VERSION from source"
d="$WORK/yajl"
mkdir -p "$d"
curl -fL --retry 3 -o "$d/src.tar.gz" "$YAJL_SRC_URL"
actual=$(sha256sum "$d/src.tar.gz" | awk '{print $1}')
[ "$actual" = "$YAJL_SRC_SHA256" ] || {
echo "!! yajl checksum mismatch (expected $YAJL_SRC_SHA256, got $actual)" >&2
exit 1
}
mkdir -p "$d/src" && tar -xf "$d/src.tar.gz" -C "$d/src" --strip-components=1
# yajl uses its own cmake-free ./configure wrapper script.
( cd "$d/src" && ./configure -p /usr && make -C build install )
ldconfig 2>/dev/null || true
else
echo "==> yajl already present, skipping bootstrap build"
fi
# -----------------------------------------------------------------------------
# 5. Go toolchain (podman) — official upstream tarball.
# -----------------------------------------------------------------------------
if ! command -v go > /dev/null 2>&1; then
echo "==> Go not found, installing $GO_VERSION"
curl -fL --retry 3 -o "$WORK/go.tar.gz" "$GO_SRC_URL"
actual=$(sha256sum "$WORK/go.tar.gz" | awk '{print $1}')
[ "$actual" = "$GO_SRC_SHA256" ] || {
echo "!! Go toolchain checksum mismatch (expected $GO_SRC_SHA256, got $actual)" >&2
exit 1
}
rm -rf /usr/local/go
tar -C /usr/local -xf "$WORK/go.tar.gz"
export PATH="/usr/local/go/bin:$PATH"
# Persist PATH for subsequent steps in the same GitHub Actions job.
if [ -n "${GITHUB_PATH:-}" ]; then
echo "/usr/local/go/bin" >> "$GITHUB_PATH"
fi
else
echo "==> Go already present: $(go version)"
fi
# -----------------------------------------------------------------------------
# 6. Rust toolchain (netavark, aardvark-dns) — via rustup.
# -----------------------------------------------------------------------------
if ! command -v cargo > /dev/null 2>&1; then
echo "==> Rust/cargo not found, installing via rustup ($RUST_CHANNEL channel)"
curl -fL --retry 3 --proto '=https' --tlsv1.2 -sSf "$RUSTUP_INIT_URL" \
| sh -s -- -y --default-toolchain "$RUST_CHANNEL" --profile minimal
# shellcheck source=/dev/null
. "$HOME/.cargo/env"
if [ -n "${GITHUB_PATH:-}" ]; then
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
fi
else
echo "==> Rust already present: $(cargo --version)"
fi
echo
echo "==> Build environment ready."
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
# =============================================================================
# scripts/dev/lint.sh
#
# Local developer entry point mirroring .github/workflows/lint.yml: runs
# ShellCheck over shell scripts/SlackBuilds and xmllint over plugin/podman.plg.
# Requires `shellcheck` and `xmllint` (libxml2) to be installed locally.
# =============================================================================
set -eu
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$REPO_ROOT"
status=0
if command -v shellcheck > /dev/null 2>&1; then
echo "==> ShellCheck"
find plugin/rc.d plugin/sbin scripts -type f \
\( -name '*.sh' -o -name 'rc.*' -o -name '*.SlackBuild' \) \
-print0 \
| xargs -0 shellcheck --severity=warning --external-sources || status=1
else
echo "!! shellcheck not installed, skipping (install it to match CI: https://www.shellcheck.net/)" >&2
fi
if command -v xmllint > /dev/null 2>&1; then
echo "==> xmllint (plugin/podman.plg)"
xmllint --noout plugin/podman.plg || status=1
else
echo "!! xmllint not installed, skipping (part of libxml2-utils)" >&2
fi
exit "$status"
+176
View File
@@ -0,0 +1,176 @@
#!/bin/bash
# =============================================================================
# scripts/lib/slackbuild-common.sh
#
# Shared helper functions sourced by every packages/*/*.SlackBuild script.
# Centralizing this logic means each individual SlackBuild only has to
# describe *how to compile* its component — fetching, checksum verification,
# and final .txz packaging are implemented once, here, and used the same way
# by all seven packages. This is what keeps the seven build scripts
# consistent and short instead of each reinventing (and potentially
# forgetting) checksum verification or Slackware package metadata.
#
# Every SlackBuild is expected to:
# 1. `. "$(dirname "$0")/../../scripts/lib/slackbuild-common.sh"`
# 2. `. "$(dirname "$0")/../../versions.env"`
# 3. Call sb_init, sb_fetch_and_verify, do its own compile steps into
# $PKG, then call sb_make_package.
#
# Not meant to be run directly.
# =============================================================================
set -eu
# -----------------------------------------------------------------------------
# sb_init <pkg-name>
#
# Sets up the standard SlackBuild working directories and exports the
# variables every subsequent helper (and the calling SlackBuild) relies on:
# CWD - directory the SlackBuild itself lives in (packages/<name>/)
# TMP - scratch/build directory (source is extracted and compiled here)
# PKG - staging directory that becomes the .txz payload
# OUTPUT - where the finished .txz + checksum files are written
# All three of TMP/PKG/OUTPUT are safe to delete/recreate; nothing outside
# of them is ever touched, and nothing under them is ever committed to git
# (see .gitignore: packages/**/src, packages/**/pkg, packages/**/work).
# -----------------------------------------------------------------------------
sb_init() {
local pkg_name="$1"
PRGNAM="$pkg_name"
CWD="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)"
TMP="${TMP:-$CWD/work}"
PKG="${PKG:-$TMP/package-$PRGNAM}"
OUTPUT="${OUTPUT:-$CWD/../../dist}"
export PRGNAM CWD TMP PKG OUTPUT
rm -rf "$TMP"
mkdir -p "$TMP" "$PKG" "$OUTPUT"
echo "==> [$PRGNAM] TMP=$TMP"
echo "==> [$PRGNAM] PKG=$PKG"
echo "==> [$PRGNAM] OUTPUT=$OUTPUT"
}
# -----------------------------------------------------------------------------
# sb_fetch_and_verify <url> <expected-sha256> <dest-filename>
#
# Downloads a source tarball into $TMP and verifies it against the SHA256
# pinned in versions.env before anything is extracted or built. Aborts the
# build loudly on any mismatch — a checksum mismatch means either
# versions.env is stale (a legitimate new upstream release) or the source is
# not what it claims to be; either way, an unreviewed build must not proceed.
# -----------------------------------------------------------------------------
sb_fetch_and_verify() {
local url="$1"
local expected_sha256="$2"
local dest_name="$3"
local dest_path="$TMP/$dest_name"
echo "==> [$PRGNAM] Fetching $url"
curl -fL --retry 3 --retry-delay 2 -o "$dest_path" "$url"
local actual_sha256
actual_sha256=$(sha256sum "$dest_path" | awk '{print $1}')
if [ "$actual_sha256" != "$expected_sha256" ]; then
echo "!! [$PRGNAM] SHA256 MISMATCH for $dest_name" >&2
echo "!! expected: $expected_sha256" >&2
echo "!! actual: $actual_sha256" >&2
echo "!! Refusing to build against unverified source. If upstream" >&2
echo "!! genuinely released a new version, update versions.env via" >&2
echo "!! scripts/update-versions.sh instead of editing the hash by hand." >&2
exit 1
fi
echo "==> [$PRGNAM] SHA256 verified ($actual_sha256)"
echo "$dest_path"
}
# -----------------------------------------------------------------------------
# sb_extract <tarball-path> [strip-components]
#
# Extracts a verified tarball into $TMP/src, normalizing away the
# "<repo>-<version>/" wrapper directory GitHub/cgit archives always contain,
# so every SlackBuild can `cd "$TMP/src"` regardless of upstream's archive
# layout.
# -----------------------------------------------------------------------------
sb_extract() {
local tarball="$1"
local strip="${2:-1}"
mkdir -p "$TMP/src"
tar -xf "$tarball" -C "$TMP/src" --strip-components="$strip"
echo "$TMP/src"
}
# -----------------------------------------------------------------------------
# sb_install_docs <license-file> [readme-file]
#
# Installs upstream's license (and optionally README) into the standard
# Slackware package documentation location, plus this project's own build
# metadata, so `installpkg` output and /usr/doc/<pkg>-<version>/ are
# populated per Slackware convention.
# -----------------------------------------------------------------------------
sb_install_docs() {
local license_file="$1"
local readme_file="${2:-}"
local docdir="$PKG/usr/doc/$PRGNAM-$VERSION"
mkdir -p "$docdir"
[ -f "$license_file" ] && cp "$license_file" "$docdir/"
[ -n "$readme_file" ] && [ -f "$readme_file" ] && cp "$readme_file" "$docdir/"
{
echo "Built by unraid-podman from upstream source."
echo "Package: $PRGNAM $VERSION"
echo "Built: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
} > "$docdir/unraid-podman.build-info"
}
# -----------------------------------------------------------------------------
# sb_make_package
#
# Finalizes the Slackware package: installs slack-desc + doinst.sh (if
# present) into $PKG/install/, strips binaries, sets root:root ownership,
# runs makepkg to produce the .txz, then writes SHA256 + MD5 sidecar files
# next to it.
#
# - SHA256 is used by scripts/build-packages.sh / CI to verify build
# artifacts between jobs.
# - MD5 is additionally generated because Unraid's .plg <FILE> mechanism
# verifies downloads via MD5 by convention (see
# docs/ARCHITECTURE.md section 3.2) — scripts/release.sh reads the .md5
# file to populate plugin/podman.plg's entities.
#
# Produces: $OUTPUT/<name>-<version>-<arch>-<build><tag>.txz (+ .sha256, .md5)
# -----------------------------------------------------------------------------
sb_make_package() {
local version="$1"
local arch="$2"
local build="$3"
local tag="$4"
mkdir -p "$PKG/install"
if [ -f "$CWD/slack-desc" ]; then
cp "$CWD/slack-desc" "$PKG/install/slack-desc"
else
echo "!! [$PRGNAM] missing packages/$PRGNAM/slack-desc" >&2
exit 1
fi
[ -f "$CWD/doinst.sh" ] && cp "$CWD/doinst.sh" "$PKG/install/doinst.sh"
# Strip debug symbols where possible to keep package size down; ignore
# failures (e.g. static/stripped-already binaries, non-ELF files).
find "$PKG" -type f \( -perm -u+x -o -name '*.so*' \) -exec sh -c \
'file "$1" | grep -q ELF && strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \;
local pkg_file="$PRGNAM-$version-$arch-$build$tag.txz"
( cd "$PKG" && makepkg --linkadd y --chown y "$OUTPUT/$pkg_file" )
( cd "$OUTPUT" && sha256sum "$pkg_file" > "$pkg_file.sha256" )
( cd "$OUTPUT" && md5sum "$pkg_file" > "$pkg_file.md5" )
echo "==> [$PRGNAM] built $OUTPUT/$pkg_file"
}
+146
View File
@@ -0,0 +1,146 @@
#!/bin/bash
# =============================================================================
# scripts/release.sh
#
# Cuts a release of the plugin itself:
# 1. Bumps the &version; entity in plugin/podman.plg to <new-version>.
# 2. Builds all seven packages (scripts/build-packages.sh) unless
# SKIP_BUILD=1 is set (useful when CI already built them in a prior job
# and only wants this script to do the .plg/CHANGELOG bookkeeping).
# 3. Verifies + consolidates checksums (scripts/checksums.sh).
# 4. Rewrites the per-package <!ENTITY ..._txz_version/_txz_file/_txz_md5>
# entities in plugin/podman.plg from the freshly built dist/*.txz.md5
# sidecar files, and the &baseURL; entity to point at the GitHub
# Release that will hold these assets.
# 5. Moves CHANGELOG.md's [Unreleased] section under a new dated heading.
#
# This script deliberately does NOT `git commit`, `git tag`, or `gh release
# create` — it only prepares files. Committing/tagging/publishing is left to
# the caller (locally) or to .github/workflows/release.yml (in CI), so a
# human always reviews the diff before anything becomes public. See
# docs/ARCHITECTURE.md section 13 (Updates).
#
# Usage:
# scripts/release.sh <new-version> # e.g. scripts/release.sh 0.2.0
#
# Env vars:
# SKIP_BUILD=1 Skip scripts/build-packages.sh (assume dist/ already built)
# =============================================================================
set -eu
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PLG_FILE="$REPO_ROOT/plugin/podman.plg"
CHANGELOG_FILE="$REPO_ROOT/CHANGELOG.md"
DIST_DIR="$REPO_ROOT/dist"
NEW_VERSION="${1:-}"
if [ -z "$NEW_VERSION" ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
if ! [[ "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "!! <new-version> must be plain SemVer (X.Y.Z), got: $NEW_VERSION" >&2
exit 1
fi
# The GitHub Release tag/URL this release's assets will be published under.
# Must match whatever .github/workflows/release.yml actually creates the
# release as (tag "v<version>") — see that workflow for the release job.
RELEASE_TAG="v$NEW_VERSION"
REPO_SLUG="${GITHUB_REPOSITORY:-OWNER/unraid-podman}"
RELEASE_BASE_URL="https://github.com/$REPO_SLUG/releases/download/$RELEASE_TAG"
# Component name -> the entity name prefix used in podman.plg. Must match
# plugin/podman.plg's <!ENTITY NAME_txz_...> declarations exactly.
# unraid-podman is the plugin's own scaffolding package (see
# packages/unraid-podman/README.md), not an upstream component, but it's
# released and entity-updated exactly like the other seven.
COMPONENTS=(podman conmon crun netavark aardvark-dns passt fuse-overlayfs unraid-podman)
echo "==> Releasing unraid-podman plugin v$NEW_VERSION (packages tag: $RELEASE_TAG)"
# --- 1. Build ----------------------------------------------------------------
if [ "${SKIP_BUILD:-0}" != "1" ]; then
echo "==> Building all packages"
"$REPO_ROOT/scripts/build-packages.sh"
else
echo "==> SKIP_BUILD=1, assuming $DIST_DIR is already populated"
fi
"$REPO_ROOT/scripts/checksums.sh" "$DIST_DIR"
# --- 2. Update plugin version entity ------------------------------------------
echo "==> Setting <!ENTITY version> to $NEW_VERSION in $PLG_FILE"
sed -i -E "s|(<!ENTITY version[[:space:]]+\")[^\"]*(\">)|\1${NEW_VERSION}\2|" "$PLG_FILE"
sed -i -E "s|(<!ENTITY baseURL[[:space:]]+\")[^\"]*(\">)|\1${RELEASE_BASE_URL}\2|" "$PLG_FILE"
# --- 3. Update per-package entities from dist/*.txz.md5 -----------------------
for name in "${COMPONENTS[@]}"; do
# dist/ contains files like podman-6.0.1-x86_64-1_unraidpodman.txz — find
# the one for this component (there should be exactly one per release).
txz_path=$(find "$DIST_DIR" -maxdepth 1 -name "${name}-*-*-*.txz" | head -n1)
if [ -z "$txz_path" ]; then
echo "!! No built .txz found for component '$name' in $DIST_DIR" >&2
echo "!! Did scripts/build-packages.sh run successfully for it?" >&2
exit 1
fi
txz_file=$(basename "$txz_path")
md5_path="$txz_path.md5"
if [ ! -f "$md5_path" ]; then
echo "!! Missing $md5_path" >&2
exit 1
fi
md5=$(awk '{print $1}' "$md5_path")
# Component version is everything between "<name>-" and the next "-<arch>-"
# e.g. "podman-6.0.1-x86_64-1_unraidpodman.txz" -> "6.0.1"
txz_version=$(echo "$txz_file" | sed -E "s/^${name}-(.+)-[^-]+-[0-9]+[^-]*\.txz\$/\1/")
entity_prefix=$(echo "$name" | tr '-' '_')
echo "==> [$name] $txz_file (md5=$md5)"
sed -i -E "s|(<!ENTITY ${entity_prefix}_txz_version[[:space:]]+\")[^\"]*(\">)|\1${txz_version}\2|" "$PLG_FILE"
sed -i -E "s|(<!ENTITY ${entity_prefix}_txz_file[[:space:]]+\")[^\"]*(\">)|\1${txz_file}\2|" "$PLG_FILE"
sed -i -E "s|(<!ENTITY ${entity_prefix}_txz_md5[[:space:]]+\")[^\"]*(\">)|\1${md5}\2|" "$PLG_FILE"
done
# --- 4. Update CHANGELOG.md ---------------------------------------------------
# Pure awk (no python/perl dependency — this script also has to run inside
# the minimal Slackware build container, see .github/workflows/release.yml):
# inserts a fresh "## [<version>] - <date>" heading right after the existing
# "## [Unreleased]" marker, leaving [Unreleased] itself empty and at the top
# for the next round of changes.
echo "==> Moving CHANGELOG.md [Unreleased] section under [$NEW_VERSION]"
TODAY=$(date -u +%Y-%m-%d)
if ! grep -q '^## \[Unreleased\]$' "$CHANGELOG_FILE"; then
echo "!! No '## [Unreleased]' heading found in $CHANGELOG_FILE" >&2
exit 1
fi
awk -v ver="$NEW_VERSION" -v date="$TODAY" '
!done && $0 == "## [Unreleased]" {
print $0
print ""
print "## [" ver "] - " date
done = 1
next
}
{ print $0 }
' "$CHANGELOG_FILE" > "$CHANGELOG_FILE.tmp"
mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE"
echo
echo "==> Release preparation complete for v$NEW_VERSION."
echo "==> Review the diff, then:"
echo " git add plugin/podman.plg CHANGELOG.md"
echo " git commit -m \"release: v$NEW_VERSION\""
echo " git tag $RELEASE_TAG"
echo " git push && git push origin $RELEASE_TAG"
echo "==> Pushing the tag triggers .github/workflows/release.yml, which"
echo "==> rebuilds artifacts in CI (for reproducibility/provenance) and"
echo "==> publishes the GitHub Release with dist/*.txz + CHECKSUMS.* + the"
echo "==> updated podman.plg attached."
+151
View File
@@ -0,0 +1,151 @@
#!/bin/bash
# =============================================================================
# scripts/update-versions.sh
#
# Re-pins one (or all) upstream components in versions.env to their current
# latest release, recomputing the SHA256 checksum against the freshly
# downloaded source tarball. This is the ONLY supported way to change a
# version/checksum pair in versions.env — never hand-edit a checksum, since
# that defeats the entire point of pinning it (see the header comment in
# versions.env).
#
# Usage:
# scripts/update-versions.sh # check/update all components
# scripts/update-versions.sh podman crun # only these components
#
# This script only rewrites versions.env. It does not build anything, and it
# does not commit — review the diff (`git diff versions.env`) before
# committing, ideally by also running a build to confirm the new source
# still compiles (scripts/build-packages.sh <name>).
# =============================================================================
set -eu
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VERSIONS_FILE="$REPO_ROOT/versions.env"
# Maps our internal component name -> GitHub "owner/repo", for every
# component that actually has GitHub-tagged releases. passt is handled
# separately below (see versions.env for why).
declare -A GITHUB_REPO=(
[podman]="containers/podman"
[conmon]="containers/conmon"
[crun]="containers/crun"
[netavark]="containers/netavark"
[aardvark-dns]="containers/aardvark-dns"
[fuse-overlayfs]="containers/fuse-overlayfs"
)
# Maps our internal component name -> the *_VERSION variable prefix used in
# versions.env (uppercased, hyphens -> underscores).
env_prefix() {
echo "$1" | tr '[:lower:]-' '[:upper:]_'
}
update_github_component() {
local name="$1"
local repo="${GITHUB_REPO[$name]}"
local prefix
prefix=$(env_prefix "$name")
echo "==> [$name] checking latest release for $repo"
local api_response
api_response=$(curl -sL --max-time 15 "https://api.github.com/repos/$repo/releases/latest")
local tag
tag=$(echo "$api_response" | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/')
if [ -z "$tag" ]; then
echo "!! [$name] could not determine latest tag (rate-limited or repo has no releases?)" >&2
return 1
fi
# Strip a leading "v" for the version we store, but keep it for the URL
# since GitHub tags for these projects are inconsistent about it (crun
# tags plain "1.28", others tag "v1.28").
local version="${tag#v}"
local url="https://github.com/$repo/archive/refs/tags/$tag.tar.gz"
echo "==> [$name] latest = $version, downloading to verify + checksum"
local tmpfile
tmpfile=$(mktemp)
curl -fL --max-time 120 -o "$tmpfile" "$url"
local sha256
sha256=$(sha256sum "$tmpfile" | awk '{print $1}')
rm -f "$tmpfile"
echo "==> [$name] sha256=$sha256"
apply_update "$prefix" "$version" "$url" "$sha256"
}
apply_update() {
local prefix="$1" version="$2" url="$3" sha256="$4"
# In-place rewrite of the three lines for this component. Using distinct
# sed expressions per variable (rather than one blanket substitution)
# keeps this safe even if variable order in versions.env changes.
sed -i \
-e "s|^${prefix}_VERSION=.*|${prefix}_VERSION=\"${version}\"|" \
-e "s|^${prefix}_SRC_SHA256=.*|${prefix}_SRC_SHA256=\"${sha256}\"|" \
"$VERSIONS_FILE"
# The *_SRC_URL line is templated against *_VERSION (e.g.
# ".../v${PODMAN_VERSION}.tar.gz") in most cases, so it doesn't need
# rewriting — only touch it if it isn't already parameterized.
if ! grep -q "^${prefix}_SRC_URL=.*\${${prefix}_VERSION}" "$VERSIONS_FILE" \
&& ! grep -q "^${prefix}_SRC_URL=.*\$${prefix}_VERSION" "$VERSIONS_FILE"; then
sed -i -e "s|^${prefix}_SRC_URL=.*|${prefix}_SRC_URL=\"${url}\"|" "$VERSIONS_FILE"
fi
echo "==> updated ${prefix}_VERSION / ${prefix}_SRC_SHA256 in $VERSIONS_FILE"
}
update_passt() {
echo "==> [passt] checking latest master commit at https://passt.top/passt/"
local atom
atom=$(curl -sL --max-time 15 "https://passt.top/passt/atom/?h=master")
local commit
commit=$(echo "$atom" | grep -m1 -oE '<id>[a-f0-9]{40}</id>' | sed -E 's/<\/?id>//g')
if [ -z "$commit" ]; then
echo "!! [passt] could not determine latest commit" >&2
return 1
fi
local url="https://passt.top/passt/snapshot/passt-${commit}.tar.gz"
echo "==> [passt] latest commit = $commit, downloading to verify + checksum"
local tmpfile
tmpfile=$(mktemp)
curl -fL --max-time 120 -o "$tmpfile" "$url"
local sha256
sha256=$(sha256sum "$tmpfile" | awk '{print $1}')
rm -f "$tmpfile"
sed -i \
-e "s|^PASST_COMMIT=.*|PASST_COMMIT=\"${commit}\"|" \
-e "s|^PASST_VERSION=.*|PASST_VERSION=\"git${commit:0:7}\"|" \
-e "s|^PASST_SRC_SHA256=.*|PASST_SRC_SHA256=\"${sha256}\"|" \
"$VERSIONS_FILE"
echo "==> updated PASST_COMMIT / PASST_VERSION / PASST_SRC_SHA256 in $VERSIONS_FILE"
}
requested=("$@")
if [ "${#requested[@]}" -eq 0 ]; then
requested=("${!GITHUB_REPO[@]}" passt)
fi
for name in "${requested[@]}"; do
if [ "$name" = "passt" ]; then
update_passt
elif [ -n "${GITHUB_REPO[$name]:-}" ]; then
update_github_component "$name"
else
echo "!! Unknown component: $name" >&2
exit 1
fi
done
echo
echo "==> Done. Review the diff before committing:"
echo " git -C \"$REPO_ROOT\" diff versions.env"
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
#
# version-bump.sh — bumps the plugin version entity in plugin/podman.plg
# without performing a full release (useful for pre-release testing builds).
#
# Usage (planned): scripts/version-bump.sh <new-version>
#
# STATUS: placeholder skeleton, not yet functional.
set -eu
NEW_VERSION="${1:-}"
if [ -z "$NEW_VERSION" ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
# TODO: update <!ENTITY version "..."> in plugin/podman.plg
echo "TODO: implement version-bump.sh for version $NEW_VERSION"
exit 1