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.
This commit is contained in:
@@ -15,6 +15,12 @@ Authorization boundary (deliberate):
|
||||
command family from application deploys (endpoints.deploy) so app-push rights
|
||||
never imply SYSTEM-level agent replacement. That push pipe is a later slice.
|
||||
|
||||
Staging also keeps the pushable application row current (see agentregistry):
|
||||
whenever the current build changes, the bundle is repackaged and the row's
|
||||
installpath/installersha256 are rewritten. Registration is deliberately NOT
|
||||
allowed to fail the upload - the binary is already staged and audited by then,
|
||||
so a packaging problem is reported in the response payload rather than raised.
|
||||
|
||||
Uploads are capped and PE-checked so a wrong or hostile file cannot be staged.
|
||||
"""
|
||||
|
||||
@@ -24,12 +30,47 @@ from shopdb.api import (success_response, error_response, ErrorCodes, AuditLog,
|
||||
require_permission)
|
||||
|
||||
from .routes import endpoints_bp
|
||||
from . import agentstore
|
||||
from . import agentstore, agentregistry
|
||||
|
||||
# Hard cap on an uploaded agent binary. The real agent is a few MB; this is a
|
||||
# generous ceiling that still refuses an operator who points at a giant wrong
|
||||
# file before it is buffered fully into memory-staged storage.
|
||||
_MAX_UPLOAD_BYTES = 64 * 1024 * 1024
|
||||
# Hard cap on an uploaded agent binary. This is a generous ceiling that still
|
||||
# refuses an operator who points at a giant wrong file before it is buffered
|
||||
# fully into memory-staged storage.
|
||||
#
|
||||
# 128 MB, not 64: the self-contained single-file tray publishes at ~69 MB
|
||||
# (it carries the whole .NET runtime plus WinForms), which the old 64 MB cap
|
||||
# rejected outright. The upload failed on SIZE, but the operator saw only a
|
||||
# version-detection failure, because the size check returns before the PE is
|
||||
# ever parsed. Config.MAX_CONTENT_LENGTH must stay above this - it bounds the
|
||||
# whole multipart envelope, so a cap equal to it would reject a legal file on
|
||||
# the boundary bytes alone.
|
||||
_MAX_UPLOAD_BYTES = 128 * 1024 * 1024
|
||||
|
||||
|
||||
def _sync_agent_registration():
|
||||
"""Repackage and re-register the current build. Returns a status dict.
|
||||
|
||||
Called from every route that can change which build is current, so the
|
||||
pushable application row can never describe a build that is no longer
|
||||
staged. That drift is the whole failure this is meant to prevent, and it
|
||||
would be silent - the row would keep pointing at bytes nobody selected.
|
||||
|
||||
Never raises. Staging has already succeeded on disk and been audited by the
|
||||
time this runs; a packaging or database problem must not turn a successful
|
||||
upload into an error response and leave the operator thinking the binary
|
||||
was rejected. The failure is reported in the response payload instead, so
|
||||
the UI can surface it without losing the upload.
|
||||
"""
|
||||
try:
|
||||
registration = agentregistry.register_current_agent_package()
|
||||
except Exception as exc: # noqa: BLE001 - reported, not swallowed
|
||||
db.session.rollback()
|
||||
return {'ok': False, 'error': str(exc)}
|
||||
if registration is None:
|
||||
# No current agent build to package. Normal when only a tray has been
|
||||
# staged: the bundle is defined by the agent, so there is nothing to
|
||||
# register yet.
|
||||
return {'ok': False, 'error': 'no current agent build to package'}
|
||||
return {'ok': True, **registration}
|
||||
|
||||
|
||||
@endpoints_bp.route('/siteagent/binaries/<binkey>', methods=['GET'])
|
||||
@@ -69,11 +110,6 @@ def upload_agent_binary(binkey):
|
||||
ErrorCodes.VALIDATION_ERROR,
|
||||
'binkey must be one of: agent, tray',
|
||||
http_code=400)
|
||||
if not agentstore.is_valid_version(version):
|
||||
return error_response(
|
||||
ErrorCodes.VALIDATION_ERROR,
|
||||
'version must be a dotted-numeric string such as 1.6.3',
|
||||
http_code=400)
|
||||
|
||||
upload = request.files.get('file')
|
||||
if upload is None or not upload.filename:
|
||||
@@ -94,6 +130,22 @@ def upload_agent_binary(binkey):
|
||||
'uploaded file is not a Windows executable',
|
||||
http_code=400)
|
||||
|
||||
# Gate the version at the PE, not at the raw input. Product versions carry
|
||||
# semver build metadata (1.5.0+abcdef...) that is not a legal directory
|
||||
# name, so the operator's typed string is only a hint. The dotted-numeric
|
||||
# FILE version stamped in the binary's PE resource is the authority; fall
|
||||
# back to it whenever the typed value is missing or not dotted-numeric, and
|
||||
# only reject when neither yields a usable version.
|
||||
if not agentstore.is_valid_version(version):
|
||||
detected = agentstore.read_pe_file_version(data)
|
||||
if agentstore.is_valid_version(detected):
|
||||
version = detected
|
||||
else:
|
||||
return error_response(
|
||||
ErrorCodes.VALIDATION_ERROR,
|
||||
'version must be a dotted-numeric string such as 1.6.3',
|
||||
http_code=400)
|
||||
|
||||
result = agentstore.store_version(binkey, version, data,
|
||||
make_current=makecurrent)
|
||||
|
||||
@@ -110,10 +162,16 @@ def upload_agent_binary(binkey):
|
||||
'madecurrent': makecurrent})
|
||||
db.session.commit()
|
||||
|
||||
# Keep the pushable application row in step with what is now current, in
|
||||
# the same request. Doing it here rather than at push time means the row
|
||||
# and the staged build cannot disagree in between.
|
||||
registration = _sync_agent_registration()
|
||||
|
||||
return success_response({
|
||||
'binary': binkey,
|
||||
'current': result['current'],
|
||||
'versions': result['versions'],
|
||||
'registration': registration,
|
||||
})
|
||||
|
||||
|
||||
@@ -196,10 +254,12 @@ def adopt_legacy_agent_binary(binkey):
|
||||
ErrorCodes.VALIDATION_ERROR,
|
||||
'the legacy binary could not be adopted',
|
||||
http_code=400)
|
||||
registration = _sync_agent_registration()
|
||||
return success_response({
|
||||
'binary': binkey,
|
||||
'current': result['current'],
|
||||
'versions': result['versions'],
|
||||
'registration': registration,
|
||||
})
|
||||
|
||||
|
||||
@@ -224,8 +284,13 @@ def set_current_agent_binary(binkey):
|
||||
ErrorCodes.NOT_FOUND,
|
||||
'that version is not staged',
|
||||
http_code=404)
|
||||
# A rollback changes the bytes the fleet should receive; repackage so the
|
||||
# application row follows the pointer instead of pinning the build the
|
||||
# operator just rolled away from.
|
||||
registration = _sync_agent_registration()
|
||||
return success_response({
|
||||
'binary': binkey,
|
||||
'current': agentstore.current_version(binkey),
|
||||
'versions': agentstore.list_versions(binkey),
|
||||
'registration': registration,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user