10730 字
54 分钟
Surgio 机场面板的部署

前言#

技术要点:Surgio+Github+Netlify+Redis+CICD
项目地址

部署参考

1.安装Node.js(已安装可跳过)#

Node.js官网
(可选)配置nodejs
如果是更新nodejs,直接下载安装包安装即可

2.安装Surgio#

通过命令生成项目文件夹,第一次用才安装,你可以拉取现成的配置仓库,再npm install

npm init surgio-store surgio-to-yaml
cd surgio-to-yaml

2.5.主要操作内容#

provider文件夹 / surgio.conf.js文件 / template文件夹

3.provider文件夹使用说明#

provider文件夹中主要存放订阅链接
资料查询
模板案例:
① Clash模板

module.exports = {
  // 订阅类型
  type: 'clash',
  // 订阅链接
  url: '>>>>订阅地址<<<<<',
  // 是否添加旗帜
  addFlag: true,
  requestUserAgent: "clash-verge/v2.0.0",
  // 钩子函数
  hooks: {
...

② V2ray模板【vmess相关的节点可能会无效】

module.exports = {
  // 订阅类型
  type: 'v2rayn_subscribe',
  // 订阅链接
  url: '>>>>订阅地址<<<<<',
  // 是否添加旗帜
  addFlag: true,
  requestUserAgent: "clash-verge/v2.0.0",
  // 钩子函数
  hooks: {
...

3.5.原生节点自动改名#

可有效避免v2ray节点名称相同,及达到统一节点名称的效果
资料参考

...
  // 钩子函数
  hooks: {

    // 通过订阅链接获取节点内容失败后,返回一个带失败信息的 nodeList 节点列表
    onError: async error => {
      // error: Error

      // 自定义前缀
      const customPrefix = "";

      return [
        {
          nodeName: `${customPrefix}-Fallback: ${error.name}`,
          type: 'shadowsocks',
          hostname: 'fallback.example.com',
          port: 443,
          method: 'chacha20-ietf-poly1305',
          password: 'password',
        },
      ];
    },

    // 在获取到远程订阅内容后,修改节点的内容
    afterNodeListResponse: async (nodeList, customParams) => {
      // nodeList: NodeConfig[]
      // customerParams: {}

      // 自定义前缀
      const customPrefix = "";

      // 国家正则替换规则
      const countryRegexRules = [
        { regex: /^(?=.*(香港|HK|Hong|🇭🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "香港" },
        { regex: /^(?=.*(日本|JP|Japan|🇯🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "日本" },
        { regex: /^(?=.*(韩国||KR|Korea|🇰🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "韩国" },
        { regex: /^(?=.*(新加坡|狮城|SG|Singapore|🇸🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "新加坡" },
        { regex: /^(?=.*(美国|佛罗里达|US|United States|America|🇺🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "美国" },
        { regex: /^(?=.*(英国|UK|United Kingdom|🇬🇧))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "英国" },
        { regex: /^(?=.*(法国|FR|France|🇫🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "法国" },
        { regex: /^(?=.*(德国|DE|Germany|🇩🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "德国" },
        { regex: /^(?=.*(台湾|台灣|TW|Taiwan|Wan|🇹🇼))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "台湾" },
        { regex: /^(?=.*(阿根廷|ARG|Argentina|🇦🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿根廷" },
        { regex: /^(?=.*(澳大利亚|AUS|Australia|🇦🇺))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "澳大利亚" },
        { regex: /^(?=.*(巴西|BRA|Brazil|🇧🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "巴西" },
        { regex: /^(?=.*(加拿大|CAN|Canada|🇨🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "加拿大" },
        { regex: /^(?=.*(瑞士|CHE|Switzerland|🇨🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "瑞士" },
        { regex: /^(?=.*(智利|CHL|Chile|🇨🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "智利" },
        { regex: /^(?=.*(西班牙|ESP|Spain|🇪🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "西班牙" },
        { regex: /^(?=.*(以色列|ISR|Israel|🇮🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "以色列" },
        { regex: /^(?=.*(印度|IND|India|🇮🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "印度" },
        { regex: /^(?=.*(意大利|ITA|Italy|🇮🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "意大利" },
        { regex: /^(?=.*(马来西亚|MYS|Malaysia|🇲🇾))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "马来西亚" },
        { regex: /^(?=.*(荷兰|NLD|Netherlands|🇳🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "荷兰" },
        { regex: /^(?=.*(菲律宾|PHL|Philippines|🇵🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "菲律宾" },
        { regex: /^(?=.*(俄罗斯|莫斯科|RUS|Russia|🇷🇺))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "俄罗斯" },
        { regex: /^(?=.*(泰国|THA|Thailand|🇹🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "泰国" },
        { regex: /^(?=.*(土耳其|TUR|Turkey|🇹🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "土耳其" },
        { regex: /^(?=.*(乌克兰|UKR|Ukraine|🇺🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "乌克兰" },
        { regex: /^(?=.*(越南|VNM|Vietnam|🇻🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "越南" },
        { regex: /^(?=.*(南非|约翰内斯堡|ZAF|South Africa|🇿🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "南非" },
        { regex: /^(?=.*(埃及|EGY|Egypt|🇪🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "埃及" },
        { regex: /^(?=.*(新西兰|NZL|New Zealand|🇳🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "新西兰" },
        { regex: /^(?=.*(朝鲜|PRK|North Korea|🇰🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "朝鲜" },
        { regex: /^(?=.*(波兰|POL|Poland|🇵🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "波兰" },
        { regex: /^(?=.*(缅甸|MMR|Myanmar|🇲🇲))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "缅甸" },
        { regex: /^(?=.*(蒙古|MNG|Mongolia|🇲🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "蒙古" },
        { regex: /^(?=.*(芬兰|FIN|Finland|🇫🇮))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "芬兰" },
        { regex: /^(?=.*(希腊|GRC|Greece|🇬🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "希腊" },
        { regex: /^(?=.*(立陶宛|LTU|Lithuania|🇱🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "立陶宛" },
        { regex: /^(?=.*(阿联酋|迪拜|Dubai|ARE|United Arab Emirates|🇦🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿联酋" },
        { regex: /^(?=.*(阿富汗|AFG|Afghanistan|🇦🇫))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿富汗" },
        { regex: /^(?=.*(罗马尼亚|ROU|Romania|🇷🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "罗马尼亚" },
        { regex: /^(?=.*(奥地利|维也纳|AUT|Austria|🇦🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "奥地利" },
        { regex: /^(?=.*(瑞典|SWE|Sweden|🇸🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "瑞典" },
        { regex: /^(?=.*(葡萄牙|PRT|Portugal|🇵🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "葡萄牙" },
        { regex: /^(?=.*(秘鲁|PER|Peru|🇵🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "秘鲁" },
        { regex: /^(?=.*(沙特阿拉伯|SAU|Saudi Arabia|🇸🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "沙特阿拉伯" },
        { regex: /^(?=.*(挪威|NOR|Norway|🇳🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "挪威" },
        { regex: /^(?=.*(津巴布韦|ZWE|Zimbabwe|🇿🇼))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "津巴布韦" },
        { regex: /^(?=.*(摩尔多瓦|MDA|Moldova|🇲🇩))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "摩尔多瓦" },
        { regex: /^(?=.*(斯洛伐克|SVK|Slovakia|🇸🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "斯洛伐克" },
        { regex: /^(?=.*(尼泊尔|NPL|Nepal|🇳🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "尼泊尔" },
        { regex: /^(?=.*(孟加拉|BGD|Bangladesh|🇧🇩))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "孟加拉" },
        { regex: /^(?=.*(突尼斯|TUN|Tunisia|🇹🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "突尼斯" },
        { regex: /^(?=.*(哥伦比亚|COL|Colombia|🇨🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "哥伦比亚" },
        { regex: /^(?=.*(冰岛|ISL|Iceland|🇮🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "冰岛" },
        { regex: /^(?=.*(匈牙利|HUN|Hungary|🇭🇺))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "匈牙利" },
        { regex: /^(?=.*(卡塔尔|QAT|Qatar|🇶🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "卡塔尔" },
        { regex: /^(?=.*(北马其顿|北马|MKD|North Macedonia|🇲🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "北马其顿" },
        { regex: /^(?=.*(伊拉克|IRQ|Iraq|🇮🇶))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "伊拉克" },
        { regex: /^(?=.*(柬埔寨|KHM|Cambodia|🇰🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "柬埔寨" },
        { regex: /^(?=.*(阿塞拜疆|AZE|Azerbaijan|🇦🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿塞拜疆" },
        { regex: /^(?=.*(哈萨克斯坦|KAZ|Kazakhstan|🇰🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "哈萨克斯坦" },
        { regex: /^(?=.*(摩洛哥|MAR|Morocco|🇲🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "摩洛哥" },
        { regex: /^(?=.*(尼日利亚|NGA|Nigeria|🇳🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "尼日利亚" },
        { regex: /^(?=.*(比利时|BEL|Belgium|🇧🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "比利时" },
        { regex: /^(?=.*(保加利亚|索菲亚|BGR|Bulgaria|🇧🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "保加利亚" },
        { regex: /^(?=.*(巴基斯坦|PAK|Pakistan|🇵🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "巴基斯坦" },
        { regex: /^(?=.*(安哥拉|AGO|Angola|🇦🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "安哥拉" },
        { regex: /^(?=.*(捷克|CZE|Czech Republic|🇨🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "捷克" },
        { regex: /^(?=.*(爱尔兰|IRL|Ireland|🇮🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "爱尔兰" },
        { regex: /^(?=.*(丹麦|DNK|Denmark|🇩🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "丹麦" },
        { regex: /^(?=.*(墨西哥|MEX|Mexico|🇲🇽))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "墨西哥" },
        { regex: /^(?=.*(塞尔维亚|SRB|Serbia|🇷🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "塞尔维亚" },
        { regex: /^(?=.*(克罗地亚|HRV|Croatia|🇭🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "克罗地亚" },
        { regex: /^(?=.*(乌兹别克|UZB|Uzbekistan|🇺🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "乌兹别克" }
      ];

      // 用于存储已经使用的名称,避免重复
      const usedNames = new Set();

      // 处理数据
      const NewNodeList = nodeList.map(item => {
        let name = item.nodeName;

        // 首先筛选出有特殊字样的内容
        // 1.专线
        const regex1 = /^(?=.*(IPLC|IEPL|CN2|GAPN|CUVIP|AIA))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/;
        // 2.倍率
        const regex2 = /^(?!.*(?:网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*(?:0[.](0[1-9]|[1-8][0-9]?|9[Xx])|[1-9][0-9]?[Xx]).*/;
        // 3.Emby|家宽 等特殊字样
        const regex3 = /^(?=.*(Emby|emby|家宽))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/;

        // 提取匹配国家的逻辑为函数
        function replaceCountry(name, rules) {
          for (const rule of rules) {
            if (rule.regex.test(name)) {
              return rule.replace;
            }
          }
          return name;
        }

        // 提取匹配特殊关键字的逻辑为函数
        function matchKeywords(name, regex) {
          const keywords = name.match(regex) || [];
          return keywords.join(" ");
        }

        // 主逻辑
        if (regex3.test(name)) {
          // 匹配特殊字样3
          const otherSpecialWordsStr = matchKeywords(name, /(Emby|emby|家宽)/g);

          if (regex1.test(name)) {
            // 保留 IPLC|IEPL|CN2|GAPN|CUVIP|AIA 字样
            const specialKeywordsStr = matchKeywords(name, /(IPLC|IEPL|CN2|GAPN|CUVIP|AIA)/g);
            name = replaceCountry(name, countryRegexRules);
            name = `${customPrefix} (${otherSpecialWordsStr}) ${name} [${specialKeywordsStr}]`.trim();
          } else if (regex2.test(name)) {
            // 保留 0.1|0.2X|0.3x|3x|5X 等字样
            const specialKeywords = name.match(/(0\.\d{1,2}(?:[Xx]?)|[1-9]\d?[Xx])/g) || [];
            const cleanedKeywords = specialKeywords.map(keyword => keyword.replace(/[Xx]/g, ''));
            const specialKeywordsStr = cleanedKeywords.join(" ");
            name = replaceCountry(name, countryRegexRules);
            name = `${customPrefix} (${otherSpecialWordsStr}) ${name} [${specialKeywordsStr}x]`.trim();
          } else {
            name = replaceCountry(name, countryRegexRules);
            name = `${customPrefix} (${otherSpecialWordsStr}) ${name}`.trim();
          }
        } else {
          if (regex1.test(name)) {
            // 保留 IPLC|IEPL|CN2|GAPN|CUVIP|AIA 字样
            const specialKeywordsStr = matchKeywords(name, /(IPLC|IEPL|CN2|GAPN|CUVIP|AIA)/g);
            name = replaceCountry(name, countryRegexRules);
            name = `${customPrefix} ${name} [${specialKeywordsStr}]`.trim();
          } else if (regex2.test(name)) {
            // 保留 0.1|0.2X|0.3x|3x|5X 等字样
            const specialKeywords = name.match(/(0\.\d{1,2}(?:[Xx]?)|[1-9]\d?[Xx])/g) || [];
            const cleanedKeywords = specialKeywords.map(keyword => keyword.replace(/[Xx]/g, ''));
            const specialKeywordsStr = cleanedKeywords.join(" ");
            name = replaceCountry(name, countryRegexRules);
            name = `${customPrefix} ${name} [${specialKeywordsStr}x]`.trim();
          } else {
            name = replaceCountry(name, countryRegexRules);
            name = `${customPrefix} ${name}`.trim();
          }
        }

        // 处理重复名称
        let suffix = 1;
        let finalName = `${name} ${suffix}`;
        while (usedNames.has(finalName)) {
          suffix++;
          finalName = `${name} ${suffix}`;
        }
        usedNames.add(finalName);

        return {
          ...item,
          nodeName: finalName
        };
      });

      return NewNodeList
    },
  },
...

3.6.获取订阅内容失败后返回自定义节点#

资料参考

...
  // 钩子函数
  hooks: {
    // 通过订阅链接获取节点内容失败后,返回一个带失败信息的 nodeList 节点列表
    onError: async error => {
      // error: Error

      // 自定义前缀
      const customPrefix = "Nekoda";

      return [
        {
          nodeName: `${customPrefix}-Fallback: ${error.name}`,
          type: 'shadowsocks',
          hostname: 'fallback.example.com',
          port: 443,
          method: 'chacha20-ietf-poly1305',
          password: 'password',
        },
      ];
    },
...

4.template文件夹使用说明#

template文件夹中主要存放生成的订阅文件的格式模板
资料查询
格式及案例参考
分流规则参考
完整案例参考
模板中主要替换条目:

...
proxies: {{ getClashNodes(nodeList) | json }}
...

4.5.片段的使用#

使用参考
① template 文件夹下创建 rules.tpl (仅供参考)

{% macro main(jp_rule,change_rule,extranet_rule) %}
  #### 私人-直连
  - DOMAIN,keylol.com,DIRECT
  #### 私人-仅限日本IP
  - DOMAIN-SUFFIX,erogamescape.dyndns.org,{{ jp_rule }}
  #### 私人-可切换
  - DOMAIN,steamdb.info,{{ change_rule }}
  #### 私人-默认外网
  - DOMAIN,zi0.cc,{{ extranet_rule }}
  - DOMAIN-SUFFIX,api.steampowered.com,{{ extranet_rule }}
{% endmacro %}

② 修改 template 文件夹下的主 tpl 文件

...
rules:
  {% import './rules.tpl' as rules %}
  {{ rules.main('🇯🇵 - 自动选择','🎯 节点选择','自动选择') }}
...

4.6.基本配置参考#

配置参考

# 全局配置
http-port: 7890
socks-port: 7891
redir-port: 7892
tproxy-port: 7893
allow-lan: true
geodata-mode: true
unified-delay: true
mode: rule
log-level: info
ipv6: true
tcp-concurrent: true
keep-alive-interval: 30
geo-auto-update: true
geo-update-interval: 24
geox-url:
  geoip: "https://gcore.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat"
  geosite: "https://gcore.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
  mmdb: "https://gcore.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb"
find-process-mode: strict
global-client-fingerprint: chrome

# 域名嗅探
sniffer:
  enable: true
  parse-pure-ip: true
  force-dns-mapping: true
  override-destination: true
  sniff:
    HTTP:
      ports: [80, 443, 8080-8880]
      override-destination: true
    TLS:
      ports: [80, 443, 853]
    QUIC:
      ports: [80, 443, 853]
  skip-domain:
    - "+.push.apple.com"
    - "Mijia Cloud"
  skip-dst-address:
    - 91.105.192.0/23
    - 91.108.4.0/22
    - 91.108.8.0/21
    - 91.108.16.0/21
    - 91.108.56.0/22
    - 95.161.64.0/20
    - 149.154.160.0/20
    - 185.76.151.0/24
    - 2001:67c:4e8::/48
    - 2001:b28:f23c::/47
    - 2001:b28:f23f::/48
    - 2a0a:f280:203::/48

profile:
  store-fake-ip: true
  store-selected: true

# 裸核配置
# external-controller: 127.0.0.1:9090
# external-ui: zashborad
# external-ui-url: "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip"

bind-address: "*"

# Tunnel 配置
tun:
  enable: true
  device: kk
  stack: mixed
  dns-hijack:
    - any:53
  auto-route: true
  auto-detect-interface: true

# DNS 配置
dns:
  enable: true
  ipv6: true
  listen: 0.0.0.0:1053
  prefer-h3: true
  enhanced-mode: fake-ip
  fake-ip-range: 28.0.0.0/8
  fake-ip-filter:
    - '*'
    - localhost.ptlogin2.qq.com
    - dns.msftncsi.com
    - www.msftncsi.com
    - www.msftconnecttest.com
    - time1.cloud.tencent.com
    - '+.lan'
    - '+.invalid.*'
    - '+.localhost'
    - '+.local.*'
    - '+.time.*'
    - '+.ntp.*'
    - '+.time.edu.cn'
    - '+.ntp.org.cn'
    - '+.pool.ntp.org'
    - '+.qpic.cn'
  default-nameserver:
    - 223.5.5.5
    - 119.29.29.29
    - '[2402:4e00::]'
    - '[2400:3200::1]'
  proxy-server-nameserver:
    - https://1.12.12.12/dns-query
  direct-nameserver:
    - system
  direct-nameserver-follow-policy: true
  nameserver:
    - tls://8.8.8.8#🎯 节点选择
    - tls://208.67.222.222#🎯 节点选择
    - 'tls://[2001:4860:4860::8888]#🎯 节点选择'
    - 'tls://[2620:119:35::35]#🎯 节点选择'
  nameserver-policy:
    "geosite:cn,private":
      - https://1.12.12.12/dns-query
      - https://223.5.5.5/dns-query
      - https://120.53.53.53/dns-query
    "geosite:!cn,!private,!gfw": 
      - https://8.8.8.8/dns-query
      - https://208.67.222.222/dns-query
      - https://146.112.71.71/dns-query

# 节点筛选
## 特殊过滤
FilterEmby: &FilterEmby '^(?=.*(Emby|emby))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$'
FilterJK: &FilterJK '^(?=.*(家宽))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$'
FilterZX: &FilterZX '^(?=.*(IPLC|IEPL|CN2|GAPN|CUVIP|AIA))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$'
FilterDB: &FilterDB '^(?!.*(?:网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*(?:0[.](0[1-9]|[1-8][0-9]?|9[Xx])).*'
FilterGB: &FilterGB '^(?!.*(?:网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*(?:[3-9][Xx]|[1-9][0-9][Xx]|游戏).*'
## 按国家过滤
FilterHK: &FilterHK '^(?=.*(香港|HK|Hong|🇭🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterJP: &FilterJP '^(?=.*(日本|JP|Japan|🇯🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterKR: &FilterKR '^(?=.*(韩国|韓|KR|Korea|🇰🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterSG: &FilterSG '^(?=.*(新加坡|狮城|SG|Singapore|🇸🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterUS: &FilterUS '^(?=.*(美国|US|United States|America|🇺🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterUK: &FilterUK '^(?=.*(英国|UK|United Kingdom|🇬🇧))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterFR: &FilterFR '^(?=.*(法国|FR|France|🇫🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterDE: &FilterDE '^(?=.*(德国|DE|Germany|🇩🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterTW: &FilterTW '^(?=.*(台湾|台灣|TW|Taiwan|Wan|🇹🇼))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
## 其他过滤
FilterOthers: &FilterOthers '^(?!.*(🇭🇰|HK|Hong|香港|台湾|台灣|🇹🇼|TW|Taiwan|Wan|🇯🇵|JP|Japan|日本|🇸🇬|SG|Singapore|狮城|🇺🇸|US|United States|America|美国|🇩🇪|DE|Germany|德国|🇬🇧|UK|United Kingdom|英国|🇰🇷|KR|Korea|韩国|韓|🇫🇷|FR|France|法国)).*$'
FilterAll: &FilterAll '^(?=.*(.))(?!.*((?i)群|邀请|返利|循环|官网|客服|网站|网址|获取|订阅|流量|到期|机场|下次|版本|官址|备用|过期|已用|联系|邮箱|工单|贩卖|通知|倒卖|防止|国内|地址|频道|无法|说明|使用|提示|特别|访问|支持|教程|关注|更新|作者|加入|Expire|Traffic|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|(\b(USE|USED|TOTAL|EXPIRE|EMAIL|Panel|Channel|Author)\b|(\d{4}-\d{2}-\d{2}|\d+G)))).*$'

Select: &Select {type: select, url: 'https://www.gstatic.com/generate_204', disable-udp: false, hidden: false, include-all: true}
Auto: &Auto {type: url-test, url: 'https://www.gstatic.com/generate_204', interval: 300, tolerance: 50, disable-udp: false, hidden: true, include-all: true}
Balance: &Balance {type: load-balance, url: 'https://www.gstatic.com/generate_204', interval: 300, disable-udp: false, hidden: true, include-all: true}

# 自定义节点
proxies: {{ getClashNodes(nodeList) | json }}

# 策略组
proxy-groups:
  - {name: 🎯 节点选择, type: select, proxies: [直连, 自动选择, 手动选择], url: https://cp.cloudflare.com, icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Static.png}
  - {name: 手动选择, type: select, proxies: [直连, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择, 低倍率 - 手动选择, 高倍率 - 手动选择, 家宽 - 手动选择, Emby专用 - 手动选择], url: https://cp.cloudflare.com, icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Cylink.png}
  - {name: 自动选择, type: select, proxies: [🇭🇰 - 自动选择, 🇯🇵 - 自动选择, 🇰🇷 - 自动选择, 🇸🇬 - 自动选择, 🇺🇸 - 自动选择, 🇬🇧 - 自动选择, 🇫🇷 - 自动选择, 🇩🇪 - 自动选择, 🇹🇼 - 自动选择], url: https://cp.cloudflare.com, icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Urltest.png}
  - {name: Steam, type: select, proxies: [直连, 自动选择, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/steam.png}
  - {name: Emby, type: select, proxies: [直连, 自动选择, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择, 低倍率 - 手动选择, Emby专用 - 手动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Emby.png}
  - {name: Telegram, type: select, proxies: [直连, 自动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Telegram.png}
  - {name: 下载选择, type: select, proxies: [直连, 自动选择, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Socloud.png}
  
  # 自动选择
  - {name: 🇭🇰 - 自动选择, <<: *Balance, filter: *FilterHK}
  - {name: 🇯🇵 - 自动选择, <<: *Balance, filter: *FilterJP}
  - {name: 🇰🇷 - 自动选择, <<: *Balance, filter: *FilterKR}
  - {name: 🇸🇬 - 自动选择, <<: *Balance, filter: *FilterSG}
  - {name: 🇺🇸 - 自动选择, <<: *Balance, filter: *FilterUS}
  - {name: 🇬🇧 - 自动选择, <<: *Balance, filter: *FilterUK}
  - {name: 🇫🇷 - 自动选择, <<: *Balance, filter: *FilterFR}
  - {name: 🇩🇪 - 自动选择, <<: *Balance, filter: *FilterDE}
  - {name: 🇹🇼 - 自动选择, <<: *Balance, filter: *FilterTW}

  # 手动选择
  - {name: 🇭🇰 - 手动选择, <<: *Select, filter: *FilterHK}
  - {name: 🇯🇵 - 手动选择, <<: *Select, filter: *FilterJP}
  - {name: 🇰🇷 - 手动选择, <<: *Select, filter: *FilterKR}
  - {name: 🇸🇬 - 手动选择, <<: *Select, filter: *FilterSG}
  - {name: 🇺🇸 - 手动选择, <<: *Select, filter: *FilterUS}
  - {name: 🇬🇧 - 手动选择, <<: *Select, filter: *FilterUK}
  - {name: 🇫🇷 - 手动选择, <<: *Select, filter: *FilterFR}
  - {name: 🇩🇪 - 手动选择, <<: *Select, filter: *FilterDE}
  - {name: 🇹🇼 - 手动选择, <<: *Select, filter: *FilterTW}
  - {name: Others - 手动选择, <<: *Select, filter: *FilterOthers}
  
  # 全部节点
  - {name: AllIn - 手动选择, <<: *Select, filter: *FilterAll}
  - {name: AllIn - 自动选择, <<: *Balance, filter: *FilterAll}

  # 其他
  - {name: 专线 - 手动选择, <<: *Select, filter: *FilterZX}
  - {name: 高倍率 - 手动选择, <<: *Select, filter: *FilterGB}
  - {name: 低倍率 - 手动选择, <<: *Select, filter: *FilterDB}
  - {name: 家宽 - 手动选择, <<: *Select, filter: *FilterJK}
  - {name: Emby专用 - 手动选择, <<: *Select, filter: *FilterEmby}
  - {name: 直连, type: select, proxies: [DIRECT]}
  - {name: 拦截, type: select, proxies: [REJECT]}
  - {name: 跳过匹配, type: select, proxies: [PASS]}
  - {name: 广告, type: select, proxies: [REJECT-DROP,REJECT,PASS]}

# 规则配置
## 通用分流
RuleSet_classical: &RuleSet_classical {type: http, behavior: classical, interval: 86400}
## 域名分流
RuleSet_domain: &RuleSet_domain {type: http, behavior: domain, interval: 86400}
## IP分流
RuleSet_ipcidr: &RuleSet_ipcidr {type: http, behavior: ipcidr, interval: 86400}

# 订阅规则
rule-providers:
  Google:
    <<: *RuleSet_classical
    url: "https://raw.githubusercontent.com/Ctory-Nily/rule-script/main/rules/Clash/Google/Google.yaml"
    path: ./rule_providers/Google.yaml
...
...
...
  ## 配置广告分流域名
  秋风广告规则:
    <<: *RuleSet_domain
    format: yaml
    url: "https://raw.githubusercontent.com/TG-Twilight/AWAvenue-Ads-Rule/main/Filters/AWAvenue-Ads-Rule-Clash.yaml"
    path: ./rule_providers/AWAvenue-Ads-Rule-Clash.yaml
  AD-Clash:
    <<: *RuleSet_domain
    format: yaml
    url: "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/reject.txt"
    path: ./rule_providers/AD-Clash.yaml
  ## 本地规则分流
  userDirect:
    type: file
    behavior: classical
    path: ./rule_providers/userDirect.yaml
  userProxy:
    type: file
    behavior: classical
    path: ./rule_providers/userProxy.yaml
  userReject:
    type: file
    behavior: classical
    path: ./rule_providers/userReject.yaml

# 分流规则
rules:
  - IP-CIDR,127.0.0.1/32,REJECT,no-resolve
  - IP-CIDR,192.168.0.0/16,DIRECT
  - AND,((DST-PORT,5228-5230),(NETWORK,TCP),(DOMAIN-KEYWORD,google)),DIRECT
  - IP-CIDR,95.161.76.100/31,REJECT,no-resolve
  ### 广告拦截策略
  - RULE-SET,AD-Clash,广告
  - RULE-SET,秋风广告规则,广告
  ### 本地分流策略
  - RULE-SET,userDirect,直连
  - RULE-SET,userProxy,自动选择
  - RULE-SET,userReject,拦截
  ### 私人分流策略
  {% import './rules.tpl' as rules %}
  {{ rules.main('🇯🇵 - 自动选择','🎯 节点选择','自动选择') }}
...
...
...
  - RULE-SET,Google,自动选择
  ## GEO分流策略
  - GEOSITE,CN,直连
  - GEOIP,CN,直连
  ## 默认策略
  - MATCH,🎯 节点选择

4.7.扩展-目前可用的全球 DNS 解析服务 [2025.3.6]#

208.67.222.222
208.67.220.220

208.67.222.123
208.67.220.123

208.67.222.2
208.67.220.2

146.112.41.2
146.112.41.3
146.112.41.4
146.112.41.5

204.194.232.200
204.194.234.200

208.67.222.64
208.67.220.64

146.112.71.71
146.112.70.70

5.surgio.conf.js文件使用说明#

surgio.conf.js文件主要用于配置和生成订阅文件
配置文件内容修改
Artifacts部分修改
模板参考:

module.exports = {  
  artifacts: [  
  {
    "name": "Clash_JiChang.yaml",
    "template": "clash",
    "provider": "JiChang1",
    "subscriptionUserInfoProvider": "JiChang1",
    "combineProviders": [
      "JiChang2",
      "JiChang3"
    ]
  },
  {
    "name": "Clash_Free.yaml",
    "template": "clash",
    "provider": "Free1",
    "combineProviders": [
      "Free2",
      "Free3"
    ]
  }
],
  clashConfig: {
    enableTuic: true,
    enableShadowTls: true,
    enableHysteria2: true,
    enableVless: true,
    clashCore: 'clash.meta'
  }
}

6. 测试生成及检查是否有报错#

编写完以后在cmd中使用此代码,来生成订阅文件,订阅文件生成在dist文件夹下

npx surgio generate

7.安装git(已安装可跳过)#

Git官网

7.1.配置github公钥私钥#

① 安装完git后输入,生成公钥私钥
邮箱要与后面git配置的邮箱一致

ssh-keygen -t rsa -C “邮箱”

② 查看公钥并配置到github中
公私钥生成路径: C:\Users\用户名\.ssh
id_rsa: 私钥
id_rsa.pub: 公钥
查看公钥 > 打开Github > Setting > SSH and GPG keys > New SSH key > 输入名称 > 输入公钥内容 > Add SSH key

③ 验证配置

ssh git@github.com

7.5.在github上创建仓库并将修改好的项目上传到github上#

第一次 上传到github:

git config --global user.name “用户名”
git config --global user.email “邮箱”
git init
git add .
git commit -m "第一次提交"
git remote add origin ssh://git@ssh.github.com:443/Ctory-Nily/surgio-to-yaml.git
git push -u origin main

更新本地仓库后上传:

git add .
git commit -m "更新信息"
git push -u origin main

第一次 拉取最新的github仓库:

git clone ssh://git@ssh.github.com:443/Ctory-Nily/surgio-to-yaml.git
npm install

更新拉取最新的github仓库内容:

git pull origin main

8.将github项目托管到Netlify上#

参考资料1
参考资料2
参考资料3
Netlify官网
① 开启接口授权 surgio.conf.js

...
  gateway: {
    auth: true,
    accessToken: '后台面板密码',
    viewerToken: '订阅链接密码'
  },
...

② 项目根目录创建netlify.toml

[build]
  command = "exit 0"
  functions = "netlify/functions"
  publish = "."

[functions]
  included_files = [
    "node_modules/surgio/**",
    "node_modules/@surgio/**",
    "provider/**",
    "template/**",
    "*.js",
    "*.json"
  ]

[[redirects]]
  from = "/*"
  to = "/.netlify/functions/index"
  status = 200
  force = true

③ 项目根目录创建 netlify/functions 并新建文件 netlify/functions/index.js

'use strict';

const gateway = require('@surgio/gateway');

module.exports.handler = gateway.createLambdaHandler();

④ 更新surgio.conf.js内urlBase的值
将urlBase设置为你面板上的链接

...
  urlBase: 'https://surgio-demo.netlify.app/get-artifact/'
...

⑤ 重新推送到仓库,此时Netlify的配置就部署好了

git add .
git commit -m "重新推送"
git push -u origin main

⑥ 在 Netlify 中选择新建项目,并选择此github项目, 然后就会自动部署了

点击 Open production deploy 就可以查看到流量面板了,访问密码就是 surgio.conf.js 中的 accessToken

此时就可以在这里 查看剩余流量 和 获取到订阅链接了

9.配置Redis#

配置参考

① 获取到免费的在线redis服务
Redis在线服务网站upstash

② 新建redis项目
需要注意,假如你的 Surgio 服务部署在美西,那 Redis 也最好在美西。
Railway 默认的部署区域是 us-west-1,Vercel 的默认部署区域是 us-east-1,Netlify 的默认部署区域是 us-east-2

默认链接类似:

redis://:xxx...@some-thing-like-35533.upstash.io:35533    

如果你redis的TLS/SSL是Enabled那么格式要改成:

rediss://:xxx...@some-thing-like-35533.upstash.io:35533    

③ 在surgio.conf.js内新增redis的内容

...
  cache: process.env.REDIS_URL || process.env.REDIS_URI
    ? {
      type: 'redis',
      redisUrl: process.env.REDIS_URL || process.env.REDIS_URI,
    }
    : undefined,
...

④ 在 Netlify 中设置环境变量 REDIS_URL 的值
进入自己的项目 > Site configuration > Environment variables > 新建REDIS_URL环境变量 > 写入value值

⑤ 重新推送到仓库,此时Netlify在部署时,就会去加载Redis的缓存

git add .
git commit -m "加载缓存"
git push -u origin main

10.配置CICD#

① 生成 Personal Access Token
点击右上角头像 > 选择 Settings > Developer settings > Personal access tokens > Generate new token

勾选以下权限:
repo(完全控制仓库)
workflow(允许操作工作流)

点击 Generate token,复制生成的 Token

② 将 Token 添加到仓库的 Secrets
打开你的GitHub仓库 > 点击Settings > Secrets and variables > Actions > New repository secret

输入以下内容:
Name: PUSH_EVERYDAY(或其他你喜欢的名字)
Value: 粘贴刚才生成的 Personal Access Token

点击 Add secret

③ 设置 Workflow的写入权限 打开你的GitHub仓库 > 点击Settings > Actions > General > Workflow permissions > 选择 Read and write permissions

④ 在你的项目根目录下创建 .github/workflows 文件夹
新建 update-everyday.yml

name: Daily Auto Push

on:
  schedule:
    # 每天 UTC 时间 20:00 触发(北京时间 04:00)
    - cron: '0 20 * * *'
  workflow_dispatch: # 允许手动触发

jobs:
  auto-push:
    runs-on: ubuntu-latest

    steps:
      # 检出代码
      - name: Checkout repository
        uses: actions/checkout@v3

      # 模拟修改(例如更新文件)
      - name: Make changes
        run: |
          date > timestamp.txt

      # 推送更改
      - name: Commit and push changes
        env:
          GH_TOKEN: ${{ secrets.PUSH_EVERYDAY }} # 使用之前保存的 Token
        run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git add .
          git commit -m "Auto-update: Daily changes"
          git remote set-url origin https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git
          git push origin main

⑤ 重新推送到仓库,此时在Actions选项卡中就可以看到 Daily Auto Push 工作流

git add .
git commit -m "加载工作流"
git push -u origin main

⑥ 工作流每日提交之后,本地文件更新前,需要先把仓库同步到本地

git pull origin main

10.5.配置清空Action工作流(可选)#

在你的项目根目录下创建 .github/workflows 文件夹

① 新建 clear-commit.yml

name: Clear Commits

on:
  workflow_dispatch: # 允许手动触发

jobs:
  clear-commits:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      actions: write

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
        with:
          ref: main
          fetch-depth: 0

      - name: Configure Git
        run: |
          git config --global user.name "GitHub Action"
          git config --global user.email "action@github.com"
          git config --global advice.detachedHead false

      - name: Rewrite Git History
        run: |
          git checkout --orphan temp-branch
          git add -A
          git commit -m "Initial commit after history clear"
          git branch -D main || true
          git branch -m main
          git push -f origin main

      - name: Cleanup Workflow Runs
        run: |
          # 使用 GitHub API 删除工作流记录
          echo "Cleaning up workflow runs..."
          RUNS_URL="https://api.github.com/repos/${{ github.repository }}/actions/runs"
          RUNS_RESPONSE=$(curl -s -H "Authorization: Bearer ${{ secrets.PUSH_EVERYDAY }}" "$RUNS_URL")
          RUN_IDS=$(echo "$RUNS_RESPONSE" | jq -r '.workflow_runs[].id')

          for RUN_ID in $RUN_IDS; do
            echo "Deleting run $RUN_ID..."
            curl -s -X DELETE -H "Authorization: Bearer ${{ secrets.PUSH_EVERYDAY }}" "$RUNS_URL/$RUN_ID"
          done
          echo "Workflow runs cleanup completed."

      - name: Post-Cleanup Check
        run: echo "History cleared successfully at $(date)"

② 清除工作流之后会把github项目内的所有文件提交到最新分支,此时需要将本地的项目合并到新的分支

git pull --rebase

11.整体流程#

① 购买机场获取订阅链接,添加链接到surgio

② 使用surgio提取节点,合并订阅链接,配置订阅规则

③ 上传到github上,配置CICD自动更新项目,触发Netlify的更新,触发Redis缓存机制

④ Netlify上会自动更新节点内容,获取自己的订阅链接

⑤ 在Mihomo上填写自己的订阅链接

⑥ 管理好本地的规则文件

12.本地自动更新脚本 “提交更新.bat”#

无论本地文件先修改还是后修改都可以同步上传上去,注意bat文件格式要为ANSI

@echo off
echo 正在同步github仓库
git pull origin main

echo 正在添加所有文件到暂存区
git add .

:: 格式化日期和时间
for /f "tokens=1-3 delims=/- " %%a in ("%date%") do (
    set year=%%a
    set month=%%b
    set day=%%c
)
for /f "tokens=1-3 delims=:.," %%a in ("%time%") do (
    set hour=%%a
    set minute=%%b
)

:: 去掉小时前面的空格(如果小时是单数)
set hour=%hour: =%

:: 组合成 "年 月 日 时 分" 格式
set formatted_time=%year%年%month%月%day%日%hour%时%minute%分

:: 询问是否自定义提交信息
:input_confirm
set /p confirm=是否自定义提交信息?(输入 y 或 n): 
if "%confirm%"=="" (
    echo 输入不能为空,请重新输入!
    goto input_confirm
)

if "%confirm%"=="y" (
    :input_msg
    set /p commit_msg=请输入提交信息: 
    if "%commit_msg%"=="" (
        echo 输入不能为空,请重新输入!
        goto input_msg
    )
    git commit -m "%commit_msg%"
) else (
    git commit -m "%formatted_time%"
)

echo 正在推送到远程仓库
git push -u origin main

echo 操作完成!
pause

13.扩展-添加工作流自动生成 provider 文件并修改配置文件#

① 项目目录下新建 script 文件夹, 创建 provider_list.json

[
  {
    "provider_type": "clash",
    "provider_url": ">>>订阅链接<<<",
    "custom_prefix": "Test"
  },
  {
    "provider_type": "clash",
    "provider_url": ">>>订阅链接<<<",
    "custom_prefix": "Free1"
  },
  {
    "provider_type": "v2rayn_subscribe",
    "provider_url": ">>>订阅链接<<<",
    "custom_prefix": "Free2"
  },
]

② 在 script 文件夹下创建名为 template.js 的 provider 文件模板
(将原先 provider 文件夹下的 .js 文件复制过来后, 更改名称, 将文件内的 type、url 和 customPrefix 清空)

...
  // 订阅类型
  type: '',
  // 订阅链接
  url: '',
  // 是否添加旗帜
  addFlag: true,
  requestUserAgent: "clash-verge/v2.0.0",
  // 钩子函数
  hooks: {

    // 通过订阅链接获取节点内容失败后,返回一个带失败信息的 nodeList 节点列表
    onError: async error => {
      // error: Error

      // 自定义前缀
      const customPrefix = "";

      return [
        {
...
...
...
      ];
    },

    // 在获取到远程订阅内容后,修改节点的内容
    afterNodeListResponse: async (nodeList, customParams) => {
      // nodeList: NodeConfig[]
      // customerParams: {}

      // 自定义前缀
      const customPrefix = "";
...

③ 在 script 文件夹下创建 process_json_and_config.py
根据 provider_list.json 中的内容
1.生成 provider 文件
2.删除多余的 provider 文件
3.更改 surgio.conf.js 配置文件的内容

import os
import logging
import json
from typing import List, Dict, Optional, Union
import shutil

# 初始化 artifacts 数据
config_artifacts = [
    {
        "name": "Clash_JiChang.yaml",
        "template": "clash",
        "provider": "",  # 非 Free 开头的第一个 custom_prefix
        "subscriptionUserInfoProvider": "",
        "combineProviders": []  # 非 Free 开头的后续 custom_prefix
    },
    {
        "name": "Clash_Free.yaml",
        "template": "clash",
        "provider": "",  # Free 开头的第一个 custom_prefix
        "combineProviders": []  # Free 开头的后续 custom_prefix
    }
]

def delete_unnecessary_files(provider_list_data: List[Dict[str, str]], provider_path: str) -> None:
    """
    删除 provider 文件夹下不匹配的文件
    :param provider_list_data: 列表数据
    :param provider_path: 删除路径
    """
    # 获取所有 custom_prefix 值,并加上 .js 后缀
    valid_files = {f"{provider['custom_prefix']}.js" for provider in provider_list_data}

    # 遍历 provider 文件夹,删除不匹配的文件
    for filename in os.listdir(provider_path):
        file_path = os.path.join(provider_path, filename)
        
        # 检查文件名是否在 valid_files 中
        if os.path.isfile(file_path) and filename not in valid_files:
            os.remove(file_path)
            logging.info(f"已删除不匹配的文件: {filename}")

def update_artifacts(provider_list_data: List[Dict[str, str]]) -> List[Dict[str, Union[str, List[str]]]]:
    """
    更新 artifacts 列表数据
    :param provider_list_data: 列表数据
    :return: 更改后的列表数据
    """
    for provider in provider_list_data:
        custom_prefix = provider["custom_prefix"]
        # 处理 Free 开头的 custom_prefix
        if custom_prefix.startswith("Free"):
            # 如果 provider 为空时 第一次赋值
            if not config_artifacts[1]["provider"]:
                config_artifacts[1]["provider"] = custom_prefix
            # 如果 provider 不为空时 第二次开始赋值
            else:
                config_artifacts[1]["combineProviders"].append(custom_prefix)
        # 处理非 Free 开头的 custom_prefix
        else:
            # 如果 provider 为空时 第一次赋值
            if not config_artifacts[0]["provider"]:
                config_artifacts[0]["provider"] = custom_prefix
                config_artifacts[0]["subscriptionUserInfoProvider"] = custom_prefix
            # 如果 provider 不为空时 第二次开始赋值
            else:
                config_artifacts[0]["combineProviders"].append(custom_prefix)

    # 如果 combineProviders 为空,则删除该字段
    for artifact in config_artifacts:
        if not artifact["combineProviders"]:
            artifact.pop("combineProviders", None)
    
    return config_artifacts

def write_provider(provider_type: str, provider_url: str, custom_prefix: str, provider_template_path: str, provider_path: str) -> None:
    """
    复制 js 模板文件并重命名, 并修改内容
    :param provider_type: 订阅链接的书写类型
    :param provider_url: 订阅链接
    :param custom_prefix: 订阅名称
    :param provider_template_path: 模板路径
    :param provider_path: 生成路径
    """
    # 定义以 custom_prefix 命名的 .js 文件
    target_file = os.path.join(provider_path, f"{custom_prefix}.js")

    source_file = os.path.join(provider_template_path, "template.js")

    # 检查模板文件是否存在
    if not os.path.exists(source_file):
        logging.error(f"模板文件 {source_file} 不存在,跳过")
        exit(1)
    
    # 复制并重命名文件 (覆盖或创建)
    shutil.copy(source_file, target_file)

    # 替换文件内容
    with open(target_file, "r", encoding="utf-8") as file:
        content = file.read()

    # 根据 provider_type 替换 provider 的链接订阅类型
    if provider_type == "clash":
        content = content.replace('type: \'\'', f'type: \'{provider_type}\'')
    elif provider_type == "v2rayn_subscribe":
        content = content.replace('type: \'\'', f'type: \'{provider_type}\'')
    else:
        logging.error(f"未知 provider 链接订阅类型: {provider_type}")
        exit(1)

    # 替换 url 和 customPrefix
    content = content.replace('url: \'\'', f'url: \'{provider_url}\'')
    content = content.replace('const customPrefix = "";', f'const customPrefix = "{custom_prefix}";')

    # 写回文件
    with open(target_file, "w", encoding="utf-8") as file:
        file.write(content)

def modify_config(provider_list_data: List[Dict[str, str]], provider_path: str) -> None:
    """
    修改 surgio.conf.js 配置文件中的内容
    :param provider_list_data: 列表数据
    :param provider_path: 生成路径
    """
    # 删除 provider 文件夹下不匹配的文件
    delete_unnecessary_files(provider_list_data, provider_path)

    # 更新 artifacts 列表数据
    config_artifacts = update_artifacts(provider_list_data)

    # 读取原始配置文件
    with open("surgio.conf.js", "r", encoding="utf-8") as file:
        config_content = file.read()

    # 替换 artifacts 部分
    start_index = config_content.find("artifacts: [")
    end_index = config_content.find("],", start_index) + 2
    update_config_content = (
        config_content[:start_index] +
        "artifacts: " + json.dumps(config_artifacts, indent=2) + "," +
        config_content[end_index:]
    )

    # 写回配置文件
    with open("surgio.conf.js", "w", encoding="utf-8") as file:
        file.write(update_config_content)

if __name__ == "__main__":

    provider_template_path = 'script/'
    provider_path = "provider/"

    os.makedirs(provider_template_path, exist_ok=True)
    os.makedirs(provider_path, exist_ok=True)

    # 获取 provider_list.json 的路径
    json_file_path = os.path.join(os.path.dirname(__file__), 'provider_list.json')

    # 读取 provider_list.json 文件
    try:
        with open(json_file_path, "r", encoding="utf-8") as json_file:
            provider_list_data = json.load(json_file)
    except FileNotFoundError:
        logging.error(f"文件未找到: {json_file_path}")
        exit(1)
    except json.JSONDecodeError:
        logging.error(f"JSON 文件格式错误: {json_file_path}")
        exit(1)

    # 生成 provider 文件
    for item in provider_list_data:
        write_provider(item["provider_type"], item["provider_url"], item["custom_prefix"], provider_template_path, provider_path)
    
    # 处理 配置文件
    modify_config(provider_list_data, provider_path)

④ 在 .github/workflows 文件夹下创建工作流文件 process-json-and-config.yml

name: Process JSON and Config File

on:
  # 手动触发
  workflow_dispatch:
  push:
    paths:
  # 当 script 文件夹下 任意文件有更新时触发
      - 'script/**'

jobs:
  process_json_and_config:
    runs-on: ubuntu-latest

    # 将 has_changes 作为作业输出
    outputs:
      has_changes: ${{ steps.check-changes.outputs.has_changes }} 

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0  # 获取完整的历史记录

      - name: Check for changes in script directory
        id: check-changes
        run: |
          # 检查 script 文件夹下 任意文件是否有更改
          if git diff --quiet HEAD~1 HEAD -- script; then
            echo "No changes in script directory."
            echo "has_changes=false" >> $GITHUB_OUTPUT
          else
            echo "Changes detected in script directory."
            echo "has_changes=true" >> $GITHUB_OUTPUT
          fi

      - name: Set up Python
        if: steps.check-changes.outputs.has_changes == 'true'
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
      
      # 运行 process_json_and_config.py 脚本
      - name: Run fetch and convert script
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          python script/process_json_and_config.py

      # 提交推送
      - name: Commit and push changes
        if: steps.check-changes.outputs.has_changes == 'true'
        env:
          GH_TOKEN: ${{ secrets.PUSH_EVERYDAY }}
        run: |
          git config --local user.email "actions@github.com"
          git config --local user.name "GitHub Actions"
          git add .
          if git diff-index --quiet HEAD; then
            echo "没有文件更改,跳过提交。"
          else
            git commit -m "Process Json file and Update Config file"
            git pull origin main --rebase
            git remote set-url origin https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git
            if git push origin main; then
              echo "推送成功。"
            else
              echo "推送失败,请检查远程分支是否有冲突。"
              exit 1
            fi
          fi

14.扩展-个人的规则使用参考#

1.配置 file 类型的规则 (直接在本地文件中添加规则, 不需要任何提交, 但可能会丢失)

  userDirect:
    type: file
    behavior: classical
    path: ./rule_providers/userDirect.yaml
  • 临时不常用的规则使用这个方式

2.配置 http 类型的规则 (修改 rule-script 仓库, 修改完后提交仓库, 提交完后需要 Mihomo 同步规则文件)

  Google:
    type: http, 
    behavior: classical, 
    interval: 86400,
    url: "https://raw.githubusercontent.com/Ctory-Nily/rule-script/main/rules/Clash/Google/Google.yaml"
    path: ./rule_providers/Google.yaml
  • 大量常用的规则使用这个方式

3.直接将规则写入 rules (需要修改 surgio 仓库 template 文件夹下的 clash.tpl, 修改完后提交仓库, Netlify 会同步)

rules:
  #### 私人-直连
  - DOMAIN,keylol.com,DIRECT
  #### 私人-仅限日本IP
  - DOMAIN-SUFFIX,erogamescape.dyndns.org,🇯🇵 - 自动选择
  • 少量常用的规则使用这个方式

15.之后的使用方法#

仓库每天都在更新, 在更改本地仓库内容之前首先拉取最新的内容

git pull origin main

① 如果要修改机场链接 或者 新增机场链接
打开 script 文件夹下的 provider_list.json 复制新增一行并修改 provider_type、provider_url 和 custom_prefix

② 如果要修改规则、新增规则、修改 DNS 就需要修改 template 文件夹下的 .tpl 文件
修改 rule-providers、rules、dns 后的内容

③ 修改完成后打开命令行, 验证修改的文件是否有问题

npx surgio generate

能够在 dist 文件夹下正常输出 .yaml 文件就没有问题了

④ 通过 git 上传并更新 github 仓库

git add .
git commit -m "Initial commit"
git push -u origin main

⑤ 提交之后如果 provider_list.json 有内容更新, 那么就会自动执行工作流, 自动生成新的 provider 文件 并 修改 surgio.conf.js 文件

⑥ 当 Github 仓库有内容更新时, Netlify 就会检测到并重新构建, 订阅内容就会被更新
只要 surgio.conf.js 文件中的 name 和 viewerToken 没有发生变化,订阅链接基本不会改变

你也可以在 Netlify 上更改此项目的域名

Surgio 机场面板的部署
https://fuwari.vercel.app/posts/部署教程/surgiotoyaml/surgio-机场面板的部署/
作者
Ctory-Nily
发布于
2025-01-20
许可协议
CC BY-NC-SA 4.0