Bind essentials recovery to explicit manifest and verified offsite provenance
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
This commit is contained in:
parent
4707bf08d7
commit
11322a5256
7 changed files with 151 additions and 14 deletions
40
scripts/forgejo_essentials_profile.py
Normal file
40
scripts/forgejo_essentials_profile.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Seal a measured essentials archive with an explicit recovery contract."""
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import os
|
||||
from pathlib import PurePosixPath
|
||||
import shutil
|
||||
import stat
|
||||
import zipfile
|
||||
|
||||
MANIFEST='backup-manifest.json'
|
||||
OMITTED=('data/packages/','data/repo-archive/','data/indexers/','data/actions_log/','log/')
|
||||
BUDGET=600*1024**2
|
||||
|
||||
|
||||
def seal(path):
|
||||
temporary=path.with_name(path.name+'.sealing')
|
||||
fd=os.open(temporary,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd)
|
||||
try:
|
||||
with zipfile.ZipFile(path) as source, zipfile.ZipFile(temporary,'w') as target:
|
||||
kept=0
|
||||
for member in source.infolist():
|
||||
name=PurePosixPath(member.filename)
|
||||
if name.is_absolute() or '..' in name.parts or stat.S_ISLNK(member.external_attr>>16):
|
||||
raise ValueError('unsafe_archive_member')
|
||||
if member.filename==MANIFEST: raise ValueError('archive_already_sealed')
|
||||
if member.filename.startswith(OMITTED): continue
|
||||
with source.open(member) as incoming, target.open(member,'w') as outgoing:
|
||||
shutil.copyfileobj(incoming,outgoing,1024*1024)
|
||||
kept+=1
|
||||
target.writestr(MANIFEST,json.dumps({'schema':'platform.forgejo-backup-profile.v1',
|
||||
'profile':'essentials','created_at':datetime.now(timezone.utc).isoformat(),
|
||||
'complete_application_backup':False,'omitted_prefixes':list(OMITTED),
|
||||
'kept_members':kept,'package_registry_available':False,
|
||||
'recovery_contract':'Git and collaboration recovery; packages require full primary or an independent artifact source.',
|
||||
'unique_data_policy':'Keep repositories, database, configuration, attachments, LFS and Actions artifacts when present.'},sort_keys=True),compress_type=zipfile.ZIP_DEFLATED)
|
||||
if temporary.stat().st_size>BUDGET: raise ValueError('essentials_budget_exceeded')
|
||||
os.replace(temporary,path)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
Loading…
Add table
Add a link
Reference in a new issue