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>
This commit is contained in:
2026-10-19 22:18:09 +08:00
parent 3a1545a3dd
commit decae6931c
6 changed files with 112 additions and 7 deletions

View File

@@ -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@

3
.gitignore vendored
View File

@@ -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

View File

@@ -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` 到公开远程把密码扩散到非预期平台(本文件已含密码,推送前确认远程可见范围)

View File

@@ -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

View File

@@ -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",

104
scripts/deploy_remote.py Normal file
View File

@@ -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()