# 生产部署 Runbook (Linux) — 上线任务 #3 (2026-06-10 续 26)

> 取代 macOS launchd 方案 (`x-ingest-cron-runbook.md` 保留作 operator 本机 X 抓取参考)。
> 目标形态: **飞书周报推送 (主入口) + Cloudflare 静态快照仪表盘 (辅)**, 不向用户开放直连 API。

## §1 安装

```bash
sudo useradd -r -m satagent
sudo git clone <repo> /opt/InvesResearch && sudo chown -R satagent: /opt/InvesResearch
sudo -u satagent bash -c '
  cd /opt/InvesResearch/agent
  python3 -m venv .venv
  .venv/bin/pip install -e ".[dev,export]"
'
```

## §2 生产 .env (必填项)

```bash
cd /opt/InvesResearch/agent && cp .env.example .env && $EDITOR .env
```

```ini
# --- 生产硬要求 ---
SATAGENT_ENV=production                  # 强制 token: 未配 token 时 API 全 503
SATAGENT_API_TOKEN=<openssl rand -hex 32>
SATAGENT_CORS_ORIGINS=https://你的门户域名
SATAGENT_DB=/opt/InvesResearch/agent/data/agent.db

# --- LLM (可选: 日报中文化 / debate / 对照实验; 分类兜底经 v22/v23 实测
#     在当前规则强度下净减分, 不建议开 use_llm, jobs 默认即关) ---
SATAGENT_LLM_PROVIDER=zhipu
SATAGENT_LLM_API_KEY=<key>
SATAGENT_LLM_BASE_URL=https://open.bigmodel.cn/api/coding/paas/v4   # Coding Plan 端点
SATAGENT_LLM_TIMEOUT=120                 # ⚠ glm-4.6 思考型输出实测 60s+, 默认 20s 必超时

# --- 飞书周推 (上线任务 #4, 主入口) ---
SATAGENT_FEISHU_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/<token>
SATAGENT_FEISHU_SECRET=<机器人签名密钥>
```

## §3 初始化与标准 jobs

```bash
sudo -u satagent /opt/InvesResearch/agent/scripts/bootstrap_production_jobs.sh
# 幂等注册 5 个标准 job: daily-rss / x-freshness / weekly-feishu-push /
# monthly-validate / x-ingest-daily, 并打印 job list
```

**⚠ 生产纪律: 绝不在生产库跑 `satagent ingest-file samples/...`** — samples 是
演示语料 (来源字段为拟真标签, 无外部出处)。演示/快照用临时库
(`refresh_live_snapshot.py` 默认行为)。种子公司卡片自带
`data_quality=seed-placeholder` 标识, API 输出与 live 前端均显式可见。

## §4 systemd (API) + 反向代理

```bash
sudo cp scripts/systemd/satagent-api.service /etc/systemd/system/   # 先改路径/用户
sudo systemctl daemon-reload && sudo systemctl enable --now satagent-api
curl -s http://127.0.0.1:8000/health                                # {"status":"ok",...}
```

API 只监听 127.0.0.1。需要公网 live 模式时 (可选, 静态快照模式不需要), 两个方案
(都解决 Cloudflare 门户 https 页面拦 http 混合内容的问题):

**推荐: Cloudflare Tunnel** — cloudflared 纯出站连接, 免开端口 / 源站 IP 不暴露 /
免费 HTTPS / 可叠加 Access SSO 门 (≤50 用户免费)。完整步骤与 Cloudflare 部署
全量评估见 [`cloudflare-deployment.md`](./cloudflare-deployment.md)。

**备选: caddy 反代** (需开 80/443):

```
api.你的域名 {
    reverse_proxy 127.0.0.1:8000
}
```

## §5 crontab (satagent 用户)

```cron
# 数据
15 6 * * *   /opt/InvesResearch/agent/scripts/run_job.sh daily-rss
# X 抓取 (ECS 本地, 走 X_PROXY 代理; 部署见 expert/X/DEPLOY-ECS.md) — 早于 x-ingest 完成
0  5 * * *   /opt/InvesResearch/expert/X/scripts/run_scrape.sh
45 6 * * *   /opt/InvesResearch/agent/scripts/run_job.sh x-ingest-daily
0  7 * * *   /opt/InvesResearch/agent/scripts/run_job.sh x-freshness
# 用户入口: 每周一 09:00 飞书决策周报
0  9 * * 1   /opt/InvesResearch/agent/scripts/run_job.sh weekly-feishu-push
# 质量守护: 每月 1 号
30 7 1 * *   /opt/InvesResearch/agent/scripts/run_job.sh monthly-validate
# 备份: 每日 (在线 .backup, 保留 14 天; SATAGENT_BACKUP_KEEP_DAYS 可调)
0  5 * * *   /opt/InvesResearch/agent/scripts/backup_db.sh
# 门户快照 (可选, 已被 GitHub Actions data-refresh.yml 每日自动刷新取代 —
# Actions 链路无 X 信号; 想发布含 X 的生产库快照才开本行, 且需停用 Actions
# 定时器避免次日被覆写。注意 --db 模式只读复用库, 不会混入演示语料):
# 30 9 * * 1   cd /opt/InvesResearch/agent && .venv/bin/python scripts/refresh_live_snapshot.py --db data/agent.db --no-inline
```

退出码语义: 0=success · 1=error (含 x-freshness 断供告警) · 2=skipped (job disabled)。
连续失败到 max_failures 自动 disable, 修复后 `satagent job enable <name>`。

## §6 升级

```bash
sudo -u satagent bash -c '
  cd /opt/InvesResearch && git pull
  cd agent && .venv/bin/pip install -e ".[dev,export]" -q
  .venv/bin/satagent init        # 幂等 migration + 新种子 (INSERT OR IGNORE)
  .venv/bin/python -m pytest -q  # 可选: 升级后回归
'
sudo systemctl restart satagent-api
```

## §7 已知边界 (如实告知首批用户)

- **判断质量**: 203 条标注语料 (corpus v3.1, 口径统一) rules-only 总吻合度 **62.1%**
  (2026-06-11 D V2.1 判分口径: 负样本拒识计分 + 幻觉挂线计罚; 历史口径
  35.5%/37.4% 不可比)。**LLM 分类兜底定稿: 生产 rules-only** — v22 always 54.2% (-2.0pp) /
  v23 conditional 52.2% (-4.0pp), 两种覆盖模式在新基线下均净减分 (报告
  v22/v23); LLM 保留用于日报中文化与 debate。输出仅供研究效率提升,
  **不构成投资建议**
- **数据新鲜度**: 持续自动产新 RSS 源 **4 个** (SpaceNews + SpaceflightNow/
  European Spaceflight/SatNews, 续 35 扩源, 实测密度 40-50%); X 源靠 operator 本机抓取
  节奏 (断供 48h 由 x-freshness 飞书告警); wiki/HarnessFarm/Shock 为静态语料
- **占位数据**: 公司卡片 orders/score/市场模型为 seed 占位
  (`data_quality=seed-placeholder`); **头部 10 家已按 2026Q1 公开财报覆盖营收并标
  `verified`** (中国卫通/海格/华力创通/震有/复旦微电/北斗星通/中国卫星/烽火/
  Rocket Lab/Iridium, 续 35), 其余逐家推进; `satagent init` 升级时自动把新核实
  数据覆盖到仍为占位的旧行
- **SQLite 单写者**: 当前部署形态 (cron 串行 + 只读 API) 安全;
  多用户写入场景需迁 Postgres (Tier 2, 见 PRODUCT-STRATEGY §6)

## §8 X 链路服务器化评估 (上线任务 #8 · 结论)

**当前决策 (2026-07-08, operator 指令): 抓取迁到 ECS 本地, 走代理。**
早前口径是「不迁移抓取, 只迁移数据」—— twikit 依赖 cookies.json 登录态, 数据中心 IP
直抓触发风控/封号的风险显著升高 (账号是稀缺资产), 且 429 节奏需人工判断。现按 operator
指令迁移, 用**住宅/移动代理 (`X_PROXY`)** + 保守节流把该风险压下来。部署与风险缓解详见
[`expert/X/DEPLOY-ECS.md`](../../expert/X/DEPLOY-ECS.md)。当前拓扑:

```
ECS cron 05:00  expert/X/scripts/run_scrape.sh  (走 X_PROXY 代理, cookies.json 本机生成后上传)
   └─ 就地写 /opt/InvesResearch/expert/X/data/x.sqlite3
        └─ ECS cron 06:45  x-ingest-daily (since=auto 增量) → events
             └─ 07:00  x-freshness (48h 断供 → 飞书红卡)
```

> ~~rsync 一行 (operator 本机 crontab, 抓完即推)~~ —— **已退役**: 抓取改在 ECS 本地跑,
> 不再从本机 rsync `x.sqlite3` (否则会用本机旧库覆盖 ECS 新库)。若仍保留本机抓取作
> 冗余, 需确保两边不互相覆盖。
> ```cron
> # 30 3 * * * rsync -az ~/InvesResearch/expert/X/data/x.sqlite3 satagent@服务器:/opt/InvesResearch/expert/X/data/
> ```

**换 cookie 仍偶尔在本机做一次** (数据中心 IP 上 login 更易触发挑战), 生成后 `scp` 覆盖
ECS 的 `data/cookies.json`。断供 48h 系统自动告警 (一页纸 §不能信什么 第 4 条)。
