v4 升级指南
Surgio v4 将配置仓库统一为原生 TypeScript ESM Project,并让本地 CLI、Node Gateway 和 Cloudflare Worker 可以共用同一份配置。由于配置入口、Provider 加载、缓存和 Gateway 都有变化,建议把升级当作一次有基线、有验证的迁移,而不是只修改 package.json 中的版本号。
Surgio v4 正在 beta 测试,发布在 npm 的 beta tag 下,latest 仍指向 v3。兼容 v4 的 @surgio/gateway 同样发布在 beta tag 下。安装时需要显式使用 @beta,见升级依赖和运行时。
v4 新特性
除了统一的 Project 和 Worker 运行时,v4 还扩展了以下客户端的输出能力。它们不需要迁移操作,升级后即可在模板中使用。
Egern
新增 getEgernNodes 和 getEgernNodeNames,用于在 YAML 模板中生成 Egern 的 proxies 和 policy_groups。支持 Shadowsocks、Snell v1~v5、Trojan、AnyTLS、Hysteria2、TUIC v5、SOCKS5、HTTP(S)、Vmess、Vless 和单 Peer 的 WireGuard 节点,Egern 无法表达的节点会告警并被忽略。
Gateway 的直接导出 Provider 接口新增 format=egern,可以在 Egern 的 external 策略组中引用。Provider 中可以用 utils.isEgern(useragent) 按客户端返回不同节点。
v2rayN
getV2rayNNodes 在 v3 中只输出 VMess 节点,v4 支持 VMess、Shadowsocks、SOCKS5、VLESS、Trojan、Hysteria2、TUIC、WireGuard、AnyTLS 和 HTTP(S)。分享链接无法表达的字段会通过 logger 告警,不会静默丢弃节点。
Surfboard
Surfboard 节点输出按 Surfboard mobile 2.34.4+ 重写,新增 Snell、AnyTLS、Hysteria2、TUIC v5、SOCKS5 和 WireGuard 节点。WireGuard 需要在模板中用 getSurfboardWireguardNodes 生成独立的 [WireGuard ...] 配置段,并与 getSurfboardNodes 使用相同的节点列表和过滤器。VLESS、TUIC v4、Reality 等不支持的组合会告警并省略。
Loon
- Shadowsocks(包括 SS2022)节点支持 Shadow TLS 参数,VMess 节点会输出
alterId。 getLoonNodes和getLoonNodeNames共用同一套兼容性判断,不支持的 VMess/VLESS 传输和 Shadowsocks 混淆节点会从两者中一起省略,策略组不会引用不存在的节点。- 规则转换新增
IP-CIDR6、IP-ASN、SRC-PORT、DEST-PORT、PROTOCOL以及AND、OR、NOT逻辑规则。端口、协议和逻辑规则需要 Loon 3.1.7 或更新版本。 - 行内注释只在前面有空白时才会被去掉,URL 和正则中的
//不再被误删。
sing-box
新增 getSingboxRules 和 getSingboxHeadlessRules,把 Surge 格式的规则文本转换为 sing-box 的 route.rules 或 rule-set 文件。配合新增的 extendRoute、extendDns、extendInbounds 和 extendRuleSet,可以在 JSON 模板中直接复用现有的规则片段。.tpl 模板可以使用 singbox filter 输出 JSON 规则片段。用法参见 sing-box 规则维护指南。
sing-box 1.14.0 及以上版本支持 Snell v4 和 v6 节点,节点需要显式设置 version。v5 会告警并按 v4 输出,其它限制见 Snell 节点配置。
WireGuard 节点改为输出到 endpoints,需要 sing-box 1.11 或更新版本,旧模板需要调整,见 sing-box WireGuard 节点。
升级前准备
Surgio v4 要求 Node.js >=22.22.2。Project 直接由 Node.js 运行可擦除的 TypeScript 语法,不需要 Bun、tsx、ts-node 或其它运行时编译器。
开始前先确认工作区干净,并保存 v3 的生成结果:
git status --short
pnpm gen
find dist -type f -exec shasum -a 256 {} + | LC_ALL=C sort \
> /tmp/surgio-v3.sha256
如果仓库提供 Gateway、Lambda 或其它服务端入口,还应保存至少一个代表性请求的状态码、响应 headers 和 body。迁移完成并验证一致之前,不要删除旧配置,也不要直接提交批量生成的差异。
使用迁移 Skill(推荐)
仓库提供了 migrate-v3-config Skill,可以让 coding agent 盘点现有配置和部署方式、保存基线、执行 TypeScript 迁移并验证结果。使用 Vercel 的 open agent skills CLI 安装,不依赖某个 agent 自带的 Skill 安装功能;该工具支持 Claude Code、Codex、Cursor、OpenCode 等多种 coding agent。
1. 安装 Skill
在 Surgio 配置仓库根目录运行:
npx skills add geekdada/surgio --skill migrate-v3-config
CLI 会检测本机安装的 coding agent,并在需要时让你选择目标。默认执行项目级安装,适合随配置仓库共享;若希望在所有仓库中使用,可以增加 --global:
npx skills add geekdada/surgio --skill migrate-v3-config --global
如果安装后没有出现在 agent 的 Skill 列表中,请重新启动该 agent。
2. 在配置仓库中运行
在所选 coding agent 中打开 Surgio 配置仓库,并从仓库根目录开始一个新任务。不同 agent 的显式 Skill 语法可能不同,因此下面使用通用的自然语言调用。显式告诉 Skill 是否保留当前部署方式;如果没有说明,它会先盘点 Node、Lambda、容器、Wrangler 等入口,再询问你要选择哪条路径。
只保留现有非 Worker 部署:
请使用 migrate-v3-config skill 将当前仓库升级到 Surgio v4,沿用现有非 Worker 部署方式。
先保存 Artifact 和服务响应基线,迁移后验证行为一致。
升级并且切换至 Cloudflare Worker 部署:
请使用 migrate-v3-config skill 将当前仓库升级到 Surgio v4,并切换至 Cloudflare Worker
部署、配置 bindings、Assets、路由和域名,并验证 workerd 与 Wrangler dry-run 结果一致。
同时支持本地/Node 与 Worker:
请使用 migrate-v3-config skill 将当前仓库升级到 Surgio v4,保留 Node Gateway 和
Cloudflare Worker 两种部署,让它们共用同一个 surgio.project.ts,并比较两端
代表性 Artifact 的输出。
Skill 不会因为 Surgio 支持 Worker 就擅自增加 Worker 部署,也不会强迫你把已有的字面量凭据改成环境变量。只有明确要求切换部署方式时,它才会删除旧 adapter 和部署脚本。完成后应审阅它报告的命令、测试数量、Artifact 哈希和真实运行时验证结果,不要只接受“TypeScript 编译通过”。
手动迁移
不使用 Skill 时,可以按以下顺序完成同样的迁移。
1. 升级依赖和运行时
将 Node.js 升级到 22.22.2 或更高版本,并在配置仓库中启用 ESM:
{
"type": "module",
"engines": {
"node": ">=22.22.2"
}
}
升级 Surgio。使用 Gateway 的项目还需要安装与 Surgio v4 兼容的 Gateway。v4 正式版发布前,两者都从 npm 的 beta tag 安装:
pnpm add surgio@beta
pnpm add @surgio/gateway@beta
pnpm add -D typescript @types/node
不使用 Gateway 时省略第二条命令。surgio@^4 这样的版本范围不会匹配 beta 预发布版本,@surgio/gateway@latest 会安装与 v4 不兼容的 Gateway v2。不要在配置仓库中依赖 Surgio 间接安装的 TypeScript;应把编译器列为直接开发依赖。
2. 建立唯一的 Project
把 surgio.conf.js、provider/ 中的 Provider 和 Artifact 注册合并到唯一的 surgio.project.ts。Surgio 也能识别 .mts、.mjs 和 .js,但同一目录中只能存在一个 Project 入口。
可以先让共享配置单独通过类型检查:
// config.ts
import type { SurgioProjectConfig } from 'surgio/project'
const config = {
artifacts: [
{
name: 'surge.conf',
provider: 'demo',
template: 'surge',
},
],
urlBase: 'https://example.com/',
} satisfies SurgioProjectConfig
export default config
然后建立 Project:
// surgio.project.ts
import {
defineClashProvider,
defineSurgioProject,
env,
type SurgioNodeOptions,
} from 'surgio/project'
import config from './config.ts'
export default defineSurgioProject({
...config,
providers: {
demo: () =>
defineClashProvider({
url: env('DEMO_SUBSCRIPTION_URL'),
}),
},
templateDir: './template',
})
export const nodeOptions = async (): Promise<SurgioNodeOptions> => ({
output: './dist',
cache: { type: 'filesystem' },
})
Surgio 配置字段直接位于 Project 顶层。providers 和 templateDir 是 Project 元数据,不要写成 { config, providers }。templateDir 可以省略,默认使用 ./template。
output、filesystem/Redis/Upstash cache 和 upload 只属于 Node 侧,必须放在具名导出的 nodeOptions() 中。Worker manifest 只读取默认导出,不会导入或序列化 nodeOptions()。
env(name) 从 process.env 读取必需的字符串,缺失时立即抛错。它只是语法糖;已有的凭据管理方式可以继续使用,不需要把所有字面量强制改成 env()。
3. 显式注册 Provider
为了兼容 Worker 的部署形式,新版 Surgio 将不再扫描 provider/,因此所有 Provider 都应显式注册到 Project 的 providers 对象。静态配置可以直接注册;需要运行时依赖时使用 Provider factory:
import {
defineClashProvider,
defineSurgioProject,
type ProjectProviderContext,
} from 'surgio/project'
const createDynamicProvider = async ({
cache,
httpClient,
logger,
}: ProjectProviderContext) => {
const url = await cache.wrap<string>(
'demo:subscription-url',
async () => {
const response = await httpClient.get('https://example.com/subscription')
return new URL(response.body.trim()).toString()
},
5 * 60 * 1000,
)
logger.debug('loaded dynamic subscription URL')
return defineClashProvider({ url })
}
export default defineSurgioProject({
artifacts: [],
providers: { dynamic: createDynamicProvider },
})
Provider factory 获得的 cache、HTTP 和 logger 会分别由 Node 或 Worker runtime 提供;不要访问 Node 全局缓存或自行创建另一套客户端。若读取外部 JSON,应为响应定义最小 interface 并校验未知数据,不要用 any 或双重断言绕过类型检查。
确认新 Project 能正常加载后,再删除 surgio.conf.js 和旧 Provider 扫描所需的文件。新旧入口同时存在时 Surgio 会明确报错。
4. 配置严格 TypeScript
推荐使用严格且不输出文件的配置:
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"rewriteRelativeImportExtensions": true,
"types": ["node"]
},
"include": ["*.ts", "lib/**/*.ts", "provider/**/*.ts", "scripts/**/*.ts"],
"exclude": [".surgio", "dist", "node_modules"]
}
本地相对 import 使用 .ts 扩展名;包 import 使用 surgio/project、surgio/runtime 等公开子路径。只使用 Node 能直接擦除的 TypeScript 语法,例如 type、interface、satisfies、as const 和 type-only import;不要声明需要额外代码生成的 enum、namespace 或 parameter property。
按部署方式迁移
本地 CLI
CLI 会优先加载 surgio.project.ts,并使用 nodeOptions() 中的 output、cache 和 upload。原有生成命令通常可以保持不变:
{
"scripts": {
"gen": "SURGIO_GFW_FREE=1 surgio generate --cache-snippet",
"typecheck": "tsc --noEmit",
"check": "pnpm typecheck && eslint ."
}
}
旧的 surgio.conf.js + provider/ 仍可作为 Node-only 兼容入口临时运行,但不能与 Project 共存,也不能用于 Worker manifest。建议在一次升级中完成 Project 迁移,避免继续维护两种加载模型。
Node Gateway
Node Gateway 使用相同的 Project,不再自行读取 Artifact、Provider 目录或模板 engine。入口改为 Hono Node adapter:
// server.ts
import { startServer } from '@surgio/gateway/node'
await startServer()
默认从当前目录加载 surgio.project.ts。如需指定监听地址、端口或 assets,可以把选项传给 startServer();不要重新实现 Gateway 路由。
AWS Lambda
Lambda 使用同一套 Node Project 和 Gateway app,只替换平台 adapter:
import { createLambdaHandler } from '@surgio/gateway/lambda'
export const handler = createLambdaHandler()
Cloudflare Worker
Worker 在构建期读取同一个 surgio.project.ts,预编译模板并生成 manifest。使用 Gateway 时,构建脚本同时准备前端 assets:
// scripts/build-worker.ts
import { buildGatewayWorker } from '@surgio/gateway/worker/build'
await buildGatewayWorker()
Worker 入口只装配平台 binding:
// worker.ts
import { createWorkerGateway } from '@surgio/gateway/worker'
import { createCloudflareKvStore } from 'surgio/cache/cloudflare'
import { TtlCache } from 'surgio/cache/core'
import manifest from './.surgio/worker-manifest.mjs'
export default createWorkerGateway<Env>(manifest, {
bindings(env) {
return {
cache: new TtlCache({
store: createCloudflareKvStore(env.SURGIO_CACHE),
}),
assets: env.ASSETS,
}
},
})
使用 wrangler types 生成并提交 worker-configuration.d.ts,不要手写宽泛的 Env。Wrangler 必须启用 nodejs_compat,并使用不早于 2025-04-01 的 compatibility date,使文本变量和 Secrets 自动暴露给 process.env。KV 和 Assets 是结构化 binding,仍由 Worker adapter 显式注入。
Worker Provider、远程 snippet 和 Artifact 缓存必须共用上例中的 TtlCache。Worker 代码只从 surgio/cache/core 和 surgio/cache/cloudflare 导入所需能力,不要加载 Node cache 聚合入口、filesystem、Upstash、CLI 或运行时模板编译器。
完整的 manifest、Wrangler 和模板限制参见 Cloudflare Worker 指南。
Node 与 Worker 并存
双运行时项目仍然只保留一个 surgio.project.ts:
- 共享配置、Artifact、Provider registry 和
templateDir位于默认导出; - Node-only output、cache 和 upload 位于
nodeOptions(); - Node Gateway 使用
@surgio/gateway/node; - Worker 使用 manifest、Cloudflare KV 和 Assets binding。
平台差异只能出现在 adapter、cache 和 binding 层。不要创建 surgio.worker.ts、worker.config.ts 或另一份 Artifact/Provider 配置。
需要主动处理的破坏性变化
Project 与模块格式
- Surgio 包和 v4 Project 使用 ESM。
defineSurgioConfig和defineWorkerProject已删除;统一使用defineSurgioProject。- Project 必须显式注册 Provider。目录扫描只属于 legacy Node 入口。
缓存
- Node 默认使用 filesystem;
default仅作为 filesystem 的旧名称接受。 - v3 的
cache: { type: 'redis', redisUrl }在 Node 中继续可用,需要移到nodeOptions()。省略redisUrl时读取REDIS_URL环境变量。 - Serverless Node 环境可以使用 Upstash REST,配置
UPSTASH_REDIS_REST_URL和UPSTASH_REDIS_REST_TOKEN。 - Cloudflare Worker 不支持 Redis TCP,需要使用显式注入的 KV binding。
v4 改变了缓存记录的格式和 key 前缀,v3 的 Redis 和临时文件记录不会迁移。缓存数据可以丢弃,升级后的首次运行出现 cold miss 属于正常行为。详细配置参见 Redis 缓存、Upstash REST 缓存 和缓存配置。
对象存储上传
surgio upload 的实现从 ali-oss 换成了 S3 兼容客户端,现在同时支持阿里云 OSS、Cloudflare R2 和其它 S3 服务。升级时需要处理三点:
- 凭据环境变量改名为
S3_BACKEND_ACCESS_KEY_ID和S3_BACKEND_ACCESS_KEY_SECRET。旧的OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET不再读取,只在 CI 中升级版本会让上传失败。 upload和cache配置改为严格校验。v3 中被忽略的多余字段现在会让配置校验直接失败。upload.endpoint只接受服务级 OSS Endpoint。绑定到单个 Bucket 的 CNAME 地址会被拒绝,依赖 CNAME 访问中国内地 Bucket 的项目无法使用surgio upload。
region 仍然接受 oss-cn-hangzhou 这类旧格式,加载后会归一化。完整字段见 upload 配置。
HTTP 客户端
httpClient 不再是 Got 实例,而是 Node 和 Worker 共用的 ky/Fetch 封装:
const response = await httpClient.get(url, { headers })
response.body // string
response.headers // Record<string, string | string[] | undefined>
response.statusCode // number
删除 Got 专用的 agent、timeout、retry 和 response API 用法。Provider factory 应优先使用 runtime 注入的 httpClient。
Surgio 也不再内置代理 agent,因此不会自己读取 HTTP_PROXY 和 HTTPS_PROXY。需要经本地代理访问订阅源时,交给 Node.js 处理:
export NODE_USE_ENV_PROXY=1
export HTTPS_PROXY=http://127.0.0.1:6152
export HTTP_PROXY=http://127.0.0.1:6152
如果只升级版本而没有加上 NODE_USE_ENV_PROXY=1,v3 下能正常生成的项目会开始报 connect ECONNREFUSED 或 connect ECONNRESET。
Clash 默认核心
clashConfig.clashCore 的默认值由旧 Clash 改为 Mihomo(内部值为 'clash.meta')。如果需要维持旧 Clash 的字段和过滤行为,请显式设置:
clashConfig: {
clashCore: 'clash',
}
'mihomo' 也可以作为输入别名,加载后会归一化为 'clash.meta'。
sing-box WireGuard 节点
getSingboxNodes 不再输出 WireGuard 节点。WireGuard 和 Tailscale 一样输出到 sing-box 配置顶层的 endpoints,需要 sing-box 1.11 或更新版本。只用 extendOutbounds 的模板升级后会丢失 WireGuard 节点,需要改用 getSingboxEndpoints 和 extendEndpoints,写法见 Tailscale 等 endpoint 节点。getSingboxNodeNames 同时包含 outbound 和 endpoint 的 tag,策略组不需要修改。
surgio new
surgio new 改为直接编辑 surgio.project.ts,把配置写入 providers registry 和 artifacts 数组,不再在 provider/ 下生成独立文件,也不再改写 surgio.conf.js。遇到动态构造的 registry 时,命令会保持文件原样并输出可以手工粘贴的配置片段。
已删除的 Provider 和 Surge SSR 输出
- BlackSSL Provider 已完全删除;使用该类型会得到“不支持的 Provider 类型”错误。
- Surge 不再生成 ShadowsocksR external proxy。混合订阅中的 SSR 节点会被警告并省略,其它节点继续输出。
- 删除
binPath、surgeConfig.resolveHostname和provider.startPort。 - 节点配置上的
binPath、localPort和hostnameIp字段一并删除。CustomProvider中手写过这些字段的节点会在类型检查时报错,生成时它们会被忽略。 - ShadowsocksR 的节点模型、订阅解析及 Clash、Quantumult X、Loon、portable 和 Worker 输出仍然保留。
SSD 订阅已移除
Surgio v4 不再支持 type: 'ssd',也不再导出 defineSsdProvider 或 SsdProvider。优先向订阅服务提供方获取 Clash 订阅,并使用 defineClashProvider 注册。只有 SSD 地址时,应先通过外部工具转换为 Clash 订阅,或使用 defineCustomProvider 自行读取和转换节点。需要继续直接读取 SSD 订阅的项目应暂留 Surgio v3。
Shadowsocks JSON 订阅已移除
Surgio v4 不再支持 type: 'shadowsocks_json_subscribe',也不再导出 defineShadowsocksJsonSubscribeProvider 或 ShadowsocksJsonSubscribeProvider。这个 Provider 读取的是 Shadowsocks for Windows 的旧 gui-config.json,不是 SIP008。优先改用 Clash 或普通 Shadowsocks 订阅。只有 gui-config.json 地址时,应先通过外部工具转换,或使用 defineCustomProvider 自行读取和转换节点。需要继续直接读取该格式的项目应暂留 Surgio v3。Gateway 的 format=shadowsocks-json 输出和模板中的 getShadowsocksNodesJSON helper 也已移除;需要 JSON 输出时应使用 Clash 或 sing-box 格式。
验证升级结果
所有项目先运行类型检查、lint 和本地生成:
pnpm check
pnpm gen
find dist -type f -exec shasum -a 256 {} + | LC_ALL=C sort \
> /tmp/surgio-v4.sha256
diff -u /tmp/surgio-v3.sha256 /tmp/surgio-v4.sha256
文件数量和哈希应该一致。若远程订阅在迁移期间发生变化,应在相同缓存和尽可能短的时间窗口内重试,并逐项调查差异,不能直接更新基线。
然后按部署方式继续验证:
- 本地 CLI:生成所有 Artifact,并检查
clean-cache、upload 等实际使用命令。 - Node Gateway:启动真实 HTTP server,检查登录、鉴权、Artifact、Provider export、订阅 headers 和静态资源。
- Lambda:使用平台 adapter 或真实 handler 请求验证状态码、headers 和 body。
- Worker:运行
wrangler types --check、workerd 集成测试和wrangler deploy --dry-run,检查 KV、Assets 和生产 bundle。 - 双运行时:比较 Node 与 Worker 的代表性 Artifact 和 Provider 响应。
Worker bundle 不应包含 filesystem、完整 Nunjucks compiler、动态模块加载器、Got、ioredis 或 Upstash client。最终还应搜索仓库和 CI 配置,确认没有重复 Project、defineWorkerProject、Redis 配置、OSS_ACCESS_KEY_ID 一类的旧上传变量,以及只为 Surge SSR 保留的字段。