Eliminate USB requirement for WinPE PXE boot, add image upload script
- Add startnet.cmd: FlatSetupLoader.exe + Boot.tag/Media.tag eliminates physical USB requirement for WinPE PXE deployment - Add Upload-Image.ps1: PowerShell script to robocopy MCL cached images to PXE server via SMB (Deploy, Tools, Sources) - Add gea-shopfloor-mce image type across playbook, webapp, startnet - Change webapp import to move (not copy) for upload sources to save disk - Add Samba symlink following config for shared image directories - Add Media.tag creation task in playbook for drive detection - Update prepare-boot-tools.sh with Blancco config/initramfs patching - Add grub-efi-amd64-bin to download-packages.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
164
Upload-Image.ps1
Normal file
164
Upload-Image.ps1
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#
|
||||||
|
# Upload-Image.ps1 — Copy Media Creator Lite cached image to the PXE server
|
||||||
|
#
|
||||||
|
# Copies Deploy/, Tools/, and Sources (from Boot/Sources.zip) to the
|
||||||
|
# PXE server's image-upload share using robocopy with authentication.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# .\Upload-Image.ps1 (uses default MCL cache path)
|
||||||
|
# .\Upload-Image.ps1 -CachePath "D:\MCL\Cache" (custom cache location)
|
||||||
|
# .\Upload-Image.ps1 -Server 10.9.100.1 (custom server IP)
|
||||||
|
#
|
||||||
|
# After upload, use the PXE webapp (http://10.9.100.1:9009) to import
|
||||||
|
# the uploaded content into the desired image type.
|
||||||
|
#
|
||||||
|
|
||||||
|
param(
|
||||||
|
[string]$CachePath = "C:\ProgramData\GEAerospace\MediaCreator\Cache",
|
||||||
|
[string]$Server = "10.9.100.1",
|
||||||
|
[string]$User = "pxe-upload",
|
||||||
|
[string]$Pass = "pxe",
|
||||||
|
[switch]$IncludeDell10
|
||||||
|
)
|
||||||
|
|
||||||
|
$Share = "\\$Server\image-upload"
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host " PXE Server Image Uploader" -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# --- Validate source paths ---
|
||||||
|
$DeployPath = Join-Path $CachePath "Deploy"
|
||||||
|
$ToolsPath = Join-Path (Split-Path $CachePath -Parent) "Tools"
|
||||||
|
# Tools is a sibling of Cache in the MCL directory structure
|
||||||
|
if (-not (Test-Path $ToolsPath -PathType Container)) {
|
||||||
|
# Fallback: try Tools inside CachePath parent's parent
|
||||||
|
$ToolsPath = Join-Path (Split-Path (Split-Path $CachePath -Parent) -Parent) "Tools"
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $ToolsPath -PathType Container)) {
|
||||||
|
$ToolsPath = "C:\ProgramData\GEAerospace\MediaCreator\Tools"
|
||||||
|
}
|
||||||
|
$SourcesZip = Join-Path $CachePath "Boot\Sources.zip"
|
||||||
|
|
||||||
|
if (-not (Test-Path $DeployPath -PathType Container)) {
|
||||||
|
Write-Host "ERROR: Deploy directory not found at $DeployPath" -ForegroundColor Red
|
||||||
|
Write-Host " Provide the correct cache path: .\Upload-Image.ps1 -CachePath ""D:\Path\To\Cache""" -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Cache Path: $CachePath"
|
||||||
|
Write-Host " Deploy: $DeployPath" -ForegroundColor $(if (Test-Path $DeployPath) { "Green" } else { "Red" })
|
||||||
|
Write-Host " Tools: $ToolsPath" -ForegroundColor $(if (Test-Path $ToolsPath) { "Green" } else { "Yellow" })
|
||||||
|
Write-Host " Sources.zip: $SourcesZip" -ForegroundColor $(if (Test-Path $SourcesZip) { "Green" } else { "Yellow" })
|
||||||
|
Write-Host " Server: $Server"
|
||||||
|
if (-not $IncludeDell10) {
|
||||||
|
Write-Host " Excluding: Dell_10 drivers (use -IncludeDell10 to include)" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# --- Connect to SMB share ---
|
||||||
|
Write-Host "Connecting to $Share ..." -ForegroundColor Gray
|
||||||
|
|
||||||
|
# Remove any stale connection
|
||||||
|
net use $Share /delete 2>$null | Out-Null
|
||||||
|
|
||||||
|
$netResult = net use $Share /user:$User $Pass 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "ERROR: Could not connect to $Share" -ForegroundColor Red
|
||||||
|
Write-Host $netResult -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Make sure:" -ForegroundColor Yellow
|
||||||
|
Write-Host " - The PXE server is running at $Server" -ForegroundColor Yellow
|
||||||
|
Write-Host " - This PC is on the 10.9.100.x network" -ForegroundColor Yellow
|
||||||
|
Write-Host " - Samba is running on the PXE server" -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "Connected." -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
$failed = $false
|
||||||
|
|
||||||
|
# --- Step 1: Copy Deploy/ ---
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "[1/3] Copying Deploy/ ..." -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$robocopyArgs = @($DeployPath, "$Share\Deploy", "/E", "/R:3", "/W:5", "/NP", "/ETA")
|
||||||
|
if (-not $IncludeDell10) {
|
||||||
|
$robocopyArgs += @("/XD", "Dell_10")
|
||||||
|
}
|
||||||
|
& robocopy @robocopyArgs
|
||||||
|
if ($LASTEXITCODE -ge 8) {
|
||||||
|
Write-Host "ERROR: Deploy copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||||
|
$failed = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Step 2: Copy Tools/ ---
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "[2/3] Copying Tools/ ..." -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
if (Test-Path $ToolsPath -PathType Container) {
|
||||||
|
robocopy $ToolsPath "$Share\Tools" /E /R:3 /W:5 /NP /ETA
|
||||||
|
if ($LASTEXITCODE -ge 8) {
|
||||||
|
Write-Host "ERROR: Tools copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||||
|
$failed = $true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "SKIPPED: Tools directory not found at $ToolsPath" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Step 3: Extract and copy Sources/ ---
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "[3/3] Extracting and copying Sources/ ..." -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
if (Test-Path $SourcesZip) {
|
||||||
|
$TempExtract = Join-Path $env:TEMP "SourcesExtract"
|
||||||
|
Write-Host " Extracting Sources.zip..."
|
||||||
|
Remove-Item -Recurse -Force $TempExtract -ErrorAction SilentlyContinue
|
||||||
|
Expand-Archive $SourcesZip -DestinationPath $TempExtract -Force
|
||||||
|
|
||||||
|
# Handle nested Sources folder (zip may contain Sources/ at root)
|
||||||
|
$TempSources = $TempExtract
|
||||||
|
if ((Test-Path (Join-Path $TempExtract "Sources")) -and -not (Test-Path (Join-Path $TempExtract "Diskpart"))) {
|
||||||
|
$TempSources = Join-Path $TempExtract "Sources"
|
||||||
|
}
|
||||||
|
|
||||||
|
robocopy $TempSources "$Share\Sources" /E /R:3 /W:5 /NP /ETA
|
||||||
|
if ($LASTEXITCODE -ge 8) {
|
||||||
|
Write-Host "ERROR: Sources copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||||
|
$failed = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean up temp extraction
|
||||||
|
Remove-Item -Recurse -Force $TempExtract -ErrorAction SilentlyContinue
|
||||||
|
} else {
|
||||||
|
Write-Host "SKIPPED: Sources.zip not found at $SourcesZip" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Disconnect ---
|
||||||
|
net use $Share /delete 2>$null | Out-Null
|
||||||
|
|
||||||
|
# --- Summary ---
|
||||||
|
Write-Host ""
|
||||||
|
if ($failed) {
|
||||||
|
Write-Host "========================================" -ForegroundColor Red
|
||||||
|
Write-Host " Upload completed with errors." -ForegroundColor Red
|
||||||
|
Write-Host "========================================" -ForegroundColor Red
|
||||||
|
} else {
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host " Upload complete!" -ForegroundColor Green
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Next steps:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 1. Open the PXE webapp: http://$Server`:9009" -ForegroundColor White
|
||||||
|
Write-Host " 2. Go to Image Import" -ForegroundColor White
|
||||||
|
Write-Host " 3. Select source 'image-upload' and target image type" -ForegroundColor White
|
||||||
|
Write-Host " 4. Click Import" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
@@ -27,6 +27,8 @@ PLAYBOOK_PACKAGES=(
|
|||||||
cron
|
cron
|
||||||
wimtools
|
wimtools
|
||||||
p7zip-full
|
p7zip-full
|
||||||
|
grub-efi-amd64-bin
|
||||||
|
grub-common
|
||||||
)
|
)
|
||||||
|
|
||||||
# Packages installed during autoinstall late-commands (NetworkManager, WiFi, etc.)
|
# Packages installed during autoinstall late-commands (NetworkManager, WiFi, etc.)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
- gea-standard
|
- gea-standard
|
||||||
- gea-engineer
|
- gea-engineer
|
||||||
- gea-shopfloor
|
- gea-shopfloor
|
||||||
|
- gea-shopfloor-mce
|
||||||
- ge-standard
|
- ge-standard
|
||||||
- ge-engineer
|
- ge-engineer
|
||||||
- ge-shopfloor-lockdown
|
- ge-shopfloor-lockdown
|
||||||
@@ -95,6 +96,33 @@
|
|||||||
debug:
|
debug:
|
||||||
msg: "Using {{ pxe_iface }} for DHCP/TFTP"
|
msg: "Using {{ pxe_iface }} for DHCP/TFTP"
|
||||||
|
|
||||||
|
- name: "Expand root partition and filesystem to use full disk"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
shell: |
|
||||||
|
# Find the root LV device
|
||||||
|
ROOT_DEV=$(findmnt -n -o SOURCE /)
|
||||||
|
ROOT_DISK=$(lsblk -n -o PKNAME $(readlink -f "$ROOT_DEV") | tail -1)
|
||||||
|
if [ -z "$ROOT_DISK" ]; then
|
||||||
|
echo "Could not determine root disk, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Find the partition number for the LVM PV
|
||||||
|
PV_PART=$(pvs --noheadings -o pv_name | tr -d ' ' | head -1)
|
||||||
|
if [ -z "$PV_PART" ]; then
|
||||||
|
echo "No LVM PV found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
PART_NUM=$(echo "$PV_PART" | grep -o '[0-9]*$')
|
||||||
|
echo "Expanding /dev/${ROOT_DISK} partition ${PART_NUM} (${PV_PART})..."
|
||||||
|
growpart "/dev/${ROOT_DISK}" "${PART_NUM}" 2>&1 || true
|
||||||
|
pvresize "$PV_PART" 2>&1
|
||||||
|
lvextend -l +100%FREE "$ROOT_DEV" 2>&1 || true
|
||||||
|
resize2fs "$ROOT_DEV" 2>&1
|
||||||
|
echo "Disk: $(df -h / | tail -1)"
|
||||||
|
register: disk_expand
|
||||||
|
changed_when: "'CHANGED' in disk_expand.stdout or 'resized' in disk_expand.stdout"
|
||||||
|
|
||||||
- name: "Configure dnsmasq for DHCP and TFTP"
|
- name: "Configure dnsmasq for DHCP and TFTP"
|
||||||
copy:
|
copy:
|
||||||
dest: /etc/dnsmasq.conf
|
dest: /etc/dnsmasq.conf
|
||||||
@@ -158,9 +186,9 @@
|
|||||||
menu GE Aerospace PXE Boot Menu
|
menu GE Aerospace PXE Boot Menu
|
||||||
item --gap -- ---- Windows Deployment ----
|
item --gap -- ---- Windows Deployment ----
|
||||||
item winpe Windows PE (Image Deployment)
|
item winpe Windows PE (Image Deployment)
|
||||||
item --gap -- ---- Utilities ----
|
item --gap -- ---- Utilities (Secure Boot must be DISABLED) ----
|
||||||
item clonezilla Clonezilla Live (Disk Imaging)
|
|
||||||
item blancco Blancco Drive Eraser
|
item blancco Blancco Drive Eraser
|
||||||
|
item clonezilla Clonezilla Live (Disk Imaging)
|
||||||
item memtest Memtest86+ (Memory Diagnostics)
|
item memtest Memtest86+ (Memory Diagnostics)
|
||||||
item --gap -- ----
|
item --gap -- ----
|
||||||
item reboot Reboot
|
item reboot Reboot
|
||||||
@@ -168,6 +196,13 @@
|
|||||||
choose --default winpe --timeout 30000 target && goto ${target}
|
choose --default winpe --timeout 30000 target && goto ${target}
|
||||||
|
|
||||||
:winpe
|
:winpe
|
||||||
|
echo
|
||||||
|
echo Windows deployment requires Secure Boot to be ENABLED.
|
||||||
|
echo If you disabled it for Blancco/Clonezilla, re-enable it now.
|
||||||
|
echo
|
||||||
|
prompt --timeout 5000 Press any key to continue (auto-boot in 5s)... && goto winpe_boot || goto winpe_boot
|
||||||
|
|
||||||
|
:winpe_boot
|
||||||
kernel http://${server}/win11/wimboot gui
|
kernel http://${server}/win11/wimboot gui
|
||||||
initrd http://${server}/win11/EFI/Microsoft/Boot/boot.stl EFI/Microsoft/Boot/Boot.stl
|
initrd http://${server}/win11/EFI/Microsoft/Boot/boot.stl EFI/Microsoft/Boot/Boot.stl
|
||||||
initrd http://${server}/win11/EFI/Microsoft/Boot/BCD EFI/Microsoft/Boot/BCD
|
initrd http://${server}/win11/EFI/Microsoft/Boot/BCD EFI/Microsoft/Boot/BCD
|
||||||
@@ -178,20 +213,34 @@
|
|||||||
|
|
||||||
:clonezilla
|
:clonezilla
|
||||||
set base http://${server}/clonezilla
|
set base http://${server}/clonezilla
|
||||||
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales= keyboard-layouts= ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs
|
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales= keyboard-layouts= ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs || goto secureboot_warn
|
||||||
initrd ${base}/initrd.img
|
initrd ${base}/initrd.img
|
||||||
boot
|
boot
|
||||||
|
|
||||||
:blancco
|
:blancco
|
||||||
set bbase http://${server}/blancco
|
chain http://${server}/blancco/grubx64.efi || goto secureboot_warn
|
||||||
kernel ${bbase}/vmlinuz-bde-linux archisobasedir=arch archiso_http_srv=http://${server}/blancco/ copytoram=y cow_spacesize=50% memtest=00 vmalloc=400M ip=dhcp quiet nomodeset libata.allow_tpm=1
|
|
||||||
initrd ${bbase}/intel-ucode.img ${bbase}/amd-ucode.img ${bbase}/config.img ${bbase}/initramfs-bde-linux.img
|
|
||||||
boot
|
|
||||||
|
|
||||||
:memtest
|
:memtest
|
||||||
kernel http://${server}/memtest/memtest.efi
|
kernel http://${server}/memtest/memtest.efi || goto secureboot_warn
|
||||||
boot
|
boot
|
||||||
|
|
||||||
|
:secureboot_warn
|
||||||
|
echo
|
||||||
|
echo ======================================================
|
||||||
|
echo This option requires Secure Boot to be DISABLED.
|
||||||
|
echo
|
||||||
|
echo 1. Reboot this machine
|
||||||
|
echo 2. Press F2 / Del to enter BIOS Setup
|
||||||
|
echo 3. Disable Secure Boot
|
||||||
|
echo 4. Save and exit BIOS
|
||||||
|
echo 5. PXE boot again and select this option
|
||||||
|
echo
|
||||||
|
echo Re-enable Secure Boot after completing the task.
|
||||||
|
echo ======================================================
|
||||||
|
echo
|
||||||
|
prompt Press any key to return to menu...
|
||||||
|
goto menu
|
||||||
|
|
||||||
:reboot
|
:reboot
|
||||||
reboot
|
reboot
|
||||||
|
|
||||||
@@ -248,6 +297,25 @@
|
|||||||
state: directory
|
state: directory
|
||||||
mode: '0777'
|
mode: '0777'
|
||||||
|
|
||||||
|
- name: "Create image upload staging directory"
|
||||||
|
file:
|
||||||
|
path: /home/pxe/image-upload
|
||||||
|
state: directory
|
||||||
|
mode: '0777'
|
||||||
|
owner: pxe
|
||||||
|
group: pxe
|
||||||
|
|
||||||
|
- name: "Enable Samba symlink following (shared image dirs)"
|
||||||
|
blockinfile:
|
||||||
|
path: /etc/samba/smb.conf
|
||||||
|
backup: yes
|
||||||
|
marker: "# {mark} MANAGED - GLOBAL SYMLINKS"
|
||||||
|
insertafter: '\[global\]'
|
||||||
|
block: |
|
||||||
|
follow symlinks = yes
|
||||||
|
wide links = yes
|
||||||
|
unix extensions = no
|
||||||
|
|
||||||
- name: "Configure Samba shares"
|
- name: "Configure Samba shares"
|
||||||
blockinfile:
|
blockinfile:
|
||||||
path: /etc/samba/smb.conf
|
path: /etc/samba/smb.conf
|
||||||
@@ -257,22 +325,48 @@
|
|||||||
path = {{ samba_share }}
|
path = {{ samba_share }}
|
||||||
browseable = yes
|
browseable = yes
|
||||||
read only = no
|
read only = no
|
||||||
guest ok = yes
|
guest ok = no
|
||||||
|
valid users = pxe-upload
|
||||||
|
force user = root
|
||||||
|
|
||||||
[clonezilla]
|
[clonezilla]
|
||||||
path = /srv/samba/clonezilla
|
path = /srv/samba/clonezilla
|
||||||
browseable = yes
|
browseable = yes
|
||||||
read only = no
|
read only = no
|
||||||
guest ok = yes
|
guest ok = no
|
||||||
|
valid users = pxe-upload
|
||||||
|
force user = root
|
||||||
comment = Clonezilla backup images
|
comment = Clonezilla backup images
|
||||||
|
|
||||||
[blancco-reports]
|
[blancco-reports]
|
||||||
path = /srv/samba/blancco-reports
|
path = /srv/samba/blancco-reports
|
||||||
browseable = yes
|
browseable = yes
|
||||||
read only = no
|
read only = no
|
||||||
guest ok = yes
|
guest ok = no
|
||||||
|
valid users = pxe-upload blancco
|
||||||
|
force user = root
|
||||||
comment = Blancco Drive Eraser reports
|
comment = Blancco Drive Eraser reports
|
||||||
|
|
||||||
|
[image-upload]
|
||||||
|
path = /home/pxe/image-upload
|
||||||
|
browseable = yes
|
||||||
|
read only = no
|
||||||
|
guest ok = no
|
||||||
|
valid users = pxe-upload
|
||||||
|
force user = pxe
|
||||||
|
force group = pxe
|
||||||
|
comment = PXE image upload staging area
|
||||||
|
|
||||||
|
- name: "Create Samba users (pxe-upload and blancco)"
|
||||||
|
shell: |
|
||||||
|
id pxe-upload >/dev/null 2>&1 || useradd -M -s /usr/sbin/nologin pxe-upload
|
||||||
|
echo -e 'pxe\npxe' | smbpasswd -a pxe-upload -s
|
||||||
|
id blancco >/dev/null 2>&1 || useradd -M -s /usr/sbin/nologin blancco
|
||||||
|
echo -e 'blancco\nblancco' | smbpasswd -a blancco -s
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: "Create image-type top-level directories"
|
- name: "Create image-type top-level directories"
|
||||||
file:
|
file:
|
||||||
path: "{{ samba_share }}/{{ item }}"
|
path: "{{ samba_share }}/{{ item }}"
|
||||||
@@ -289,6 +383,14 @@
|
|||||||
- "{{ image_types }}"
|
- "{{ image_types }}"
|
||||||
- "{{ deploy_subdirs }}"
|
- "{{ deploy_subdirs }}"
|
||||||
|
|
||||||
|
- name: "Create Media.tag for FlatSetupLoader.exe drive detection"
|
||||||
|
copy:
|
||||||
|
content: ""
|
||||||
|
dest: "{{ samba_share }}/{{ item }}/Deploy/Control/Media.tag"
|
||||||
|
mode: '0644'
|
||||||
|
force: no
|
||||||
|
loop: "{{ image_types }}"
|
||||||
|
|
||||||
- name: "Copy WinPE & boot files from USB (skipped if not present)"
|
- name: "Copy WinPE & boot files from USB (skipped if not present)"
|
||||||
copy:
|
copy:
|
||||||
src: "{{ usb_root }}/{{ item.src }}"
|
src: "{{ usb_root }}/{{ item.src }}"
|
||||||
@@ -303,6 +405,20 @@
|
|||||||
- { src: "boot.wim", dest: "sources/boot.wim" }
|
- { src: "boot.wim", dest: "sources/boot.wim" }
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Inject startnet.cmd into boot.wim (virtual BOOT/MEDIA volumes)"
|
||||||
|
shell: |
|
||||||
|
WIM="{{ web_root }}/win11/sources/boot.wim"
|
||||||
|
STARTNET="{{ usb_mount }}/startnet.cmd"
|
||||||
|
if [ -f "$WIM" ] && [ -f "$STARTNET" ]; then
|
||||||
|
echo "add $STARTNET /Windows/System32/startnet.cmd" | wimupdate "$WIM" 1
|
||||||
|
echo "Updated startnet.cmd in boot.wim"
|
||||||
|
else
|
||||||
|
echo "Skipped: boot.wim or startnet.cmd not found"
|
||||||
|
fi
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "Copy iPXE binaries from USB (skipped if not present)"
|
- name: "Copy iPXE binaries from USB (skipped if not present)"
|
||||||
copy:
|
copy:
|
||||||
src: "{{ usb_root }}/{{ item }}"
|
src: "{{ usb_root }}/{{ item }}"
|
||||||
@@ -320,6 +436,25 @@
|
|||||||
- blancco
|
- blancco
|
||||||
- memtest
|
- memtest
|
||||||
|
|
||||||
|
- name: "Create TFTP blancco directory for GRUB boot"
|
||||||
|
file:
|
||||||
|
path: "{{ tftp_dir }}/blancco"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: "Symlink Blancco boot files to TFTP (GRUB loads via TFTP)"
|
||||||
|
file:
|
||||||
|
src: "{{ web_root }}/blancco/{{ item }}"
|
||||||
|
dest: "{{ tftp_dir }}/blancco/{{ item }}"
|
||||||
|
state: link
|
||||||
|
force: yes
|
||||||
|
loop:
|
||||||
|
- vmlinuz-bde-linux
|
||||||
|
- intel-ucode.img
|
||||||
|
- amd-ucode.img
|
||||||
|
- config.img
|
||||||
|
- initramfs-bde-linux.img
|
||||||
|
|
||||||
- name: "Check for WinPE deployment content on USB"
|
- name: "Check for WinPE deployment content on USB"
|
||||||
stat:
|
stat:
|
||||||
path: "{{ usb_root }}/images"
|
path: "{{ usb_root }}/images"
|
||||||
|
|||||||
105
playbook/startnet.cmd
Normal file
105
playbook/startnet.cmd
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
@echo off
|
||||||
|
echo Please wait while 'WinPE' is being processed. This may take a few seconds.
|
||||||
|
wpeinit
|
||||||
|
powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
|
||||||
|
|
||||||
|
:menu
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo WinPE Setup Menu
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
echo Please select an option:
|
||||||
|
echo.
|
||||||
|
echo 1. GEA Standard
|
||||||
|
echo 2. GEA Engineer
|
||||||
|
echo 3. GEA Shopfloor
|
||||||
|
echo 4. GEA Shopfloor MCE
|
||||||
|
echo 5. GE Standard
|
||||||
|
echo 6. GE Engineer
|
||||||
|
echo 7. GE Shopfloor Lockdown
|
||||||
|
echo 8. GE Shopfloor MCE
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
set /p choice=Enter your choice (1-8):
|
||||||
|
|
||||||
|
echo. > X:\Boot.tag
|
||||||
|
if "%choice%"=="1" goto gea-standard
|
||||||
|
if "%choice%"=="2" goto gea-engineer
|
||||||
|
if "%choice%"=="3" goto gea-shopfloor
|
||||||
|
if "%choice%"=="4" goto gea-shopfloor-mce
|
||||||
|
if "%choice%"=="5" goto ge-standard
|
||||||
|
if "%choice%"=="6" goto ge-engineer
|
||||||
|
if "%choice%"=="7" goto ge-shopfloor-lockdown
|
||||||
|
if "%choice%"=="8" goto ge-shopfloor-mce
|
||||||
|
echo Invalid choice. Please try again.
|
||||||
|
pause
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:gea-standard
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Standard setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-standard /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:gea-engineer
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Engineer setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-engineer /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:gea-shopfloor
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Shopfloor setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-shopfloor /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:gea-shopfloor-mce
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Shopfloor MCE setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-shopfloor-mce /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-standard
|
||||||
|
echo.
|
||||||
|
echo Starting GE Standard setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-standard /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-engineer
|
||||||
|
echo.
|
||||||
|
echo Starting GE Engineer setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-engineer /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-shopfloor-lockdown
|
||||||
|
echo.
|
||||||
|
echo Starting GE Shopfloor Lockdown setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-lockdown /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-shopfloor-mce
|
||||||
|
echo.
|
||||||
|
echo Starting GE Shopfloor MCE setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-mce /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:end
|
||||||
@@ -105,7 +105,10 @@ if [ -n "$BLANCCO_ISO" ] && [ -f "$BLANCCO_ISO" ]; then
|
|||||||
cp "$TMPDIR/arch/x86_64/airootfs.sfs" "$OUT_DIR/blancco/arch/x86_64/"
|
cp "$TMPDIR/arch/x86_64/airootfs.sfs" "$OUT_DIR/blancco/arch/x86_64/"
|
||||||
echo " Extracted Blancco boot files."
|
echo " Extracted Blancco boot files."
|
||||||
|
|
||||||
# Patch config.img to auto-save reports to PXE server Samba share
|
# --- Patch config.img (size-preserving) ---
|
||||||
|
# config.img is a CPIO archive containing preferences.xml (padded to 32768 bytes).
|
||||||
|
# The CPIO itself must remain exactly 194560 bytes (380 x 512-byte blocks).
|
||||||
|
# We use Python for byte-level replacement to preserve exact sizes.
|
||||||
if [ -f "$OUT_DIR/blancco/config.img" ]; then
|
if [ -f "$OUT_DIR/blancco/config.img" ]; then
|
||||||
echo " Patching config.img for network report storage..."
|
echo " Patching config.img for network report storage..."
|
||||||
CFGTMP=$(mktemp -d)
|
CFGTMP=$(mktemp -d)
|
||||||
@@ -113,19 +116,149 @@ if [ -n "$BLANCCO_ISO" ] && [ -f "$BLANCCO_ISO" ]; then
|
|||||||
cpio -id < "$OUT_DIR/blancco/config.img" 2>/dev/null
|
cpio -id < "$OUT_DIR/blancco/config.img" 2>/dev/null
|
||||||
|
|
||||||
if [ -f "$CFGTMP/preferences.xml" ]; then
|
if [ -f "$CFGTMP/preferences.xml" ]; then
|
||||||
# Set network share to PXE server's blancco-reports Samba share
|
ORIG_SIZE=$(stat -c%s "$CFGTMP/preferences.xml")
|
||||||
sed -i 's|<hostname></hostname>|<hostname>10.9.100.1</hostname>|' "$CFGTMP/preferences.xml"
|
|
||||||
sed -i 's|<path></path>|<path>blancco-reports</path>|' "$CFGTMP/preferences.xml"
|
|
||||||
# Enable auto-backup of reports to the network share
|
|
||||||
sed -i 's|<auto_backup>false</auto_backup>|<auto_backup>true</auto_backup>|' "$CFGTMP/preferences.xml"
|
|
||||||
|
|
||||||
# Repack config.img
|
python3 << 'PYEOF'
|
||||||
ls -1 | cpio -o -H newc > "$OUT_DIR/blancco/config.img" 2>/dev/null
|
import sys
|
||||||
echo " Reports will auto-save to \\\\10.9.100.1\\blancco-reports"
|
|
||||||
|
with open("preferences.xml", "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
orig_size = len(data)
|
||||||
|
|
||||||
|
# Set SMB share credentials and path
|
||||||
|
data = data.replace(
|
||||||
|
b'<username encrypted="false"></username>',
|
||||||
|
b'<username encrypted="false">blancco</username>'
|
||||||
|
)
|
||||||
|
data = data.replace(
|
||||||
|
b'<password encrypted="false"></password>',
|
||||||
|
b'<password encrypted="false">blancco</password>'
|
||||||
|
)
|
||||||
|
data = data.replace(
|
||||||
|
b'<hostname></hostname>',
|
||||||
|
b'<hostname>10.9.100.1</hostname>'
|
||||||
|
)
|
||||||
|
data = data.replace(
|
||||||
|
b'<path></path>',
|
||||||
|
b'<path>blancco-reports</path>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable auto-backup
|
||||||
|
data = data.replace(
|
||||||
|
b'<auto_backup>false</auto_backup>',
|
||||||
|
b'<auto_backup>true</auto_backup>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable bootable report
|
||||||
|
data = data.replace(
|
||||||
|
b'<bootable_report>\n <enabled>false</enabled>\n </bootable_report>',
|
||||||
|
b'<bootable_report>\n <enabled>true</enabled>\n </bootable_report>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Maintain exact file size by trimming trailing padding/whitespace
|
||||||
|
diff = len(data) - orig_size
|
||||||
|
if diff > 0:
|
||||||
|
# The file has trailing whitespace/padding before the final XML closing tags
|
||||||
|
# Trim from the padding area (spaces before closing comment or end of file)
|
||||||
|
end_pos = data.rfind(b'<!-- ')
|
||||||
|
if end_pos > 0:
|
||||||
|
comment_end = data.find(b' -->', end_pos)
|
||||||
|
if comment_end > 0:
|
||||||
|
data = data[:comment_end - diff] + data[comment_end:]
|
||||||
|
if len(data) > orig_size:
|
||||||
|
# Fallback: trim trailing whitespace
|
||||||
|
data = data.rstrip()
|
||||||
|
data = data + b'\n' * (orig_size - len(data))
|
||||||
|
elif diff < 0:
|
||||||
|
# Pad with spaces to maintain size
|
||||||
|
data = data[:-1] + b' ' * (-diff) + data[-1:]
|
||||||
|
|
||||||
|
if len(data) != orig_size:
|
||||||
|
print(f" WARNING: Size mismatch ({len(data)} vs {orig_size}), padding to match")
|
||||||
|
if len(data) > orig_size:
|
||||||
|
data = data[:orig_size]
|
||||||
|
else:
|
||||||
|
data = data + b'\x00' * (orig_size - len(data))
|
||||||
|
|
||||||
|
with open("preferences.xml", "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
print(f" preferences.xml: {orig_size} bytes (preserved)")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
# Repack CPIO with exact 512-byte block alignment (194560 bytes)
|
||||||
|
ls -1 "$CFGTMP" | (cd "$CFGTMP" && cpio -o -H newc 2>/dev/null) | \
|
||||||
|
dd bs=512 conv=sync 2>/dev/null > "$OUT_DIR/blancco/config.img"
|
||||||
|
echo " Reports: SMB blancco@10.9.100.1/blancco-reports, bootable report enabled"
|
||||||
fi
|
fi
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
rm -rf "$CFGTMP"
|
rm -rf "$CFGTMP"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- Patch initramfs to keep network interfaces up after copytoram ---
|
||||||
|
# Blancco uses copytoram=y which triggers archiso_pxe_common latehook to
|
||||||
|
# flush all network interfaces. Binary-patch the check from "y" to "N" so
|
||||||
|
# the condition never matches. IMPORTANT: full extract/repack BREAKS booting.
|
||||||
|
echo " Patching initramfs to preserve network after copytoram..."
|
||||||
|
python3 << PYEOF
|
||||||
|
import lzma, sys, os
|
||||||
|
|
||||||
|
initramfs = "$OUT_DIR/blancco/initramfs-bde-linux.img"
|
||||||
|
|
||||||
|
with open(initramfs, "rb") as f:
|
||||||
|
compressed = f.read()
|
||||||
|
|
||||||
|
# Decompress XZ stream
|
||||||
|
try:
|
||||||
|
raw = lzma.decompress(compressed)
|
||||||
|
except lzma.LZMAError:
|
||||||
|
print(" WARNING: Could not decompress initramfs (not XZ?), skipping patch")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Binary patch: change the copytoram check from "y" to "N"
|
||||||
|
old = b'"y" ]; then\n for curif in /sys/class/net'
|
||||||
|
new = b'"N" ]; then\n for curif in /sys/class/net'
|
||||||
|
|
||||||
|
if old not in raw:
|
||||||
|
# Try alternate pattern with different whitespace
|
||||||
|
old = b'"\${copytoram}" = "y"'
|
||||||
|
new = b'"\${copytoram}" = "N"'
|
||||||
|
|
||||||
|
if old in raw:
|
||||||
|
raw = raw.replace(old, new, 1)
|
||||||
|
# Recompress with same XZ settings as archiso
|
||||||
|
recompressed = lzma.compress(raw, format=lzma.FORMAT_XZ,
|
||||||
|
check=lzma.CHECK_CRC32,
|
||||||
|
preset=6)
|
||||||
|
with open(initramfs, "wb") as f:
|
||||||
|
f.write(recompressed)
|
||||||
|
print(" initramfs patched: copytoram network flush disabled")
|
||||||
|
else:
|
||||||
|
print(" WARNING: copytoram pattern not found in initramfs, skipping patch")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
# --- Build GRUB EFI binary for Blancco chainload ---
|
||||||
|
# Broadcom iPXE can't pass initrd to Linux kernels in UEFI mode.
|
||||||
|
# Solution: iPXE chains to grubx64.efi, which loads kernel+initrd via TFTP.
|
||||||
|
GRUB_CFG="$SCRIPT_DIR/boot-tools/blancco/grub-blancco.cfg"
|
||||||
|
if [ -f "$GRUB_CFG" ]; then
|
||||||
|
if command -v grub-mkstandalone &>/dev/null; then
|
||||||
|
echo " Building grubx64.efi (GRUB chainload for Blancco)..."
|
||||||
|
grub-mkstandalone \
|
||||||
|
--format=x86_64-efi \
|
||||||
|
--output="$OUT_DIR/blancco/grubx64.efi" \
|
||||||
|
--modules="linux normal echo net efinet tftp chain sleep" \
|
||||||
|
"boot/grub/grub.cfg=$GRUB_CFG" 2>/dev/null
|
||||||
|
echo " Built grubx64.efi ($(du -h "$OUT_DIR/blancco/grubx64.efi" | cut -f1))"
|
||||||
|
else
|
||||||
|
echo " WARNING: grub-mkstandalone not found. Install grub-efi-amd64-bin:"
|
||||||
|
echo " sudo apt install grub-efi-amd64-bin grub-common"
|
||||||
|
echo " Then re-run this script to build grubx64.efi"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " WARNING: grub-blancco.cfg not found at $GRUB_CFG"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo " Could not extract boot files from ISO."
|
echo " Could not extract boot files from ISO."
|
||||||
fi
|
fi
|
||||||
|
|||||||
105
startnet-template.cmd
Normal file
105
startnet-template.cmd
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
@echo off
|
||||||
|
echo Please wait while 'WinPE' is being processed. This may take a few seconds.
|
||||||
|
wpeinit
|
||||||
|
powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
|
||||||
|
|
||||||
|
:menu
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo WinPE Setup Menu
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
echo Please select an option:
|
||||||
|
echo.
|
||||||
|
echo 1. GEA Standard
|
||||||
|
echo 2. GEA Engineer
|
||||||
|
echo 3. GEA Shopfloor
|
||||||
|
echo 4. GEA Shopfloor MCE
|
||||||
|
echo 5. GE Standard
|
||||||
|
echo 6. GE Engineer
|
||||||
|
echo 7. GE Shopfloor Lockdown
|
||||||
|
echo 8. GE Shopfloor MCE
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
set /p choice=Enter your choice (1-8):
|
||||||
|
|
||||||
|
echo. > X:\Boot.tag
|
||||||
|
if "%choice%"=="1" goto gea-standard
|
||||||
|
if "%choice%"=="2" goto gea-engineer
|
||||||
|
if "%choice%"=="3" goto gea-shopfloor
|
||||||
|
if "%choice%"=="4" goto gea-shopfloor-mce
|
||||||
|
if "%choice%"=="5" goto ge-standard
|
||||||
|
if "%choice%"=="6" goto ge-engineer
|
||||||
|
if "%choice%"=="7" goto ge-shopfloor-lockdown
|
||||||
|
if "%choice%"=="8" goto ge-shopfloor-mce
|
||||||
|
echo Invalid choice. Please try again.
|
||||||
|
pause
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:gea-standard
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Standard setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-standard /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:gea-engineer
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Engineer setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-engineer /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:gea-shopfloor
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Shopfloor setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-shopfloor /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:gea-shopfloor-mce
|
||||||
|
echo.
|
||||||
|
echo Starting GEA Shopfloor MCE setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\gea-shopfloor-mce /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-standard
|
||||||
|
echo.
|
||||||
|
echo Starting GE Standard setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-standard /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-engineer
|
||||||
|
echo.
|
||||||
|
echo Starting GE Engineer setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-engineer /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-shopfloor-lockdown
|
||||||
|
echo.
|
||||||
|
echo Starting GE Shopfloor Lockdown setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-lockdown /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:ge-shopfloor-mce
|
||||||
|
echo.
|
||||||
|
echo Starting GE Shopfloor MCE setup...
|
||||||
|
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
|
||||||
|
for /l %%i in (1,1,2000000) do rem
|
||||||
|
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-mce /persistent:no
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:end
|
||||||
@@ -72,6 +72,7 @@ IMAGE_TYPES = [
|
|||||||
"gea-standard",
|
"gea-standard",
|
||||||
"gea-engineer",
|
"gea-engineer",
|
||||||
"gea-shopfloor",
|
"gea-shopfloor",
|
||||||
|
"gea-shopfloor-mce",
|
||||||
"ge-standard",
|
"ge-standard",
|
||||||
"ge-engineer",
|
"ge-engineer",
|
||||||
"ge-shopfloor-lockdown",
|
"ge-shopfloor-lockdown",
|
||||||
@@ -82,6 +83,7 @@ FRIENDLY_NAMES = {
|
|||||||
"gea-standard": "GE Aerospace Standard",
|
"gea-standard": "GE Aerospace Standard",
|
||||||
"gea-engineer": "GE Aerospace Engineer",
|
"gea-engineer": "GE Aerospace Engineer",
|
||||||
"gea-shopfloor": "GE Aerospace Shop Floor",
|
"gea-shopfloor": "GE Aerospace Shop Floor",
|
||||||
|
"gea-shopfloor-mce": "GE Aerospace Shop Floor MCE",
|
||||||
"ge-standard": "GE Legacy Standard",
|
"ge-standard": "GE Legacy Standard",
|
||||||
"ge-engineer": "GE Legacy Engineer",
|
"ge-engineer": "GE Legacy Engineer",
|
||||||
"ge-shopfloor-lockdown": "GE Legacy Shop Floor Lockdown",
|
"ge-shopfloor-lockdown": "GE Legacy Shop Floor Lockdown",
|
||||||
@@ -213,8 +215,9 @@ def find_upload_sources():
|
|||||||
return sources
|
return sources
|
||||||
|
|
||||||
|
|
||||||
def _import_deploy(src_deploy, dst_deploy, target=""):
|
def _import_deploy(src_deploy, dst_deploy, target="", move=False):
|
||||||
"""Copy Deploy directory contents, merging shared subdirs into _shared."""
|
"""Import Deploy directory contents, merging shared subdirs into _shared.
|
||||||
|
When move=True, files are moved instead of copied (saves disk space)."""
|
||||||
# Build list of scoped shared dirs for this target
|
# Build list of scoped shared dirs for this target
|
||||||
scoped_shared = []
|
scoped_shared = []
|
||||||
prefix_key = ""
|
prefix_key = ""
|
||||||
@@ -224,20 +227,23 @@ def _import_deploy(src_deploy, dst_deploy, target=""):
|
|||||||
prefix_key = prefix
|
prefix_key = prefix
|
||||||
break
|
break
|
||||||
|
|
||||||
|
_transfer = shutil.move if move else shutil.copy2
|
||||||
|
_transfer_tree = shutil.move if move else shutil.copytree
|
||||||
|
|
||||||
os.makedirs(dst_deploy, exist_ok=True)
|
os.makedirs(dst_deploy, exist_ok=True)
|
||||||
for item in os.listdir(src_deploy):
|
for item in os.listdir(src_deploy):
|
||||||
src_item = os.path.join(src_deploy, item)
|
src_item = os.path.join(src_deploy, item)
|
||||||
dst_item = os.path.join(dst_deploy, item)
|
dst_item = os.path.join(dst_deploy, item)
|
||||||
|
|
||||||
if not os.path.isdir(src_item):
|
if not os.path.isdir(src_item):
|
||||||
shutil.copy2(src_item, dst_item)
|
_transfer(src_item, dst_item)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Global shared (e.g., Out-of-box Drivers) — one copy for all
|
# Global shared (e.g., Out-of-box Drivers) — one copy for all
|
||||||
if item in SHARED_DEPLOY_GLOBAL:
|
if item in SHARED_DEPLOY_GLOBAL:
|
||||||
shared_dest = os.path.join(SHARED_DIR, item)
|
shared_dest = os.path.join(SHARED_DIR, item)
|
||||||
os.makedirs(shared_dest, exist_ok=True)
|
os.makedirs(shared_dest, exist_ok=True)
|
||||||
_merge_tree(src_item, shared_dest)
|
_merge_tree(src_item, shared_dest, move=move)
|
||||||
_replace_with_symlink(dst_item, shared_dest)
|
_replace_with_symlink(dst_item, shared_dest)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -245,14 +251,14 @@ def _import_deploy(src_deploy, dst_deploy, target=""):
|
|||||||
if item in scoped_shared:
|
if item in scoped_shared:
|
||||||
shared_dest = os.path.join(SHARED_DIR, f"{prefix_key}{item}")
|
shared_dest = os.path.join(SHARED_DIR, f"{prefix_key}{item}")
|
||||||
os.makedirs(shared_dest, exist_ok=True)
|
os.makedirs(shared_dest, exist_ok=True)
|
||||||
_merge_tree(src_item, shared_dest)
|
_merge_tree(src_item, shared_dest, move=move)
|
||||||
_replace_with_symlink(dst_item, shared_dest)
|
_replace_with_symlink(dst_item, shared_dest)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Normal copy
|
# Normal transfer
|
||||||
if os.path.exists(dst_item):
|
if os.path.exists(dst_item):
|
||||||
shutil.rmtree(dst_item)
|
shutil.rmtree(dst_item)
|
||||||
shutil.copytree(src_item, dst_item)
|
_transfer_tree(src_item, dst_item)
|
||||||
|
|
||||||
|
|
||||||
def _replace_with_symlink(link_path, target_path):
|
def _replace_with_symlink(link_path, target_path):
|
||||||
@@ -264,21 +270,24 @@ def _replace_with_symlink(link_path, target_path):
|
|||||||
os.symlink(target_path, link_path)
|
os.symlink(target_path, link_path)
|
||||||
|
|
||||||
|
|
||||||
def _merge_tree(src, dst):
|
def _merge_tree(src, dst, move=False):
|
||||||
"""Recursively merge src tree into dst, overwriting existing files."""
|
"""Recursively merge src tree into dst, overwriting existing files.
|
||||||
|
When move=True, files are moved instead of copied."""
|
||||||
|
_transfer = shutil.move if move else shutil.copy2
|
||||||
|
_transfer_tree = shutil.move if move else shutil.copytree
|
||||||
for item in os.listdir(src):
|
for item in os.listdir(src):
|
||||||
s = os.path.join(src, item)
|
s = os.path.join(src, item)
|
||||||
d = os.path.join(dst, item)
|
d = os.path.join(dst, item)
|
||||||
if os.path.isdir(s):
|
if os.path.isdir(s):
|
||||||
if os.path.isdir(d):
|
if os.path.isdir(d):
|
||||||
_merge_tree(s, d)
|
_merge_tree(s, d, move=move)
|
||||||
else:
|
else:
|
||||||
if os.path.exists(d):
|
if os.path.exists(d):
|
||||||
os.remove(d)
|
os.remove(d)
|
||||||
shutil.copytree(s, d)
|
_transfer_tree(s, d)
|
||||||
else:
|
else:
|
||||||
os.makedirs(os.path.dirname(d), exist_ok=True)
|
os.makedirs(os.path.dirname(d), exist_ok=True)
|
||||||
shutil.copy2(s, d)
|
_transfer(s, d)
|
||||||
|
|
||||||
|
|
||||||
def allowed_import_source(source):
|
def allowed_import_source(source):
|
||||||
@@ -659,6 +668,11 @@ def images_import():
|
|||||||
os.makedirs(dest, exist_ok=True)
|
os.makedirs(dest, exist_ok=True)
|
||||||
src_items = os.listdir(source)
|
src_items = os.listdir(source)
|
||||||
|
|
||||||
|
# Move files from network upload to save disk space; copy from USB
|
||||||
|
use_move = source == UPLOAD_DIR or source.startswith(UPLOAD_DIR + "/")
|
||||||
|
_transfer = shutil.move if use_move else shutil.copy2
|
||||||
|
_transfer_tree = shutil.move if use_move else shutil.copytree
|
||||||
|
|
||||||
# Detect layout: if source has Deploy/, Sources/, Tools/ at top
|
# Detect layout: if source has Deploy/, Sources/, Tools/ at top
|
||||||
# level, it's the full image root structure (USB-style).
|
# level, it's the full image root structure (USB-style).
|
||||||
# Otherwise treat it as Deploy/ contents directly.
|
# Otherwise treat it as Deploy/ contents directly.
|
||||||
@@ -673,18 +687,18 @@ def images_import():
|
|||||||
shared_root = dirs
|
shared_root = dirs
|
||||||
break
|
break
|
||||||
|
|
||||||
# Full image root: copy Deploy contents + sibling dirs
|
# Full image root: import Deploy contents + sibling dirs
|
||||||
for item in src_items:
|
for item in src_items:
|
||||||
src_item = os.path.join(source, item)
|
src_item = os.path.join(source, item)
|
||||||
if item == "Deploy":
|
if item == "Deploy":
|
||||||
_import_deploy(src_item, dest, target)
|
_import_deploy(src_item, dest, target, move=use_move)
|
||||||
elif os.path.isdir(src_item) and item in shared_root:
|
elif os.path.isdir(src_item) and item in shared_root:
|
||||||
# Shared sibling: merge into _shared/{prefix}{item}
|
# Shared sibling: merge into _shared/{prefix}{item}
|
||||||
# and symlink from image root
|
# and symlink from image root
|
||||||
prefix_key = target.split("-")[0] + "-"
|
prefix_key = target.split("-")[0] + "-"
|
||||||
shared_dest = os.path.join(SHARED_DIR, f"{prefix_key}{item}")
|
shared_dest = os.path.join(SHARED_DIR, f"{prefix_key}{item}")
|
||||||
os.makedirs(shared_dest, exist_ok=True)
|
os.makedirs(shared_dest, exist_ok=True)
|
||||||
_merge_tree(src_item, shared_dest)
|
_merge_tree(src_item, shared_dest, move=use_move)
|
||||||
dst_item = os.path.join(root, item)
|
dst_item = os.path.join(root, item)
|
||||||
if os.path.islink(dst_item):
|
if os.path.islink(dst_item):
|
||||||
os.remove(dst_item)
|
os.remove(dst_item)
|
||||||
@@ -696,12 +710,12 @@ def images_import():
|
|||||||
dst_item = os.path.join(root, item)
|
dst_item = os.path.join(root, item)
|
||||||
if os.path.exists(dst_item):
|
if os.path.exists(dst_item):
|
||||||
shutil.rmtree(dst_item)
|
shutil.rmtree(dst_item)
|
||||||
shutil.copytree(src_item, dst_item)
|
_transfer_tree(src_item, dst_item)
|
||||||
else:
|
else:
|
||||||
shutil.copy2(src_item, os.path.join(root, item))
|
_transfer(src_item, os.path.join(root, item))
|
||||||
else:
|
else:
|
||||||
# Flat layout: treat source as Deploy contents
|
# Flat layout: treat source as Deploy contents
|
||||||
_import_deploy(source, dest, target)
|
_import_deploy(source, dest, target, move=use_move)
|
||||||
|
|
||||||
audit("IMAGE_IMPORT", f"{source} -> {target}")
|
audit("IMAGE_IMPORT", f"{source} -> {target}")
|
||||||
flash(
|
flash(
|
||||||
|
|||||||
Reference in New Issue
Block a user