From e7c9bf1df2317efc6d73fb6b6fd5849a1015bc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Tue, 22 Sep 2026 15:44:37 +0000 Subject: [PATCH 1/8] tkldev-setup bugfix: verify the downloaded bootstrap tarball, not its dir 78e7142 moved the closing bracket to the end of the path, so $(dirname "$BOOTSTRAP_PATH/$bootstrap_tar") returns $BOOTSTRAP_PATH itself and signature-verify is called on /turnkey/fab/bootstraps/- (and .hash), which do not exist. With bash -e this aborts tkldev-setup on every bootstrap download: FATAL [signature-verify]: file &/or hashfile not found (/turnkey/fab/bootstraps/bookworm-amd64 / .../bookworm-amd64.hash) Tested with RELEASE=debian/bookworm: GPG and checksum verification pass and the bootstrap is unpacked. --- overlay/usr/local/sbin/tkldev-setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay/usr/local/sbin/tkldev-setup b/overlay/usr/local/sbin/tkldev-setup index d824c06..3174bd8 100755 --- a/overlay/usr/local/sbin/tkldev-setup +++ b/overlay/usr/local/sbin/tkldev-setup @@ -317,7 +317,7 @@ else wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar.hash" \ || fail "$bootstrap_tar.hash" if [[ -f "$BT_VERIFY" ]]; then - bootstrap_tar_path=$(dirname "$BOOTSTRAP_PATH/$bootstrap_tar") + bootstrap_tar_path="$(dirname "$BOOTSTRAP_PATH")/$bootstrap_tar" BT_DEBUG=$DEBUG $BT_VERIFY \ --force-gpg "$bootstrap_tar_path" "$bootstrap_tar_path.hash" else From 231e7610c977923064835d756bfde45424cfa5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Tue, 22 Sep 2026 15:47:28 +0000 Subject: [PATCH 2/8] tkldev-setup: build the bootstrap locally when it can't be downloaded 19.0 shipped without bootstrap-trixie-amd64.tar.gz on the mirror, so on a fresh TKLDev 19.0 the 40tkldev firstboot hook (tkldev-setup core) dies with a 404 and leaves no bootstrap, and nothing can be built. When the tarball (or its .hash) can't be downloaded, build it with the bootstrap repo instead, the same way bt-bootstrap does: clone or update turnkeylinux/bootstrap next to FAB_PATH and run 'make install'. - default: warn and build locally - --strict: fail, as before - --transition: warn and skip, as before; it no longer goes on to verify files that were never downloaded Tested on a TKLDev 19.0 VM with the trixie bootstrap removed: default builds and installs it (81s), --strict exits 1, --transition skips; a bookworm download still verifies and unpacks. --- overlay/usr/local/sbin/tkldev-setup | 55 +++++++++++++++++++---------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/overlay/usr/local/sbin/tkldev-setup b/overlay/usr/local/sbin/tkldev-setup index 3174bd8..35868fe 100755 --- a/overlay/usr/local/sbin/tkldev-setup +++ b/overlay/usr/local/sbin/tkldev-setup @@ -13,14 +13,16 @@ $(basename "$0"): [-h|--help] [APP] [... APP] Tool to initialize and maintain TKLDev. Relevant git repos are downloaded/updated (see below), bootstrap is downloaded -if needed and the latest 'fab' package will be installed when relevant. +if needed (or built locally if not available for download) and the latest +'fab' package will be installed when relevant. Options: APP [APP ...] Appliance name/s - defaults if unset: 'core' -f|--force Attempt checkout of default branch (see below) if alternate branch checked out; dirty repos still skipped -n|--full-clone Full clone (rather than default shallow clone)* - -s|--strict Fail rather than skip non-fatal steps + -s|--strict Fail rather than skip non-fatal steps (including a missing + bootstrap download, rather than building it locally) -t|--transition Transition build - no fail on missing bootstrap download** -h|--help Show this help and exit @@ -297,34 +299,49 @@ apt-get update -qq info "Installing newer fab version (if available)" DEBIAN_FRONTEND=noninteractive apt-get install -y fab +build_bootstrap() { + local src + src="$(dirname "$FAB_PATH")/bootstrap" + clone_or_update bootstrap "$src" master + info "Building $BOOTSTRAP_NAME locally (from $src)" + make -C "$src" clean + make -C "$src" install \ + FAB_PATH="$FAB_PATH" RELEASE="$RELEASE" FAB_ARCH="$FAB_ARCH" + if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then + fatal "Local build of $BOOTSTRAP_NAME did not create $BOOTSTRAP_PATH" + fi +} + if [[ -d "$BOOTSTRAP_PATH" ]]; then if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then fatal "$BOOTSTRAP_PATH exists, but does not appear to be a bootstrap" fi info "$BOOTSTRAP_PATH exists, skipping download." else - if [[ -n $TRANSITION ]]; then - fail() { warning "Failed download: $* - skipping due to transition"; } - else - fail() { fatal "Failed download: $*"; } - fi info "Downloading $BOOTSTRAP_NAME" mkdir -p "$(dirname "$BOOTSTRAP_PATH")" cd "$(dirname "$BOOTSTRAP_PATH")" bootstrap_tar="$BOOTSTRAP_NAME.tar.gz" - wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar" \ - || fail "$bootstrap_tar" - wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar.hash" \ - || fail "$bootstrap_tar.hash" - if [[ -f "$BT_VERIFY" ]]; then - bootstrap_tar_path="$(dirname "$BOOTSTRAP_PATH")/$bootstrap_tar" - BT_DEBUG=$DEBUG $BT_VERIFY \ - --force-gpg "$bootstrap_tar_path" "$bootstrap_tar_path.hash" + if wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar" \ + && wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar.hash"; then + if [[ -f "$BT_VERIFY" ]]; then + bootstrap_tar_path="$(dirname "$BOOTSTRAP_PATH")/$bootstrap_tar" + BT_DEBUG=$DEBUG $BT_VERIFY \ + --force-gpg "$bootstrap_tar_path" "$bootstrap_tar_path.hash" + else + fatal "$BT_VERIFY script not found, unable to verify downloaded files." + fi + info "Unpacking $BOOTSTRAP_NAME" + mkdir "$BOOTSTRAP_PATH" + tar -zxf "$BOOTSTRAP_NAME.tar.gz" -C "$BOOTSTRAP_PATH" + elif [[ -n $TRANSITION ]]; then + warning "Failed download: $bootstrap_tar - skipping due to transition" + elif [[ -n $STRICT ]]; then + fatal "Failed download: $bootstrap_tar" else - fatal "$BT_VERIFY script not found, unable to verify downloaded files." + warning "Failed download: $bootstrap_tar - building it locally instead" + rm -f "$bootstrap_tar" "$bootstrap_tar.hash" + build_bootstrap fi - info "Unpacking $BOOTSTRAP_NAME" - mkdir "$BOOTSTRAP_PATH" - tar -zxf "$BOOTSTRAP_NAME.tar.gz" -C "$BOOTSTRAP_PATH" fi info "$(basename "$0") complete." From e5c2dfad10a6cbaaf6ae509282eb49cea32efb20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Fri, 25 Sep 2026 17:35:04 +0000 Subject: [PATCH 3/8] tkldev-setup: move the bootstrap download into a function No behaviour change: the block that downloads (or builds) the bootstrap now lives in setup_bootstrap(), called from the same place. --- overlay/usr/local/sbin/tkldev-setup | 62 +++++++++++++++-------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/overlay/usr/local/sbin/tkldev-setup b/overlay/usr/local/sbin/tkldev-setup index 35868fe..7b330f6 100755 --- a/overlay/usr/local/sbin/tkldev-setup +++ b/overlay/usr/local/sbin/tkldev-setup @@ -312,36 +312,40 @@ build_bootstrap() { fi } -if [[ -d "$BOOTSTRAP_PATH" ]]; then - if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then - fatal "$BOOTSTRAP_PATH exists, but does not appear to be a bootstrap" - fi - info "$BOOTSTRAP_PATH exists, skipping download." -else - info "Downloading $BOOTSTRAP_NAME" - mkdir -p "$(dirname "$BOOTSTRAP_PATH")" - cd "$(dirname "$BOOTSTRAP_PATH")" - bootstrap_tar="$BOOTSTRAP_NAME.tar.gz" - if wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar" \ - && wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar.hash"; then - if [[ -f "$BT_VERIFY" ]]; then - bootstrap_tar_path="$(dirname "$BOOTSTRAP_PATH")/$bootstrap_tar" - BT_DEBUG=$DEBUG $BT_VERIFY \ - --force-gpg "$bootstrap_tar_path" "$bootstrap_tar_path.hash" - else - fatal "$BT_VERIFY script not found, unable to verify downloaded files." +setup_bootstrap() { + if [[ -d "$BOOTSTRAP_PATH" ]]; then + if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then + fatal "$BOOTSTRAP_PATH exists, but does not appear to be a bootstrap" fi - info "Unpacking $BOOTSTRAP_NAME" - mkdir "$BOOTSTRAP_PATH" - tar -zxf "$BOOTSTRAP_NAME.tar.gz" -C "$BOOTSTRAP_PATH" - elif [[ -n $TRANSITION ]]; then - warning "Failed download: $bootstrap_tar - skipping due to transition" - elif [[ -n $STRICT ]]; then - fatal "Failed download: $bootstrap_tar" + info "$BOOTSTRAP_PATH exists, skipping download." else - warning "Failed download: $bootstrap_tar - building it locally instead" - rm -f "$bootstrap_tar" "$bootstrap_tar.hash" - build_bootstrap + info "Downloading $BOOTSTRAP_NAME" + mkdir -p "$(dirname "$BOOTSTRAP_PATH")" + cd "$(dirname "$BOOTSTRAP_PATH")" + bootstrap_tar="$BOOTSTRAP_NAME.tar.gz" + if wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar" \ + && wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar.hash"; then + if [[ -f "$BT_VERIFY" ]]; then + bootstrap_tar_path="$(dirname "$BOOTSTRAP_PATH")/$bootstrap_tar" + BT_DEBUG=$DEBUG $BT_VERIFY \ + --force-gpg "$bootstrap_tar_path" "$bootstrap_tar_path.hash" + else + fatal "$BT_VERIFY script not found, unable to verify downloaded files." + fi + info "Unpacking $BOOTSTRAP_NAME" + mkdir "$BOOTSTRAP_PATH" + tar -zxf "$BOOTSTRAP_NAME.tar.gz" -C "$BOOTSTRAP_PATH" + elif [[ -n $TRANSITION ]]; then + warning "Failed download: $bootstrap_tar - skipping due to transition" + elif [[ -n $STRICT ]]; then + fatal "Failed download: $bootstrap_tar" + else + warning "Failed download: $bootstrap_tar - building it locally instead" + rm -f "$bootstrap_tar" "$bootstrap_tar.hash" + build_bootstrap + fi fi -fi +} + +setup_bootstrap info "$(basename "$0") complete." From 877587f71297481c88478b6f6e3afcbb78c2b644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Fri, 25 Sep 2026 17:36:13 +0000 Subject: [PATCH 4/8] tkldev-setup: define the functions before the main body Pure move plus a guard: all functions now come first, and the file returns early when sourced instead of executed, so a test can load the functions without running the setup. Executed directly, the order of operations is unchanged. --- overlay/usr/local/sbin/tkldev-setup | 190 ++++++++++++++-------------- 1 file changed, 98 insertions(+), 92 deletions(-) diff --git a/overlay/usr/local/sbin/tkldev-setup b/overlay/usr/local/sbin/tkldev-setup index 7b330f6..726e7bb 100755 --- a/overlay/usr/local/sbin/tkldev-setup +++ b/overlay/usr/local/sbin/tkldev-setup @@ -64,50 +64,6 @@ EOF exit $exit_code } -# check for BT_DEBUG as well as bt scripts call tkldev-setup -if [[ -n "$DEBUG" ]] || [[ -n "$BT_DEBUG" ]]; then - set -x -fi -[[ $(id -u) -eq 0 ]] || fatal "Root user required." - -GIT_DEPTH=${GIT_DEPTH:-1} - -distro="$(lsb_release -si)" -if [[ "${distro,,}" == "turnkey" ]] || [[ "${distro,,}" == "debian" ]]; then - distro="debian" -fi -tkl_ver=$(turnkey-version -t) -unset FORCE STRICT TRANSITION - -export RELEASE="${RELEASE:-${distro}/$(lsb_release -s -c)}" -CODENAME="$(basename "$RELEASE")" -export CODENAME -export FAB_ARCH="${FAB_ARCH:-$(dpkg --print-architecture)}" -export BRANCH="${tkl_ver::2}.x" -IMAGES="http://mirror.turnkeylinux.org/turnkeylinux/images" -FAB_PATH="${FAB_PATH:-/turnkey/fab}" -BOOTSTRAP_NAME="bootstrap-${CODENAME}-${FAB_ARCH}" -BOOTSTRAP_PATH="$FAB_PATH/bootstraps/${CODENAME}-${FAB_ARCH}" -BT_PATH="${BT_PATH:-/turnkey/buildtasks}" -BT_VERIFY="$BT_PATH/bin/signature-verify" - -read -ra BT_REFS <<< "${BT_REFS:-$BRANCH ${BRANCH}-dev master}" -read -ra TKLBAM_REFS <<< "${TKLBAM_REFS:-$BRANCH ${BRANCH}-dev master}" -read -ra COMMON_REFS <<< "${COMMON_REFS:-$BRANCH ${BRANCH}-dev master}" -read -ra APP_REFS <<< "${APP_REFS:-$BRANCH ${BRANCH}-dev master}" -read -ra CDROOTS_REFS <<< "${CDROOTS_REFS:-$BRANCH ${BRANCH}-dev master}" - -GIT_REMOTE_URL="${GIT_REMOTE_URL:-https://github.com/turnkeylinux}" -GIT_REMOTE_NAME="${GIT_REMOTE_NAME:-origin}" - -# remove trailing '/' if needed -GIT_REMOTE_URL="${GIT_REMOTE_URL%/}" -# Append '-apps' to git remote URL for apps if default org is 'turnkeylinux' -APP_GIT_REMOTE_URL="$GIT_REMOTE_URL" -if [[ "$GIT_REMOTE_URL" =~ [:/]turnkeylinux ]]; then - APP_GIT_REMOTE_URL="$GIT_REMOTE_URL-apps" -fi - tkl_remote() { local dir=$1 local git_remote @@ -246,6 +202,104 @@ clone_or_update() { fi } +build_bootstrap() { + local src + src="$(dirname "$FAB_PATH")/bootstrap" + clone_or_update bootstrap "$src" master + info "Building $BOOTSTRAP_NAME locally (from $src)" + make -C "$src" clean + make -C "$src" install \ + FAB_PATH="$FAB_PATH" RELEASE="$RELEASE" FAB_ARCH="$FAB_ARCH" + if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then + fatal "Local build of $BOOTSTRAP_NAME did not create $BOOTSTRAP_PATH" + fi +} + +setup_bootstrap() { + if [[ -d "$BOOTSTRAP_PATH" ]]; then + if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then + fatal "$BOOTSTRAP_PATH exists, but does not appear to be a bootstrap" + fi + info "$BOOTSTRAP_PATH exists, skipping download." + else + info "Downloading $BOOTSTRAP_NAME" + mkdir -p "$(dirname "$BOOTSTRAP_PATH")" + cd "$(dirname "$BOOTSTRAP_PATH")" + bootstrap_tar="$BOOTSTRAP_NAME.tar.gz" + if wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar" \ + && wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar.hash"; then + if [[ -f "$BT_VERIFY" ]]; then + bootstrap_tar_path="$(dirname "$BOOTSTRAP_PATH")/$bootstrap_tar" + BT_DEBUG=$DEBUG $BT_VERIFY \ + --force-gpg "$bootstrap_tar_path" "$bootstrap_tar_path.hash" + else + fatal "$BT_VERIFY script not found, unable to verify downloaded files." + fi + info "Unpacking $BOOTSTRAP_NAME" + mkdir "$BOOTSTRAP_PATH" + tar -zxf "$BOOTSTRAP_NAME.tar.gz" -C "$BOOTSTRAP_PATH" + elif [[ -n $TRANSITION ]]; then + warning "Failed download: $bootstrap_tar - skipping due to transition" + elif [[ -n $STRICT ]]; then + fatal "Failed download: $bootstrap_tar" + else + warning "Failed download: $bootstrap_tar - building it locally instead" + rm -f "$bootstrap_tar" "$bootstrap_tar.hash" + build_bootstrap + fi + fi +} + +# When sourced (as the tests do) stop here: the functions above are defined +# and nothing below runs. +if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then + return 0 +fi + +# check for BT_DEBUG as well as bt scripts call tkldev-setup +if [[ -n "$DEBUG" ]] || [[ -n "$BT_DEBUG" ]]; then + set -x +fi +[[ $(id -u) -eq 0 ]] || fatal "Root user required." + +GIT_DEPTH=${GIT_DEPTH:-1} + +distro="$(lsb_release -si)" +if [[ "${distro,,}" == "turnkey" ]] || [[ "${distro,,}" == "debian" ]]; then + distro="debian" +fi +tkl_ver=$(turnkey-version -t) +unset FORCE STRICT TRANSITION + +export RELEASE="${RELEASE:-${distro}/$(lsb_release -s -c)}" +CODENAME="$(basename "$RELEASE")" +export CODENAME +export FAB_ARCH="${FAB_ARCH:-$(dpkg --print-architecture)}" +export BRANCH="${tkl_ver::2}.x" +IMAGES="http://mirror.turnkeylinux.org/turnkeylinux/images" +FAB_PATH="${FAB_PATH:-/turnkey/fab}" +BOOTSTRAP_NAME="bootstrap-${CODENAME}-${FAB_ARCH}" +BOOTSTRAP_PATH="$FAB_PATH/bootstraps/${CODENAME}-${FAB_ARCH}" +BT_PATH="${BT_PATH:-/turnkey/buildtasks}" +BT_VERIFY="$BT_PATH/bin/signature-verify" + +read -ra BT_REFS <<< "${BT_REFS:-$BRANCH ${BRANCH}-dev master}" +read -ra TKLBAM_REFS <<< "${TKLBAM_REFS:-$BRANCH ${BRANCH}-dev master}" +read -ra COMMON_REFS <<< "${COMMON_REFS:-$BRANCH ${BRANCH}-dev master}" +read -ra APP_REFS <<< "${APP_REFS:-$BRANCH ${BRANCH}-dev master}" +read -ra CDROOTS_REFS <<< "${CDROOTS_REFS:-$BRANCH ${BRANCH}-dev master}" + +GIT_REMOTE_URL="${GIT_REMOTE_URL:-https://github.com/turnkeylinux}" +GIT_REMOTE_NAME="${GIT_REMOTE_NAME:-origin}" + +# remove trailing '/' if needed +GIT_REMOTE_URL="${GIT_REMOTE_URL%/}" +# Append '-apps' to git remote URL for apps if default org is 'turnkeylinux' +APP_GIT_REMOTE_URL="$GIT_REMOTE_URL" +if [[ "$GIT_REMOTE_URL" =~ [:/]turnkeylinux ]]; then + APP_GIT_REMOTE_URL="$GIT_REMOTE_URL-apps" +fi + APPS=() while [[ "$#" -gt 0 ]]; do case $1 in @@ -299,53 +353,5 @@ apt-get update -qq info "Installing newer fab version (if available)" DEBIAN_FRONTEND=noninteractive apt-get install -y fab -build_bootstrap() { - local src - src="$(dirname "$FAB_PATH")/bootstrap" - clone_or_update bootstrap "$src" master - info "Building $BOOTSTRAP_NAME locally (from $src)" - make -C "$src" clean - make -C "$src" install \ - FAB_PATH="$FAB_PATH" RELEASE="$RELEASE" FAB_ARCH="$FAB_ARCH" - if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then - fatal "Local build of $BOOTSTRAP_NAME did not create $BOOTSTRAP_PATH" - fi -} - -setup_bootstrap() { - if [[ -d "$BOOTSTRAP_PATH" ]]; then - if [[ ! -f "$BOOTSTRAP_PATH/bin/bash" ]]; then - fatal "$BOOTSTRAP_PATH exists, but does not appear to be a bootstrap" - fi - info "$BOOTSTRAP_PATH exists, skipping download." - else - info "Downloading $BOOTSTRAP_NAME" - mkdir -p "$(dirname "$BOOTSTRAP_PATH")" - cd "$(dirname "$BOOTSTRAP_PATH")" - bootstrap_tar="$BOOTSTRAP_NAME.tar.gz" - if wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar" \ - && wget -nc -nv "$IMAGES/bootstrap/$bootstrap_tar.hash"; then - if [[ -f "$BT_VERIFY" ]]; then - bootstrap_tar_path="$(dirname "$BOOTSTRAP_PATH")/$bootstrap_tar" - BT_DEBUG=$DEBUG $BT_VERIFY \ - --force-gpg "$bootstrap_tar_path" "$bootstrap_tar_path.hash" - else - fatal "$BT_VERIFY script not found, unable to verify downloaded files." - fi - info "Unpacking $BOOTSTRAP_NAME" - mkdir "$BOOTSTRAP_PATH" - tar -zxf "$BOOTSTRAP_NAME.tar.gz" -C "$BOOTSTRAP_PATH" - elif [[ -n $TRANSITION ]]; then - warning "Failed download: $bootstrap_tar - skipping due to transition" - elif [[ -n $STRICT ]]; then - fatal "Failed download: $bootstrap_tar" - else - warning "Failed download: $bootstrap_tar - building it locally instead" - rm -f "$bootstrap_tar" "$bootstrap_tar.hash" - build_bootstrap - fi - fi -} - setup_bootstrap info "$(basename "$0") complete." From 486b6e38a920936a2e371eb104364177279ba052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Fri, 25 Sep 2026 17:41:55 +0000 Subject: [PATCH 5/8] tkldev-setup: make the tklbam-profiles path configurable TKLBAM_PATH (default /turnkey/tklbam-profiles) joins BT_PATH and FAB_PATH, so a run can be pointed at a scratch tree end to end. --- overlay/usr/local/sbin/tkldev-setup | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/overlay/usr/local/sbin/tkldev-setup b/overlay/usr/local/sbin/tkldev-setup index 726e7bb..92bcd15 100755 --- a/overlay/usr/local/sbin/tkldev-setup +++ b/overlay/usr/local/sbin/tkldev-setup @@ -35,7 +35,7 @@ branches will be skipped. Default git repos, branches and local paths are: remote repo branch local path ------------------------------------------------------ turnkeylinux/buildtasks .x $BT_PATH -turnkeylinux/tklbam-profiles master /turnkey/tklbam-profiles +turnkeylinux/tklbam-profiles master $TKLBAM_PATH turnkeylinux/cdroots .x $FAB_PATH/cdroots (amd64 only) turnkeylinux/common .x $FAB_PATH/common turnkeylinux-apps/APP master $FAB_PATH/products/APP @@ -45,6 +45,7 @@ Env vars: FAB_PATH Fab PATH (default: $FAB_PATH) FAB_ARCH Fab architecture (default: $FAB_ARCH) BT_PATH Buildtasks PATH (default: $BT_PATH) + TKLBAM_PATH tklbam-profiles PATH (default: $TKLBAM_PATH) RELEASE Release to target - e.g. debian/bookworm (default: $RELEASE) ARCH Arch to target (default: $ARCH) BRANCH Preferred series ref (default: $BRANCH) @@ -281,6 +282,7 @@ FAB_PATH="${FAB_PATH:-/turnkey/fab}" BOOTSTRAP_NAME="bootstrap-${CODENAME}-${FAB_ARCH}" BOOTSTRAP_PATH="$FAB_PATH/bootstraps/${CODENAME}-${FAB_ARCH}" BT_PATH="${BT_PATH:-/turnkey/buildtasks}" +TKLBAM_PATH="${TKLBAM_PATH:-/turnkey/tklbam-profiles}" BT_VERIFY="$BT_PATH/bin/signature-verify" read -ra BT_REFS <<< "${BT_REFS:-$BRANCH ${BRANCH}-dev master}" @@ -328,7 +330,7 @@ done [[ -n "${APPS[*]}" ]] || APPS=(core) clone_or_update buildtasks "$BT_PATH" "${BT_REFS[@]}" -clone_or_update tklbam-profiles /turnkey/tklbam-profiles "${TKLBAM_REFS[@]}" +clone_or_update tklbam-profiles "$TKLBAM_PATH" "${TKLBAM_REFS[@]}" clone_or_update common "$FAB_PATH/common" "${COMMON_REFS[@]}" for app in "${APPS[@]}"; do clone_or_update _APP_ "$app" "$FAB_PATH/products/$app" "${APP_REFS[@]}" From 104bd147a39ffb2e667133f2926c42e7ac9b6238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Fri, 25 Sep 2026 17:50:35 +0000 Subject: [PATCH 6/8] tkldev-setup: add a bats suite with PATH stubs 63 tests under tests/: option parsing and the main body run the whole script, resolve_ref, update_repo, clone_or_update, setup_bootstrap and build_bootstrap are sourced and called one at a time. git, wget, apt-get, make, lsb_release, turnkey-version, dpkg, id and signature-verify are stubs in tests/stubs that record their arguments; FAB_PATH, BT_PATH and TKLBAM_PATH point at a scratch directory. Run with: bats tests/ --- tests/stubs/apt-get | 3 + tests/stubs/dpkg | 4 + tests/stubs/git | 41 +++ tests/stubs/id | 4 + tests/stubs/lsb_release | 7 + tests/stubs/make | 19 ++ tests/stubs/signature-verify | 5 + tests/stubs/turnkey-version | 4 + tests/stubs/wget | 19 ++ tests/tkldev-setup.bats | 588 +++++++++++++++++++++++++++++++++++ 10 files changed, 694 insertions(+) create mode 100755 tests/stubs/apt-get create mode 100755 tests/stubs/dpkg create mode 100755 tests/stubs/git create mode 100755 tests/stubs/id create mode 100755 tests/stubs/lsb_release create mode 100755 tests/stubs/make create mode 100755 tests/stubs/signature-verify create mode 100755 tests/stubs/turnkey-version create mode 100755 tests/stubs/wget create mode 100644 tests/tkldev-setup.bats diff --git a/tests/stubs/apt-get b/tests/stubs/apt-get new file mode 100755 index 0000000..f8f83d5 --- /dev/null +++ b/tests/stubs/apt-get @@ -0,0 +1,3 @@ +#!/bin/bash +# Stand-in for apt-get: records the call only. +echo "apt-get $*" >> "${STUB_LOG:?}" diff --git a/tests/stubs/dpkg b/tests/stubs/dpkg new file mode 100755 index 0000000..882f31c --- /dev/null +++ b/tests/stubs/dpkg @@ -0,0 +1,4 @@ +#!/bin/bash +# Stand-in for dpkg --print-architecture: STUB_ARCH sets the answer. +echo "dpkg $*" >> "${STUB_LOG:?}" +echo "${STUB_ARCH:-amd64}" diff --git a/tests/stubs/git b/tests/stubs/git new file mode 100755 index 0000000..f82f1f8 --- /dev/null +++ b/tests/stubs/git @@ -0,0 +1,41 @@ +#!/bin/bash +# Stand-in for git used by the tests: records the call in STUB_LOG and +# answers from STUB_* variables instead of touching any repository. +# STUB_REFS refs that "exist" on the remote (ls-remote) +# STUB_GIT_STATUS output of "git status -s" (empty = clean) +# STUB_GIT_BRANCH output of "git branch --show-current" +# STUB_GIT_FAIL subcommands that fail: fetch pull clone checkout, or +# checkout: to fail one branch only +echo "git $*" >> "${STUB_LOG:?}" +if [[ $1 == -C ]]; then + shift 2 +fi +sub=$1 +shift +fails=" ${STUB_GIT_FAIL:-} " +case $sub in + ls-remote) + ref="${*: -1}" + [[ " ${STUB_REFS:-} " == *" $ref "* ]] + ;; + remote) + printf '%s\t%s (fetch)\n' origin https://github.com/turnkeylinux/repo.git + printf '%s\t%s (push)\n' origin https://github.com/turnkeylinux/repo.git + ;; + status) + [[ -z ${STUB_GIT_STATUS:-} ]] || echo "$STUB_GIT_STATUS" + ;; + branch) + echo "${STUB_GIT_BRANCH:-19.x}" + ;; + checkout) + [[ $fails != *" checkout "* && $fails != *" checkout:$1 "* ]] + ;; + clone) + [[ $fails != *" clone "* ]] || exit 128 + mkdir -p "${*: -1}/.git" + ;; + fetch|pull) + [[ $fails != *" $sub "* ]] + ;; +esac diff --git a/tests/stubs/id b/tests/stubs/id new file mode 100755 index 0000000..a41d456 --- /dev/null +++ b/tests/stubs/id @@ -0,0 +1,4 @@ +#!/bin/bash +# Stand-in for id -u: STUB_UID sets the answer (default root). +echo "id $*" >> "${STUB_LOG:?}" +echo "${STUB_UID:-0}" diff --git a/tests/stubs/lsb_release b/tests/stubs/lsb_release new file mode 100755 index 0000000..0bdca71 --- /dev/null +++ b/tests/stubs/lsb_release @@ -0,0 +1,7 @@ +#!/bin/bash +# Stand-in for lsb_release: STUB_DISTRO and STUB_CODENAME set the answers. +echo "lsb_release $*" >> "${STUB_LOG:?}" +case " $* " in + *" -si "*) echo "${STUB_DISTRO:-TurnKey}" ;; + *" -c "*) echo "${STUB_CODENAME:-trixie}" ;; +esac diff --git a/tests/stubs/make b/tests/stubs/make new file mode 100755 index 0000000..a7f1cf5 --- /dev/null +++ b/tests/stubs/make @@ -0,0 +1,19 @@ +#!/bin/bash +# Stand-in for make: records the call and, on "install", creates the +# bootstrap where the FAB_PATH/RELEASE/FAB_ARCH arguments point, unless +# STUB_MAKE_NO_BOOTSTRAP is set. +echo "make $*" >> "${STUB_LOG:?}" +fab_path= release= arch= install= +for arg in "$@"; do + case $arg in + FAB_PATH=*) fab_path="${arg#*=}" ;; + RELEASE=*) release="${arg#*=}" ;; + FAB_ARCH=*) arch="${arg#*=}" ;; + install) install=y ;; + esac +done +if [[ -n $install && -z ${STUB_MAKE_NO_BOOTSTRAP:-} ]]; then + dst="$fab_path/bootstraps/$(basename "$release")-$arch" + mkdir -p "$dst/bin" + echo '#!/bin/sh' > "$dst/bin/bash" +fi diff --git a/tests/stubs/signature-verify b/tests/stubs/signature-verify new file mode 100755 index 0000000..9cbc96f --- /dev/null +++ b/tests/stubs/signature-verify @@ -0,0 +1,5 @@ +#!/bin/bash +# Stand-in for buildtasks/bin/signature-verify: STUB_VERIFY_RC is its exit +# code. The test installs it under BT_PATH/bin. +echo "signature-verify $*" >> "${STUB_LOG:?}" +exit "${STUB_VERIFY_RC:-0}" diff --git a/tests/stubs/turnkey-version b/tests/stubs/turnkey-version new file mode 100755 index 0000000..3837a5d --- /dev/null +++ b/tests/stubs/turnkey-version @@ -0,0 +1,4 @@ +#!/bin/bash +# Stand-in for turnkey-version: STUB_TKL_VERSION sets the answer. +echo "turnkey-version $*" >> "${STUB_LOG:?}" +echo "${STUB_TKL_VERSION:-19.0}" diff --git a/tests/stubs/wget b/tests/stubs/wget new file mode 100755 index 0000000..62e5b71 --- /dev/null +++ b/tests/stubs/wget @@ -0,0 +1,19 @@ +#!/bin/bash +# Stand-in for wget: records the call and creates the requested file in the +# current directory (a real tarball with bin/bash for *.tar.gz). +# STUB_WGET_FAIL "all" fails every download, "hash" only the *.hash one +echo "wget $*" >> "${STUB_LOG:?}" +url="${*: -1}" +name="$(basename "$url")" +fails=" ${STUB_WGET_FAIL:-} " +[[ $fails != *" all "* ]] || exit 8 +if [[ $name == *.hash ]]; then + [[ $fails != *" hash "* ]] || exit 8 + echo "stub hash for $name" > "$name" + exit 0 +fi +work="$(mktemp -d)" +mkdir -p "$work/bin" +echo '#!/bin/sh' > "$work/bin/bash" +tar -C "$work" -zcf "$name" bin +rm -rf "$work" diff --git a/tests/tkldev-setup.bats b/tests/tkldev-setup.bats new file mode 100644 index 0000000..70395d2 --- /dev/null +++ b/tests/tkldev-setup.bats @@ -0,0 +1,588 @@ +#!/usr/bin/env bats +# Tests for overlay/usr/local/sbin/tkldev-setup. +# +# Every external command the script runs (git, wget, apt-get, make, +# lsb_release, turnkey-version, dpkg, id, signature-verify) is replaced by a +# stub from tests/stubs that records its arguments in STUB_LOG, and every +# path the script writes is under a scratch directory. Nothing here touches +# the live system or the network. +# +# Two kinds of tests: "run_setup" executes the whole script (option parsing +# and the main body), "call" sources it inside a fresh "bash -e" and runs one +# function, as the script itself would. + +setup() { + TESTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" && pwd)" + SCRIPT="$TESTS_DIR/../overlay/usr/local/sbin/tkldev-setup" + SCRATCH="$BATS_TEST_TMPDIR" + + export PATH="$TESTS_DIR/stubs:$PATH" + export STUB_LOG="$SCRATCH/calls.log" + : > "$STUB_LOG" + export STUB_REFS="19.x master" + export STUB_GIT_BRANCH="19.x" + unset STUB_GIT_STATUS STUB_GIT_FAIL STUB_WGET_FAIL STUB_MAKE_NO_BOOTSTRAP + unset STUB_DISTRO STUB_CODENAME STUB_TKL_VERSION STUB_ARCH STUB_UID + unset STUB_VERIFY_RC + + export FAB_PATH="$SCRATCH/fab" + export BT_PATH="$SCRATCH/buildtasks" + export TKLBAM_PATH="$SCRATCH/tklbam-profiles" + unset RELEASE FAB_ARCH BRANCH GIT_DEPTH GIT_REMOTE_URL GIT_REMOTE_NAME + unset BT_REFS TKLBAM_REFS COMMON_REFS APP_REFS CDROOTS_REFS + unset DEBUG BT_DEBUG STRICT FORCE TRANSITION + + # buildtasks checked out, with the example config and the verify script + make_git_dir "$BT_PATH" + mkdir -p "$BT_PATH/config.example" "$BT_PATH/bin" + echo 'BT_GPGKEY=DEADBEEF' > "$BT_PATH/config.example/common.cfg" + cp "$TESTS_DIR/stubs/signature-verify" "$BT_PATH/bin/signature-verify" + + # variables the functions read when called on their own + export RELEASE_UNDER_TEST="debian/trixie" + export BOOTSTRAP_NAME="bootstrap-trixie-amd64" + export BOOTSTRAP_PATH="$FAB_PATH/bootstraps/trixie-amd64" + export BT_VERIFY="$BT_PATH/bin/signature-verify" + export IMAGES="http://mirror.turnkeylinux.org/turnkeylinux/images" + export GIT_REMOTE_URL="https://github.com/turnkeylinux" + export APP_GIT_REMOTE_URL="https://github.com/turnkeylinux-apps" + export GIT_DEPTH=1 +} + +make_git_dir() { + mkdir -p "$1/.git" +} + +make_bootstrap() { + mkdir -p "$1/bin" + echo '#!/bin/sh' > "$1/bin/bash" +} + +# run the whole script +run_setup() { + run "$SCRIPT" "$@" +} + +# run one function of the script in a fresh bash -e, as the script does +call() { + run bash -e -c 'source "$1"; shift; "$@"' tkldev-setup-test "$SCRIPT" "$@" +} + +logged() { + grep -qF -- "$1" "$STUB_LOG" +} + +not_logged() { + ! grep -qF -- "$1" "$STUB_LOG" +} + +output_has() { + [[ "$output" == *"$1"* ]] +} + +# --- options ---------------------------------------------------------------- + +@test "-h prints the usage and exits 0" { + run_setup -h + [ "$status" -eq 0 ] + output_has "Tool to initialize and maintain TKLDev" + output_has "FAB_PATH Fab PATH (default: $FAB_PATH)" + output_has "TKLBAM_PATH tklbam-profiles PATH (default: $TKLBAM_PATH)" + not_logged "git clone" +} + +@test "--help is the same as -h" { + run_setup --help + [ "$status" -eq 0 ] + output_has "Options:" + not_logged "git clone" +} + +@test "without arguments core is the only appliance" { + run_setup + [ "$status" -eq 0 ] + logged "git clone --branch 19.x --depth 1 https://github.com/turnkeylinux-apps/core.git $FAB_PATH/products/core" + logged "https://github.com/turnkeylinux/tklbam-profiles.git $TKLBAM_PATH" + logged "https://github.com/turnkeylinux/common.git $FAB_PATH/common" + logged "https://github.com/turnkeylinux/cdroots.git $FAB_PATH/cdroots" + [ "$(grep -c 'products/' "$STUB_LOG")" -eq 1 ] +} + +@test "every argument is processed as an appliance" { + run_setup foo bar + [ "$status" -eq 0 ] + logged "turnkeylinux-apps/foo.git $FAB_PATH/products/foo" + logged "turnkeylinux-apps/bar.git $FAB_PATH/products/bar" + not_logged "products/core" +} + +@test "an unknown option is taken as an appliance name" { + run_setup --bogus + [ "$status" -eq 0 ] + logged "turnkeylinux-apps/--bogus.git $FAB_PATH/products/--bogus" +} + +@test "clones are shallow by default" { + run_setup + [ "$status" -eq 0 ] + logged "git clone --branch 19.x --depth 1 https://github.com/turnkeylinux/common.git" +} + +@test "-n clones without --depth" { + run_setup -n + [ "$status" -eq 0 ] + logged "git clone --branch 19.x https://github.com/turnkeylinux/common.git" + not_logged "--depth" +} + +@test "--full-clone is the same as -n" { + run_setup --full-clone + [ "$status" -eq 0 ] + not_logged "--depth" +} + +@test "a dirty repo is skipped with a warning by default" { + export STUB_GIT_STATUS=" M bin/signature-verify" + run_setup + [ "$status" -eq 0 ] + output_has "WARNING [tkldev-setup]: $BT_PATH has uncommitted files, skipping." +} + +@test "-s fails on a dirty repo instead of skipping" { + export STUB_GIT_STATUS=" M bin/signature-verify" + run_setup -s + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup]: $BT_PATH has uncommitted files" + not_logged "git clone" +} + +@test "--strict is the same as -s" { + export STUB_GIT_STATUS=" M bin/signature-verify" + run_setup --strict + [ "$status" -eq 1 ] +} + +@test "-f checks out the resolved branch when another one is checked out" { + export STUB_GIT_BRANCH=master + run_setup -f + [ "$status" -eq 0 ] + output_has "Force used - attempting to checkout 19.x" + logged "git -C $BT_PATH checkout 19.x" + logged "git -C $BT_PATH pull origin 19.x" +} + +@test "--force is the same as -f" { + export STUB_GIT_BRANCH=master + run_setup --force + [ "$status" -eq 0 ] + logged "git -C $BT_PATH checkout 19.x" +} + +@test "-t skips a bootstrap that cannot be downloaded" { + export STUB_WGET_FAIL=all + run_setup -t + [ "$status" -eq 0 ] + output_has "Failed download: bootstrap-trixie-amd64.tar.gz - skipping due to transition" + not_logged "make" +} + +@test "--transition is the same as -t" { + export STUB_WGET_FAIL=all + run_setup --transition + [ "$status" -eq 0 ] + not_logged "make" +} + +# --- main body -------------------------------------------------------------- + +@test "fails when not run as root" { + export STUB_UID=1000 + run_setup + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup]: Root user required." + not_logged "lsb_release" +} + +@test "DEBUG and BT_DEBUG turn on tracing" { + DEBUG=1 run_setup + [ "$status" -eq 0 ] + BT_DEBUG=1 run_setup + [ "$status" -eq 0 ] +} + +@test "release and architecture come from the system" { + export STUB_WGET_FAIL=all + run_setup + [ "$status" -eq 0 ] + logged "wget -nc -nv $IMAGES/bootstrap/bootstrap-trixie-amd64.tar.gz" + logged "make -C $SCRATCH/bootstrap install FAB_PATH=$FAB_PATH RELEASE=debian/trixie FAB_ARCH=amd64" +} + +@test "a distro other than TurnKey or Debian keeps its own name" { + export STUB_DISTRO=Ubuntu STUB_CODENAME=noble STUB_WGET_FAIL=all + run_setup + [ "$status" -eq 0 ] + logged "RELEASE=Ubuntu/noble FAB_ARCH=amd64" + logged "bootstrap-noble-amd64.tar.gz" +} + +@test "the series branch comes from turnkey-version" { + export STUB_TKL_VERSION=18.2 STUB_REFS="18.x" + run_setup + [ "$status" -eq 0 ] + logged "git clone --branch 18.x --depth 1 https://github.com/turnkeylinux/common.git" +} + +@test "on arm64 cdroots is not cloned" { + export STUB_ARCH=arm64 + run_setup + [ "$status" -eq 0 ] + not_logged "cdroots" + logged "bootstrap-trixie-arm64.tar.gz" +} + +@test "the example config is copied when config is missing" { + run_setup + [ "$status" -eq 0 ] + [ -f "$BT_PATH/config/common.cfg" ] +} + +@test "an existing config is kept" { + mkdir -p "$BT_PATH/config" + echo 'BT_GPGKEY=CAFEBABE' > "$BT_PATH/config/common.cfg" + run_setup + [ "$status" -eq 0 ] + grep -q CAFEBABE "$BT_PATH/config/common.cfg" +} + +@test "fails when config/common.cfg is missing" { + mkdir -p "$BT_PATH/config" + run_setup + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup]: $BT_PATH config not found" + not_logged "apt-get" +} + +@test "a remote outside turnkeylinux is used unchanged for appliances" { + export GIT_REMOTE_URL="https://[2001:db8::1]/mirror/" + run_setup + [ "$status" -eq 0 ] + logged "https://[2001:db8::1]/mirror/common.git" + logged "https://[2001:db8::1]/mirror/core.git" + not_logged "mirror//" + not_logged "mirror-apps" +} + +@test "apt is updated and fab installed" { + run_setup + [ "$status" -eq 0 ] + logged "apt-get update -qq" + logged "apt-get install -y fab" + output_has "INFO [tkldev-setup]: tkldev-setup complete." +} + +# --- resolve_ref ------------------------------------------------------------ + +@test "resolve_ref returns the first candidate that exists on the remote" { + export STUB_REFS="19.x-dev master" + call resolve_ref "$GIT_REMOTE_URL/common" 19.x 19.x-dev master + [ "$status" -eq 0 ] + [ "$output" = "19.x-dev" ] + logged "git ls-remote --exit-code --heads $GIT_REMOTE_URL/common 19.x" + logged "git ls-remote --exit-code --heads $GIT_REMOTE_URL/common 19.x-dev" + not_logged "common master" +} + +@test "resolve_ref skips empty candidates and fails when none exists" { + export STUB_REFS="" + call resolve_ref "$GIT_REMOTE_URL/common" "" 19.x + [ "$status" -eq 1 ] + [ -z "$output" ] + not_logged "common $" + logged "common 19.x" +} + +# --- update_repo ------------------------------------------------------------ + +@test "update_repo fetches and pulls a clean repo on the right branch" { + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + logged "git -C $BT_PATH remote --verbose" + logged "git -C $BT_PATH fetch origin" + logged "git -C $BT_PATH pull origin 19.x" + not_logged "checkout" +} + +@test "update_repo skips a dirty repo with a warning" { + export STUB_GIT_STATUS="?? new-file" + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + output_has "WARNING [tkldev-setup-test]: $BT_PATH has uncommitted files, skipping." + not_logged "fetch" +} + +@test "update_repo fails on a dirty repo with --strict" { + export STUB_GIT_STATUS="?? new-file" STRICT=true + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: $BT_PATH has uncommitted files" +} + +@test "update_repo skips when the fetch fails" { + export STUB_GIT_FAIL=fetch + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + output_has "($BT_PATH) Fetching remote: origin failed, skipping." + not_logged "pull" +} + +@test "update_repo fails when the fetch fails with --strict" { + export STUB_GIT_FAIL=fetch STRICT=true + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: ($BT_PATH) Fetching remote: origin failed" +} + +@test "update_repo uses the current branch when none is given" { + export STUB_GIT_BRANCH=master + call update_repo "$BT_PATH" "" + [ "$status" -eq 0 ] + logged "git -C $BT_PATH pull origin master" + not_logged "checkout" +} + +@test "update_repo skips a repo on an alternate branch" { + export STUB_GIT_BRANCH=my-feature + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + output_has "($BT_PATH) checked out branch is my-feature (not 19.x) so skipping" + not_logged "pull" +} + +@test "update_repo fails on an alternate branch with --strict" { + export STUB_GIT_BRANCH=my-feature STRICT=true + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: ($BT_PATH) checked out branch is my-feature (not 19.x)" +} + +@test "update_repo checks out the branch with --force" { + export STUB_GIT_BRANCH=my-feature FORCE=y + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + logged "git -C $BT_PATH checkout 19.x" + not_logged "checkout 19.x-dev" + logged "git -C $BT_PATH pull origin 19.x" +} + +@test "update_repo with --force falls back to the -dev branch" { + export STUB_GIT_BRANCH=my-feature FORCE=y STUB_GIT_FAIL="checkout:19.x" + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + logged "git -C $BT_PATH checkout 19.x-dev" + logged "git -C $BT_PATH pull origin 19.x" +} + +@test "update_repo with --force skips when neither checkout works" { + export STUB_GIT_BRANCH=my-feature FORCE=y STUB_GIT_FAIL=checkout + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + output_has "($BT_PATH) Checking out '19.x' failed, skipping." + not_logged "pull" +} + +@test "update_repo moves from the series dev branch to the release branch" { + export STUB_GIT_BRANCH=19.x-dev + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + output_has "current checked out branch is 19.x-dev attempting to checkout 19.x" + logged "git -C $BT_PATH checkout 19.x" + logged "git -C $BT_PATH pull origin 19.x" +} + +@test "update_repo skips when leaving the dev branch fails" { + export STUB_GIT_BRANCH=19.x-dev STUB_GIT_FAIL=checkout + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + output_has "($BT_PATH) checkout failed - skipping" + not_logged "pull" +} + +@test "update_repo fails when leaving the dev branch fails with --strict" { + export STUB_GIT_BRANCH=19.x-dev STUB_GIT_FAIL=checkout STRICT=true + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: ($BT_PATH) checkout failed" +} + +@test "update_repo pulls the requested branch from another series without checkout" { + export STUB_GIT_BRANCH=18.x + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + not_logged "checkout" + logged "git -C $BT_PATH pull origin 19.x" +} + +@test "update_repo skips when the pull fails" { + export STUB_GIT_FAIL=pull + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 0 ] + output_has "($BT_PATH) Updating '19.x' failed, skipping." +} + +@test "update_repo fails when the pull fails with --strict" { + export STUB_GIT_FAIL=pull STRICT=true + call update_repo "$BT_PATH" 19.x + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: ($BT_PATH) Updating '19.x' failed" +} + +# --- clone_or_update -------------------------------------------------------- + +@test "clone_or_update updates an existing git checkout" { + call clone_or_update buildtasks "$BT_PATH" 19.x master + [ "$status" -eq 0 ] + output_has "INFO [tkldev-setup-test]: Resolved buildtasks to ref: 19.x" + output_has "$BT_PATH exists and is a git repo, attempting update." + logged "git -C $BT_PATH pull origin 19.x" + not_logged "clone" +} + +@test "clone_or_update fails when the destination is not a git repo" { + mkdir -p "$SCRATCH/plain" + call clone_or_update common "$SCRATCH/plain" 19.x + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: $SCRATCH/plain exists, but is not a git repo." +} + +@test "clone_or_update clones a missing destination with the resolved ref and depth" { + call clone_or_update common "$FAB_PATH/common" 19.x master + [ "$status" -eq 0 ] + output_has "Attempting to clone $GIT_REMOTE_URL/common into $FAB_PATH/common." + logged "git clone --branch 19.x --depth 1 $GIT_REMOTE_URL/common.git $FAB_PATH/common" + [ -d "$FAB_PATH/common/.git" ] +} + +@test "clone_or_update clones fully when GIT_DEPTH is full" { + export GIT_DEPTH=full + call clone_or_update common "$FAB_PATH/common" 19.x + [ "$status" -eq 0 ] + logged "git clone --branch 19.x $GIT_REMOTE_URL/common.git $FAB_PATH/common" +} + +@test "clone_or_update falls back to the default branch when no ref resolves" { + export STUB_REFS="" + call clone_or_update common "$FAB_PATH/common" 19.x master + [ "$status" -eq 0 ] + output_has "Could not resolve preferred refs for common; falling back to repo default branch." + logged "git clone --depth 1 $GIT_REMOTE_URL/common.git $FAB_PATH/common" +} + +@test "clone_or_update fails when no ref resolves with --strict" { + export STUB_REFS="" STRICT=true + call clone_or_update common "$FAB_PATH/common" 19.x master + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: Could not resolve preferred refs for common" + not_logged "clone" +} + +@test "clone_or_update uses the apps remote for _APP_" { + call clone_or_update _APP_ core "$FAB_PATH/products/core" 19.x + [ "$status" -eq 0 ] + logged "git clone --branch 19.x --depth 1 $APP_GIT_REMOTE_URL/core.git $FAB_PATH/products/core" +} + +@test "clone_or_update fails when the clone fails" { + export STUB_GIT_FAIL=clone + call clone_or_update common "$FAB_PATH/common" 19.x + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: Cloning $FAB_PATH/common failed." +} + +# --- setup_bootstrap and build_bootstrap ------------------------------------- + +@test "setup_bootstrap skips the download when the bootstrap exists" { + make_bootstrap "$BOOTSTRAP_PATH" + call setup_bootstrap + [ "$status" -eq 0 ] + output_has "$BOOTSTRAP_PATH exists, skipping download." + not_logged "wget" +} + +@test "setup_bootstrap fails when the directory is not a bootstrap" { + mkdir -p "$BOOTSTRAP_PATH" + call setup_bootstrap + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: $BOOTSTRAP_PATH exists, but does not appear to be a bootstrap" +} + +@test "setup_bootstrap downloads, verifies and unpacks the bootstrap" { + call setup_bootstrap + [ "$status" -eq 0 ] + output_has "INFO [tkldev-setup-test]: Downloading bootstrap-trixie-amd64" + logged "wget -nc -nv $IMAGES/bootstrap/bootstrap-trixie-amd64.tar.gz" + logged "wget -nc -nv $IMAGES/bootstrap/bootstrap-trixie-amd64.tar.gz.hash" + logged "signature-verify --force-gpg $FAB_PATH/bootstraps/bootstrap-trixie-amd64.tar.gz $FAB_PATH/bootstraps/bootstrap-trixie-amd64.tar.gz.hash" + output_has "Unpacking bootstrap-trixie-amd64" + [ -f "$BOOTSTRAP_PATH/bin/bash" ] + not_logged "make" +} + +@test "setup_bootstrap fails when the verify script is missing" { + export BT_VERIFY="$SCRATCH/no-such-verify" + call setup_bootstrap + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: $BT_VERIFY script not found, unable to verify downloaded files." + [ ! -d "$BOOTSTRAP_PATH" ] +} + +@test "setup_bootstrap stops when verification fails" { + export STUB_VERIFY_RC=3 + call setup_bootstrap + [ "$status" -eq 3 ] + [[ "$output" != *"Unpacking"* ]] + [ ! -d "$BOOTSTRAP_PATH" ] +} + +@test "setup_bootstrap builds locally when the download fails" { + export STUB_WGET_FAIL=all RELEASE="$RELEASE_UNDER_TEST" FAB_ARCH=amd64 + call setup_bootstrap + [ "$status" -eq 0 ] + output_has "WARNING [tkldev-setup-test]: Failed download: bootstrap-trixie-amd64.tar.gz - building it locally instead" + logged "git clone --branch master --depth 1 $GIT_REMOTE_URL/bootstrap.git $SCRATCH/bootstrap" + output_has "Building bootstrap-trixie-amd64 locally (from $SCRATCH/bootstrap)" + logged "make -C $SCRATCH/bootstrap clean" + logged "make -C $SCRATCH/bootstrap install FAB_PATH=$FAB_PATH RELEASE=debian/trixie FAB_ARCH=amd64" + [ -f "$BOOTSTRAP_PATH/bin/bash" ] +} + +@test "setup_bootstrap removes a partial download before building" { + export STUB_WGET_FAIL=hash RELEASE="$RELEASE_UNDER_TEST" FAB_ARCH=amd64 + call setup_bootstrap + [ "$status" -eq 0 ] + [ ! -e "$FAB_PATH/bootstraps/bootstrap-trixie-amd64.tar.gz" ] + logged "make -C $SCRATCH/bootstrap install" +} + +@test "setup_bootstrap fails on a missing download with --strict" { + export STUB_WGET_FAIL=all STRICT=true + call setup_bootstrap + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: Failed download: bootstrap-trixie-amd64.tar.gz" + not_logged "make" +} + +@test "setup_bootstrap skips a missing download with --transition, even with --strict" { + export STUB_WGET_FAIL=all STRICT=true TRANSITION=true + call setup_bootstrap + [ "$status" -eq 0 ] + output_has "WARNING [tkldev-setup-test]: Failed download: bootstrap-trixie-amd64.tar.gz - skipping due to transition" + not_logged "make" +} + +@test "build_bootstrap fails when the local build produces no bootstrap" { + export STUB_MAKE_NO_BOOTSTRAP=1 RELEASE="$RELEASE_UNDER_TEST" FAB_ARCH=amd64 + call build_bootstrap + [ "$status" -eq 1 ] + output_has "FATAL [tkldev-setup-test]: Local build of bootstrap-trixie-amd64 did not create $BOOTSTRAP_PATH" +} From fdf4515cd84df553b3a4a24914bb830e7429e0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Fri, 25 Sep 2026 17:50:35 +0000 Subject: [PATCH 7/8] tests: add coverage.sh (kcov line coverage, threshold 95) Runs the bats suite under kcov and fails below the threshold for tkldev-setup. Measured on a TKLDev 19 host: 99.46 percent (185 of 186 lines); the remaining line is the continuation of a two-line warning call, which bash reports on its first line only. --- tests/coverage.sh | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 tests/coverage.sh diff --git a/tests/coverage.sh b/tests/coverage.sh new file mode 100755 index 0000000..2b25d76 --- /dev/null +++ b/tests/coverage.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Line coverage of overlay/usr/local/sbin/tkldev-setup under the bats suite +# in this directory, measured with kcov. Exits 1 when the covered share of +# the script is below the threshold (default 95), 2 when a tool is missing. +# +# tests/coverage.sh [THRESHOLD] +# +# COVERAGE_DIR keeps the kcov report (default: a temporary directory). +# Needs the Debian packages kcov and bats. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +target="$(cd "$here/.." && pwd)/overlay/usr/local/sbin/tkldev-setup" +threshold="${1:-95}" + +for tool in kcov bats; do + if ! command -v "$tool" >/dev/null; then + echo "$tool not found (apt-get install $tool)" >&2 + exit 2 + fi +done + +report="${COVERAGE_DIR:-$(mktemp -d)}" +kcov --include-path="$target" "$report" bats "$here" + +# kcov names the sub directory after the command; with --include-path the +# report holds one file, so its first entry is ours +json="$(ls -t "$report"/*/coverage.json | grep -v /kcov-merged/ | head -1)" +percent="$(grep -o '"percent_covered": "[0-9.]*"' "$json" | head -1 | grep -o '[0-9.]*')" +covered="$(grep -o '"covered_lines": "[0-9]*"' "$json" | head -1 | grep -o '[0-9]*')" +total="$(grep -o '"total_lines": "[0-9]*"' "$json" | head -1 | grep -o '[0-9]*')" + +echo "tkldev-setup: $percent percent ($covered of $total lines) covered, threshold $threshold" +if ! awk -v p="$percent" -v t="$threshold" 'BEGIN { exit !(p + 0 >= t + 0) }'; then + echo "coverage below threshold (report: $report)" >&2 + exit 1 +fi From 3824e627da2302f4b9f96ada033c836c8225565a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Sat, 26 Sep 2026 09:10:23 +0000 Subject: [PATCH 8/8] tests/coverage.sh: read COVERAGE_THRESHOLD when no argument is given The reusable workflow test-shell.yml exports the caller's threshold as COVERAGE_THRESHOLD and runs the script without arguments, so the script enforced its own default (95) whatever the workflow said. The positional argument still wins, the environment variable comes next, 95 remains the fallback. --- tests/coverage.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/coverage.sh b/tests/coverage.sh index 2b25d76..a6a49d6 100755 --- a/tests/coverage.sh +++ b/tests/coverage.sh @@ -3,7 +3,7 @@ # in this directory, measured with kcov. Exits 1 when the covered share of # the script is below the threshold (default 95), 2 when a tool is missing. # -# tests/coverage.sh [THRESHOLD] +# tests/coverage.sh [THRESHOLD] (or COVERAGE_THRESHOLD in the environment) # # COVERAGE_DIR keeps the kcov report (default: a temporary directory). # Needs the Debian packages kcov and bats. @@ -11,7 +11,7 @@ set -euo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" target="$(cd "$here/.." && pwd)/overlay/usr/local/sbin/tkldev-setup" -threshold="${1:-95}" +threshold="${1:-${COVERAGE_THRESHOLD:-95}}" for tool in kcov bats; do if ! command -v "$tool" >/dev/null; then