NanoJev-Web / scripts /release.py
candypunk's picture
Release NanoJev-Web browser action model
a0a9254 verified
Raw History Blame Contribute Delete
2.43 kB
"""Verify or export the exact public files listed in MANIFEST.json."""
import argparse,gzip,hashlib,json,tarfile
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
FORBIDDEN={'.local','.runtime','.venv','node_modules','__pycache__','.git','runs','bench'}
def digest(path):
h=hashlib.sha256()
with path.open('rb') as f:
for block in iter(lambda:f.read(8*1024*1024),b''):h.update(block)
return h.hexdigest()
def verify():
manifest=json.loads((ROOT/'MANIFEST.json').read_text())
for name,info in manifest['files'].items():
p=Path(name)
if name=='bench.command' or p.is_absolute() or '..' in p.parts or set(p.parts)&FORBIDDEN or any(x.startswith('.env') for x in p.parts):raise ValueError('Forbidden manifest path')
file=ROOT/p
if file.is_symlink() or not file.is_file() or ROOT not in file.resolve().parents:raise ValueError('Release file missing or not a regular contained file')
if file.stat().st_size!=info['bytes'] or digest(file)!=info['sha256']:raise ValueError('Release checksum mismatch: '+name)
return manifest
def export(path,manifest):
path=path.resolve()
if path==ROOT or ROOT in path.parents:raise ValueError('Export outside the release directory')
path.parent.mkdir(parents=True,exist_ok=True)
if path.exists():raise ValueError('Output already exists; choose a new filename')
with path.open('xb') as raw,gzip.GzipFile(filename='',fileobj=raw,mode='wb',mtime=0,compresslevel=6) as gz,tarfile.open(fileobj=gz,mode='w') as archive:
for name in sorted([*manifest['files'],'MANIFEST.json']):
file=ROOT/name;info=tarfile.TarInfo('NanoJev-Web/'+name)
info.size=file.stat().st_size;info.mode=0o755 if name.endswith('.command') or name.endswith('.sh') else 0o644
info.uid=info.gid=0;info.uname=info.gname='';info.mtime=0
with file.open('rb') as src:archive.addfile(info,src)
checksum=digest(path);path.with_suffix(path.suffix+'.sha256').write_text(checksum+' '+path.name+'\n')
print(json.dumps({'archive':path.name,'bytes':path.stat().st_size,'sha256':checksum}))
if __name__=='__main__':
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--export',type=Path);a=p.parse_args();m=verify()
print(json.dumps({'verified':True,'files':len(m['files']),'bytes':sum(i['bytes'] for i in m['files'].values())}))
if a.export:export(a.export,m)