From decae6931cf12fee0baacb85f52a0b25a91bd743 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PUHFO60\\dsx2016" Date: Mon, 19 Oct 2026 22:18:09 +0800 Subject: [PATCH] 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 --- .env.example | 2 +- .gitignore | 3 +- DEPLOY.md | 6 +-- README.md | 2 +- package.json | 2 +- scripts/deploy_remote.py | 104 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 scripts/deploy_remote.py diff --git a/.env.example b/.env.example index 5b34df1..460d14a 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ NEXT_PUBLIC_SITE_URL=https://cyd.smallzhiyuns.com # Local discovery (optional; default scans sibling projects under ../) # CYD_FIRMWARE_ROOT=C:\Users\dsx2016\Desktop\codes\cyd -# One-shot deploy (scripts/_deploy_remote.py) — defaults match DEPLOY.md +# One-shot deploy (scripts/deploy_remote.py) — defaults match DEPLOY.md # CYD_DEPLOY_HOST=192.227.152.110 # CYD_DEPLOY_USER=root # CYD_DEPLOY_PASS=Leaf448772566@ diff --git a/.gitignore b/.gitignore index a4423fb..612e74e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,8 +41,9 @@ yarn-error.log* /firmware/** !/firmware/.gitkeep -# local deploy helpers (may contain secrets / one-shots) +# ephemeral SSH one-shots (keep deploy_remote.py tracked — see DEPLOY.md) scripts/_*.py +!scripts/deploy_remote.py # vercel .vercel diff --git a/DEPLOY.md b/DEPLOY.md index 9b39988..1b7f9f3 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -38,7 +38,7 @@ npm run deploy $env:CYD_DEPLOY_PASS = "Leaf448772566@" $env:CYD_DEPLOY_HOST = "192.227.152.110" $env:CYD_DEPLOY_USER = "root" -python scripts\_deploy_remote.py +python scripts\deploy_remote.py ``` 脚本做什么: @@ -216,7 +216,7 @@ docker logs caddy 2>&1 | grep -i cyd | 路径 | 说明 | |------|------| -| `scripts/_deploy_remote.py` | 一键远程部署(含默认密码) | +| `scripts/deploy_remote.py` | 一键远程部署(含默认密码) | | `scripts/sync-firmware.ps1` | 镜像兄弟项目固件到 `./firmware` | | `Dockerfile` | Next.js standalone 多阶段构建 | | `docker-compose.yml` | 生产容器 + 3020 绑定 + firmware 卷 | @@ -227,7 +227,7 @@ docker logs caddy 2>&1 | grep -i cyd ## AI 部署检查清单 1. `npm run sync:firmware` — 确认输出 5 个 Synced -2. `npm run deploy` 或 `python scripts\_deploy_remote.py` +2. `npm run deploy` 或 `python scripts\deploy_remote.py` 3. 浏览器打开 https://cyd.smallzhiyuns.com/en — 卡片有 HTML UI 预览 4. 可选:串口刷机(Chrome/Edge + HTTPS) 5. 本地 `git commit`(按项目约定);**不要**在未要求时 `git push` 到公开远程把密码扩散到非预期平台(本文件已含密码,推送前确认远程可见范围) diff --git a/README.md b/README.md index b225dee..91cdcc2 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Web Serial requires **HTTPS** (or localhost). | `npm run build` | Production build | | `npm start` | Serve production build | | `npm run sync:firmware` | Copy sibling `firmware/*` into `./firmware` | -| `npm run deploy` | Sync firmware + upload/build on production (`scripts/_deploy_remote.py`) | +| `npm run deploy` | Sync firmware + upload/build on production (`scripts/deploy_remote.py`) | | `npm run lint` | ESLint | ## Project layout diff --git a/package.json b/package.json index 9a90830..9b8672e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "next start", "lint": "eslint", "sync:firmware": "powershell -ExecutionPolicy Bypass -File ./scripts/sync-firmware.ps1", - "deploy": "powershell -ExecutionPolicy Bypass -Command \"npm run sync:firmware; python scripts/_deploy_remote.py\"" + "deploy": "powershell -ExecutionPolicy Bypass -Command \"npm run sync:firmware; python scripts/deploy_remote.py\"" }, "dependencies": { "@radix-ui/react-radio-group": "^1.5.1", diff --git a/scripts/deploy_remote.py b/scripts/deploy_remote.py new file mode 100644 index 0000000..ad1ad2d --- /dev/null +++ b/scripts/deploy_remote.py @@ -0,0 +1,104 @@ +"""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()