124 lines
6.5 KiB
Markdown
124 lines
6.5 KiB
Markdown
# 部署(生产 · openclaw-01)
|
||
|
||
> 适用于当前唯一的线上环境:`openclaw-01`(192.168.8.188),访问入口 `http://192.168.8.188:8200`。
|
||
> 本地开发见 [`DEVELOPMENT.md`](DEVELOPMENT.md)。部署铁律见仓库根 [`AGENTS.md`](../AGENTS.md) 第 2 节。
|
||
|
||
## 1. 生产拓扑
|
||
|
||
```
|
||
浏览器 ──HTTP──▶ openclaw-01:8200
|
||
└ gunicorn(2 × UvicornWorker) ── app.main:app
|
||
├ /api/v1/** 业务 API
|
||
└ / 前端产物 web/dist(单页应用,404 回退 index.html)
|
||
│
|
||
┌────────────────────────┼────────────────────────┐
|
||
▼ ▼ ▼
|
||
fnsvr-01:8432 PG 17.6 fnsvr-01:6379 Redis 本机 backend/var/uploads
|
||
库 dadaotask_prod db 6(独立库号) 附件文件
|
||
角色 dadao_prod_app(RLS)
|
||
角色 dadao_prod_owner(迁移)
|
||
```
|
||
|
||
为什么是这个形状:
|
||
|
||
- **生产库和开发库在同一个 PG 实例**(fnsvr-01),所以生产用**独立的库 + 独立的角色**。复用 `dadao_app` 等于拿到开发口令就能连生产。迁移里的 `GRANT`/`DEFAULT PRIVILEGES` 按 `APP_DB_ROLE` / `OWNER_DB_ROLE` 生成,靠 `backend/app/roles.py` 注入。
|
||
- **Redis 用 6 号库**隔离:共享实例没有密码,靠库号避免和别的项目撞键(验证码、幂等键)。
|
||
- **没有 nginx**:openclaw 账号无 sudo 密码,改不了系统 nginx,所以后端单端口同时托管 API 与前端产物(同源,免 CORS)。root 愿意配合时按 `deploy/prod/nginx.dadaotask.conf.example` 加反代,不用改应用。
|
||
- **用户级 systemd**:openclaw-01 上所有服务(easymemo、portal-site…)都是 `systemctl --user` 起的,本方案沿用,全程不需要 root。
|
||
|
||
## 2. 首次部署(一次性)
|
||
|
||
前置:能 SSH 到 openclaw-01;fnsvr-01 的 root 权限(只在建库这一步需要)。
|
||
|
||
### 2.1 建生产库与角色(在 fnsvr-01 上,root)
|
||
|
||
```bash
|
||
# 口令自己生成,别用示例里的
|
||
OWNER_PW='<dadao_prod_owner 的口令>' APP_PW='<dadao_prod_app 的口令>' \
|
||
bash deploy/prod/provision-db.sh
|
||
```
|
||
|
||
会创建:库 `dadaotask_prod`(owner `dadao_prod_owner`)、角色 `dadao_prod_owner` / `dadao_prod_app`(`NOSUPERUSER NOBYPASSRLS`,否则 RLS 会被跳过)、`public` schema 归 owner、`REVOKE CREATE ... FROM PUBLIC`。建表与 RLS 策略由 alembic 负责(2.4)。
|
||
|
||
### 2.2 代码落地(在 openclaw-01 上,openclaw 身份)
|
||
|
||
```bash
|
||
git clone --branch main --single-branch http://gitea.tatta.cn/Tatta/DadaoTask.git ~/app/dadaotask
|
||
```
|
||
|
||
### 2.3 配置(不进仓库)
|
||
|
||
```bash
|
||
mkdir -p ~/.config/dadaotask && chmod 700 ~/.config/dadaotask
|
||
cp ~/app/dadaotask/deploy/prod/env.production.example ~/.config/dadaotask/env.production
|
||
chmod 600 ~/.config/dadaotask/env.production
|
||
vim ~/.config/dadaotask/env.production # 替换全部 __CHANGE_ME__
|
||
```
|
||
|
||
必填项:`DATABASE_URL` / `MIGRATION_DATABASE_URL`(2.1 的口令)、`JWT_SECRET`(≥32 位随机串)、`REDIS_URL`、`WEB_DIST_DIR`、`UPLOAD_DIR`。
|
||
|
||
两个踩过的坑:
|
||
|
||
- **`CORS_ORIGINS` 必须写成 JSON 数组**(`'["http://...","http://..."]'`)。它是 list 字段,逗号分隔的字符串会让 pydantic-settings 当 JSON 解析并抛 `SettingsError`,alembic 这一步就挂。
|
||
- **Redis 有口令**:fnsvr-01 的 redis 开了 `requirepass`(口令看 `docker inspect redis`),写进 URL 时要百分号编码(`#` → `%23`),否则连不上、发码/登录直接 500。
|
||
|
||
**当前状态是内测模式**:短信通道还没接(非 dev provider 直接 501),所以 `SMS_PROVIDER=dev` + `INTERNAL_TEST_LOGIN=true`,固定验证码 `123456`。启动日志会打 WARNING。**只允许内网访问**;短信通道接好后删掉 `INTERNAL_TEST_LOGIN` 并把 `SMS_PROVIDER` 换成真实供应商。
|
||
|
||
### 2.4 装服务单元
|
||
|
||
```bash
|
||
mkdir -p ~/.config/systemd/user
|
||
cp ~/app/dadaotask/deploy/prod/dadaotask-api.service ~/.config/systemd/user/
|
||
systemctl --user daemon-reload
|
||
```
|
||
|
||
### 2.5 首次上线
|
||
|
||
```bash
|
||
bash ~/app/dadaotask/deploy/prod/deploy.sh
|
||
```
|
||
|
||
## 3. 日常发布(每次上线)
|
||
|
||
```bash
|
||
# 本地:提交并 push 到 main(生产只跑仓库 HEAD)
|
||
# 服务器:
|
||
bash ~/app/dadaotask/deploy/prod/deploy.sh
|
||
```
|
||
|
||
脚本做六件事,任何一步失败即中止:代码对齐 `origin/main`(`git reset --hard`,禁止服务器手工改)→ 装 Python 依赖 → `alembic upgrade head`(用迁移角色)→ **在服务器上** `npm ci && npm run build` → 写 `env.build` 并重启 → 校验 `/health` 与 `/version` 的 commit 等于 HEAD。
|
||
|
||
前端产物只在服务器上构建,**不允许**本地上传 dist(AGENTS.md 铁律 3)。
|
||
|
||
## 4. 部署后校验
|
||
|
||
```bash
|
||
curl -s http://192.168.8.188:8200/health # {"status":"ok","db":true,...}
|
||
curl -s http://192.168.8.188:8200/version # commit 必须等于 git rev-parse --short HEAD
|
||
curl -sI http://192.168.8.188:8200/ | head -1 # 200,text/html(前端产物)
|
||
```
|
||
|
||
浏览器验收:打开 `http://192.168.8.188:8200` → 手机号任意 + 验证码 `123456` 登录 → 任务/工单/知识各点一遍。
|
||
|
||
## 5. 运维
|
||
|
||
```bash
|
||
systemctl --user status dadaotask-api # 状态
|
||
systemctl --user restart dadaotask-api # 重启
|
||
journalctl --user -u dadaotask-api -f # 日志(gunicorn 输出到 stdout)
|
||
```
|
||
|
||
- 附件落盘:`backend/var/uploads`(按工作区分子目录),删库不删文件,备份时一起打包
|
||
- 回滚:`cd ~/app/dadaotask && git reset --hard <旧 commit> && bash deploy/prod/deploy.sh`;**数据库迁移不回滚**,回滚代码前确认旧代码兼容新表结构
|
||
- 端口:`8200`(systemd 单元与 `deploy.sh` 的 `PORT` 默认值,两处要一致)
|
||
|
||
## 6. 已知限制(当前阶段)
|
||
|
||
| 限制 | 影响 | 解除方式 |
|
||
|---|---|---|
|
||
| 没有 HTTPS / 域名 | 只能内网 IP 访问,令牌明文传输 | 域名 + nginx + 证书(需 root) |
|
||
| 内测固定验证码 | 谁都能用任意手机号登录 | 接短信通道,关 `INTERNAL_TEST_LOGIN` |
|
||
| 无 pgvector | 语义检索走 `real[]` 全表扫描,数据量大了会慢 | 上独立 pgvector 实例(`deploy/docker-compose.vector.yml`) |
|
||
| 上传目录在本机磁盘 | 机器坏了附件就没了 | 备份 `backend/var/uploads`,或将来到对象存储 |
|
||
| 共享 PG / Redis | 别人占满连接或内存会波及本站 | 独立实例 |
|