From cef23496a0fe98631b09bc179708510c0b1f8f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Mon, 28 Sep 2026 04:33:24 +0000 Subject: [PATCH 1/5] fix: name Keel as the dpkg vendor /etc/dpkg/origins/default resolved to a TurnKey file, so dpkg-vendor answered TurnKey and every bug reporting tool on the appliance addressed turnkeylinux/tracker. Closes the vendor half of #6. The overlay now ships a Keel origin file and no TurnKey one, and the conf script points default at it. The Keel file keeps Parent: Debian, so Dpkg::Vendor resolves the same vendor object dpkg-dev used before and package building is unaffected. tests/dpkg-vendor.bats takes every verdict from the real dpkg-vendor, pointed at the tree the script produced through dpkg's own DPKG_ORIGINS_DIR, rather than reading back the symlink the script wrote: --query Vendor, --query Bugs, --is, --derives-from. 15 bats, the script at 100 percent of 6 lines under kcov, covering both refusals. --- conf/turnkey.d/dpkg-vendor | 28 ++- .../dpkg-vendor/etc/dpkg/origins/Keel | 4 + .../dpkg-vendor/etc/dpkg/origins/TurnKey | 4 - tests/dpkg-vendor.bats | 160 ++++++++++++++++++ 4 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/Keel delete mode 100644 overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/TurnKey create mode 100644 tests/dpkg-vendor.bats diff --git a/conf/turnkey.d/dpkg-vendor b/conf/turnkey.d/dpkg-vendor index 559fb87d..fd94f1ac 100755 --- a/conf/turnkey.d/dpkg-vendor +++ b/conf/turnkey.d/dpkg-vendor @@ -1,7 +1,27 @@ #!/bin/bash -e # -# Creates required symlink so dpkg-vendor --query vendor -# returns the correct string +# Makes a vendor query answer Keel. +# +# dpkg reads the vendor of the running system from the 'default' entry of the +# origins directory (Dpkg::Vendor), which has to resolve to one of the files +# beside it. The Keel file itself is shipped by the matching overlay, +# overlays/turnkey.d/dpkg-vendor. Everything a vendor query answers comes out +# of the file this points at: the name dpkg-vendor reports, and the tracker +# that bug reporting tools address. It used to point at TurnKey, so both +# named TurnKey (Keel-Linux/common#6). +# +# DPKG_ORIGINS_DIR is dpkg's own override of that directory, honoured here so +# tests/dpkg-vendor.bats can arrange a tree and then ask the real dpkg-vendor +# what it makes of it, rather than reading back the link this wrote. A build +# leaves it unset and the directory is /etc/dpkg/origins. + +fatal() { echo "'$(basename "$0")' Error: $*" >&2; exit 1; } + +origins="${DPKG_ORIGINS_DIR:-/etc/dpkg/origins}" +vendor="Keel" + +[ -d "$origins" ] || fatal "the dpkg origins directory '$origins' does not exist" +[ -f "$origins/$vendor" ] || fatal "'$origins/$vendor' is missing - it is shipped by overlays/turnkey.d/dpkg-vendor" -rm -rf /etc/dpkg/origins/default -ln -s /etc/dpkg/origins/TurnKey /etc/dpkg/origins/default +rm -rf "$origins/default" +ln -s "$origins/$vendor" "$origins/default" diff --git a/overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/Keel b/overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/Keel new file mode 100644 index 00000000..2536efd5 --- /dev/null +++ b/overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/Keel @@ -0,0 +1,4 @@ +Vendor: Keel +Vendor-URL: https://keellinux.org/ +Bugs: https://github.com/Keel-Linux/tracker/issues +Parent: Debian diff --git a/overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/TurnKey b/overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/TurnKey deleted file mode 100644 index 67c4695f..00000000 --- a/overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/TurnKey +++ /dev/null @@ -1,4 +0,0 @@ -Vendor: TurnKey -Vendor-URL: https://www.turnkeylinux.org/ -Bugs: https://github.com/turnkeylinux/tracker/issues -Parent: Debian diff --git a/tests/dpkg-vendor.bats b/tests/dpkg-vendor.bats new file mode 100644 index 00000000..57716b78 --- /dev/null +++ b/tests/dpkg-vendor.bats @@ -0,0 +1,160 @@ +#!/usr/bin/env bats +# Tests for conf/turnkey.d/dpkg-vendor and the origin file it selects, +# overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins/Keel. +# +# No verdict here reads back the file the conf script wrote (docs/traps.md, +# "Asserting the configuration is not asserting the behaviour"). Every one is +# the answer the real dpkg-vendor gives when it is pointed at the tree the +# script produced, through dpkg's own DPKG_ORIGINS_DIR (Dpkg::Vendor). What is +# asserted is therefore what a bug reporting tool, dpkg-buildpackage or +# anything else asking "who is the vendor of this machine" is told. +# +# Refutations are written "run ! cmd", never a bare "! cmd": bash does not +# apply errexit to a negated command, so a bare one asserts nothing. + +bats_require_minimum_version 1.5.0 + +setup() { + TESTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" && pwd)" + REPO="$(cd "$TESTS_DIR/.." && pwd)" + SCRIPT="$REPO/conf/turnkey.d/dpkg-vendor" + SHIPPED="$REPO/overlays/turnkey.d/dpkg-vendor/etc/dpkg/origins" + + # the origins directory of an image being built: what the overlay ships, + # plus the Debian file dpkg itself installs, which Parent: resolves to + export DPKG_ORIGINS_DIR="$BATS_TEST_TMPDIR/origins" + mkdir -p "$DPKG_ORIGINS_DIR" + cp -a "$SHIPPED"/. "$DPKG_ORIGINS_DIR/" + cat > "$DPKG_ORIGINS_DIR/debian" <<'DEBIAN' +Vendor: Debian +Vendor-URL: https://www.debian.org/ +Bugs: debbugs://bugs.debian.org +DEBIAN + + # DEB_VENDOR would override the default symlink, which is the thing under + # test; dpkg-vendor is the real one from dpkg-dev + unset DEB_VENDOR + command -v dpkg-vendor >/dev/null || { + echo "dpkg-vendor not found (apt-get install dpkg-dev)" >&2 + return 1 + } +} + +# the real dpkg-vendor, reading the tree the conf script just arranged +vendor() { + env -u DEB_VENDOR DPKG_ORIGINS_DIR="$DPKG_ORIGINS_DIR" dpkg-vendor "$@" +} + +# --------------------------------------------------------- what it answers + +@test "a vendor query answers Keel" { + run "$SCRIPT" + [ "$status" -eq 0 ] + run vendor --query Vendor + [ "$status" -eq 0 ] + [ "$output" = Keel ] +} + +@test "a vendor query does not answer TurnKey" { + "$SCRIPT" + run vendor --query Vendor + [ "$output" != TurnKey ] + run vendor --is TurnKey + [ "$status" -ne 0 ] +} + +@test "the vendor is Keel by dpkg's own --is test" { + "$SCRIPT" + run vendor --is Keel + [ "$status" -eq 0 ] +} + +@test "bug reports are addressed to our own tracker" { + "$SCRIPT" + run vendor --query Bugs + [ "$status" -eq 0 ] + [ "$output" = "https://github.com/Keel-Linux/tracker/issues" ] +} + +@test "nothing a vendor query answers names a turnkeylinux host" { + "$SCRIPT" + for field in Vendor Vendor-URL Bugs Parent; do + run vendor --query "$field" + [[ "$output" != *turnkeylinux* ]] + done +} + +@test "the vendor URL is our own site" { + "$SCRIPT" + run vendor --query Vendor-URL + [ "$output" = "https://keellinux.org/" ] +} + +@test "the vendor still derives from Debian, so dpkg-dev behaves as before" { + "$SCRIPT" + run vendor --derives-from Debian + [ "$status" -eq 0 ] +} + +@test "the vendor does not claim to derive from Ubuntu" { + "$SCRIPT" + run vendor --derives-from Ubuntu + [ "$status" -ne 0 ] +} + +# ------------------------------------------------------------- the script + +@test "the TurnKey origin file is not shipped at all" { + [ ! -e "$SHIPPED/TurnKey" ] + [ -f "$SHIPPED/Keel" ] +} + +@test "running it twice leaves the same answer" { + "$SCRIPT" + "$SCRIPT" + run vendor --query Vendor + [ "$status" -eq 0 ] + [ "$output" = Keel ] +} + +@test "an inherited default pointing at TurnKey is replaced" { + # the upgrade path: a parent layer built before this change + printf 'Vendor: TurnKey\nVendor-URL: https://www.turnkeylinux.org/\nBugs: https://github.com/turnkeylinux/tracker/issues\nParent: Debian\n' \ + > "$DPKG_ORIGINS_DIR/TurnKey" + ln -sf "$DPKG_ORIGINS_DIR/TurnKey" "$DPKG_ORIGINS_DIR/default" + run vendor --query Vendor + [ "$output" = TurnKey ] + "$SCRIPT" + run vendor --query Vendor + [ "$output" = Keel ] +} + +@test "a default that is a regular file rather than a symlink is replaced" { + cp "$DPKG_ORIGINS_DIR/Keel" "$DPKG_ORIGINS_DIR/default" + printf 'Vendor: Whoever\n' > "$DPKG_ORIGINS_DIR/default" + "$SCRIPT" + run vendor --query Vendor + [ "$output" = Keel ] +} + +@test "a default that is a directory is replaced" { + mkdir -p "$DPKG_ORIGINS_DIR/default" + "$SCRIPT" + run vendor --query Vendor + [ "$output" = Keel ] +} + +@test "it refuses when the origin file it points at is missing" { + rm -f "$DPKG_ORIGINS_DIR/Keel" + run "$SCRIPT" + [ "$status" -eq 1 ] + [[ "$output" == *Keel* ]] + [ ! -e "$DPKG_ORIGINS_DIR/default" ] +} + +@test "it refuses when the origins directory is missing" { + rm -rf "$DPKG_ORIGINS_DIR" + run "$SCRIPT" + [ "$status" -eq 1 ] + [[ "$output" == *origins* ]] +} From 9123362630b1cb85915034eb720cf7baaa8a2ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Mon, 28 Sep 2026 04:33:35 +0000 Subject: [PATCH 2/5] fix: stop announcing the appliance in the apt User-Agent mk/turnkey.mk and mk/turnkey-desktop.mk wrote a per-appliance /etc/apt/apt.conf.d/01turnkey: Acquire::http::User-Agent "TurnKey APT-HTTP/1.3 (turnkey-wordpress-19.0-trixie-amd64)"; so deb.debian.org, security.debian.org, every mirror in between and anyone watching the connection were told which appliance this machine is and which version it runs, on every apt run it ever made. Closes the User-Agent half of #6. The header is now a fixed file the overlay ships, 01keel, naming the distribution and nothing else. apt's https method reads the same setting, measured, so one line covers both schemes. conf/turnkey.d/apt-identity exists because apt reads apt.conf.d in lexical order and the last assignment of a scalar wins: a 01turnkey inherited from a parent layer built before this change silently beats the 01keel beside it, measured, and the appliance goes back to announcing itself. The script removes it and refuses if 01keel is not there. tests/apt-identity.bats reads every User-Agent verdict off the wire. tests/ua-recorder.py records the header a real apt-get update sent, over http and over TLS, so what is asserted is what apt announces rather than what the file says it should. The stale-01turnkey hazard has a test that measures it both ways. 11 bats, the script at 100 percent of 4 lines. --- conf/turnkey.d/apt-identity | 22 ++ mk/turnkey-desktop.mk | 13 +- mk/turnkey.mk | 16 +- .../apt-identity/etc/apt/apt.conf.d/01keel | 13 + tests/apt-identity.bats | 258 ++++++++++++++++++ tests/ua-recorder.py | 73 +++++ 6 files changed, 383 insertions(+), 12 deletions(-) create mode 100755 conf/turnkey.d/apt-identity create mode 100644 overlays/turnkey.d/apt-identity/etc/apt/apt.conf.d/01keel create mode 100644 tests/apt-identity.bats create mode 100755 tests/ua-recorder.py diff --git a/conf/turnkey.d/apt-identity b/conf/turnkey.d/apt-identity new file mode 100755 index 00000000..9e08236d --- /dev/null +++ b/conf/turnkey.d/apt-identity @@ -0,0 +1,22 @@ +#!/bin/bash -e +# +# Keeps the apt User-Agent of the image the one the overlay ships. +# +# The header itself is overlays/turnkey.d/apt-identity's 01keel. This exists +# because apt reads /etc/apt/apt.conf.d in lexical order and the last +# assignment of a scalar wins, so a 01turnkey inherited from a parent layer +# built before Keel-Linux/common#6 silently beats the 01keel beside it and the +# appliance goes back to announcing which appliance and which version it is. +# Measured: with both files in place apt sends TurnKey's header, and +# tests/apt-identity.bats takes that verdict off the wire. +# +# APT_CONF_DIR is a test hook; a build leaves it unset. + +fatal() { echo "'$(basename "$0")' Error: $*" >&2; exit 1; } + +conf_dir="${APT_CONF_DIR:-/etc/apt/apt.conf.d}" + +[ -d "$conf_dir" ] || fatal "the apt configuration directory '$conf_dir' does not exist" +[ -f "$conf_dir/01keel" ] || fatal "'$conf_dir/01keel' is missing - it is shipped by overlays/turnkey.d/apt-identity" + +rm -f "$conf_dir/01turnkey" diff --git a/mk/turnkey-desktop.mk b/mk/turnkey-desktop.mk index 7e97153e..2de6b7a9 100644 --- a/mk/turnkey-desktop.mk +++ b/mk/turnkey-desktop.mk @@ -47,12 +47,15 @@ endef bootstrap/post += $(_bootstrap/post) # tag package management system with release package -# set /etc/turnkey_version and apt user-agent +# set /etc/turnkey_version +# +# The apt User-Agent is no longer written here, for the reason given in +# mk/turnkey.mk: overlays/turnkey.d/apt-identity ships it (Keel-Linux/common#6). define _root.patched/post - - # + + # # tagging package management system with release package - # setting /etc/turnkey_version and apt user-agent + # setting /etc/turnkey_version # @if [ -f $(FAB_PATH)/products/core/changelog ]; then \ echo $(FAB_SHARE_PATH)/make-release-deb.py $(FAB_PATH)/products/core/changelog $O/root.patched; \ @@ -62,9 +65,7 @@ define _root.patched/post echo $(FAB_SHARE_PATH)/make-release-deb.py ./changelog $O/root.patched; \ $(FAB_SHARE_PATH)/make-release-deb.py ./changelog $O/root.patched; \ turnkey_version=$$($(FAB_SHARE_PATH)/turnkey-version.py --dist=$(CODENAME) --tag=$(VERSION_TAG) ./changelog $(FAB_ARCH)); \ - turnkey_aptconf="Acquire::http::User-Agent \"TurnKey APT-HTTP/1.3 ($$turnkey_version)\";"; \ echo $$turnkey_version > $O/root.patched/etc/turnkey_version; \ - echo $$turnkey_aptconf > $O/root.patched/etc/apt/apt.conf.d/01turnkey; \ else \ echo; \ echo "WARNING: can't tag local release (./changelog doesn't exist)"; \ diff --git a/mk/turnkey.mk b/mk/turnkey.mk index 75baac19..1a483329 100644 --- a/mk/turnkey.mk +++ b/mk/turnkey.mk @@ -44,20 +44,24 @@ endef bootstrap/post += $(_bootstrap/post) # tag package management system with release package -# set /etc/turnkey_version and apt user-agent +# set /etc/turnkey_version +# +# The apt User-Agent is no longer written here. It used to carry the appliance +# and its version to every archive the machine ever contacted; it is now a +# fixed header naming the distribution and nothing else, shipped by +# overlays/turnkey.d/apt-identity as /etc/apt/apt.conf.d/01keel +# (Keel-Linux/common#6). define _root.patched/post - - # + + # # tagging package management system with release package - # setting /etc/turnkey_version and apt user-agent + # setting /etc/turnkey_version # @if [ -f ./changelog ]; then \ echo $(FAB_SHARE_PATH)/make-release-deb.py ./changelog $O/root.patched; \ $(FAB_SHARE_PATH)/make-release-deb.py ./changelog $O/root.patched; \ turnkey_version=$$($(FAB_SHARE_PATH)/turnkey-version.py --dist=$(CODENAME) --tag=$(VERSION_TAG) ./changelog $(FAB_ARCH)); \ - turnkey_aptconf="Acquire::http::User-Agent \"TurnKey APT-HTTP/1.3 ($$turnkey_version)\";"; \ echo $$turnkey_version > $O/root.patched/etc/turnkey_version; \ - echo $$turnkey_aptconf > $O/root.patched/etc/apt/apt.conf.d/01turnkey; \ else \ echo; \ echo "WARNING: can't tag local release (./changelog doesn't exist)"; \ diff --git a/overlays/turnkey.d/apt-identity/etc/apt/apt.conf.d/01keel b/overlays/turnkey.d/apt-identity/etc/apt/apt.conf.d/01keel new file mode 100644 index 00000000..153e221a --- /dev/null +++ b/overlays/turnkey.d/apt-identity/etc/apt/apt.conf.d/01keel @@ -0,0 +1,13 @@ +// The User-Agent apt announces to every archive it contacts. +// +// It names the distribution and nothing else. What used to stand here was +// /etc/apt/apt.conf.d/01turnkey, written per appliance by mk/turnkey.mk: +// +// Acquire::http::User-Agent "TurnKey APT-HTTP/1.3 (turnkey-wordpress-19.0-trixie-amd64)"; +// +// so deb.debian.org, security.debian.org, every mirror in between and anyone +// watching the connection were told which appliance this machine is and which +// version it runs, on every apt run it ever made. Keel-Linux/common#6. +// +// The https method of apt reads this same setting, so one line covers both. +Acquire::http::User-Agent "Keel APT-HTTP/1.3"; diff --git a/tests/apt-identity.bats b/tests/apt-identity.bats new file mode 100644 index 00000000..22970667 --- /dev/null +++ b/tests/apt-identity.bats @@ -0,0 +1,258 @@ +#!/usr/bin/env bats +# Tests for the apt identity of an image: +# +# overlays/turnkey.d/apt-identity/etc/apt/apt.conf.d/01keel the User-Agent +# conf/turnkey.d/apt-identity keeps it the one in force +# conf/bootstrap_apt the source URIs +# +# Every User-Agent verdict is taken off the wire: a local server records the +# header a real apt-get update sent it, so what is asserted is what apt +# announces, not what the file says it should announce (docs/traps.md, +# "Asserting the configuration is not asserting the behaviour"). The source +# URIs are likewise read back from apt, which is asked what it would fetch +# from the stanzas conf/bootstrap_apt generates. +# +# Every refutation is written "run ! cmd", never a bare "! cmd": bash does not +# apply errexit to a negated command, so a bare one passes whatever happens +# and asserts nothing (measured; shellcheck SC2314 names it). + +bats_require_minimum_version 1.5.0 + +setup() { + TESTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" && pwd)" + REPO="$(cd "$TESTS_DIR/.." && pwd)" + SCRIPT="$REPO/conf/turnkey.d/apt-identity" + SHIPPED="$REPO/overlays/turnkey.d/apt-identity/etc/apt/apt.conf.d" + BOOTSTRAP="$REPO/conf/bootstrap_apt" + RECORDER="$TESTS_DIR/ua-recorder.py" + + # a scratch apt root: everything apt reads and writes is under here + APTROOT="$BATS_TEST_TMPDIR/aptroot" + export APT_CONF_DIR="$APTROOT/etc/apt/apt.conf.d" + mkdir -p "$APT_CONF_DIR" "$APTROOT/etc/apt/sources.list.d" \ + "$APTROOT/var/lib/apt/lists/partial" \ + "$APTROOT/var/cache/apt/archives/partial" "$APTROOT/var/lib/dpkg" + : > "$APTROOT/var/lib/dpkg/status" + APT_CONFIG="$BATS_TEST_TMPDIR/apt.conf" + export APT_CONFIG + cat > "$APT_CONFIG" < "$UA_LOG" + RECORDER_PID= +} + +teardown() { + [ -n "${RECORDER_PID:-}" ] && kill "$RECORDER_PID" 2>/dev/null + return 0 +} + +# the file the overlay ships, in the scratch tree +ship_01keel() { + cp "$SHIPPED/01keel" "$APT_CONF_DIR/01keel" +} + +# start_recorder [CERTFILE]; sets PORT +start_recorder() { + local portfile="$BATS_TEST_TMPDIR/port" + rm -f "$portfile" + python3 "$RECORDER" "$UA_LOG" "$@" > "$portfile" & + RECORDER_PID=$! + local waited + for waited in $(seq 1 100); do + [ -s "$portfile" ] && break + sleep 0.1 + done + [ -n "$waited" ] || return 1 + [ -s "$portfile" ] || { + echo "the recorder never printed a port" >&2 + return 1 + } + PORT="$(cat "$portfile")" +} + +# the distinct User-Agent strings the recorder was sent +sent_user_agents() { + cut -f3 "$UA_LOG" | sort -u +} + +# --------------------------------------------------- what apt puts on the wire + +@test "apt announces Keel over http, and no appliance identity" { + ship_01keel + start_recorder + printf 'deb [trusted=yes] http://127.0.0.1:%s/debian trixie main\n' "$PORT" \ + > "$APTROOT/etc/apt/sources.list" + apt-get update >/dev/null 2>&1 || true + + [ -s "$UA_LOG" ] + run sent_user_agents + [ "$output" = "Keel APT-HTTP/1.3" ] +} + +@test "apt announces the same over https" { + ship_01keel + openssl req -x509 -newkey rsa:2048 -days 1 -nodes -subj /CN=localhost \ + -keyout "$BATS_TEST_TMPDIR/key.pem" -out "$BATS_TEST_TMPDIR/cert.pem" 2>/dev/null + cat "$BATS_TEST_TMPDIR/key.pem" "$BATS_TEST_TMPDIR/cert.pem" > "$BATS_TEST_TMPDIR/pair.pem" + start_recorder "$BATS_TEST_TMPDIR/pair.pem" + printf 'deb [trusted=yes] https://127.0.0.1:%s/debian trixie main\n' "$PORT" \ + > "$APTROOT/etc/apt/sources.list" + apt-get update -o Acquire::https::Verify-Peer=false \ + -o Acquire::https::Verify-Host=false >/dev/null 2>&1 || true + + [ -s "$UA_LOG" ] + run sent_user_agents + [ "$output" = "Keel APT-HTTP/1.3" ] +} + +@test "what apt announces names no appliance, no version and no codename" { + ship_01keel + start_recorder + printf 'deb [trusted=yes] http://127.0.0.1:%s/debian trixie main\n' "$PORT" \ + > "$APTROOT/etc/apt/sources.list" + apt-get update >/dev/null 2>&1 || true + + [ -s "$UA_LOG" ] + local ua + ua="$(sent_user_agents)" + # the leak was everything inside the parentheses of the old header: + # "TurnKey APT-HTTP/1.3 (turnkey-wordpress-19.0-trixie-amd64)" + [[ "$ua" != *"("* ]] + [[ "$ua" != *TurnKey* ]] + [[ "$ua" != *turnkey* ]] + [[ "$ua" != *wordpress* ]] + [[ "$ua" != *trixie* ]] + [[ "$ua" != *amd64* ]] + [[ "$ua" != *19.0* ]] +} + +@test "a stale 01turnkey from a parent layer beats 01keel until it is removed" { + ship_01keel + # what an appliance layer built on a core layer from before the change has + printf 'Acquire::http::User-Agent "TurnKey APT-HTTP/1.3 (turnkey-wordpress-19.0-trixie-amd64)";\n' \ + > "$APT_CONF_DIR/01turnkey" + start_recorder + printf 'deb [trusted=yes] http://127.0.0.1:%s/debian trixie main\n' "$PORT" \ + > "$APTROOT/etc/apt/sources.list" + apt-get update >/dev/null 2>&1 || true + run sent_user_agents + [ "$output" = "TurnKey APT-HTTP/1.3 (turnkey-wordpress-19.0-trixie-amd64)" ] + + # the conf script is what stops that + run "$SCRIPT" + [ "$status" -eq 0 ] + : > "$UA_LOG" + rm -rf "$APTROOT/var/lib/apt/lists" + mkdir -p "$APTROOT/var/lib/apt/lists/partial" + apt-get update >/dev/null 2>&1 || true + run sent_user_agents + [ "$output" = "Keel APT-HTTP/1.3" ] +} + +# ---------------------------------------------------------------- the script + +@test "the conf script is a no-op when only 01keel is there" { + ship_01keel + run "$SCRIPT" + [ "$status" -eq 0 ] + [ -f "$APT_CONF_DIR/01keel" ] + run ls "$APT_CONF_DIR" + [ "$output" = 01keel ] +} + +@test "the conf script refuses when the overlay file is missing" { + run "$SCRIPT" + [ "$status" -eq 1 ] + [[ "$output" == *01keel* ]] +} + +@test "the conf script refuses when there is no apt configuration directory" { + rm -rf "$APT_CONF_DIR" + run "$SCRIPT" + [ "$status" -eq 1 ] + [[ "$output" == *apt.conf.d* ]] +} + +# ------------------------------------------- the URIs the bootstrap generates + +# the body of the heredoc conf/bootstrap_apt writes to the named source file +stanza() { + sed -n "/^ *cat > \$SOURCES_LIST\/$1 <&2 + return 1 + } + local KEY_CODENAME=trixie CODENAME=trixie + local MIRROR_URL=http://deb.debian.org/debian + local SEC_MIRROR=http://security.debian.org/debian-security + local sec_repo=trixie-security + local tkl_apt_repo_enabled=yes tkl_apt_testing_enabled=yes + local debian_backports_enabled=yes + local SUPPORTED_ARCH=(amd64 arm64) + local debian_components=(main non-free-firmware) + eval "cat < "$APTROOT/etc/apt/sources.list.d/$f" + done +} + +@test "no source the bootstrap writes fetches the turnkey archive over plain http" { + write_all_sources + fetch_uris > "$BATS_TEST_TMPDIR/uris" + [ -s "$BATS_TEST_TMPDIR/uris" ] + run ! grep -q '^http://archive\.turnkeylinux\.org' "$BATS_TEST_TMPDIR/uris" +} + +@test "the turnkey archive is fetched over https, in all three suites" { + write_all_sources + run fetch_uris + [ "$status" -eq 0 ] + local suite + for suite in trixie trixie-security trixie-testing; do + grep -q "^https://archive\.turnkeylinux\.org/debian/dists/$suite/" <<< "$output" + done +} + +@test "the debian sources are left as Debian ships them" { + write_all_sources + run fetch_uris + [ "$status" -eq 0 ] + grep -q '^http://deb\.debian\.org/debian/dists/trixie/' <<< "$output" + grep -q '^http://security\.debian\.org/debian-security/dists/trixie-security/' <<< "$output" + grep -q '^http://deb\.debian\.org/debian/dists/trixie-backports/' <<< "$output" +} + +@test "the legacy sources.list the bootstrap writes for older releases is https too" { + # the pre deb822 branch of conf/bootstrap_apt, still reached for bookworm + grep -n 'deb .*archive\.turnkeylinux\.org' "$BOOTSTRAP" > "$BATS_TEST_TMPDIR/legacy" + [ -s "$BATS_TEST_TMPDIR/legacy" ] + run ! grep -q 'http://archive\.turnkeylinux\.org' "$BATS_TEST_TMPDIR/legacy" +} diff --git a/tests/ua-recorder.py b/tests/ua-recorder.py new file mode 100755 index 00000000..6a9859a7 --- /dev/null +++ b/tests/ua-recorder.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Records the User-Agent of every request it is sent, and answers 404. + +Used by tests/apt-identity.bats to read off the wire what apt announces to an +archive it contacts, rather than reading back the configuration file that was +supposed to make it announce that (docs/traps.md, "Asserting the +configuration is not asserting the behaviour"). + + ua-recorder.py LOGFILE [CERTFILE] + +Listens on 127.0.0.1 on a port the kernel picks, prints that port on stdout as +one line, and then serves until it is killed. With CERTFILE (a PEM holding +both the key and the certificate) it speaks HTTPS instead of HTTP, which is +how the same verdict is taken for the https method of apt. + +Every request appends one line to LOGFILE: + + METHODPATHUSER-AGENT + +404 is a perfectly good answer here: apt only has to send the request for its +User-Agent to be on the wire, and a server with nothing in it keeps the test +free of fixtures. +""" + +import http.server +import socketserver +import ssl +import sys + + +def main() -> int: + if not 2 <= len(sys.argv) <= 3: + print(__doc__, file=sys.stderr) + return 2 + + log = open(sys.argv[1], "a", buffering=1, encoding="utf-8") + + class Handler(http.server.BaseHTTPRequestHandler): + # HTTP/1.0 so every request stands alone and nothing is held open + protocol_version = "HTTP/1.0" + + def _record(self) -> None: + log.write( + "%s\t%s\t%s\n" + % (self.command, self.path, self.headers.get("User-Agent", "-")) + ) + self.send_response(404) + self.send_header("Content-Length", "0") + self.end_headers() + + do_GET = _record + do_HEAD = _record + + def log_message(self, *args) -> None: + pass # the access log is the file above, not stderr + + socketserver.TCPServer.allow_reuse_address = True + server = socketserver.TCPServer(("127.0.0.1", 0), Handler) + if len(sys.argv) == 3: + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.load_cert_chain(sys.argv[2]) + server.socket = context.wrap_socket(server.socket, server_side=True) + + print(server.server_address[1], flush=True) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 724df1e511ca7dafb5cbc5d953b1b1227c40a90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Mon, 28 Sep 2026 04:33:50 +0000 Subject: [PATCH 3/5] fix: fetch the TurnKey archive over https Every source conf/bootstrap_apt wrote for archive.turnkeylinux.org was plain http, including trixie-security. The archive serves https: measured 2026-09-28, HTTP/2 200 with a certificate that verifies, on the Release file of trixie, trixie-security and trixie-testing. Part of #6. The three deb822 stanzas and the three legacy sources.list lines written for pre-Trixie releases all move to https. The Debian sources beside them are left on http: they are Debian's own default, and the build host reaches them through a caching proxy that http is what feeds. This does not remove the TurnKey suites. 39 installed packages still resolve to that archive and none of them is available from Debian, so removing the sources would stop the build; the measurement and the plan per package are in the pull request and the follow-up issue. tests/apt-identity.bats extracts the stanzas from conf/bootstrap_apt itself, renders them with a build's variables and asks apt with apt-get indextargets what it would fetch, so the verdict is apt's own resolution rather than a grep over the file. --- conf/bootstrap_apt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/conf/bootstrap_apt b/conf/bootstrap_apt index 60c571d9..bd161f2b 100755 --- a/conf/bootstrap_apt +++ b/conf/bootstrap_apt @@ -205,7 +205,7 @@ if [[ $deb_ver -ge 13 ]]; then # Main repos cat > $SOURCES_LIST/sources.sources < $SOURCES_LIST/security.sources.sources < $SOURCES_LIST/turnkey-testing.sources < $SOURCES_LIST/sources.list < $SOURCES_LIST/security.sources.list < $SOURCES_LIST/$TKL_TESTING_LIST < Date: Mon, 28 Sep 2026 04:33:50 +0000 Subject: [PATCH 4/5] docs: record the apt and vendor identity work A changelog entry for the three changes, in changes/turnkey.changelog where an appliance changelog picks it up, and the measured coverage in COVERAGE.md: three files, all at 100 percent, 33 bats under kcov 43. Also records a defect the work uncovered: a bare "! cmd" in a bats test body asserts nothing, because bash does not apply errexit to a negated command. Both new suites use "run ! cmd". tests/postfix-local.bats has three bare ones left, noted for whoever owns that file next. --- COVERAGE.md | 49 ++++++++++++++++++++++++++++++---- changes/turnkey.changelog | 16 ++++++++++++ tests/coverage.sh | 55 +++++++++++++++++++++++++++------------ 3 files changed, 98 insertions(+), 22 deletions(-) diff --git a/COVERAGE.md b/COVERAGE.md index 0895e226..2861c73e 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -4,13 +4,52 @@ Measured on 2026-09-24 against upstream 19.x (b60dd23), following the project decision 0003 (90 percent floor per repository, 95 percent for every file our changes touch). -## Measured baseline on the default branch: 100 percent (2026-09-26) +## Measured baseline on the default branch: 100 percent (2026-09-28) Pull request #2 merged on 2026-09-26 (merge commit 5a0a381) and brought -`tests/coverage.sh` with it: conf/turnkey.d/postfix-local 17 of 17 lines under kcov, 100 percent, 7 bats. The gate in -`.github/workflows/tests.yml` is set to 100, the measured number rounded -down, and is only ever raised. The sections that follow record the state -before the merge. +`tests/coverage.sh` with it. Every file it measures is at 100 percent: + +| File | What it is | Measured | +| --- | --- | --- | +| `conf/turnkey.d/postfix-local` | the build-time postfix configuration | 100 percent, 17 of 17 lines, 7 bats | +| `conf/turnkey.d/dpkg-vendor` | points the dpkg vendor at Keel | 100 percent, 6 of 6 lines, 15 bats | +| `conf/turnkey.d/apt-identity` | keeps the shipped apt User-Agent the one in force | 100 percent, 4 of 4 lines, 11 bats | + +33 bats, measured on 2026-09-28 with kcov 43 and bats 1.11. The gate in +`.github/workflows/tests.yml` is set to 100, the measured number, and is +only ever raised. The sections that follow record the state before the +first merge. + +## The apt and vendor identity of an image + +Three things used to tell an archive, or a bug reporting tool, that this +machine is a TurnKey appliance (Keel-Linux/common#6). None of the +assertions below reads back a file the code under test wrote: + +- **the vendor.** `conf/turnkey.d/dpkg-vendor` points the origins `default` + entry at the `Keel` file the matching overlay ships. Every verdict in + `tests/dpkg-vendor.bats` is an answer from the real `dpkg-vendor`, pointed + at the tree the script produced through dpkg's own `DPKG_ORIGINS_DIR`: + `--query Vendor`, `--query Bugs`, `--is`, `--derives-from`. The `Keel` + file keeps `Parent: Debian`, so `dpkg-dev` resolves the same vendor object + it did before and package building is unaffected. +- **the apt User-Agent.** The header is a fixed file the overlay ships, + `/etc/apt/apt.conf.d/01keel`; `mk/turnkey.mk` and `mk/turnkey-desktop.mk` + no longer write a per-appliance `01turnkey`. Every verdict in + `tests/apt-identity.bats` is read off the wire: `tests/ua-recorder.py` + records the header a real `apt-get update` sent, over http and over TLS. + One test measures the hazard the conf script exists for: with a stale + `01turnkey` beside `01keel`, apt sends TurnKey's header, because + `apt.conf.d` is read in lexical order and the last assignment wins. +- **the source URIs.** The stanzas are extracted from `conf/bootstrap_apt` + itself, rendered with a build's variables and handed to apt, which is + asked with `apt-get indextargets` what it would fetch. No network. + +Refutations in both suites are written `run ! cmd`, never a bare `! cmd`: +bash does not apply errexit to a negated command, so a bare one passes +whatever happens. Measured on this branch before the fix; shellcheck names +it SC2314. `tests/postfix-local.bats` still has three of them (lines 66, 96 +and 97) and they are left for the pull request that owns that file. ## Baseline before the merge: 0 percent, nothing measured diff --git a/changes/turnkey.changelog b/changes/turnkey.changelog index 20fcef6f..3d7fde38 100644 --- a/changes/turnkey.changelog +++ b/changes/turnkey.changelog @@ -1,5 +1,21 @@ turnkey-core-19.0 (1) turnkey; urgency=low + * apt no longer announces the appliance to every archive it contacts. The + per-appliance '/etc/apt/apt.conf.d/01turnkey', which carried a User-Agent + of 'TurnKey APT-HTTP/1.3 (turnkey----)', is + replaced by a fixed '/etc/apt/apt.conf.d/01keel' naming the distribution + and nothing else. A stale 01turnkey inherited from a parent layer would + win on lexical order, so conf/turnkey.d/apt-identity removes it. + + * 'dpkg-vendor' answers Keel, and bug reporting tools address + Keel-Linux/tracker rather than turnkeylinux/tracker. The TurnKey origin + file is no longer shipped; the new Keel one keeps 'Parent: Debian', so + dpkg-dev behaves exactly as before. + + * The TurnKey archive is fetched over https rather than plain http, in all + three suites and in the legacy sources.list written for pre-Trixie + releases. The Debian sources are unchanged. + * Upgraded base distribution to Debian 13.x/Trixie. * Replace TurnKey custom Debian-Installer based 'di-live' with new custom diff --git a/tests/coverage.sh b/tests/coverage.sh index e6a880b2..67ced292 100755 --- a/tests/coverage.sh +++ b/tests/coverage.sh @@ -1,18 +1,27 @@ #!/bin/bash -# Line coverage of conf/turnkey.d/postfix-local 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. +# Line coverage of the shell this repository is measured on, with kcov +# (decision 0004). Exits 1 when any measured file is below the threshold +# (default 95, the bar for project-authored code), 2 when a tool is missing. # # 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. +# COVERAGE_DIR keeps the kcov reports, one directory per measured file +# (default: a temporary directory). Needs the Debian packages kcov and bats; +# tests/apt-identity.bats also needs apt, openssl and python3, and +# tests/dpkg-vendor.bats needs dpkg-vendor from dpkg-dev. set -euo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -target="$(cd "$here/.." && pwd)/conf/turnkey.d/postfix-local" +root="$(cd "$here/.." && pwd)" threshold="${1:-${COVERAGE_THRESHOLD:-95}}" +# Each entry is a measured file and the bats file that exercises it. +targets=( + "conf/turnkey.d/postfix-local:tests/postfix-local.bats" + "conf/turnkey.d/dpkg-vendor:tests/dpkg-vendor.bats" + "conf/turnkey.d/apt-identity:tests/apt-identity.bats" +) + for tool in kcov bats; do if ! command -v "$tool" >/dev/null; then echo "$tool not found (apt-get install $tool)" >&2 @@ -20,18 +29,30 @@ for tool in kcov bats; do fi done -report="${COVERAGE_DIR:-$(mktemp -d)}" -kcov --include-path="$target" "$report" bats "$here" +reports="${COVERAGE_DIR:-$(mktemp -d)}" +failed=0 + +for target in "${targets[@]}"; do + measured="${target%%:*}" + suite="${target#*:}" + name="$(basename "$measured")" + report="$reports/$name" + mkdir -p "$report" + kcov --include-path="$root/$measured" "$report" bats "$root/$suite" + + # with --include-path the report holds one file, so its first entry is ours + json="$(find "$report" -name coverage.json -not -path '*/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]*')" -# 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 "$name: $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 "$name: coverage below threshold (report: $report)" >&2 + failed=1 + fi +done -echo "postfix-local: $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 +if [ "$failed" -ne 0 ]; then exit 1 fi From b10ae1e29a59925a32780a39a368a3a765d23883 Mon Sep 17 00:00:00 2001 From: navigator Date: Tue, 29 Sep 2026 03:24:16 +0000 Subject: [PATCH 5/5] fix: remove an inherited TurnKey origin file, and correct the bats record An overlay only adds, so on a layer built over a parent from before this change /etc/dpkg/origins/TurnKey stayed on disk and dpkg kept knowing that vendor by name; the review found it on both live containers. The conf script now removes it, as apt-identity removes a stale 01turnkey. The test asks the real dpkg-vendor for TurnKey's Bugs field before and after, and failed without the change. The script now says why "Parent: Debian" in the Keel origin file is required rather than decoration. COVERAGE.md said a bare "! cmd" in a bats test never asserts. It does when it is the last command, because bats takes the last status as the verdict, so of the three bare negations in tests/postfix-local.bats only line 96 is inert. Corrected there and in the two suites' headers. --- COVERAGE.md | 22 ++++++++++++++-------- changes/turnkey.changelog | 5 +++-- conf/turnkey.d/dpkg-vendor | 11 +++++++++++ tests/dpkg-vendor.bats | 20 +++++++++++++++++++- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/COVERAGE.md b/COVERAGE.md index 2861c73e..da242480 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -12,10 +12,10 @@ Pull request #2 merged on 2026-09-26 (merge commit 5a0a381) and brought | File | What it is | Measured | | --- | --- | --- | | `conf/turnkey.d/postfix-local` | the build-time postfix configuration | 100 percent, 17 of 17 lines, 7 bats | -| `conf/turnkey.d/dpkg-vendor` | points the dpkg vendor at Keel | 100 percent, 6 of 6 lines, 15 bats | +| `conf/turnkey.d/dpkg-vendor` | points the dpkg vendor at Keel, and removes an inherited TurnKey origin | 100 percent, 7 of 7 lines, 16 bats | | `conf/turnkey.d/apt-identity` | keeps the shipped apt User-Agent the one in force | 100 percent, 4 of 4 lines, 11 bats | -33 bats, measured on 2026-09-28 with kcov 43 and bats 1.11. The gate in +34 bats, measured on 2026-09-29 with kcov 43 and bats 1.11. The gate in `.github/workflows/tests.yml` is set to 100, the measured number, and is only ever raised. The sections that follow record the state before the first merge. @@ -32,7 +32,9 @@ assertions below reads back a file the code under test wrote: at the tree the script produced through dpkg's own `DPKG_ORIGINS_DIR`: `--query Vendor`, `--query Bugs`, `--is`, `--derives-from`. The `Keel` file keeps `Parent: Debian`, so `dpkg-dev` resolves the same vendor object - it did before and package building is unaffected. + it did before and package building is unaffected. A `TurnKey` origin file + inherited from a parent layer is removed, so dpkg no longer knows that + vendor by name. - **the apt User-Agent.** The header is a fixed file the overlay ships, `/etc/apt/apt.conf.d/01keel`; `mk/turnkey.mk` and `mk/turnkey-desktop.mk` no longer write a per-appliance `01turnkey`. Every verdict in @@ -45,11 +47,15 @@ assertions below reads back a file the code under test wrote: itself, rendered with a build's variables and handed to apt, which is asked with `apt-get indextargets` what it would fetch. No network. -Refutations in both suites are written `run ! cmd`, never a bare `! cmd`: -bash does not apply errexit to a negated command, so a bare one passes -whatever happens. Measured on this branch before the fix; shellcheck names -it SC2314. `tests/postfix-local.bats` still has three of them (lines 66, 96 -and 97) and they are left for the pull request that owns that file. +Refutations in both suites are written `run ! cmd`, never a bare `! cmd`. +bash does not apply errexit to a negated command, so a bare one that is +not the last command of its test passes whatever happens; as the last +command it does decide the test, because bats takes the last status as the +verdict. `run !` asserts wherever it stands, which is why it is the +convention. shellcheck grades the two cases differently: SC2314 is an error +for the inert one and a note otherwise. Of the three bare negations in +`tests/postfix-local.bats`, only line 96 is inert; lines 66 and 97 are last +in their tests. It is left for the pull request that owns that file. ## Baseline before the merge: 0 percent, nothing measured diff --git a/changes/turnkey.changelog b/changes/turnkey.changelog index 3d7fde38..1cdb7da9 100644 --- a/changes/turnkey.changelog +++ b/changes/turnkey.changelog @@ -9,8 +9,9 @@ turnkey-core-19.0 (1) turnkey; urgency=low * 'dpkg-vendor' answers Keel, and bug reporting tools address Keel-Linux/tracker rather than turnkeylinux/tracker. The TurnKey origin - file is no longer shipped; the new Keel one keeps 'Parent: Debian', so - dpkg-dev behaves exactly as before. + file is no longer shipped, and one inherited from a parent layer is + removed; the new Keel one keeps 'Parent: Debian', so dpkg-dev behaves + exactly as before. * The TurnKey archive is fetched over https rather than plain http, in all three suites and in the legacy sources.list written for pre-Trixie diff --git a/conf/turnkey.d/dpkg-vendor b/conf/turnkey.d/dpkg-vendor index fd94f1ac..8065c047 100755 --- a/conf/turnkey.d/dpkg-vendor +++ b/conf/turnkey.d/dpkg-vendor @@ -10,6 +10,12 @@ # that bug reporting tools address. It used to point at TurnKey, so both # named TurnKey (Keel-Linux/common#6). # +# The Keel file keeps "Parent: Debian", and that line is not decoration. +# There is no Dpkg::Vendor::Keel perl module, so Dpkg::Vendor falls back to +# the parent's object, Dpkg::Vendor::Debian, the one TurnKey resolved to: +# Debian's hardening defaults, update-buildflags hook and changelog +# handling. Without Parent it would fall to Dpkg::Vendor::Default instead. +# # DPKG_ORIGINS_DIR is dpkg's own override of that directory, honoured here so # tests/dpkg-vendor.bats can arrange a tree and then ask the real dpkg-vendor # what it makes of it, rather than reading back the link this wrote. A build @@ -25,3 +31,8 @@ vendor="Keel" rm -rf "$origins/default" ln -s "$origins/$vendor" "$origins/default" + +# An overlay only adds files, so a parent layer built before this change +# leaves its TurnKey origin file behind, and dpkg keeps knowing that vendor +# by name. Remove it, as apt-identity removes a stale 01turnkey. +rm -f "$origins/TurnKey" diff --git a/tests/dpkg-vendor.bats b/tests/dpkg-vendor.bats index 57716b78..af597a89 100644 --- a/tests/dpkg-vendor.bats +++ b/tests/dpkg-vendor.bats @@ -10,7 +10,8 @@ # anything else asking "who is the vendor of this machine" is told. # # Refutations are written "run ! cmd", never a bare "! cmd": bash does not -# apply errexit to a negated command, so a bare one asserts nothing. +# apply errexit to a negated command, so a bare one asserts nothing unless +# it happens to be the last command of its test. bats_require_minimum_version 1.5.0 @@ -129,6 +130,23 @@ vendor() { [ "$output" = Keel ] } +@test "an inherited TurnKey origin file is removed, not only unselected" { + # an overlay only adds, so a parent layer built before this change + # leaves its TurnKey file on the image; dpkg still knows that vendor by + # name until the file is gone + printf 'Vendor: TurnKey\nVendor-URL: https://www.turnkeylinux.org/\nBugs: https://github.com/turnkeylinux/tracker/issues\nParent: Debian\n' \ + > "$DPKG_ORIGINS_DIR/TurnKey" + ln -sf "$DPKG_ORIGINS_DIR/TurnKey" "$DPKG_ORIGINS_DIR/default" + run dpkg-vendor --vendor TurnKey --query Bugs + [ "$output" = https://github.com/turnkeylinux/tracker/issues ] + "$SCRIPT" + run dpkg-vendor --vendor TurnKey --query Bugs + [ "$status" -ne 0 ] + [[ "$output" == *"vendor TurnKey doesn't exist"* ]] + run vendor --query Vendor + [ "$output" = Keel ] +} + @test "a default that is a regular file rather than a symlink is replaced" { cp "$DPKG_ORIGINS_DIR/Keel" "$DPKG_ORIGINS_DIR/default" printf 'Vendor: Whoever\n' > "$DPKG_ORIGINS_DIR/default"