更新
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user