Files
cydweb/scripts/deploy_remote.py
DESKTOP-PUHFO60\dsx2016 decae6931c Track deploy_remote.py and point docs at the committed entrypoint.
Keep ephemeral scripts/_*.py ignored while making one-shot production deploy reproducible from git.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 22:18:09 +08:00

105 lines
3.4 KiB
Python

"""One-shot deploy to https://cyd.smallzhiyuns.com. See DEPLOY.md."""
from __future__ import annotations
import io
import os
import sys
import tarfile
from pathlib import Path
import paramiko
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
# Defaults match DEPLOY.md (overridable via env).
HOST = os.environ.get("CYD_DEPLOY_HOST", "192.227.152.110")
USER = os.environ.get("CYD_DEPLOY_USER", "root")
PASSWORD = os.environ.get("CYD_DEPLOY_PASS", "Leaf448772566@")
REMOTE_DIR = "/home/dsx2016/docker/cydweb"
CADDY_DIR = "/home/dsx2016/docker/caddy"
DOMAIN = "cyd.smallzhiyuns.com"
ROOT = Path(__file__).resolve().parents[1]
EXCLUDE_DIRS = {"node_modules", ".next", ".git", ".cursor", "__pycache__"}
def should_skip(rel: str) -> bool:
parts = Path(rel).parts
if any(p in EXCLUDE_DIRS for p in parts):
return True
if rel.replace("\\", "/").startswith("scripts/_"):
return True
return False
def make_tarball() -> bytes:
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
for path in ROOT.rglob("*"):
if not path.is_file():
continue
rel = path.relative_to(ROOT).as_posix()
if should_skip(rel):
continue
tar.add(path, arcname=rel)
return buf.getvalue()
def main() -> None:
if not PASSWORD:
raise SystemExit("Set CYD_DEPLOY_PASS (see DEPLOY.md)")
print(f"Deploy target: {USER}@{HOST} -> {REMOTE_DIR} ({DOMAIN})")
print("Creating archive…")
data = make_tarball()
print(f"Archive size: {len(data) / 1024 / 1024:.1f} MB")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASSWORD, timeout=30)
def run(cmd: str, timeout: int = 600) -> str:
print(f"$ {cmd}")
stdin, stdout, stderr = client.exec_command(cmd, timeout=timeout)
out = stdout.read().decode("utf-8", errors="replace")
err = stderr.read().decode("utf-8", errors="replace")
code = stdout.channel.recv_exit_status()
text = (out + err).encode("ascii", errors="replace").decode("ascii")
if text.strip():
print(text)
if code != 0:
raise RuntimeError(f"cmd failed ({code}): {cmd}")
return out
run(f"mkdir -p {REMOTE_DIR}")
sftp = client.open_sftp()
remote_tar = "/tmp/cydweb-deploy.tar.gz"
print(f"Uploading to {remote_tar}…")
with sftp.file(remote_tar, "wb") as rf:
rf.write(data)
sftp.close()
run(f"find {REMOTE_DIR} -mindepth 1 -maxdepth 1 -exec rm -rf {{}} +")
run(f"tar -xzf {remote_tar} -C {REMOTE_DIR} && rm -f {remote_tar}")
run(f"find {REMOTE_DIR}/firmware -name firmware.bin | wc -l")
run(f"find {REMOTE_DIR}/firmware -name preview.html | wc -l")
run(f"cd {REMOTE_DIR} && docker compose build", timeout=1800)
run(f"cd {REMOTE_DIR} && docker compose up -d")
run(
f"cd {CADDY_DIR} && docker compose exec -T caddy "
f"caddy reload --config /etc/caddy/Caddyfile || true"
)
run("sleep 4; curl -sI http://127.0.0.1:3020/en | head -15")
run(f"curl -sI https://{DOMAIN}/en | head -20")
run(
f"curl -s https://{DOMAIN}/en | grep -o 'umami.dsx2020.com/script.js' | head -3 || true"
)
client.close()
print(f"DONE — https://{DOMAIN}")
if __name__ == "__main__":
main()