Workflow
flowchart TD
%% =========================
%% Entry points
%% =========================
A1[systemd timer 触发<br/>OnBootSec=2min / OnUnitActiveSec=interval] --> A2[vmess-domain-rotator.service]
A2 --> A3[run_update_and_commit.sh config.json]
A4[手动执行<br/>bash scripts/run_update_and_commit.sh config.json] --> A3
A5[仅手动更新不走服务提交<br/>python3 scripts/domain_updater.py --config config.json] --> B1
%% =========================
%% domain_updater.py pipeline
%% =========================
subgraph U["domain_updater.py(域名选择主流程)"]
direction TB
B1[读取 config.json<br/>解析 output.runtime_dir] --> B2[读取 runtime/state.json<br/>last_good_domain]
B2 --> B3[请求 API<br/>api.url/method/headers/params/body/timeout]
B3 --> B4[解析候选域名<br/>parser.field_paths/json_paths/regex fallback]
B4 --> B5[域名过滤<br/>domain_filter.include_suffixes/exclude_regex]
B5 --> B6[记录级过滤<br/>record_filter.exclude_if_any<br/>contains/equals/regex]
B6 --> B7[解析评分记录<br/>scoring.records_path/ip_field/created_time_field/score_fields]
B7 --> B8[排序<br/>within_hours + prefer_lower/use_api_order]
B8 --> B9{healthcheck.enabled?}
B9 -- 是 --> B10[TLS 探测候选<br/>attempts/timeout_ms/port/tls_verify]
B9 -- 否 --> B11[跳过 healthcheck]
B10 --> B12[choose_domain]
B11 --> B12
B12 --> B13{是否选出域名?}
B13 -- 是 --> B16[status=ok]
B13 -- 否 --> B14{last_good_domain 存在?}
B14 -- 是 --> B15[使用 last_good_domain<br/>status=fallback_last_good]
B14 -- 否 --> BE1[报错并退出]
B15 --> B17[写 runtime/current_domain.txt]
B16 --> B17
B17 --> B18[写 runtime/current_domain.json]
B18 --> B19[写 runtime/substore_vars.json]
B19 --> B20[可选渲染 v2ray 模板<br/>v2ray.template_file/output_file/replace_token]
B20 --> B21[写 runtime/state.json]
B21 --> B22[可选 notify.command]
B22 --> B23[stdout 输出本次 JSON 结果]
BE1 --> BE2[写 state.json status=error]
BE2 --> BE3{last_good_domain 存在?}
BE3 -- 是 --> BE4[写 current_domain*.json/txt<br/>status=error_use_last_good]
BE4 --> BE5[notify.command + 输出 error_use_last_good]
BE3 -- 否 --> BE6[stderr 输出 error 并 exit 1]
end
%% updater 结果回到 wrapper
B23 --> C1
BE5 --> C1
%% =========================
%% run_update_and_commit.sh pipeline
%% =========================
subgraph W["run_update_and_commit.sh(runtime-state 自动提交/推送)"]
direction TB
C1[检查 runtime/current_domain.txt 存在且非空] --> C2{满足 git 环境?<br/>git存在+在仓库+HEAD有效}
C2 -- 否 --> C0[仅完成本地 runtime 更新并退出]
C2 -- 是 --> C3[确定 runtime_branch / push_remote / auth 选项]
C3 --> C4{当前分支是 runtime-state?}
C4 -- 是 --> C5[直接在当前仓库操作]
C4 -- 否 --> C6[创建临时 worktree]
C6 --> C7{runtime-state 分支存在?}
C7 -- 本地存在 --> C8[checkout 本地 runtime-state]
C7 -- 仅远程存在 --> C9[fetch 后 checkout]
C7 -- 都不存在 --> C10[创建 orphan runtime-state]
C8 --> C11
C9 --> C11
C10 --> C11
C5 --> C11[读取 runtime-state HEAD 的 runtime/current_domain.txt]
C11 --> C12{force_commit!=1 且 新旧域名相同?}
C12 -- 是 --> C13[skip git commit/push]
C12 -- 否 --> C14[同步 4 个 runtime 文件到目标 worktree]
C14 --> C15[git add runtime/*.txt/json]
C15 --> C16{有 staged 变化?}
C16 -- 否 且 force=0 --> C17[skip commit]
C16 -- 否 且 force=1 --> C18[allow-empty commit]
C16 -- 是 --> C19[正常 commit]
C18 --> C20[提交信息 manual: ...]
C19 --> C21[提交信息 chore: rotate preferred domain ...]
C20 --> C22{GIT_PUSH_ENABLED=1?}
C21 --> C22
C22 -- 否 --> C23[结束(仅本地提交)]
C22 -- 是 --> C24{有可用 remote?}
C24 -- 否且required=1 --> C25[exit 1]
C24 -- 否且required=0 --> C26[跳过 push]
C24 -- 是 --> C27[按认证方式 push<br/>credential helper 或 HTTP header/token]
C27 --> C28{push 成功?}
C28 -- 是 --> C29[结束]
C28 -- 否且required=1 --> C30[exit 1]
C28 -- 否且required=0 --> C31[记录失败并结束]
end
%% =========================
%% Consumers
%% =========================
B18 --> D1[runtime/current_domain.json 对外可读]
D1 --> D2[substore/operator_template.js 拉取 JSON]
D2 --> D3[scriptResourceCache 缓存 domain(默认 5 分钟)]
D3 --> D4[重写匹配节点 vmess server]
C29 --> E1[runtime-state 分支更新]
E1 --> E2[下游通过 raw/runtime-state/runtime/*.json 消费]