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

148 lines
8.8 KiB
Markdown

<!-- docmeta
purpose: howto
audience: operator, adopter
status: current
summary: Get the shipped asset and printer reporters running on a fleet, prove it, and fix it when a bay returns an error.
loadwith: COLLECTOR-AUTH.md
-->
# 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](COLLECTOR-PAYLOADS.md) and the credential choice is
[COLLECTOR-AUTH](COLLECTOR-AUTH.md); 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](ADOPTING-AT-ANOTHER-SITE.md).
---
## 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](COLLECTOR-AUTH.md) 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](COLLECTOR-AUTH.md) 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](COLLECTOR-AUTH.md) 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
```powershell
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.