Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
62 lines
3.1 KiB
Python
62 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
"""Back up boot partitions through the existing SSH master; sudo stays in the user's terminal."""
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def main():
|
|
if not sys.stdin.isatty():
|
|
raise RuntimeError('Run in your terminal: sudo may ask for the phone password there')
|
|
socket = ROOT / 'logs/device/ssh-control'
|
|
spec = importlib.util.spec_from_file_location('collector', ROOT / 'tools/collect-device-ssh.py')
|
|
collector = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(collector)
|
|
base = collector.ssh_base(socket, '10.15.19.82', 'droidian')
|
|
subprocess.run(base[:-1] + ['-O', 'check', base[-1]], check=True)
|
|
source = (ROOT / 'droidian/backup-boot.py').read_bytes()
|
|
sha = hashlib.sha256(source).hexdigest()
|
|
remote = '/home/droidian/vulpes-tundra-tests/backup-boot-' + sha[:16] + '.py'
|
|
subprocess.run(base + [shlex.join(['python3', '-c',
|
|
'import pathlib,sys;pathlib.Path(sys.argv[1]).write_bytes(sys.stdin.buffer.read())', remote])],
|
|
input=source, check=True)
|
|
# Password entry goes directly to the terminal, never to logs or the assistant.
|
|
subprocess.run(base[:-1] + ['-t', base[-1], shlex.join(['sudo', '/usr/bin/python3', '-I', remote])], check=True)
|
|
listing = subprocess.check_output(base + [shlex.join(['python3', '-c',
|
|
"import pathlib;print('\\n'.join(str(p) for p in sorted(pathlib.Path('/var/tmp').glob('vulpes-boot-backup-*/manifest.json')) if p.is_file()))"])], text=True)
|
|
os.umask(0o077)
|
|
destination = ROOT.parents[2] / 'backups/tundra/sargo/raw-boot'
|
|
for entry in listing.splitlines():
|
|
folder = Path(entry).parent
|
|
if folder.parent != Path('/var/tmp') or not folder.name.startswith('vulpes-boot-backup-'):
|
|
raise RuntimeError('Unexpected remote backup path')
|
|
read = lambda p: subprocess.check_output(base + [shlex.join(['cat', str(p)])], timeout=120)
|
|
manifest_bytes = read(folder / 'manifest.json')
|
|
manifest = json.loads(manifest_bytes)
|
|
expected = {'boot_a.img', 'boot_b.img', 'dtbo_a.img', 'dtbo_b.img', 'vbmeta_a.img', 'vbmeta_b.img'}
|
|
if manifest.get('device') != 'sargo' or set(manifest.get('files', {})) != expected:
|
|
raise RuntimeError('Invalid backup manifest')
|
|
local = destination / folder.name
|
|
local.mkdir(parents=True, exist_ok=True)
|
|
for name, info in manifest['files'].items():
|
|
target = local / name
|
|
data = target.read_bytes() if target.exists() else read(folder / name)
|
|
if len(data) != info['bytes'] or hashlib.sha256(data).hexdigest() != info['sha256']:
|
|
raise RuntimeError('Backup checksum mismatch: ' + name)
|
|
if not target.exists():
|
|
with target.open('xb') as output:
|
|
output.write(data)
|
|
(local / 'manifest.json').write_bytes(manifest_bytes)
|
|
print('Verified on PC:', local)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|