Add initial_path option to remote rule-sets

This commit is contained in:
世界
2026-07-24 20:33:13 +08:00
parent 4c885ee1d0
commit 07dba74657
5 changed files with 57 additions and 5 deletions
+14 -2
View File
@@ -2,7 +2,8 @@
:material-plus: [http_client](#http_client) :material-plus: [http_client](#http_client)
:material-delete-clock: [download_detour](#download_detour) :material-delete-clock: [download_detour](#download_detour)
:material-alert: [tag](#tag) :material-alert: [tag](#tag)
:material-plus: [initial_path](#initial_path)
!!! quote "Changes in sing-box 1.10.0" !!! quote "Changes in sing-box 1.10.0"
@@ -49,6 +50,7 @@
"tag": "", // or [] "tag": "", // or []
"format": "source", // or binary "format": "source", // or binary
"url": "", "url": "",
"initial_path": "",
"http_client": "", // or {} "http_client": "", // or {}
"update_interval": "", "update_interval": "",
@@ -76,7 +78,7 @@ Tag of rule-set.
`tag` also accepts a list of tags to define multiple rule-sets sharing other options at once. `tag` also accepts a list of tags to define multiple rule-sets sharing other options at once.
The `{tag}` placeholder in `path` or `url` is replaced by each tag, The `{tag}` placeholder in `path`, `url` or `initial_path` is replaced by each tag,
and is required when multiple tags are set. and is required when multiple tags are set.
Multiple tags conflict with `type: inline`. Multiple tags conflict with `type: inline`.
@@ -121,6 +123,16 @@ File path of rule-set.
Download URL of rule-set. Download URL of rule-set.
#### initial_path
!!! question "Since sing-box 1.14.0"
File path of the initial rule-set content.
Read once at startup when no cached rule-set is available, so startup is not
blocked by the initial download. The rule-set is still updated in the background
immediately after startup.
#### http_client #### http_client
!!! question "Since sing-box 1.14.0" !!! question "Since sing-box 1.14.0"
+13 -2
View File
@@ -2,7 +2,8 @@
:material-plus: [http_client](#http_client) :material-plus: [http_client](#http_client)
:material-delete-clock: [download_detour](#download_detour) :material-delete-clock: [download_detour](#download_detour)
:material-alert: [tag](#tag) :material-alert: [tag](#tag)
:material-plus: [initial_path](#initial_path)
!!! quote "sing-box 1.10.0 中的更改" !!! quote "sing-box 1.10.0 中的更改"
@@ -49,6 +50,7 @@
"tag": "", // 或 [] "tag": "", // 或 []
"format": "source", // or binary "format": "source", // or binary
"url": "", "url": "",
"initial_path": "",
"http_client": "", // 或 {} "http_client": "", // 或 {}
"update_interval": "", "update_interval": "",
@@ -76,7 +78,7 @@
`tag` 也接受一组标签,用于一次定义多个共享其他选项的规则集。 `tag` 也接受一组标签,用于一次定义多个共享其他选项的规则集。
`path` 或 `url` 中的 `{tag}` 占位符将被替换为每个标签,设置多个标签时必填。 `path`、`url` 或 `initial_path` 中的 `{tag}` 占位符将被替换为每个标签,设置多个标签时必填。
多个标签与 `type: inline` 冲突。 多个标签与 `type: inline` 冲突。
@@ -120,6 +122,15 @@
规则集的下载 URL。 规则集的下载 URL。
#### initial_path
!!! question "自 sing-box 1.14.0 起"
规则集初始内容的文件路径。
仅在启动时没有可用的规则集缓存时读取一次,使启动不被初始下载阻塞。
启动后规则集仍会立即在后台更新。
#### http_client #### http_client
!!! question "自 sing-box 1.14.0 起" !!! question "自 sing-box 1.14.0 起"
+3
View File
@@ -15327,6 +15327,9 @@
"url": { "url": {
"type": "string" "type": "string"
}, },
"initial_path": {
"type": "string"
},
"http_client": { "http_client": {
"$ref": "#/$defs/HTTPClientReference" "$ref": "#/$defs/HTTPClientReference"
}, },
+4
View File
@@ -113,6 +113,9 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
if !strings.Contains(r.RemoteOptions.URL, C.RuleSetTagPlaceholder) { if !strings.Contains(r.RemoteOptions.URL, C.RuleSetTagPlaceholder) {
return E.New("missing ", C.RuleSetTagPlaceholder, " placeholder in url") return E.New("missing ", C.RuleSetTagPlaceholder, " placeholder in url")
} }
if r.RemoteOptions.InitialPath != "" && !strings.Contains(r.RemoteOptions.InitialPath, C.RuleSetTagPlaceholder) {
return E.New("missing ", C.RuleSetTagPlaceholder, " placeholder in initial_path")
}
} }
} }
return nil return nil
@@ -177,6 +180,7 @@ type LocalRuleSet struct {
type RemoteRuleSet struct { type RemoteRuleSet struct {
URL string `json:"url"` URL string `json:"url"`
InitialPath string `json:"initial_path,omitempty"`
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
UpdateInterval badoption.Duration `json:"update_interval,omitempty"` UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
// Deprecated: use http_client instead // Deprecated: use http_client instead
+23 -1
View File
@@ -5,6 +5,7 @@ import (
"context" "context"
"io" "io"
"net/http" "net/http"
"path/filepath"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -22,6 +23,7 @@ import (
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager"
"github.com/sagernet/sing/service/pause" "github.com/sagernet/sing/service/pause"
"go4.org/netipx" "go4.org/netipx"
@@ -36,6 +38,7 @@ type RemoteRuleSet struct {
outbound adapter.OutboundManager outbound adapter.OutboundManager
tag string tag string
url string url string
initialPath string
options option.RuleSet options option.RuleSet
updateInterval time.Duration updateInterval time.Duration
httpClient *http.Client httpClient *http.Client
@@ -58,6 +61,11 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, tag stri
} else { } else {
updateInterval = 24 * time.Hour updateInterval = 24 * time.Hour
} }
var initialPath string
if options.RemoteOptions.InitialPath != "" {
initialPath = filemanager.BasePath(ctx, strings.ReplaceAll(options.RemoteOptions.InitialPath, C.RuleSetTagPlaceholder, tag))
initialPath, _ = filepath.Abs(initialPath)
}
return &RemoteRuleSet{ return &RemoteRuleSet{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
@@ -65,6 +73,7 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, tag stri
logger: logger, logger: logger,
tag: tag, tag: tag,
url: strings.ReplaceAll(options.RemoteOptions.URL, C.RuleSetTagPlaceholder, tag), url: strings.ReplaceAll(options.RemoteOptions.URL, C.RuleSetTagPlaceholder, tag),
initialPath: initialPath,
options: options, options: options,
updateInterval: updateInterval, updateInterval: updateInterval,
pauseManager: service.FromContext[pause.Manager](ctx), pauseManager: service.FromContext[pause.Manager](ctx),
@@ -98,7 +107,20 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
} }
} }
} }
if s.lastUpdated.IsZero() { var loadedFromInitialPath bool
if s.lastUpdated.IsZero() && s.initialPath != "" {
var content []byte
content, err = filemanager.ReadFile(s.ctx, s.initialPath)
if err == nil {
err = s.loadBytes(content)
}
if err != nil {
s.logger.Warn(E.Cause(err, "load initial rule-set from ", s.initialPath))
} else {
loadedFromInitialPath = true
}
}
if s.lastUpdated.IsZero() && !loadedFromInitialPath {
err = s.fetch(ctx, true) err = s.fetch(ctx, true)
if err != nil { if err != nil {
return E.Cause(err, "initial rule-set: ", s.tag) return E.Cause(err, "initial rule-set: ", s.tag)