本文最后更新于 2026-07-21,文章内容可能已经过时。

一.什么是Sub-Store?:

Sub-Store 是网络代理圈(尤其是 iOS 网络工具用户)中极负盛名的“瑞士军刀”。

简单来说,它是一个高级订阅管理工具,专门解决多个代理订阅格式不统一、节点重复、筛选困难以及需要重命名等痛点。

二.搭建准备:

1.首先我们这次服务器搭建,所以最基础的准备就是一台可以上网的Linux主机,可以使用家里不使用的电脑安装Linux系统,或者在网上购买一台云主机,我这里使用直接的本地Linux小主机搭建(Ubuntu系统)

2.为了让搭建好的Sub-Store可以随时使用,那么需要去购买一个域名

以上两个就是我们这次搭建需要的核心东西,准备好就可以开始下一步

三.搭建:

1.安装1Panel面板:

1Panel 是一个界面现代化、深度集成 Docker、开源免费且安全易用的 Linux 服务器运维面板,能让你像操作手机App一样轻松管理服务器

首先连接你的Linux主机,输入:

curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && bash quick_start.sh

然后按照指示步骤一步一步来执行安装

这里输入2选择中文:

这里直接回车(默认)

这个镜像加速根据直接的情况来定

端口,安全入口,账号密码,根据自己喜欢的填,需要自己能记住的,完成设置后记得保存命令行返回的信息

根将ip地址输入浏览器进行访问,并输入刚刚设置的账号密码

2.安装OpenResty:

进入后打开应用商店,下载Openresty

安装 OpenResty,是为了给面板提供高性能的网站运行引擎和流量网关,从而让你能够通过图形化界面轻松地创建网站、配置反向代理、自动管理 SSL 证书以及实现基础的 Web 安全防护。

image-Fkav.png

全部默认即可

当显示"已启动"即可

3.为1Panel添加第三方库

1Panel原本自带的应用商店较少,需要自行添加第三方库

打开左侧的"计划任务"点击创建任务,任务类型选择shell脚本,然后将一下脚本复制粘贴到脚本内容中:

# 创建临时存放目录
git clone -b localApps https://github.com/okxlin/appstore /opt/1panel/resource/apps/local/appstore-localApps
# 将下载的应用移动到 1Panel 本地应用目录
cp -rf /opt/1panel/resource/apps/local/appstore-localApps/apps/* /opt/1panel/resource/apps/local/
# 删除临时目录
rm -rf /opt/1panel/resource/apps/local/appstore-localApps

任务名称随意,执行周期按照自己的情况来

完成后点击确定,再点击执行,然后查看报告

当"执行中"变为成功即可

完成后回到应用商店界面,点击"更新应用列表",可以看到应用数量从232条变成577条

4.开始安装Sub-Store:

在应用商店处搜索Sub-Store,

注意,由于我们最终的效果是使用自己的域名进行访问,而且也要防止别人随意使用自己的Sub-Store,前端后端路径需要设置一复杂自己记得住的

我这里使用/King888为例:

填写完后点击确认等待安装完成即可:

5.在 Cloudflare 控制台配置 Tunnel

这是将你的域名与服务器内网 40232 端口打通的关键

访问https://cloudflare.com/(根据自己的情况来),登陆完成后,点击左侧的Zero Trust

再次点击左侧的网络->连接器->创建隧道

隧道类型我们选Cloudflared,隧道名称随便取一个自己记得住的,我这里使用"test-sub"为例

点击确认后,再操作系统的位置,我们选择Docker,这时下方会给问你一串"命令"我们将他复制到Linux终端回车执行.当终端显示类似于这样

且在Cloudflare上下方显示出连接器id已连接的时候就可以下一步了

点击下一步后根据自己的喜好填写Subdomain-子域名,域填写自己的域名

服务类型选http,URL填写局域网内网IP:40232(40232为最开始安装Sub-Store时填写的端口,请根据自己的情况来)

点击完成后,我们还需要去1Panel控制面板的容器.找到刚刚的cloudflare的docker容器,如果分辨不出来可以看镜像那一栏,一般都是cloudflare/cloudflared:latest,找到后点击右侧的更多->编辑,下滑找到重启规则,找到一直重启,这样当你的主机在重启后,该服务将会自动启动

然后就可以通过你的域名去访问网站了,例如我的是test-sub.king818.xyz

6.Sub-Store设置

首次打开,需要在后端连接配置中输入你的后端路径或地址,也就是我们刚刚三.4 部分所填写的前端后端路径,我填写的/King888,那我直接填入King888即可登录成功,首次打开网站,订阅处,啥也没有,需要我们自己去动手添加.

(1).点击立即添加,在点击单条订阅

image-dsvx.png

(2).输入对应名称,选择远程订阅(下方的常用配置和节点操作可以根据自己的使用情况进行设置)设置完成后点击保存即可

image-wsFB.png

保存成功后会在下方显示流量信息

image-QWvq.png

(3).组合订阅的使用:

组合订阅,顾名思义,就是2个及以上的订阅服务,所以我们需要再添加一个订阅,添加完成后,就可以点击左上角的加号添加组合订阅了

名字随便取,在手动选择的订阅 里面将刚刚添加的两个订阅添加上去

image-Ytul.png

其实勾选完成后,你就已经可以开始组成使用了,Sub-Store已经将你的所有机场汇总到一起,但是呢为了让你有更好的使用体验,我们可以在最下方添加2个脚本和1个旗帜操作。首先我们点击脚本操作,点击远程连接,并将下面这串地址复制粘贴上去:

https://raw.githubusercontent.com/Keywos/rule/main/rename.js

解释一下这段脚本的作用是:

这是Github上Keywos大佬的规则,简单来说就是

用于对机场节点进行智能化清洗、格式标准化与信息整理

  • 智能地区识别与转换:自动识别节点名中的中英文地名、缩写或图标,并统一按需输出为中文、英文或 Emoji 国旗。

  • 规范重命名与序列号:去除原始混乱名称,自动按地区进行分组并追加补零序号(如 🇯🇵 日本 01),且支持自动移除单节点地区的冗余编号。

  • 节点信息杂质清理:自动扫描并过滤掉机场订阅中混入的广告、官网网址、到期时间、剩余流量等非节点杂项。

  • 关键标识提取与保留:精准识别并保留节点的倍率(如 0.5x2x)、专线类型(如 IPLCIEPL家宽)及自定义关键词,同时支持对关键属性进行重命名替换。

接下来添加一个旗帜操作

image-QuoV.png

最后再添加一个脚本操作,点击脚本操作,点击本地内容,将以下内容直接复制粘贴上去:

// 合并组合订阅中单条订阅的流量 仅做流量加法

async function operator(proxies = [], targetPlatform, context) {
  const SUBS_KEY = 'subs'
  const COLLECTIONS_KEY = 'collections'
  const $ = $substore
  const { source } = context
  const { _collection: collection } = source
  if (!collection || Object.keys(source).length > 1) throw new Error('暂时仅支持组合订阅, 请在组合订阅中使用此脚本')

  const allSubs = $.read(SUBS_KEY) || []
  let uploadSum = 0
  let downloadSum = 0
  let totalSum = 0
  let expire

  let args = $arguments || {}
  const { parseFlowHeaders, getFlowHeaders, flowTransfer, getRmainingDays } = flowUtils

  const subnames = [...collection.subscriptions]
  let subscriptionTags = collection.subscriptionTags
  if (Array.isArray(subscriptionTags) && subscriptionTags.length > 0) {
    allSubs.forEach(sub => {
      if (
        Array.isArray(sub.tag) &&
        sub.tag.length > 0 &&
        !subnames.includes(sub.name) &&
        sub.tag.some(tag => subscriptionTags.includes(tag))
      ) {
        subnames.push(sub.name)
      }
    })
  }

  for await (const sub of allSubs) {
    if (subnames.includes(sub.name)) {
      let subInfo
      if (sub.source === 'local' && !['localFirst', 'remoteFirst'].includes(sub.mergeSources)) {
        if (sub.subUserinfo) {
          if (/^https?:\/\//.test(sub.subUserinfo)) {
            subInfo = await getFlowHeaders(undefined, undefined, undefined, sub.proxy, sub.subUserinfo)
          } else {
            subInfo = sub.subUserinfo
          }
        }
      } else {
        let url = `${sub.url}`
          .split(/[\r\n]+/)
          .map(i => i.trim())
          .filter(i => i.length)?.[0]

        let urlArgs = {}
        const rawArgs = url.split('#')
        url = url.split('#')[0]
        if (rawArgs.length > 1) {
          try {
            // 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
            urlArgs = JSON.parse(decodeURIComponent(rawArgs[1]))
          } catch (e) {
            for (const pair of rawArgs[1].split('&')) {
              const key = pair.split('=')[0]
              const value = pair.split('=')[1]
              // 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
              urlArgs[key] = value == null || value === '' ? true : decodeURIComponent(value)
            }
          }
        }
        args = { ...urlArgs, ...args }
        if (!args.noFlow) {
          if (sub.subUserinfo) {
            if (/^https?:\/\//.test(sub.subUserinfo)) {
              subInfo = await getFlowHeaders(undefined, undefined, undefined, sub.proxy, sub.subUserinfo)
            } else {
              subInfo = sub.subUserinfo
            }
          } else {
            subInfo = await getFlowHeaders(url)
          }
        }
      }
      if (subInfo) {
        const {
          total,
          usage: { upload, download },
          expires,
        } = parseFlowHeaders(subInfo)
        if (upload > 0) uploadSum += upload
        if (download > 0) downloadSum += download
        if (total > 0) totalSum += total
        if (expires && expires * 1000 > Date.now()) {
          expire = expire ? Math.min(expire, expires) : expires
        }
      }
    }
  }
  const subUserInfo = `upload=${uploadSum}; download=${downloadSum}; total=${totalSum}${
    expire ? ` ; expire=${expire}` : ''
  }`

  // 旧版需要写入, 返回响应头里使用这个
  const allCols = $.read(COLLECTIONS_KEY) || []
  for (var index = 0; index < allCols.length; index++) {
    if (collection.name === allCols[index].name) {
      // 写入订阅流量信息
      allCols[index].subUserinfo = subUserInfo
      break
    }
  }
  $.write(allCols, COLLECTIONS_KEY)

  // 新版直接可以加到响应头里
  if ($options) {
    $options._res = {
      headers: {
        'subscription-userinfo': subUserInfo,
      },
    }
  }

  return proxies
}

这个脚本的作用就是汇总组合订阅中的流量与过期时间,以上操作都完成后,点击保存,那么组合订阅就完工了.

四.导入工具

点击保存好的组合连接

image-mLFe.png

会弹出以下窗口,点击通用订阅右边的复制按钮

image-UZtB.png

打开工具(我这里以Clash Verge为例),点击订阅,将刚刚复制好的内容粘贴到上方输入窗口,再点击订阅,多出来一个订阅选项就代表我们成功了

image-IwAo.png