Files
pxe-server/scripts/download-packages.sh
cproudlock ead3314f58 Two-NIC standard: pin imaging and GE NICs by MAC everywhere
Every server has an imaging NIC (static, the only one that serves
DHCP/TFTP/HTTP boot/SMB) and a GE corporate NIC (DHCP client, SSH and the
webapp on 9009). The playbook guessed the PXE NIC and the installer put
172.16.9.1/24 on every en* NIC, so a two-NIC box could answer on the GE
network.

- New per-site vars pxe_nic_mac / corp_nic_mac (required, from
  playbook/site-vars.yml or -e; template site-vars.example.yml), stable
  names pxe_nic_name / corp_nic_name (default pxe0 / corp0, overridable),
  pxe_server_ip, pxe_subnet, DHCP range. pxe_imaging_mac / pxe_ge_mac are
  derived from them. The play stops before touching anything if a MAC is
  unset, malformed, duplicated or not on the box (lists the box's NICs).
- Heuristic NIC detection and the single-NIC netplan task replaced by a
  two-NIC netplan template (match macaddress + set-name), installed by
  pxe-netplan-apply.sh: validate in a scratch root, back up, generate,
  rename, apply, restore the previous config on any failure. cloud-init
  network config disabled so it cannot rewrite it.
- dnsmasq: /etc/dnsmasq.d/pxe-nic-only.conf from the vars (interface=,
  except-interface=, no-dhcp-interface=); dnsmasq.conf IP/range from vars.
- UFW per interface: imaging NIC 22, 67/udp, 69/udp, 80, 4433, 445, 139,
  137/udp, 138/udp, 9009; GE NIC 22 and 9009 only. Old any-interface rules
  and the stale 9011 rule removed after the new ones are in.
- sysctl drop-in: IPv4/IPv6 forwarding off; ip_nonlocal_bind so Apache
  can bind the imaging IP before the address is up.
- Samba [global]: interfaces = 127.0.0.0/8 <imaging NIC>, bind interfaces
  only. Apache 80 and 4433 on the imaging IP only; 9009 stays on all (TODO:
  the webapp has no auth).
- autoinstall: DHCP only on en*, no static 172.16.9.1; apt geoip off and
  offline-install fallback now that the corp NIC may have a lease.
- Blancco UEFI/Secure Boot: blancco_kver (7.0.0-34-generic) drives the
  kernel, initramfs and modules builds instead of the running kernel;
  outputs vmlinuz-ubuntu-70, kexec-initrd-70.img, kmod-<kver>.tar.gz as the
  menu and blancco-init.sh expect; creates: guards keyed on the full kver.
  Ubuntu's shimx64.efi.signed published as blancco/shimx64.efi.
  download-packages.sh adds shim-signed, linux-generic-hwe-24.04 and the
  pinned linux-image/modules/modules-extra for BLANCCO_KVER.

Rendered with the live MACs and names, netplan generate output is
identical to the live config; pxe-nic-only.conf and the Samba binding
match live directive for directive.
2026-10-15 08:53:41 -04:00

164 lines
5.5 KiB
Bash
Executable File

#!/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:-}}")"
if [ "${IN_DOCKER:-}" != "1" ] && [ "$HOST_CODENAME" != "noble" ]; then
echo "Host is '$HOST_CODENAME', not 'noble' (Ubuntu 24.04)."
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"
docker run --rm -i \
-v "$REPO_DIR:/repo" \
-v "$OUT_DIR_ABS:/out" \
-e IN_DOCKER=1 \
-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"
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-<kver>.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}"
"linux-modules-extra-${BLANCCO_KVER}"
)
# 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..."
EXPLICIT_DEPS=$(apt-get install --simulate "${ALL_PACKAGES[@]}" 2>&1 \
| 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="$REPO_ROOT/pip-wheels"
mkdir -p "$PIP_DIR"
pip3 download -d "$PIP_DIR" flask lxml 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 ""