Add installer/script delivery metadata to the core software model

AppVersion gains sha256 and silentargs so the endpoint update_app push can pin exact installer bytes and carry per-version silent switches; Application gains installersha256 for the app-level install source. New ShopDB-hosted script library (Script model plus scripts API) backs run_script delivery, with the body fetched by digest on push rather than pre-staged or sent over the command wire. Two idempotent migrations with real downgrades. Script added to the shopdb.api contract surface for plugin use.
This commit is contained in:
benjamin.baker
2026-08-30 14:39:58 -04:00
committed by cproudlock
parent 5f8061c55d
commit 7ac1772881
12 changed files with 483 additions and 3 deletions

View File

@@ -0,0 +1,42 @@
"""AppVersion installer-delivery metadata: sha256 + silentargs.
These two per-version fields feed the endpoint installer push: sha256 pins the
exact bytes the agent fetches, and silentargs carries the version's unattended-
install switches. The API must accept and echo both, and the model must
serialize them.
"""
def _create_app(client, auth_headers, appname='DeliveryApp'):
resp = client.post('/api/applications',
json={'appname': appname, 'isinstallable': True},
headers=auth_headers)
return resp.get_json()['data']['appid']
def test_create_version_accepts_and_echoes_delivery_metadata(client, db, auth_headers):
"""POST versions with sha256 + silentargs; the response carries both back."""
appid = _create_app(client, auth_headers)
digest = 'a' * 64
resp = client.post(f'/api/applications/{appid}/versions',
json={'version': '1.2.3',
'sha256': digest,
'silentargs': '/S /v/qn'},
headers=auth_headers)
assert resp.status_code == 201
data = resp.get_json()['data']
assert data['sha256'] == digest
assert data['silentargs'] == '/S /v/qn'
def test_version_delivery_metadata_defaults_to_null(client, db, auth_headers):
"""A version created without delivery metadata serializes both as null,
so a version that predates the push feature reads cleanly."""
appid = _create_app(client, auth_headers, appname='NoMetaApp')
resp = client.post(f'/api/applications/{appid}/versions',
json={'version': '0.9.0'},
headers=auth_headers)
assert resp.status_code == 201
data = resp.get_json()['data']
assert data['sha256'] is None
assert data['silentargs'] is None

View File

@@ -83,6 +83,15 @@ PUBLIC_ENDPOINTS = {
# requesting user. Same reasoning as endpoints.report_command_result in
# tests/test_core/test_authz.py.
'endpoints.claim_next_command',
# The endpoint agent downloads an installer package for an update_app
# command. Authenticated inside the handler by the endpoints collector API
# key - the same shape as claim_next_command - because a bay has no user
# session. Queueing the update_app command is the actual authority and stays
# gated on endpoints.deploy, audited with the requesting user.
'endpoints.download_app_package',
'endpoints.download_app_package_by_app',
'endpoints.download_script_body',
}
# Reads that need a LOGIN but no named permission, because the handler decides