Files
shopdb-flask/docs/COLLECTOR-CLIENT-SETUP.md
cproudlock 534f179ee6 Make main the lean product a site receives
main was 16 days and 259 commits behind the line the product is actually built
on, at contract 0.30.0 against 0.35.0, with no per-PC credential code at all. It
was neither current nor curated - just stale. This brings it forward and, at the
same time, makes it mean something: main is what a receiving site installs, and
nothing else.

WHAT IT CARRIES. Twelve plugins - backups, computers, endpoints, guides,
knowledgebase, machines, network, notifications, printers, slides, tools,
warranty - plus applications, which is manifest-less core and always ships. The
only manifest dependency inside that set is endpoints on computers, and both are
in it, so the closure is self-contained.

WHAT IT DOES NOT. employees, geenforce, measuringtools, printedparts and usb
stay on the site line. So do the Windows installer tree, the VM rehearsal
helpers, the agent tooling config, one site's data loader, the internal audit
and findings records, and the planner spreadsheet. 1,135 tracked files against
1,438.

A REMOVAL THAT BREAKS THE APP IS NOT A REMOVAL, and two nearly shipped:

deploy/site-profile-universal.json still named all seventeen plugins. It is the
file docs/DEPLOY.md tells a site to provision from, so a fresh install would
have failed at `flask plugin apply-profile` with PluginError - the first thing a
receiving site does, broken by the branch meant to serve them. It now names the
twelve that exist, and its comment says the two must agree.

agent/ was never refreshed. Whoever populated this branch copied shopdb/,
plugins/, frontend/ and tests/ from the site line and left the agent tree at its
September state: 22 C# and PowerShell sources missing, including AgentCredentials,
EnrollmentService, AgentHttpFactory and the fleet-alert client, plus one file the
site line had deleted still present. The old tree is internally consistent, so it
compiles, and nothing in CI builds it - every gate was green over a server that
speaks enrollment and per-PC credentials to a bundled agent source that cannot.
Resynced whole.

DECISIONS THIS ENCODES, rather than defers:

- The plugin-authoring lab builds printedparts, whose manifest depends on
  employees, so it could not stay. The lab remains on the site line and main
  ships no build-along tutorial. That is a real loss for an adopting developer
  and is recorded here rather than discovered later.
- Recognition, Training and Recertification notifications are no longer seeded.
  Each splits one notification into a card per employee and renders a photo,
  which needs a plugin this branch does not bundle. Seeding them would offer an
  author a type that cannot resolve anybody. A site that installs employees gets
  them from that plugin's own seed.
- Windows/IIS is documented as the legacy path throughout, and the README leads
  with Docker, because that is what a receiving site gets.

The API description is regenerated for THIS branch - 347 paths and 483
operations, down from 415 and 573 - so the published contract describes what is
here rather than what the site line serves. docs/CONFIG.md lost the two
measuring-tool label keys the code no longer declares, which two lanes had
decided opposite ways.

Suite: 3124 passed, 6 skipped. Naming and style gates pass. The docs gates run
rather than skip, and every index line count is derived from the file it names.
2026-09-19 11:24:40 -04:00

8.8 KiB

Running the collector reporters on a fleet

Follow this to put PC reporting on a fleet that does not have it yet, or to move an existing fleet off a classic-ASP reporter. The payload contract is COLLECTOR-PAYLOADS and the credential choice is COLLECTOR-AUTH; you should not need either open to work through this page.

Adopting the whole tool set at a plant that has none of it is a wider job - ADOPTING-AT-ANOTHER-SITE.


1. Use the shipped reporters

Do not write your own. Two scripts ship in this repository and version with the contract they implement:

Script Reports Posts to
plugins/computers/client/Report-AssetToShopDB.ps1 hostname, BIOS serial, pc-type, logged-in user, make/model, OS, last boot, machine number, device id, every routable NIC POST /api/collector/computers
plugins/printers/client/Report-PrintersToShopDB.ps1 every real print queue on the host POST /api/collector/printers

Both run as SYSTEM, always exit 0 so "last run result" stays clean, and log to C:\Logs\Shopfloor\. Neither names a site: ADR-015 makes a site name in product code a defect, and these ship everywhere.

Each reads its configuration in this order:

  1. Its own parameters (-ApiUrl, -ApiKey), which a scheduled task's arguments can supply for testing.
  2. HKLM:\SOFTWARE\GE\ShopDB - BaseUrl for the server and CollectorKey for the shared key. This is the secret store every shipped reporter reads, and whatever images or configures the bay is what provisions it.

With neither, the script logs the reason and exits without posting.

Which credential it sends is decided on the PC, not in configuration: if the PC holds a per-PC credential it is sent as X-Endpoint-Credential and the shared key is not sent at all; otherwise the shared key goes as X-API-Key. If the server refuses the credential as invalid or revoked, the reporter retries the same POST once with the shared key, which is what makes a revoke a rollback rather than an outage.

Shortcut for a single site: Settings > Computers > Asset reporter generates Report-AssetToShopDB.ps1 with your server's URL already in the parameter defaults, and shows its SHA-256. The key is deliberately not included.

2. Prepare the server

  1. Enable the computers plugin (and printers, if you want observed queues).
  2. Mint the credential the fleet will start on - a managed token scoped to collector.ingest. COLLECTOR-AUTH section 4 has the steps. An un-enrolled fleet needs this even if you intend to move to per-PC credentials, because enrollment itself auto-issues only to a call the shared key authenticated.
  3. Set collector_corporate_ranges to this site's routable CIDRs. Leave it blank and the server falls back to the NIC the bay says carries the default route, which is a guess on any bay with two NICs.
  4. Map your imaging pc-types. Unmapped values only warn, but every report warns until you do.

3. Deliver the key to the PCs

HKLM:\SOFTWARE\GE\ShopDB value CollectorKey, ACL'd to SYSTEM and Administrators. How it gets there depends on what you run - an imaging task, Intune, DSC or by hand - and the options with their trade-offs are in COLLECTOR-AUTH section 6.

Two rules regardless: never bake the key into a script that lives on a file share, and never put it in a manifest or a scheduled task's arguments.

4. Schedule the reporter

Reporting is an EVENT on a schedule, not a state to converge on. Two placements, pick one:

  • As a scheduled task, registered at imaging time, by Intune, or by hand. SYSTEM, RunLevel Highest, at startup plus an interval.
  • As an entry in whatever enforces configuration on your floor, run off a share every cycle with no local copy. Order the key-provisioning step BEFORE it so the key is present the same cycle the first report fires.

Either way, run it ALONGSIDE whatever reports today during rollout. Do not remove the old path until the new one is proven.

5. Stage the rollout

  1. Leave the existing reporting in place. Provision CollectorKey and enable the plugins.
  2. Add the reporter on a handful of pilot bays, one per pc-type. It cannot break enforcement: it never throws and always exits 0.
  3. Verify on the server. Pilot PCs appear or update, action is created/updated, and every warning is understood - map any unmapped pctype, add missing OS strings to the operatingsystems vocabulary. Cross-check the client log's OK lines against the audit log.
  4. Roll it out fleet-wide. From the common scope, every pc-type gets it.
  5. Once ShopDB is the system of record and has survived a full imaging cycle, retire the old reporter.
  6. Migrate to per-PC credentials, then switch the shared key off. That is its own rollout and deliberately comes last: read COLLECTOR-AUTH for what a PC does to enroll and what an admin approves, and plugins/endpoints/docs/PER-PC-CREDENTIALS-ROLLOUT.md for the order the shared key has to be cleared out of the places it lives. Both shipped reporters already send a credential when one is present and fall back to the shared key when it is not, so a PC that has not enrolled keeps reporting and nothing has to be redeployed to start.

6. Verify one PC

Invoke-RestMethod -Uri 'https://shopdb.example.net/api/computers/by-hostname/<HOSTNAME>'

The PC should exist with its serial, type and last-reported timestamp. The client log is C:\Logs\Shopfloor\report-asset-YYYYMMDD.log (printer queues: report-printers-YYYYMMDD.log); it records the POST line and the server's response verbatim, including which credential it used.

7. When a bay does not report

Symptom HTTP Cause / fix
Invalid API key 401 The X-API-Key header is missing or wrong. Confirm the client key matches COLLECTOR_API_KEY_COMPUTERS (or the shared COLLECTOR_API_KEY) or is the managed token you minted. Check the client actually read it. The querystring form no longer works.
Collector API key not configured 500 Fail-closed: no env key set server-side and the caller presented no managed token. A SERVER config gap, not a client problem.
Endpoint credential is unknown or revoked 401 The PC's credential was revoked, or the database was restored past its issue. The reporter falls back to the shared key on its own; let the PC enroll again to restore it.
The shared key is not accepted for this endpoint 401 This PC has enrolled, so it must use its own credential - or the shared key is switched off site-wide. Check that credential.bin is present and readable by SYSTEM.
The request names a different endpoint than the credential 403 The hostname in the payload is on a different asset than the credential, or on no computer record at all. The message says which. Do not retry; fix the record.
No collector registered for plugin computers 404 The plugin is disabled or not loaded on that instance. Enable it.
hostname is required / No data provided 400 Empty body, non-JSON body, or missing identity field. Check -ContentType 'application/json'.
Internal error processing collector payload 500 Generic by design - the server does not leak the cause. The real error is in the server log (Collector upsert failed for computers).
queues is required; send an empty array for a host with no queues 400 The printers payload omitted queues entirely. Absent and empty are not the same thing here.
A bay's observed queues all vanished 200 It reported queues: [], which legitimately clears the host. Check the client log for posting NOTHING (a failed enumeration correctly sends nothing) versus an empty set. If the bay really has queues, fix the enumeration on that host.
Observed queues show as unknown 200 The queue matched no printer asset by port address or name. Add the printer to ShopDB with its IP and re-read - matching happens at read time, so the bay does not need to report again.
warnings present but action is created/updated 200 Soft issues only; the row WAS written. Common: unmapped pctype, unknown osname, unknown app name, pcsubtype not stored. No action needed unless the warning matters to you.
The wrong IP is recorded as primary 200 collector_corporate_ranges is blank or does not cover this bay. The bay reports every NIC and takes no view; set the ranges.

Client-side logs are under C:\Logs\Shopfloor\. Server-side, the Flask app log carries upsert failures and per-plugin schema failures.