# vmess-domain-rotator

一个用于选择优选目标并写出运行时文件的工具集，当前支持两套独立模式：

- 云服务器模式：调用远程 API，选择优选域名，写入 `runtime/`，可自动提交到 `runtime-state`
- 本地路由器模式：调用本地 `cfst`，选择优选 IP，写入 `cfip_runtime/`；HTTP 暴露推荐使用项目目录下的完整 BusyBox `httpd`

这两套模式已经彻底拆开：

- 服务器模式使用 [`config.server.json`](./config.server.json)
- 本地 `cfst` 模式使用 [`config.router.json`](./config.router.json)
- BusyBox 路由器脚本使用 [`router_local.conf`](./router_local.conf)

旧的 `config.json` / `config.example.json` 已废弃，不再使用。

## 1. 目录与入口

核心脚本：

- `scripts/domain_updater.py`
  统一的 Python 主入口，支持 `api` 和 `cfst_local` 两种 `source.type`
- `scripts/run_update_and_commit.sh`
  服务器模式入口，执行 updater 并按配置同步运行时文件到 `runtime-state`
- `scripts/install_debian.sh`
  Debian/Ubuntu 一键安装 systemd service + timer
- `scripts/uninstall_debian.sh`
  卸载 systemd service + timer
- `scripts/router_local_update.sh`
  BusyBox `sh` 路由器入口，执行 `cfst` 并写出 `cfip_runtime`
- `scripts/router_local_http.sh`
  BusyBox `sh` 路由器 HTTP 暴露入口，优先使用 BusyBox `httpd` 暴露运行时文件
- `scripts/update_vmess_links.py`
  可选工具，用运行时文件里的值批量替换 `vmess://` 节点

配置文件：

- `config.server.json`
  服务器模式配置，输出默认是：
  - `runtime/current_domain.txt`
  - `runtime/current_domain.json`
  - `runtime/state.json`
  - `runtime/substore_vars.json`
- `config.router.json`
  本地 `cfst` 模式配置，输出默认是：
  - `cfip_runtime/current_ip.txt`
  - `cfip_runtime/current_ip.json`
  - `cfip_runtime/state.json`
  - `cfip_runtime/substore_vars.json`
- `router_local.conf`
  BusyBox 路由器脚本配置，给 `router_local_update.sh` / `router_local_http.sh` 使用

## 2. 共用设计

两套模式共用同一套“输出抽象”：

- 文本值文件：当前选中的值
- JSON 文件：当前结果
- state 文件：上次可用值和状态
- export vars 文件：给外部系统消费

关键点：

- 输出文件名和输出目录都从配置读取，不再在 shell 脚本里硬编码 `runtime/current_domain.txt`
- `run_update_and_commit.sh` 会先解析配置里的输出路径，再决定同步哪些文件
- 只有服务器模式默认集成 git commit/push；路由器模式不做 git 操作
- `domain_updater.py` 会按 `--config` 所在目录解析相对路径

## 3. 模式选择

### 3.1 云服务器模式

适用场景：

- 有 `python3`
- 有网络访问目标 API
- 需要 systemd 定时执行
- 需要自动提交 `runtime-state`

入口：

- 手动执行：`python3 scripts/domain_updater.py --config config.server.json`
- 定时执行：`bash scripts/run_update_and_commit.sh config.server.json`
- Debian 安装：`sudo bash scripts/install_debian.sh --config config.server.json`

### 3.2 本地 `cfst` 模式

适用场景：

- 在本机、Mac、Linux 上已有可执行的 `cfst`
- 希望由 `domain_updater.py` 直接调用本地 `cfst`
- 不需要 BusyBox 专用脚本

入口：

- `python3 scripts/domain_updater.py --config config.router.json`

### 3.3 BusyBox 路由器模式

适用场景：

- 路由器没有 Python
- 只有 BusyBox `sh` / `awk` / `sed` 等基础工具即可执行更新脚本
- HTTP 暴露推荐使用项目目录下的完整 BusyBox，例如 `BUSYBOX_BIN="./busybox_armv7l"`，脚本会调用它的 `httpd` applet；系统自带精简 `nc` 不够

入口：

- 更新结果：`sh scripts/router_local_update.sh ./router_local.conf`
- 暴露 HTTP：`sh scripts/router_local_http.sh ./router_local.conf`

## 4. 云服务器模式部署

### 4.1 前置条件

- Debian/Ubuntu
- `git clone` 后在仓库目录执行
- 远程仓库已配置，例如 `origin`
- 运行用户对仓库有读写权限
- 机器上可访问 `config.server.json` 中的 API

### 4.2 核心配置文件

服务器模式固定使用 [`config.server.json`](./config.server.json)。

它当前默认行为：

- `source.type = "api"`
- 通过 `api.url` 拉取候选结果
- 通过 `parser`、`record_mapping`、`record_filter`、`scoring` 选择目标
- 写出到 `runtime/`

你通常只需要修改：

- `api.url`
- `api.headers`
- `parser`
- `record_mapping`
- `record_filter`
- `scoring`
- `healthcheck`

### 4.3 本地手动跑通

先做语法检查：

```bash
env PYTHONPYCACHEPREFIX=/tmp/pycache python3 -m py_compile scripts/domain_updater.py
```

执行一次：

```bash
python3 scripts/domain_updater.py --config config.server.json
```

查看结果：

```bash
cat runtime/current_domain.txt
cat runtime/current_domain.json
cat runtime/state.json
cat runtime/substore_vars.json
```

如果你只想看脚本解析出的输出路径：

```bash
python3 scripts/domain_updater.py --config config.server.json --print-output-settings
```

### 4.4 自动提交到 runtime-state

服务器模式提交脚本：

```bash
bash scripts/run_update_and_commit.sh config.server.json
```

它会执行：

1. 调用 `domain_updater.py`
2. 从配置解析出运行时文件路径
3. 比较本次“选中值”和 `runtime-state` 分支上次记录
4. 值未变化时跳过 commit/push
5. 值变化时同步配置定义的输出文件并提交

强制提交：

```bash
bash scripts/run_update_and_commit.sh --force-commit config.server.json
```

或：

```bash
GIT_FORCE_COMMIT=1 bash scripts/run_update_and_commit.sh config.server.json
```

### 4.5 Debian 一键安装

推荐命令：

```bash
sudo bash scripts/install_debian.sh --config config.server.json
```

默认行为：

- 使用当前 `sudo` 前的用户作为 service 用户
- 定时周期 `1h`
- 自动 push 开启
- 目标分支 `runtime-state`

常用参数：

```bash
sudo bash scripts/install_debian.sh \
  --config config.server.json \
  --interval 10min \
  --git-push 1 \
  --git-push-remote origin
```

如果用 token：

```bash
sudo bash scripts/install_debian.sh \
  --config config.server.json \
  --git-http-username <your-user> \
  --git-http-token-file /root/.config/vmess-token \
  --git-use-credential-store 1
```

### 4.6 安装后验证

```bash
sudo systemctl status vmess-domain-rotator.timer
sudo systemctl status vmess-domain-rotator.service
sudo systemctl start vmess-domain-rotator.service
sudo journalctl -u vmess-domain-rotator.service -n 120 --no-pager
```

成功时通常应看到：

- updater 输出 JSON
- `committed output changes on runtime-state`
- `pushed to origin/runtime-state`

### 4.7 卸载

```bash
sudo bash scripts/uninstall_debian.sh
```

保留认证文件：

```bash
sudo bash scripts/uninstall_debian.sh --keep-auth-files
```

## 5. 本地 `cfst` 模式部署

### 5.1 适用环境

- 有 `python3`
- 有可执行的 `cfst`
- 不依赖 BusyBox
- 希望继续复用 `domain_updater.py` 的统一输出逻辑

### 5.2 核心配置文件

本模式使用 [`config.router.json`](./config.router.json)。

它当前默认指向：

- `./cfst_darwin_arm64/cfst`
- 结果文件 `./cfst_darwin_arm64/result.csv`
- 输出目录 `./cfip_runtime`

你通常只需要修改：

- `cfst_local.work_dir`
- `cfst_local.binary`
- `cfst_local.run_args`
- `cfst_local.result_file`
- `cfst_local.columns`
- `output.*`

### 5.3 手动执行

```bash
python3 scripts/domain_updater.py --config config.router.json
```

查看结果：

```bash
cat cfip_runtime/current_ip.txt
cat cfip_runtime/current_ip.json
cat cfip_runtime/state.json
cat cfip_runtime/substore_vars.json
```

查看解析出的输出路径：

```bash
python3 scripts/domain_updater.py --config config.router.json --print-output-settings
```

### 5.4 配置说明

`cfst_local` 关键项：

- `work_dir`
  `cfst` 工作目录
- `binary`
  `cfst` 可执行文件路径
- `run_args`
  执行参数数组，例如 `-f ip.txt -o result.csv -p 10`
- `result_file`
  `cfst` 输出结果文件
- `skip_run`
  为 `true` 时不执行 `cfst`，只解析现有结果文件
- `columns`
  CSV 列映射，默认按：
  - `0` IP
  - `1` 已发送
  - `2` 已接收
  - `3` 丢包率
  - `4` 平均延迟
  - `5` 下载速度
  - `6` 地区

## 6. BusyBox 路由器模式部署

### 6.1 适用环境

- 路由器没有 Python
- 已准备对应架构的 `cfst`
- 已准备完整 BusyBox，例如本项目默认使用 `busybox_armv7l`
- HTTP 暴露使用完整 BusyBox 的 `httpd` applet，不使用 ASUS 固件自带 `/usr/sbin/httpd`

架构以目标路由器实际输出为准：

```bash
uname -m
```

`cfst` 和完整 BusyBox 都必须匹配路由器架构。ASUS RT-AC68U 示例里，完整 BusyBox 文件名使用 `busybox_armv7l`。

### 6.2 路由器目录建议

示例（ASUS RT-AC68U 推荐放在 `/jffs/vmess`）：

```text
/jffs/vmess/
├── busybox_armv7l
├── router_local.conf
├── scripts/
│   ├── router_local_update.sh
│   └── router_local_http.sh
└── cfst/
    ├── cfst
    ├── ip.txt
    └── result.csv
```

其中：

- `router_local.conf` 指定 `CFST_WORK_DIR`、输出目录、HTTP 端口、完整 BusyBox 路径等
- `busybox_armv7l` 是你下载的完整 BusyBox，HTTP 服务脚本会优先调用它，而不是系统自带 BusyBox/ASUS `httpd`
- `cfst/` 放路由器架构对应的 `cfst`

### 6.3 配置 router_local.conf

当前仓库里的 [`router_local.conf`](./router_local.conf) 就是 BusyBox 配置文件。

最重要的字段：

- `CFST_WORK_DIR`
  `cfst` 所在目录
- `CFST_BIN`
  `cfst` 可执行文件名，一般是 `./cfst`
- `CFST_IP_FILE`
  输入 IP 列表
- `CFST_RESULT_FILE`
  `cfst` 结果文件
- `RUNTIME_DIR`
  运行时输出目录，默认 `./cfip_runtime`
- `VALUE_TEXT_FILE`
  当前值文本文件，默认 `current_ip.txt`
- `VALUE_JSON_FILE`
  当前值 JSON 文件，默认 `current_ip.json`
- `BUSYBOX_BIN`
  完整 BusyBox 二进制路径，默认 `./busybox_armv7l`；HTTP 脚本会优先调用它的 `httpd` applet，避免误用 ASUS `/usr/sbin/httpd`
- `HTTP_PORT`
  局域网 HTTP 监听端口，默认 `8080`

### 6.4 手动更新一次

```bash
sh scripts/router_local_update.sh ./router_local.conf
```

成功时会输出类似：

```text
[router-local] selected ip: x.x.x.x
```

生成文件默认在：

- `cfip_runtime/current_ip.txt`
- `cfip_runtime/index.html`（复制当前 IP 文本，供 `/` 访问）
- `cfip_runtime/current_ip.json`
- `cfip_runtime/state.json`
- `cfip_runtime/substore_vars.json`

### 6.5 暴露到局域网

```bash
sh scripts/router_local_http.sh ./router_local.conf
```

默认会监听：

```text
0.0.0.0:8080
```

这个脚本不再使用系统自带的精简 `nc` 或 ASUS `/usr/sbin/httpd`。它会读取 `router_local.conf` 里的 `BUSYBOX_BIN`，优先调用项目目录下完整 BusyBox 的 `httpd` applet：

```bash
./busybox_armv7l httpd -f -p 8080 -h ./cfip_runtime
```

如果你的 BusyBox applet 列表里没有 `httpd`，当前脚本会明确报错。ASUS 固件自带的 `/usr/sbin/httpd` 通常是路由器管理后台服务，不是 BusyBox 静态文件服务器；即使支持 `-p` 端口参数，也不代表支持 `-h ./cfip_runtime` 这类目录发布。

可访问路径：

- `/`（由 `index.html` 返回当前 IP 文本）
- `/current_ip.txt`
- `/current_ip.json`
- `/state.json`
- `/substore_vars.json`

例如局域网内访问：

```bash
curl http://192.168.50.1:8080/current_ip.json
curl http://192.168.50.1:8080/current_ip.txt
```

### 6.6 ASUS RT-AC68U services-start 自启动

如果路由器使用 `/jffs/scripts/services-start` 统一启动任务，可以用 `cru` 注册定时更新和 watchdog。这个小节是 ASUS RT-AC68U / KoolShare 风格固件专用示例，假设项目放在 `/jffs/vmess`，完整 BusyBox 位于 `/jffs/vmess/busybox_armv7l`。

部署前建议确认：

```sh
chmod +x /jffs/vmess/busybox_armv7l
chmod +x /jffs/scripts/services-start
/jffs/vmess/busybox_armv7l --list | grep '^httpd$'
```

`router_local.conf` 至少需要包含：

```sh
BUSYBOX_BIN="./busybox_armv7l"
HTTP_PORT="8080"
```

把 `/jffs/scripts/services-start` 写成：

```sh
#!/bin/sh
/koolshare/bin/ks-services-start.sh

VMESS_DIR="/jffs/vmess"
CONFIG="./router_local.conf"
UPDATE_LOG="/tmp/router_local_update.log"
HTTP_LOG="/tmp/router_http.log"

start_vmess_http() {
  if ps | grep -v grep | grep -q 'router_local_http.sh'; then
    return 0
  fi

  cd "$VMESS_DIR" || exit 1
  nohup sh scripts/router_local_http.sh "$CONFIG" >> "$HTTP_LOG" 2>&1 &
}

cd "$VMESS_DIR" || exit 1
sh scripts/router_local_update.sh "$CONFIG" >> "$UPDATE_LOG" 2>&1

cru d vmess_rotate
cru a vmess_rotate "0 */3 * * * cd $VMESS_DIR && sh scripts/router_local_update.sh $CONFIG >> $UPDATE_LOG 2>&1"

sleep 10
start_vmess_http

cru d vmess_watchdog
cru a vmess_watchdog "*/5 * * * * if ! ps | grep -v grep | grep -q 'router_local_http.sh'; then cd $VMESS_DIR && nohup sh scripts/router_local_http.sh $CONFIG >> $HTTP_LOG 2>&1 & fi"
```

`router_local_http.sh` 会保留 wrapper 进程并等待子进程里的完整 BusyBox `httpd`，所以 watchdog 用 `router_local_http.sh` 作为进程关键字可以正常判断服务是否仍在运行。示例里先 `cru d` 再 `cru a`，避免 `services-start` 被重复触发后留下旧的定时任务定义；启动 HTTP 前也会先检查进程，避免重复监听同一个端口。

### 6.7 定时执行

可以用 BusyBox `crond` 定时更新，例如每 15 分钟执行一次：

```cron
*/15 * * * * cd /tmp/home/root/vmess-domain-rotator && sh scripts/router_local_update.sh ./router_local.conf >> /tmp/router_local_update.log 2>&1
```

### 6.8 手动常驻 HTTP

HTTP 服务如果需要手动后台启动：

```bash
cd /tmp/home/root/vmess-domain-rotator
nohup sh scripts/router_local_http.sh ./router_local.conf >/tmp/router_http.log 2>&1 &
```

## 7. VMess 链接批量替换

如果下游要消费“当前值文件”，可使用 `scripts/update_vmess_links.py`。

服务器模式示例：

```bash
python3 scripts/update_vmess_links.py \
  --input ./nodes.txt \
  --output ./nodes.updated.txt \
  --domain-file ./runtime/current_domain.txt
```

路由器/本地 `cfst` 模式示例：

```bash
python3 scripts/update_vmess_links.py \
  --input ./nodes.txt \
  --output ./nodes.updated.txt \
  --domain-file ./cfip_runtime/current_ip.txt
```

仅替换匹配名称的节点：

```bash
python3 scripts/update_vmess_links.py \
  --input ./nodes.txt \
  --output ./nodes.updated.txt \
  --domain-file ./runtime/current_domain.txt \
  --name-regex "(argo|cf|vm)"
```

## 8. 常用运维命令

服务器模式：

```bash
sudo systemctl status vmess-domain-rotator.timer
sudo systemctl status vmess-domain-rotator.service
sudo systemctl start vmess-domain-rotator.service
sudo journalctl -u vmess-domain-rotator.service -f
git log runtime-state --oneline -n 20
```

路由器模式：

```bash
sh scripts/router_local_update.sh ./router_local.conf
sh scripts/router_local_http.sh ./router_local.conf
cat cfip_runtime/current_ip.txt
cat cfip_runtime/current_ip.json
```

## 9. 注意事项

1. 服务器模式和路由器模式的配置不要混用。
2. `run_update_and_commit.sh` 设计目标是服务器模式；路由器模式默认不走 git 提交。
3. 服务器模式下，service 用户和 git 凭证用户必须一致，否则会出现 `terminal prompts disabled`。
4. `credential.helper store` 是明文存储，只适合可控服务器。
5. BusyBox 路由器模式下，`router_local_update.sh` 只依赖常见基础 applet；HTTP 暴露会优先调用 `BUSYBOX_BIN` 指向的完整 BusyBox `httpd`，不要依赖 ASUS `/usr/sbin/httpd` 或系统精简 `nc`。
6. `state.json` 需要持久化，否则 fallback 不可用。
