SubOnline 是一个无状态、单租户的 Mihomo/Clash 订阅转换服务。它把部署访问密钥、真实 YAML 订阅地址和可信 JavaScript 覆写地址全部在浏览器中加密,生成一个永久的 /sub?payload=... URL。Mihomo 每次请求该 URL 时,Netlify Function 都会获取最新上游、执行覆写并返回完整 config.yaml。
一键部署按钮要求本仓库已经公开发布在上述 GitHub 地址。若你部署的是 fork,请把按钮链接中的
repository改为自己的公开仓库 URL。
浏览器 Netlify Node Function 远端
│ GET /api/public-key │ │
│◄──────── SPKI 公钥 ──────────────│ │
│ 本地 RSA-OAEP + AES-GCM 加密 │ │
│ │ │
Mihomo ── GET /sub?payload=... ────►│ │
├── 获取当前 YAML ──────────►│
├── 获取当前覆写 JS ────────►│
│ 执行 main(config)、校验、序列化
Mihomo ◄──────── config.yaml ───────┤
- 没有数据库、Blob、账户、定时任务或生成结果缓存。
- 生成器后端只会收到公钥请求;三个敏感输入不会以明文提交、写入浏览器存储或放入页面 URL。
- 每次
/sub请求都会重新获取上游,成功前不会流式返回任何部分配置。 - 修改
SUBONLINE_PRIVATE_KEY或SUBONLINE_ACCESS_SECRET会让所有旧链接失效。
在可信设备上运行:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out subonline-private.pem
openssl rand -base64 32第一条命令生成 PKCS#8 RSA 私钥;第二条输出可用作高熵访问密钥。不要把 subonline-private.pem、访问密钥或 .env 提交到 Git。
部署向导会要求填写:
SUBONLINE_PRIVATE_KEY:subonline-private.pem的完整内容。Netlify 输入框不便粘贴多行时,可将换行写成字面量\n。SUBONLINE_ACCESS_SECRET:上一步生成的随机访问密钥。
项目的 netlify.toml 已配置 npm run build、.next 发布目录、Node.js 24 和模板环境变量。Next.js App Router 路由由 Netlify 的 OpenNext 适配器部署到 Node.js Functions,而不是 Edge Runtime。
部署完成后打开站点首页,输入同一个访问密钥、一个 HTTPS Mihomo/Clash YAML URL,以及一个 HTTPS JavaScript 覆写 URL,即可生成订阅链接。生成器不会自动访问新链接;由你复制并明确交给 Mihomo。
Netlify Deploy Preview 必须配置与生产环境不同的私钥和访问密钥。不要让预览部署复用生产机密。
把页面生成的 URL 填入订阅管理器,或在配置中作为 HTTP provider 使用:
proxy-providers:
subonline:
type: http
url: "https://your-site.netlify.app/sub?payload=..."
path: ./providers/subonline.yaml
interval: 3600源文档必须本身就是包含非空 proxies 数组的 Mihomo/Clash YAML。SubOnline v1 不解析 Base64 节点列表、VMess/VLESS URI 列表,也不合并多个订阅。
v1 实现 powerfullz/override-rules 所需的 Sub-Store 子集:
- 把 URL fragment 按查询字符串语义解析成全局
$arguments;重复键取最后一个值; - 获取脚本时明确移除 fragment;
- 要求脚本定义
main(config),支持同步或异步返回; - 返回值必须是普通对象,并包含非空
proxies数组。
例如:
https://cdn.jsdelivr.net/gh/powerfullz/override-rules@<commit>/convert.min.js#grouptype=1&full=true
覆写脚本以正常 Node.js 权限执行,并不是安全沙箱。它能读取 process.env(包括私钥和访问密钥)、访问网络并消耗计算资源。worker 和执行时限只是资源控制,不是安全隔离承诺。加密也无法保护你免受恶意或被篡改的覆写脚本影响。
优先使用 commit 固定、不可变版本或经过验证的 release/tag URL;不要使用不受信任的脚本或可被静默改写的分支/CDN 地址。脚本源一旦被入侵,应立即轮换两项部署机密并重新生成所有链接。
需要 Node.js 24。仓库通过 .node-version 固定主版本;本机使用 fnm 时:
fnm use
npm install
cp .env.example .env.local
npm run dev本地页面默认是 HTTP,而生成器按产品协议只接受 HTTPS 部署地址。浏览器端到端生成请使用 Netlify Preview 或本地 HTTPS 反向代理;核心协议和请求管线可直接通过测试验证。
npm run typecheck
npm test
RUN_NETWORK_CONTRACT=1 npm test -- test/powerfullz.contract.test.ts
npm run buildnpm run check 会依次运行类型检查、默认测试和生产构建。默认测试不依赖公网;固定到 v2.5.5 commit f3f6dfd2a4093994ff16e762da7f8ebbc77a13df 的 powerfullz/override-rules 契约测试需要显式开启网络。
测试覆盖 Base64URL 规范解析、RSA/AES 往返、随机密文、GCM 篡改、密钥与访问密钥轮换、URL/descriptor 校验、YAML 安全解析、fragment 参数、同步/异步脚本、错误脱敏、远端限制,以及带本地 YAML/脚本服务器的完整管线。
| 路由 | 行为 |
|---|---|
GET / |
公共静态生成器 |
GET /api/public-key |
返回由部署私钥推导的 SPKI PEM 公钥;Cache-Control: no-store |
GET /sub?payload=... |
验证、实时转换并返回 application/yaml; charset=utf-8 |
集中配置的 v1 默认限制:
- 加密 payload:16 KiB;解密 descriptor:8 KiB;
- 源 YAML:解码后 5 MiB;覆写脚本:解码后 1 MiB;
- 每次上游请求最多 3 次重定向、10 秒超时;每个重定向目标都重新检查 HTTPS 和嵌入凭据;
- 覆写执行 5 秒,整体请求预算 27 秒,Netlify Function
maxDuration为 30 秒; /sub始终Cache-Control: no-store;只允许转发subscription-userinfo和profile-update-interval两个安全元数据头。
所有公开错误都使用稳定错误码和简短消息,不包含 URL、payload、上游正文、生成 YAML、stack trace 或环境内容。解密失败、GCM 认证失败和访问密钥错误统一返回 authentication_failed,不会透露具体原因。
server_misconfigured:确认两个环境变量均已设置,私钥是 RSA PKCS#8 PEM,并重新部署。authentication_failed:生成页面输入的访问密钥不匹配,或部署机密在链接生成后发生过轮换。source_fetch_failed/override_fetch_failed:检查 HTTPS 上游状态、重定向、超时和大小限制。invalid_source_yaml:源内容必须是 YAML mapping 且proxies非空。script_failed/invalid_script_output:脚本需提供main(config)并返回带非空proxies的普通对象。
完整产品与安全设计见 docs/spark/2026-07-10-subonline-design.md。