Last active
March 4, 2026 12:51
-
-
Save AfroThundr3007730/341e10a368e2c099da6c451632585086 to your computer and use it in GitHub Desktop.
Build binary zfs-module packages for all kernel packages
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [Unit] | |
| Description=Build binary ZFS module packages and publish repo | |
| RequiresMountsFor=/mnt/pool0/data/app /mnt/pool0/mirror/repo | |
| [Service] | |
| Type=exec | |
| Restart=no | |
| TimeoutSec=10 | |
| ExecStart=/mnt/pool0/data/app/repo/debian-zfs/bin/zfs-module-build -y | |
| EnvironmentFile=-/mnt/pool0/data/app/repo/debian-zfs/etc/environment | |
| EnvironmentFile=-%h/.config/systemd/user/zfs-module-build.env |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Build binary zfs-module packages for all kernel packages | |
| # Version 0.10.8 modified 2026-03-03 by AfroThundr | |
| # SPDX-License-Identifier: GPL-3.0-or-later | |
| # For issues or updated versions of this script, browse to the following URL: | |
| # https://gist.github.com/AfroThundr3007730/341e10a368e2c099da6c451632585086 | |
| # Run in strict mode | |
| set -eEuo pipefail | |
| shopt -s extdebug extglob nullglob | |
| #----------------------------------------------------------------------# | |
| # MARK: Top Matter | |
| #----------------------------------------------------------------------# | |
| # Print program usage text | |
| print_usage() { | |
| local b='\e[1m' e='\e[1;2m' n='\e[22m' l=${_LE} _______='' | |
| say -h " | |
| ${l}$(sed -n "s/# //; s/$/${_LE}/; 2,4p" "$0") | |
| ${l} | |
| ${l}Usage: ${b}${_ME} (-v | -h | -b | -s | -y)${n} | |
| ${l} | |
| ${l}Options: | |
| ${l} | |
| ${l} ${b}-b${n} Run build job queue (internal) | |
| ${l} ${b}-h${n} Display script usage information | |
| ${l} ${b}-s${n} Run repo signing jobs (internal) | |
| ${l} ${b}-v${n} Emit version header and details | |
| ${l} ${b}-y${n} Confirm to start the build | |
| ${l} | |
| ${l}Metadata variables: Default values that may be overridden | |
| ${l} | |
| ${l} * ${b}LOGFILE${n} File to write script output to | |
| ${l} $_______ Default: ${e}${LOGFILE:-(none)}${n} | |
| ${l} * ${b}PURGEDAYS${n} Max age of stamp before purging | |
| ${l} $_______ Default: ${e}${PURGEDAYS:-(none)}${n} | |
| ${l} ${b}MEMLIMIT${n} Set container max memory limit | |
| ${l} $_______ Default: ${e}${MEMLIMIT:-(none)}${n} | |
| ${l} ${b}ENGINE${n} Container engine to use (in PATH) | |
| ${l} $_______ Default: ${e}${ENGINE:-(none)}${n} | |
| ${l} ${b}IMAGE${n} Registry URI for base build image | |
| ${l} $_______ Default: ${e}${IMAGE:-(none)}${n} | |
| ${l} * ${b}MIRROR${n} Repository mirror to check for metadata | |
| ${l} $_______ Default: ${e}${MIRROR:-(none)}${n} | |
| ${l} ${b}APPDIR${n} Application root directory to map in | |
| ${l} $_______ Default: ${e}${APPDIR:-(none)}${n} | |
| ${l} ${b}PUBDIR${n} Repository root directory to map in | |
| ${l} $_______ Default: ${e}${PUBDIR:-(none)}${n} | |
| ${l} | |
| ${l}Control variables: Flags enabled if non-empty (some have overloads) | |
| ${l} | |
| ${l} * ${b}QUIET${n} Suppress output to terminal | |
| ${l} * ${b}RETRY${n} Re-attempt build (ignore build stamp) | |
| ${l} * ${b}REBUILD${n} Rerun build (ignore built package) | |
| ${l} * ${b}STAMPONLY${n} Only refresh timestamps (no build) | |
| ${l} * ${b}SRCPULL${n} Force repull source tarballs | |
| ${l} * ${b}NOBUILD${n} Skip the build job queue completely | |
| ${l} * ${b}BATCH${n} Run multiple concurrent builds per kernel | |
| ${l} * ${b}NOPURGE${n} Skip the stale package purge step | |
| ${l} * ${b}NOPUBLISH${n} Skip the publish job queue completely | |
| ${l} * ${b}REPUBLISH${n} Regenerate repository manifests and release | |
| ${l} $_______ Overload: all - republish all architectures | |
| ${l} * ${b}EXTRAS${n} Force regenerate extrafiles manifest | |
| ${l} ${b}REPULL${n} Force repull container images | |
| ${l} ${b}PULLONLY${n} Only pull container images (no build) | |
| ${l} ${b}USEQEMU${n} Use QEMU emulation instead of crossbuilding | |
| ${l} ${b}SETUP${n} Rerun environment setup even if already present | |
| ${l} ${b}OVERWRITE${n} Overwrite script executable if different | |
| ${l} | |
| ${l}Override variables: Provided as a space-separated list of values | |
| ${l} | |
| ${l} * ${b}XKVER_LIST${n} List of kernel versions to build | |
| ${l} * ${b}XPVER_LIST${n} List of module versions to build | |
| ${l} ${b}XRELS_LIST${n} List of release names to build on | |
| ${l} ${b}XARCH_LIST${n} List of architectures to build on | |
| ${l} | |
| ${l}Note: Variables marked with a '*' are passed into the container. | |
| ${l} | |
| ${l}Note: Override list variables will be validated against values | |
| ${l} retrieved from the upstream repository mirrors. | |
| ${l} | |
| ${l}Note: QEMU emulation is typically slower than cross-compiling and | |
| ${l} requires the 'qemu-user-binfmt' package to be installed. | |
| ${l} | |
| ${l}$(sed -n "s/# //; s/$/${_LE}/; 6,7p" "$0") | |
| " | |
| } | |
| # Print program version | |
| print_version() { say -h "$(sed -n "s/# //; s/$/${_LE}/; 3p" "$0")"; } | |
| # Silent pushd | |
| pushd() { command pushd "$@" >/dev/null; } | |
| # Silent popd | |
| popd() { command popd >/dev/null; } | |
| # Silent apt | |
| qapt() { command apt "$@" &>/dev/null; } | |
| # Print formatted message | |
| say() { | |
| local c1='' c2='' end='\n' fd=1 | |
| while (($#)); do | |
| case $1 in | |
| -e) c1='\e[31;1;2m' c2='\e[m' fd=2 && shift ;; | |
| -h) c1='\e[34m' c2='\e[m' && shift ;; | |
| -n) end='' && shift ;; | |
| *) break ;; | |
| esac | |
| done | |
| (($#)) || return 0 | |
| # shellcheck disable=SC2059 | |
| [[ ${LOGFILE:-} ]] && set -- "${1//\\e+([[0-9;m])/}" "${@:2}" && | |
| printf -- "${1}${end}" "${@:2}" >>"${LOGFILE}" 2>&- || : | |
| [[ ${QUIET:-} ]] && return | |
| [[ ${_LE} ]] && set -- "${1//\\n/\\r\\n}" "${@:2}" | |
| # shellcheck disable=SC2059 | |
| printf -- "${c1}${1}${end:+${_LE}}${end}${c2}" "${@:2}" >&"${fd}" 2>&- || : | |
| } | |
| # Set global variables | |
| set_globals() { | |
| . /etc/os-release | |
| # Executable Script | |
| _ME=${0##*/} _ME=${_ME%.sh} | |
| # Line Ending Format | |
| [[ -t 1 && ! $(stty) =~ -opost ]] && _LE='' || _LE='\r' | |
| # Native Architecture | |
| _ARCH=$(dpkg --print-architecture 2>&- || uname -m) | |
| # Time Output Format | |
| printf -v TIMEFORMAT '%%0lR%b' "${_LE}" | |
| # Locale Override | |
| LC_ALL=C | |
| # Canonical Architecture | |
| CARCH=${CARCH:-${_ARCH}} | |
| # Target Host Architecture | |
| HARCH=${HARCH:-${_ARCH}} | |
| # Debian Component for Package | |
| COMPONENT=contrib | |
| # Binary Package Prefix | |
| MODNAME=zfs-modules | |
| # Source Package Prefix | |
| SRCNAME=zfs-linux | |
| # Release Codename | |
| CNAME=${CNAME:-${VERSION_CODENAME}} | |
| # Internal Data Root | |
| BASEDIR=/srv/zfs | |
| # Build Root | |
| BUILDDIR=/tmp/build | |
| # Repository Root | |
| REPODIR=/srv/repo | |
| # Signing Helper | |
| SIGNER=/usr/local/sbin/module-sign | |
| # Stripping Helper | |
| STRIP=/usr/local/sbin/strip | |
| # Apt Sources File | |
| SOURCES=/etc/apt/sources.list.d/debian.sources | |
| # Location of Distribution Files | |
| DISTDIR=${BASEDIR}/dist/${CNAME} | |
| # Location of Build Log Files | |
| LOGDIR=${BASEDIR}/logs/${CNAME} | |
| # Location of Prepared Source Archives | |
| SRCDIR=${BASEDIR}/source/${CNAME} | |
| # Location of Timestamp Files | |
| STAMPDIR=${BASEDIR}/stamp/${CNAME} | |
| # Repository Relative POOL Path | |
| POOLDIR=pool/${COMPONENT}/${SRCNAME:0:1}/${SRCNAME} | |
| # Required Dependencies for Building | |
| BUILDDEPS=(debhelper fakeroot initramfs-tools lsb-release) | |
| # Reqiored Dependencies for Publication | |
| REPODEPS=(apt-utils ca-certificates curl gawk gnupg xz-utils) | |
| # Output Log File | |
| LOGFILE=${LOGFILE:-} | |
| # Max Age Before Purging | |
| PURGEDAYS=${PURGEDAYS:-30} | |
| # Max Memory Limit | |
| MEMLIMIT=${MEMLIMIT:-1G} | |
| # Container Engine | |
| ENGINE=${ENGINE:-podman} | |
| # Container Registry URI | |
| IMAGE=${IMAGE:-docker.io/library/debian} | |
| # Repository Mirror URL | |
| MIRROR=${MIRROR:-https://deb.debian.org/debian} | |
| # Application Root Directory | |
| APPDIR=${APPDIR:-/opt/debian-zfs} | |
| # Repository Root Directory | |
| PUBDIR=${PUBDIR:-/srv/debian-zfs} | |
| declare -gr \ | |
| _ME _LE _ARCH TIMEFORMAT LC_ALL \ | |
| CARCH HARCH COMPONENT MODNAME SRCNAME CNAME \ | |
| BASEDIR BUILDDIR REPODIR SIGNER STRIP SOURCES \ | |
| DISTDIR LOGDIR SRCDIR STAMPDIR POOLDIR \ | |
| BUILDDEPS REPODEPS | |
| declare -grx \ | |
| LOGFILE PURGEDAYS \ | |
| MEMLIMIT ENGINE IMAGE MIRROR APPDIR PUBDIR \ | |
| QUIET RETRY REBUILD STAMPONLY SRCPULL NOBUILD \ | |
| BATCH NOPURGE NOPUBLISH REPUBLISH EXTRAS \ | |
| REPULL PULLONLY USEQEMU SETUP OVERWRITE | |
| declare -gx \ | |
| XKVER_LIST XPVER_LIST XRELS_LIST XARCH_LIST | |
| declare -gA TAINTED | |
| } | |
| #----------------------------------------------------------------------# | |
| # MARK: Package Build | |
| #----------------------------------------------------------------------# | |
| # Setup build environment | |
| setup_builder() { | |
| say '[+] Release: %s (%s) | Arch: %s | Component: %s' \ | |
| "${NAME}" "${VERSION_CODENAME}" "${HARCH}" "${COMPONENT}" | |
| [[ "${CARCH}" == "${_ARCH}" && "${HARCH}" == "${_ARCH}" ]] && | |
| say '[+] Native building: %s -> %s' "${CARCH}" "${HARCH}" | |
| [[ "${CARCH}" == "${_ARCH}" ]] || | |
| say '[+] QEMU emulating: %s -> %s' "${CARCH}" "${_ARCH}" | |
| [[ "${HARCH}" == "${_ARCH}" ]] || | |
| say '[+] Cross compiling: %s -> %s' "${_ARCH}" "${HARCH}" | |
| sed -i 's/: deb/& deb-src/g; s/: main/& contrib/g' "${SOURCES}" | |
| sed -i "s/${CNAME}-updates/& ${CNAME}-backports/g" "${SOURCES}" | |
| declare -grx DEBIAN_FRONTEND=nonintractive | |
| dpkg --add-architecture "${HARCH}" && qapt update && qapt upgrade -y | |
| mkdir -p "${BUILDDIR}" "${LOGDIR}" "${SRCDIR}" "${STAMPDIR}" \ | |
| "${DISTDIR}"{,-backports} "${REPODIR}/${POOLDIR}" | |
| install -m 0755 /dev/null "${SIGNER}" | |
| # shellcheck disable=SC2016 | |
| printf >"${SIGNER}" '#!/bin/bash\nshopt -s nullglob | |
| SIGNER=$(find /usr/lib/linux-kbuild-* -type f -name sign-file -print -quit) | |
| for i in $1/{,*/}*.ko; do ${SIGNER} sha256 /etc/keys/db.{key,crt} $i; done' | |
| install -m 0755 /dev/null "${STRIP}" | |
| # shellcheck disable=SC2016 | |
| printf >"${STRIP}" '#!/bin/bash\nshopt -s nullglob | |
| for i in $1/{,*/}*.ko; do ${DEB_HOST_GNU_TYPE}-strip -g $i; done' | |
| } | |
| # Get version lists | |
| get_versions() { | |
| mapfile -t KVER_LIST < <( | |
| apt list -a 'linux-headers-*' 2>&- | awk -F'[/ ]' -v arch="${HARCH}" \ | |
| '$1 ~ /-[0-9]+\./ && $4 ~ arch {print substr($1, 15) ":" $3}' | |
| ) | |
| mapfile -t PVER_LIST < <( | |
| apt-cache showsrc "${SRCNAME}" 2>&- | awk '/^Version:/ {print $2}' | |
| ) | |
| [[ ! ${XKVER_LIST[*]:-} ]] || mapfile -t KVER_LIST < <( | |
| read -ra XKVER_LIST <<<"${XKVER_LIST[*]}" | |
| for kver in "${KVER_LIST[@]}"; do | |
| for xkver in "${XKVER_LIST[@]}"; do | |
| [[ "${kver%:*}" == "${xkver}" ]] && printf '%s\n' "${kver}" | |
| done | |
| done | |
| ) && declare -gr KVER_LIST && unset XKVER_LIST | |
| [[ ! ${XPVER_LIST[*]:-} ]] || mapfile -t PVER_LIST < <( | |
| read -ra XPVER_LIST <<<"${XPVER_LIST[*]}" | |
| for pver in "${PVER_LIST[@]}"; do | |
| for xpver in "${XPVER_LIST[@]}"; do | |
| [[ "${pver}" == "${xpver}" ]] && printf '%s\n' "${pver}" | |
| done | |
| done | |
| ) && declare -gr PVER_LIST && unset XPVER_LIST | |
| } | |
| # Get CC mapping | |
| get_ccmap() { | |
| local abi=gnu arch host | |
| case ${HARCH} in | |
| amd64) arch=x86-64 ;; | |
| arm64) arch=aarch64 ;; | |
| armel) arch=arm abi=gnueabi ;; | |
| armhf) arch=arm abi=gnueabihf ;; | |
| i386) arch=i686 ;; | |
| loong64) arch=loongarch64 ;; | |
| mips64el) abi=gnuabi64 ;; | |
| ppc64el) arch=powerpc64le ;; | |
| esac | |
| host=${arch:-${HARCH}}-linux-${abi} | |
| mapfile -t PKG < <( | |
| apt-cache depends gcc gcc-"${host}" | | |
| awk '/: gcc-[0-9]/ {g = $2} /: libc6/ {l = $2} END {print g "\n" l}' | |
| ) | |
| declare -grx PKG CC=/usr/bin/${host/x86-64/x86_64}-${PKG[0]%-"${host}"} | |
| } | |
| # Check disk usage | |
| check_usage() { | |
| local ssize=200 bsize=600 bavail savail bmount smount pkgs | |
| read -rd '\n' bavail bmount savail smount < <( | |
| df -BM "${BUILDDIR}" /usr/src | | |
| awk '$4 ~ /[0-9]+M/ {gsub(/M/, ""); print $4 "\n" $6}' | |
| ) || : | |
| mapfile -t pkgs < <( | |
| find /usr/src -maxdepth 1 -type d -name '*-common*' -print | sort -V | |
| ) && ((${#pkgs[@]})) && unset 'pkgs[${#pkgs[@]}-1]' | |
| ([[ $bmount != "$smount" ]] || ((bavail >= bsize + ssize)) && | |
| ((bavail >= bsize && savail >= ssize)) && ((${#pkgs[@]} == 0))) || { | |
| say '[-] Clearing space for build (purging headers).' | |
| [[ ${_hpkg:-} ]] && qapt remove -y "${_hpkg%%_*}" | |
| qapt remove -y "${pkgs[@]##*/}" | |
| } || { | |
| say -e '[!] Unable to clear enough space for build.' && _die 1 | |
| } | |
| } | |
| # Check build preconditions | |
| check_build() { | |
| local file rc stub=${STAMPDIR}/${srcstub}.dsc | |
| [[ ! -f ${STAMPDIR}/${deb} || ${RETRY:-} || ${REBUILD:-} ]] || rc=1 | |
| [[ ! -f ${out}/${deb} || ${REBUILD:-} ]] || rc=1 | |
| [[ ! ${STAMPONLY:-} ]] || rc=1 | |
| [[ -f ${stub} ]] && | |
| while read -r file; do touch "${STAMPDIR}/${file}"; done <"${stub}" | |
| [[ -f ${STAMPDIR}/${deb} ]] && touch "${STAMPDIR}/${deb}" | |
| return "${rc:-0}" | |
| } | |
| # Install build dependencies | |
| install_deps() { | |
| [[ ${installed:-} ]] && return 0 || installed=1 | |
| say '[*] Installing build dependencies.' | |
| qapt install -y --no-install-recommends "${PKG[@]}" "${BUILDDEPS[@]}" | |
| [[ ${VERSION_ID:-} -ne 12 ]] || | |
| qapt install -y --no-install-recommends "linux-base=$( | |
| apt list -a linux-base 2>&- | awk 'NF == 3 {print $2; exit}' | |
| )" | |
| } | |
| # Prepare source archive | |
| prepare_source() { | |
| local file out stub=${STAMPDIR}/${srcstub}.dsc | |
| [[ -f ${stub} && -f ${tar} && ! ${SRCPULL:-} ]] && return 0 | |
| [[ ${SRCPULL:-} && -f ${BUILDDIR}/repull:${srcstub} ]] && return | |
| [[ ${SRCPULL:-} ]] && : >"${BUILDDIR}/repull:${srcstub}" | |
| [[ $pver =~ bpo ]] && out=${DISTDIR}-backports || out=${DISTDIR} | |
| say '[*] Retrieving sources for: %s %s' "${SRCNAME}" "${pver}" | |
| pushd "${BUILDDIR}" | |
| qapt source "${SRCNAME}=${pver}" | |
| for file in "${SRCNAME}"_*; do | |
| : >>"${STAMPDIR}/${file}" && printf '%s\n' "${file}" >>"${stub}" | |
| done | |
| cp "${SRCNAME}"_* "${out}" && TAINTED[${out##*/}]+=s && rm -f "${SRCNAME}"_* | |
| # shellcheck disable=SC2016 | |
| sed -i '/module modules/a\\tstrip $(CURDIR)/module' "${src}/debian/rules" | |
| # shellcheck disable=SC2016 | |
| sed -i '/strip \$/a\\tmodule-sign $(CURDIR)/module' "${src}/debian/rules" | |
| # shellcheck disable=SC2016 | |
| sed -i 's/with-linux-obj=$(KOBJ)/& \\\n\t\t--host=$(DEB_HOST_GNU_TYPE)/g' \ | |
| "${src}/debian/rules" | |
| tar cf - -C "${src}" . | gzip -9 >"${tar}" && rm -fr "${src}" | |
| popd # ${BUILDDIR} | |
| } | |
| # Install kernel headers | |
| install_headers() { | |
| local common kbuild | |
| [[ -d /usr/src/linux-headers-${kver} ]] && return 0 | |
| say '[*] Installing linux headers for: %s' "${kver}" | |
| IFS=: read -r common kbuild < <( | |
| apt-cache depends linux-headers-"${kver}" | | |
| awk '/common/ {c = $NF} /kbuild/ {k = $NF} END {print c ":" k}' | |
| ) | |
| pushd "${BUILDDIR}" | |
| [[ ${_hpkg:-} ]] && qapt remove -y "${_hpkg%%_*}" | |
| declare -g _hpkg=linux-headers-${kver}_${kpver}_${HARCH}.deb | |
| qapt install -y --no-install-recommends "${common}" "${kbuild}" | |
| qapt download linux-headers-"${kver}" && | |
| dpkg -i --force-all "${_hpkg}" &>/dev/null && rm -f "./${_hpkg}" | |
| popd # ${BUILDDIR} | |
| declare -gx KOBJ=/usr/src/linux-headers-"${kver}" KSRC=/usr/src/"${common}" | |
| } | |
| # Build kernel modules | |
| build_modules() { | |
| say '[*] [%03d] Building: %s | Kernel: %s | Package: %s' \ | |
| "${id}" "${MODNAME}" "${kver}" "${pver}" | |
| pushd "${BUILDDIR}" | |
| rm -fr "${build}" && mkdir "${build}" && tar xf "${tar}" -C "${build}" | |
| pushd "${build}" | |
| dpkg-architecture -c fakeroot nice -n 19 ionice -c 3 debian/rules \ | |
| override_dh_binary-modules 1>"${log1}" 2>"${log2}" || { | |
| : >"${STAMPDIR}/${deb}" && popd # ${build} | |
| say -e '[!] [%03d] Errors during build, check logs:\n--- [%03d] %s' \ | |
| "${id}" "${id}" "${log2}" | |
| gzip -9f "${log1}" "${log2}" && rm -fr "${build}" && return 0 | |
| } && { | |
| : >"${STAMPDIR}/${deb}" && popd # ${build} | |
| say '[+] [%03d] Package build completed successfully.' "${id}" | |
| cp "${deb}" "${out}" && : >"taint:${out##*/}" | |
| gzip -9f "${log1}" "${log2}" && rm -fr "${build}" "${deb}" | |
| apt info "${out}/${deb}" 2>&- | awk -v id="${id}" -v l="${_LE}" \ | |
| '{printf "--- [%03d] %s%s\n", id, $0, l} /^Description/ {exit}' | |
| } | |
| popd # ${BUILDDIR} | |
| } | |
| # Remove unneeded headers | |
| prune_headers() { | |
| ((id)) && say '[+] All module package builds complete.' | |
| ((id)) || say '[-] No module package builds occurred.' | |
| qapt purge -y linux-{image,headers,kbuild}-'*' && qapt autoremove -y | |
| } | |
| # Purge expired packages | |
| clean_packages() { | |
| [[ ${NOPURGE:-} ]] && return 0 | |
| local stamps stamp file dir queue count | |
| mapfile -t stamps < <( | |
| find "${STAMPDIR}" -type f -mtime +"${PURGEDAYS}" -printf '%f\n' -delete | |
| ) && ((${#stamps[@]})) || return 0 | |
| say '[*] Purging stale module packages.' | |
| for stamp in "${stamps[@]}"; do | |
| say '--- Purging stale package: %s' "${stamp##*/}" | |
| queue=() count=0 | |
| for file in "${DISTDIR}"{,-backports}/"${stamp}"; do | |
| queue+=("${file}") dir=${file%/*} dir=${dir##*/} | |
| case ${file##*.} in | |
| deb) TAINTED[dir]+=b ;; | |
| dsc) TAINTED[dir]+=s ;; | |
| esac | |
| done | |
| for _ in "${BASEDIR}"/dist/*/"${stamp}"; do ((++count)); done | |
| ((count == 1)) && queue+=("${REPODIR}/${POOLDIR}/${stamp}") | |
| case ${stamp##*.} in | |
| deb) queue+=("${LOGDIR}/${stamp}".{1,2}.log.gz) ;; | |
| dsc) queue+=("${SRCDIR}/${stamp%.*}".tar.gz) ;; | |
| esac | |
| rm -f "${queue[@]}" | |
| done | |
| say ' [+] Purging stale packages complete.' | |
| } | |
| # Publish packages to repo | |
| publish_packages() { | |
| for _ in "${BUILDDIR}"/taint:*; do TAINTED[${_#*:}]+=b; done | |
| [[ ${!TAINTED[*]} || ${REPUBLISH:-} ]] && local candidates=( | |
| "${DISTDIR}"{,-backports}/{"${SRCNAME}"_*,"${MODNAME}"-*"${HARCH}"*} | |
| ) && ((${#candidates[@]})) || return 0 | |
| say '[+] Copying new packages to pool.' | |
| [[ $(stat -c %g "${BASEDIR}") -eq 65534 ]] || | |
| find "${BASEDIR}" ! -user 1000 -print0 | xargs -0r chown 1000 2>&- | |
| cp -nt "${REPODIR}/${POOLDIR}" "${candidates[@]}" | |
| } | |
| # Run package build jobs | |
| build_packages() { | |
| [[ ${NOBUILD:-} ]] && return 0 | |
| local id=0 kver kpver pver | |
| local verstub srcstub deb src tar build log1 log2 out | |
| say '=== Begin ZFS package build job queue.' | |
| setup_builder | |
| get_versions | |
| get_ccmap | |
| pushd "${BASEDIR}" | |
| for kver in "${KVER_LIST[@]}"; do | |
| kpver=${kver#*:} kver=${kver%:*} | |
| declare -gx KVERS=${kver} | |
| check_usage | |
| for pver in "${PVER_LIST[@]}"; do | |
| verstub=${kver}_${pver}_${HARCH} | |
| srcstub=${SRCNAME}_${pver} | |
| deb=${MODNAME}-${verstub}.deb | |
| src=${BUILDDIR}/${SRCNAME}-${pver%%-*} | |
| tar=${SRCDIR}/${srcstub}.tar.gz | |
| build=${BUILDDIR}/${SRCNAME}-${verstub} | |
| log1=${LOGDIR}/${deb}.1.log | |
| log2=${LOGDIR}/${deb}.2.log | |
| [[ $pver =~ bpo || $kpver =~ bpo ]] && | |
| out=${DISTDIR}-backports || out=${DISTDIR} | |
| check_build || continue | |
| install_deps | |
| prepare_source | |
| install_headers | |
| ((++id)) | |
| time { | |
| build_modules | |
| say -n '[-] [%03d] Build time: ' "${id}" | |
| } & | |
| [[ ${BATCH:-} ]] || wait | |
| done | |
| wait | |
| done | |
| popd # ${BASEDIR} | |
| prune_headers | |
| clean_packages | |
| publish_packages | |
| say '=== End ZFS package build job queue.' | |
| } | |
| #----------------------------------------------------------------------# | |
| # MARK: Repo Publish | |
| #----------------------------------------------------------------------# | |
| # Setup repo environment | |
| setup_repository() { | |
| local dist prefix='APT::FTPArchive::Release' | |
| say '[*] Installing publish dependencies.' | |
| qapt update && qapt install -y --no-install-recommends "${REPODEPS[@]}" | |
| gpg -q --import /etc/keys/release.asc | |
| read -r SUITE VERSION < <( | |
| curl -fsS "${MIRROR}/dists/README" | | |
| awk -v name="${CNAME}" '$3 == name {sub(/,/, ""); print $1, $NF}' | |
| ) | |
| read -r ARCHES < <( | |
| curl -fsS "${MIRROR}/dists/${CNAME}/InRelease" | | |
| awk -F: '$1 ~ /Architectures/ {print $2}' | |
| ) | |
| [[ ${VERSION%.*} =~ ^[0-9]+$ ]] || VERSION=${SUITE} && | |
| declare -gr ARCHES SUITE VERSION | |
| [[ ${REPUBLISH:-} ]] && { | |
| [[ ${REPUBLISH:-} == all ]] && alist="${ARCHES}" | |
| [[ ! ${SUITE} =~ ^(oldstable|stable|testing)$ ]] || | |
| TAINTED[${CNAME/%/-backports}]+=r && TAINTED[${CNAME}]+=r | |
| } | |
| for dist in "${!TAINTED[@]}"; do | |
| [[ ! ${REPUBLISH:-} && -f /etc/repo/${dist}.conf ]] || | |
| printf >"/etc/repo/${dist}.conf" '%s' " | |
| ${prefix}::Patterns \"contents-*\"; | |
| ${prefix}::Origin \"DM4 Productions\"; | |
| ${prefix}::Label \"Debian ZFS Modules\"; | |
| ${prefix}::Suite \"${dist//${CNAME}/${SUITE}}\"; | |
| ${prefix}::Version \"${dist//${CNAME}/${VERSION%.*}}\"; | |
| ${prefix}::Codename \"${dist}\"; | |
| ${prefix}::Architectures \"${ARCHES}\"; | |
| ${prefix}::Components \"${COMPONENT}\"; | |
| ${prefix}::Description \"Precompiled ZFS module packages for each kernel\"; | |
| " | |
| done | |
| } | |
| # Generate source manifest | |
| generate_sources() { | |
| [[ ${TAINTED[$dist]} =~ s|r ]] || return 0 && mkdir -p "${sources%/*}" | |
| say '--- Generating package source manifest.' | |
| apt-ftparchive sources "${ddir}" "${hashes[@]}" >"${sources}" 2>&- | |
| sed -i "s|${ddir}|${POOLDIR}|g" "${sources}" | |
| xz -9fk "${sources}" && gzip -9fk "${sources}" && rm -f "${sources}" | |
| } | |
| # Generate package manifest | |
| generate_packages() { | |
| [[ ${TAINTED[$dist]} =~ b|r ]] || return 0 && mkdir -p "${packages%/*}" | |
| say '--- Generating package manifest for: %s' "${arch}" | |
| apt-ftparchive packages -a "${arch}" "${ddir}" "${hashes[@]}" >"${packages}" | |
| apt-ftparchive contents -a "${arch}" "${ddir}" >"${contents}" | |
| sed -i "s|${ddir}|${POOLDIR}|g" "${packages}" | |
| xz -9fk "${packages}" "${contents}" && | |
| gzip -9fk "${packages}" "${contents}" && | |
| rm -f "${packages}" "${contents}" | |
| } | |
| # Generate release file | |
| generate_release() { | |
| [[ ${TAINTED[$dist]} =~ b|s|r ]] || return 0 && mkdir -p "${reldir}" | |
| local files treldir=${reldir/${REPODIR}//tmp/repo} | |
| say '--- Generating and signing release file.' | |
| mkdir -p "${treldir}" && cp -R "${reldir}"/. "${treldir}" | |
| mapfile -t files < <(find "${treldir}" -type f -name '*.gz' -print) && | |
| ((${#files[@]})) && gzip -dfk "${files[@]}" | |
| apt-ftparchive release "${treldir}" "${hashes[@]}" \ | |
| -c "/etc/repo/${dist}.conf" | gpg -as --clear-sign --yes -o "${release}" | |
| rm -fr "${treldir}" | |
| ln -fsrn "${reldir}" "${reldir//${CNAME}/${SUITE}}" | |
| } | |
| # Generate extra files manifest | |
| generate_extrafiles() { | |
| local fl ml mf=extrafiles | |
| mapfile -t fl < <( | |
| find . -maxdepth 1 -type f ! -name "${mf}" -printf '%f\n' | sort | |
| ) | |
| mapfile -t ml < <( | |
| [[ -f ${mf} ]] && | |
| awk 'NF == 2 && $1 ~ /[0-9a-f]{32}/ {print $2}' "${mf}" | |
| ) | |
| [[ -f ${mf} && ! ${REPUBLISH:-} ]] && sha256sum -c "${mf}" &>/dev/null && | |
| [[ "${fl[*]}" == "${ml[*]}" ]] || { | |
| [[ "${fl[*]}" ]] || { rm -f "${mf}" && return 0; } | |
| say '[+] Generating top-level extrafiles manifest.' | |
| sha256sum -- "${fl[@]}" 2>&- | gpg -as --clear-sign --yes -o "${mf}" | |
| } | |
| [[ $(stat -c %g "${REPODIR}") -eq 65534 ]] || | |
| find "${REPODIR}" ! -user 1000 -print0 | xargs -0r chown 1000 2>&- | |
| } | |
| # Run repository publish jobs | |
| sign_repository() { | |
| [[ ${NOPUBLISH:-} ]] && return 0 | |
| [[ ${!TAINTED[*]} || ${REPUBLISH:-} || ${EXTRAS:-} ]] || return 0 | |
| local arch ddir dist file reldir compdir release contents packages sources | |
| local alist="${HARCH}" hashes=(--sha256 --no-sha512 --no-sha'1' --no-md'5') | |
| say '=== Begin repository publish job queue.' | |
| setup_repository | |
| pushd "${REPODIR}" | |
| for dist in "${!TAINTED[@]}"; do | |
| ddir=${DISTDIR//${CNAME}/${dist}} | |
| reldir=${REPODIR}/dists/${dist} | |
| compdir=${reldir}/${COMPONENT} | |
| release=${reldir}/InRelease | |
| sources=${compdir}/source/Sources | |
| say '[*] Update started for repository: %s' "${dist}" | |
| generate_sources | |
| for arch in ${alist}; do | |
| contents=${compdir}/Contents-${arch} | |
| packages=${compdir}/binary-${arch}/Packages | |
| generate_packages | |
| done | |
| generate_release | |
| say '[+] Update complete for repository: %s' "${dist}" | |
| done | |
| generate_extrafiles | |
| say '=== End repository publish job queue.' | |
| popd # ${REPODIR} | |
| } | |
| #----------------------------------------------------------------------# | |
| # MARK: Build Matrix | |
| #----------------------------------------------------------------------# | |
| # Check workspace structure | |
| check_workspace() { | |
| local exe=${APPDIR}/bin/${_ME} | |
| mkdir -p "${APPDIR}"/{bin,etc/{keys,repo},work} "${PUBDIR}" 2>&- || { | |
| say -e '[!] Failed to prepare workspace, ensure the following exist:' | |
| say -e '--- APPDIR: %s\n--- PUBDIR: %s' "${APPDIR}" "${PUBDIR}" | |
| say -e '--- Or set them to appropriate values.' && _die 1 | |
| } | |
| [[ ! ${SETUP:-} && "$0" == "${exe}" ]] && return 0 | |
| ([[ -f ${exe} && ! -L ${exe} ]] && diff "$0" "${exe}" &>/dev/null) || { | |
| [[ ! ${OVERWRITE:-} && -f ${exe} && ! -L ${exe} ]] || { | |
| say '[*] Installing main script to:\n--- %s' "${exe}" | |
| install -m 0755 "$0" "${exe}" | |
| } | |
| } | |
| } | |
| # Check GPG release key | |
| check_releasekey() { | |
| local ghome=/dev/shm/gpgtmp \ | |
| signkey=${APPDIR}/etc/keys/release.asc \ | |
| signpub=${APPDIR}/etc/keys/release.pub \ | |
| subj='Auto-generated Release Signing Key <release@localhost.local>' | |
| [[ ! ${SETUP:-} && -f ${signkey} && ! -L ${signkey} ]] && return 0 | |
| say '[*] Generating new release signing key:' | |
| rm -fr "${ghome}" && mkdir -m 0700 "${ghome}" | |
| gpg -q --homedir "${ghome}" --batch --passphrase '' \ | |
| --quick-gen-key "${subj}" ed25519 sign,cert 2y | |
| gpg --homedir "${ghome}" --batch --export-secret-keys -qao "${signkey}" | |
| gpg --homedir "${ghome}" --batch --export -qao "${signpub}" | |
| say '--- SIGNKEY: %s\n--- PUBKEY: %s' "${signkey}" "${signpub}" | |
| gpg --homedir "${ghome}" --batch -qK | sed -n "s/^/--- /; s/$/${_LE}/; 3,5p" | |
| chmod 0600 "${signkey}" && rm -fr "${ghome}" | |
| } | |
| # Check module signing cert | |
| check_signcert() { | |
| local cert=${APPDIR}/etc/keys/db.crt \ | |
| key=${APPDIR}/etc/keys/db.key \ | |
| subj='Auto-generated Kernel Module Signing Cert' \ | |
| config='[sign_cert] | |
| basicConstraints=critical,CA:false | |
| subjectKeyIdentifier=hash | |
| keyUsage=critical,digitalSignature | |
| extendedKeyUsage=codeSigning | |
| ' | |
| [[ ! ${SETUP:-} && -f ${cert} && ! -L ${cert} ]] && return 0 | |
| say '[*] Generating new module signing certificate:' | |
| openssl req -quiet -batch -x509 -days 730 -sha256 -utf8 -noenc \ | |
| -newkey ec -pkeyopt ec_paramgen_curve:secp256k1 \ | |
| -keyout "${key}" -out "${cert}" -subj "/CN=${subj}" \ | |
| -config <(printf '%s' "$config") -extensions sign_cert 2>&- | |
| say '--- DBCERT: %s\n--- DBKEY: %s' "${cert}" "${key}" | |
| openssl x509 -text -noout -in "${cert}" | | |
| sed -nE "s/\s{8}/--- /; s/$/${_LE}/; 6,11p" | |
| } | |
| # Get release list | |
| get_releases() { | |
| mapfile -t RELS_LIST < <( | |
| curl -fsS "${MIRROR}/dists/README" | awk \ | |
| '$1 ~ /^(((old|)old|un|)stable|testing|experimental),/ {print $3}' | |
| ) | |
| [[ ! ${XRELS_LIST[*]:-} ]] || mapfile -t RELS_LIST < <( | |
| read -ra XRELS_LIST <<<"${XRELS_LIST}" | |
| for rel in "${RELS_LIST[@]}"; do | |
| for xrel in "${XRELS_LIST[@]}"; do | |
| [[ "${rel}" == "${xrel}" ]] && printf '%s\n' "${rel}" | |
| done | |
| done | |
| ) && declare -gr RELS_LIST && unset XRELS_LIST | |
| } | |
| # Get architecture list | |
| get_arches() { | |
| mapfile -t ARCH_LIST < <( | |
| curl -fsS "${MIRROR}/dists/${rel}/InRelease" | | |
| awk '$1 ~ /^Architectures:/ {for (i=2; i <= NF; i++) print $i}' | |
| ) | |
| [[ ! ${XARCH_LIST:-} ]] || mapfile -t ARCH_LIST < <( | |
| read -ra XARCH_LIST <<<"${XARCH_LIST}" | |
| for arch in "${ARCH_LIST[@]}"; do | |
| for xarch in "${XARCH_LIST[@]}"; do | |
| [[ "${arch}" == "${xarch}" ]] && printf '%s\n' "${arch}" | |
| done | |
| done | |
| ) && declare -g ARCH_LIST | |
| } | |
| # Pull container image | |
| pull_variant() { | |
| [[ ${REPULL:-} ]] || | |
| ! ${ENGINE} image exists "${IMAGE}:${rel}-${carch:-arch}" || return 0 | |
| say '[*] Pulling image for: %s | Arch: %s' "${rel}" "${carch:-arch}" | |
| ${ENGINE} image pull -q ${USEQEMU:+--platform "${plat}"} \ | |
| "${IMAGE}:${rel}" >&- || { | |
| say -e '[!] Failed to pull image:\n--- %s:%s | Arch: %s' \ | |
| "${IMAGE}" "${rel}" "${carch:-arch}" && return 1 | |
| } | |
| ${ENGINE} image tag "${IMAGE}:${rel}" "${IMAGE}:${rel}-${carch:-arch}" | |
| ${ENGINE} image untag "${IMAGE}:${rel}" "${IMAGE}:${rel}" | |
| ${ENGINE} image prune -f | |
| } | |
| # Run build container | |
| run_build() { | |
| [[ ! ${PULLONLY:-} ]] || return 0 | |
| ${ENGINE} run --rm --replace --net host \ | |
| --memory "${MEMLIMIT}" \ | |
| --name "zfs-build-${rel}-${arch}" \ | |
| ${USEQEMU:+--platform "${plat}"} \ | |
| ${USEQEMU:+-e CARCH="${_ARCH}"} \ | |
| ${arch:+-e HARCH="${arch}"} \ | |
| ${rel:+-e CNAME="${rel}"} \ | |
| ${RETRY:+-e RETRY} \ | |
| ${REBUILD:+-e REBUILD} \ | |
| ${STAMPONLY:+-e STAMPONLY} \ | |
| ${SRCPULL:+-e SRCPULL} \ | |
| ${NOBUILD:+-e NOBUILD} \ | |
| ${BATCH:+-e BATCH} \ | |
| ${NOPURGE:+-e NOPURGE} \ | |
| ${NOPUBLISH:+-e NOPUBLISH} \ | |
| ${REPUBLISH:+-e REPUBLISH} \ | |
| ${EXTRAS:+-e EXTRAS} \ | |
| ${QUIET:+-e QUIET} \ | |
| ${LOGFILE:+-e LOGFILE} \ | |
| ${PURGEDAYS:+-e PURGEDAYS} \ | |
| ${MIRROR:+-e MIRROR} \ | |
| ${XKVER_LIST:+-e XKVER_LIST} \ | |
| ${XPVER_LIST:+-e XPVER_LIST} \ | |
| -v "${APPDIR}/bin:/usr/local/bin:ro" \ | |
| -v "${APPDIR}/etc/keys:/etc/keys:ro" \ | |
| -v "${APPDIR}/etc/repo:/etc/repo:rw" \ | |
| -v "${APPDIR}/work:/srv/zfs:rw" \ | |
| -v "${PUBDIR}:/srv/repo:rw" \ | |
| "${IMAGE}:${rel}-${carch:-arch}" \ | |
| "/usr/local/bin/${_ME}" -b & | |
| sudo -in systemd-inhibit --who="${_ME}:${rel}-${arch}" \ | |
| --why="Building ZFS module packages: ${rel}/${arch}" \ | |
| pidwait -g "${SYSTEMD_EXEC_PID:-$$}" "${ENGINE}" 2>&- || | |
| { say '[-] Unable to set inhibitor, waiting instead.' && wait; } | |
| } | |
| # Run build job matrix | |
| build_matrix() { | |
| local rel arch plat carch | |
| say '=== Begin ZFS package build matrix.' | |
| check_workspace | |
| check_releasekey | |
| check_signcert | |
| pushd "${APPDIR}" | |
| get_releases | |
| for rel in "${RELS_LIST[@]}"; do | |
| get_arches | |
| for arch in "${ARCH_LIST[@]}"; do | |
| plat='' | |
| case ${arch} in | |
| all) continue ;; | |
| armel) plat=linux/arm/v5 ;; | |
| armhf) plat=linux/arm/v7 ;; | |
| loong64) [[ ${USEQEMU:-} ]] && continue ;; | |
| mipsel) [[ ${USEQEMU:-} ]] && continue ;; | |
| mips64el) plat=linux/mips64le ;; | |
| ppc64el) plat=linux/ppc64le ;; | |
| *) : "${plat:=linux/${arch}}" ;; | |
| esac | |
| [[ ${USEQEMU:-} ]] || carch=${_ARCH} | |
| pull_variant || continue | |
| run_build || continue | |
| done | |
| done | |
| popd # ${APPDIR} | |
| say '=== End ZFS package build matrix.' | |
| } | |
| #----------------------------------------------------------------------# | |
| # MARK: End Matter | |
| #----------------------------------------------------------------------# | |
| # Show stack trace on error | |
| _show_trace() { | |
| trap - ERR | |
| local i=0 l s f | |
| say -e '[!] Fatal Error | Exit: %d | Stack Trace:' "${1}" | |
| while read -r l s f < <(caller ${i}); do | |
| ((++i)) && say -e '::: at %s:%d in function %s:\n::: %s' \ | |
| "${f##*/}" "${l}" "${s}" "$(sed -nE "s/^\s+//; ${l}p" "$0")" | |
| done | |
| exit "$1" | |
| } >&2 | |
| # Stop container on termination | |
| _kill_build() { | |
| trap - INT QUIT TERM ERR | |
| local id err=Exit && (($1 > 128)) && err=Signal | |
| id=$(${ENGINE} rm -fit0 "zfs-build-${rel:-*}-${arch:-*}" 2>&-) | |
| say -e '[!] Terminated | %s: %d | Killed container: %s' \ | |
| "${err}" "$(($1 % 128))" "${id:-N/A}" | |
| kill -n 15 -$$ | |
| } >&2 | |
| # Exit without stack trace | |
| _die() { trap - ERR && exit "$1"; } | |
| # Main program entrypoint | |
| _main() { | |
| trap '_show_trace "$?"' ERR | |
| trap '_kill_build "$?"' INT QUIT TERM | |
| declare -rf \ | |
| print_usage print_version pushd popd qapt say \ | |
| set_globals setup_builder get_versions get_ccmap \ | |
| check_usage check_build install_deps prepare_source \ | |
| install_headers build_modules prune_headers \ | |
| clean_packages publish_packages build_packages \ | |
| setup_repository generate_sources generate_packages \ | |
| generate_release generate_extrafiles sign_repository \ | |
| check_workspace check_releasekey check_signcert \ | |
| get_releases get_arches pull_variant run_build \ | |
| build_matrix _show_trace _kill_build _die _main | |
| set_globals | |
| case ${1:-} in | |
| -b) build_packages ;& | |
| -s) sign_repository ;; | |
| -y) build_matrix ;; | |
| -h) print_usage ;; | |
| -v) print_version ;; | |
| *) say -e 'Use -y to start build matrix, or -h for usage.' && _die 1 ;; | |
| esac | |
| } | |
| # Only execute if not being sourced | |
| [[ ${BASH_SOURCE[0]} == "$0" ]] || return 0 && _main "$@" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [Unit] | |
| Description=Build binary ZFS modules packages and publish repo | |
| Wants=network.target | |
| [Timer] | |
| OnCalendar=daily | |
| Persistent=1 | |
| [Install] | |
| WantedBy=timers.target |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO: