aboutsummaryrefslogtreecommitdiffstats
path: root/.extras/image-builder/bootstrap.sh
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-07-14 17:03:08 +0200
committerDanilo M. <danix@danix.xyz>2026-07-14 17:03:08 +0200
commitabccc01065b74f06191c7b408ee2903aca9fdcfb (patch)
treef21136474e50ebc8e52300536e11a7c7813460ff /.extras/image-builder/bootstrap.sh
parent8e3003d162ec1e362523c0d3c232753950d2d343 (diff)
downloadsbo-slackbuilds-abccc01065b74f06191c7b408ee2903aca9fdcfb.tar.gz
sbo-slackbuilds-abccc01065b74f06191c7b408ee2903aca9fdcfb.zip
extract docker tooling to ../sbo-dockerbuild
The test-build tool and image-builder chain grew from a .extras/ helper into a standalone toolchain, now maintained in its own repo (../sbo-dockerbuild). Remove them here (scripts, examples, design docs) and repoint CLAUDE.md: test-building is now done with the installed `test-build` CLI, deps resolved from the SBo tree, both trees verified per bump. Package-maintenance files stay: nvchecker.toml, hooks/, assets/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to '.extras/image-builder/bootstrap.sh')
-rwxr-xr-x.extras/image-builder/bootstrap.sh552
1 files changed, 0 insertions, 552 deletions
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}"