"""Explicit offline recovery; Deployment helpers are injected by the SSH wrapper.""" import os import re import subprocess def prepare_recovery(deployment): deployment.preflight() if deployment.pending.exists() or deployment.flag.exists(): raise RuntimeError('Resolve existing deployment/maintenance before administrator recovery') expected = manifest(deployment.app / 'current') folder = deployment.home / '.config/werkjournal' if folder.parent.is_symlink() or folder.is_symlink(): raise RuntimeError('Recovery directory must not be a symlink') folder.mkdir(mode=0o700, parents=True, exist_ok=True) if folder.stat().st_mode & 0o077: raise RuntimeError('Recovery directory must be private to its owner') destination = folder / 'admin-recovery-code' temporary = destination.with_name(destination.name + '.next') if destination.is_symlink() or temporary.exists() or temporary.is_symlink(): raise RuntimeError('Unexpected recovery file layout') deployment.maintenance() deployment.stop() try: # Discard JVM diagnostics: neither exception output nor process arguments contain the code. result = subprocess.run([str(deployment.home / 'opt/jdk25/bin/java'), '-Xmx128m', '-jar', str(deployment.app / 'current/werkjournal.jar'), '--prepare-admin-recovery'], cwd=deployment.app, env={'HOME': str(deployment.home), 'PATH': '/usr/bin:/bin'}, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, timeout=60) if result.returncode != 0 or re.fullmatch(r'[A-Za-z0-9_-]{43}', result.stdout) is None: raise RuntimeError('Recovery refused or failed; an administrator may still exist') atomic(destination, result.stdout + '\n') finally: deployment.start() deployment.ready(expected) deployment.flag.unlink() sync_directory(deployment.site) print('Recovery prepared for 24 hours. Read ~/.config/werkjournal/admin-recovery-code privately; enter it on the login setup form, then verify the email.') if __name__ == '__main__': os.umask(0o077) deployment = Deployment(Path.home()) with (deployment.app / '.deploy.lock').open('a') as lock: fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) prepare_recovery(deployment)