Files
IKAR/tool/deploy_video_fix.py
T

75 lines
3.2 KiB
Python

"""Deploy the video source/preview fix to the IKAR API with backup and rollback."""
from pathlib import Path
import json
import os
import shutil
import sqlite3
import subprocess
import tarfile
import time
import urllib.request
root = Path('/mnt/data/ikar_server')
release = root / 'releases/20261007-video'
current = root / 'current'
previous = current.resolve(strict=True)
if not os.path.ismount('/mnt/data') or previous != root / 'releases/20261006-1.0.1' or release.exists():
raise RuntimeError('Unexpected release or storage state')
def snapshot():
containers = subprocess.check_output(['docker', 'ps', '-q'], text=True).split()
return {item['Name']: {'id': item['Id'], 'startedAt': item['State']['StartedAt']}
for item in json.loads(subprocess.check_output(['docker', 'inspect', *containers], text=True))
if item['Name'] != '/ikar_server'}
protected_before = snapshot()
(root / 'deployment-video-before.json').write_text(json.dumps(protected_before, indent=2))
backup = subprocess.check_output(['python3', str(previous / 'backup.py')], text=True).strip()
shutil.copytree(previous, release, ignore=shutil.ignore_patterns('publish', 'release.tar.gz'))
with tarfile.open(root / 'video-fix.tar.gz') as archive:
archive.extractall(release, filter='data')
compose = release / 'compose.yaml'
text = compose.read_text()
old_image = 'image: ikar_server:20261006-1.0.1'
if old_image not in text:
raise RuntimeError('Unexpected API image in compose')
compose.write_text(text.replace(old_image, 'image: ikar_server:20261007-video', 1))
with (root / 'build-video.log').open('w') as log:
subprocess.run(['docker', 'build', '-f', 'Dockerfile.release', '-t', 'ikar_server:20261007-video', '.'],
cwd=release, stdout=log, stderr=subprocess.STDOUT, check=True)
def update(path):
subprocess.run(['docker', 'compose', '--project-directory', str(path), '--env-file', str(root / 'shared/turn.env'),
'up', '-d', '--no-deps', 'ikar_server'], check=True)
try:
update(release)
health = None
for attempt in range(30):
try:
with urllib.request.urlopen('http://127.0.0.1:5099/health', timeout=3) as response:
health = json.load(response)
if health.get('status') == 'ok':
break
except Exception:
pass
time.sleep(1)
if health is None or health.get('status') != 'ok':
raise RuntimeError('New API failed health verification')
subprocess.run(['docker', 'exec', 'ikar_server', 'ffmpeg', '-version'], stdout=subprocess.DEVNULL, check=True)
pending = root / 'current.next-video'
pending.symlink_to(release, target_is_directory=True)
os.replace(pending, current)
except Exception:
update(previous)
raise
with sqlite3.connect(f'file:{root}/shared/data/ikar.db?mode=ro', uri=True) as db:
integrity = db.execute('PRAGMA integrity_check').fetchone()[0]
report = {
'release': str(release), 'previousRelease': str(previous), 'backup': backup, 'health': health,
'databaseIntegrity': integrity, 'ffmpegAvailable': True,
'protectedServicesUnchanged': snapshot() == protected_before,
}
(root / 'deployment-video.json').write_text(json.dumps(report, indent=2))
print(json.dumps(report))