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:
@@ -72,6 +72,7 @@ IMAGE_TYPES = [
|
||||
"gea-standard",
|
||||
"gea-engineer",
|
||||
"gea-shopfloor",
|
||||
"gea-shopfloor-mce",
|
||||
"ge-standard",
|
||||
"ge-engineer",
|
||||
"ge-shopfloor-lockdown",
|
||||
@@ -82,6 +83,7 @@ FRIENDLY_NAMES = {
|
||||
"gea-standard": "GE Aerospace Standard",
|
||||
"gea-engineer": "GE Aerospace Engineer",
|
||||
"gea-shopfloor": "GE Aerospace Shop Floor",
|
||||
"gea-shopfloor-mce": "GE Aerospace Shop Floor MCE",
|
||||
"ge-standard": "GE Legacy Standard",
|
||||
"ge-engineer": "GE Legacy Engineer",
|
||||
"ge-shopfloor-lockdown": "GE Legacy Shop Floor Lockdown",
|
||||
@@ -213,8 +215,9 @@ def find_upload_sources():
|
||||
return sources
|
||||
|
||||
|
||||
def _import_deploy(src_deploy, dst_deploy, target=""):
|
||||
"""Copy Deploy directory contents, merging shared subdirs into _shared."""
|
||||
def _import_deploy(src_deploy, dst_deploy, target="", move=False):
|
||||
"""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
|
||||
scoped_shared = []
|
||||
prefix_key = ""
|
||||
@@ -224,20 +227,23 @@ def _import_deploy(src_deploy, dst_deploy, target=""):
|
||||
prefix_key = prefix
|
||||
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)
|
||||
for item in os.listdir(src_deploy):
|
||||
src_item = os.path.join(src_deploy, item)
|
||||
dst_item = os.path.join(dst_deploy, item)
|
||||
|
||||
if not os.path.isdir(src_item):
|
||||
shutil.copy2(src_item, dst_item)
|
||||
_transfer(src_item, dst_item)
|
||||
continue
|
||||
|
||||
# Global shared (e.g., Out-of-box Drivers) — one copy for all
|
||||
if item in SHARED_DEPLOY_GLOBAL:
|
||||
shared_dest = os.path.join(SHARED_DIR, item)
|
||||
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)
|
||||
continue
|
||||
|
||||
@@ -245,14 +251,14 @@ def _import_deploy(src_deploy, dst_deploy, target=""):
|
||||
if item in scoped_shared:
|
||||
shared_dest = os.path.join(SHARED_DIR, f"{prefix_key}{item}")
|
||||
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)
|
||||
continue
|
||||
|
||||
# Normal copy
|
||||
# Normal transfer
|
||||
if os.path.exists(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):
|
||||
@@ -264,21 +270,24 @@ def _replace_with_symlink(link_path, target_path):
|
||||
os.symlink(target_path, link_path)
|
||||
|
||||
|
||||
def _merge_tree(src, dst):
|
||||
"""Recursively merge src tree into dst, overwriting existing files."""
|
||||
def _merge_tree(src, dst, move=False):
|
||||
"""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):
|
||||
s = os.path.join(src, item)
|
||||
d = os.path.join(dst, item)
|
||||
if os.path.isdir(s):
|
||||
if os.path.isdir(d):
|
||||
_merge_tree(s, d)
|
||||
_merge_tree(s, d, move=move)
|
||||
else:
|
||||
if os.path.exists(d):
|
||||
os.remove(d)
|
||||
shutil.copytree(s, d)
|
||||
_transfer_tree(s, d)
|
||||
else:
|
||||
os.makedirs(os.path.dirname(d), exist_ok=True)
|
||||
shutil.copy2(s, d)
|
||||
_transfer(s, d)
|
||||
|
||||
|
||||
def allowed_import_source(source):
|
||||
@@ -659,6 +668,11 @@ def images_import():
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
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
|
||||
# level, it's the full image root structure (USB-style).
|
||||
# Otherwise treat it as Deploy/ contents directly.
|
||||
@@ -673,18 +687,18 @@ def images_import():
|
||||
shared_root = dirs
|
||||
break
|
||||
|
||||
# Full image root: copy Deploy contents + sibling dirs
|
||||
# Full image root: import Deploy contents + sibling dirs
|
||||
for item in src_items:
|
||||
src_item = os.path.join(source, item)
|
||||
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:
|
||||
# Shared sibling: merge into _shared/{prefix}{item}
|
||||
# and symlink from image root
|
||||
prefix_key = target.split("-")[0] + "-"
|
||||
shared_dest = os.path.join(SHARED_DIR, f"{prefix_key}{item}")
|
||||
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)
|
||||
if os.path.islink(dst_item):
|
||||
os.remove(dst_item)
|
||||
@@ -696,12 +710,12 @@ def images_import():
|
||||
dst_item = os.path.join(root, item)
|
||||
if os.path.exists(dst_item):
|
||||
shutil.rmtree(dst_item)
|
||||
shutil.copytree(src_item, dst_item)
|
||||
_transfer_tree(src_item, dst_item)
|
||||
else:
|
||||
shutil.copy2(src_item, os.path.join(root, item))
|
||||
_transfer(src_item, os.path.join(root, item))
|
||||
else:
|
||||
# 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}")
|
||||
flash(
|
||||
|
||||
Reference in New Issue
Block a user