# 云服务器部署方案与必要性验证 (以阿里云为例) — 2026-06-11 · 续 33

> 回答: "P0 装机, 先直接部署在 Cloudflare; 如果需要 Linux 云服务器跑后台, 验证必要性并给出部署方案。"
> 网页版: [`cloud-server-deployment.html`](./cloud-server-deployment.html) ·
> 配套: [`cloudflare-deployment.md`](./cloudflare-deployment.md)(Cloudflare 侧)· [`production-runbook.md`](./production-runbook.md)(通用 Linux 细则)

## §0 结论速览

**先 Cloudflare, 服务器按需 — 且"需"的边界已经验证清楚:**

1. **阶段 A(纯 Cloudflare + GitHub Actions, ¥0/月)已经在跑**: 门户 + 真实数据仪表盘日更 + 飞书决策周报(本轮新增 serverless 版, 配一个 secret 即开)。**不买服务器, 产品主入口今天就能给真实用户用。**
2. **服务器只被三件事真正需要**(见 §2): X 信号链路 / 运营态持久化 / live API 实时模式。这三件事一件都不要的话, **不必买服务器**。
3. 要买也很便宜: 阿里云 **2C2G 经济型 ECS(~¥100/月级, 新用户 99 元/年档也够)**, 装机一条命令(`setup_server.sh`), 与 Cloudflare 前端用 **Tunnel** 连接(不开任何入站端口)。

## §1 阶段 A — 纯 Cloudflare/Actions 已覆盖的部分(现状盘点)

| 能力 | 承载 | 状态 |
|---|---|---|
| 门户 36 页 + live.html 仪表盘 | Cloudflare Pages(main 自动部署) | ✅ 在线 |
| 数据日更(SpaceNews RSS → 归档 → 快照) | GitHub Actions `data-refresh.yml` 每日北京 06:45 | ✅ 在线(云端首跑已验证, 自动产生数据提交) |
| **飞书投研日报(产品主入口)** | GitHub Actions `daily-feishu.yml` 每日北京约 08:30 · top-3 事件卡片(配 `SATAGENT_LLM_API_KEY` secret 后中文标题+一句话简介;GitHub 定时有数小时抖动,失败有告警) | ✅ **在线(已验收)**; 0 条时发"无新增信号"心跳卡 |
| **飞书决策周报** | GitHub Actions `weekly-feishu.yml` 每周一北京约 09:30 · 7 天窗口双视角决策卡(与日报并存, 频率/内容不同) | ✅ 在线 |
| **实时看板(即首页)** | oaf.world 首页即看板(原文档门户降级 `portal.html`): 拉 Actions 每 6h 刷新的快照 + 页面 10 分钟自动轮询 | ✅ 在线; 秒级实时需阶段 B 自托管 API |
| **失败自动告警** | GitHub Actions `failure-alert.yml`: daily/weekly/data-refresh 任一失败 → 飞书红卡(续 39 补无人值守自我监控) | ✅ 在线 |
| 判断质量守护 | agent-ci(Python 3.9/3.11, 536 tests)每次推送 | ✅ 在线 |

**开启 serverless 飞书周报(唯一人工步骤, 2 分钟):**

1. 飞书群 → 设置 → 群机器人 → 添加"自定义机器人", 拿到 webhook URL(建议同时开"签名校验"拿 secret);
2. GitHub 仓库 → Settings → Secrets and variables → Actions → New repository secret:
   - `SATAGENT_FEISHU_WEBHOOK` = webhook URL(必填)
   - `SATAGENT_FEISHU_SECRET` = 签名密钥(开了校验才填)
3. 完成。每日北京约 08:30 自动推送(GitHub 定时排队有抖动,实测可能延迟数小时,失败会有飞书告警); 想立即验证: Actions 页 → daily-feishu → Run workflow。
   (2026-06-11 已验收: 周报模式首发成功后, 按用户要求改为日报 top-3 模式)

**阶段 A 的诚实边界**(一页纸口径, 对用户明示):
- 周报信号 = SpaceNews RSS + wiki/HarnessFarm/Shock 静态语料, **无 X 信号**;
- 无运营态: WYHTB 看牛/看熊、证伪 trigger 等操作员配置不持久(每次重建库);
- 仪表盘是日更静态快照, 不是实时查询。

## §2 必要性验证 — 服务器到底解锁什么

逐能力对照(✅=Cloudflare/Actions 可承载, ❌=只有自托管服务器能做):

| # | 能力 | 无服务器 | 有服务器 | 为什么 Actions/Workers 做不了 |
|---|---|---|---|---|
| 1 | **X 推文信号入库** | ❌ | ✅ rsync 接收端 + x-ingest cron | operator 本机 twikit 抓取后需要一个**常驻可写目标**; Actions runner 无固定地址不可 rsync; x.sqlite3 不宜进 git(体积增长 + 隐私边界) |
| 2 | **运营态持久化**(WYHTB/trigger/thesis 历史/job 审计/token 账) | ❌ | ✅ SQLite 单一真库 | Actions 每次跑都是新容器; 这类状态是操作员手工配置与累积历史, 不适合也不应该进代码仓库 |
| 3 | **live API 实时模式**(live.html 切实时) | ❌ | ✅ FastAPI + Tunnel | Workers Python beta + SQLite 无持久化(评估见 cloudflare-deployment §3) |
| 4 | 飞书周报 | ⚠️ 可(降级: 无 #1/#2) | ✅ 全量信号 | — |
| 5 | 数据日更/快照/门户 | ✅ | ✅(可叠加) | — |
| 6 | LLM 兜底 / monthly-validate | ⚠️ 可(加 secret, Actions 单 job 上限 6h 够 45min 跑) | ✅ | — |
| 7 | 每日 DB 备份纪律 | (无状态可备) | ✅ backup_db.sh 14 天滚动 | — |

**判定**: 服务器的必要性 = **是否要 X 信号(#1)与运营态(#2)**。
- X 信号是本产品的差异化输入之一(8 源之一, 95 账号, 断供告警体系都已建好)——
  **中期要上**; 但第一批用户验证期(看周报形态/主线判断是否有用)可以先用阶段 A 跑通反馈闭环。
- **推荐节奏: 现在 0 成本开阶段 A 给用户 → 用户反馈值得继续 → 再买服务器升阶段 B(半天装完)。**

## §3 服务器要求(阶段 B · 阿里云选型)

| 项 | 要求 | 说明 |
|---|---|---|
| 实例 | **ECS 2 vCPU / 2 GiB**(经济型 e / 突发型 t6 即可) | 负载 = cron 串行任务 + 只读 API, 无常驻重计算; 1C1G 也能跑但 pip 安装与 LLM 验证跑批会紧 |
| 系统盘 | 40 GB ESSD Entry | 代码+venv ~1.5G, DB 百 MB 级, 备份 14 天滚动 |
| OS | **Ubuntu 24.04 LTS** 或 Alibaba Cloud Linux 3 | 脚本对 apt/dnf/yum 自适应; Python ≥3.9(CI 双版本验证口径) |
| 地域 | 离 operator 近即可(rsync 走公网) | 出网要能访问 spacenews.com / open.bigmodel.cn / open.feishu.cn / github.com |
| 带宽 | 按量 1-3 Mbps 足够 | 流量极小(RSS+webhook+rsync 增量) |
| **安全组** | **只开 22(SSH, 建议限源 IP)**; **不开 80/443** | 公网入口全部走 Cloudflare Tunnel 出站连接(§5), 源站零暴露 |
| 价格档 | ~¥100/月; 新用户 99 元/年活动机也满足 | 成本上限清晰 |

## §4 安装部署过程

### §4.1 一键装机(root)

```bash
# 私有仓库: REPO_URL 带 PAT, 或先配好部署密钥用 git@ 地址
curl -fsSL https://<你的仓库 raw 地址>/agent/scripts/setup_server.sh -o setup_server.sh
sudo REPO_URL="https://<PAT>@github.com/zhanglunet/InvesResearch.git" bash setup_server.sh
```

脚本做 6 件事(幂等, 重复跑=升级): 装系统依赖(git/python3≥3.9/sqlite3/rsync)→ 建 `satagent` 运行用户 → clone/pull 到 `/opt/InvesResearch` → venv + `pip install -e ".[dev,export]"` → `satagent init`(幂等 migration+种子)→ 铺 `.env` 模板(已存在绝不覆盖, chmod 600)。

### §4.2 配置 .env(人工, 必做)

```bash
sudo -u satagent vim /opt/InvesResearch/agent/.env
```

必填项见 [runbook §2](./production-runbook.md): `SATAGENT_ENV=production`(强制 token)/ `SATAGENT_API_TOKEN` / LLM 三件套(provider/key/base_url + **`SATAGENT_LLM_TIMEOUT=120`**)/ 飞书 webhook+secret。**key 只进 .env, 永不进 git。**

### §4.3 注册标准 jobs + crontab

```bash
sudo -u satagent /opt/InvesResearch/agent/scripts/bootstrap_production_jobs.sh
sudo -u satagent crontab -e    # 粘 runbook §5 的 6 行(数据/周推/月检/备份)
```

### §4.4 (可选)live API systemd

```bash
sudo cp /opt/InvesResearch/agent/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","version":"0.5.x"}
```

### §4.5 验收清单

```bash
sudo -u satagent bash -c 'cd /opt/InvesResearch/agent && .venv/bin/python -m pytest -q'   # 519 passed
sudo -u satagent /opt/InvesResearch/agent/scripts/run_job.sh daily-rss                    # exit=0
sudo -u satagent bash -c 'cd /opt/InvesResearch/agent && .venv/bin/satagent notify-test'  # 飞书测试卡片
```

## §5 与 Cloudflare 前端连接

前端(Pages)与后端(ECS)各司其职, 三种连接关系:

1. **静态快照模式(默认, 不需要本节)**: Pages 仪表盘吃 Actions 日更的快照 JSON, 与服务器无连接。
2. **实时模式(可选)**: 服务器装 **cloudflared Tunnel** 把 `127.0.0.1:8000` 发布为 `https://api.你的域名` —— 纯出站连接, 安全组不开 80/443, 源站 IP 不暴露, HTTPS 证书 Cloudflare 边缘签发。完整命令见 [cloudflare-deployment §2](./cloudflare-deployment.md); 要点:
   - `.env` 里 `SATAGENT_CORS_ORIGINS=https://你的 Pages 域名`(跨域放行);
   - live.html 的 API 框填 `https://api.你的域名` 即切实时(token 用 Bearer 头, 不走 URL);
   - 可叠加 Cloudflare **Access**(SSO/OTP 门, ≤50 用户免费)。
3. **推送双轨互斥**: 服务器 crontab 的飞书推送 job 与 Actions `daily-feishu.yml` **二选一**(否则每天收两份); 上服务器后在 GitHub Actions 页 disable 后者。服务器侧日报用同款 action: `satagent job add --action daily-events-notify-feishu --params '{"webhook_url":"...","limit":3}'`。快照同理: Actions `data-refresh.yml` 日更继续跑即可(无 X), 想发布含 X 的快照再按 runbook §5 注释行切换并停用 Actions 定时器。

## §6 运行维护指南

**日常巡检(每周 1 次, 2 分钟):**

```bash
sudo -u satagent bash -c 'cd /opt/InvesResearch/agent && .venv/bin/satagent job list'
# 看三列: last_status 全 success? · fails 0/3? · last_run 时间新鲜?
tail -50 /opt/InvesResearch/agent/data/jobs.log     # cron 包装层日志 (run_job.sh)
```

**告警语义(被动, 飞书送达):** x-freshness 红卡 = X 数据断供 48h(去 operator 本机看抓取)· weekly-feishu 没来 = 看 `job list` 的 fails 与 `job show weekly-feishu --history`。job 连续失败到 max_failures 自动 disable, 修复后 `satagent job enable <name>`。

**备份与恢复:** `backup_db.sh` 每日在线 `.backup`(SQLite 一致性快照), 14 天滚动;
恢复: `systemctl stop satagent-api && cp data/backups/agent-<日期>.db data/agent.db && systemctl start satagent-api`。

**升级:** 重跑 `sudo bash setup_server.sh`(= git pull + pip 升级 + 幂等 migration), 然后 `sudo systemctl restart satagent-api`(若启用)。

**故障排查表:**

| 症状 | 第一刀 |
|---|---|
| job 全红 | `run_job.sh <name>` 手跑看 stderr; 多半是 .env 漏配或出网被安全组/代理拦 |
| LLM 验证 applied=0 | `SATAGENT_LLM_TIMEOUT=120` 没配(glm-4.6 思考输出 ~65s, 默认 20s 必超时) |
| API 503 | `SATAGENT_ENV=production` 但没配 `SATAGENT_API_TOKEN`(设计如此) |
| 周报收到两份 | §5.3 双轨没有互斥, disable 一边 |
| `database is locked` | 确认没有并行写者(cron 串行设计); 升级到最新代码(连接生命周期修复已入) |

## §7 X 链路接通(operator 本机 → 服务器)

抓取**不**搬服务器(账号风控, 见 runbook §8), 只搬数据。operator 本机 crontab 一行:

```cron
30 3 * * * rsync -az ~/InvesResearch/expert/X/data/x.sqlite3 satagent@<服务器IP>:/opt/InvesResearch/expert/X/data/
```

服务器侧 `x-ingest-daily`(since=auto 增量)已由 bootstrap 注册, crontab 已含; 断供 48h 由 x-freshness 自动发飞书红卡。

## §8 安全清单

- [ ] 安全组只开 22 且限源 IP; 80/443 不开(Tunnel 出站架构)
- [ ] `.env` chmod 600, key 永不进 git(仓库根 .gitignore 双保险已配)
- [ ] `SATAGENT_ENV=production` + 强随机 `SATAGENT_API_TOKEN`(`openssl rand -hex 32`)
- [ ] live.html 不在 URL 传 token(已收口, Bearer 头)
- [ ] 飞书机器人开签名校验(`SATAGENT_FEISHU_SECRET`)
- [ ] (可选)Tunnel 前叠 Cloudflare Access SSO 门
- [ ] 每日备份在跑: `ls /opt/InvesResearch/agent/data/backups/ | tail`
