Import IKAR messenger client and server
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user