name: publish description: "将 landing/ 官方销售网站构建产物发布到 ai-board.reai.com(部署机 cn.reai.com,nginx + Let's Encrypt 自动 SSL)。首次发布自动完成 ACME 签证与 nginx 配置,后续发布只同步静态文件。Trigger when user types /publish 或提到 发布、部署到服务器、发布网页、更新线上、deploy。"
Publish — ai-board.reai.com 官网发布
把 landing/ 官方销售网站(Vue 3 + Supabase)的 Vite 构建产物(landing/dist/)发布到 ai-board.reai.com,部署机 cn.reai.com,nginx 直出 + Let's Encrypt 自动签证 / 续期。
发布对象是 landing/,不是 web/。
web/是 K203 桌面应用(Tauri)的操作界面,依赖本地 IPC,浏览器跑不了——不是网站。landing/才是面向公网的官网。详见仓库内两目录定位说明。
部署环境
| 项目 | 值 |
|---|---|
| 服务器 | cn.reai.com (SSH: root@cn.reai.com) |
| 服务器公网 IP | 47.106.107.172(ai-board.reai.com 已正确指向它) |
| OS | Ubuntu 22.04, nginx 1.18.0, certbot 5.2.2 |
| 域名 | ai-board.reai.com |
| 静态根目录 | /var/www/ai-board.reai.com/(远端,nginx root) |
| nginx 配置 | /etc/nginx/sites-available/ai-board.reai.com.conf(symlink 到 sites-enabled) |
| ACME webroot | /var/www/certbot(全服务器共用) |
| 证书路径 | /etc/letsencrypt/live/ai-board.reai.com/ |
| SSL 续期 | certbot 系统 timer 自动续期,无需人工 |
| 本地构建 | cd landing && bun run build → 产物 landing/dist/ |
注意:远端静态目录不是 git 仓库,文件通过 rsync 同步。nginx 配置模板在本 skill 的 references/,scp 到远端安装。
构建前置:环境变量(landing 是动态站,硬依赖)
landing/ 前端直连 Supabase,构建时把以下 VITE_ 变量编译进产物。发布生产前必须确认 landing/.env.local 是生产配置(尤其 Supabase URL 不能是本地/dev 地址,否则线上预订/订单/admin 全部连不上):
| 变量 | 作用 |
|---|---|
VITE_SUPABASE_URL |
Supabase 项目地址(生产) |
VITE_SUPABASE_ANON_KEY |
Supabase 匿名公钥 |
VITE_DOWNLOAD_MACOS / VITE_DOWNLOAD_WINDOWS |
客户端下载链接 |
VITE_PAYMENT_URL |
支付链接 |
VITE_SALE_OPEN |
销售开关(控制是否开放下单) |
# 发布前自检:确认不是本地地址
grep VITE_SUPABASE_URL landing/.env.local
架构
浏览器 → DNS(ai-board.reai.com → 47.106.107.172 = cn.reai.com)
→ nginx :443 (Let's Encrypt SSL)
→ root /var/www/ai-board.reai.com (静态直出,SPA history fallback → index.html)
→ 前端 JS 直连 Supabase(数据收集 / 订单 / admin)
智能判断:首发 vs 常规发布
触发 /publish 时,先探测远端是否已 bootstrap(证书 + 正式 nginx 配置是否就位):
ssh root@cn.reai.com '
HAS_CERT=$(test -f /etc/letsencrypt/live/ai-board.reai.com/fullchain.pem && echo y || echo n)
HAS_CONF=$(test -L /etc/nginx/sites-enabled/ai-board.reai.com.conf && echo y || echo n)
echo "cert=$HAS_CERT conf=$HAS_CONF"
'
cert=y conf=y→ 已 bootstrap,走 常规发布(只同步静态文件)- 其他 → 未 bootstrap,走 首次发布(含 DNS 检查 + ACME 签证 + nginx 安装)
前置检查(每次都跑,不可跳过)
# 1. 本地代码状态(提醒,不强制)
git status --short
# 2. 服务器可达
ssh -o ConnectTimeout=10 root@cn.reai.com "echo OK"
# 3. DNS 校验 —— ai-board.reai.com 必须解析到 47.106.107.172
# 本机可能是代理 fake-ip 模式(dig 出 198.18.x.x 不可信),
# 一律以「服务器视角 + 公共 DNS」为准。
DNS_IP=$(ssh root@cn.reai.com "dig +short ai-board.reai.com A | head -1")
echo "ai-board.reai.com 解析 = $DNS_IP(期望 47.106.107.172)"
if [ "$DNS_IP" != "47.106.107.172" ]; then
echo "❌ DNS 未指向部署机,certbot webroot 签证会失败。请先修正 A 记录。"
exit 1
fi
# 4. 构建环境变量自检(确认 Supabase 是生产地址)
grep VITE_SUPABASE_URL landing/.env.local
本地构建
cd landing && bun run build && cd ..
ls landing/dist/index.html # 确认产物存在
首次发布(Bootstrap,一次性)
前提:前置检查全部通过(DNS 指向 47.106.107.172、.env.local 为生产配置)。
# Step 1: 远端建静态根目录 + webroot,先同步一份文件
ssh root@cn.reai.com "mkdir -p /var/www/ai-board.reai.com /var/www/certbot"
rsync -avz --delete landing/dist/ root@cn.reai.com:/var/www/ai-board.reai.com/
# Step 2: 安装 http-only 配置(仅 80 + ACME challenge),enable 并 reload
scp .claude/skills/publish/references/nginx-ai-board.http-only.conf \
root@cn.reai.com:/etc/nginx/sites-available/ai-board.reai.com.conf
ssh root@cn.reai.com "
ln -sf /etc/nginx/sites-available/ai-board.reai.com.conf \
/etc/nginx/sites-enabled/ai-board.reai.com.conf
nginx -t && nginx -s reload
"
# Step 3: certbot webroot 签证(自动注册到续期 timer)
ssh root@cn.reai.com "
certbot certonly --webroot -w /var/www/certbot \
-d ai-board.reai.com \
--non-interactive --agree-tos -m admin@reai.com \
--key-type ecdsa
"
# Step 4: 换上正式配置(80→443 + 443 静态直出),reload
scp .claude/skills/publish/references/nginx-ai-board.conf \
root@cn.reai.com:/etc/nginx/sites-available/ai-board.reai.com.conf
ssh root@cn.reai.com "nginx -t && nginx -s reload"
-m admin@reai.com:Let's Encrypt 到期提醒邮箱,按需替换。 签证成功后自动注册续期 timer,SSL 续期全自动。
常规发布(已 bootstrap)
最常见路径——只同步静态文件,不碰 nginx / 证书:
cd landing && bun run build && cd ..
rsync -avz --delete landing/dist/ root@cn.reai.com:/var/www/ai-board.reai.com/
--delete 保证远端与本地 dist/ 完全一致(清掉旧 hash 资源)。静态站无需 reload nginx。
验证
# (a) HTTPS 可达
curl -sf -o /dev/null -w "HTTPS %{http_code}\n" https://ai-board.reai.com/
# (b) 证书域名 / 有效期
ssh root@cn.reai.com "certbot certificates -d ai-board.reai.com 2>/dev/null | grep -E 'Domains|Expiry'"
# (c) HTTP 自动跳转 HTTPS(应为 301)
curl -s -o /dev/null -w "HTTP→ %{http_code} → %{redirect_url}\n" http://ai-board.reai.com/
# (d) SPA 深链回退(多语言路由应回 index.html 而非 404)
curl -sf -o /dev/null -w "深链 /zh/pricing → %{http_code}\n" https://ai-board.reai.com/zh/pricing
回滚
# 静态内容回滚:发布前可先备份当前版本
# ssh root@cn.reai.com "cp -a /var/www/ai-board.reai.com /var/www/ai-board.reai.com.bak"
# 回退:
ssh root@cn.reai.com "rm -rf /var/www/ai-board.reai.com && mv /var/www/ai-board.reai.com.bak /var/www/ai-board.reai.com"
# nginx 配置回滚:删 enable symlink + reload(站点下线,不影响其他站)
ssh root@cn.reai.com "rm -f /etc/nginx/sites-enabled/ai-board.reai.com.conf && nginx -t && nginx -s reload"
安全注意
- 所有 rsync/scp/certbot 走 SSH,不暴露文件到公网。
- 静态文件同步用
--delete保证与本地一致,避免残留旧资源。 - nginx reload 前必
nginx -t验证配置。 - 这台机器同时托管大量其他 *.cn.reai.com 站点:只操作 ai-board.reai.com 自己的 conf 和 /var/www/ai-board.reai.com 目录,绝不动其他站点的配置 / 目录 / 证书。
- 构建前确认
.env.local为生产 Supabase 配置,避免线上连错数据库。