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:
@@ -16,6 +16,8 @@ from .communication import Communication, CommunicationType
|
||||
from .user import (User, Role, Permission, full_permission_catalog,
|
||||
guest_grantable_permissions)
|
||||
from .application import Application, AppVersion
|
||||
from .script_library import (Script, RUNCONTEXTS, RUNCONTEXT_SYSTEM,
|
||||
RUNCONTEXT_USER)
|
||||
from .supportteam import SupportTeam, SupportTeamContact
|
||||
from .setting import Setting
|
||||
from .auditlog import AuditLog
|
||||
@@ -62,6 +64,11 @@ __all__ = [
|
||||
# Applications
|
||||
'Application',
|
||||
'AppVersion',
|
||||
# Script library (endpoint run_script delivery)
|
||||
'Script',
|
||||
'RUNCONTEXTS',
|
||||
'RUNCONTEXT_SYSTEM',
|
||||
'RUNCONTEXT_USER',
|
||||
# Support teams
|
||||
'SupportTeam',
|
||||
'SupportTeamContact',
|
||||
|
||||
@@ -34,6 +34,11 @@ class Application(BaseModel):
|
||||
comment='Required on all PCs (drives the software-compliance report)'
|
||||
)
|
||||
image = db.Column(db.String(255))
|
||||
# Digest of the app-level uploaded installer (installpath). Lets the
|
||||
# endpoint update_app push resolve an install source by app alone, with no
|
||||
# AppVersion row: the agent fetches the pinned bytes and verifies this hash.
|
||||
# Stamped on package upload; null when installpath is a site share path.
|
||||
installersha256 = db.Column(db.String(64))
|
||||
|
||||
# Relationships
|
||||
supportteam = db.relationship('SupportTeam', back_populates='applications')
|
||||
@@ -70,6 +75,12 @@ class AppVersion(db.Model):
|
||||
notes = db.Column(db.String(255))
|
||||
dateadded = db.Column(db.DateTime, default=_utcnow)
|
||||
isactive = db.Column(db.Boolean, default=True)
|
||||
# Delivery metadata for the endpoint installer push. sha256 pins the exact
|
||||
# bytes the agent must fetch (warn-not-block for now; hardened later);
|
||||
# silentargs carries this version's unattended-install switches because
|
||||
# homegrown EXEs vary by developer (MSI is uniform via msiexec).
|
||||
sha256 = db.Column(db.String(64))
|
||||
silentargs = db.Column(db.String(255))
|
||||
|
||||
# Relationships
|
||||
application = db.relationship('Application', back_populates='versions')
|
||||
@@ -88,7 +99,9 @@ class AppVersion(db.Model):
|
||||
'releasedate': self.releasedate.isoformat() if self.releasedate else None,
|
||||
'notes': self.notes,
|
||||
'dateadded': self.dateadded.isoformat() + 'Z' if self.dateadded else None,
|
||||
'isactive': self.isactive
|
||||
'isactive': self.isactive,
|
||||
'sha256': self.sha256,
|
||||
'silentargs': self.silentargs
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
43
shopdb/core/models/script_library.py
Normal file
43
shopdb/core/models/script_library.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""ShopDB-hosted script library for endpoint run_script delivery.
|
||||
|
||||
A script is authored/uploaded once here, and the endpoint agent fetches its
|
||||
body by digest on push (mirroring the application installer delivery path). The
|
||||
body never lives on the endpoint ahead of time and never crosses the command
|
||||
wire; the agent downloads it with its collector credential and verifies sha256.
|
||||
"""
|
||||
|
||||
from shopdb.extensions import db
|
||||
from .base import BaseModel
|
||||
|
||||
# Execution context for a delivered script. SYSTEM is the service account the
|
||||
# agent already runs as (no user session, cannot see mapped drives); USER runs
|
||||
# in the interactive logged-on user's session. Default SYSTEM stays fail-safe.
|
||||
RUNCONTEXT_SYSTEM = 'system'
|
||||
RUNCONTEXT_USER = 'user'
|
||||
RUNCONTEXTS = (RUNCONTEXT_SYSTEM, RUNCONTEXT_USER)
|
||||
|
||||
|
||||
class Script(BaseModel):
|
||||
"""ShopDB-hosted PowerShell script the endpoint agent runs on push."""
|
||||
__tablename__ = 'scripts'
|
||||
|
||||
scriptid = db.Column(db.Integer, primary_key=True)
|
||||
scriptname = db.Column(db.String(100), unique=True, nullable=False)
|
||||
scriptdescription = db.Column(db.String(255))
|
||||
# Stored file name under the scripts upload directory (script-<id>.ps1). The
|
||||
# body is served by the collector-authenticated download route.
|
||||
filename = db.Column(db.String(255))
|
||||
# Digest of the stored body; the agent verifies this before running.
|
||||
sha256 = db.Column(db.String(64))
|
||||
# 'system' or 'user' (see RUNCONTEXTS). Per-script so a drive-mapping script
|
||||
# can target the user session while a machine-wide script runs as SYSTEM.
|
||||
runcontext = db.Column(db.String(16), default=RUNCONTEXT_SYSTEM, nullable=False)
|
||||
|
||||
def to_dict(self):
|
||||
result = super().to_dict()
|
||||
# hasbody lets the UI disable pushing a script with no uploaded file.
|
||||
result['hasbody'] = bool(self.filename and self.sha256)
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Script {self.scriptname}>"
|
||||
Reference in New Issue
Block a user