#!/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"