#!/bin/bash # sync-preinstall.sh - Push preinstall.json + installer binaries to the live PXE server. # # Run this on the workstation (not on the PXE server) any time: # - You update preinstall.json in playbook/preinstall/ # - You update installer binaries in /home/camp/pxe-images/main/ # # The PXE server's /srv/samba/enrollment/preinstall/ tree is updated in place. The # next imaged PC picks up the new files via startnet.cmd's xcopy during WinPE phase. # # Usage: # ./playbook/sync-preinstall.sh # # Requires: sshpass (apt install sshpass), scp, ssh, tar set -euo pipefail # --- Config --- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PXE_HOST="${PXE_HOST:-10.9.100.1}" PXE_USER="${PXE_USER:-pxe}" PXE_PASS="${PXE_PASS:-pxe}" PREINSTALL_JSON="$PROJECT_ROOT/playbook/preinstall/preinstall.json" PXE_IMAGES_DIR="${PXE_IMAGES_DIR:-/home/camp/pxe-images/main}" REMOTE_DIR="/srv/samba/enrollment/preinstall" REMOTE_INSTALLERS="$REMOTE_DIR/installers" REMOTE_TEMP="/tmp/preinstall-stage" # Subtrees copied recursively into installers/, preserving directory structure. # vcredist/ holds per-version MSI+CAB pairs (one subdir per version) extracted from # the Microsoft bootstrappers. The bootstrappers ignore /norestart and trigger # immediate Windows reboots; the extracted MSIs honor REBOOT=ReallySuppress and do # not. Each MSI's CAB filename ('vc_red.cab' for 2008/2010, 'cab1.cab' for the rest) # is hardcoded in the MSI's Media table - do not rename. TREE_SUBDIRS=( "dependencies/vcredist" ) # Individual files copied flat (basename only) into installers/. FLAT_INSTALLERS=( "machineapps/UDC_Setup.exe" "globalassets/oracleclient/Oracle 10.2.0.3.msi" ) # --- Helpers --- ssh_run() { sshpass -p "$PXE_PASS" ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "$PXE_USER@$PXE_HOST" "$@" } scp_to() { sshpass -p "$PXE_PASS" scp -o StrictHostKeyChecking=no -o LogLevel=ERROR "$1" "$PXE_USER@$PXE_HOST:'$2'" } # --- Validate sources --- echo "Validating source files..." if [ ! -f "$PREINSTALL_JSON" ]; then echo "ERROR: preinstall.json not found at $PREINSTALL_JSON" >&2 exit 1 fi missing=0 for tree in "${TREE_SUBDIRS[@]}"; do src="$PXE_IMAGES_DIR/$tree" if [ ! -d "$src" ]; then echo " MISSING: $src (directory)" >&2 missing=$((missing + 1)) else size=$(du -sb "$src" | cut -f1) printf " OK %10d %s/ (tree)\n" "$size" "$tree" fi done for rel in "${FLAT_INSTALLERS[@]}"; do src="$PXE_IMAGES_DIR/$rel" if [ ! -f "$src" ]; then echo " MISSING: $src" >&2 missing=$((missing + 1)) else printf " OK %10d %s\n" "$(stat -c %s "$src")" "$rel" fi done if [ "$missing" -gt 0 ]; then echo "ERROR: $missing source(s) missing in $PXE_IMAGES_DIR" >&2 exit 1 fi # --- Verify PXE server reachable --- echo "Pinging PXE server $PXE_HOST..." if ! ping -c 1 -W 2 "$PXE_HOST" >/dev/null 2>&1; then echo "ERROR: PXE server $PXE_HOST not reachable" >&2 exit 1 fi # --- Build local tarball of all TREE_SUBDIRS --- LOCAL_TARBALL="$(mktemp /tmp/preinstall-trees.XXXXXX.tar)" trap 'rm -f "$LOCAL_TARBALL" "${LOCAL_TEMP_SCRIPT:-}"' EXIT echo "Building local tarball of subtrees..." tar_args=() for tree in "${TREE_SUBDIRS[@]}"; do parent_dir="$PXE_IMAGES_DIR/$(dirname "$tree")" leaf="$(basename "$tree")" tar_args+=( -C "$parent_dir" "$leaf" ) done tar cf "$LOCAL_TARBALL" "${tar_args[@]}" echo " -> $(stat -c %s "$LOCAL_TARBALL") bytes" # --- Stage to /tmp on PXE, then sudo install --- echo "Staging files to $PXE_HOST:$REMOTE_TEMP..." ssh_run "rm -rf $REMOTE_TEMP && mkdir -p $REMOTE_TEMP/flat" # preinstall.json echo " -> preinstall.json" scp_to "$PREINSTALL_JSON" "$REMOTE_TEMP/preinstall.json" # tarball of subtrees echo " -> trees.tar" scp_to "$LOCAL_TARBALL" "$REMOTE_TEMP/trees.tar" # flat installers (preserve filenames including spaces, all into flat/) for rel in "${FLAT_INSTALLERS[@]}"; do src="$PXE_IMAGES_DIR/$rel" base="$(basename "$rel")" echo " -> $base" scp_to "$src" "$REMOTE_TEMP/flat/$base" done # --- Build remote install script (runs under sudo on PXE) --- LOCAL_TEMP_SCRIPT="$(mktemp /tmp/sync-preinstall-remote.XXXXXX.sh)" cat > "$LOCAL_TEMP_SCRIPT" < /srv/samba/enrollment/preinstall/preinstall.json cp "$REMOTE_TEMP/preinstall.json" "$REMOTE_DIR/preinstall.json" chmod 0644 "$REMOTE_DIR/preinstall.json" chown root:root "$REMOTE_DIR/preinstall.json" # Remove legacy flat VC++ files (replaced by vcredist/ subdir tree). These were the # pre-Burn-extraction layout where 2008's MSI/CAB sat at the root and 2010/2012/2013/ # 2015 used bootstrappers directly. All four now live under vcredist//. rm -f "$REMOTE_INSTALLERS/vc_red.msi" \ "$REMOTE_INSTALLERS/vc_red.cab" \ "$REMOTE_INSTALLERS/vcredist2008_x86.exe" \ "$REMOTE_INSTALLERS/vcredist2010_x86.exe" \ "$REMOTE_INSTALLERS/vcredist2012_x86.exe" \ "$REMOTE_INSTALLERS/vcredist2013_x86.exe" \ "$REMOTE_INSTALLERS/vcredist2015_2017_2019_2022_x86.exe" # Extract tree tarball into installers/ (preserves subdirs) tar xf "$REMOTE_TEMP/trees.tar" -C "$REMOTE_INSTALLERS/" # Flat installers -> installers/ shopt -s dotglob nullglob for f in "$REMOTE_TEMP/flat/"*; do base="\$(basename "\$f")" cp "\$f" "$REMOTE_INSTALLERS/\$base" done # Normalize ownership and perms (files 0644, dirs 0755, all root:root) chown -R root:root "$REMOTE_INSTALLERS" find "$REMOTE_INSTALLERS" -type f -exec chmod 0644 {} + find "$REMOTE_INSTALLERS" -type d -exec chmod 0755 {} + rm -rf "$REMOTE_TEMP" echo echo "Final state of $REMOTE_DIR:" ls -la "$REMOTE_DIR" echo echo "Final state of $REMOTE_INSTALLERS:" ls -la "$REMOTE_INSTALLERS" echo echo "vcredist tree:" find "$REMOTE_INSTALLERS/vcredist" -type f -printf '%10s %p\n' 2>/dev/null | sort -k2 REMOTE_SCRIPT # Stage the install script alongside the data files scp_to "$LOCAL_TEMP_SCRIPT" "$REMOTE_TEMP/_install.sh" # Execute remotely with sudo echo "Installing files to $REMOTE_DIR (sudo)..." ssh_run "echo '$PXE_PASS' | sudo -S bash $REMOTE_TEMP/_install.sh" echo echo "Done. Preinstall bundle synced to $PXE_HOST:$REMOTE_DIR"