132 lines
5.2 KiB
Python
132 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Publish a versioned uni-app H5 build through the existing BaoTa gateway."""
|
|
import argparse
|
|
import hashlib
|
|
import os
|
|
import pwd
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('archive')
|
|
parser.add_argument('sha256')
|
|
args = parser.parse_args()
|
|
archive = Path(args.archive).resolve()
|
|
assert archive.is_file() and archive.parent == Path('/tmp')
|
|
assert hashlib.sha256(archive.read_bytes()).hexdigest() == args.sha256.lower()
|
|
assert os.geteuid() == 0
|
|
root = Path('/www/wwwroot/im.bchongw.com')
|
|
go_config = Path('/www/server/panel/vhost/nginx/go_xingyu_im.conf')
|
|
main_config = go_config if go_config.is_file() else Path('/www/server/panel/vhost/nginx/html_im.bchongw.com.conf')
|
|
config = Path('/www/server/panel/vhost/nginx/extension/xingyu_im/xingyu_routes.conf') if go_config.is_file() else main_config
|
|
old_config = config.read_text()
|
|
assert 'server_name im.bchongw.com;' in main_config.read_text()
|
|
release = Path('/www/wwwroot/xingyu-h5/releases') / time.strftime('%Y%m%d-%H%M%S')
|
|
release.mkdir(mode=0o755, parents=True, exist_ok=False)
|
|
with zipfile.ZipFile(str(archive)) as package:
|
|
for info in package.infolist():
|
|
candidate = (release / info.filename.replace('\\', '/')).resolve()
|
|
assert os.path.commonpath([str(candidate), str(release)]) == str(release)
|
|
# Windows PowerShell archives may store backslash separators. Normalize
|
|
# them explicitly instead of creating literal backslashes on Linux.
|
|
if info.is_dir() or info.filename.endswith(('\\', '/')):
|
|
candidate.mkdir(parents=True, exist_ok=True)
|
|
else:
|
|
candidate.parent.mkdir(parents=True, exist_ok=True)
|
|
with package.open(info) as source, candidate.open('wb') as target:
|
|
shutil.copyfileobj(source, target)
|
|
www = pwd.getpwnam('www')
|
|
for parent, directories, files in os.walk(str(release)):
|
|
os.chown(parent, www.pw_uid, www.pw_gid)
|
|
os.chmod(parent, 0o755)
|
|
for filename in files:
|
|
path = os.path.join(parent, filename)
|
|
os.chown(path, www.pw_uid, www.pw_gid)
|
|
os.chmod(path, 0o644)
|
|
assert '/app/assets/' in (release / 'index.html').read_text()
|
|
assert (release / 'static/favicon.svg').is_file()
|
|
entry = root / 'app'
|
|
assert not entry.exists() or entry.is_symlink()
|
|
previous = os.readlink(str(entry)) if entry.is_symlink() else None
|
|
backup = Path('/www/backup/xingyu-h5-publish-' + time.strftime('%Y%m%d-%H%M%S'))
|
|
backup.mkdir(mode=0o700)
|
|
shutil.copy2(str(config), str(backup / config.name))
|
|
block = '''
|
|
# XINGYU_H5_START
|
|
location = /app { return 301 /app/; }
|
|
location = /app/ {
|
|
root /www/wwwroot/im.bchongw.com;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
try_files /app/index.html =404;
|
|
}
|
|
location = /app/index.html {
|
|
root /www/wwwroot/im.bchongw.com;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
try_files $uri =404;
|
|
}
|
|
location ^~ /app/assets/ {
|
|
root /www/wwwroot/im.bchongw.com;
|
|
expires 7d;
|
|
add_header Cache-Control "public, immutable";
|
|
try_files $uri =404;
|
|
}
|
|
location ^~ /app/static/ {
|
|
root /www/wwwroot/im.bchongw.com;
|
|
expires 1h;
|
|
try_files $uri =404;
|
|
}
|
|
location ^~ /app/ {
|
|
root /www/wwwroot/im.bchongw.com;
|
|
try_files $uri $uri/ /app/index.html;
|
|
}
|
|
# XINGYU_H5_END
|
|
|
|
'''
|
|
|
|
|
|
def switch(target):
|
|
next_link = root / 'app.next'
|
|
assert not next_link.exists() and not next_link.is_symlink()
|
|
os.symlink(target, str(next_link))
|
|
os.replace(str(next_link), str(entry))
|
|
|
|
|
|
try:
|
|
switch(str(release))
|
|
if '# XINGYU_H5_START' not in old_config:
|
|
marker = ' location = /admin {'
|
|
assert old_config.count(marker) == 1
|
|
config.write_text(old_config.replace(marker, block + marker))
|
|
subprocess.run(['/www/server/nginx/sbin/nginx', '-t'], check=True)
|
|
subprocess.run(['/etc/init.d/nginx', 'reload'], check=True)
|
|
assert entry.resolve() == release.resolve()
|
|
assert (release / 'index.html').is_file() and (release / 'static/favicon.svg').is_file()
|
|
assert (root / 'admin').resolve().joinpath('index.html').is_file()
|
|
with urllib.request.urlopen('http://127.0.0.1:18888/healthz', timeout=10) as response:
|
|
assert response.status == 200
|
|
os.chdir('/www/server/panel')
|
|
sys.path.insert(0, '/www/server/panel')
|
|
sys.path.insert(0, '/www/server/panel/class')
|
|
import public
|
|
project_name = 'xingyu_im' if go_config.is_file() else 'im.bchongw.com'
|
|
public.M('sites').where('name=?', (project_name,)).setField(
|
|
'ps', '星遇 IM · im.bchongw.com · 管理端 /admin/ · H5 /app/')
|
|
print('H5_PUBLISHED=' + str(release))
|
|
print('NGINX_BACKUP=' + str(backup))
|
|
except Exception:
|
|
config.write_text(old_config)
|
|
if previous:
|
|
switch(previous)
|
|
else:
|
|
# Remove only the symlink just created; retain all release files for diagnosis.
|
|
assert entry.is_symlink() and os.readlink(str(entry)) == str(release)
|
|
entry.unlink()
|
|
subprocess.run(['/www/server/nginx/sbin/nginx', '-t'], check=True)
|
|
subprocess.run(['/etc/init.d/nginx', 'reload'], check=True)
|
|
raise
|