Commit Graph

91 Commits

Author SHA1 Message Date
benjamin.baker
2cf46da55e Merge remote-tracking branch 'origin/wj' into wilm
# Conflicts:
#	scripts/check-naming-and-style.sh
2026-09-07 15:13:33 -04:00
benjamin.baker
0d5b9730e3 Add load test harness for pool and fleet sizing
Adds the scripts used to size the connection pool and validate fleet polling under load: burst arrival and mixed workload drivers, pool and resource samplers, a UI probe, host seed/drop SQL, and a gevent Dockerfile. Hosts are addressed by compose service name (api:5001) rather than a local path, so the harness runs against any stack rather than one machine.

loadtest_token.py mints a scoped collector token named LOADTEST and prints the secret once, then revokes only that token by name so a real collector token is never touched. envkeyfallback is false, so the harness needs a genuine managed token rather than an env key.
2026-09-07 12:39:53 -04:00
benjamin.baker
ea3739262b Fix style gate: file-level --exclude was cancelling every --include
The gate reported 2277 lines of findings and had become unreadable, which read as accumulated debt. It was a tooling bug. In this grep build a single file-level --exclude= silently cancels every --include= on the same command; --exclude-dir= is unaffected. Two innocuous entries, --exclude=findings.sarif and --exclude=report.md, had therefore been disabling the include filters on every check that used the EXCLUDES array. That is why a check whose flags say .py/.vue/.js/.ts was reporting .md, .ps1, .json, .gitignore and vendored DLLs: it was scanning the whole tree including build artifacts.

Removes all file-level excludes and reproduces their intent with a drop_generated post-filter, adds grep -I so binaries can never be reported (there is no line to fix in a DLL), excludes bin/obj/publish/dev-bin and exports/backups, and adds the drop_scratch the shorthand loop was missing while every other check had it. Findings drop from 2277 lines to 11 with all ten checks passing. Verified with a planted canary file that the gate still catches cfg, a snake_case __tablename__ and a snake_case db.Column - a green gate immediately after a filtering change is worth distrusting until shown to still fail. Do not re-add --exclude=<file> to that array.
2026-09-07 12:39:40 -04:00
Proudlock
c9abb18dda fix: improve naming/style check to exclude external code
- Remove UTF-8 BOM from shopdb-admin.ps1
- Add --binary-files=without-match to grep (skip images/binaries)
- Exclude C# client code (.cs) from Python naming rules
- Exclude external directories: agent/, docs/, .github/, deploy/
- Exclude gitignored directories: _transfer/, instance/
- Exclude temporary files: scan.log, findings.sarif, report.md
- Exclude README.md from site-specific literal check (history docs)
- Scope banned-shorthand check to product code only (shopdb/, plugins/, frontend/src/)

The check now only validates actual Python/Vue/JS product code, not:
- Documentation examples (CONTRIBUTING.md shows violations as examples)
- External code (C# screensaver client, PowerShell installer)
- Binary files (images, bundles, restic binary)
- Build artifacts and temporary files

Fixes CI naming/style check failures

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-07 09:57:23 -04:00
Proudlock
d40f1a6a92 fix: replace unicode characters and fix finalize-restore endpoint
Style check fixes:
- Replace unicode box-drawing characters with ASCII (|-- instead of ├──)
- Replace emoji status markers with [OK]/[FAIL]/[WARN] tags
- Replace arrows (→ with ->, ↔ with <->) and em-dashes (— with --)
- Add venv-quick to excluded directories in check script
- Exclude darkwing report files (findings.sarif, report.md)

Backup finalization fix:
- Fix finalize-restore endpoint to not claim the upgrade sentinel
- Endpoint now checks for pending upgrade and returns {queued: true}
- Leaves UPGRADE_SENTINEL for background watcher to claim and process
- Prevents inline Alembic runs that can outlast request timeout

Claude Code context:
- Add .claude/ directory with INDEX.md and QUICK-REFERENCE.md
- Add CLAUDE.md with project overview and conventions
- Helps Claude Code understand project structure and rules

Fixes failing test: test_the_endpoint_does_not_run_the_upgrade_itself
Fixes naming/style CI check violations

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-07 03:48:11 -04:00
Proudlock
5767765ada Fix Docker deployment: restore HTTPS, auto-run plugin migrations, install core plugins only
- Restore original HTTPS config in docker-compose files (gunicorn + gevent DOES support SSL)
- Add plugin migration step to docker-entrypoint.sh (fixes missing plugin tables on first deploy)
- Install only core plugins on bootstrap (backups, computers, endpoints, guides, knowledgebase, machines, network, notifications, printers, slides, tools, warranty)
- Set FLASK_ENV=production to disable Werkzeug debugger

Bug: bootstrap-plugins.sh installed ALL plugins but didn't run migrations, causing
'Table does not exist' errors. Now installs core plugins only and runs 'flask plugin
upgrade-all' after bootstrap.

Optional plugins (employees, geenforce, measuringtools, printedparts, usb) can be
installed later via Settings > Plugins.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-07 02:32:14 -04:00
Proudlock
783b5d3806 Fix backup script - remove all Python dependencies
Backup was mostly working but failing at the end due to Python calls.

CHANGES:
- Made manifest generation work without Python (bash JSON)
- Made check verification recording work without Python
- All Python calls now wrapped in 'command -v python3' checks
- Falls back to bash sed for JSON updates

RESULT: Backup completes successfully without Python
- Database dump: SUCCESS (24K)
- Minimal manifest: SUCCESS (bash-generated JSON)
- Restic snapshot: SUCCESS
- Repository verification: SUCCESS
- Script exit: SUCCESS (no more Python errors)

Backups now work in offline/corporate proxy environments with
zero downloads during Docker build.
2026-09-07 00:42:10 -04:00
Proudlock
f1ed2b19cd Fix backup system - offline restic package approach
PROBLEM: Corporate proxy (Zscaler) blocked ALL package downloads during
Docker build - Oracle Linux yum, Alpine apk, even with SSL certs.

SOLUTION: Offline package approach
- Pre-download restic binary on host (works outside Docker)
- Bake binary into repo at scripts/backup/offline-packages/
- Updated Dockerfile.backup to use offline binary (no downloads)
- Simplified: mysql:8.4 base + offline restic only (no Python/alembic)

RESULT: Backup containers build successfully behind corporate proxy

FILES:
- scripts/backup/offline-packages/restic_0.17.3_linux_amd64.bz2 (8.6MB)
- scripts/backup/Dockerfile.backup: Use offline package, no downloads

VERIFICATION:
- docker compose build backup: SUCCESS
- Backup watcher running: Polling every 30s
- Backup store running: restic REST server on port 8000

WORKING:
- Click 'Back Up Now' in UI
- Watcher detects sentinel file within 30s
- Backup executes automatically

The offline package can be checked into git for easy deployment at
other sites behind similar proxies.
2026-09-07 00:37:54 -04:00
Proudlock
3e36b54101 Fix backup feature for local Windows development
- Add --no-reload flag to run_dev.py to prevent env var loss
- Configure SHOPDB_BACKUPS_ROOT in .env for Windows path
- Create local backups directory
- Document local dev setup in backup README
- Fix: Flask auto-reloader was spawning process without env vars
2026-09-06 23:57:54 -04:00
Proudlock
5eb5c83b23 Document backup feature in scripts/backup/README.md
- Quick start guide for backup/restore
- Configuration options (local, SMB, S3)
- Troubleshooting common issues
- API endpoints reference
- Clarify empty list on first deployment is normal
2026-09-06 23:54:11 -04:00
Proudlock
316bc81a4c Add automatic plugin installation on first Docker deployment
- Add docker-entrypoint.sh: runs migrations and bootstraps plugins
- Add bootstrap-plugins.sh: installs all plugins (disabled by default)
- Update Dockerfile to use entrypoint
- Plugins can now be enabled via Settings UI after first deployment
- Idempotent: safe to run multiple times
- Solves issue where imported prod databases had tables but no plugin registry
2026-09-06 23:01:06 -04:00
Proudlock
a6af09858c Merge wilm: gevent migration, endpoint telemetry phase 2, backup/restore
Brings in 98 commits from wilm (origin/wilm @ 811e6ca61a):

Major features:
- Move API to gevent workers for better concurrency
- Endpoint agent startup jitter (prevents thundering herd)
- Complete backup/restore with restic
- Endpoint telemetry phase 2 (disk health, mapped drives, user context)
- PC Detail UI rework with new cards and tabs
- Dell Command actions and Corporate Tools tab
- Agent version column in Fleet view

Conflict resolution:
- Backup/restore: Accepted wilm implementation (10 files)
- Agent startup jitter: Accepted wilm v1.12.0
- PC Types list fix: Accepted wilm version
- Endpoint plugin domain clamping: Kept ours (defensive)
- Test files: Accepted wilm versions for consistency

Testing required:
- Verify gevent handles concurrent requests
- Test backup/restore functionality
- Verify agent startup jitter timing
- Test endpoint telemetry collection
- Validate PC Detail UI changes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-06 21:23:42 -04:00
993353dfc0 Endpoint telemetry, PC Detail rework and a checked backup/restore path 2026-09-06 18:53:11 -04:00
benjamin.baker
d6bd7b5f54 Fix endpoint user-context collection running in the wrong session
Mapped drives reported Unavailable on endpoints where the operator could
see them fine. The data path was intact; the collection was running under
the wrong token. Mapped drives are per logon token, so an elevated probe
enumerates a different drive table than the interactive user.

Collection:
- Pin the user-context spool path so the probe and service agree on one
  location, and persist/overwrite it instead of deleting on read.
- Refuse to collect while elevated (exit code 2) rather than emitting a
  confident wrong answer, and stamp elevated/username on the spool.
- Reject spools stamped elevated or from an unexpected account on read,
  so an admin run cannot masquerade as valid.
- Log netuse/smbmapping/userlistener counts so each run leaves evidence
  of what it actually saw.

Install and launch context:
- Move collection to a 'ShopDB user-context probe' scheduled task under
  S-1-5-32-545 at RunLevel Limited, the only mechanism that reliably runs
  in the operator's own session. Verify registration and record it in the
  install ledger.
- Stop starting the tray as a child of the elevated installer; explorer.exe
  de-elevates without changing ACCOUNT, so an admin install produced an
  admin tray. The Run key only fires at logon, which left the tray dead on
  a machine already signed in, so add a transient 'ShopDB tray first launch'
  task that starts it once in the existing session and unregisters itself.
- Hide the console window during --collect-usercontext. The agent is a
  console exe; the tray used to mask this with CreateNoWindow, but the
  scheduled task invokes it directly and flashed a window every cycle.

Installer edits are in _install_script() in plugins/endpoints/api/siteagent.py,
which generates the install.ps1 that actually ships. The standalone
agent/installer/install-shopdbagent.ps1 was never shipped once the package
moved to the UI builder and is deleted here to stop the divergence that
caused an earlier fix to land in a file no endpoint ever ran.

Versions: agent 1.10.1, tray 1.3.0. These share the pinned spool path and
must be deployed together.

Pre-existing test failures unrelated to this work (8, identical on a clean
tree): docs publishable/versions, plugin contract symlink scan, import
guard tamper check, security headers upload sandbox, and three endpoint
timestamp tests.
2026-09-05 17:32:03 -04:00
benjamin.baker
e00bb44d6b merge storage and disk health into one card with two sections
Volume capacity and physical drive health were two adjacent cards, and the operator reading a full disk is usually the operator reading a dying disk in the same minute. They are now one Storage card with two sections. Visual grouping only - the two tables share no key, storage being per volume and health per physical device, and one drive can back several volumes or none the operator sees. Nothing is reconciled across the sections; this is not the mapped-drive merge where two sources described the same drive and the worse status won. The tabs render type was the only sections-in-one-card mechanism and emitted label/value fields with no table path, so it now takes table sections too. That is the renderer every plugin uses, so both behaviour changes in it are mutation-tested. Two things only surfaced on contact: the empty-section filter had to grow a table branch, because a table section declares columns up front and dropping it when it has no rows takes its tab with it and reads as no disks rather than nothing reported; and full-width span was keyed off the declared render type, but a tabs panel holding a six-column table needs the same span and that is only knowable from fetched data. The diskhealth route stays. It no longer backs a panel, but it is a stable read for callers outside the renderer and removing it for a UI-only change would break them.
2026-09-05 07:05:30 -04:00
benjamin.baker
b973561cb7 Gate restore on the dump's data revision, not the code head
A restore could load a database dump whose schema was older than the running
code. The manifest stamped schemarev from the code image on disk, so the
compatibility check compared the code against itself and always passed. A set
captured from a database at 7d37_import_batches_and_journal restored under
code at 7d41_drop_guest_tools_secrets, leaving the data behind the code: every
query on a table the newer migrations touched failed with 1054 Unknown column,
which surfaced as empty configuration-policy dropdowns and a failure to load
data on edit PC.

The authoritative signal is the alembic_version of the database that was
dumped, so manifests now carry datarev alongside schemarev, backup.sh reads it
from the database and warns when it has drifted from the image, and the gate
asks whether the running code knows that revision. Data behind the code is
allowed because it can be rolled forward; data ahead of the code is refused
because it cannot.

Rolling it forward has to happen somewhere with an app context. The backup
sidecar is mysql plus restic plus alembic only and has no Flask, so restore.sh
loads the dump and parks the run in an upgrading phase behind an
.upgrade-requested sentinel rather than declaring a success it cannot verify.
The api container owns the Flask CLI and already runs these exact idempotent
commands on every deploy, so a finalize endpoint there runs db upgrade plus
plugin upgrade-all and stamps the terminal phase. The UI drives it on seeing
the upgrading phase, so recovery needs no operator intervention.

The live simulation runs one codebase against itself, so its dump is always at
the same revision as the code restoring it and it can never reproduce this
drift. The gate therefore gets a standalone tester that builds throwaway
migration chains and dumps offline, invoked as a simulation preflight. It
caught a real bug in the legacy dump-parse fallback, where the revision was
matched loosely enough to return the alembic_version table name instead of the
value it holds.
2026-09-04 15:53:58 -04:00
benjamin.baker
3c9321f16c Add developer-only restore simulation harness
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / gates (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / lean-build (push) Has been cancelled
FOR DEVELOPER USE ONLY. Adds a live restore round-trip diagnostic that fingerprints every base table via CHECKSUM TABLE, backs up and restores through the real sentinel path, and proves all 110 tables return to backup-time state.

Documents expected drift so future runs are not misread: apitokens last-used churn, external endpoint-agent writes (computers/endpointstates/events/applications) that cannot be quiesced, and that live asset-row deletion is not permitted under RESTRICT/CMMC. Restore remains a full drop-and-recreate replace, so a missing or corrupted asset is still fully restored - verified, no restore.sh change needed.

Writes simulate-restore-results.txt to the script dir and ships a lightweight README with the testing cadence and parameters.
2026-09-04 05:01:03 -04:00
benjamin.baker
debe1fce69 it is risen - add restore progress and backup/restore history
The watcher scripts now write live status and append-only run history onto the shared volume; the api exposes them via /shopdbbackups/status and /history. The restore modal shows an indeterminate spinner with phase labels, and two cards list backup and restore run records. Timestamps render in UTC to match the Zulu-stamped set ids, the history directory is excluded from the set list, and a stale-status guard stops a previous run's done phase from collapsing the spinner on the next restore.
2026-09-03 20:43:42 -04:00
benjamin.baker
7e4469c44e Wire restore into the backup watcher and fix backup timestamps
The api queued .restore-requested but nothing consumed it, so restores silently did nothing. The watcher now services the restore sentinel by running restore.sh, and its instance_data mount is read-write so restore can lay files back down (the manual one-shot stays read-only). Backup stamps use a colon-free UTC form that Date could not parse, so the UI showed raw strings; normalize them before formatting.
2026-09-03 20:26:01 -04:00
benjamin.baker
19071ec6c6 Add opt-in backup overlay with third-container restic store
Backups are per-site and not part of the base stack, so they ship as a separate compose overlay with an isolated restic REST server the app can only reach through the backup utility (no shell or db creds). The sidecar image trusts the corporate proxy root CA at build time so its Oracle yum, github, and PyPI fetches succeed behind TLS interception; shell scripts are pinned to LF via .gitattributes so the container entrypoints do not die on a CRLF shebang.
2026-09-03 20:06:45 -04:00
benjamin.baker
d5d823fd8b Merge remote-tracking branch 'origin/wj' into wilm 2026-09-03 17:35:48 -04:00
ff9ae693a6 Remediate the 2026-09-03 feature-test findings 2026-09-03 17:14:53 -04:00
benjamin.baker
ab949f8bec Merge remote-tracking branch 'origin/wj' into wilm
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / gates (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / lean-build (push) Has been cancelled
2026-09-03 13:46:17 -04:00
ddf41c3e65 Make a partly-undone import retryable, and refuse a plan it cannot deliver 2026-09-03 09:16:01 -04:00
b0bd73f62d Import the wilm agent store, WINC adoption work and batches CLI, with four fixes 2026-09-03 08:16:38 -04:00
benjamin.baker
17825ec8f1 Add WINC batches CLI and a CLI how-to README
The web import panel can list applied/undone batches, but the CLI had no equivalent, so an operator working from the shell had no way to find a batchid to feed into the undo command. Add a read-only batches command that reuses the same importbatches query, and a short README documenting the four WINC commands and their switches.
2026-09-03 08:07:01 -04:00
benjamin.baker
2998e35ddf Add reversible Maximo/WINC machine-adoption import
Machine data arrives as a Maximo asset export (validated against the WINC
site) that must update existing machines in place, not create canonical
per-table rows, so it does not fit the ShopDB-native/Azure/Thor CSV adapter
path. Bulk edits to live machines also need an escape hatch, so the whole
commit is journaled and can be undone as one batch.

Add a dedicated dry-run analyzer, approval packet, resolver/suggest helpers,
and a reversible writer/undo backed by a new importbatches/importjournalentries
schema (with migration). Expose preview/commit/batches/undo settings routes and
a matching frontend source card and panel, and surface the batch/journal
contract through shopdb.api for plugins.
2026-09-03 05:04:22 -04:00
benjamin.baker
27ccf8962e exclude gitignored devnotes from the naming/style gate
The gate walks the working tree with grep, which does not honor .gitignore, so it scanned devnotes/ (local scratch notes, already gitignored and never committed) and failed on pasted logs full of em-dashes and arrows. Exclude devnotes/ the same way venv, node_modules, and dist are already excluded.
2026-09-02 18:39:23 -04:00
benjamin.baker
ebece2d3bd Merge origin/main into align-wilm-main (align wilm with Cameron's latest)
Bring main's endpoint hardening, operator-report validation, launch-path
policy, and theme-variable CSS into wilm. Channel model reconciled to this
repo's shipping (stable=release tag, edge=main trunk) while keeping wilm's
remote-BuildKit publish step and GHCR pull-based launcher.
2026-09-02 17:41:03 -04:00
bb733f1332 Merge the wilm work, act on the five-agent review, verify the launch-path policy on Windows 2026-09-02 16:15:53 -04:00
benjamin.baker
d0e9f80287 distribute prebuilt channel images via GHCR instead of building on pilots
Pilots behind the inspecting proxy cannot reliably build the frontend from source; npm's many small fetches get reset mid-install. Build the image once in CI on the clean runners and have pilots pull the finished channel image. One resumable image pull survives the proxy where hundreds of npm requests do not, and a pilot now needs only Docker.
2026-09-02 09:34:40 -04:00
benjamin.baker
3cbba2df14 re-clone when git reports dubious ownership on the working copy
a clone first created from an elevated prompt is owned by Administrators; a
later normal-user run then fails every git op with 'dubious ownership'
(exit 128), which the launcher misreported as an auth error. detect that
case and remove the clone for a clean re-clone.
2026-09-02 08:54:37 -04:00
benjamin.baker
aff3bcb8a0 keep launcher cleanup from aborting on docker stderr warnings
docker/compose emit warnings on stderr (e.g. nothing to remove), which
tripped ErrorActionPreference=Stop and killed the run before the clone.
soften the preference around the best-effort cleanup steps.
2026-09-02 08:18:18 -04:00
benjamin.baker
30544988ce reset interrupted runs before retry in the dev launcher
proxy timeouts can kill a build mid-flight, leaving orphaned containers on
the channel ports, dangling build cache, and half-written clones. clean
those on every start, scoped to the channel and keeping the DB volume, so a
re-run starts from a clean slate.
2026-09-02 08:16:13 -04:00
benjamin.baker
58040164c2 auto-detect and stage corp TLS-inspection root CA before docker build
GE/Zscaler proxy inspection breaks npm ci silently, leaving node_modules
incomplete and vite missing at build time. Detect the root CA from the
host's chain to the npm registry and stage it as corp-root-ca.pem so the
Dockerfile trusts it; harmless on clean networks.
2026-09-02 07:58:31 -04:00
benjamin.baker
30b089bc8a replace web iex launcher with a save-and-run script
The iex (irm ...) one-liner could never work against a private repo: raw.githubusercontent.com returns 404 without a token, and a git operation's Credential Manager auth does not apply to a plain irm. The prior token-passing workaround (get.ps1, 717387f) asked pilots to paste a PAT, which they will not do and may not have.

Start-ShopDBDev.ps1 is a downloaded FILE, not a web pipe, so only its git clone/fetch need auth - and those go through Git Credential Manager using the login pilots already have from cloning this repo or signing in via Visual Studio / VS Code. No token, no paste. It takes -Channel stable|edge and hands off to dev-bootstrap.ps1. DEV-BUILD-TEST.md is rewritten for the save-and-run flow with a GCM-seeding fallback.
2026-09-02 07:43:06 -04:00
benjamin.baker
717387f665 authenticate the web launcher against the private repo
raw.githubusercontent.com returns 404 for a private repo without a token, so the anonymous iex one-liner could never fetch get.ps1, and even once fetched its git clone of the same private repo would fail. get.ps1 now reads a GitHub PAT from SHOPDB_TOKEN (or GITHUB_TOKEN) and uses it for the clone/fetch; the raw fetch is authenticated by irm -Headers in the one-liner. The token is kept off disk: origin is reset to the clean URL after clone, and the update path fetches by explicit URL rather than a tokenized origin.
2026-09-02 07:33:30 -04:00
benjamin.baker
68c33ee896 add channel-aware distributable installers and web launcher
Follows the config-driven-badge decision through to packaging: a single channel-parameterized builder emits two distinct installers (stable from main, edge from dev-be) rather than two drifting scripts. Each package ships the image bundle, the airgap+channel compose templates, a pre-filled .env, and a one-command site installer.

Secrets ship as CHANGE_ME_* sentinels and Install-ShopDB.ps1 refuses to start until every one is rotated, so a distributable can never come up on shipped credentials. docker-compose.airgap.yml now threads SHOPDB_ENV_LABEL through the shared app-env anchor, otherwise a distributed edge stack would run the badge-capable image but never light the badge.

get.ps1 is the web launcher that makes the iex one-liners work: dev-bootstrap.ps1 operates on a local clone, so get.ps1 clones/updates the channel branch and hands off. docs/DEV-BUILD-TEST.md is the short reviewer handout with the two one-liners for Cameron to try.
2026-09-02 05:41:25 -04:00
benjamin.baker
0524ce7751 add config-driven channel label and dev-workstation bootstrap
A bleeding-edge container running next to a stable one on the same dev box is easy to confuse, and a branch-only badge would force perpetual rebase/cherry-pick drift between main and dev-be. Instead the badge is config-driven: the settings endpoint injects a synthetic env_label from SHOPDB_ENV_LABEL, the sidebar renders a red DEV tag only when it is non-empty, and stable simply leaves it unset. Same code on both channels, no divergence.

dev-bootstrap.ps1 makes a fresh Windows box turnkey: it checks git/Docker (winget-installs with a prompt), verifies the engine is actually running, then builds and starts an isolated per-channel stack so stable and edge coexist on distinct ports, projects, and volumes. docker-compose.channel.yml is a thin overlay so the base compose file stays channel-agnostic.
2026-09-02 05:27:01 -04:00
benjamin.baker
ab4b9d7ec9 add operator issue reports for endpoint agent
Operators had no way to flag a problem PC from the tray. This adds a report path: the tray captures a reason, free-text note, and optional screenshot and the agent uploads it on the collector token to a new operatorreports table linked to the asset. Ships the endpoints 0009 migration so the table exists before the code queries it; the running DB had drifted behind the code and returned HTTP 500 on upload.
2026-09-01 19:39:32 -04:00
benjamin.baker
a016753bb6 Update shared styles, panels, naming hook, and ignore rules
Supporting changes for the monitor/drift work: shared panel styling for the new warning state, a naming-hook tweak, and gitignore entries for editor state (.vs/) and scratch capture files (pt*.txt) so they stay out of version control.
2026-09-01 15:46:18 -04:00
benjamin.baker
1a1bf0640f Merge origin/main (West Jeff) into wilm
Bring in the natural-key import fix, Metrics->Monitors rename, PC detail tabbing and panel groups, guest search grants, and the 2026-08-30 security fixes. Reconcile endpoint packaging/installer, migrations, contract version, and tests; keep the delivery uninstaller and 6-step installer alongside main's permission-based RBAC and pin-drift protection.
2026-08-31 16:59:07 -04:00
benjamin.baker
022affc5d0 Ship uninstaller in agent bundle and harden tray launch
The site-agent bundle now includes uninstall-shopdbagent.ps1 so operators can cleanly remove an install without hunting for the repo script. The packaging route reads it from scripts/, which was not mounted into the api container, so add a read-only ./scripts:/app/scripts mount (recreate the container to apply).

install.ps1 now registers the tray to autostart for every user via the HKLM Run value ShopDbEndpointTray, adds an All-Users 'ShopDB Agent' Start Menu shortcut for manual relaunch, and launches the tray once post-install. The tray enforces a single instance via a Global named mutex so repeated launches no longer stack duplicate icons. The uninstaller removes the Start Menu shortcut alongside the existing Run key and credential cleanup.
2026-08-31 16:09:05 -04:00
6680c3c5de Merge the wilm delivery work into main
Ben Baker's installed-software collection, software delivery (run_script and
update_app), agentless adoption tool and command-queue hardening, with his
authorship preserved.

Reviewed before promotion. Fixed: the endpoints plugin tests were never collected
by pytest, the SBOM snapshot table's key would exhaust on MySQL, ingest did not
truncate two columns strict MySQL would reject, and update_app's version branch
pinned a digest for a file that route never serves. Agent packaging moved off
require_role('admin') onto a new endpoints.agentpackage permission.

Contract 0.27.0. Full suite 2231, frontend 314.
2026-08-30 19:41:15 -04:00
benjamin.baker
d6cd0c91b3 Merge origin/main into wilm: dashboard/fleet console, vuln upgrades, colours 2026-08-30 14:41:56 -04:00
benjamin.baker
c3875b0eec Add a standalone endpoint agent uninstaller
Reverses everything the installer and site-agent generator create: stops and deletes the service, kills agent/tray processes, removes the tray Run key and the HKLM\SOFTWARE\GE\ShopDB credential hive in both registry views, drops the Defender path exclusion, and deletes the install directory and ProgramData tree. Every step is best-effort and idempotent so it is safe to re-run and safe on a partially installed host.
2026-08-30 14:41:33 -04:00
benjamin.baker
b9f6815161 Green up
Tracking docs/openapi.json (so the app can serve the spec at runtime) makes docs/ exist on the published GitHub mirror, which defeated the skipif(not DOCS.is_dir()) guard in the docs publish/version tests: they then ran against a prose-less checkout and failed on CI. Key the skip off docs/*.md prose instead, matching the canonical published-checkout definition in scripts/check-gates-ran.sh.

Also: the OpenAPI generator emitted a trailing-slash path (/api/docs/), which a strict proxy like IIS treats as a redirecting resource a subpath-mounted client cannot follow; normalise trailing slashes in _norm_path and correct the committed spec. And allow docx (python-docx) as an undeclared import: it is used only by scripts/docs/generate_docx.py, a developer tool that never runs on a deployed site.
2026-08-30 11:59:01 -04:00
8666e0271c Reciprocal controls links, dead chip links, and a resumable legacy import
The machine page now shows the PC that controls it. The endpoint was correct all
along and returned both directions; AssetTriageBand discarded every peer whose
type was not 'machine' and was mounted only on the PC page. The counterpart type,
label and relationship types are now props.

Fixed while adopting it more widely: every band chip linked to /assets/<id>, and
there is no such route. The resolver that AssetRelationships.vue kept privately
is now frontend/src/utils/assetRoute.js, used by both - and its type lookup is
case-insensitive, because seed data capitalises the type names while this site's
database does not.

The legacy import is now safe to resume. The 409-resolve asked for
per_page=10000 against a server that clamps to 100, so it silently searched the
first 100 rows and dropped links past that on every re-run. The crosswalk is now
bound to the database it describes, so one carried across a rebuild is refused
rather than attaching rows to the wrong assets. Source database names are
resolved from the environment, so the loader can be aimed without editing it.

Full suite 2178 passed, frontend 280 passed.
2026-08-29 19:09:06 -04:00
a8f8580738 Upgrade the eight shipped dependencies carrying advisories
Clears every shipped finding the SBOM scan reported: pyjwt 2.13.0, idna 3.19,
axios 1.20.0, postcss 8.5.26, dompurify 3.4.14, nanoid 3.3.18, follow-redirects
1.16.0, form-data 4.0.6. All in-major bumps inside ranges the manifests already
allowed, so this is a lockfile refresh rather than a migration.

The axios and dompurify floors were raised to match, since the old ones still
nominally admitted the vulnerable range.

The PyJWT allowlist entry was removed rather than kept - it stopped matching once
the upgrade landed.

Verified: full suite 2161 passed, frontend 273 passed, build clean, npm audit
--omit=dev reports 0 vulnerabilities.
2026-08-29 17:42:58 -04:00
7ddfe4d3ef Recommend the smallest upgrade that clears an advisory, not the highest
The rollup took the highest fix version across a component's advisories, which
is wrong whenever an advisory is patched on more than one release branch. nanoid
is fixed on both 3.3.x and 5.x, and it is transitive under a postcss range of
^3.3.11 that no 5.x release can satisfy - so the recommendation was unreachable
rather than merely large.

Per advisory the target is now the lowest fix above the version in use; per
component, the highest of those. Fixes on older branches are skipped rather than
offered as a downgrade.
2026-08-29 17:24:52 -04:00