# 部署 / 运维 — content-harvester > 敏感值(token、MinIO 凭据、服务器登录信息)不写进本文件 —— 放本地工作目录里被 `.gitignore` 排除的文件:`.env.dev`(env 变量,`docker run --env-file` 直接用)、`SERVERS.local.md`(各环境服务器登录方式、目录、容器、注意事项)。下面命令里凡是 `--env-file .env.dev` 就会带上 token/凭据。 ## 部署形态 - 跑一个容器 `content-harvester`,`--restart unless-stopped`,挂在跟主站 backend 同一个 docker 网络上(主站用 `http://content-harvester:8000` 访问),host 端口任选(仅供本机直接调试),挂卷 `content-harvester-profile:/data/browser_profile`,`--memory=2g --cpus=2`,MinIO 配置走容器 env。 - 代码:`git clone -b dev https://git.aidfxn.com/admin/content-harvester.git`,以后 `git pull origin dev`。 - 对接的主站 Skill:「内容网站采集(content-harvester)」(`executor_type=lobster`,`lobster_api_url=http://content-harvester:8000`,`lobster_api_key` = `.env.dev` 里的 `HARVESTER_TOKEN`)。 ## 重新部署 / 更新 ### dev:自动部署(push `dev` 分支即触发) `.gitea/workflows/deploy.yml` —— **push 到 `dev` 分支**(或在 gitea 上手动 `workflow_dispatch`)→ dev 开发机的 gitea runner(标签 `srv1563674`,跟主站 AIautomation 的 deploy.yml 用同一个)自动:`actions/checkout` → `docker build -t content-harvester .` → `docker rm -f content-harvester` → `docker run -d ...`(env 走 `--env-file .env`)→ healthz 检查 → `docker image prune`。**只管 dev**;`master`/prod 不在 `on.push.branches` 里,不会被这个工作流碰(prod 仍走下面的手动流程)。 **前置条件(一次性)**:在该仓库的 Gitea Actions secrets 里加一个 **`CH_ENV_FILE`**,内容就是要喂给容器的 env 文件全文(等价于 `.env.dev`),至少: ``` HARVESTER_TOKEN=<和主站采集 Skill 的 lobster_api_key 一致> MINIO_ENDPOINT=https://minio.aidfxn.com MINIO_BUCKET=clawlerdata MINIO_ACCESS_KEY=<...> MINIO_SECRET_KEY=<...> # 可选:HEADLESS=0 / BROWSER_CHANNEL=chrome / MAX_PAGES=... / DEFAULT_MAX_IMAGES=... 等 ``` 没配 `CH_ENV_FILE` 的话工作流会在「Prepare env file」那步报错退出(不会拿一份残缺的 env 去跑)。工作流里硬编码的部署参数(非密钥):host 端口 `8019`、docker 网络 `ai-admin-cuiyue_default`、卷 `content-harvester-profile:/data/browser_profile`、`--memory=2g --cpus=2`、`--restart unless-stopped` —— 要改这些就改 `deploy.yml` 顶部的 `env:` 块。 > 工作流在 runner 的 workspace 里 build(`actions/checkout` 取的那份代码),**不动 `/opt/content-harvester`** —— 那个目录会逐渐过时,「现在 dev 跑的是哪份代码」要看运行中的容器(`docker exec content-harvester ...`),别看 `/opt/content-harvester` 的 git。 ### 手动部署(prod;或 dev 想临时绕过工作流) ```bash cd /opt/content-harvester git pull -q origin # prod 用 master docker build -t content-harvester . docker rm -f content-harvester docker run -d --name content-harvester \ --restart unless-stopped \ --network ai-admin-cuiyue_default \ # 跟主站 backend 同一个 docker 网络 -p 8019:8000 \ --env-file /opt/content-harvester/.env.dev \ # 这个文件要先放到服务器上(本地 scp 过去,或在服务器上照样写一份;内容同 CH_ENV_FILE) -v content-harvester-profile:/data/browser_profile \ --memory=2g --cpus=2 \ content-harvester sleep 6 && curl -s localhost:8019/healthz # 期望 {"ok":true,"headless":false,...} ``` > 注:`.env.dev` 被 `.gitignore` 排除,`git pull` 不会带它过去。第一次在服务器上手动部署时把它放到 `/opt/content-harvester/.env.dev`(从本地 scp,或照本地内容写一份)。或者也可以不用 env-file、把那几个 `-e KEY=VALUE` 直接展开写在 `docker run` 里(但 token/凭据就会出现在 shell history / `docker inspect`)。 如果一时没法在服务器上 build,也可以本地 `docker build` 后 `docker save content-harvester | gzip | ssh ... 'gunzip | docker load'` —— 但**注意 CPU 架构**:Mac 一般是 arm64,服务器多是 x86_64,本地构建的 arm64 镜像在服务器跑不了。所以一般还是在服务器上 build。 ## 在一台新服务器上首次部署 1. 装 Docker。 2. `git clone -b dev https://git.aidfxn.com/admin/content-harvester.git`(私有仓库,需要 git.aidfxn.com 的凭据)。 3. 在工程目录放一份 `.env.dev`(从本地拷,或照 `.env.dev` 注释里的字段写;至少要 `HARVESTER_TOKEN` + 4 个 `MINIO_*` + `HEADLESS=0`)。 4. `docker build -t content-harvester .`(base 镜像 ~1GB+,首次几分钟)。 5. `docker run -d ...`(同上面那串;`--network` 改成跟主站 backend 同一个 docker 网络的名字,这样主站能用 `http://content-harvester:8000` 访问)。 6. 主站侧:确认采集 Skill 的 `lobster_api_url` 指向 `http://content-harvester:8000`、`lobster_api_key` 跟 `HARVESTER_TOKEN` 一致;`agent-system-backend` 已含配套的 5 处改动(见 README 第 6 节)。 7. x86_64 服务器想用真实 Chrome 过 Cloudflare 更稳:Dockerfile 里把 `playwright install chromium` 改成 `playwright install chromium chrome`,`.env.dev` 里加 `BROWSER_CHANNEL=chrome`,重 build。 ## 本地跑(开发调试) ```bash python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt && python -m playwright install chromium set -a; . ./.env.dev; set +a # 加载 HARVESTER_TOKEN / MINIO_* 等 HEADLESS=1 uvicorn server.app:app --reload # 本机没 Xvfb 用 HEADLESS=1,但那样过不了 xchina 详情页的 Cloudflare,只能验证接口/列表页 # 真要本地完整测(过 Cloudflare),用 Docker 跑(镜像里有 xvfb):docker build -t content-harvester . && docker run --rm -p 8019:8000 --env-file .env.dev content-harvester ``` ## 排查 - `curl -s localhost:8019/healthz` —— 服务活着吗。 - 从主站网络内看:`docker run --rm --network <主站网络> curlimages/curl -s http://content-harvester:8000/healthz` —— 主站 backend 能不能访问到。 - 提一个作业看日志:`curl -X POST localhost:8019/agents/xchina/invoke -H "Authorization: Bearer $HARVESTER_TOKEN" -H 'Content-Type: application/json' -d '{"source_kind":"website","content_url":"https://xchina.co/video/id-XXXX.html"}'` → 拿 task_id → `curl localhost:8019/tasks/`(返回里 `log_tail` 是最近作业日志)。`docker logs content-harvester` 看服务日志。 - 主站那头任务一直 RUNNING:看主站 backend 容器日志,`call_lobster_agent` 在轮询;crawl 类超时是 `CRAWL_LOBSTER_TIMEOUT`(默认 7200s)。任务 SUCCEEDED 但资源库没东西:多半是 `content` 为空被 ingest 拒收(`reason:"正文为空"`)——本服务已兜底用 title,理论上不会再发生,真发生了查 scraper 的 `_extract_description`。 - 容器抽风/吃内存:`--memory=2g --cpus=2` 已经限了,真有问题重启容器即可(状态都在内存,重启即丢,主站会把那次任务标失败,重试就行)。