- 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
204 lines
6.1 KiB
Ucode
204 lines
6.1 KiB
Ucode
#!/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";
|
|
const SRS_SUPERCELL_URL = "https://raw.githubusercontent.com/ushan0v/sing-box-supercell-ruleset/main/supercell.srs";
|
|
const SRS_GITHUB_URL = "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/github.srs";
|
|
const SRS_GEOIP_RU_URL = "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/ru.srs";
|
|
const SRS_GEOSITE_RU_URL = "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/category-ru.srs";
|
|
const SRS_GEOIP_US_URL = "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/us.srs";
|
|
const SRS_GEOIP_CN_URL = "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/cn.srs";
|
|
|
|
const COMMUNITY_SERVICES = {
|
|
russia_inside: true,
|
|
russia_outside: true,
|
|
ukraine_inside: true,
|
|
geoblock: true,
|
|
block: true,
|
|
porn: true,
|
|
news: true,
|
|
anime: true,
|
|
youtube: true,
|
|
hdrezka: true,
|
|
tiktok: true,
|
|
google_ai: true,
|
|
google_play: true,
|
|
hodca: true,
|
|
discord: true,
|
|
meta: true,
|
|
twitter: true,
|
|
cloudflare: true,
|
|
cloudfront: true,
|
|
digitalocean: true,
|
|
hetzner: true,
|
|
ovh: true,
|
|
telegram: true,
|
|
roblox: true,
|
|
ads_hagezi_pro: true,
|
|
supercell: true,
|
|
github: true,
|
|
geoip_ru: true,
|
|
geosite_ru: true,
|
|
geoip_us: true,
|
|
geoip_cn: true
|
|
};
|
|
|
|
let as_string = common.as_string;
|
|
|
|
function is_community(name) {
|
|
name = as_string(name);
|
|
if (COMMUNITY_SERVICES[name] === true)
|
|
return true;
|
|
if (match(name, /^geoip_[a-z]{2}$/) != null || match(name, /^geosite_[a-z]{2}$/) != null)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
function community_url(name) {
|
|
name = as_string(name);
|
|
if (name == "ads_hagezi_pro")
|
|
return SRS_ADS_HAGEZI_PRO_URL;
|
|
if (name == "supercell")
|
|
return SRS_SUPERCELL_URL;
|
|
if (name == "github")
|
|
return SRS_GITHUB_URL;
|
|
if (name == "geosite_ru")
|
|
return SRS_GEOSITE_RU_URL;
|
|
|
|
let geoip_match = match(name, /^geoip_([a-z]{2})$/);
|
|
if (geoip_match)
|
|
return "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/" + geoip_match[1] + ".srs";
|
|
|
|
let geosite_match = match(name, /^geosite_([a-z]{2})$/);
|
|
if (geosite_match)
|
|
return "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/" + geosite_match[1] + ".srs";
|
|
|
|
return SRS_MAIN_URL + "/" + name + ".srs";
|
|
}
|
|
|
|
function hash12(value) {
|
|
value = as_string(value);
|
|
let first = 2166136261;
|
|
let second = 16777619;
|
|
|
|
for (let i = 0; i < length(value); i++) {
|
|
let code = ord(substr(value, i, 1));
|
|
first = (first * 33 + code) % 4294967296;
|
|
second = (second * 131 + code) % 4294967296;
|
|
}
|
|
|
|
return sprintf("%06x%06x", first % 16777216, second % 16777216);
|
|
}
|
|
|
|
function file_extension(value) {
|
|
let basename = as_string(value);
|
|
let slash = rindex(basename, "/");
|
|
if (slash >= 0)
|
|
basename = substr(basename, slash + 1);
|
|
|
|
let query = index(basename, "?");
|
|
if (query >= 0)
|
|
basename = substr(basename, 0, query);
|
|
|
|
let fragment = index(basename, "#");
|
|
if (fragment >= 0)
|
|
basename = substr(basename, 0, fragment);
|
|
|
|
let dot = rindex(basename, ".");
|
|
return dot >= 0 ? lc(substr(basename, dot + 1)) : "";
|
|
}
|
|
|
|
function kind_from_reference_hint(reference) {
|
|
reference = lc(as_string(reference));
|
|
if (index(reference, "geosite") >= 0 || index(reference, "domain") >= 0 ||
|
|
index(reference, "domains") >= 0 || index(reference, "adguard") >= 0 ||
|
|
index(reference, "filter") >= 0)
|
|
return "domains";
|
|
if (index(reference, "geoip") >= 0 || index(reference, "subnet") >= 0 ||
|
|
index(reference, "subnets") >= 0 || index(reference, "cidr") >= 0)
|
|
return "subnets";
|
|
return "unknown";
|
|
}
|
|
|
|
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,
|
|
is_valid_srs_file,
|
|
ensure_empty_srs_stub
|
|
};
|
|
}
|
|
|
|
if ((sourcepath(1) != null && sourcepath(1) != "") || ARGV[0] == null)
|
|
return module_exports();
|
|
|
|
let mode = ARGV[0] || "";
|
|
|
|
if (mode == "file-extension")
|
|
print(file_extension(ARGV[1]), "\n");
|
|
else if (mode == "is-community")
|
|
exit(is_community(ARGV[1]) ? 0 : 1);
|
|
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|is-valid-srs-file|ensure-empty-srs-stub> ...\n");
|
|
exit(1);
|
|
} |