diff --git a/README-CN.md b/README-CN.md index 6497d91..421d12d 100644 --- a/README-CN.md +++ b/README-CN.md @@ -4,7 +4,7 @@
-
+
diff --git a/README.md b/README.md
index 54eeac5..27a159c 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-
+
diff --git a/docs/release-notes.md b/docs/release-notes.md
index a15cf83..7c045e0 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,49 +1,43 @@
-# SubBoost v2.5.0
+# SubBoost v2.5.1
## 中文
### 更新重点
-SubBoost v2.5.0 主要改善代理组编辑、自部署更新和订阅生成稳定性。建议 v2.4.0 用户升级。
+SubBoost v2.5.1 是一个修复版本,主要修复自定义代理组的默认成员逻辑,并提升自部署安装、更新时的版本来源稳定性。建议 v2.5.0 用户升级。
### 主要变化
-- **移除筛选代理组**,相关功能转移到分流代理组高级模式和自定义分流组。
-- 分流代理组高级模式支持按来源、地区、关键词和排除条件整理节点。
-- 订阅生成更稳,规则顺序、代理组输出和常见 Mihomo 字段处理减少了意外变化。
-- 节点导入兼容性更好,覆盖更多常见节点链接和 Mihomo YAML 配置。
-- 自部署安装和更新流程更可靠,`subboost update`、状态检查和失败提示都有改进。
-- Dashboard 下载订阅 YAML 的行为更接近直接访问订阅链接,文件名和响应头更稳定。
-- 首次安装后的管理员初始化、登录和数据库连接更稳,减少安装完成后进不去后台的情况。
-- 安全和发布检查加强,降低公开包、安装资产和更新流程出错的风险。
+- 修复新建自定义代理组时默认成员过宽的问题,避免默认生成出“节点选择”和自定义组互相引用的循环配置。
+- `🚀 节点选择` 默认不再自动包含自定义代理组,减少代理组套娃导致配置异常或预览卡顿的风险。
+- 新建普通自定义代理组默认只包含 `DIRECT`、`REJECT` 和真实节点,保留高级模式里的手动配置能力。
+- 改进自部署安装和 `subboost update` 的版本来源处理,稳定版安装资产会默认固定到对应 release 版本。
+- 改进自部署管理脚本的更新来源,减少从指定版本更新时意外跟随其它发布通道的风险。
### 升级说明
- 建议升级前备份 `/opt/subboost/.env` 和数据库,方便需要时回滚。
-- 已安装 v2.4.0 的自部署实例可以继续使用 `subboost update` 更新。
+- 已安装 v2.5.0 的自部署实例可以继续使用 `subboost update` 更新。
- 普通订阅转换、模板和规则功能不需要手动改环境变量。
-- 如果你在 v2.4.0 使用过筛选代理组,请升级后到自定义代理组或分流代理组高级模式里重新配置。
+- 已经手动配置过的高级代理组不会被自动改写;本次主要改变新建和默认生成行为。
## English
### Highlights
-SubBoost v2.5.0 mainly improves proxy group editing, self-hosted updates, and subscription generation stability. v2.4.0 users are encouraged to upgrade.
+SubBoost v2.5.1 is a patch release that fixes default custom proxy group membership and improves version-source stability for self-hosted install and update flows. v2.5.0 users are encouraged to upgrade.
### Main Changes
-- Removed filtered proxy groups. Related features have moved to proxy group advanced mode and custom proxy groups.
-- Proxy group advanced mode supports organizing nodes by source, region, keyword, and exclusion rules.
-- Made subscription generation more stable, reducing unexpected changes in rule order, proxy group output, and common Mihomo fields.
-- Improved node import compatibility for more common node links and Mihomo YAML configurations.
-- Made self-hosted install and update flows more reliable, including `subboost update`, status checks, and failure messages.
-- Dashboard YAML downloads now behave more like direct subscription links, with steadier filenames and response headers.
-- Improved first-install admin setup, login, and database connection reliability to reduce post-install access issues.
-- Strengthened safety and release checks to reduce the risk of problems in public packages, install assets, and updates.
+- Fixed overly broad default members for newly created custom proxy groups, preventing default configurations where node selection and custom groups reference each other in a loop.
+- `🚀 节点选择` no longer automatically includes custom proxy groups by default, reducing the risk of nested proxy group loops causing invalid output or preview slowdowns.
+- Newly created normal custom proxy groups now default to `DIRECT`, `REJECT`, and real nodes only, while manual advanced configuration remains available.
+- Improved self-hosted install and `subboost update` version-source handling so stable release assets stay pinned to the matching release version by default.
+- Improved the self-hosted manager update source to reduce the risk of a pinned-version update unexpectedly following another release channel.
### Upgrade Notes
- Back up `/opt/subboost/.env` and the database before upgrading so rollback is easier if needed.
-- Existing v2.4.0 self-hosted installations can continue to update with `subboost update`.
+- Existing v2.5.0 self-hosted installations can continue to update with `subboost update`.
- Normal subscription conversion, templates, and rules do not require manual environment-variable changes.
-- If you used filtered proxy groups in v2.4.0, reconfigure them after upgrading through custom proxy groups or proxy group advanced mode.
+- Manually configured advanced proxy groups are not rewritten automatically; this release mainly changes new and default generated behavior.
diff --git a/local/package.json b/local/package.json
index a57f440..86c48f8 100644
--- a/local/package.json
+++ b/local/package.json
@@ -1,6 +1,6 @@
{
"name": "@subboost/local",
- "version": "2.5.0",
+ "version": "2.5.1",
"license": "AGPL-3.0-only",
"private": true,
"type": "module",
diff --git a/package-lock.json b/package-lock.json
index 3340d5f..ec0d0d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "subboost",
- "version": "2.5.0",
+ "version": "2.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "subboost",
- "version": "2.5.0",
+ "version": "2.5.1",
"license": "AGPL-3.0-only",
"workspaces": [
"packages/*",
@@ -67,7 +67,7 @@
},
"local": {
"name": "@subboost/local",
- "version": "2.5.0",
+ "version": "2.5.1",
"license": "AGPL-3.0-only",
"dependencies": {
"@prisma/adapter-pg": "^7.8.0",
diff --git a/package.json b/package.json
index def6930..795f5dc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "subboost",
- "version": "2.5.0",
+ "version": "2.5.1",
"license": "AGPL-3.0-only",
"private": true,
"engines": {
diff --git a/packages/core/src/generator/proxy-groups.test.ts b/packages/core/src/generator/proxy-groups.test.ts
index cd7f7db..07a881a 100644
--- a/packages/core/src/generator/proxy-groups.test.ts
+++ b/packages/core/src/generator/proxy-groups.test.ts
@@ -141,7 +141,55 @@ describe("proxy group generator", () => {
});
});
- it("keeps filtered-node custom groups node-scoped while exposing them as policy members", () => {
+ it("defaults ordinary custom groups to node-only members without adding them to node select", () => {
+ const groups = generateProxyGroups({
+ nodes: [node("Node A"), node("Node B")],
+ enabledModules: ["select", "auto"],
+ ruleProviderBaseUrl: "https://rules.example.com",
+ testUrl: "https://probe.example.com/204",
+ testInterval: 120,
+ customProxyGroups: [
+ customGroup("one", "select"),
+ customGroup("two", "select"),
+ customGroup("direct", "direct-first"),
+ customGroup("reject", "reject-first"),
+ ],
+ });
+
+ expect(groups.find((group) => group.name === "🚀 节点选择")?.proxies).toEqual([
+ "⚡ 自动选择",
+ "DIRECT",
+ "REJECT",
+ "Node A",
+ "Node B",
+ ]);
+ expect(groups.find((group) => group.name === "Custom one")?.proxies).toEqual([
+ "DIRECT",
+ "REJECT",
+ "Node A",
+ "Node B",
+ ]);
+ expect(groups.find((group) => group.name === "Custom two")?.proxies).toEqual([
+ "DIRECT",
+ "REJECT",
+ "Node A",
+ "Node B",
+ ]);
+ expect(groups.find((group) => group.name === "Custom direct")?.proxies).toEqual([
+ "DIRECT",
+ "REJECT",
+ "Node A",
+ "Node B",
+ ]);
+ expect(groups.find((group) => group.name === "Custom reject")?.proxies).toEqual([
+ "REJECT",
+ "DIRECT",
+ "Node A",
+ "Node B",
+ ]);
+ });
+
+ it("keeps filtered-node custom groups node-scoped while exposing them as non-select policy members", () => {
const groups = generateProxyGroups({
nodes: [node("Node A"), node("Node B"), node("Filtered")],
proxyProviderNames: ["remote"],
@@ -152,7 +200,7 @@ describe("proxy group generator", () => {
customProxyGroups: [
{
id: "filtered",
- name: "Filtered",
+ name: "Filtered Group",
emoji: "",
memberSource: "filtered-nodes",
includeInGroupMembers: true,
@@ -163,25 +211,26 @@ describe("proxy group generator", () => {
],
});
- expect(groups[0]?.name).toBe("Filtered");
- expect(groups.find((group) => group.name === "Filtered")?.proxies).toEqual([
+ expect(groups[0]?.name).toBe("Filtered Group");
+ expect(groups.find((group) => group.name === "Filtered Group")?.proxies).toEqual([
"DIRECT",
"REJECT",
"Node A",
"Filtered",
]);
- expect(groups.find((group) => group.name === "Filtered")).not.toHaveProperty("use");
- expect(groups.find((group) => group.name === "Filtered")?.proxies).not.toContain("Custom normal");
- expect(groups.find((group) => group.name === "Filtered")?.proxies).not.toContain("🚀 节点选择");
- expect(groups.find((group) => group.name === "Custom normal")?.proxies).toContain("Filtered");
+ expect(groups.find((group) => group.name === "Filtered Group")).not.toHaveProperty("use");
+ expect(groups.find((group) => group.name === "Filtered Group")?.proxies).not.toContain("Custom normal");
+ expect(groups.find((group) => group.name === "Filtered Group")?.proxies).not.toContain("🚀 节点选择");
+ expect(groups.find((group) => group.name === "Custom normal")?.proxies).not.toContain("Filtered Group");
+ expect(groups.find((group) => group.name === "Custom normal")?.proxies).not.toContain("🚀 节点选择");
expect(groups.find((group) => group.name === "Custom normal")?.proxies).not.toContain("Custom normal");
expect(groups.find((group) => group.name === "Custom normal")).toMatchObject({ use: ["remote"] });
- expect(groups.find((group) => group.name === "🚀 节点选择")?.proxies).toContain("Filtered");
- expect(groups.find((group) => group.name === "🚀 节点选择")?.proxies).toContain("Custom normal");
+ expect(groups.find((group) => group.name === "🚀 节点选择")?.proxies).not.toContain("Filtered Group");
+ expect(groups.find((group) => group.name === "🚀 节点选择")?.proxies).not.toContain("Custom normal");
expect(groups.find((group) => group.name === "🏠 私有网络")?.proxies?.slice(0, 5)).toEqual([
"DIRECT",
"REJECT",
- "Filtered",
+ "Filtered Group",
"Custom normal",
"🚀 节点选择",
]);
diff --git a/packages/core/src/generator/proxy-groups.ts b/packages/core/src/generator/proxy-groups.ts
index edc7c2f..63d56d5 100644
--- a/packages/core/src/generator/proxy-groups.ts
+++ b/packages/core/src/generator/proxy-groups.ts
@@ -197,7 +197,7 @@ export function generateProxyGroups(options: GenerateOptions): ProxyGroup[] {
"DIRECT",
"REJECT"
);
- const customBaseProxies = moduleBaseProxies;
+ const customBaseProxies = fallbackTargets("DIRECT", "REJECT", ...filteredNodeNames);
const availableMemberProxyNames = fallbackTargets(
"DIRECT",
"REJECT",
@@ -252,7 +252,6 @@ export function generateProxyGroups(options: GenerateOptions): ProxyGroup[] {
autoTarget,
"DIRECT",
"REJECT",
- ...customGroupNames,
...nodeNames
);
groups.push({
@@ -408,7 +407,7 @@ export function generateProxyGroups(options: GenerateOptions): ProxyGroup[] {
name: customGroup.name,
type: "select",
proxies: resolveCustom(
- fallbackTargets("DIRECT", "REJECT", selectTarget, autoTarget, ...filteredNodeNames).filter(
+ fallbackTargets("DIRECT", "REJECT", ...filteredNodeNames).filter(
(target) => target !== customGroup.name
)
),
@@ -420,7 +419,7 @@ export function generateProxyGroups(options: GenerateOptions): ProxyGroup[] {
name: customGroup.name,
type: "select",
proxies: resolveCustom(
- fallbackTargets("REJECT", "DIRECT", selectTarget).filter(
+ fallbackTargets("REJECT", "DIRECT", ...filteredNodeNames).filter(
(target) => target !== customGroup.name
)
),
diff --git a/packages/ui/src/store/config-store/actions/custom-actions.test.ts b/packages/ui/src/store/config-store/actions/custom-actions.test.ts
index 0658668..0404386 100644
--- a/packages/ui/src/store/config-store/actions/custom-actions.test.ts
+++ b/packages/ui/src/store/config-store/actions/custom-actions.test.ts
@@ -85,7 +85,7 @@ describe("custom config-store actions", () => {
const groupId = getState().customProxyGroups[0].id;
expect(groupId).toBe("custom-group-1767225600000");
expect(getState().customProxyGroups[0]).toEqual(
- expect.objectContaining({ includeInGroupMembers: true })
+ expect.objectContaining({ includeInGroupMembers: false })
);
actions.updateCustomProxyGroup(groupId, {
diff --git a/packages/ui/src/store/config-store/actions/custom-actions.ts b/packages/ui/src/store/config-store/actions/custom-actions.ts
index 4a3cc8b..a92dd87 100644
--- a/packages/ui/src/store/config-store/actions/custom-actions.ts
+++ b/packages/ui/src/store/config-store/actions/custom-actions.ts
@@ -140,7 +140,7 @@ export function createCustomActions(
...(typeof group.description === "string" ? { description: group.description.trim() } : {}),
...(group.memberSource === "filtered-nodes" ? { memberSource: "filtered-nodes" as const } : {}),
includeInGroupMembers:
- typeof group.includeInGroupMembers === "boolean" ? group.includeInGroupMembers : true,
+ typeof group.includeInGroupMembers === "boolean" ? group.includeInGroupMembers : false,
groupType: group.groupType,
...(group.groupType === "load-balance" && group.strategy ? { strategy: group.strategy } : {}),
...(group.advanced ? { advanced: normalizeProxyGroupAdvancedConfig(group.advanced) } : {}),