

## 全局地图

```
服务器
│
├── /opt/winning-wallet-finder/          ← 项目代码（git clone 来的）
│   ├── copybot.py                        ← bot 主程序
│   ├── copybot.paper.json                ← paper 配置（跟哪些钱包、金额）
│   ├── config.live.json                  ← live 配置（跟哪些钱包、金额）
│   ├── copybot_state.json                ← paper bot 的账本（自动生成）
│   ├── copybot_state.live.json           ← live bot 的账本（自动生成）
│   ├── copybot_fills.jsonl               ← paper 成交记录
│   ├── copybot_fills.live.jsonl          ← live 成交记录
│   └── live/
│       ├── serve_dashboard.py            ← 网页 dashboard 程序
│       ├── bot_dashboard.html            ← 中文界面
│       ├── copybot_live.json             ← paper bot 的实时 feed
│       ├── copybot_live_real.json        ← live bot 的实时 feed
│       ├── portfolio.py                  ← 回测程序
│       ├── backtest.json                 ← 回测配置
│       ├── daily.sh                      ← 每日流水线
│       └── cache.duckdb                  ← 钱包历史数据缓存
│
├── /etc/systemd/system/                  ← systemd 服务定义（4 个）
│   ├── copybot-paper.service             ← paper bot 服务
│   ├── copybot-live.service              ← live bot 服务
│   ├── copybot-dashboard.service         ← dashboard 服务
│   └── mihomo.service                    ← 代理服务
│
├── /etc/copybot/                         ← 环境变量文件（密钥、代理）
│   ├── paper.env                         ← paper bot 的环境变量
│   └── live.env                           ← live bot 的环境变量
│
├── /etc/mihomo/                          ← 代理配置
│   ├── config.yaml                       ← Clash 订阅配置
│   └── cache.db                          ← 记住上次选的节点
│
├── /usr/local/bin/mihomo                 ← 代理程序本体（二进制）
├── /opt/mihomo-healthcheck.sh            ← 代理健康检查脚本
├── /opt/winning-wallet-finder/.netrc     ← Gitea PAT 认证（wwf 用户用，bot 自动 push 时读）
│
└── crontab                               ← 定时任务
    ├── 0 2 * * * daily.sh                ← 每日 10:00 北京时间跑回测
    └── */5 * * * * mihomo-healthcheck.sh ← 每 5 分钟检查代理
```

> `.netrc` 不在目录树里显示是因为它是隐藏文件（以 `.` 开头）。用 `ls -la` 才能看到。

## 三个文件夹的分工

| 文件夹                        | 放什么                     | 为什么在这                         |
| ----------------------------- | -------------------------- | ---------------------------------- |
| `/opt/winning-wallet-finder/` | 项目代码 + 配置 + 状态     | git clone 来的，所有代码在这       |
| `/etc/systemd/system/`        | 服务定义（.service 文件）  | Linux 约定俗成，systemd 服务都放这 |
| `/etc/copybot/`               | 环境变量（密钥、代理地址） | 敏感文件不放代码仓库，独立管理     |
| `/etc/mihomo/`                | 代理配置                   | mihomo 的数据目录                  |

> `/opt` 是你的代码，`/etc` 是系统配置。分开是因为**密钥不应该跟代码放一起**（怕 commit 到 Gitea 泄露）。

## live.env 和 config.live.json 怎么连起来的

它们**不在一个文件夹**，但 systemd 是桥梁：

```
/etc/systemd/system/copybot-live.service
    │
    │  ExecStart=python3 copybot.py --config config.live.json --live
    │                                          ↑
    │                              读 /opt/winning-wallet-finder/config.live.json
    │                              （跟哪些钱包、金额、风险参数）
    │
    │  EnvironmentFile=/etc/copybot/live.env
    │                    ↑
    │              读 /etc/copybot/live.env
    │              （私钥、代理地址、确认短语）
    │
    ▼
copybot.py 启动时：
  1. 从 EnvironmentFile 读 LIVE_PRIVATE_KEY -> 放进 config["live"]["private_key"]
  2. 从 EnvironmentFile 读 HTTPS_PROXY -> 所有网络请求走代理
  3. 从 --config 读 config.live.json -> 知道跟哪些钱包、bankroll 多少
  4. 合并两者 -> 完整配置
```

简单说：

| 文件               | 内容                       | 在哪                          | 谁读                                       |
| ------------------ | -------------------------- | ----------------------------- | ------------------------------------------ |
| `config.live.json` | 跟哪些钱包、金额、风险参数 | `/opt/winning-wallet-finder/` | `--config` 参数传给 copybot.py             |
| `live.env`         | 私钥、代理、确认短语       | `/etc/copybot/`               | systemd `EnvironmentFile` 自动注入环境变量 |

**为什么要分开**：config.live.json 可以 commit 到 Gitea（里面没有密钥），live.env 绝不能 commit（里面有私钥）。

## 每个服务的完整启动链

```
paper bot:
  systemd -> copybot.py --config copybot.paper.json
           + EnvironmentFile: paper.env (ALCHEMY_RPC_URL)
           = 直连 Polymarket，不走代理

live bot:
  systemd -> copybot.py --config config.live.json --live
           + EnvironmentFile: live.env (LIVE_PRIVATE_KEY + HTTPS_PROXY + TRADE LIVE)
           = 走代理绕 geo-block，真钱下单

dashboard:
  systemd -> serve_dashboard.py
           = 读本地 JSON 文件（不涉及 Gitea），网页展示
           - /api/feed  -> live/copybot_live.json (paper)
           - /api/live  -> live/copybot_live_real.json (live)
           - /          -> live/bot_dashboard.html (中文界面)

mihomo:
  systemd -> mihomo -d /etc/mihomo
           = 代理服务，监听 127.0.0.1:7890
```

## bot 自动推 Gitea（自动备份 + 远程可读）

bot 和 daily.sh 会自动把状态文件 commit + push 到 Gitea，**不需要你手动操作**。

### 推什么

| 推什么 | 频率 | 谁推的 | 目的 |
|---|---|---|---|
| `copybot_state.json` / `copybot_state.live.json` | 有状态变化时（节流） | bot 自动（`publish_feed()`） | 备份账本 |
| `live/copybot_live.json` / `live/copybot_live_real.json` | 有状态变化时 | bot 自动 | 备份 feed |
| `copybot_fills.jsonl` / `copybot_fills.live.jsonl` | 有成交时 | bot 自动 | 备份成交记录 |
| `live/watch_sharps.json` + `watch_skilled.json` + `dashboard.html` + `portfolio.json` | 每天 1 次 | daily.sh 自动 | 刷新钱包池 |

### 认证方式

wwf 用户的 `.netrc` 文件（`/opt/winning-wallet-finder/.netrc`）里有 Gitea PAT，git push 时自动读，不需要手动输密码。

### git log 里你会看到

```
copybot: live paper feed [skip ci]    ← bot 自动推的（每次有状态变化）
fix: re-query order after cancel...    ← 你手动推的（改代码）
docs: add 日常使用.md...               ← 你手动推的（改文档）
```

`[skip ci]` 表示跳过 CI（这个项目没配 CI，只是习惯标记）。

### 这导致的副作用

你在本地改完代码 `git push` 时经常被拒：

```
! [rejected] main -> main (fetch first)
```

因为服务器上的 bot 已经自动 commit 了新的 state。解决方法：

```bash
# 方法 1：rebase（推荐，保留你的改动）
git pull --rebase --autostash origin main && git push origin main

# 方法 2：强制用远程版本（会丢本地未提交的改动，慎用）
git fetch origin && git reset --hard origin/main
```

### 远程读 JSON（不用 SSH 上服务器）

Gitea 提供 raw URL，浏览器直接访问就能看 bot 的实时数据：

```
# paper bot feed
https://code.aifunny.ltd/gavindiaz/winning-wallet-finder/raw/branch/main/live/copybot_live.json

# live bot feed
https://code.aifunny.ltd/gavindiaz/winning-wallet-finder/raw/branch/main/live/copybot_live_real.json

# 钱包候选列表
https://code.aifunny.ltd/gavindiaz/winning-wallet-finder/raw/branch/main/live/watch_sharps.json
```

> 但 raw JSON 不好看，建议还是用 dashboard（`http://45.207.206.36:18080/`）。

### ⚠️ 注意：本地 Clash 会拦截直连 IP

如果你本地开了 Clash Verge 系统代理，访问 `http://45.207.206.36:18080/` 可能被拦截（Clash 把直连 IP 也走了代理）。解决方法：

1. **关掉 Clash 系统代理**（最简单）
2. 或在 Clash 规则里加 `IP-CIDR,45.207.206.36/32,DIRECT`（让这个 IP 直连不走代理）

---

## 你日常改东西去哪改

| 想改什么                    | 改哪个文件                                 | 在哪                          |
| --------------------------- | ------------------------------------------ | ----------------------------- |
| 跟哪些钱包 / 金额           | `copybot.paper.json` 或 `config.live.json` | `/opt/winning-wallet-finder/` |
| 私钥 / 代理地址 / 确认短语  | `live.env`                                 | `/etc/copybot/`               |
| Alchemy RPC                 | `paper.env`                                | `/etc/copybot/`               |
| bot 程序参数（poll 间隔等） | `.service` 文件                            | `/etc/systemd/system/`        |
| 代理节点 / 订阅             | `config.yaml`                              | `/etc/mihomo/`                |
| 每日 cron 时间              | `crontab -e`                               | 系统                          |
| dashboard 端口              | `.service` 文件加 `Environment="PORT=xxx"` | `/etc/systemd/system/`        |

改完任何东西记得 `systemctl restart 对应服务`。