Commit Graph

16 Commits

Author SHA1 Message Date
cproudlock
5de3594425 displays: the client module updates itself
Install-ShopdbKiosk.ps1 lays the enforce client down once at bootstrap and
never refreshes it. So a client change rode the code deploy to the server
and then sat one directory away from where kiosks actually fetch, waiting
for someone to re-stage the installer bundle by hand - which is how the new
display-type reporting reached prod and changed nothing on any kiosk.

The module now ships as a manifest entry like everything else in this
scope: inline over HTTPS, Hash detection against the exact bytes shipped,
written to the same path the installer uses so bootstrap and self-update
cannot disagree. Ordered first, so a stale client refreshes before anything
leans on it. The installer keeps its real job - a fresh kiosk still needs
something that can talk to shopdb - it just stops being the update path.

Self-modifying by design: this module is what stages payloads, but
PowerShell loads it into memory at start, so rewriting the file mid-run is
harmless and lands on the next cycle. Pilot a client change on ONE kiosk
before the fleet: a broken module cannot fetch its own replacement, and on
a share-less display that means a site visit.
2026-08-12 17:21:29 -04:00
cproudlock
787f475208 displays: clear a pending Edge update without waiting for 02:00
Every kiosk was sitting on Edge's restart-to-update prompt. The scope
already sets RelaunchNotification=2 so Edge restarts unattended, but
RelaunchWindow defers that restart to 02:00-04:00, so during the day the
update waits and there is nobody on site to dismiss the prompt.

A one-shot entry runs the Edge updater and stops the browser. It does not
relaunch it - the enforce task is SYSTEM in session 0, where a launched
browser is invisible - so it leans on the watchdog that already relaunches
the kiosk from the Startup shortcut. That relaunch is what applies the
staged update.

One-shot is DetectionMethod=MarkerFile. The engine writes the marker only
after a 0 exit, so a failed run retries next cycle instead of being
recorded as done, and the script exits 0 when no Edge was running - that
is a success, and failing it would withhold the marker and re-kill Edge on
every cycle from then on. The marker path carries a date, which is the
re-arm mechanism for a future update.

Ordered after the watchdog entry: a display seeing both for the first time
must have its relauncher registered before anything stops the browser.
2026-08-12 12:26:29 -04:00
cproudlock
c0a7655aab geenforce: kiosk watchdog must see a WINDOW, not just a process
Some checks failed
CI / backend (push) Failing after 8s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 7s
Reported from a real display: Edge closed on the desktop but still listed in
Task Manager, and the watchdog never relaunched it.

That is this watchdog's own bug. It asked only whether a --kiosk process
existed. After an Edge update the window can be gone while the process lingers,
so the check said "kiosk is up" and returned - every cycle, forever. Matching on
the command line was chosen to stop a stray renderer masking a dead kiosk; it
does not help when the orphan is the parent.

A kiosk now counts as up only when a --kiosk process still owns a visible
window (MainWindowHandle). Windowless ones are killed BEFORE relaunching:
leaving them would satisfy the next cycle's check again, and a second browser
would fight the first for the display.

Also refuses to run as SYSTEM. MainWindowHandle is session-scoped, so a SYSTEM
caller reads 0 for a perfectly healthy kiosk and would kill and relaunch it on
every cycle. The task uses an interactive Users principal so this cannot
normally happen; the guard makes a mis-registered task fail loudly instead of
thrashing a display in a hallway.

Verified on Windows this time by letting the SCHEDULED TASK do the work rather
than invoking the script by hand - which is what the first version was missing:
  - task fired unattended (rc=0) and launched the kiosk into session 1
  - the next cycle saw the healthy kiosk and did nothing, no relaunch loop
  - a windowless --kiosk process was killed and replaced
  - run as SYSTEM, it refused and the healthy kiosk survived
2026-08-10 09:03:10 -04:00
cproudlock
939cdd0882 geenforce: relaunch the display kiosk when Edge goes away
Some checks failed
CI / backend (push) Failing after 9s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 6s
Kiosks were updating, closing Edge, and never coming back - the display stayed
dead until the next logon or reboot.

The kiosk is launched by an all-users Startup shortcut, which runs ONCE at
logon, and nothing supervised the browser afterwards. RelaunchNotification=2
was meant to cover the update case and does not: that policy drives Edge's own
update-restart, which depends on session restore to return to where it was.
Kiosk mode restores no session and has no UI to show the notification in, so
Edge honours the close and never the relaunch. The same gap swallowed crashes
and anyone closing the window.

Adds a scope entry that registers a scheduled task in the INTERACTIVE session -
SYSTEM cannot launch a visible browser, which is why the dispatcher writes a
shortcut rather than calling Start-Process. The task relaunches from that same
shortcut, so the target URL keeps one source of truth: retarget a subtype in
DISPLAY_TYPE_TARGETS and the watchdog follows unchanged.

Two details that matter. It matches on the COMMAND LINE, not the image name:
Edge runs a crowd of msedge.exe children and only the parent carries --kiosk,
so testing "is msedge running" would let a stray renderer mask a dead kiosk
forever - verified against a real kiosk PC showing 7 processes and 1 match. And
it avoids -RepetitionDuration [TimeSpan]::MaxValue, which serialises out of
range and is rejected, exactly as the kiosk installer documents.

A launch debounce stops a display that fails to start from spawning a browser
every cycle, the log is size-bounded because this runs forever on a PC nobody
watches, and it does nothing at all when no kiosk shortcut is present so it
cannot put Edge on a PC that never asked for one.

Verified on Windows: registers with the right principal and triggers, relaunches
when the kiosk is gone, debounces an immediate re-run, and is idempotent across
cycles (the staged script compare is trimmed - Set-Content adds a trailing
newline the here-string lacks, so an untrimmed compare rewrote it every cycle).
2026-08-10 07:49:43 -04:00
cproudlock
af6bcd4726 geenforce display dispatcher: purge legacy autostart in Wow6432Node + all hives
The old kiosk kept relaunching the dead URL from an HKLM Run value the 32-bit
Inno installer wrote - WOW64-redirected into SOFTWARE\Wow6432Node, which 64-bit
tooling (and the earlier purge) never saw. Broaden the sweep to both registry
views, every loaded user hive, Run/RunOnce/Policies-Explorer-Run, matching by
legacy name AND by any value pointing at the old URLs, plus every per-user and
common Startup folder.
2026-07-30 15:54:18 -04:00
cproudlock
01a545f507 geenforce display dispatcher: revert to direct Edge shortcut, drop VBS launcher
The white-on-login was the old Dashboard/Lobby installer's leftover autostart
relaunching the dead old URL (404 -> white), not a network race - so the
wait-for-URL launcher solved the wrong problem. Go back to the plain direct
Edge kiosk shortcut and clean up any stale launcher file. The real fix (the
legacy HKLM Run-key + old .lnk purge) stays; it just has to be published.
2026-07-30 15:37:26 -04:00
cproudlock
95df51fddf geenforce display dispatcher: wait for kiosk URL before launching Edge
At auto-login the Startup shortcut fired before the network was up, so Edge
--kiosk navigated to nothing and sat on a blank white page with no retry.
Point the shortcut at a hidden VBS launcher (wscript, no console flash) that
polls the kiosk URL until it responds (up to ~3 min) and only then launches
Edge fullscreen, so the first paint is the real page. Falls through to launch
anyway after the timeout so a display is never left dark.
2026-07-30 15:33:49 -04:00
cproudlock
e6533d5205 geenforce display dispatcher: purge legacy HKLM Run autostart
The old LobbyDisplay/Dashboard Inno installers planted an HKLM
...\CurrentVersion\Run value (plus a Startup .lnk). The dispatcher already
swept the stale .lnk/.url launchers but never the Run value, so a display
with our new ShopDB Kiosk.lnk still relaunched the old kiosk URL at logon
(the Run key beats the Startup shortcut). Remove the two legacy Run values
and kill any running old-URL Edge so the display self-heals to the resolved
target on the next enforce cycle.
2026-07-30 14:28:44 -04:00
cproudlock
516e33a6c3 geenforce display dispatcher: kiosk .lnk uses only --kiosk + --edge-kiosk-type=fullscreen (drop the extra Edge flags) 2026-07-30 13:53:04 -04:00
cproudlock
b22701a444 geenforce display dispatcher: resolve role from the server by FQDN, fall back to display-type.txt
The dispatcher now derives its FQDN (F<BIOS serial>.<domain>) and asks
/api/dashboarddefaults/display-role for its role/path, so changing a display's
type/location in Settings > Dashboard Defaults takes effect with no reimage. If
there is no serial, no server mapping, or the lookup fails, it falls back to the
local display-type.txt map (offline-safe). VM-verified both paths.
2026-07-29 07:34:21 -04:00
cproudlock
9a2d0ccebb dashboard: resolve employee names from directory/user, GE monogram photo fallback, kiosk sweep + label
- notifications shopfloor feed: resolve the employee name live when the stored
  value is a bare SSO (WJ notifications imported as SSOs, never converted), for
  both single and split-per-employee cards
- employee name resolver: after a directory miss, fall back to the shopdb User
  account (firstname/lastname, keyed by SSO username) so users from other
  locations still show a name
- shopfloor dashboard: employee photo falls back to the GE monogram (own asset,
  independent of the site_logo setting) with a loop-guarded onerror; recognition
  + recert tiles both covered
- shopfloor dashboard: 'All Business Units' filter label -> 'All Locations'
- geenforce display dispatcher: startup sweep also matches the imaging
  installers' 'GE Aerospace Dashboard/Lobby' shortcuts by name
2026-07-28 18:21:47 -04:00
cproudlock
3a8df166cf geenforce: broaden kiosk startup sweep to match single-dash -kiosk and shopdb-URL launchers
The prior sweep only matched '--kiosk'; the imaging installers (Inno
GEAerospaceDashboardSetup / lobby) create Startup shortcuts with single-dash
'-kiosk' pointing at /shopdb/shopfloor-dashboard, so they survived. Match any
msedge/chrome Startup .lnk whose args contain -kiosk (one or two dashes) OR a
shopdb kiosk URL (tsgwp00525 / /shopdb/ / shopfloor-dashboard). Unrelated
Startup items are left untouched (VM-verified).
2026-07-28 18:09:12 -04:00
cproudlock
5712f72ccf geenforce: fix http-payload path doubling + sweep stale kiosk startup shortcuts
- Resolve-ShopdbPayloads wrote an absolute local path into the entry, and the
  engine resolves it as Join-Path InstallerRoot <field>, doubling it
  (C:\...\payloads\C:\...\payloads\<sha>.ps1 -> PS1 not found). Write the leaf
  filename instead; the runner already sets InstallerRoot to that payloads dir.
- display dispatcher now removes leftover kiosk launchers from prior installs
  (any Startup .lnk that runs Edge --kiosk, plus .url to a shopdb kiosk page),
  not just its own, so two kiosks do not fight.
2026-07-28 17:49:47 -04:00
cproudlock
4c0cc672a2 geenforce: harden allowlist + fix share-less kiosk client and display scope
- allowlist auth uses remote_addr, not the spoofable first X-Forwarded-For hop
  (adds _trusted_client_ip + a regression test); rate-limit path unchanged
- client psm1: fix Set-StrictMode crashes reading absent keys in Get-ShopdbConfig
  (token-less mode) and Resolve-ShopdbPayloads (no-payload entries); validate
  the manifest response is JSON before overwriting the last-known-good cache
- runner: pass the engine its required -InstallerRoot/-LogFile; create the log
  directory so enforce logging is not silently lost on a fresh kiosk
- display scope: dispatcher writes an all-users Startup shortcut instead of
  Start-Process (SYSTEM cannot show a window in session 0), resolves the base
  URL from HKLM, and adds an always-on power/no-lock entry; tests updated for
  the 6-entry scope
2026-07-28 17:09:21 -04:00
cproudlock
d0bf37ced7 geenforce: display scope is self-sufficient, no common inheritance
Per decision: displays need none of the fleet-wide common scope's software, so
the gea-shopfloor-display scope carries everything it enforces and does not
inherit common. This avoids repackaging common's SMB-backed payloads for a
share-less display.

- Invert the client common-merge switch: -NoCommon (default-on) becomes
  -IncludeCommon (default OFF). A scope now enforces alone unless opted in.
  The capability stays for a future share-less non-display PC; displays omit it.
- Drop the common SMB-payload audit + inheritance sections from the display
  seed comments and docs (GE-ENFORCE-DISPLAY.md); document self-sufficiency.
- GE-ENFORCE-CLIENT.md: common-scope inheritance is now opt-in.
2026-07-23 08:22:23 -04:00
cproudlock
9d65ef103d geenforce: display-readiness batch (server hardening, PS client wiring, display scope)
Get GE-Enforce closer to running on credential-less Intune/Entra display PCs
that pull manifest + payloads over HTTPS instead of SMB.

Server (plugins/geenforce/api/routes.py):
- Rate-limit + 512MB served-size ceiling on GET /payload/<sha256> (reuses the
  login limiter's cache pattern, config-overridable via GEENFORCE_PAYLOAD_*).
- New tests: payload hardening, manifestblobs model-vs-migration parity, and a
  report-contract test locking the lowercase per-entry report keys.

PS client (plugins/geenforce/client/):
- Fix New-ShopdbReport per-entry key casing to lowercase (name/action/selfhealed/
  exitcode/message) to match what the server reads; the engine emits PascalCase.
- Enforce TLS 1.2 in the network functions.
- Fetch + merge the fleet-wide common scope alongside the pctype scope
  (pctype wins on conflict; -NoCommon opt-out).
- Normalize whatever the engine returns into a well-formed summary.
- Make the empty-cache fail-safe observable: event-log entry + report ping
  instead of a silent exit 0.

Manifest (plugins/geenforce/seed_display_scope.py + docs/GE-ENFORCE-DISPLAY.md):
- Seed a gea-shopfloor-display scope: 4 Edge kiosk drift-heal registry entries
  + 1 data-driven dispatcher (Dashboard/Lobby/3DPrintRoom via display-type.txt).
  Kiosk EXEs stay image-baked; the manifest heals policy/config drift only.
- Documents the common SMB-payload audit (entries needing http/inline before a
  share-less display can inherit common).

Migration registry (shopdb/plugins/alembic_template.py + test):
- Register the pre-existing manifestblobs and the new printersupplyalerts tables
  in PLUGIN_TABLE_OWNERS; update EXPECTED_HEAD_REVISION for geenforce (0002blobs),
  printers (0002supplyalerts), and printedparts (0004txnrev) which had drifted.
2026-07-23 08:16:38 -04:00