fix(singbox): prevent cold-start crash loop on missing community rulesets (#46)

- Bundle universal 17-byte empty SRS stub and add is_valid_srs_file/ensure_empty_srs_stub in rulesets.uc
- Add prepare_community_rulesets in lifecycle.uc to pre-fetch via gh-proxy mirrors or initialize safe local stub before sing-box start
- Support local community rulesets and stubs in generator_routes.uc
- Add fallback to component proxy for list downloads in updates.uc and preserve cached stubs
- Skip non-functional jsdelivr for release downloads and prioritize gh-proxy mirrors in core/url.uc
- Add tests/cold_boot_community_stub.sh testing cold boot fallback behavior
This commit is contained in:
Dushnilin
2026-09-07 23:50:01 +03:00
parent 5539b9701f
commit 203bb9b433
7 changed files with 287 additions and 21 deletions
@@ -727,17 +727,37 @@ function ensure_custom_ruleset(config, reference) {
tag_name = "builtin-" + reference + "-ruleset";
kind = "domains";
if (!ruleset_registered(config, tag_name)) {
let rule_set = {
type: "remote",
tag: tag_name,
format: "binary",
url: runtime_rulesets.community_url(reference)
};
let detour = ctx.download_detour_tag(ctx.runtime_settings());
if (is_valid_detour(config, detour))
rule_set.download_detour = detour;
rule_set.update_interval = remote_ruleset_update_interval();
push(config.route.rule_set, rule_set);
let folder = ctx.runtime_ruleset_folder || runtime_ruleset_folder;
let tmp_srs = folder + "/community-" + reference + ".srs";
let etc_srs = "/etc/tachyon/rulesets/community-" + reference + ".srs";
let local_path = null;
if (runtime_rulesets.is_valid_srs_file(tmp_srs) || helpers.file_is_usable(tmp_srs, 16))
local_path = tmp_srs;
else if (runtime_rulesets.is_valid_srs_file(etc_srs) || helpers.file_is_usable(etc_srs, 16))
local_path = etc_srs;
if (local_path != null) {
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "binary",
path: local_path
});
}
else {
let rule_set = {
type: "remote",
tag: tag_name,
format: "binary",
url: runtime_rulesets.community_url(reference)
};
let detour = ctx.download_detour_tag(ctx.runtime_settings());
if (is_valid_detour(config, detour))
rule_set.download_detour = detour;
rule_set.update_interval = remote_ruleset_update_interval();
push(config.route.rule_set, rule_set);
}
}
return { tag: tag_name, kind };
}
@@ -795,9 +815,9 @@ function ensure_community_ruleset(config, section_name, community) {
let etc_srs = "/etc/tachyon/rulesets/community-" + community + ".srs";
let local_path = null;
if (helpers.file_is_usable(tmp_srs, 100))
if (runtime_rulesets.is_valid_srs_file(tmp_srs) || helpers.file_is_usable(tmp_srs, 16))
local_path = tmp_srs;
else if (helpers.file_is_usable(etc_srs, 100))
else if (runtime_rulesets.is_valid_srs_file(etc_srs) || helpers.file_is_usable(etc_srs, 16))
local_path = etc_srs;
if (local_path != null) {
+49 -2
View File
@@ -1,6 +1,10 @@
#!/usr/bin/env ucode
let common = require("core.common");
let fs = require("fs");
const EMPTY_SRS_B64 = "U1JTAXjaYgAEAAD//wABAAE=";
const EMPTY_SRS_PATH = "/usr/share/tachyon/rulesets/empty.srs";
const SRS_MAIN_URL = "https://github.com/itdoginfo/allow-domains/releases/latest/download";
const SRS_ADS_HAGEZI_PRO_URL = "https://github.com/zxc-rv/ad-filter/releases/latest/download/adlist.srs";
@@ -126,15 +130,54 @@ function remote_format(reference) {
return file_extension(reference) == "json" ? "source" : "binary";
}
function is_valid_srs_file(path) {
let p = as_string(path);
let st = fs.stat(p);
if (!st || st.size < 17)
return false;
let f = fs.open(p, "r");
if (!f)
return false;
let magic = f.read(3);
f.close();
return magic == "SRS";
}
function ensure_empty_srs_stub(target_path) {
target_path = as_string(target_path);
if (is_valid_srs_file(target_path))
return true;
let slash = rindex(target_path, "/");
if (slash > 0)
common.ensure_dir(substr(target_path, 0, slash));
if (is_valid_srs_file(EMPTY_SRS_PATH)) {
let content = fs.readfile(EMPTY_SRS_PATH);
if (content && fs.writefile(target_path, content) != null)
return true;
}
let b = b64dec(EMPTY_SRS_B64);
if (b && fs.writefile(target_path, b) != null)
return true;
return false;
}
function module_exports() {
return {
EMPTY_SRS_PATH,
EMPTY_SRS_B64,
COMMUNITY_SERVICES,
is_community,
community_url,
hash12,
file_extension,
kind_from_reference_hint,
remote_format
remote_format,
is_valid_srs_file,
ensure_empty_srs_stub
};
}
@@ -151,7 +194,11 @@ else if (mode == "kind-from-reference-hint")
print(kind_from_reference_hint(ARGV[1]), "\n");
else if (mode == "remote-format")
print(remote_format(ARGV[1]), "\n");
else if (mode == "is-valid-srs-file")
exit(is_valid_srs_file(ARGV[1]) ? 0 : 1);
else if (mode == "ensure-empty-srs-stub")
exit(ensure_empty_srs_stub(ARGV[1]) ? 0 : 1);
else {
warn("Usage: singbox/rulesets.uc <file-extension|is-community|kind-from-reference-hint|remote-format> ...\n");
warn("Usage: singbox/rulesets.uc <file-extension|is-community|kind-from-reference-hint|remote-format|is-valid-srs-file|ensure-empty-srs-stub> ...\n");
exit(1);
}