import pathlib,json,hashlib,tarfile,io,subprocess,sys,tempfile archive,fqdn=sys.argv[1:] stack=pathlib.Path('/opt/skeletonworks/conductor')/fqdn def fingerprint():return {p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in stack.iterdir() if p.is_file()} before=fingerprint() with tempfile.TemporaryDirectory() as directory: directory=pathlib.Path(directory) with tarfile.open(archive) as source: original=[(m,source.extractfile(m).read() if m.isfile() else b'') for m in source.getmembers()] for kind in ['checksum','symlink','traversal','missing-applications']: target=directory/(kind+'.tar.gz') with tarfile.open(target,'w:gz') as output: for member,data in original: if kind=='missing-applications' and (member.name=='applications' or member.name.startswith('applications/')):continue if kind=='checksum' and member.name=='manifest.json':data=data+b' ' member.size=len(data) if member.isfile() else 0 output.addfile(member,io.BytesIO(data) if member.isfile() else None) if kind in ['symlink','traversal']: member=tarfile.TarInfo('unsafe' if kind=='symlink' else '../unsafe') if kind=='symlink':member.type=tarfile.SYMTYPE;member.linkname='/etc/passwd' output.addfile(member) result=subprocess.run(['/opt/skeletonworks/scripts/restore-conductor.sh','--fqdn',fqdn,'--backup-file',str(target),'--force','--skip-safety-backup'],capture_output=True,text=True) assert result.returncode!=0,kind assert fingerprint()==before,kind+' changed configuration before rejecting the archive' print('PASS: corrupted, linked, traversing and incomplete-app backups rejected without configuration changes.')