97 lines
3.5 KiB
Python
97 lines
3.5 KiB
Python
#!/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()
|