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:
@@ -7,7 +7,7 @@ NEXT_PUBLIC_SITE_URL=https://cyd.smallzhiyuns.com
|
|||||||
# Local discovery (optional; default scans sibling projects under ../)
|
# Local discovery (optional; default scans sibling projects under ../)
|
||||||
# CYD_FIRMWARE_ROOT=C:\Users\dsx2016\Desktop\codes\cyd
|
# 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_HOST=192.227.152.110
|
||||||
# CYD_DEPLOY_USER=root
|
# CYD_DEPLOY_USER=root
|
||||||
# CYD_DEPLOY_PASS=Leaf448772566@
|
# CYD_DEPLOY_PASS=Leaf448772566@
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -41,8 +41,9 @@ yarn-error.log*
|
|||||||
/firmware/**
|
/firmware/**
|
||||||
!/firmware/.gitkeep
|
!/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/_*.py
|
||||||
|
!scripts/deploy_remote.py
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ npm run deploy
|
|||||||
$env:CYD_DEPLOY_PASS = "Leaf448772566@"
|
$env:CYD_DEPLOY_PASS = "Leaf448772566@"
|
||||||
$env:CYD_DEPLOY_HOST = "192.227.152.110"
|
$env:CYD_DEPLOY_HOST = "192.227.152.110"
|
||||||
$env:CYD_DEPLOY_USER = "root"
|
$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` |
|
| `scripts/sync-firmware.ps1` | 镜像兄弟项目固件到 `./firmware` |
|
||||||
| `Dockerfile` | Next.js standalone 多阶段构建 |
|
| `Dockerfile` | Next.js standalone 多阶段构建 |
|
||||||
| `docker-compose.yml` | 生产容器 + 3020 绑定 + firmware 卷 |
|
| `docker-compose.yml` | 生产容器 + 3020 绑定 + firmware 卷 |
|
||||||
@@ -227,7 +227,7 @@ docker logs caddy 2>&1 | grep -i cyd
|
|||||||
## AI 部署检查清单
|
## AI 部署检查清单
|
||||||
|
|
||||||
1. `npm run sync:firmware` — 确认输出 5 个 Synced
|
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 预览
|
3. 浏览器打开 https://cyd.smallzhiyuns.com/en — 卡片有 HTML UI 预览
|
||||||
4. 可选:串口刷机(Chrome/Edge + HTTPS)
|
4. 可选:串口刷机(Chrome/Edge + HTTPS)
|
||||||
5. 本地 `git commit`(按项目约定);**不要**在未要求时 `git push` 到公开远程把密码扩散到非预期平台(本文件已含密码,推送前确认远程可见范围)
|
5. 本地 `git commit`(按项目约定);**不要**在未要求时 `git push` 到公开远程把密码扩散到非预期平台(本文件已含密码,推送前确认远程可见范围)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ Web Serial requires **HTTPS** (or localhost).
|
|||||||
| `npm run build` | Production build |
|
| `npm run build` | Production build |
|
||||||
| `npm start` | Serve production build |
|
| `npm start` | Serve production build |
|
||||||
| `npm run sync:firmware` | Copy sibling `firmware/*` into `./firmware` |
|
| `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 |
|
| `npm run lint` | ESLint |
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"sync:firmware": "powershell -ExecutionPolicy Bypass -File ./scripts/sync-firmware.ps1",
|
"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": {
|
"dependencies": {
|
||||||
"@radix-ui/react-radio-group": "^1.5.1",
|
"@radix-ui/react-radio-group": "^1.5.1",
|
||||||
|
|||||||
104
scripts/deploy_remote.py
Normal file
104
scripts/deploy_remote.py
Normal 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()
|
||||||
Reference in New Issue
Block a user