---
description: 推送本地变更到 njcq 服务器并重新部署 Docker Compose
argument-hint: "[可选 --no-build 仅 rsync 不重启 | --seed 部署后执行 db:seed（危险：会清空用户数据）]"
---

# 一键部署 citywalk-stamp 到 njcq 服务器

## 部署目标
- 主机：SSH 别名 `njcq`
- 目录：`/opt/1panel/apps/citywalk-stamp`
- 容器：`citywalk-stamp`（端口 `127.0.0.1:3001` ← 宿主 3000 被 1Panel Gitea 占用）
- 持久化：`./data/`（SQLite）、`./uploads/`（图章图片）、`.env`（密钥）均在服务器保留，本次部署不会覆盖

## 参数

用户可能通过 `$ARGUMENTS` 传入：
- `--no-build`：跳过 compose up，仅同步代码
- `--seed`：部署后执行 `pnpm db:seed`（⚠️ 会清空 Stamp 表并级联删除用户 Collection 数据，仅首次部署或彻底重置时使用）
- 无参数：默认推送代码 + 重新 build 并重启容器

## 步骤

### 1. 预检

确认当前在项目根：
```bash
test -f Dockerfile && test -f docker-compose.yml && test -f pnpm-workspace.yaml
```
如缺文件立即中止。

### 2. rsync 推送代码（sudo 身份写 /opt）

```bash
rsync -avz --delete --rsync-path="sudo rsync" \
  --exclude='.git/' \
  --exclude='node_modules/' \
  --exclude='**/node_modules/' \
  --exclude='**/dist/' \
  --exclude='.env' \
  --exclude='.env.backup' \
  --exclude='.env.local' \
  --exclude='.claude/' \
  --exclude='prisma/dev.db*' \
  --exclude='/data/' \
  --exclude='/uploads/' \
  --exclude='.DS_Store' \
  --exclude='build.log' \
  ./ njcq:/opt/1panel/apps/citywalk-stamp/
```

**关键陷阱（必须保持以下写法）**：
- `--rsync-path="sudo rsync"` 解决 ubuntu 用户对 `/opt` 无写权限（否则 rsync 会静默 status 23，目录仍为空）
- `/data/` 和 `/uploads/` **带前导斜杠**——只排除项目根下这两个目录（它们是 docker bind mount 产生的运行时数据），**不要**写成 `data/` 或 `uploads/`，否则会把 `packages/server/uploads/stamps/` 也一并排除
- `.env` 被排除，远端 JWT_SECRET / ADMIN_API_KEY / SITE_URL 不受影响
- `--delete` 会删除远端多余文件，但 exclude 的目录保留

### 3. 如果是 `--no-build`，到这里就结束

只同步代码而不重启服务，只在改了纯文档或静态资源、容器内存里的代码用不上时才用。

### 4. 远端后台启动 compose（避免 SSH 断开导致构建中断）

```bash
ssh njcq 'sudo bash -c "cd /opt/1panel/apps/citywalk-stamp && rm -f build.log && nohup docker compose up -d --build > build.log 2>&1 &" && sleep 2 && echo started'
```

`nohup ... &` 让构建脱离 SSH 进程。ssh 立即返回，构建在远端持续进行。

### 5. 轮询等待构建完成（可能 3-5 分钟）

**不要**靠 `pgrep` 判断构建是否结束——服务器用的是 docker v2（`docker compose`），没有名为 `docker-compose compose up` 的进程，pgrep 永远返回 false，while 循环会立即退出，接着的 `docker compose ps` 会在 daemon 忙时阻塞，表现为"卡住"。直接读 `build.log` 的末尾特征字符串才准：

```bash
ssh njcq '
cd /opt/1panel/apps/citywalk-stamp
for i in $(seq 1 40); do  # 最多等 10 分钟
  tail_out=$(sudo tail -n 30 build.log 2>/dev/null)
  # 成功标记：docker compose v2 finish line
  if echo "$tail_out" | grep -qE "Container .* (Started|Running|Healthy)"; then
    echo "=== build finished ==="
    break
  fi
  # 失败标记
  if echo "$tail_out" | grep -qE "(failed to solve|Error response from daemon|ERROR: )"; then
    echo "=== build FAILED ==="
    echo "$tail_out" | tail -15
    exit 1
  fi
  sleep 15
  echo "... building $(date +%H:%M:%S)"
  echo "$tail_out" | tail -2 | sed "s/^/    /"
done
echo "---"
sudo docker compose ps
echo "=== container logs (last 20 lines) ==="
sudo docker compose logs --tail=20 app
'
```

用 **Bash `run_in_background: true`** 启动这条命令，然后用 TaskOutput 等待完成——避免 SSH 命令卡住主 Claude 流程。

### 6. 健康检查

```bash
ssh njcq '
echo "--- /api/health ---"
curl -sS http://127.0.0.1:3001/api/health && echo
echo "--- /api/stamps count ---"
curl -sS http://127.0.0.1:3001/api/stamps | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d[\"data\"]))"
echo "--- /uploads sample ---"
curl -sI http://127.0.0.1:3001/uploads/stamps/stamp-01-color.jpg | head -1
echo "--- /album (SPA) ---"
curl -s -o /dev/null -w "http %{http_code}\n" http://127.0.0.1:3001/album
'
```

期望输出：
- health: `{"success":true,...}`
- stamps count: `16`（如果是首次部署或数据库被清空，可能是 `0`，此时需要 `--seed`）
- stamps 首图: `HTTP/1.1 200 OK`
- SPA: `http 200`

### 7. 如传入 `--seed`，执行首次 seed

**仅在首次部署或主动重置时使用**。会触发 `seed.ts` 里的 `prisma.stamp.deleteMany()` 级联删除所有 Collection：

```bash
ssh njcq 'cd /opt/1panel/apps/citywalk-stamp && sudo docker compose exec -T app pnpm db:seed' | tail -10
```

## 完成后给用户的反馈

一段简短摘要，包含：
- 镜像是否重建、容器是否在运行
- `/api/stamps` 返回的图章数量
- 访问入口：`https://njcitywalk.njcqit.com`（外部 Nginx 反代到 127.0.0.1:3001）
- 如果有异常（stamps=0、health 失败、容器重启循环），提示如何查看日志：
  `ssh njcq 'cd /opt/1panel/apps/citywalk-stamp && sudo docker compose logs -f app'`

## 不做的事（防止破坏生产数据）

- ❌ 不删除远端 `data/` 或 `uploads/`（已排除）
- ❌ 不覆盖 `.env`（已排除）
- ❌ 不自动 `pnpm db:seed`（必须显式 `--seed`）
- ❌ 不修改 Nginx 配置或证书
- ❌ 不在远端执行 `git pull`（没 git 仓，只走 rsync）
