dpl/docs/maintenance/NODE_BATCH_RUNTIME.md
hellcat e26b5bc6ca
Some checks are pending
Docker Build and Publish / build (push) Waiting to run
fix: finalize acceptance identity and raw QA review evidence
2026-10-17 15:32:03 +08:00

13 KiB
Raw Blame History

批量接入正式适配器

本专题是后续批量接入的默认技术入口。适配器实现、隔离测试及文档于2026-10-17完成;本轮没有新增线上节点、部署新运维版本或运行真实SSH/客户端验收。上一轮三节点实际验收使用的是已冻结旧任务适配器,不能套用为新实现的生产验收。

batch.py保留原命令适配器兼容,新计划默认用configure-site.py生成runtime.kind=ssh-site-v1。旧计划、账本、任务handler一律保留,不修改源码摘要后直接续跑旧任务。生产操作仍须具体接入授权、独立助手审核、Git运维部署及现场验收。

文件与职责

文件 职责
configure-site.py 本地合并明确站点参数,固定解释器、Paramiko版本、源码与非秘密配置摘要;不联网、不写账本、不覆盖旧计划
site_adapter.py 复用正式stage/provision/promote流程;按国家、组、批次和路径参数执行,按需连接服务器,进行动态正式订阅验收
ssh_pool.py 线程内复用白色机及下游SSH会话;下游强制通过白色机direct-tcpip,不提供直连回退
panel-acceptance.php 开工前选择有效正式组账号;缺少时创建本批受限非管理员测试用户,记录归属并收尾禁用
acceptance.py 动态集合、逐节点结果、原始证据摘要、QA身份/禁用/拒绝/下发排除和时效的完整自动审计

准备与Git部署

先按CoDocs确认VPS、白色机访问路线、正式组、展示编号及资源冲突。Python执行环境使用requirements.txt的Paramiko 5.0.0;配置与执行必须使用同一解释器。安装来源限官方包源/已审核环境,不临时导入任务目录中的未知transport模块。

生产前先提交并推送源码,再生成、推送和部署独立运维Git版本:保留原安装脚本,加入panel-acceptance.php及其operations.php,还有stage/promote/rollback/readiness/verify-node入口;面板、网关和目标节点的对应运维目录必须为计划中完整provision_commit且无工作区差异。配置文件和凭据不入Git。独立快照由正式bundle-ops.py生成,保持基础运维树、工作目录和真实Git索引不变,仅复制10个明确的已提交运维文件,默认拒绝已有分支或bundle,不联网:

python3 scripts/node-onboarding/bundle-ops.py --source FULL_SOURCE_COMMIT --base FULL_OPS_BASE_COMMIT --branch codex/node-tools-BATCH --bundle /private/task/provision.bundle

同一运维分支的后续修订只有显式--advance才允许,且--base必须精确等于当前分支头;新提交以该base为父并通过旧值比较更新,仅快进,不强推或覆盖并行修改。bundle仍要求新路径。随后按已授权任务推送返回的运维分支、审核commit/bundle摘要,再经白色机部署到明确版本。当前源码/运维快照的提交推送与远端部署分别报告;本轮不自动切换任何生产应用或旧任务的运维目录。

适配器在stage前检查本地依赖、远端Git版本及工具、正式组和账号、节点既有配置、独立端口、原服务、证书归属与钩子。既有证书无本任务owner、全局证书钩子或目标证书的额外pre/post钩子均停止。缺依赖须在准备阶段解决,不先创建一批面板对象后再临时安装工具。

本地参数文件

全部放在权限700的任务目录,文件600。基础计划沿用NODE_BATCH,包括本批任意数量、任意已支持国家的候选节点;不固定五节点、日本、组1或任务日期。新增站点配置JSON如下,示例路径必须换为实际已核实值:

{
  "ops_root": "/opt/dpl-batch/provision",
  "node_root": "/opt/dpl-node/provision",
  "batch_backup_root": "/root/codex-backups/dpl/BATCH",
  "node_backup_root": "/root/codex-backups/dpl/BATCH-nodes",
  "qa_root": "/var/lib/dpl-sgw/BATCH",
  "provider_snapshot": "/private/task/providers.json",
  "exit_registry": "/private/task/exits.json",
  "ssh_config": "/private/task/ssh.json",
  "preserved_service": {
    "unit": "XrayR", "binary": "/usr/local/XrayR/XrayR",
    "config": "/etc/XrayR/config.yml", "port": 443
  },
  "formal_socks_port": 19203,
  "client_sha256": "填已核验官方客户端文件的64位SHA256",
  "gateway_containers": ["dpl-subscribe-gateway", "dpl-subscribe-admin", "subscribe-gateway", "subscribe-admin"]
}

批次备份目录必须独立且固定,不跨实际批次复用旧资源基线。新节点端口、正式客户端端口均查重预留;共享网关客户端还使用同一账本内的进程锁。当前适配器用于独立AnyTLS节点及明确列出的既有服务;不自动把白色机、面板或网关当节点宿主。

ssh.json含known_hosts绝对路径及hosts映射,key为准确CoDocs VPS ID。每台登记host、port、username、用户已批准的host_key_sha256(OpenSSH SHA256格式)、可选credential_ref;白色机额外登记role=white_jump,ID须与context.jump_vps一致。候选IP必须与冻结计划相同。credential_ref只引用既定安全存储中的600文件。默认JSON支持password/key_filename/passphrase;既有CoDocs Markdown可设credential_format=codocs-password,仅按原文件中唯一的“密码:”字段在内存读取,不能为了适配而复制或改写原凭据正文。内容不得进入计划、日志、argv或Git。配置和出口登记摘要固定,凭据只按需读取,不默认自动轮换。

只从known-hosts文件选择与冻结配置中已批准指纹完全相符的公钥;找不到匹配时在读取/发送凭据前停止。全局known-hosts增加无关记录无需改变本批受信任指纹。不关闭主机密钥检查,不自动TOFU;首次信任先按访问规则核验/批准精确指纹,保存公开known-host条目。会话丢失只标记未知,不自动重发安装/开放命令;下次仍从现场probe恢复。

exits.json按SNI登记独立已知预期,例如:

{"node.example.invalid":{"expected_exit":"192.0.2.11","country":"JP"}}

覆盖该账号当前应获得的全部旧节点与新候选,不能从本次实测结果反填预期来让检查通过。增加或改变节点时,按CoDocs/授权现场证据更新新任务的登记;遇到未知预期或不支持协议停止,不能静默省略。当前站点适配器针对IPv4 A记录、固定端口AnyTLS;端口范围或其他协议停止而非静默省略,扩展需另补验证能力。

providers.json是实际已登记后台UI或授权官方API的近期观察,最多900秒有效,不使用语言相关DOM字符串:

{
  "captured_at": 0,
  "dns": [{"type":"A","name":"node.example.invalid","address":"192.0.2.11","proxy":false}],
  "relays": [{"name":"dpl-new-anytls","host":"relay.example.invalid","port":47011,"target":"192.0.2.11","listen_port":24443,"proxy_protocol_in":false,"proxy_protocol_out":false}]
}

captured_at填实际采集Unix时间。观察文件可以刷新,不能把静态勾选当现场证据。资源存在但参数/归属不符停止;尚未创建的DNS/中转仍按注册后台批量操作、更新观察后续跑。本实现没有新增Cloudflare/中转官方API写入权限,也不默认直连其源站。

执行与等待

/path/to/reviewed/python scripts/node-onboarding/configure-site.py --plan /private/task/base.json --site /private/task/site.json --output /private/task/frozen.json
/path/to/reviewed/python scripts/node-onboarding/batch.py plan --plan /private/task/frozen.json --state-root /private/task/ledger
/path/to/reviewed/python scripts/node-onboarding/batch.py run --plan /private/task/frozen.json --state-root /private/task/ledger --jobs 3 --timeout 900 --execute --events

配置/plan只做本地工作,不创建线上测试用户。run --execute进入准备前检时才允许准备验收账号,随后全批前检屏障通过才stage。--events输出无秘密的阶段状态JSON行,最后一行为批次结果;不加此参数保持原单JSON输出协议。账本events.jsonl仅追加状态变化,等待时使用进程输出/事件,不每几秒重复查全部状态。只有有新依赖证据、外部DNS/中转变化或用户恢复指令时续跑,不能无限重试。

SSH会话仅在本次进程、同一工作线程内复用,线程不共用可变SSH客户端;进程退出关闭连接,不建立后台守护进程。每个阶段通过lazy连接只访问必要主机。Paramiko channel有远端命令超时;进程内模式不承诺--timeout能杀死已发送远端命令,停止后仍须核实实际结果。

动态验收和测试用户

准备阶段先按正式组、banned、额度和有效期选既有账号,优先管理员且最多检查20个候选,避免全表巡检客户凭据;检查真实ServerService::getAvailableServers及getAvailableUsers,包括插件过滤;候选被实际节点下发过滤时不算可用。未找到可用账号时自动创建本任务测试用户:非管理员、100MiB、1小时、1设备、无订单/套餐改写及通知消息。私有journal在DB提交前记录身份,丢失回包时恢复同一用户,不重复创建或接管同名用户。

新节点仍沿用各自隔离QA→严格TLS/出口/上报→独立助手按精确报告审核→append开放并禁用QA。适配器直接生成key-evidence.json,包含原始报告字节SHA256、请求摘要、QA身份、报告时间及最新在线/双向上报状态,600原子保存,不再需要任务专用采集脚本。助手完成独立审核后,主协调者在同目录记录key-reviewed.json:request_sha256、report_sha256、qa_user_id、verified_at必须与该报告完全相同;未审核不得自动填批准文件。全部本组候选开放后,正式验收按账号当前实际应获得的集合生成完整快照,与客户端完整订阅逐条校验,并逐条实际联网。集合绑定节点ID、标签、端点、SNI、独立预期出口/国家;验收后再取一次集合,增加、删除或改变任一项均阻止旧报告通过,不固定节点数。旧节点同样必须覆盖。

每组正式客户端串行运行,保留完整订阅DNS/路由,只给诊断目的地固定出站。新建测试用户可能等待节点拉取用户列表;只对通用客户端运行失败最多做3轮诊断读测试,间隔15秒且共用900秒预算。结构、TLS或出口断言错误不重试;不重放生产写入、不另建用户来绕过失败。证据绑定完整计划摘要、节点集合摘要、原始报告及QA拒绝文件SHA256、QA实际ID、403、banned、实际下发排除及900秒时效。任一缺项或摘要/集合变化失败。旧节点关键配置、全部套餐、原用户关联和订阅地址按批次原始基线核对,网关容器ID/启动时间/重启数再次核对。

正式验收成功或失败都禁用本任务新建的正式测试用户;既有账号不被禁用或改密码/权限。进程正常退出在finally收尾,即使等待或部分前检失败也尝试清理;遇到断网/SIGKILL无法保证立即清理,记录的归属、1小时有效期与额度是兜底,恢复后先核对旧journal。已关闭用户绝不重新启用;下一次需要用户时创建新受限租约,保留历史并在最终审计核对其403/下发排除。完成要求由服务端实际集合/订阅拒绝证明,不承诺立即踢断已经建立的客户端会话。

续跑与原服务调查

preflight/publish/audit始终现场执行。只有先前状态为done、收到真实前检依赖摘要、请求/代码/config未变、证据未过期,才可复用stage/dns/provision/relay/verify。前驱依赖变化使后续缓存失效;running/unknown/conflict绝不跳过。旧适配器不提供这些证据时仍按原逐阶段probe逻辑执行。生产写入仍先probe、持久化意图、apply后probe,不为速度跳过归属检查。

原服务保存最初unit/程序/配置SHA256、监听、active及PID/启动时间/重启数。配置/程序/监听改变或不可用停止;仅进程变化分类为“需调查”,不宣称是DPL导致,也不自动刷新基线。记录观察后由操作者及助手核查生命周期、OOM、操作时间线和稳定采样;认可时在本节点私有备份登记preserved-service-review.json,绑定最初/本次观察摘要、原因及证据SHA256。原基线保持;再次重启不匹配旧审核,须重新调查。

验证范围

新增Python测试覆盖动态1/2/7/23节点、集合变化/漏旧节点、出口/TLS/QA身份/摘要/时效、白色机跳转及严格密钥策略、线程会话复用/断线、依赖续跑及未知/过期状态、原服务精确调查回执、远端脚本与PHP构造。PHP隔离测试使用真实Eloquent/SQLite和ServerService,覆盖自动受限用户、既有管理员复用、真实下发、无效账号、journal恢复、关闭后新租约、归属漂移、插件排除及失败收尾;不加载.env/应用provider/Redis/HTTP。生产停机、失败回退、远端真实会话复用性能及十节点耗时尚未演练,不承诺固定提速倍数。

隔离回归入口(不使用项目默认PHPUnit配置):

python3 -B -m unittest discover -s tests/node-onboarding -p 'test_*.py' -q
php vendor/bin/phpunit --no-configuration tests/node-onboarding/PanelChangeIsolatedTest.php
php vendor/bin/phpunit --no-configuration tests/node-onboarding/AcceptanceUserIsolatedTest.php