Fix USB install reliability: bash, LV resize, deps, idempotency
- autoinstall/user-data: move lvextend/growpart/pvresize BEFORE playbook so 130GB of drivers+PPKGs fits during first-boot copy. Use tr -d "[:space:]" to avoid breaking outer bash -c single-quote wrap. - playbook: add executable: /bin/bash to Dell driver deploy (process substitution) and Blancco initramfs builder (brace expansion). - playbook: make "Ensure Samba user for Blancco reports" idempotent via pdbedit check so re-runs don't abort the play. - download-packages.sh: also download dist-upgrade package set. Explicit --simulate misses transitive version bumps (e.g. gnupg 17.4 needs matching gpgv 17.4) causing offline dpkg "dependency problems" when ISO baseline is older than noble-updates.
This commit is contained in:
@@ -72,6 +72,16 @@ autoinstall:
|
|||||||
curtin in-target --target=/target -- bash -c '
|
curtin in-target --target=/target -- bash -c '
|
||||||
cat <<"EOF" > /opt/first-boot.sh
|
cat <<"EOF" > /opt/first-boot.sh
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Expand root LV to full disk BEFORE playbook (playbook copies ~130GB of drivers+PPKGs)
|
||||||
|
ROOT_DEV=$(findmnt -n -o SOURCE /)
|
||||||
|
ROOT_DISK=$(lsblk -n -o PKNAME "$(readlink -f "$ROOT_DEV")" | tail -1)
|
||||||
|
PV_PART=$(pvs --noheadings -o pv_name 2>/dev/null | tr -d "[:space:]" | head -1)
|
||||||
|
if [ -n "$ROOT_DISK" ] && [ -n "$PV_PART" ]; then
|
||||||
|
PART_NUM=$(echo "$PV_PART" | grep -o "[0-9]*$")
|
||||||
|
growpart "/dev/${ROOT_DISK}" "${PART_NUM}" 2>&1 || true
|
||||||
|
pvresize "$PV_PART" 2>&1 || true
|
||||||
|
fi
|
||||||
|
lvextend -r -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv 2>&1 || true
|
||||||
CIDATA_DEV=$(blkid -L CIDATA)
|
CIDATA_DEV=$(blkid -L CIDATA)
|
||||||
if [ -n "$CIDATA_DEV" ]; then
|
if [ -n "$CIDATA_DEV" ]; then
|
||||||
mkdir -p /mnt/usb
|
mkdir -p /mnt/usb
|
||||||
@@ -88,7 +98,6 @@ autoinstall:
|
|||||||
umount /mnt/usb
|
umount /mnt/usb
|
||||||
fi
|
fi
|
||||||
sed -i "s|^/opt/first-boot.sh.*|# &|" /etc/rc.local
|
sed -i "s|^/opt/first-boot.sh.*|# &|" /etc/rc.local
|
||||||
lvextend -r -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv || true
|
|
||||||
EOF
|
EOF
|
||||||
chmod +x /opt/first-boot.sh
|
chmod +x /opt/first-boot.sh
|
||||||
'
|
'
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# download-packages.sh — Download all .deb packages needed for offline PXE server setup
|
# download-packages.sh - Download all .deb packages needed for offline PXE server setup
|
||||||
#
|
#
|
||||||
# Run this on a machine with internet access running Ubuntu 24.04 (Noble).
|
# The PXE server installs Ubuntu 24.04 (Noble), so all packages MUST come from the
|
||||||
# It downloads every .deb needed by the Ansible playbook into a local directory,
|
# 24.04 archive. If this script is run on a non-24.04 host (e.g. Zorin 17 / 22.04),
|
||||||
# which then gets bundled onto the installer USB.
|
# it auto-spawns an Ubuntu 24.04 docker container to do the download.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./download-packages.sh [output_directory]
|
# ./download-packages.sh [output_directory]
|
||||||
@@ -14,6 +14,40 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
OUT_DIR="${1:-./offline-packages}"
|
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="$(dirname "$SCRIPT_PATH")"
|
||||||
|
mkdir -p "$OUT_DIR_ABS"
|
||||||
|
|
||||||
|
docker run --rm -i \
|
||||||
|
-v "$REPO_DIR:/repo" \
|
||||||
|
-v "$OUT_DIR_ABS:/out" \
|
||||||
|
-e IN_DOCKER=1 \
|
||||||
|
-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/download-packages.sh /out"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "============================================"
|
||||||
|
echo "Container build complete. Files in: $OUT_DIR_ABS"
|
||||||
|
echo "============================================"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$OUT_DIR"
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
# Packages installed by the Ansible playbook (pxe_server_setup.yml)
|
# Packages installed by the Ansible playbook (pxe_server_setup.yml)
|
||||||
@@ -30,10 +64,12 @@ PLAYBOOK_PACKAGES=(
|
|||||||
grub-efi-amd64-bin
|
grub-efi-amd64-bin
|
||||||
grub-common
|
grub-common
|
||||||
conntrack
|
conntrack
|
||||||
|
busybox-static
|
||||||
|
zstd
|
||||||
|
cpio
|
||||||
)
|
)
|
||||||
|
|
||||||
# Packages installed during autoinstall late-commands (NetworkManager, WiFi, etc.)
|
# Packages installed during autoinstall late-commands (NetworkManager, WiFi, etc.)
|
||||||
# These are already in your ubuntu_playbook/*.deb files, but we can refresh them here too.
|
|
||||||
AUTOINSTALL_PACKAGES=(
|
AUTOINSTALL_PACKAGES=(
|
||||||
network-manager
|
network-manager
|
||||||
wpasupplicant
|
wpasupplicant
|
||||||
@@ -45,7 +81,7 @@ AUTOINSTALL_PACKAGES=(
|
|||||||
ALL_PACKAGES=("${PLAYBOOK_PACKAGES[@]}" "${AUTOINSTALL_PACKAGES[@]}")
|
ALL_PACKAGES=("${PLAYBOOK_PACKAGES[@]}" "${AUTOINSTALL_PACKAGES[@]}")
|
||||||
|
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo "Offline Package Downloader"
|
echo "Offline Package Downloader (Ubuntu 24.04 noble)"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo "Output directory: $OUT_DIR"
|
echo "Output directory: $OUT_DIR"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -54,18 +90,29 @@ printf ' - %s\n' "${ALL_PACKAGES[@]}"
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Update package cache
|
# Update package cache
|
||||||
echo "[1/3] Updating package cache..."
|
echo "[1/4] Updating package cache..."
|
||||||
sudo apt-get update -qq
|
sudo apt-get update -qq
|
||||||
|
|
||||||
# Simulate install to find all dependencies
|
# Simulate install to find all dependencies
|
||||||
echo "[2/3] Resolving dependencies..."
|
echo "[2/4] Resolving dependencies..."
|
||||||
DEPS=$(apt-get install --simulate "${ALL_PACKAGES[@]}" 2>&1 \
|
EXPLICIT_DEPS=$(apt-get install --simulate "${ALL_PACKAGES[@]}" 2>&1 \
|
||||||
| grep "^Inst " \
|
| grep "^Inst " \
|
||||||
| awk '{print $2}' \
|
| awk '{print $2}')
|
||||||
| sort -u)
|
|
||||||
|
# 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)
|
DEP_COUNT=$(echo "$DEPS" | wc -l)
|
||||||
echo " Found $DEP_COUNT packages (including dependencies)"
|
echo " Found $DEP_COUNT packages (explicit + baseline upgrades)"
|
||||||
|
|
||||||
# Download all packages
|
# Download all packages
|
||||||
echo "[3/4] Downloading .deb packages to $OUT_DIR..."
|
echo "[3/4] Downloading .deb packages to $OUT_DIR..."
|
||||||
@@ -79,7 +126,9 @@ echo " $DEB_COUNT packages ($TOTAL_SIZE)"
|
|||||||
|
|
||||||
# Download pip wheels for Flask webapp (offline install)
|
# Download pip wheels for Flask webapp (offline install)
|
||||||
echo "[4/4] Downloading Python wheels for webapp..."
|
echo "[4/4] Downloading Python wheels for webapp..."
|
||||||
PIP_DIR="$(dirname "$OUT_DIR")/pip-wheels"
|
# Place pip-wheels next to the script (or /repo when in docker), not next to OUT_DIR
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PIP_DIR="$SCRIPT_DIR/pip-wheels"
|
||||||
mkdir -p "$PIP_DIR"
|
mkdir -p "$PIP_DIR"
|
||||||
pip3 download -d "$PIP_DIR" flask lxml 2>&1 | tail -5
|
pip3 download -d "$PIP_DIR" flask lxml 2>&1 | tail -5
|
||||||
|
|
||||||
|
|||||||
@@ -307,9 +307,19 @@
|
|||||||
mode: '0777'
|
mode: '0777'
|
||||||
|
|
||||||
- name: "Deploy PPKG enrollment packages to enrollment share"
|
- name: "Deploy PPKG enrollment packages to enrollment share"
|
||||||
shell: cp -f {{ usb_mount }}/enrollment/*.ppkg /srv/samba/enrollment/ 2>/dev/null || true
|
shell: |
|
||||||
args:
|
set +e
|
||||||
warn: false
|
# Copy any whole PPKGs (small enough to fit on FAT32)
|
||||||
|
cp -f {{ usb_root }}/enrollment/*.ppkg /srv/samba/enrollment/ 2>/dev/null
|
||||||
|
# Reassemble any split files (foo.ppkg.part.00, .01, ... -> foo.ppkg)
|
||||||
|
for first in {{ usb_root }}/enrollment/*.part.00; do
|
||||||
|
[ -e "$first" ] || continue
|
||||||
|
base="${first%.part.00}"
|
||||||
|
name="$(basename "$base")"
|
||||||
|
echo "Reassembling $name from chunks..."
|
||||||
|
cat "${base}.part."* > "/srv/samba/enrollment/$name"
|
||||||
|
done
|
||||||
|
ls -lh /srv/samba/enrollment/*.ppkg 2>/dev/null
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "Deploy run-enrollment.ps1 to enrollment share"
|
- name: "Deploy run-enrollment.ps1 to enrollment share"
|
||||||
@@ -320,16 +330,29 @@
|
|||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "Deploy Dell driver packs to shared Out-of-box Drivers"
|
- name: "Deploy Dell driver packs to shared Out-of-box Drivers"
|
||||||
shell: >
|
|
||||||
if [ -d "{{ usb_mount }}/drivers" ]; then
|
|
||||||
mkdir -p "/srv/samba/winpeapps/_shared/Out-of-box Drivers/Dell_11"
|
|
||||||
cp -r {{ usb_mount }}/drivers/* "/srv/samba/winpeapps/_shared/Out-of-box Drivers/Dell_11/"
|
|
||||||
echo "Deployed Dell drivers from USB"
|
|
||||||
else
|
|
||||||
echo "No drivers/ on USB - skipping"
|
|
||||||
fi
|
|
||||||
args:
|
args:
|
||||||
warn: false
|
executable: /bin/bash
|
||||||
|
shell: |
|
||||||
|
set +e
|
||||||
|
SRC="{{ usb_root }}/drivers"
|
||||||
|
DEST="/srv/samba/winpeapps/_shared/Out-of-box Drivers/Dell_11"
|
||||||
|
if [ ! -d "$SRC" ]; then
|
||||||
|
echo "No drivers/ on USB - skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
# Copy everything except split chunks
|
||||||
|
rsync -a --exclude='*.part.*' "$SRC/" "$DEST/"
|
||||||
|
# Reassemble any split driver files
|
||||||
|
while IFS= read -r first; do
|
||||||
|
base="${first%.part.00}"
|
||||||
|
rel="${base#$SRC/}"
|
||||||
|
out="$DEST/$rel"
|
||||||
|
mkdir -p "$(dirname "$out")"
|
||||||
|
echo "Reassembling $rel from chunks..."
|
||||||
|
cat "${base}.part."* > "$out"
|
||||||
|
done < <(find "$SRC" -name '*.part.00')
|
||||||
|
echo "Deployed Dell drivers from USB"
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "Deploy shopfloor setup scripts to enrollment share"
|
- name: "Deploy shopfloor setup scripts to enrollment share"
|
||||||
@@ -372,6 +395,17 @@
|
|||||||
- models.txt
|
- models.txt
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Deploy BIOS update binaries from USB"
|
||||||
|
shell: >
|
||||||
|
if [ -d "{{ usb_root }}/bios" ]; then
|
||||||
|
cp -f {{ usb_root }}/bios/*.exe /srv/samba/enrollment/BIOS/ 2>/dev/null || true
|
||||||
|
count=$(find /srv/samba/enrollment/BIOS -name '*.exe' | wc -l)
|
||||||
|
echo "Deployed $count BIOS binaries"
|
||||||
|
else
|
||||||
|
echo "No bios/ on USB - skipping"
|
||||||
|
fi
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "Create image upload staging directory"
|
- name: "Create image upload staging directory"
|
||||||
file:
|
file:
|
||||||
path: /home/pxe/image-upload
|
path: /home/pxe/image-upload
|
||||||
@@ -579,13 +613,24 @@
|
|||||||
# Boot Ubuntu kernel, download Blancco rootfs, overlay mount, switch_root.
|
# Boot Ubuntu kernel, download Blancco rootfs, overlay mount, switch_root.
|
||||||
|
|
||||||
- name: "Build Blancco PXE initramfs"
|
- name: "Build Blancco PXE initramfs"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
creates: "{{ web_root }}/blancco/kexec-initrd.img"
|
||||||
shell: |
|
shell: |
|
||||||
set -e
|
set -e
|
||||||
WORK=$(mktemp -d)
|
WORK=$(mktemp -d)
|
||||||
mkdir -p "$WORK"/{bin,lib/modules,lib64,sbin,usr/share/udhcpc}
|
mkdir -p "$WORK"/{bin,lib/modules,lib64,sbin,usr/share/udhcpc}
|
||||||
|
|
||||||
# Busybox (static)
|
# Busybox (static) - bundled on USB at playbook/busybox-static
|
||||||
cp /bin/busybox "$WORK/bin/" 2>/dev/null || apt-get install -y busybox-static >/dev/null && cp /bin/busybox "$WORK/bin/"
|
if [ -f /bin/busybox ]; then
|
||||||
|
cp /bin/busybox "$WORK/bin/"
|
||||||
|
elif [ -f "{{ usb_root }}/playbook/busybox-static" ]; then
|
||||||
|
cp "{{ usb_root }}/playbook/busybox-static" "$WORK/bin/busybox"
|
||||||
|
chmod +x "$WORK/bin/busybox"
|
||||||
|
else
|
||||||
|
echo "ERROR: No busybox available (not at /bin/busybox or on USB)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
for cmd in sh awk cat chmod echo grep gunzip ifconfig ip ln losetup ls mkdir mknod mount reboot route sed sleep switch_root tar udhcpc umount wget cpio; do
|
for cmd in sh awk cat chmod echo grep gunzip ifconfig ip ln losetup ls mkdir mknod mount reboot route sed sleep switch_root tar udhcpc umount wget cpio; do
|
||||||
ln -sf busybox "$WORK/bin/$cmd"
|
ln -sf busybox "$WORK/bin/$cmd"
|
||||||
done
|
done
|
||||||
@@ -614,8 +659,7 @@
|
|||||||
find . | cpio -o -H newc 2>/dev/null | gzip > "{{ web_root }}/blancco/kexec-initrd.img"
|
find . | cpio -o -H newc 2>/dev/null | gzip > "{{ web_root }}/blancco/kexec-initrd.img"
|
||||||
rm -rf "$WORK"
|
rm -rf "$WORK"
|
||||||
echo "Built kexec-initrd.img: $(stat -c %s '{{ web_root }}/blancco/kexec-initrd.img') bytes"
|
echo "Built kexec-initrd.img: $(stat -c %s '{{ web_root }}/blancco/kexec-initrd.img') bytes"
|
||||||
args:
|
ignore_errors: yes
|
||||||
creates: "{{ web_root }}/blancco/kexec-initrd.img"
|
|
||||||
|
|
||||||
- name: "Copy Ubuntu kernel for Blancco PXE boot"
|
- name: "Copy Ubuntu kernel for Blancco PXE boot"
|
||||||
copy:
|
copy:
|
||||||
@@ -648,12 +692,11 @@
|
|||||||
args:
|
args:
|
||||||
creates: "{{ web_root }}/blancco/config-clean.xml"
|
creates: "{{ web_root }}/blancco/config-clean.xml"
|
||||||
|
|
||||||
- name: "Create Samba user for Blancco reports"
|
- name: "Ensure Samba user for Blancco reports exists (idempotent)"
|
||||||
shell: |
|
shell: |
|
||||||
id blancco 2>/dev/null || useradd -r -s /usr/sbin/nologin blancco
|
id blancco >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin blancco
|
||||||
echo -e "blancco\nblancco" | smbpasswd -a -s blancco 2>/dev/null
|
pdbedit -L 2>/dev/null | grep -q '^blancco:' || (echo -e "blancco\nblancco" | smbpasswd -a -s blancco)
|
||||||
args:
|
changed_when: false
|
||||||
creates: /etc/samba/smbpasswd
|
|
||||||
|
|
||||||
- name: "Check for WinPE deployment content on USB"
|
- name: "Check for WinPE deployment content on USB"
|
||||||
stat:
|
stat:
|
||||||
|
|||||||
Reference in New Issue
Block a user