webapp/imaging: rewind detection + WinPE-phase status push

services/imaging_status.py - if a new POST arrives with stage_index <= 1
that is lower than the cached stage_index, OR the previous run already
finished (status=succeeded|failed), reset the session: clear log_tail,
mint a fresh started_at, drop the status field so the in_progress
default re-applies. Preserves serial + records the previous run's
last_updated under previous_run_at for audit. Without this, a reimage
on the same bay would leave a stale 6/8 "succeeded" card visible until
the new run progressed past that index.

playbook/startnet.cmd - one-line PowerShell POST after the PXE menu
choice + enrollment-share mount, before PESetup.exe waits to start.
Captures BIOS serial via wmic, MAC via Get-NetAdapter, and posts:
  stage_index=2, current_stage="WinPE: PESetup / WIM apply".
Best-effort; try/catch swallows any network failure so a missing
webapp never blocks imaging. PXE clients will now appear on the
/imaging dashboard during WinPE phase instead of only post-PPKG.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-13 11:11:03 -04:00
parent 908b668bde
commit 4e018feaa0
2 changed files with 65 additions and 12 deletions

View File

@@ -85,6 +85,24 @@ if "%ges_choice%"=="7" set PCTYPE=gea-shopfloor-heattreat
if "%ges_choice%"=="8" set PCTYPE=gea-shopfloor-waxtrace if "%ges_choice%"=="8" set PCTYPE=gea-shopfloor-waxtrace
if "%ges_choice%"=="9" set PCTYPE=gea-shopfloor-display if "%ges_choice%"=="9" set PCTYPE=gea-shopfloor-display
if "%PCTYPE%"=="" goto gea_shopfloor_submenu if "%PCTYPE%"=="" goto gea_shopfloor_submenu
if "%PCTYPE%"=="gea-shopfloor-display" goto display_submenu
goto enroll_menu
:display_submenu
cls
echo.
echo ========================================
echo Display Kiosk Sub-Type
echo ========================================
echo.
echo 1. Dashboard (shop floor metrics dashboard)
echo 2. Lobby Display (lobby information screen)
echo.
set DISPLAYTYPE=
set /p disp_choice=Enter your choice (1-2):
if "%disp_choice%"=="1" set DISPLAYTYPE=Dashboard
if "%disp_choice%"=="2" set DISPLAYTYPE=Lobby
if "%DISPLAYTYPE%"=="" goto display_submenu
goto enroll_menu goto enroll_menu
:enroll_menu :enroll_menu
@@ -107,8 +125,8 @@ REM --- PPKG configuration (constructed at menu time, see docs) ---
REM Vendor ships one source PPKG; we construct the BPRT-tagged filename REM Vendor ships one source PPKG; we construct the BPRT-tagged filename
REM by filling in Office, Region, Expiry, Version on the target copy. REM by filling in Office, Region, Expiry, Version on the target copy.
REM Update SOURCE_PPKG + PPKG_VER when a new PPKG is released. REM Update SOURCE_PPKG + PPKG_VER when a new PPKG is released.
set SOURCE_PPKG=GCCH_Prod_SFLD_v4.12.ppkg set SOURCE_PPKG=GCCH_Prod_SFLD_v4.14.ppkg
set PPKG_VER=v4.12 set PPKG_VER=v4.14
set PPKG_EXP=20260831 set PPKG_EXP=20260831
set REGION=US set REGION=US
@@ -125,13 +143,12 @@ set PPKG=
if not "%OFFICE%"=="" set PPKG=GCCH_Prod_SFLD_%OFFICE%_%REGION%_Exp_%PPKG_EXP%_%PPKG_VER%.ppkg if not "%OFFICE%"=="" set PPKG=GCCH_Prod_SFLD_%OFFICE%_%REGION%_Exp_%PPKG_EXP%_%PPKG_VER%.ppkg
REM --- 2026-05-04 rename reorg: PCTYPE is set by gea_shopfloor_submenu above REM --- 2026-05-04 rename reorg: PCTYPE is set by gea_shopfloor_submenu above
REM (single string, e.g. gea-shopfloor-collections). Old per-type submenus REM (single string, e.g. gea-shopfloor-collections). pc-subtype.txt is no
REM (Standard sub-type, Display sub-type) are gone - flatten via the new REM longer written. DISPLAYTYPE IS still written (display-type.txt) when
REM gea-shopfloor-* names. PCSUBTYPE / DISPLAYTYPE are no longer written REM PCTYPE=gea-shopfloor-display because Install-KioskApp.cmd needs Lobby
REM (pc-subtype.txt / display-type.txt deprecated). Configure-PC.ps1 still REM vs Dashboard to choose installer + Get-PCProfile builds Display-{type}
REM looks up site-config.json profiles via the single full string. REM profile key.
set PCSUBTYPE= set PCSUBTYPE=
set DISPLAYTYPE=
REM --- Machine number (collections + nocollections only; other variants don't use one) --- REM --- Machine number (collections + nocollections only; other variants don't use one) ---
set MACHINENUM=9999 set MACHINENUM=9999
@@ -228,6 +245,15 @@ goto end
:end :end
echo. echo.
REM --- Push initial "WinPE staging" status to PXE webapp ---
REM Best-effort POST so the imaging dashboard shows this bay during the
REM WinPE / WIM-apply phase, BEFORE Run-ShopfloorSetup.ps1 takes over the
REM status updates post-PPKG. Identifies the session by BIOS serial.
REM Errors are swallowed - never block imaging on a status push.
for /f "tokens=2 delims==" %%S in ('wmic bios get serialnumber /value 2^>nul ^| find "="') do set BIOS_SERIAL=%%S
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $body = @{ serial=$env:BIOS_SERIAL; mac=((Get-NetAdapter -Physical | Where-Object { $_.Status -eq 'Up' } | Select-Object -First 1).MacAddress -replace '-',':'); pctype=$env:PCTYPE; current_stage='WinPE: PESetup / WIM apply'; stage_index=2; stage_total=8; status='in_progress' } | ConvertTo-Json -Compress; Invoke-WebRequest -Uri 'http://10.9.100.1:9009/imaging/status' -Method POST -Body $body -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null } catch { }"
echo Waiting for PESetup.exe to start... echo Waiting for PESetup.exe to start...
:wait_start :wait_start
ping -n 3 127.0.0.1 >NUL ping -n 3 127.0.0.1 >NUL
@@ -279,10 +305,11 @@ echo Manual fallback created at W:\enroll.cmd
REM --- Copy shopfloor PC type setup scripts --- REM --- Copy shopfloor PC type setup scripts ---
if "%PCTYPE%"=="" goto cleanup_enroll if "%PCTYPE%"=="" goto cleanup_enroll
echo %PCTYPE%> W:\Enrollment\pc-type.txt echo %PCTYPE%> W:\Enrollment\pc-type.txt
REM 2026-05-04 rename reorg: pc-subtype.txt and display-type.txt no longer REM 2026-05-04 rename reorg: pc-subtype.txt no longer written.
REM written. PCTYPE is a single full string ("gea-shopfloor-collections", REM display-type.txt IS still written for gea-shopfloor-display because
REM "gea-shopfloor-display", etc.). pcSubType-aware code paths fall back REM Install-KioskApp.cmd reads it to pick Lobby vs Dashboard installer
REM to empty string and are handled as alias-resolution-only. REM and Get-PCProfile.ps1 reads it to build the Display-{type} profile key.
if not "%DISPLAYTYPE%"=="" echo %DISPLAYTYPE%> W:\Enrollment\display-type.txt
if not "%MACHINENUM%"=="" echo %MACHINENUM%> W:\Enrollment\machine-number.txt if not "%MACHINENUM%"=="" echo %MACHINENUM%> W:\Enrollment\machine-number.txt
copy /Y "Y:\shopfloor-setup\Run-ShopfloorSetup.ps1" "W:\Enrollment\Run-ShopfloorSetup.ps1" copy /Y "Y:\shopfloor-setup\Run-ShopfloorSetup.ps1" "W:\Enrollment\Run-ShopfloorSetup.ps1"
REM --- Always copy Shopfloor baseline scripts --- REM --- Always copy Shopfloor baseline scripts ---
@@ -299,6 +326,13 @@ if exist "Y:\shopfloor-setup\common" (
xcopy /E /Y /I "Y:\shopfloor-setup\common" "W:\Enrollment\shopfloor-setup\common\" xcopy /E /Y /I "Y:\shopfloor-setup\common" "W:\Enrollment\shopfloor-setup\common\"
echo Copied common setup files. echo Copied common setup files.
) )
REM --- Copy _ntlars-backups (147 per-bay .reg files restored by gea-shopfloor-{collections,nocollections}\03-RestoreEDncConfig.ps1) ---
REM Same root level as common/, referenced by 03-RestoreEDncConfig.ps1 via Join-Path $PSScriptRoot '..\_ntlars-backups'.
if exist "Y:\shopfloor-setup\_ntlars-backups" (
mkdir W:\Enrollment\shopfloor-setup\_ntlars-backups 2>NUL
xcopy /E /Y /I "Y:\shopfloor-setup\_ntlars-backups" "W:\Enrollment\shopfloor-setup\_ntlars-backups\"
echo Copied _ntlars-backups.
)
REM --- Copy type-specific scripts on top of baseline --- REM --- Copy type-specific scripts on top of baseline ---
if exist "Y:\shopfloor-setup\%PCTYPE%" ( if exist "Y:\shopfloor-setup\%PCTYPE%" (
mkdir "W:\Enrollment\shopfloor-setup\%PCTYPE%" 2>NUL mkdir "W:\Enrollment\shopfloor-setup\%PCTYPE%" 2>NUL

View File

@@ -66,12 +66,31 @@ def update_session(payload: dict) -> dict:
except (json.JSONDecodeError, OSError): except (json.JSONDecodeError, OSError):
state = {} state = {}
# Reimage detection: if the new payload's stage_index is <= 1 and we have
# an existing session that was further along, treat this as a fresh run.
# Clear log_tail + reset started_at; preserve serial. Without this, a
# reimage on the same bay leaves stale "succeeded" / high-idx state on
# the dashboard until the new run progresses past idx 1.
if state:
try:
old_idx = int(state.get("stage_index") or 0)
new_idx = int(payload.get("stage_index") or 0)
except (TypeError, ValueError):
old_idx, new_idx = 0, 0
rewind = new_idx > 0 and new_idx < old_idx and new_idx <= 1
prev_done = state.get("status") in ("succeeded", "failed")
if rewind or (prev_done and new_idx > 0 and new_idx <= 1):
state = {"serial": serial, "previous_run_at": state.get("last_updated"), "log_tail": []}
if not state: if not state:
state = { state = {
"serial": serial, "serial": serial,
"started_at": _now_iso(), "started_at": _now_iso(),
"log_tail": [], "log_tail": [],
} }
elif "started_at" not in state:
# Fresh state after a rewind - mint a new started_at.
state["started_at"] = _now_iso()
# Append any new log lines (preserve old; cap to LOG_TAIL_MAX). # Append any new log lines (preserve old; cap to LOG_TAIL_MAX).
new_lines = payload.pop("log_lines", None) new_lines = payload.pop("log_lines", None)