Files
pxe-server/scripts/offline-apt-install.sh
cproudlock bcd91b488a Offline install that lands fully patched: resolve against the ISO's own base
- autoinstall: dead mirror + fallback offline-install, so the installed
  base is exactly the ISO's; late-commands run offline-apt-install.sh on
  CIDATA packages/ (local apt repo, one transaction, errors fail the
  install, log /var/log/offline-apt-install.log); first boot no longer
  runs dpkg -i; real archive sources written back at the end.
- download-packages.sh --iso: unpacks the ISO's default install source
  into a docker image (pxe-iso-base:<version>) and downloads there: all
  updates of the base, the explicit list (zip included) and the newest
  pool packages with their closure; writes Packages + explicit.list and
  checks the set offline in a fresh base container (--network none).
- offline-apt-install.sh: new; also used by that check (--simulate).
- build-usb.sh: ships Packages, explicit.list and the script; refuses a
  set without them.
- tests/vm: pxetest-client-nosb (Secure Boot off, e1000e, SATA), one
  client at a time.
2026-10-15 10:59:30 -04:00

86 lines
3.0 KiB
Bash
Executable File

#!/bin/bash
#
# offline-apt-install.sh - Install and patch from the offline package set
#
# Uses DIR (offline-packages/ as built by download-packages.sh: the .debs,
# a Packages index and explicit.list) as the ONLY apt source:
# 1. every installed package that DIR has a newer version of is upgraded
# 2. every package in DIR/explicit.list is installed
# in one apt transaction, then checks that nothing is left to upgrade.
# The system's own sources.list and lists are not read or changed.
#
# Any apt failure stops the script with a non-zero exit and apt's message.
#
# Usage:
# offline-apt-install.sh DIR install (as root)
# offline-apt-install.sh --simulate DIR resolve only (apt-get -s)
#
# Runs in the autoinstall late-commands (curtin in-target) and in
# download-packages.sh's offline check of the bundle.
set -euo pipefail
SIMULATE=0
if [ "${1:-}" = "--simulate" ]; then SIMULATE=1; shift; fi
DIR="${1:?usage: $0 [--simulate] DIR}"
DIR="$(cd "$DIR" && pwd)"
for f in Packages explicit.list; do
[ -f "$DIR/$f" ] || { echo "offline-apt: ERROR: $DIR/$f missing (rebuild with download-packages.sh)" >&2; exit 1; }
done
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
mkdir -p "$WORK/lists/partial" "$WORK/parts"
echo "deb [trusted=yes] file:$DIR ./" >"$WORK/sources.list"
APT=(apt-get
-o "Dir::Etc::SourceList=$WORK/sources.list"
-o "Dir::Etc::SourceParts=$WORK/parts"
-o "Dir::State::Lists=$WORK/lists"
-o "APT::Sandbox::User=root"
-o "Dpkg::Options::=--force-confdef"
-o "Dpkg::Options::=--force-confold")
export DEBIAN_FRONTEND=noninteractive
echo "offline-apt: source $DIR ($(find "$DIR" -maxdepth 1 -name '*.deb' | wc -l) debs)"
"${APT[@]}" update
mapfile -t EXPLICIT < <(grep -v '^[[:space:]]*\(#\|$\)' "$DIR/explicit.list")
DU=$("${APT[@]}" -s dist-upgrade)
if grep -q 'kept back' <<<"$DU"; then
echo "offline-apt: ERROR: upgrades kept back (missing dependencies in $DIR):" >&2
echo "$DU" >&2
exit 1
fi
mapfile -t UPGRADES < <(awk '/^Inst /{print $2}' <<<"$DU")
echo "offline-apt: ${#UPGRADES[@]} upgrades, ${#EXPLICIT[@]} explicit packages"
if [ "$SIMULATE" -eq 1 ]; then
OUT=$("${APT[@]}" -s install "${EXPLICIT[@]}" "${UPGRADES[@]}")
echo "offline-apt: simulated: $(grep -c '^Inst ' <<<"$OUT") packages to unpack, $(grep -c '^Conf ' <<<"$OUT") to configure"
exit 0
fi
"${APT[@]}" -y install "${EXPLICIT[@]}" "${UPGRADES[@]}"
AFTER=$("${APT[@]}" -s dist-upgrade)
LEFT=$(awk '/^Inst /{print $2}' <<<"$AFTER")
if grep -q 'kept back' <<<"$AFTER"; then
echo "offline-apt: ERROR: upgrades kept back (missing dependencies in $DIR):" >&2
echo "$AFTER" >&2
exit 1
fi
if [ -n "$LEFT" ]; then
echo "offline-apt: ERROR: still upgradable from $DIR after install:" >&2
echo "$LEFT" >&2
exit 1
fi
BROKEN=$(dpkg --audit || true)
if [ -n "$BROKEN" ]; then
echo "offline-apt: ERROR: dpkg --audit reports:" >&2
echo "$BROKEN" >&2
exit 1
fi
echo "offline-apt: done, nothing left to upgrade from the offline set"