diff options
Diffstat (limited to '.extras/image-builder')
| -rw-r--r-- | .extras/image-builder/README | 69 | ||||
| -rwxr-xr-x | .extras/image-builder/bootstrap.sh | 552 | ||||
| -rwxr-xr-x | .extras/image-builder/build-full-image.sh | 233 | ||||
| -rwxr-xr-x | .extras/image-builder/build-sbo-testbuild.sh | 131 | ||||
| -rw-r--r-- | .extras/image-builder/config | 15 | ||||
| -rw-r--r-- | .extras/image-builder/lib.sh | 72 | ||||
| -rwxr-xr-x | .extras/image-builder/test-image-builder.sh | 68 |
7 files changed, 0 insertions, 1140 deletions
diff --git a/.extras/image-builder/README b/.extras/image-builder/README deleted file mode 100644 index fccab11..0000000 --- a/.extras/image-builder/README +++ /dev/null @@ -1,69 +0,0 @@ -sbo-testbuild image builder -=========================== - -Builds the docker images that .extras/test-build consumes: - docker.noland.dnx:5000/sbo-testbuild:current - docker.noland.dnx:5000/sbo-testbuild:15.0 - -Three scripts, chained (see .extras/docs/specs/2026-07-13-image-builder-design.md): - bootstrap.sh sbo-base:{ver} FROM scratch, base pkgs from NAS - build-full-image.sh sbo-full:{ver} FROM base, all series - build-sbo-testbuild.sh sbo-testbuild:{ver} FROM full, + sbopkg + tools - -All settings live in ./config. - -VM setup (docker.noland.dnx, Slackware x86_64, 4 vCPU / 4 GB / 80 GB) --------------------------------------------------------------------- -1. Install docker; enable the daemon. - -2. NFS-mount the two NAS trees read-only, named to match: - /mnt/nas/slackware64-current -> -current mirror tree - /mnt/nas/slackware64-15.0 -> 15.0 mirror tree - Each is a full mirror (PACKAGES.TXT, ChangeLog.txt, slackware64/, patches/, - extra/). Root must be able to read them (bootstrap runs installpkg as root). - -3. Run a LAN registry: - docker run -d --restart=always -p 5000:5000 \ - -v /opt/registry/data:/var/lib/registry --name registry registry:2 - -4. Mark the registry insecure (plain HTTP) on the VM AND every pulling client - (this dev box, the buildsystem VM). In /etc/docker/daemon.json: - { "insecure-registries": ["docker.noland.dnx:5000"] } - then restart docker. - -5. Drop the two prebuilt packages (built once, re-drop on upstream bumps): - /opt/sbo-testbuild/pkgs/sbopkg-*.txz - /opt/sbo-testbuild/pkgs/sbo-maintainer-tools-*.txz - -6. Install the nightly cron (root). The NAS repos sync at 01:00 and 02:00, so - the chain runs after and both variants are ready well before the ~09:00 work - start. No --force: each script self-gates (bootstrap on the ChangeLog hash, - full-image on the base-image digest, build-sbo-testbuild on the full-image - digest + tools .txz hash), so an unchanged night is a cheap no-op. -current - moves daily and rebuilds most nights; 15.0 is frozen stable and rebuilds only - on a real repo update. Deployed schedule on docker.noland.dnx: - # -current (ready ~04:35) - 0 3 * * * /path/to/.extras/image-builder/bootstrap.sh --version current >> /var/log/sbo-testbuild.log 2>&1 - 20 3 * * * /path/to/.extras/image-builder/build-full-image.sh --version current >> /var/log/sbo-testbuild.log 2>&1 - 30 4 * * * /path/to/.extras/image-builder/build-sbo-testbuild.sh --version current >> /var/log/sbo-testbuild.log 2>&1 - # 15.0 (ready ~06:35) - 0 5 * * * /path/to/.extras/image-builder/bootstrap.sh --version 15.0 >> /var/log/sbo-testbuild.log 2>&1 - 20 5 * * * /path/to/.extras/image-builder/build-full-image.sh --version 15.0 >> /var/log/sbo-testbuild.log 2>&1 - 30 6 * * * /path/to/.extras/image-builder/build-sbo-testbuild.sh --version 15.0 >> /var/log/sbo-testbuild.log 2>&1 - -7. Ensure docker.noland.dnx resolves on the LAN (static IP or DNS). - -Manual first run ----------------- - ./bootstrap.sh --version current --force - ./build-full-image.sh --version current --force - ./build-sbo-testbuild.sh --version current --force -Then confirm: - docker pull docker.noland.dnx:5000/sbo-testbuild:current - docker run --rm docker.noland.dnx:5000/sbo-testbuild:current sbopkg -V - -Flags: --force (rebuild unconditionally), --version <current|15.0> (one variant). - -Tests ------ - bash test-image-builder.sh # pure-logic self-check, no docker diff --git a/.extras/image-builder/bootstrap.sh b/.extras/image-builder/bootstrap.sh deleted file mode 100755 index 9893e66..0000000 --- a/.extras/image-builder/bootstrap.sh +++ /dev/null @@ -1,552 +0,0 @@ -#!/bin/bash -# bootstrap.sh — build the sbo-base:{ver} image FROM scratch from NAS trees. -# Run as root (installpkg). Adapted from forge slackware/docker-images. -set -euo pipefail -HERE="$(cd "$(dirname "$0")" && pwd)" -source "${HERE}/config" -LOG_TAG=bootstrap -source "${HERE}/lib.sh" - -REGISTRY_IMAGE="${REGISTRY}/sbo-base" -[[ "${EUID}" -eq 0 ]] || _err "run as root." -mkdir -p "${HASH_DIR}" - -# ============================================================================ -# Package list -# -# Names only - the version and build suffix are resolved automatically -# from PACKAGES.TXT for the x86_64 target architecture. -# -# slackpkg and its runtime dependencies (perl, gnupg2, gpgme) are included -# so users can run slackpkg inside the container without extra setup. -# ============================================================================ - -PACKAGES=( - # Base system - aaa_base - coreutils - aaa_glibc-solibs - aaa_libraries - aaa_terminfo - pam - cracklib - libpwquality - acl - attr - bash - bin - brotli - bzip2 - c-ares - cpio - curl - cyrus-sasl - devs - dialog - diffutils - duktape - elvis - etc - file - findutils - flex - gawk - gnutls - grep - groff - gzip - iproute2 - iptables - jansson - less - libcgroup - libpsl - librsvg - libtermcap - mtr - ncurses - net-tools - network-scripts - nghttp2 - nghttp3 - ngtcp2 - nvi - openssh - patch - pcre2 - pinentry - pkgtools - polkit - procps-ng - quota - rsync - screen - sed - elogind - shadow - sharutils - strace - sudo - sysfsutils - sysvinit - sysvinit-scripts - tar - time - tree - eudev - usbutils - utempter - util-linux - wget - which - whois - xz - zlib - # Package management - series ap - slackpkg - # Perl runtime required by slackpkg - series d - perl - # e2fsprogs provides libcom_err.so.2 required by perl on 15.0 - e2fsprogs - # libunistring - Unicode string library required by gnupg2 dependencies - libunistring - # gnupg2 runtime libraries (series l) - required for gpg to start at all - libgpg-error - libgcrypt - libassuan - libksba - npth - sqlite - icu4c - # GPG/PKCS support for slackpkg (series l) - gpgme - # GnuPG binaries (series n) - # gnupg = version 1, called by slackpkg on Slackware 15.0 - # gnupg2 = version 2, called by slackpkg on -current - gnupg - gnupg2 - # Full OpenSSL and CA bundle - required for HTTPS inside the container - openssl - ca-certificates -) - -# ============================================================================ -# Argument parsing -# ============================================================================ - -FORCE=false -OPT_VERSION="" -while [[ $# -gt 0 ]]; do - case "$1" in - --version) OPT_VERSION="$2"; shift 2 ;; - --force) FORCE=true; shift ;; - *) _err "unknown argument: $1" ;; - esac -done -if [[ -n "${OPT_VERSION}" ]]; then - BUILD_VARIANTS=("${OPT_VERSION}") -else - BUILD_VARIANTS=("${VARIANTS[@]}") -fi - -# ============================================================================ -# ChangeLog tracking -# ============================================================================ - -# changelog_changed VERSION -> 0 if changed (or --force), else 1 -changelog_changed() { - local version="$1" - require_mount "${version}" # loud fail if NAS unmounted, not silent skip - local repo_key="slackware64-${version}" - local hash_file="${HASH_DIR}/${repo_key}.sha256" - local url; url="$(mirror_path "${MIRROR}" "${repo_key}/ChangeLog.txt")" - local tmp; tmp="$(mktemp)" - if ! fetch "${url}" "${tmp}"; then - rm -f "${tmp}" - _warn "${repo_key}: cannot read ChangeLog (${url}); skipping." - return 1 - fi - local live; live=$(sha256sum "${tmp}" | cut -d' ' -f1); rm -f "${tmp}" - if [[ "${FORCE}" == "true" ]]; then - echo "${live}" > "${hash_file}"; return 0 - fi - local stored=""; [[ -f "${hash_file}" ]] && stored=$(cat "${hash_file}") - if [[ "${live}" == "${stored}" ]]; then - _log "${repo_key}: ChangeLog unchanged; skipping."; return 1 - fi - _log "${repo_key}: ChangeLog changed."; echo "${live}" > "${hash_file}"; return 0 -} - -# ============================================================================ -# Package discovery via PACKAGES.TXT -# -# Search order: patches/ -> main repo -> extra/ -# This mirrors Slackware's own install priority: a patched package in -# patches/ always takes precedence over the same package in the main tree. -# ============================================================================ - -# download_pkgtxt PKGTXT_DIR PKG_PATH -# Downloads PACKAGES.TXT from patches/, the repo root, and extra/ into -# PKGTXT_DIR. Sets global associative array PKGTXT[loc] -> local file path. -# Uses -L to follow HTTP redirects (some mirrors use them) and checks that -# the downloaded file is non-empty before marking it as available. -declare -gA PKGTXT=() - -download_pkgtxt() { - local PKGTXT_DIR="$1" - local PKG_PATH="$2" - mkdir -p "${PKGTXT_DIR}" - PKGTXT=() - local LOC URL DEST - for LOC in patches main extra; do - case "${LOC}" in - patches) URL="${PKG_PATH}/patches/PACKAGES.TXT" ;; - main) URL="${PKG_PATH}/PACKAGES.TXT" ;; - extra) URL="${PKG_PATH}/extra/PACKAGES.TXT" ;; - esac - DEST="${PKGTXT_DIR}/${LOC}.txt" - if fetch "${URL}" "${DEST}" && [[ -s "${DEST}" ]]; then - PKGTXT["${LOC}"]="${DEST}" - _log " PACKAGES.TXT [${LOC}]: $(wc -l < "${DEST}") lines" - else - rm -f "${DEST}" - _log " PACKAGES.TXT [${LOC}]: not available at ${URL}" - fi - done -} - -# find_package NAME -# Searches PKGTXT files in order: patches -> main -> extra. -# Prints the path relative to PKG_PATH, e.g.: -# slackware64/a/bash-5.2-x86_64-1.txz -# -# Base name extraction uses the Slackware convention: the filename has the -# form PKGBASE-VERSION-ARCH-BUILD.txz where VERSION, ARCH and BUILD are -# guaranteed to contain no dashes. Reversing, cutting the last three fields, -# and reversing again always yields the correct PKGBASE: -# echo aaa_glibc-solibs-2.42-x86_64-1.txz | rev | cut -d- -f4- | rev -# -> aaa_glibc-solibs -find_package() { - local PKG="$1" - [[ "${#PKGTXT[@]}" -gt 0 ]] || { - _warn "find_package called with empty PKGTXT array - PACKAGES.TXT not loaded" - return 1 - } - for LOC in patches main extra; do - [[ -v "PKGTXT[${LOC}]" ]] || continue - local RESULT - RESULT=$(awk -v want="${PKG}" ' - /^PACKAGE NAME:/ { - filename = $NF - # PKGBASE = all fields except the last three (VERSION ARCH BUILD) - n = split(filename, parts, "-") - base = parts[1] - for (i = 2; i <= n-3; i++) base = base "-" parts[i] - if (base == want) found = filename - } - /^PACKAGE LOCATION:/ && found { - loc = $NF; sub(/^\.\//, "", loc) - print loc "/" found - found = ""; exit - } - ' "${PKGTXT[${LOC}]}") - if [[ -n "${RESULT}" ]]; then - echo "${RESULT}" - return 0 - fi - done - return 1 -} - -# ============================================================================ -# Build one variant -# ============================================================================ - -build_variant() { - local VERSION="$1" - require_mount "${VERSION}" - local REPO_KEY="slackware64-${VERSION}" - local PKG_PATH; PKG_PATH="$(mirror_path "${MIRROR}" "${REPO_KEY}")" - local TAG="${REGISTRY_IMAGE}:${VERSION}" - # Bake the LAN HTTP mirror into the base image: the full-image build serves - # the NFS mirror over HTTP on the bridge (see build-full-image.sh), rooted - # at the variant's mirror tree, so slackpkg pulls from LAN not the internet. - # The URL is variant-agnostic (the server's --directory selects the tree). - local SLACKPKG_MIRROR="http://${HTTP_MIRROR_HOST}:${HTTP_MIRROR_PORT}/" - - _log "=== Building ${TAG} from ${PKG_PATH} ===" - - local WORKDIR - WORKDIR="$(mktemp -d /tmp/slackware-bootstrap.XXXXXX)" - # shellcheck disable=SC2064 - trap "rm -rf '${WORKDIR}'" EXIT - - local ROOTFS="${WORKDIR}/rootfs" - local PKGCACHE="${WORKDIR}/packages" - local PKGTXT_DIR="${WORKDIR}/pkgtxt" - mkdir -p "${ROOTFS}" "${PKGCACHE}" - - # --- Download PACKAGES.TXT index files --- - download_pkgtxt "${PKGTXT_DIR}" "${PKG_PATH}" - - # --- Download packages --- - _log "Locating and downloading packages..." - local PKG RELPATH FILENAME URL - for PKG in "${PACKAGES[@]}"; do - RELPATH=$(find_package "${PKG}") || { - _warn " '${PKG}' not found in any PACKAGES.TXT - skipping" - continue - } - FILENAME="${RELPATH##*/}" - URL="${PKG_PATH}/${RELPATH}" - _log " ${FILENAME}" - fetch "${URL}" "${PKGCACHE}/${FILENAME}" || { - _warn " Failed to download ${URL}" - } - done - - # --- Install packages --- - _log "Installing packages..." - for PKG in "${PKGCACHE}"/*.txz; do - [[ -f "${PKG}" ]] || continue - ROOT="${ROOTFS}" installpkg --terse "${PKG}" - done - - # --- Run ldconfig to create library symlinks --- - # Some packages (notably aaa_libraries on stable releases) install - # versioned library files (e.g. libtinfo.so.6.3) and rely on ldconfig - # to create the unversioned symlinks (e.g. libtinfo.so.6). On -current - # the symlinks are included in the package tarballs directly; on 15.0 - # they are not. Running ldconfig here ensures the symlinks exist before - # any chroot operations and before the dependency check below. - _log "Running ldconfig..." - chroot "${ROOTFS}" /sbin/ldconfig - - # --- Check for missing library dependencies --- - # Run ldd on key binaries inside the chroot and report anything missing - # before it surfaces as a runtime failure inside the container. - _log "Checking for missing library dependencies in key binaries..." - local DEPCHECK_OK=true - for BIN in /usr/bin/gpg /usr/bin/gpg2 /usr/sbin/slackpkg /usr/bin/perl /bin/wget /usr/bin/openssl; do - [[ -f "${ROOTFS}${BIN}" ]] || continue - local MISSING_LIBS - MISSING_LIBS=$(chroot "${ROOTFS}" ldd "${BIN}" 2>/dev/null | grep "not found" || true) - if [[ -n "${MISSING_LIBS}" ]]; then - _warn "${BIN} has unresolved dependencies:" - while IFS= read -r lib; do - _warn " ${lib}" - done <<< "${MISSING_LIBS}" - DEPCHECK_OK=false - fi - done - if [[ "${DEPCHECK_OK}" == "true" ]]; then - _log "Dependency check passed." - else - _warn "Add the missing packages to PACKAGES and rebuild with --force." - fi - - # --- Prepare rootfs for container use --- - _log "Configuring rootfs for container use..." - - # Remove the empty update.d hook directory so that update-ca-certificates - # does not look for run-parts (which ships with dcron, not installed here). - rm -rf "${ROOTFS}/etc/ca-certificates/update.d" - - # Disable init scripts that manage hardware absent in a container - local SVC - for SVC in rc.acpid rc.pcmcia rc.setterm rc.udev; do - [[ -f "${ROOTFS}/etc/rc.d/${SVC}" ]] && chmod -x "${ROOTFS}/etc/rc.d/${SVC}" - done - - # Each container generates its own SSH host keys on first start - rm -f "${ROOTFS}"/etc/ssh/*key* - - # /etc/mtab should reflect the container's actual mounts - rm -f "${ROOTFS}/etc/mtab" - ln -s /proc/mounts "${ROOTFS}/etc/mtab" - - # No hardware clock in a container - sed -i -e '/^if \[ -x \/sbin\/hwclock/,/^fi$/s/^/#/' \ - "${ROOTFS}/etc/rc.d/rc.S" 2>/dev/null || true - - # The filesystem write-check would drop the container into a recovery shell - sed -i -e '/^if touch \/fsrwtestfile/,/^fi$/s/^/#/' \ - "${ROOTFS}/etc/rc.d/rc.S" 2>/dev/null || true - - # Skip all filesystem checks at boot - touch "${ROOTFS}/etc/fastboot" - - # setterm is not useful in a container - sed -i -e '/\/bin\/setterm/s/^/# /' \ - "${ROOTFS}/etc/rc.d/rc.M" 2>/dev/null || true - - # Cannot write to the hardware clock at shutdown - sed -i -e '/systohc/s/^/# /' \ - "${ROOTFS}/etc/rc.d/rc.6" 2>/dev/null || true - - # Container-appropriate fstab - cat > "${ROOTFS}/etc/fstab" <<'FSTAB' -devtmpfs /dev devtmpfs defaults 0 0 -devpts /dev/pts devpts gid=5,mode=620 0 0 -tmpfs /dev/shm tmpfs defaults,nodev,nosuid,mode=1777 0 0 -FSTAB - - # No TTYs in a container; disable agetty and reduce console definitions - sed -i -e '/agetty/s/^c/#c/' \ - "${ROOTFS}/etc/inittab" 2>/dev/null || true - sed -i -e '/^c3\|^c4\|^c5\|^c6/s/^/# /' \ - "${ROOTFS}/etc/inittab" 2>/dev/null || true - - # Invalidate the root password (containers use other means of access) - sed -i -e '/^root/s/^root::/root:!:/' \ - "${ROOTFS}/etc/shadow" 2>/dev/null || true - - # Terminal configuration - printf 'export TERM=linux\n' > "${ROOTFS}/etc/profile.d/term.sh" - chmod +x "${ROOTFS}/etc/profile.d/term.sh" - - # Source /etc/profile from .bashrc so interactive sessions are fully - # initialised (Docker sets / as the home directory for root in containers) - printf '. /etc/profile\n' > "${ROOTFS}/.bashrc" - - # DNS resolver - printf 'nameserver 1.1.1.1\nnameserver 8.8.4.4\n' \ - >> "${ROOTFS}/etc/resolv.conf" - - # Configure slackpkg for silent non-interactive use in a container - if [[ -f "${ROOTFS}/etc/slackpkg/slackpkg.conf" ]]; then - sed -i 's/DIALOG=on/DIALOG=off/' "${ROOTFS}/etc/slackpkg/slackpkg.conf" - sed -i 's/POSTINST=on/POSTINST=off/' "${ROOTFS}/etc/slackpkg/slackpkg.conf" - sed -i 's/SPINNING=on/SPINNING=off/' "${ROOTFS}/etc/slackpkg/slackpkg.conf" - sed -i 's/WGETFLAGS="--passive-ftp/& --no-verbose/' \ - "${ROOTFS}/etc/slackpkg/slackpkg.conf" - fi - - # Configure the slackpkg mirror - [[ -f "${ROOTFS}/etc/slackpkg/mirrors" ]] && \ - printf '%s\n' "${SLACKPKG_MIRROR}" >> "${ROOTFS}/etc/slackpkg/mirrors" - - # slackpkg requires an explicit opt-in for -current - if [[ "${VERSION}" == "current" ]]; then - mkdir -p "${ROOTFS}/var/lib/slackpkg" - touch "${ROOTFS}/var/lib/slackpkg/current" - fi - - # Set a predictable locale for all chroot operations. - # Without this, perl emits locale warnings when the host locale - # (e.g. en_US.UTF-8) is not installed in the minimal rootfs. - export LC_ALL=C - - # Rebuild the CA certificate database inside the rootfs. - # Without this, wget cannot verify Let's Encrypt certificates and every - # HTTPS download by slackpkg fails with an SSL error. That SSL error in - # turn causes the interactive "do you want to import the GPG key?" prompt - # even when -batch=on -default_answer=y is set. - _log "Refreshing CA certificate database..." - chroot "${ROOTFS}" /usr/sbin/update-ca-certificates --fresh 1>/dev/null || _warn "CA certificate update failed; SSL verification may not work correctly." - - # Import the Slackware GPG key and update the package list. - # 'yes YES |' feeds a pre-emptive answer to the interactive import prompt - # as a safety net; with the CA database now valid the prompt should not - # appear because the download succeeds on the first attempt. - _log "Importing Slackware GPG key via slackpkg..." - # Check slackpkg's own exit status, not the pipeline's: 'yes' takes SIGPIPE - # when slackpkg exits first, which can make the pipeline non-zero even on - # success (false "GPG update failed" warning). Wrapping in 'if' exempts the - # pipeline from set -e/pipefail so we can read PIPESTATUS[1] (slackpkg's own - # status) instead of the SIGPIPE-poisoned pipeline status. - local gpg_rc - if yes YES | chroot "${ROOTFS}" /usr/sbin/slackpkg -batch=on -default_answer=y update gpg - then gpg_rc="${PIPESTATUS[1]}"; else gpg_rc="${PIPESTATUS[1]}"; fi - [[ "${gpg_rc}" -eq 0 ]] || _warn "slackpkg GPG update failed; check the output above." - - _log "Updating slackpkg package list..." - chroot "${ROOTFS}" /usr/sbin/slackpkg -batch=on -default_answer=y update || _warn "slackpkg update failed; cache will build on first container run." - - # --- Clean up --- - _log "Cleaning up rootfs..." - rm -f "${ROOTFS}"/boot/* - rm -f "${ROOTFS}"/tmp/[A-Za-z]* - rm -f "${ROOTFS}"/var/mail/* - rm -rf "${ROOTFS}"/dev/* - rm -rf "${ROOTFS}"/usr/share/locale/* - rm -rf "${ROOTFS}"/usr/info/* - rm -rf "${ROOTFS}"/usr/man/* - (cd "${ROOTFS}/usr/doc" 2>/dev/null && \ - find . -type d -mindepth 2 -maxdepth 2 | grep -v '/cups-' | \ - xargs rm -rf) || true - rm -rf "${ROOTFS}"/usr/doc/*/html - rm -f "${ROOTFS}"/usr/doc/*/*.{pdf,db,gz,bz2,xz,txt,TXT} 2>/dev/null || true - rm -rf "${ROOTFS}"/usr/share/gtk-doc - rm -rf "${ROOTFS}"/usr/share/help - find "${ROOTFS}"/usr/share/ -type d -name doc | xargs rm -rf - # Keep only the terminfo entries the container actually needs - find "${ROOTFS}"/usr/share/terminfo/ -type f \ - ! -name 'linux' ! -name 'xterm' ! -name 'screen.linux' \ - -delete 2>/dev/null || true - find "${ROOTFS}"/usr/share/terminfo/ -xtype l -delete 2>/dev/null || true - # gpg-agent socket files left behind by slackpkg in the chroot. - # tar cannot archive Unix domain sockets; removing them eliminates - # the "socket ignored" warnings when the image tarball is created. - rm -f "${ROOTFS}"/root/.gnupg/S.* - - # Kernel-specific content has no meaning in a container - rm -rf "${ROOTFS}"/usr/src - rm -rf "${ROOTFS}"/lib/modules - rm -rf "${ROOTFS}"/lib64/modules 2>/dev/null || true - - # --- Build Docker image --- - _log "Creating image tarball..." - tar -C "${ROOTFS}" -czf "${WORKDIR}/rootfs.tar.gz" . - - local BUILD_DATE - BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - - cat > "${WORKDIR}/Dockerfile" <<DOCKERFILE -FROM scratch -LABEL maintainer="Eric Hameleers <alien@slackware.com>" -ADD rootfs.tar.gz / -ENV container=docker -ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -LABEL org.opencontainers.image.created="${BUILD_DATE}" -LABEL org.opencontainers.image.title="Slackware Linux ${VERSION} (x86_64)" -LABEL org.opencontainers.image.description="Slackware Linux ${VERSION} base container image (x86_64)" -LABEL org.opencontainers.image.vendor="Slackware Linux" -LABEL org.opencontainers.image.licenses="GPL-2.0-or-later AND LGPL-2.1-or-later" -LABEL org.opencontainers.image.url="http://www.slackware.com" -LABEL org.opencontainers.image.source="https://forge.slackware.nl/slackware/docker-images" -LABEL org.opencontainers.image.revision="${VERSION}" -LABEL slackware.version="${VERSION}" -LABEL slackware.arch="x86_64" -CMD ["/bin/bash"] -DOCKERFILE - - # Explicit exit checks: build_variant runs in an `if ! (...)` condition - # (see Main), which suppresses `set -e` in this subshell, so a failed build - # would otherwise push a nonexistent tag and log "Done". - _log "Building and pushing ${TAG}..." - if ! docker build --no-cache -t "${TAG}" "${WORKDIR}"; then - _warn "build failed for ${TAG}; not pushing." - return 1 - fi - if ! docker push "${TAG}"; then - _warn "push failed for ${TAG}." - return 1 - fi - _log "=== Done: ${TAG} ===" -} - -# ============================================================================ -# Main -# ============================================================================ - -rc=0 -for VERSION in "${BUILD_VARIANTS[@]}"; do - if changelog_changed "${VERSION}"; then - if ! ( build_variant "${VERSION}" ); then - _warn "variant ${VERSION} failed; continuing." - rc=1 - fi - fi -done -exit "${rc}" diff --git a/.extras/image-builder/build-full-image.sh b/.extras/image-builder/build-full-image.sh deleted file mode 100755 index 24cbe1d..0000000 --- a/.extras/image-builder/build-full-image.sh +++ /dev/null @@ -1,233 +0,0 @@ -#!/bin/bash -# build-full-image.sh — sbo-full:{ver} FROM sbo-base:{ver}, all series. -# Adapted from forge slackware/docker-images. No root needed. -set -euo pipefail -HERE="$(cd "$(dirname "$0")" && pwd)" -source "${HERE}/config" -LOG_TAG=full -source "${HERE}/lib.sh" - -BASE_IMAGE="${REGISTRY}/sbo-base" -FULL_IMAGE="${REGISTRY}/sbo-full" -DIGEST_LABEL="sbo.full.base-digest" - -# ============================================================================ -# Argument parsing -# ============================================================================ - -FORCE=false -OPT_VERSION="" -while [[ $# -gt 0 ]]; do - case "$1" in - --version) OPT_VERSION="$2"; shift 2 ;; - --force) FORCE=true; shift ;; - *) _err "unknown argument: $1" ;; - esac -done -if [[ -n "${OPT_VERSION}" ]]; then - BUILD_VARIANTS=("${OPT_VERSION}") -else - BUILD_VARIANTS=("${VARIANTS[@]}") -fi - -# ============================================================================ -# Tag derivation -# ============================================================================ - -# derive_tags VERSION -# Sets globals BASE_TAG and FULL_TAG for the given variant. -derive_tags() { - local VERSION="$1" - BASE_TAG="${BASE_IMAGE}:${VERSION}" - FULL_TAG="${FULL_IMAGE}:${VERSION}" -} - -# ============================================================================ -# Rebuild detection -# -# After pulling the latest base image, its digest is compared with the -# digest stored in the DIGEST_LABEL on the current full image. If -# they differ the base image has been updated and the full must be -# rebuilt to incorporate slackpkg package updates shipped with the new base. -# ============================================================================ - -needs_rebuild() { - local BASE_TAG="$1" FULL_TAG="$2" - - if [[ "${FORCE}" == "true" ]]; then - _log " --force specified; rebuilding unconditionally." - return 0 - fi - - # Digest of the base image we just pulled - local BASE_DIGEST - BASE_DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' \ - "${BASE_TAG}" 2>/dev/null || echo "") - if [[ -z "${BASE_DIGEST}" ]]; then - _warn " Could not determine base image digest; rebuilding to be safe." - return 0 - fi - - # Digest recorded in the full image's label (empty if image doesn't exist) - local RECORDED_DIGEST - RECORDED_DIGEST=$(docker inspect \ - --format "{{index .Config.Labels \"${DIGEST_LABEL}\"}}" \ - "${FULL_TAG}" 2>/dev/null || echo "") - - if [[ "${BASE_DIGEST}" == "${RECORDED_DIGEST}" ]]; then - _log " Base image unchanged (${BASE_DIGEST:0:40}...); skipping." - return 1 - fi - - _log " Base image changed; rebuilding." - _log " Was : ${RECORDED_DIGEST:0:60}..." - _log " Now : ${BASE_DIGEST:0:60}..." - return 0 -} - -# ============================================================================ -# Build one variant -# ============================================================================ - -build_variant() { - local VERSION="$1" - local REPO_KEY="slackware64-${VERSION}" - require_mount "${VERSION}" # NFS must be up: we serve it over HTTP below - derive_tags "${VERSION}" - - _log "=== ${FULL_TAG} ===" - - # Pull the latest base image so digest comparison is against the current - # state of the registry, not a stale local cache. - _log " Pulling base image ${BASE_TAG}..." - docker pull "${BASE_TAG}" - - if ! needs_rebuild "${BASE_TAG}" "${FULL_TAG}"; then - return 0 - fi - - # Record the base image digest for the label - local BASE_DIGEST - BASE_DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' "${BASE_TAG}") - - local WORKDIR - WORKDIR="$(mktemp -d /tmp/slackware-full.XXXXXX)" - - # Serve the NFS mirror over HTTP on the docker bridge for the duration of - # the build, so slackpkg in the build container fetches packages from LAN - # instead of the internet. Killed (with WORKDIR cleanup) on any exit. - local MIRROR_ROOT; MIRROR_ROOT="$(strip_scheme "$(mirror_path "${MIRROR}" "${REPO_KEY}")")" - [[ -f "${MIRROR_ROOT}/PACKAGES.TXT" ]] \ - || _err "mirror root has no PACKAGES.TXT: ${MIRROR_ROOT}" - python3 -m http.server "${HTTP_MIRROR_PORT}" \ - --bind "${HTTP_MIRROR_HOST}" --directory "${MIRROR_ROOT}" \ - >/dev/null 2>&1 & - local HTTP_PID=$! - # shellcheck disable=SC2064 - trap "kill ${HTTP_PID} 2>/dev/null; rm -rf '${WORKDIR}'" EXIT - # Wait for the server to accept connections before building. - local i - for i in 1 2 3 4 5 6 7 8 9 10; do - curl -sf -o /dev/null "http://${HTTP_MIRROR_HOST}:${HTTP_MIRROR_PORT}/PACKAGES.TXT" && break - [[ $i -eq 10 ]] && _err "HTTP mirror did not come up on ${HTTP_MIRROR_HOST}:${HTTP_MIRROR_PORT}" - sleep 0.5 - done - - # Generate the Dockerfile for this variant - cat > "${WORKDIR}/Dockerfile" <<DOCKERFILE -FROM ${BASE_TAG} -LABEL maintainer="Eric Hameleers <alien@slackware.com>" - -# Import the Slackware GPG key against THIS mirror first: without it slackpkg -# cannot verify CHECKSUMS.md5 and silently installs almost nothing (only the -# packages already in base). 'yes YES |' answers the import prompt. -RUN yes YES | LC_ALL=C slackpkg -batch=on -default_answer=y update gpg - -# Update the slackpkg package list before installing so that we always -# get the versions current at build time, not the versions cached in the -# base image. -RUN LC_ALL=C slackpkg -batch=on -default_answer=y update - -# Download and install each individual package (thanks aclemons): -RUN sed -i 's/DOWNLOAD_ALL=on/DOWNLOAD_ALL=off/' /etc/slackpkg/slackpkg.conf - -# We don't care about a big firmware package: -RUN mv /etc/slackpkg/blacklist{,.keep} && \\ - echo kernel-firmware > /etc/slackpkg/blacklist - -# Install the full Slackware: -# All packages are installed in a single RUN layer to keep the image lean. -RUN export LC_ALL=C && \\ - for series in a ap d e f k kde l n t tcl x xap xfce y ; do \\ - slackpkg -delall=on -batch=on -default_answer=y install "\$series"/* ; done - -# And back to defaults: -RUN sed -i 's/DOWNLOAD_ALL=off/DOWNLOAD_ALL=on/' /etc/slackpkg/slackpkg.conf -RUN mv /etc/slackpkg/blacklist{.keep,} - - -# Delete package cache: -RUN rm -rf /var/cache/packages/* - -# Rebuild the linker cache after adding new libraries -RUN /sbin/ldconfig - -LABEL org.opencontainers.image.created="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -LABEL org.opencontainers.image.title="Slackware Linux ${VERSION} full (x86_64)" -LABEL org.opencontainers.image.description="Slackware Linux ${VERSION} full image with all packages installed (x86_64)" -LABEL org.opencontainers.image.vendor="Slackware Linux" -LABEL org.opencontainers.image.licenses="GPL-2.0-or-later AND LGPL-2.1-or-later" -LABEL org.opencontainers.image.url="http://www.slackware.com" -LABEL org.opencontainers.image.source="https://forge.slackware.nl/slackware/docker-images" -LABEL org.opencontainers.image.revision="${VERSION}" -LABEL slackware.version="${VERSION}" -LABEL slackware.arch="x86_64" -LABEL ${DIGEST_LABEL}="${BASE_DIGEST}" -CMD ["/bin/bash"] -DOCKERFILE - - local BUILD_FLAGS=() - [[ "${FORCE}" == "true" ]] && BUILD_FLAGS+=(--no-cache) - - # Explicit exit checks: build_variant runs in an `if ! (...)` condition - # (see Main), which suppresses `set -e` inside this subshell, so a failed - # docker build would otherwise fall through to push a nonexistent tag and - # log "Done". Check each step and return non-zero on failure. - _log " Building ${FULL_TAG}..." - if ! docker build "${BUILD_FLAGS[@]}" \ - -t "${FULL_TAG}" \ - "${WORKDIR}"; then - _warn " build failed for ${FULL_TAG}; not pushing." - return 1 - fi - - _log " Pushing ${FULL_TAG}..." - if ! docker push "${FULL_TAG}"; then - _warn " push failed for ${FULL_TAG}." - return 1 - fi - - _log "=== Done: ${FULL_TAG} ===" -} - -# ============================================================================ -# Main -# ============================================================================ - -# A --force build passes --no-cache, so the stale build cache is dead weight: -# it still loads the containerd snapshotter's lease/lock bookkeeping and has -# raced the layer export ("failed to open writer: ref ... locked ... unavailable"). -# Prune it first to shrink that surface. Best-effort; never fail the build on it. -if [[ "${FORCE}" == "true" ]]; then - _log " --force: pruning build cache before rebuild" - docker builder prune -f >/dev/null 2>&1 || _warn " builder prune failed (ignored)" -fi - -rc=0 -for VERSION in "${BUILD_VARIANTS[@]}"; do - if ! ( build_variant "${VERSION}" ); then - _warn "variant ${VERSION} failed; continuing." - rc=1 - fi -done -exit "${rc}" diff --git a/.extras/image-builder/build-sbo-testbuild.sh b/.extras/image-builder/build-sbo-testbuild.sh deleted file mode 100755 index d04c426..0000000 --- a/.extras/image-builder/build-sbo-testbuild.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/bash -# build-sbo-testbuild.sh — sbo-testbuild:{ver} FROM sbo-full:{ver}. -# Adds sbopkg + sbo-maintainer-tools from prebuilt .txz in PKGDIR. -# Rebuilds when the full image OR the .txz set changes. -set -euo pipefail -HERE="$(cd "$(dirname "$0")" && pwd)" -source "${HERE}/config" -LOG_TAG=testbuild -source "${HERE}/lib.sh" - -FULL_IMAGE="${REGISTRY}/sbo-full" -TB_IMAGE="${REGISTRY}/sbo-testbuild" -DIGEST_LABEL="sbo.testbuild.full-digest" -PKGS_LABEL="sbo.testbuild.pkgs-hash" - -FORCE=false -OPT_VERSION="" -while [[ $# -gt 0 ]]; do - case "$1" in - --version) OPT_VERSION="$2"; shift 2 ;; - --force) FORCE=true; shift ;; - *) _err "unknown argument: $1" ;; - esac -done -if [[ -n "${OPT_VERSION}" ]]; then - BUILD_VARIANTS=("${OPT_VERSION}") -else - BUILD_VARIANTS=("${VARIANTS[@]}") -fi - -# Verify the required packages (*.t?z) are present before doing anything. -require_pkgs() { - [[ -d "${PKGDIR}" ]] || _err "PKGDIR missing: ${PKGDIR}" - compgen -G "${PKGDIR}/sbopkg-*.t?z" >/dev/null \ - || _err "no sbopkg-*.t?z in ${PKGDIR}" - compgen -G "${PKGDIR}/sbo-maintainer-tools-*.t?z" >/dev/null \ - || _err "no sbo-maintainer-tools-*.t?z in ${PKGDIR}" -} - -# needs_rebuild FULL_TAG TB_TAG PKGS_HASH -> 0 if rebuild needed -needs_rebuild() { - local full_tag="$1" tb_tag="$2" pkgs_hash="$3" - [[ "${FORCE}" == "true" ]] && { _log " --force."; return 0; } - - local full_digest - full_digest=$(docker inspect --format '{{index .RepoDigests 0}}' \ - "${full_tag}" 2>/dev/null || echo "") - [[ -n "${full_digest}" ]] || { _warn " no full digest; rebuilding."; return 0; } - - local rec_digest rec_pkgs - rec_digest=$(docker inspect \ - --format "{{index .Config.Labels \"${DIGEST_LABEL}\"}}" \ - "${tb_tag}" 2>/dev/null || echo "") - rec_pkgs=$(docker inspect \ - --format "{{index .Config.Labels \"${PKGS_LABEL}\"}}" \ - "${tb_tag}" 2>/dev/null || echo "") - - if [[ "${full_digest}" == "${rec_digest}" && "${pkgs_hash}" == "${rec_pkgs}" ]]; then - _log " full image and .txz set unchanged; skipping." - return 1 - fi - _log " full image or .txz set changed; rebuilding." - return 0 -} - -build_variant() { - local VERSION="$1" - local FULL_TAG="${FULL_IMAGE}:${VERSION}" - local TB_TAG="${TB_IMAGE}:${VERSION}" - _log "=== ${TB_TAG} ===" - - _log " Pulling ${FULL_TAG}..." - docker pull "${FULL_TAG}" - - local PKGS_HASH; PKGS_HASH="$(txz_hash "${PKGDIR}")" - - if ! needs_rebuild "${FULL_TAG}" "${TB_TAG}" "${PKGS_HASH}"; then - return 0 - fi - - local FULL_DIGEST - FULL_DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' "${FULL_TAG}" 2>/dev/null || echo "") - - local WORKDIR; WORKDIR="$(mktemp -d /tmp/sbo-testbuild.XXXXXX)" - # shellcheck disable=SC2064 - trap "rm -rf '${WORKDIR}'" EXIT - mkdir -p "${WORKDIR}/pkgs" - cp "${PKGDIR}"/sbopkg-*.t?z "${PKGDIR}"/sbo-maintainer-tools-*.t?z "${WORKDIR}/pkgs/" - - cat > "${WORKDIR}/Dockerfile" <<DOCKERFILE -FROM ${FULL_TAG} -LABEL maintainer="danix <danix@danix.xyz>" -COPY pkgs/ /tmp/pkgs/ -RUN installpkg /tmp/pkgs/* && rm -rf /tmp/pkgs -RUN /sbin/ldconfig -LABEL org.opencontainers.image.created="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -LABEL org.opencontainers.image.title="SBo test-build env ${VERSION} (x86_64)" -LABEL org.opencontainers.image.description="Slackware ${VERSION} full + sbopkg + sbo-maintainer-tools" -LABEL slackware.version="${VERSION}" -LABEL ${DIGEST_LABEL}="${FULL_DIGEST}" -LABEL ${PKGS_LABEL}="${PKGS_HASH}" -CMD ["/bin/bash"] -DOCKERFILE - - local BUILD_FLAGS=() - [[ "${FORCE}" == "true" ]] && BUILD_FLAGS+=(--no-cache) - # Explicit exit checks: build_variant runs in an `if ! (...)` condition - # (see Main), which suppresses `set -e` in this subshell, so a failed build - # would otherwise push a nonexistent tag and log "Done". - _log " Building ${TB_TAG}..." - if ! docker build "${BUILD_FLAGS[@]}" -t "${TB_TAG}" "${WORKDIR}"; then - _warn " build failed for ${TB_TAG}; not pushing." - return 1 - fi - _log " Pushing ${TB_TAG}..." - if ! docker push "${TB_TAG}"; then - _warn " push failed for ${TB_TAG}." - return 1 - fi - _log "=== Done: ${TB_TAG} ===" -} - -require_pkgs -rc=0 -for VERSION in "${BUILD_VARIANTS[@]}"; do - if ! ( build_variant "${VERSION}" ); then - _warn "variant ${VERSION} failed; continuing." - rc=1 - fi -done -exit "${rc}" diff --git a/.extras/image-builder/config b/.extras/image-builder/config deleted file mode 100644 index 8b52313..0000000 --- a/.extras/image-builder/config +++ /dev/null @@ -1,15 +0,0 @@ -# Shared configuration for the sbo-testbuild image builder. -# Sourced by bootstrap.sh, build-full-image.sh, build-sbo-testbuild.sh, tests. - -REGISTRY="docker.noland.dnx:5000" -MIRROR="file:///mnt/nas" -VARIANTS=(current 15.0) # x86_64 only for now -PKGDIR="/opt/sbo-testbuild/pkgs" # sbopkg + sbo-maintainer-tools .txz -HASH_DIR="/var/cache/sbo-testbuild" # ChangeLog hashes for rebuild gating - -# During the full-image build, packages are served to the build container off -# the local NFS mirror (MIRROR) over HTTP on the docker bridge, so slackpkg -# fetches from LAN instead of the internet. The bridge-gateway IP is what the -# build container sees as the host; the port is ephemeral to this host. -HTTP_MIRROR_HOST="172.17.0.1" # docker0 bridge gateway (host from container) -HTTP_MIRROR_PORT="8099" diff --git a/.extras/image-builder/lib.sh b/.extras/image-builder/lib.sh deleted file mode 100644 index da12fd0..0000000 --- a/.extras/image-builder/lib.sh +++ /dev/null @@ -1,72 +0,0 @@ -# Shared helpers for the image builder. Source, do not execute. -# Sourcing must have no side effects beyond defining functions. - -_log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${LOG_TAG:-image-builder}] $*"; } -_warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${LOG_TAG:-image-builder}] WARNING: $*" >&2; } -_err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${LOG_TAG:-image-builder}] ERROR: $*" >&2; exit 1; } - -# mirror_path MIRROR RELPATH -# Join a MIRROR (file:///... or http(s)://...) with a relative path. -mirror_path() { - local mirror="$1" rel="$2" - printf '%s/%s' "${mirror%/}" "${rel#/}" -} - -# is_local_mirror MIRROR -> 0 if file:// or bare absolute path, else 1 -is_local_mirror() { - case "$1" in - file://*) return 0 ;; - http://*|https://*) return 1 ;; - /*) return 0 ;; - *) return 1 ;; - esac -} - -# strip_scheme URL -> filesystem path for a file:// or bare-local URL -strip_scheme() { - local u="$1" - case "$u" in - file://*) printf '%s' "${u#file://}" ;; - *) printf '%s' "$u" ;; - esac -} - -# fetch SRC DEST -# Copy SRC (a full mirror URL) to DEST. Local mirror -> cp, http -> curl. -# Returns non-zero on failure; caller decides whether that is fatal. -fetch() { - local src="$1" dest="$2" - if is_local_mirror "$src"; then - local path; path="$(strip_scheme "$src")" - [[ -f "$path" ]] || return 1 - cp "$path" "$dest" - else - curl -sSfL --connect-timeout 60 "$src" -o "$dest" - fi -} - -# txz_hash DIR -# Stable hash of the set of Slackware package files (*.t?z) in DIR (names + -# contents), order -# independent. Empty/absent dir -> empty string. -txz_hash() { - local dir="$1" - [[ -d "$dir" ]] || { printf ''; return 0; } - local files; files=$(find "$dir" -maxdepth 1 -name '*.t?z' -type f | sort) - [[ -n "$files" ]] || { printf ''; return 0; } - # Hash sorted "name sha256" lines so reordering does not change output. - while IFS= read -r f; do - printf '%s %s\n' "$(basename "$f")" "$(sha256sum "$f" | cut -d' ' -f1)" - done <<< "$files" | sha256sum | cut -d' ' -f1 -} - -# require_mount VERSION -# Assert the NAS tree for VERSION is mounted (file:// mirror only). -# x86_64 tree dir is slackware64-${VERSION}. -require_mount() { - local version="$1" - is_local_mirror "$MIRROR" || return 0 # http mirror: nothing to check - local base; base="$(strip_scheme "$MIRROR")" - local dir="${base}/slackware64-${version}" - [[ -d "$dir" ]] || _err "NAS tree not mounted: ${dir}" -} diff --git a/.extras/image-builder/test-image-builder.sh b/.extras/image-builder/test-image-builder.sh deleted file mode 100755 index 39d3f1c..0000000 --- a/.extras/image-builder/test-image-builder.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash -# Assert-based self-check for lib.sh pure logic. No docker, no network. -# Run: bash .extras/image-builder/test-image-builder.sh -set -u -cd "$(dirname "$0")" -source ./lib.sh - -pass=0 fail=0 -check() { # DESC EXPECTED ACTUAL - if [[ "$2" == "$3" ]]; then pass=$((pass+1)); - else fail=$((fail+1)); echo "FAIL: $1"; echo " expected: [$2]"; echo " actual: [$3]"; fi -} -check_rc() { # DESC EXPECTED_RC ACTUAL_RC - if [[ "$2" == "$3" ]]; then pass=$((pass+1)); - else fail=$((fail+1)); echo "FAIL: $1 (rc expected $2 got $3)"; fi -} - -tmp="$(mktemp -d)" -trap 'rm -rf "$tmp"' EXIT - -# --- mirror_path --- -check "mirror_path trailing slash" \ - "file:///mnt/nas/slackware64-current/PACKAGES.TXT" \ - "$(mirror_path 'file:///mnt/nas/' 'slackware64-current/PACKAGES.TXT')" -check "mirror_path no trailing slash" \ - "file:///mnt/nas/x/y" "$(mirror_path 'file:///mnt/nas' '/x/y')" - -# --- is_local_mirror --- -is_local_mirror "file:///mnt/nas"; check_rc "is_local file://" 0 $? -is_local_mirror "/mnt/nas"; check_rc "is_local bare abs" 0 $? -is_local_mirror "https://x/y"; check_rc "is_local https" 1 $? - -# --- strip_scheme --- -check "strip_scheme file://" "/mnt/nas" "$(strip_scheme 'file:///mnt/nas')" -check "strip_scheme bare" "/mnt/nas" "$(strip_scheme '/mnt/nas')" - -# --- fetch: local file resolves --- -echo hello > "$tmp/src.txt" -fetch "file://$tmp/src.txt" "$tmp/dst.txt"; check_rc "fetch local ok" 0 $? -check "fetch local content" "hello" "$(cat "$tmp/dst.txt" 2>/dev/null)" - -# --- fetch: missing local file errors --- -fetch "file://$tmp/nope.txt" "$tmp/x.txt"; check_rc "fetch local missing" 1 $? - -# --- txz_hash: stable + content-sensitive --- -mkdir -p "$tmp/pkgs" -printf a > "$tmp/pkgs/sbopkg-1.txz" -printf b > "$tmp/pkgs/tools-1.txz" -h1="$(txz_hash "$tmp/pkgs")" -h2="$(txz_hash "$tmp/pkgs")" -check "txz_hash stable" "$h1" "$h2" -printf c > "$tmp/pkgs/tools-1.txz" # change content -h3="$(txz_hash "$tmp/pkgs")" -check_rc "txz_hash changes on content" 0 "$([[ "$h1" != "$h3" ]] && echo 0 || echo 1)" -check "txz_hash empty dir" "" "$(txz_hash "$tmp/empty-nope")" - -# --- require_mount: missing mount errors (subshell to catch _err exit) --- -MIRROR="file://$tmp/no-such-root" -( require_mount current ) 2>/dev/null; check_rc "require_mount missing" 1 $? -mkdir -p "$tmp/mnt/slackware64-current" -MIRROR="file://$tmp/mnt" -( require_mount current ) 2>/dev/null; check_rc "require_mount present" 0 $? -MIRROR="https://example/x" -( require_mount current ) 2>/dev/null; check_rc "require_mount http skips" 0 $? - -echo "----" -echo "PASS: $pass FAIL: $fail" -[[ "$fail" -eq 0 ]] |
