67 lines
3.1 KiB
Python
Executable File
67 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Package a clean, committed source tree and optional Linux executables.
|
|
|
|
No user worlds, tokens, Cargo caches, Minecraft JAR, or untracked files enter
|
|
an archive. Every extracted file is checked against the in-archive manifest.
|
|
"""
|
|
import argparse
|
|
import gzip
|
|
import hashlib
|
|
import io
|
|
import json
|
|
import subprocess
|
|
import tarfile
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
def git(*args):
|
|
return subprocess.check_output(['git', *args], cwd=ROOT)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--binaries', action='store_true')
|
|
args = parser.parse_args()
|
|
if git('status', '--porcelain').strip():
|
|
raise SystemExit('Commit the reviewed tree before packaging.')
|
|
revision = git('rev-parse', 'HEAD').decode().strip()
|
|
epoch = int(git('show', '-s', '--format=%ct', 'HEAD'))
|
|
paths = [Path(p.decode()) for p in git('ls-files', '-z').split(b'\0') if p]
|
|
if args.binaries:
|
|
paths += [Path('target/release') / name for name in
|
|
['shacraft-server', 'shacraft-mcp', 'shacraft-compat', 'shacraft-tools']]
|
|
files = []
|
|
for relative in sorted(paths):
|
|
path = ROOT / relative
|
|
if path.is_symlink() or not path.is_file():
|
|
raise SystemExit(f'Only regular files are distributable: {relative}')
|
|
if relative.name.endswith('.token') or relative.name == '.env':
|
|
raise SystemExit(f'Secret filename cannot be distributed: {relative}')
|
|
data = path.read_bytes()
|
|
files.append((relative.as_posix(), data, 0o755 if path.stat().st_mode & 0o111 else 0o644))
|
|
manifest = {'project':'Shacraft Core','git_revision':revision,'source_epoch':epoch,
|
|
'kind':'linux-x86_64' if args.binaries else 'source',
|
|
'license':'MIT OR Apache-2.0',
|
|
'files':{name:{'sha256':hashlib.sha256(data).hexdigest(),'size':len(data)} for name,data,_ in files}}
|
|
files.append(('RELEASE.json', (json.dumps(manifest,indent=2)+'\n').encode(), 0o644))
|
|
output = ROOT / 'artifacts' / f"shacraft-core-mvp-{revision[:8]}-{manifest['kind']}.tar.gz"
|
|
output.parent.mkdir(exist_ok=True)
|
|
with output.open('wb') as raw, gzip.GzipFile(fileobj=raw,mode='wb',mtime=0) as zipped:
|
|
with tarfile.open(fileobj=zipped,mode='w') as archive:
|
|
for name,data,mode in files:
|
|
info=tarfile.TarInfo('shacraft-core/'+name)
|
|
info.size=len(data);info.mode=mode;info.mtime=epoch
|
|
archive.addfile(info,io.BytesIO(data))
|
|
digest=hashlib.sha256(output.read_bytes()).hexdigest()
|
|
output.with_name(output.name+'.sha256').write_text(f'{digest} {output.name}\n')
|
|
with tarfile.open(output,'r:gz') as archive:
|
|
for name,data,_ in files:
|
|
actual=archive.extractfile('shacraft-core/'+name).read()
|
|
if actual!=data:
|
|
raise SystemExit(f'Archive verification failed for {name}')
|
|
print(json.dumps({'archive':str(output),'sha256':digest,'bytes':output.stat().st_size,
|
|
'git_revision':revision,'verified_files':len(files)},indent=2))
|
|
|
|
if __name__=='__main__':
|
|
main()
|