#!/usr/bin/env python3 """Install one HKDJ NET component; never update other Supervisor groups.""" import configparser import hashlib import io import os from pathlib import Path import re import shutil import subprocess import sys import time component, revision = sys.argv[1:] assert component in ('app', 'web') and re.fullmatch('[0-9a-f]{40}', revision) root = Path('/server/hkdj_net') incoming = root / '.incoming' / component binary = incoming / ('hkdj-net-' + component) metadata = subprocess.check_output(['strings', '-a', str(binary)], text=True) assert 'vcs.revision=' + revision in metadata and 'vcs.modified=false' in metadata assert 'hkdj-web-server/' + component in metadata destination = root / component (destination / 'conf').mkdir(parents=True, exist_ok=True) (root / 'logs').mkdir(exist_ok=True) previous = destination / binary.name if previous.exists(): old_metadata = subprocess.check_output(['strings', '-a', str(previous)], text=True) old_revision = re.search(r'vcs.revision=([0-9a-f]{40})', old_metadata) assert old_revision and 'vcs.modified=false' in old_metadata assert 'hkdj-web-server/' + component in old_metadata old_checksum = hashlib.sha256(previous.read_bytes()).hexdigest() rollback = destination / '.rollback' rollback.mkdir(exist_ok=True) shutil.copy2(previous, rollback / (binary.name + '.' + old_revision[1] + '.' + old_checksum)) os.chmod(binary, 0o755) os.replace(binary, destination / binary.name) os.replace(incoming / (component + '.yaml'), destination / 'conf' / (component + '.yaml')) if component == 'app': for name in ('ip2region_v4.xdb', 'ip2region_v6.xdb'): os.replace(incoming / name, destination / 'conf' / name) config = configparser.RawConfigParser() config.read(incoming / 'supervisor.conf') selected = configparser.RawConfigParser() name = 'hkdj_net_' + component selected['program:' + name] = dict(config['program:' + name]) out = io.StringIO() selected.write(out) target = Path('/etc/supervisor/conf.d') / (name + '.conf') changed = not target.exists() or target.read_text() != out.getvalue() temporary = target.with_suffix('.pending') temporary.write_text(out.getvalue()) os.replace(temporary, target) subprocess.run(['supervisorctl', 'reread'], check=True) subprocess.run(['supervisorctl', 'update', name], check=True) if not changed: subprocess.run(['supervisorctl', 'restart', name], check=True) for _ in range(40): status = subprocess.check_output(['supervisorctl', 'status', name], text=True) if 'RUNNING' in status: break time.sleep(1) else: raise RuntimeError('selected component did not start') print('installed', component, revision, hashlib.sha256((destination / binary.name).read_bytes()).hexdigest())