"""Remove only the retired IKAR installation after a verified replacement.""" from pathlib import Path import datetime, json, os, shutil, sqlite3, subprocess, tarfile, tempfile, urllib.request root=Path('/mnt/data/ikar_server') rollback=root/'rollback' assert os.geteuid()==0 and os.path.ismount('/mnt/data') health=json.load(urllib.request.urlopen('http://127.0.0.1:5099/health',timeout=15)) assert health['status']=='ok' and health['database']=='ok' and health['disk']=='ok' baseline=[json.loads(x) for x in (rollback/'services-before.jsonl').read_text().splitlines() if x.strip()] current={x['Names']:x for x in map(json.loads,subprocess.check_output(['docker','ps','--format','{{json .}}'],text=True).splitlines())} for before in baseline: assert current[before['Names']]['ID']==before['ID'] and current[before['Names']]['State']=='running' archive=rollback/'old-ikar-20261005.tar.gz' with tarfile.open(archive) as t, tempfile.TemporaryDirectory(dir=rollback,prefix='verify-') as temp: member=next(x for x in t.getmembers() if x.isfile() and x.name.endswith('/ikar.db')) database=Path(temp)/'old.db' with t.extractfile(member) as source, database.open('wb') as target: shutil.copyfileobj(source,target) with sqlite3.connect(f'file:{database}?mode=ro',uri=True) as db: assert db.execute('pragma quick_check').fetchone()[0]=='ok' containers=['ikar-server','ikar-turn'] existing=set(subprocess.check_output(['docker','ps','-a','--format','{{.Names}}'],text=True).splitlines()) for name in containers: if name in existing: state=json.loads(subprocess.check_output(['docker','inspect','--format','{{json .State}}',name],text=True)) assert not state['Running'] subprocess.run(['docker','rm',name],check=True,stdout=subprocess.DEVNULL) # Preserve the previous backup history within the new rollback directory. old_backups=Path('/mnt/data/ikar-backups') if old_backups.exists(): assert old_backups.resolve()==old_backups and not (rollback/'legacy-backups').exists() shutil.move(str(old_backups),str(rollback/'legacy-backups')) old_cron=subprocess.check_output(['crontab','-u','sevenhill','-l'],text=True) (rollback/'old-sevenhill.crontab').write_text(old_cron) os.chmod(rollback/'old-sevenhill.crontab',0o600) lines=old_cron.splitlines() new_lines=[line for line in lines if '/home/sevenhill/ikar-deploy/scripts/backup-ikar-data.sh' not in line] if new_lines!=lines: subprocess.run(['crontab','-u','sevenhill','-'],input='\n'.join(new_lines)+'\n',text=True,check=True) targets=[Path('/mnt/data/ikar-deploy'),Path('/mnt/data/ikar-data'),Path('/mnt/data/ikar-builds')] for target in targets: assert target.parent==Path('/mnt/data') and target.name in {'ikar-deploy','ikar-data','ikar-builds'} and target.resolve()==target if target.exists(): if target.name=='ikar-builds': with tarfile.open(rollback/'old-builds.tar.gz','w:gz') as t: t.add(target,arcname='ikar-builds') os.chmod(rollback/'old-builds.tar.gz',0o600) shutil.rmtree(target) link=Path('/home/sevenhill/ikar-deploy') if link.is_symlink(): assert os.readlink(link) in ('/mnt/data/ikar-deploy','../../mnt/data/ikar-deploy') link.unlink() report={'oldContainersRemoved':containers,'oldDirectoriesRemoved':[str(x) for x in targets], 'rollback':str(archive),'backupIntegrity':'ok','previousBackups':str(rollback/'legacy-backups'),'timestamp':datetime.datetime.now(datetime.timezone.utc).isoformat()} (rollback/'cleanup.json').write_text(json.dumps(report,indent=2)) print(json.dumps(report))