Skip to content

Repository files navigation

SubOnline

SubOnline 是一个无状态、单租户的 Mihomo/Clash 订阅转换服务。它把部署访问密钥、真实 YAML 订阅地址和可信 JavaScript 覆写地址全部在浏览器中加密,生成一个永久的 /sub?payload=... URL。Mihomo 每次请求该 URL 时,Netlify Function 都会获取最新上游、执行覆写并返回完整 config.yaml

Deploy to Netlify

一键部署按钮要求本仓库已经公开发布在上述 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_KEYSUBONLINE_ACCESS_SECRET 会让所有旧链接失效。

一键部署到 Netlify

1. 生成密钥和访问密钥

在可信设备上运行:

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。

2. 点击部署按钮

部署向导会要求填写:

  • SUBONLINE_PRIVATE_KEYsubonline-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 必须配置与生产环境不同的私钥和访问密钥。不要让预览部署复用生产机密。

Mihomo 使用示例

把页面生成的 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 build

npm run check 会依次运行类型检查、默认测试和生产构建。默认测试不依赖公网;固定到 v2.5.5 commit f3f6dfd2a4093994ff16e762da7f8ebbc77a13dfpowerfullz/override-rules 契约测试需要显式开启网络。

测试覆盖 Base64URL 规范解析、RSA/AES 往返、随机密文、GCM 篡改、密钥与访问密钥轮换、URL/descriptor 校验、YAML 安全解析、fragment 参数、同步/异步脚本、错误脱敏、远端限制,以及带本地 YAML/脚本服务器的完整管线。

API 与限制

路由 行为
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-userinfoprofile-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

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages