diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index de11a10..2eb2431 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -30,6 +30,7 @@ on: - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" - "tests/java-memory.sh" + - "tests/deb822-local-keyring.sh" - "tests/gpu-pci-class.sh" - "tests/nvidia-version-pin.sh" - "tests/external-installer.sh" @@ -89,6 +90,7 @@ on: - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" - "tests/java-memory.sh" + - "tests/deb822-local-keyring.sh" - "tests/gpu-pci-class.sh" - "tests/nvidia-version-pin.sh" - "tests/external-installer.sh" @@ -172,6 +174,9 @@ jobs: - name: Java memory drop-in tests run: bash tests/java-memory.sh + - name: deb822 local keyring tests + run: bash tests/deb822-local-keyring.sh + - name: GPU detection by PCI class tests run: bash tests/gpu-pci-class.sh diff --git a/lib/system.func b/lib/system.func index 8f6a5ba..fc56a65 100644 --- a/lib/system.func +++ b/lib/system.func @@ -55,14 +55,15 @@ curl_with_retry() { local success=false local backoff=1 - # Extract hostname for DNS pre-check - local host - host=$(echo "$url" | sed -E 's|^https?://([^/:]+).*|\1|') - - # DNS pre-check - fail fast if host is unresolvable - if ! getent hosts "$host" &>/dev/null; then - debug_log "DNS resolution failed for $host" - return 6 + # DNS pre-check - fail fast if host is unresolvable. http(s) only: a file:// + # URL has no host, and the lookup then ran on the whole URL and failed. + if [[ "$url" =~ ^https?:// ]]; then + local host + host=$(echo "$url" | sed -E 's|^https?://([^/:]+).*|\1|') + if ! getent hosts "$host" &>/dev/null; then + debug_log "DNS resolution failed for $host" + return 6 + fi fi while [[ $attempt -le $retries ]]; do @@ -1926,6 +1927,8 @@ ensure_apt_working() { # ------------------------------------------------------------------------------ # Standardized deb822 repository setup (with optional Architectures) # Always runs apt update after repo creation to ensure package availability +# $2 is the key's URL, or the path of a keyring already on the system (Debian's +# archive keyring for backports), which is used where it is. # ------------------------------------------------------------------------------ setup_deb822_repo() { local name="$1" @@ -1946,38 +1949,47 @@ setup_deb822_repo() { cleanup_old_repo_files "$name" cleanup_orphaned_sources - mkdir -p /etc/apt/keyrings || { - msg_error "Failed to create /etc/apt/keyrings" - return 252 - } - - # Import GPG key (auto-detect binary vs ASCII-armored format) - local tmp_gpg - tmp_gpg=$(mktemp) || return 252 - CURL_RETRIES=4 CURL_TIMEOUT=30 curl_with_retry "$gpg_url" "$tmp_gpg" || { - msg_error "Failed to download GPG key for ${name} from: ${gpg_url}" - msg_error "Hint: Check network connectivity. If behind a proxy or firewall, ensure HTTPS access to $(echo "$gpg_url" | grep -oE 'https?://[^/]+') is allowed." - rm -f "$tmp_gpg" - return 7 - } - - if grep -q "BEGIN PGP" "$tmp_gpg" 2>/dev/null; then - # ASCII-armored — dearmor to binary - gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" <"$tmp_gpg" || { - msg_error "Failed to install GPG key for ${name}" - rm -f "$tmp_gpg" - return 251 - } + local signed_by="/etc/apt/keyrings/${name}.gpg" + if [[ "$gpg_url" == /* ]]; then + if [[ ! -f "$gpg_url" ]]; then + msg_error "Keyring for ${name} not found: ${gpg_url}" + return 252 + fi + signed_by="$gpg_url" else - # Already binary — copy directly - cp -f "$tmp_gpg" "/etc/apt/keyrings/${name}.gpg" || { - msg_error "Failed to install GPG key for ${name}" - rm -f "$tmp_gpg" + mkdir -p /etc/apt/keyrings || { + msg_error "Failed to create /etc/apt/keyrings" return 252 } + + # Import GPG key (auto-detect binary vs ASCII-armored format) + local tmp_gpg + tmp_gpg=$(mktemp) || return 252 + CURL_RETRIES=4 CURL_TIMEOUT=30 curl_with_retry "$gpg_url" "$tmp_gpg" || { + msg_error "Failed to download GPG key for ${name} from: ${gpg_url}" + msg_error "Hint: Check network connectivity. If behind a proxy or firewall, ensure HTTPS access to $(echo "$gpg_url" | grep -oE 'https?://[^/]+') is allowed." + rm -f "$tmp_gpg" + return 7 + } + + if grep -q "BEGIN PGP" "$tmp_gpg" 2>/dev/null; then + # ASCII-armored — dearmor to binary + gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" <"$tmp_gpg" || { + msg_error "Failed to install GPG key for ${name}" + rm -f "$tmp_gpg" + return 251 + } + else + # Already binary — copy directly + cp -f "$tmp_gpg" "/etc/apt/keyrings/${name}.gpg" || { + msg_error "Failed to install GPG key for ${name}" + rm -f "$tmp_gpg" + return 252 + } + fi + rm -f "$tmp_gpg" + chmod 644 "/etc/apt/keyrings/${name}.gpg" fi - rm -f "$tmp_gpg" - chmod 644 "/etc/apt/keyrings/${name}.gpg" # Write deb822 { @@ -1989,7 +2001,7 @@ setup_deb822_repo() { echo "Components: $component" fi [[ -n "$architectures" ]] && echo "Architectures: $architectures" - echo "Signed-By: /etc/apt/keyrings/${name}.gpg" + echo "Signed-By: ${signed_by}" [[ -n "$enabled" ]] && echo "Enabled: $enabled" } >/etc/apt/sources.list.d/${name}.sources diff --git a/tests/deb822-local-keyring.sh b/tests/deb822-local-keyring.sh new file mode 100644 index 0000000..6518eff --- /dev/null +++ b/tests/deb822-local-keyring.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +TEST_DIR="$(mktemp -d)" +trap 'rm -rf -- "$TEST_DIR"' EXIT +passed=0 failed=0 + +eval "$(awk '/^curl_with_retry[(][)] [{]/,/^}/' "$CORE_DIR/lib/system.func")" +eval "$(awk '/^setup_deb822_repo[(][)] [{]/,/^}/' "$CORE_DIR/lib/system.func" | + sed -e "s#/etc/apt/keyrings#$TEST_DIR/keyrings#g" \ + -e "s#/etc/apt/sources.list.d#$TEST_DIR/sources#g")" + +LOG="$TEST_DIR/calls" +# shellcheck disable=SC2034 +STD="" +debug_log() { :; } +msg_error() { echo "error: $*" >>"$LOG"; } +cleanup_old_repo_files() { :; } +cleanup_orphaned_sources() { :; } +apt() { echo "apt $*" >>"$LOG"; } +getent() { echo "getent $*" >>"$LOG"; [[ "$2" == *.example.org ]]; } +curl() { + echo "curl ${*: -1}" >>"$LOG" + local out="" + while (($#)); do + [[ "$1" == "-o" ]] && out="$2" + shift + done + [[ -n "$out" ]] && printf 'binary-key' >"$out" + return 0 +} + +check() { + if [[ "$3" == "$2" ]]; then + echo "PASS $1" + passed=$((passed + 1)) + else + echo "FAIL $1: expected <$2>, got <$3>" >&2 + failed=$((failed + 1)) + fi +} +reset() { + rm -rf "$TEST_DIR/keyrings" "$TEST_DIR/sources" "$LOG" + mkdir -p "$TEST_DIR/sources" + : >"$LOG" +} +signed_by() { sed -n 's/^Signed-By: //p' "$TEST_DIR/sources/$1.sources" 2>/dev/null; } + +local_key="$TEST_DIR/debian-archive-keyring.gpg" +printf 'system-key' >"$local_key" + +reset +rc=0 +setup_deb822_repo debian-backports "$local_key" http://deb.debian.org/debian trixie-backports main || rc=$? +check "a local keyring succeeds" 0 "$rc" +check "a local keyring is used where it is" "$local_key" "$(signed_by debian-backports)" +check "a local keyring is neither downloaded nor looked up" "" "$(grep -E '^(curl|getent) ' "$LOG" || true)" +check "a local keyring is not copied" no "$([[ -e "$TEST_DIR/keyrings" ]] && echo yes || echo no)" +check "apt update still runs" "apt update" "$(grep '^apt ' "$LOG")" + +reset +rc=0 +setup_deb822_repo debian-backports "$TEST_DIR/missing.gpg" http://deb.debian.org/debian trixie-backports main || rc=$? +check "a missing local keyring fails as a missing resource" 252 "$rc" +check "a missing local keyring writes no sources file" no "$([[ -e "$TEST_DIR/sources/debian-backports.sources" ]] && echo yes || echo no)" + +reset +setup_deb822_repo myapp https://repo.example.org/key.gpg https://repo.example.org/deb stable main +check "a key URL is downloaded into keyrings" "$TEST_DIR/keyrings/myapp.gpg" "$(signed_by myapp)" +check "the downloaded key is in place" binary-key "$(cat "$TEST_DIR/keyrings/myapp.gpg" 2>/dev/null)" + +reset +rc=0 +curl_with_retry file:///usr/share/keyrings/debian-archive-keyring.gpg "$TEST_DIR/out" >/dev/null || rc=$? +check "file:// skips the DNS pre-check" "0 curl file:///usr/share/keyrings/debian-archive-keyring.gpg" "$rc $(cat "$LOG")" + +reset +rc=0 +curl_with_retry https://unknown.invalid/key.gpg "$TEST_DIR/out" || rc=$? +check "an unresolvable https host still fails fast" "6 getent hosts unknown.invalid" "$rc $(cat "$LOG")" + +printf '%s passed, %s failed\n' "$passed" "$failed" +((failed == 0))