#!/bin/bash # # download-packages.sh - Download all .deb packages needed for offline PXE server setup # # The PXE server installs Ubuntu 24.04 (Noble), so all packages MUST come from the # 24.04 archive. If this script is run on a non-24.04 host (e.g. Zorin 17 / 22.04), # it auto-spawns an Ubuntu 24.04 docker container to do the download. # # Usage: # ./download-packages.sh [output_directory] # # Default output: ./offline-packages/ set -euo pipefail OUT_DIR="${1:-./offline-packages}" OUT_DIR_ABS="$(cd "$(dirname "$OUT_DIR")" 2>/dev/null && pwd)/$(basename "$OUT_DIR")" # Detect host Ubuntu codename. Run inside the container if not Noble (24.04). HOST_CODENAME="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}")" # USE_DOCKER=1 forces the container even on a noble host: no sudo needed, # and the host's own apt state is left alone. if [ "${IN_DOCKER:-}" != "1" ] && { [ "$HOST_CODENAME" != "noble" ] || [ "${USE_DOCKER:-}" = "1" ]; }; then echo "Host is '$HOST_CODENAME'; downloading inside an ubuntu:24.04 container." echo "Re-running inside ubuntu:24.04 docker container..." echo "" if ! command -v docker >/dev/null; then echo "ERROR: docker not installed. Install docker or run on a real Ubuntu 24.04 host." exit 1 fi SCRIPT_PATH="$(readlink -f "$0")" REPO_DIR="$(cd "$(dirname "$SCRIPT_PATH")"/.. && pwd)" mkdir -p "$OUT_DIR_ABS" # Wheels go to their own mount so PIP_DIR is honoured inside the container # (the in-container default would be the repo's pip-wheels/). PIP_DIR_HOST="$(mkdir -p "${PIP_DIR:-$REPO_DIR/pip-wheels}" && cd "${PIP_DIR:-$REPO_DIR/pip-wheels}" && pwd)" docker run --rm -i \ -v "$REPO_DIR:/repo" \ -v "$OUT_DIR_ABS:/out" \ -v "$PIP_DIR_HOST:/pipout" \ -e IN_DOCKER=1 \ -e PIP_DIR=/pipout \ -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \ -e BLANCCO_KVER="${BLANCCO_KVER:-}" \ -w /repo \ ubuntu:24.04 \ bash -c "apt-get update -qq && apt-get install -y --no-install-recommends sudo python3-pip python3-setuptools python3-wheel ca-certificates >/dev/null && /repo/scripts/download-packages.sh /out; rc=\$?; chown -R \$HOST_UID:\$HOST_GID /out /pipout; exit \$rc" echo "" echo "============================================" echo "Container build complete. Files in: $OUT_DIR_ABS" echo "============================================" exit 0 fi mkdir -p "$OUT_DIR" # Kernel the Blancco UEFI path boots (playbook var blancco_kver; keep the two # in step). The playbook builds vmlinuz-ubuntu-70, kexec-initrd-70.img and # kmod-.tar.gz from this kernel's /boot and /lib/modules, and the # Canonical-signed image is what Ubuntu's shim starts with Secure Boot on. # The versioned packages pin it; the HWE metapackage keeps the box on the # HWE line. Override: BLANCCO_KVER=7.0.0-40-generic ./download-packages.sh BLANCCO_KVER="${BLANCCO_KVER:-7.0.0-34-generic}" # Packages installed by the Ansible playbook (pxe_server_setup.yml) PLAYBOOK_PACKAGES=( ansible dnsmasq apache2 samba unzip ufw cron wimtools p7zip-full grub-efi-amd64-bin grub-common conntrack busybox-static zstd cpio # Updates pages in the webapp (iPXE signature check, Dell pack zips, downloads) sbsigntool zip curl # Blancco with Secure Boot on: shimx64.efi.signed + the HWE kernel shim-signed linux-generic-hwe-24.04 "linux-image-${BLANCCO_KVER}" "linux-modules-${BLANCCO_KVER}" # No linux-modules-extra: from 7.0 Ubuntu ships those modules (iwlwifi # etc.) in linux-modules, and modules-extra-7.0.* does not exist. ) # Packages installed during autoinstall late-commands (NetworkManager, WiFi, etc.) AUTOINSTALL_PACKAGES=( network-manager wpasupplicant wireless-tools linux-firmware firmware-sof-signed ) ALL_PACKAGES=("${PLAYBOOK_PACKAGES[@]}" "${AUTOINSTALL_PACKAGES[@]}") echo "============================================" echo "Offline Package Downloader (Ubuntu 24.04 noble)" echo "============================================" echo "Output directory: $OUT_DIR" echo "" echo "Packages to resolve:" printf ' - %s\n' "${ALL_PACKAGES[@]}" echo "" # Update package cache echo "[1/4] Updating package cache..." sudo apt-get update -qq # Simulate install to find all dependencies echo "[2/4] Resolving dependencies..." SIM=$(apt-get install --simulate "${ALL_PACKAGES[@]}" 2>&1) || { # e.g. a package name that does not exist in noble: say which. echo "ERROR: apt could not resolve the package list:" >&2 echo "$SIM" | grep -E "^(E|W):" >&2 exit 1 } EXPLICIT_DEPS=$(echo "$SIM" | grep "^Inst " | awk '{print $2}') # ALSO pull every package that would upgrade in a dist-upgrade. This is # critical: the Ubuntu ISO ships a point-in-time baseline, but our explicit # packages (from noble-updates) may depend on *newer* versions of ISO-baseline # packages (e.g. gnupg 17.4 needs matching gpgv 17.4). Without this, offline # install fails with dpkg "dependency problems" because transitive version # bumps aren't captured by --simulate on the explicit list. UPGRADE_DEPS=$(apt-get dist-upgrade --simulate 2>&1 \ | grep "^Inst " \ | awk '{print $2}') DEPS=$(printf '%s\n%s\n' "$EXPLICIT_DEPS" "$UPGRADE_DEPS" | sort -u | grep -v '^$') DEP_COUNT=$(echo "$DEPS" | wc -l) echo " Found $DEP_COUNT packages (explicit + baseline upgrades)" # Download all packages echo "[3/4] Downloading .deb packages to $OUT_DIR..." cd "$OUT_DIR" apt-get download $DEPS 2>&1 | tail -5 DEB_COUNT=$(ls -1 *.deb 2>/dev/null | wc -l) TOTAL_SIZE=$(du -sh . | cut -f1) echo " $DEB_COUNT packages ($TOTAL_SIZE)" # Download pip wheels for Flask webapp (offline install) echo "[4/4] Downloading Python wheels for webapp..." # Place pip-wheels next to the script (or /repo when in docker), not next to OUT_DIR REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)" PIP_DIR="${PIP_DIR:-$REPO_ROOT/pip-wheels}" # Exactly the pinned, hashed set in webapp/requirements.txt, for the server's # Python 3.12 on x86_64. The folder is emptied first: the playbook installs # every wheel in it and refuses any wheel that is not pinned. find "$PIP_DIR" -mindepth 1 -delete 2>/dev/null; mkdir -p "$PIP_DIR" pip3 download -d "$PIP_DIR" --require-hashes -r "$REPO_ROOT/webapp/requirements.txt" \ --only-binary=:all: --no-deps --python-version 3.12 --implementation cp --abi cp312 \ --platform manylinux_2_28_x86_64 --platform manylinux_2_17_x86_64 --platform manylinux2014_x86_64 \ 2>&1 | tail -5 WHL_COUNT=$(ls -1 "$PIP_DIR"/*.whl "$PIP_DIR"/*.tar.gz 2>/dev/null | wc -l) echo " $WHL_COUNT Python packages downloaded to pip-wheels/" echo "" echo "============================================" echo "Download complete!" echo "============================================" echo " .deb packages: $DEB_COUNT ($TOTAL_SIZE) in $OUT_DIR/" echo " Python wheels: $WHL_COUNT in $PIP_DIR/" echo ""