geenforce: HTTPS payload delivery (content-addressed blob store + endpoint)

Lets share-less (Intune/local-account) PCs pull installers the manifest
references over HTTPS instead of SMB - the general capability the whole fleet
migrates toward. New ManifestBlob registry (migration 0002) with bytes on disk
at instance/geenforce/payloads/<sha256> (deduped by content); service.store_blob
+ blob_path; client-facing GET /api/geenforce/payload/<sha256> (geenforce.fetch
token, ETag=hash, serves the blob store or an inline DB payload by hash). The
serializer now emits PayloadSource/PayloadSha256/PayloadRef for http/inline
entries only (smb entries round-trip unchanged - parity green). CLI
'flask geenforce add-payload <file>' registers a blob and prints its sha256.
This is the shopdb half (B1); the PS client/engine fetch is B2.
This commit is contained in:
cproudlock
2026-07-21 10:10:59 -04:00
parent 60e2947fc7
commit b00ef72581
8 changed files with 268 additions and 6 deletions

View File

@@ -0,0 +1,42 @@
"""geenforce: manifestblobs (content-addressed http payload store)
Big installers reach share-less (Intune/local-account) PCs by HTTPS download
instead of SMB. The bytes live on disk at <instance>/geenforce/payloads/<sha>;
this table is the registry (dedup by content hash). Idempotent guard.
Revision ID: geenforce0002blobs
Revises: geenforce0001baseline
Create Date: 2026-07-21
"""
from alembic import op
import sqlalchemy as sa
revision = 'geenforce0002blobs'
down_revision = 'geenforce0001baseline'
branch_labels = None
depends_on = None
def upgrade():
bind = op.get_bind()
insp = sa.inspect(bind)
if 'manifestblobs' in insp.get_table_names():
return
op.create_table(
'manifestblobs',
sa.Column('sha256', sa.String(length=64), nullable=False),
sa.Column('filename', sa.String(length=255), nullable=False),
sa.Column('contenttype', sa.String(length=128), nullable=True),
sa.Column('sizebytes', sa.BigInteger(), nullable=False),
sa.Column('createdat', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('sha256'),
)
def downgrade():
bind = op.get_bind()
insp = sa.inspect(bind)
if 'manifestblobs' in insp.get_table_names():
op.drop_table('manifestblobs')