mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-04 16:03:56 +08:00
🐶 Sync 2026-10-03 11:32:07
This commit is contained in:
1 parent
a5bb91e6ed
commit
66e3d835c4
72 files changed
+5107
-1799
No files matched your search
@@ -120,6 +120,68 @@ export function get_ruleset(cfg, dm) {
|
||||
return rules;
|
||||
};
|
||||
|
||||
/* rule_set_tags(cfg) - the rule_set tags ONE `dm.routing.rulesets` section
|
||||
* produces: `cfg-<name>-rule`, plus one per entry in extra_tags.
|
||||
*
|
||||
* The generator needs it to build the tag; the CLI's environment resolution
|
||||
* needs it too, because a `{tag}` placeholder in a source path has to be
|
||||
* expanded to the real per-tag files before their existence can be checked.
|
||||
* Two copies of this list would be free to drift, and the failure mode is
|
||||
* quiet: the pre-check would stat files the running configuration never
|
||||
* names, and a genuinely missing one would go unreported.
|
||||
*
|
||||
* Declared after get_ruleset() but before its callers for the same reason that
|
||||
* function carries its own note: ucode resolves references at call time from
|
||||
* what has already been evaluated. */
|
||||
export function rule_set_tags(cfg) {
|
||||
const tags = [ 'cfg-' + cfg.name + '-rule' ];
|
||||
|
||||
for (let t in (cfg.extra_tags || []))
|
||||
push(tags, 'cfg-' + t + '-rule');
|
||||
|
||||
return tags;
|
||||
};
|
||||
|
||||
/* BUILTIN_REMOTE_RULE_SETS - the remote rule-sets the generator declares
|
||||
* itself, in the modes that split on mainland China.
|
||||
*
|
||||
* geoip-cn referenced by the route rule to split destinations by IP
|
||||
* geosite-cn referenced only by the DNS rule (server: china-dns); the
|
||||
* route layer never matches it, but sing-box still loads and
|
||||
* keeps it, so removing it would break the DNS split
|
||||
*
|
||||
* This list is shared with generate_client.uc, which has to create the
|
||||
* "do not block startup" fallback file for each of them before the generator
|
||||
* runs. The two used to be independent: route.uc hard-coded the entries and
|
||||
* the CLI knew nothing about them, which is why a cold-cache first start
|
||||
* blocked on downloading them and why a change to either side could not be
|
||||
* checked against the other. One list, two readers.
|
||||
*
|
||||
* Not a conffile-order dependency - this is the generator's own declaration,
|
||||
* in the order the generated rule_set array must keep so the emitted bytes
|
||||
* stay stable. */
|
||||
export const BUILTIN_REMOTE_RULE_SETS = [
|
||||
{
|
||||
tag: 'geoip-cn',
|
||||
format: 'binary',
|
||||
url: 'https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs',
|
||||
update_interval: '24h'
|
||||
},
|
||||
{
|
||||
tag: 'geosite-cn',
|
||||
format: 'binary',
|
||||
url: 'https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs',
|
||||
update_interval: '24h'
|
||||
}
|
||||
];
|
||||
|
||||
/* The routing modes that declare them. Kept beside the list so the CLI can
|
||||
* decide whether it needs to create their fallbacks without repeating the
|
||||
* condition route.uc branches on - and so the two cannot drift. */
|
||||
export function declaresBuiltinRemoteRuleSets(routing_mode) {
|
||||
return routing_mode === 'bypass_mainland_china' || routing_mode === 'proxy_mainland_china';
|
||||
};
|
||||
|
||||
/* Resolve the direct-node destination override that the route builder
|
||||
* needs for a `direct` routing_node target. The override is recorded
|
||||
* earlier by generate_outbound() (see outbound.uc) when a direct node
|
||||
|
||||
@@ -138,6 +138,33 @@ export function build_context(dm, env) {
|
||||
* whole config. Default false - an absent env must not turn into an
|
||||
* optimistic "assume the file is there". */
|
||||
china_ip6_ready: (env?.china_ip6_ready === true),
|
||||
/* Which enabled `type: local` rule-sets have a usable file on disk,
|
||||
* keyed by UCI section name: { '<section>': true }.
|
||||
*
|
||||
* Same shape of problem as china_ip6_ready and for the same reason it
|
||||
* lives in the CLI's env rather than under generator/ (guard 27): the
|
||||
* answer is a filesystem fact, and a `path` naming a file that is not
|
||||
* there makes `sing-box check` reject the whole configuration with
|
||||
* sing-box's own "parse rule-set[0]: open ...: no such file or
|
||||
* directory" - which names neither the rule-set the user created nor
|
||||
* the directory they are supposed to copy files into. The generator
|
||||
* turns a missing entry into a message that names both (ruleset.uc).
|
||||
*
|
||||
* The default is pessimistic for the same reason: an env that forgot
|
||||
* the field (the server path, a future caller) must not turn "no file"
|
||||
* into "assume the file is there". */
|
||||
ruleset_local_ready: (env?.ruleset_local_ready || {}),
|
||||
/* What each rule-set's file actually is, keyed by UCI section name.
|
||||
*
|
||||
* Reaches the generator as an ordinary field for the same reason
|
||||
* china_ip6_ready does - and with the same pessimistic default, which
|
||||
* matters more here. For china_ip6_ready a missing field meant "no
|
||||
* file"; here it means "no opinion about the content", and the
|
||||
* generator must read that as "leave the declared format alone".
|
||||
* Defaulting it to, say, 'source' would have every remote rule-set
|
||||
* with no initial_path silently declared as source JSON, and a binary
|
||||
* .srs fetched from a URL would then fail to parse at startup. */
|
||||
ruleset_formats: (env?.ruleset_formats || {}),
|
||||
self_mark, ntp_server, dns_port, mixed_port,
|
||||
redirect_port, tproxy_port,
|
||||
tun_name, tun_addr4, tun_addr6, tun_mtu,
|
||||
@@ -229,6 +256,29 @@ export function build_context(dm, env) {
|
||||
* Default '0' preserves the
|
||||
* 300ms / default-list behaviour so an upgrade is invisible. */
|
||||
sniffer_advanced_mode: dm.general.sniffer_advanced_mode || '0',
|
||||
/* ruleset_safe_start: when '1' (opt-in), every enabled remote
|
||||
* rule-set that has no initial_path of its own gets one, pointing at
|
||||
* an EMPTY rule-set the generator writes before the config is
|
||||
* checked. A remote rule-set with no initial file is fetched during
|
||||
* initialization, before the inbounds bind, so on a cold cache the
|
||||
* first start depends on the CDN being reachable - and on the node,
|
||||
* because the fetch goes through http_clients.
|
||||
*
|
||||
* The fallback is empty on purpose and that is the trade: a rule-set
|
||||
* with no rules matches nothing, so its traffic falls through to
|
||||
* `final`. That is a routing change, not a robustness tweak, which
|
||||
* is why the default is '0' and why the whole repository stays
|
||||
* fail-loud by default. Guard 52 pins the default, the way guard 39
|
||||
* pins sniffer_advanced_mode's. */
|
||||
ruleset_safe_start: dm.general.ruleset_safe_start || '0',
|
||||
/* Which empty startup fallbacks the CLI actually wrote, keyed by
|
||||
* rule_set tag. Presence, not a path: the generator emits
|
||||
* `initial_path` only for tags it can point at, so a fallback that
|
||||
* could not be written leaves that rule-set on today's behaviour
|
||||
* rather than pointing at a file that is not there. Pessimistic by
|
||||
* construction - an env that forgot the field (the server path) has
|
||||
* no fallbacks, and the server declares no remote rule-sets anyway. */
|
||||
ruleset_initial: (env?.ruleset_initial || {}),
|
||||
main_urltest_nodes: dm.general.main_urltest_nodes || [],
|
||||
main_urltest_interval: dm.general.main_urltest_interval,
|
||||
main_urltest_tolerance: dm.general.main_urltest_tolerance,
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToInt, strToTime, strToBool, parse_port, HP_DIR } from '../homeproxy-pro.uc';
|
||||
import { isEmpty, strToInt, strToTime, strToBool, parse_port, ruleSetInitialFallback, HP_DIR } from '../homeproxy-pro.uc';
|
||||
|
||||
import { get_outbound, get_resolver, get_ruleset, get_direct_override } from './common.uc';
|
||||
import { BUILTIN_REMOTE_RULE_SETS, declaresBuiltinRemoteRuleSets, get_outbound, get_resolver, get_ruleset, get_direct_override } from './common.uc';
|
||||
|
||||
/* --- shared initial block (every routing mode) ------------------------- */
|
||||
|
||||
@@ -216,8 +216,7 @@ function build_route_proxy(config, dm, ctx, direct_overrides) {
|
||||
* to reach this point without it and sing-box refused the whole config
|
||||
* with "initialize rule[3]: rule-set not found: geoip-cn", which the
|
||||
* health gate turned into a rollback and an unproxied network. */
|
||||
if (ctx.routing_mode === 'bypass_mainland_china'
|
||||
|| ctx.routing_mode === 'proxy_mainland_china') {
|
||||
if (declaresBuiltinRemoteRuleSets(ctx.routing_mode)) {
|
||||
/*
|
||||
* Fetched straight from the upstream SagerNet repositories and
|
||||
* downloaded through the selected node. A direct fetch depends on
|
||||
@@ -237,34 +236,47 @@ function build_route_proxy(config, dm, ctx, direct_overrides) {
|
||||
* server: china-dns); the route layer never matches
|
||||
* it. Sing-box still loads and keeps the rule-set in
|
||||
* memory even when only one block references it, so
|
||||
* removing it would also break the DNS split. It is
|
||||
* only declared for bypass_mainland_china because that
|
||||
* is the only mode whose DNS chain routes through
|
||||
* china-dns.
|
||||
* removing it would also break the DNS split.
|
||||
*
|
||||
* History: a third geosite-noncn used to be declared here as well,
|
||||
* but no rule referenced it and sing-box still loaded and updated
|
||||
* it daily, so it was removed (the comment above was rewritten
|
||||
* during the 2026-09-29 review to clarify that geosite-cn is still
|
||||
* in use - the DNS side - just not from this block).
|
||||
*
|
||||
* The list itself lives in common.uc, because the CLI has to create
|
||||
* an "initial" fallback file for each of these before this generator
|
||||
* runs - see the note on BUILTIN_REMOTE_RULE_SETS. That is the whole
|
||||
* reason this block is a loop: these two are the rule-sets a cold
|
||||
* install blocks on, so a change to them that only reached one of the
|
||||
* two readers would put the fallback out of step with the config
|
||||
* that points at it.
|
||||
*/
|
||||
push(config.route.rule_set, {
|
||||
type: 'remote',
|
||||
tag: 'geoip-cn',
|
||||
format: 'binary',
|
||||
url: 'https://v4.gh-proxy.org/https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs',
|
||||
update_interval: '24h',
|
||||
download_detour: 'main-out'
|
||||
});
|
||||
push(config.route.rule_set, {
|
||||
type: 'remote',
|
||||
tag: 'geosite-cn',
|
||||
format: 'binary',
|
||||
url: 'https://v4.gh-proxy.org/https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs',
|
||||
update_interval: '24h',
|
||||
download_detour: 'main-out'
|
||||
});
|
||||
|
||||
for (let rs in BUILTIN_REMOTE_RULE_SETS) {
|
||||
push(config.route.rule_set, {
|
||||
type: 'remote',
|
||||
tag: rs.tag,
|
||||
format: rs.format,
|
||||
url: rs.url,
|
||||
update_interval: rs.update_interval,
|
||||
/* Only with the opt-in on, and only for a fallback the CLI
|
||||
* actually wrote. A remote rule-set with no initial_path is
|
||||
* fetched during initialization, before the inbounds bind, so
|
||||
* a cold cache makes the first start depend on the CDN - and
|
||||
* on the node, since these go through http_clients. The
|
||||
* fallback is an EMPTY rule-set, which changes routing for
|
||||
* everything it would have matched, so it stays off unless
|
||||
* the user asks for it.
|
||||
*
|
||||
* These two are the rule-sets a fresh install blocks on, so
|
||||
* they are also the whole point of the feature: an earlier
|
||||
* version of this block only knew how to build the entries,
|
||||
* and the CLI had no idea they existed. */
|
||||
initial_path: (ctx.ruleset_safe_start === '1' && ctx.ruleset_initial[rs.tag])
|
||||
? ruleSetInitialFallback([ rs.tag ], rs.format, rs.url) : null,
|
||||
download_detour: 'main-out'
|
||||
});
|
||||
}
|
||||
/* Local, and not downloaded: it is generated from the same
|
||||
* china_ip4.txt the firewall renders homeproxy_mainland_addr_v4
|
||||
* from, so the two cannot drift apart. `type: local` is watched by
|
||||
|
||||
@@ -26,9 +26,81 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, validateHomeProxyPath } from '../homeproxy-pro.uc';
|
||||
import { isEmpty, rulePathRootsText, ruleSetFormatFromPath, ruleSetInitialFallback, strToTime, validateRuleSetPath } from '../homeproxy-pro.uc';
|
||||
|
||||
import { get_outbound, isDirectOutboundTag } from './common.uc';
|
||||
import { get_outbound, isDirectOutboundTag, rule_set_tags } from './common.uc';
|
||||
|
||||
/* --- the `format` a rule-set is emitted with ----------------------------- */
|
||||
|
||||
/* resolveFormat(cfg, ctx) -> 'binary' | 'source' | the declared value | null
|
||||
*
|
||||
* The declared `format` describes the file's NAME; sing-box infers it from
|
||||
* the extension and is wrong in three ways that each reject the whole
|
||||
* configuration at `sing-box check`, so the reload is aborted and the user
|
||||
* sees only "my change did not take":
|
||||
*
|
||||
* 1. an explicit format that names the wrong one
|
||||
* (`format: binary` on a file holding JSON) -> "invalid sing-box
|
||||
* rule-set file"
|
||||
* 2. no explicit format, but the content disagrees with the extension
|
||||
* (example.srs holding JSON) -> the same error, and here the extension
|
||||
* is what misled sing-box
|
||||
* 3. no explicit format and no extension to infer from
|
||||
* -> "route.rule_set[0]: missing format", a different error for the same
|
||||
* underlying mistake
|
||||
*
|
||||
* ctx.ruleset_formats carries what the file's bytes actually are, resolved by
|
||||
* the CLI (guard 27 keeps generator/ off the filesystem). All three cases are
|
||||
* answered by comparing it against what the current arrangement would produce:
|
||||
*
|
||||
* - no verdict -> emit the declared value untouched. The
|
||||
* dominant case (any remote rule-set), and the one that must stay a
|
||||
* no-op: sing-box keeps being the authority.
|
||||
* - declared, and wrong -> the verdict wins, loudly.
|
||||
* - undeclared, and the extension would have been right -> emit nothing.
|
||||
* Adding an explicit value that says what the extension already said
|
||||
* would change the generated bytes of every working rule-set for no
|
||||
* behavioural gain.
|
||||
* - undeclared, and the extension would have been wrong (or there is none)
|
||||
* -> the verdict wins, loudly.
|
||||
*
|
||||
* The correction is announced either way. warn() reaches the user through
|
||||
* $RUN_DIR/homeproxy-pro.log, which is the only place that can say "the field you
|
||||
* filled in does not describe the file you pointed at" - and unlike the
|
||||
* reload's own message, it names the rule-set.
|
||||
*/
|
||||
function resolveFormat(cfg, ctx) {
|
||||
const declared = cfg.format;
|
||||
const probed = ctx.ruleset_formats[cfg.name];
|
||||
|
||||
if (!probed)
|
||||
return declared;
|
||||
|
||||
/* The extension sing-box would have inferred from. initial_path is the
|
||||
* fallback because a remote rule-set with no `path` still has a file the
|
||||
* verdict is about. */
|
||||
const inferred = ruleSetFormatFromPath(cfg.path || cfg.initial_path);
|
||||
|
||||
if (declared) {
|
||||
if (declared === probed)
|
||||
return declared;
|
||||
|
||||
warn(sprintf("homeproxy-pro: rule-set '%s' declares format '%s' but its file is %s; using '%s'.", cfg.name, declared, probed, probed));
|
||||
return probed;
|
||||
}
|
||||
|
||||
if (inferred === probed)
|
||||
return null;
|
||||
|
||||
if (inferred) {
|
||||
warn(sprintf("homeproxy-pro: rule-set '%s' is named '*.%s' (which sing-box reads as %s) but its file is %s; declaring format '%s'.", cfg.name, inferred == 'source' ? 'json' : 'srs', inferred, probed, probed));
|
||||
return probed;
|
||||
}
|
||||
|
||||
/* No extension at all: sing-box has nothing to infer from and reports
|
||||
* "missing format". The verdict is the only thing that can save it. */
|
||||
return probed;
|
||||
};
|
||||
|
||||
/* --- user-defined rule_set entries (custom mode only) ----------------- */
|
||||
|
||||
@@ -41,11 +113,15 @@ export function build_user_rulesets(rule_set_array, dm, ctx) {
|
||||
continue;
|
||||
|
||||
const extra_tags = cfg.extra_tags || [];
|
||||
let rs_tag = 'cfg-' + cfg.name + '-rule';
|
||||
/* One source of truth for the tag list, shared with the CLI that has
|
||||
* to expand a {tag} placeholder before it can check that the files
|
||||
* exist (common.uc's rule_set_tags). A second copy would be free to
|
||||
* drift, and the failure would be quiet: the pre-check would stat
|
||||
* files the running configuration never names. */
|
||||
const all_tags = rule_set_tags(cfg);
|
||||
let rs_tag = all_tags[0];
|
||||
if (length(extra_tags) && cfg.type !== 'inline') {
|
||||
rs_tag = [rs_tag];
|
||||
for (let t in extra_tags)
|
||||
push(rs_tag, 'cfg-' + t + '-rule');
|
||||
rs_tag = all_tags;
|
||||
/* sing-box 1.14: multi-tag requires a {tag} placeholder in the fetch source
|
||||
(remote: url and initial_path, local: path). A missing
|
||||
placeholder makes sing-box try to literal-substitute the
|
||||
@@ -61,24 +137,65 @@ export function build_user_rulesets(rule_set_array, dm, ctx) {
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' uses extra tags but its initial_path lacks a {tag} placeholder.", cfg.name));
|
||||
}
|
||||
|
||||
/* Local rule-set path is read by sing-box as root. The whitelist
|
||||
* is enforced here because the LuCI form's datatype='file' is
|
||||
* only UX - UCI can be set from anywhere on the LAN, and an
|
||||
* arbitrary /etc/passwd would leak the file to anyone who
|
||||
* could write UCI. die() early (same as get_resolver on a
|
||||
* missing/disabled dns_server): silently dropping path left
|
||||
* sing-box checking whatever the field evaluated to, and the
|
||||
* user only saw the reload keep the previous config. */
|
||||
if (cfg.type === 'local' && cfg.path && !validateHomeProxyPath(cfg.path))
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' path '%s' is outside the homeproxy-pro whitelist; choose a path under /etc/homeproxy-pro/.", cfg.name, cfg.path));
|
||||
/* A local rule-set is opened by sing-box as root, so its path is a
|
||||
* file-disclosure surface: the LuCI form's datatype='file' is only
|
||||
* UX, and UCI can be set from anywhere on the LAN. The policy is
|
||||
* RULE_PATH_ROOTS (homeproxy-pro.uc) - the archive this package creates
|
||||
* at install time - and it is deliberately NARROWER than the general
|
||||
* validateHomeProxyPath() gate, which also admits /tmp/homeproxy_*
|
||||
* upload staging and the package's own resource lists.
|
||||
*
|
||||
* die() early, for the reason get_resolver() gives: dropping the
|
||||
* field silently left sing-box checking whatever the field evaluated
|
||||
* to, and the user only saw the reload keep the previous config. */
|
||||
if (cfg.type === 'local' && !isEmpty(cfg.path)) {
|
||||
if (!validateRuleSetPath(cfg.path))
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' path '%s' is outside the allowed rule-set roots (%s); move the file into one of them, or point the path at a file that is already there.", cfg.name, cfg.path, rulePathRootsText()));
|
||||
|
||||
/* The file has to be there too. `sing-box check` runs over this
|
||||
* configuration before it is ever started, and it opens every
|
||||
* local rule_set path - so a rule-set pointing at a file nobody
|
||||
* has put there produced sing-box's own
|
||||
*
|
||||
* parse rule-set[0]: open /etc/homeproxy-pro/ruleset/x.srs: no such file
|
||||
*
|
||||
* which names neither the rule-set nor anything the user can act
|
||||
* on, and surfaced to the user as "new client configuration is
|
||||
* invalid, reload aborted". The archive is created at install
|
||||
* time and before every generation precisely so that this is a
|
||||
* "you have not copied the file yet" message instead of a raw
|
||||
* filesystem error.
|
||||
*
|
||||
* The stat is root's view, taken by the one layer allowed to
|
||||
* touch the filesystem (guard 27) and handed over as
|
||||
* ctx.ruleset_local_ready. Whether the *jailed* sing-box user
|
||||
* can read the file is the runtime's job:
|
||||
* hp_prepare_runtime_files hands the archive to that user on
|
||||
* every start. */
|
||||
if (!ctx.ruleset_local_ready[cfg.name])
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' points at '%s', which is missing, not a regular file, or empty. Put the file in %s and reload.", cfg.name, cfg.path, rulePathRootsText()));
|
||||
}
|
||||
|
||||
const ruleset = {
|
||||
type: cfg.type,
|
||||
tag: rs_tag,
|
||||
format: cfg.format,
|
||||
format: resolveFormat(cfg, ctx),
|
||||
path: cfg.path,
|
||||
url: cfg.url,
|
||||
update_interval: cfg.update_interval
|
||||
/* sing-box parses this as a Go duration, which has no bare-number
|
||||
* form: `update_interval: "3600"` is a hard `time: missing unit
|
||||
* in duration "3600"` that rejects the whole configuration. That
|
||||
* one is measured, on 1.14.2. strToTime() fixes it for the bare
|
||||
* number without inventing a unit table.
|
||||
*
|
||||
* It cannot fix a wrong unit, and the placeholder the form used
|
||||
* to show ("1d") is one: Go's duration units are ns/us/ms/s/m/h
|
||||
* and there is no day - which is why the generator's own
|
||||
* built-in rule-sets say 24h. That is a claim about Go's parser
|
||||
* rather than a measurement of sing-box, so the frontend rejects
|
||||
* the shapes Go cannot read instead of relying on this comment
|
||||
* being right. */
|
||||
update_interval: strToTime(cfg.update_interval)
|
||||
};
|
||||
/* download_detour is a pre-1.14 option that only makes sense for
|
||||
remote rule-sets; emitting it for local/inline ones makes sing-box
|
||||
@@ -86,10 +203,49 @@ export function build_user_rulesets(rule_set_array, dm, ctx) {
|
||||
right below. */
|
||||
if (cfg.type === 'remote')
|
||||
ruleset.download_detour = get_outbound(cfg.outbound, dm) || get_outbound(ctx.default_outbound, dm);
|
||||
if (cfg.type === 'remote' && !isEmpty(cfg.initial_path))
|
||||
/* initial_path is read off the local disk as root; same
|
||||
* whitelist as the local ruleset path. */
|
||||
ruleset.initial_path = validateHomeProxyPath(cfg.initial_path) ? cfg.initial_path : null;
|
||||
/* initial_path is read off the local disk as root, so it answers to
|
||||
* the same rule-set policy as `path` - and it fails LOUD rather than
|
||||
* quietly becoming null. The old form (`... ? value : null`) was the
|
||||
* last asymmetry left in this file: an out-of-policy initial_path was
|
||||
* dropped, the download went back to blocking startup, and the user
|
||||
* was left with a configuration that looked configured and behaved as
|
||||
* if the field were empty. */
|
||||
if (cfg.type === 'remote' && !isEmpty(cfg.initial_path)) {
|
||||
if (!validateRuleSetPath(cfg.initial_path))
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' initial_path '%s' is outside the allowed rule-set roots (%s); move the file into one of them, or clear the field to let sing-box fetch the rule-set on its own.", cfg.name, cfg.initial_path, rulePathRootsText()));
|
||||
ruleset.initial_path = cfg.initial_path;
|
||||
} else if (cfg.type === 'remote' && ctx.ruleset_safe_start === '1') {
|
||||
/* No initial file of the user's own, and the opt-in is on: point at
|
||||
* the empty fallback the CLI wrote before this generator ran.
|
||||
*
|
||||
* ctx.ruleset_initial records PRESENCE, not a path, and every tag
|
||||
* has to be present. sing-box substitutes {tag} and opens every
|
||||
* resulting file, so a multi-tag rule-set whose second file is
|
||||
* missing blocks startup exactly as surely as one whose first is -
|
||||
* measured on 1.14.2, where a literal "{tag}.srs" on disk makes
|
||||
* it fall back to downloading and fail. All-or-nothing is the
|
||||
* only correct answer here.
|
||||
*
|
||||
* Nothing is emitted when the answer is no: the rule-set then
|
||||
* behaves exactly as it does today, which is also what happens if
|
||||
* the CLI could not write the file. */
|
||||
const fallback = ruleSetInitialFallback(all_tags, cfg.format, cfg.url);
|
||||
|
||||
if (fallback) {
|
||||
let ready = true;
|
||||
|
||||
for (let t in all_tags)
|
||||
if (!ctx.ruleset_initial[t]) {
|
||||
ready = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ready)
|
||||
ruleset.initial_path = fallback;
|
||||
else
|
||||
warn(sprintf("homeproxy-pro: rule-set '%s' has no complete empty fallback, so it will be fetched during startup as before.", cfg.name));
|
||||
}
|
||||
}
|
||||
push(rule_set_array, ruleset);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user