guanghu-ice-heart/server-tools/guanghulab-front-door/install-lite-front-door.py

120 lines
4.2 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""Install the lightweight front door and switch only /code/ to the JD tunnel."""
from __future__ import annotations
import datetime
import pathlib
import re
import shutil
import subprocess
import sys
import urllib.error
import urllib.request
SITE_CONFIG = pathlib.Path("/etc/nginx/sites-enabled/guanghulab")
WEB_ROOT = pathlib.Path("/var/www/guanghulab-front-door")
SOURCE_FILES = ("index.html", "styles.css", "robots.txt", "llms.txt", "sitemap.xml")
OLD_CODE_BLOCK = re.compile(
r""" # Forgejo 代码仓库
location /code/ \{
auth_request /auth/verify-signature;
proxy_pass http://127\.0\.0\.1:3001/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
client_max_body_size 100m;
\}
""",
)
NEW_CODE_BLOCK = """ # 光湖代码频道 · 广州备案前门 → 京东个人子频道
location /code/ {
proxy_pass http://127.0.0.1:18088/code/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
client_max_body_size 256m;
}
"""
def run(*args: str) -> None:
subprocess.run(args, check=True)
def get_status(url: str) -> int:
request = urllib.request.Request(url, method="GET")
try:
with urllib.request.urlopen(request, timeout=15) as response:
return response.status
except urllib.error.HTTPError as error:
return error.code
def main() -> None:
if len(sys.argv) != 2:
raise SystemExit("usage: install-lite-front-door.py SOURCE_DIRECTORY")
source = pathlib.Path(sys.argv[1]).resolve()
if not SITE_CONFIG.is_file():
raise RuntimeError("active guanghulab nginx config unavailable")
for name in SOURCE_FILES:
if not (source / name).is_file():
raise RuntimeError(f"front-door source missing: {name}")
current = SITE_CONFIG.read_text(encoding="utf-8")
updated, replacements = OLD_CODE_BLOCK.subn(NEW_CODE_BLOCK, current)
if replacements == 0 and NEW_CODE_BLOCK.strip() not in current:
raise RuntimeError("expected legacy /code/ block not found")
if replacements > 1:
raise RuntimeError("multiple legacy /code/ blocks found")
stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
backup_root = pathlib.Path("/var/backups/guanghu/front-door") / stamp
backup_root.mkdir(parents=True, mode=0o700)
shutil.copy2(SITE_CONFIG, backup_root / "guanghulab.nginx")
if WEB_ROOT.exists():
shutil.copytree(WEB_ROOT, backup_root / "web-root")
temporary_config = SITE_CONFIG.with_name(f"guanghulab.{stamp}.new")
temporary_config.write_text(updated, encoding="utf-8")
temporary_config.chmod(0o644)
temporary_config.replace(SITE_CONFIG)
WEB_ROOT.mkdir(parents=True, exist_ok=True)
for name in SOURCE_FILES:
destination = WEB_ROOT / name
shutil.copy2(source / name, destination)
destination.chmod(0o644)
try:
run("/usr/sbin/nginx", "-t")
run("/usr/bin/systemctl", "reload", "nginx")
if get_status("https://guanghulab.com/") != 200:
raise RuntimeError("front-door homepage verification failed")
if get_status("https://guanghulab.com/code/") not in (200, 303):
raise RuntimeError("code-channel public route verification failed")
if get_status("https://guanghulab.com/fifth-domain/api/v1/version") != 200:
raise RuntimeError("legacy Fifth Domain verification failed")
except Exception:
shutil.copy2(backup_root / "guanghulab.nginx", SITE_CONFIG)
if (backup_root / "web-root").exists():
shutil.rmtree(WEB_ROOT)
shutil.copytree(backup_root / "web-root", WEB_ROOT)
run("/usr/sbin/nginx", "-t")
run("/usr/bin/systemctl", "reload", "nginx")
raise
print(f"LIGHT_FRONT_DOOR_DEPLOYED backup={backup_root}")
if __name__ == "__main__":
main()