70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Serve public voice assets with MP3 MIME/range support; keep app services running.
|
|
|
|
Run on the existing Ubuntu host using sudo. Only manifest-listed public files
|
|
are copied. Nginx config is backed up and rolled back if validation/reload fails.
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
|
|
SOURCE = Path('/home/ubuntu/juzi/h5/assets/voice')
|
|
PUBLIC = Path('/var/www/juzi-voice-20261102')
|
|
CONFIG = Path('/etc/nginx/sites-available/juzi')
|
|
BACKUP = CONFIG.with_name('juzi.voice-20261102.bak')
|
|
MARKER = '# BEGIN juzi-voice-media 20261102'
|
|
BLOCK = ''' # BEGIN juzi-voice-media 20261102
|
|
location ^~ /state/assets/voice/ {
|
|
alias /var/www/juzi-voice-20261102/;
|
|
types { audio/mpeg mp3; application/json json; }
|
|
default_type application/octet-stream;
|
|
add_header Cache-Control "public, max-age=3600";
|
|
limit_except GET HEAD { deny all; }
|
|
}
|
|
# END juzi-voice-media 20261102
|
|
|
|
'''
|
|
|
|
def checked(command):
|
|
result = subprocess.run(command, capture_output=True, text=True)
|
|
if result.returncode:
|
|
raise RuntimeError('command failed: ' + ' '.join(command) + '\n' + result.stderr)
|
|
|
|
def main():
|
|
manifest = json.loads((SOURCE / 'manifest.json').read_text())
|
|
names = manifest['files']
|
|
assert names and all(isinstance(name, str) and re.fullmatch(r'[a-z0-9-]+', name) for name in names)
|
|
files = [SOURCE / (name + '.mp3') for name in names] + [SOURCE / 'manifest.json']
|
|
assert all(file.is_file() and not file.is_symlink() and file.resolve().parent == SOURCE.resolve() for file in files)
|
|
original = CONFIG.read_text()
|
|
if MARKER not in original:
|
|
assert not BACKUP.exists(), 'existing backup must be preserved'
|
|
assert original.count(' location /state/ {') == 1
|
|
PUBLIC.mkdir(mode=0o755, parents=True, exist_ok=True)
|
|
assert PUBLIC.resolve() == Path('/var/www/juzi-voice-20261102')
|
|
for file in files:
|
|
target = PUBLIC / file.name
|
|
assert not target.is_symlink()
|
|
shutil.copyfile(file, target)
|
|
target.chmod(0o644)
|
|
if MARKER not in original:
|
|
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
|
|
else:
|
|
checked(['nginx', '-t'])
|
|
print(json.dumps({'publicFiles': len(files), 'nginxConfigValid': True,
|
|
'backup': str(BACKUP), 'businessServicesRestarted': False}))
|
|
|
|
if __name__ == '__main__':
|
|
main()
|