133 lines
6.7 KiB
Python
133 lines
6.7 KiB
Python
"""Explicitly resume the user-requested backup and verified workstation transfer.
|
||
|
||
This script connects to the permanent test stand. Run only at the user's request.
|
||
Does not launch training, alter drivers, remove runs, or overwrite original KNP code.
|
||
"""
|
||
from datetime import datetime, timezone
|
||
import hashlib
|
||
import json
|
||
from pathlib import Path
|
||
import subprocess
|
||
import sys
|
||
import tarfile
|
||
|
||
ROOT = Path(__file__).resolve().parent
|
||
STATE = ROOT / 'handoff-state'
|
||
HOST = 'int@10.9.8.230'
|
||
KEY = str(Path.home() / '.ssh/id_ed25519_snw')
|
||
SSH = ['ssh', '-i', KEY, '-o', 'IdentitiesOnly=yes', '-o', 'BatchMode=yes',
|
||
'-o', 'ConnectTimeout=10', '-o', 'ServerAliveInterval=15',
|
||
'-o', 'ServerAliveCountMax=2', HOST]
|
||
|
||
|
||
def manifest():
|
||
excluded = {'SHA256SUMS', 'preservation-manifest.json', 'preservation-status.json'}
|
||
rows = []
|
||
for path in sorted(ROOT.rglob('*')):
|
||
if not path.is_file() or '__pycache__' in path.parts or path.name.endswith('.part'):
|
||
continue
|
||
relative = path.relative_to(ROOT).as_posix()
|
||
if relative in excluded:
|
||
continue
|
||
digest = hashlib.sha256()
|
||
with path.open('rb') as stream:
|
||
for block in iter(lambda: stream.read(1024 * 1024), b''):
|
||
digest.update(block)
|
||
rows.append({'path': relative, 'bytes': path.stat().st_size, 'sha256': digest.hexdigest()})
|
||
(ROOT / 'preservation-manifest.json').write_text(json.dumps({'files': rows}, indent=2) + '\n')
|
||
(ROOT / 'SHA256SUMS').write_text(''.join(f"{r['sha256']} {r['path']}\n" for r in rows))
|
||
return len(rows)
|
||
|
||
|
||
def status(success, detail):
|
||
(ROOT / 'preservation-status.json').write_text(json.dumps({
|
||
'updated_utc': datetime.now(timezone.utc).isoformat(),
|
||
'remote_verified': success, 'detail': detail,
|
||
'destination': HOST + ':/mnt/c/KNP/cats-dogs',
|
||
}, ensure_ascii=False, indent=2) + '\n')
|
||
|
||
|
||
def capture(name, command):
|
||
result = subprocess.run(SSH + [command], capture_output=True, check=True)
|
||
(STATE / name).write_bytes(result.stdout)
|
||
|
||
|
||
def archive(name, command):
|
||
path = STATE / name
|
||
temporary = path.with_suffix(path.suffix + '.part')
|
||
try:
|
||
with temporary.open('wb') as output:
|
||
subprocess.run(SSH + [command], stdout=output, check=True)
|
||
with tarfile.open(temporary, 'r:gz') as saved:
|
||
for _ in saved:
|
||
pass
|
||
temporary.replace(path)
|
||
finally:
|
||
temporary.unlink(missing_ok=True)
|
||
|
||
|
||
def main():
|
||
STATE.mkdir(exist_ok=True)
|
||
if '--local-manifest-only' in sys.argv:
|
||
print('Local files hashed:', manifest())
|
||
return
|
||
status(False, 'Backup/transfer in progress; no success claimed yet')
|
||
try:
|
||
subprocess.run(SSH + ['true'], check=True)
|
||
commands = {
|
||
'remote-inventory.txt': 'date -u; uname -a; ls -la /mnt/c/KNP; ls -la /mnt/c/KNP/cats-dogs; find /home/int/run/cats-dogs -maxdepth 2 -type f; ps -eo pid,etime,args | grep -E "cats_dogs|http.server|nvcc|build_fast|build_tuned" | grep -v grep || true',
|
||
'original-knp-status.txt': 'git -C /mnt/c/KNP/examples rev-parse HEAD; git -C /mnt/c/KNP/examples status --short',
|
||
'original-knp-changes.patch': 'git -C /mnt/c/KNP/examples diff --binary',
|
||
'visualizer-status.txt': 'git -C /mnt/c/KNP/viz rev-parse HEAD; git -C /mnt/c/KNP/viz status --short',
|
||
'visualizer-changes.patch': 'git -C /mnt/c/KNP/viz diff --binary',
|
||
'compiler-flags.txt': 'cat /home/int/knp-build/knp/backends/cpu/cpu-single-threaded-backend/CMakeFiles/knp-cpu-single-threaded-backend.dir/flags.make /home/int/knp-build/knp/backends/cpu/cpu-single-threaded-backend/CMakeFiles/knp-cpu-single-threaded-backend.dir/link.txt; /usr/bin/c++ --version; /home/int/cuda-12.9/bin/nvcc --version; /usr/lib/wsl/lib/nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader',
|
||
}
|
||
for name, command in commands.items():
|
||
capture(name, command)
|
||
print('Captured state', flush=True)
|
||
# First deliver the existing local context, even if a later full backup fails.
|
||
manifest()
|
||
transport = 'ssh -i ' + KEY + ' -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10'
|
||
def upload():
|
||
available = subprocess.run(SSH + ['command -v rsync'], capture_output=True)
|
||
if available.returncode == 0:
|
||
subprocess.run(['rsync', '-a', '--exclude=__pycache__', '--exclude=*.part',
|
||
'-e', transport, str(ROOT) + '/', HOST + ':/mnt/c/KNP/cats-dogs/'], check=True)
|
||
else:
|
||
pack = subprocess.Popen(['tar', '--exclude=__pycache__', '--exclude=*.part',
|
||
'-cf', '-', '-C', str(ROOT), '.'], stdout=subprocess.PIPE)
|
||
try:
|
||
subprocess.run(SSH + ['mkdir -p /mnt/c/KNP/cats-dogs && tar -xf - -C /mnt/c/KNP/cats-dogs'],
|
||
stdin=pack.stdout, check=True)
|
||
finally:
|
||
pack.stdout.close()
|
||
result = pack.wait()
|
||
if result:
|
||
raise RuntimeError(f'Upload tar exited {result}')
|
||
subprocess.run(SSH + ['cp /mnt/c/KNP/cats-dogs/HANDOFF.md /mnt/c/KNP/HANDOFF-2026-10-14.md && '
|
||
'cd /mnt/c/KNP/cats-dogs && sha256sum -c SHA256SUMS >/tmp/knp-handoff-check.log'], check=True)
|
||
upload()
|
||
print('Existing local context delivered and verified', flush=True)
|
||
archive('remote-runtime.tar.gz', 'tar -czf - -C /home/int/run cats-dogs')
|
||
archive('prior-context-and-source.tar.gz',
|
||
'tar -czf - -C / mnt/c/KNP/cats-dogs/source mnt/c/KNP/viz/altai_viz.html '
|
||
'mnt/c/KNP/viz/app.html mnt/c/KNP/Отчёт_MNIST_точность.md '
|
||
'mnt/c/KNP/Отчёт_altai.md mnt/c/KNP/Отчёт_колонки_altai.md '
|
||
'mnt/c/KNP/Отчёт_размер_колонки_и_одежда.md '
|
||
'home/int/.claude/projects/-mnt-c-knp/memory '
|
||
'home/int/.claude/projects/-mnt-c-KNP/memory')
|
||
print('Archives validated; local files hashed:', manifest(), flush=True)
|
||
upload()
|
||
status(True, 'Runtime/context archives validated, files transferred, remote SHA256SUMS passed')
|
||
subprocess.run(['scp', '-i', KEY, '-o', 'IdentitiesOnly=yes', '-o', 'BatchMode=yes',
|
||
'-o', 'ConnectTimeout=10', str(ROOT / 'preservation-status.json'),
|
||
HOST + ':/mnt/c/KNP/cats-dogs/preservation-status.json'], check=True)
|
||
print('Verified workstation copy: /mnt/c/KNP/HANDOFF-2026-10-14.md')
|
||
except Exception as error:
|
||
status(False, str(error))
|
||
manifest()
|
||
raise
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|