#!/usr/bin/env python3 """Publish an explicitly selected public video and player; reload nginx only.""" import argparse import hashlib from pathlib import Path import re import shutil import subprocess from datetime import datetime, timezone PUBLIC = Path('/var/www/juzi-media/videos') CONFIG = Path('/etc/nginx/sites-available/juzi') MARKER = '# BEGIN juzi-public-videos' BLOCK = ''' # BEGIN juzi-public-videos location ^~ /media/videos/ { alias /var/www/juzi-media/videos/; autoindex off; types { video/mp4 mp4; text/html html; } default_type application/octet-stream; charset utf-8; add_header X-Content-Type-Options nosniff; add_header Cache-Control "public, max-age=3600"; limit_except GET HEAD { deny all; } } # END juzi-public-videos ''' def checked(args): subprocess.run(args, check=True, capture_output=True, text=True) def digest(path): result = hashlib.sha256() with path.open('rb') as stream: for chunk in iter(lambda: stream.read(1024 * 1024), b''): result.update(chunk) return result.hexdigest() def main(): parser = argparse.ArgumentParser() parser.add_argument('staging') parser.add_argument('stem') parser.add_argument('sha256') args = parser.parse_args() assert re.fullmatch(r'[a-z0-9-]{1,80}', args.stem) assert re.fullmatch(r'[a-f0-9]{64}', args.sha256) staging = Path(args.staging) assert staging.resolve() == staging and staging.parent == Path('/tmp') assert staging.name.startswith('juzi-public-video-') and not staging.is_symlink() sources = [staging / (args.stem + suffix) for suffix in ('.mp4', '.html')] assert all(p.is_file() and not p.is_symlink() for p in sources) assert digest(sources[0]) == args.sha256, 'uploaded video checksum mismatch' assert not CONFIG.is_symlink() original = CONFIG.read_text() if MARKER not in original: assert 'location ^~ /media/videos/' not in original assert original.count(' location /state/ {') == 1 for directory in [PUBLIC.parent, PUBLIC]: assert not directory.is_symlink() directory.mkdir(mode=0o755, exist_ok=True) assert directory.resolve() == directory for source in sources: target = PUBLIC / source.name assert not target.is_symlink() if target.exists(): assert digest(target) == digest(source), 'refusing to overwrite a different published file' else: shutil.copyfile(source, target) target.chmod(0o644) if MARKER not in original: stamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ') backup = CONFIG.with_name('juzi.public-video-' + stamp + '.bak') assert not backup.exists() shutil.copy2(CONFIG, backup) CONFIG.write_text(original.replace(' location /state/ {', BLOCK + ' location /state/ {', 1)) try: checked(['nginx', '-t']) checked(['systemctl', 'reload', 'nginx']) except Exception: shutil.copy2(backup, CONFIG) checked(['nginx', '-t']) checked(['systemctl', 'reload', 'nginx']) raise print('nginx backup: ' + str(backup)) else: assert BLOCK in original, 'existing video location differs; inspect before changing' checked(['nginx', '-t']) print('video sha256: ' + digest(PUBLIC / sources[0].name)) print('player: https://1me.xajuzi.com/media/videos/' + args.stem + '.html') if __name__ == '__main__': main()