Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/verify-split-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down
86 changes: 49 additions & 37 deletions lib/system.func
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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
{
Expand All @@ -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

Expand Down
84 changes: 84 additions & 0 deletions tests/deb822-local-keyring.sh
Original file line number Diff line number Diff line change
@@ -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))
Loading