mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-04 12:03:56 +08:00
⛅ Sync 2026-09-29 21:45:00
This commit is contained in:
1 parent
a4d6e3e4b9
commit
da77daf88e
191 files changed
+8077
-6247
No files matched your search
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* generator/common.uc: shared helpers used by every generator module.
|
||||
*
|
||||
* Every "select a tag from a UCI-style reference" function lives in this
|
||||
* file because the same selector logic (a routing_node name, an
|
||||
* array-of-names, a direct/block/default marker, a ruleset id list) is
|
||||
* reached by the DNS section, the route section, the outbound/endpoint
|
||||
* builder and the ruleset section. Duplicating them per-module would
|
||||
* drift; consolidating them here keeps the convention "one place to
|
||||
* change a tag shape".
|
||||
*
|
||||
* The Loader owns UCI; nothing in here reads UCI directly. The orchestrator
|
||||
* passes `dm` for the routing_node lookups that need to resolve a name into
|
||||
* the underlying node id.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty } from '../homeproxy-pro.uc';
|
||||
|
||||
import { ConfigQuery } from '../config/model.uc';
|
||||
|
||||
/* parse_port() lives in homeproxy-pro.uc. The Adapter layer needs it
|
||||
* too (EndpointFactory builds the WireGuard endpoint) and an adapter must
|
||||
* not import from generator/, so the util moved to the module both layers
|
||||
* already share. Callers import it from '../homeproxy-pro.uc' now. */
|
||||
|
||||
/* Resolve a UCI outbound-style reference (string or array of strings)
|
||||
* into the sing-box tag the generator should emit. The arrays carry
|
||||
* either 'block-out' / 'direct-out' sentinels or routing_node section
|
||||
* names; a single string is the same thing with one entry.
|
||||
*
|
||||
* `dm` is needed for the routing_node -> node id lookup; we deliberately
|
||||
* do not read `dm.routing.nodes` until we know we need it (the common
|
||||
* case is the sentinel). */
|
||||
export function get_outbound(cfg, dm) {
|
||||
if (isEmpty(cfg))
|
||||
return null;
|
||||
|
||||
if (type(cfg) === 'array') {
|
||||
let outbounds = [];
|
||||
for (let i in cfg)
|
||||
push(outbounds, get_outbound(i, dm));
|
||||
return outbounds;
|
||||
}
|
||||
|
||||
switch (cfg) {
|
||||
case 'block-out':
|
||||
case 'direct-out':
|
||||
return cfg;
|
||||
default:
|
||||
const rn = ConfigQuery.find_by_name(dm.routing.nodes, cfg);
|
||||
if (!rn || isEmpty(rn.node))
|
||||
die(sprintf("%s's node is missing, please check your configuration.", cfg));
|
||||
else if (rn.node === 'urltest')
|
||||
return 'cfg-' + cfg + '-out';
|
||||
else
|
||||
return 'cfg-' + rn.node + '-out';
|
||||
}
|
||||
};
|
||||
|
||||
/* Find a loaded section by name, treating `enabled` as a gate. The Loader
|
||||
* normalises `enabled` on every list section; a section with the field unset
|
||||
* is enabled (boolean sections default to 0 in UCI, but a dns_server/ruleset
|
||||
* without the option is a valid, enabled row).
|
||||
*
|
||||
* Declared before its callers on purpose: ucode resolves function references
|
||||
* at call time from what has already been evaluated, so a `function` statement
|
||||
* placed after get_resolver() fails with "access to undeclared variable". */
|
||||
function find_enabled(items, name) {
|
||||
const item = ConfigQuery.find_by_name(items || [], name);
|
||||
if (!item)
|
||||
return null;
|
||||
if (item.enabled === false || item.enabled === '0')
|
||||
return null;
|
||||
return item;
|
||||
};
|
||||
|
||||
/* Resolve a UCI resolver reference (a dns_server section name or one of
|
||||
* the sentinels) into a sing-box resolver tag.
|
||||
*
|
||||
* `dm` is required for the same reason get_outbound() needs it: a reference to
|
||||
* a section that was deleted or disabled produces `cfg-<name>-dns`, which
|
||||
* nothing defines - sing-box then rejects the whole configuration with
|
||||
* "dns server not found", the reload silently keeps the previous one, and the
|
||||
* user only sees that their change did not take. Fail with the name instead. */
|
||||
export function get_resolver(cfg, dm) {
|
||||
if (isEmpty(cfg))
|
||||
return null;
|
||||
|
||||
switch (cfg) {
|
||||
case 'default-dns':
|
||||
case 'system-dns':
|
||||
return cfg;
|
||||
default:
|
||||
if (dm && !find_enabled((dm.dns || {}).servers, cfg))
|
||||
die(sprintf("the DNS server '%s' does not exist or is disabled; check the rule that refers to it.", cfg));
|
||||
return 'cfg-' + cfg + '-dns';
|
||||
}
|
||||
};
|
||||
|
||||
/* Resolve a UCI ruleset reference list (array of ruleset section names)
|
||||
* into the corresponding sing-box rule_set tag list. Same existence gate as
|
||||
* get_resolver(): ruleset.uc skips disabled entries, so a reference to one
|
||||
* would leave a dangling `cfg-<name>-rule` tag behind. */
|
||||
export function get_ruleset(cfg, dm) {
|
||||
if (isEmpty(cfg))
|
||||
return null;
|
||||
|
||||
let rules = [];
|
||||
for (let i in cfg) {
|
||||
if (isEmpty(i))
|
||||
continue;
|
||||
if (dm && !find_enabled((dm.routing || {}).rulesets, i))
|
||||
die(sprintf("the rule-set '%s' does not exist or is disabled; check the rule that refers to it.", i));
|
||||
push(rules, 'cfg-' + i + '-rule');
|
||||
}
|
||||
return rules;
|
||||
};
|
||||
|
||||
/* 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
|
||||
* carries override_address / override_port; this helper just looks it
|
||||
* up. Returns null when the target is not a direct node or no override
|
||||
* is configured. */
|
||||
export function get_direct_override(outbound_selector, dm, direct_overrides) {
|
||||
if (type(outbound_selector) === 'array' || isEmpty(outbound_selector))
|
||||
return null;
|
||||
|
||||
switch (outbound_selector) {
|
||||
case 'direct-out':
|
||||
case 'block-out':
|
||||
return null;
|
||||
default:
|
||||
const rn = ConfigQuery.find_by_name(dm.routing.nodes, outbound_selector);
|
||||
const node = rn && rn.node;
|
||||
return (!isEmpty(node) && node !== 'urltest') ? (direct_overrides[node] || null) : null;
|
||||
}
|
||||
};
|
||||
|
||||
/* True when `tag` (the sing-box outbound tag) belongs to a direct outbound.
|
||||
* Used by the http_clients builder to drop the detour field on a pure-TUN
|
||||
* setup: sing-box 1.14 rejects detouring to a direct outbound that has
|
||||
* nothing to detour through. */
|
||||
export function isDirectOutboundTag(tag, dm) {
|
||||
if (isEmpty(tag) || tag === 'block-out')
|
||||
return false;
|
||||
if (tag === 'direct-out')
|
||||
return true;
|
||||
|
||||
/* The caller passes the tag the generator emits - get_outbound() builds
|
||||
* `cfg-<node>-out` - not the routing_node section name, so that wrapper has
|
||||
* to be undone before the lookup. Without this the two queries below were
|
||||
* given a name that matches neither table, and the function returned false
|
||||
* for every routing_node: build_http_clients() then kept a `detour` on a
|
||||
* rule-set download in pure TUN mode, where sing-box refuses it ("detour to
|
||||
* an empty direct outbound makes no sense") and rejects the whole
|
||||
* configuration. */
|
||||
const unwrapped = match(tag, /^cfg-(.+)-out$/);
|
||||
const name = unwrapped ? unwrapped[1] : tag;
|
||||
|
||||
const rn = ConfigQuery.find_by_name(dm.routing.nodes, name);
|
||||
const node_name = (rn && rn.node) || name;
|
||||
const node = ConfigQuery.node_by_id(dm, node_name);
|
||||
return !!(node && node.type === 'direct');
|
||||
};
|
||||
|
||||
/* Append the sing-box JSON $schema field. Kept here because the value is
|
||||
* fixed and the two generators used to spell it the same way; if it ever
|
||||
* changes, both should pick up the new URL automatically. */
|
||||
export function attachSchema(config) {
|
||||
config['$schema'] = 'https://sing-box.sagernet.org/schema.json';
|
||||
return config;
|
||||
};
|
||||
|
||||
/* Attach the experimental cache_file block. Every routing mode benefits
|
||||
* from it: bypass_mainland_china and custom match a lot of DNS, and the
|
||||
* other modes still resolve the proxy node hostname and resolve any
|
||||
* domain the user clicks through. cache_file is a no-op on a fresh
|
||||
* install (no cache to read) and the file is the only piece of state the
|
||||
* generator writes outside /etc/config/homeproxy-pro. Routing-mode gating
|
||||
* used to limit it to bypass_mainland_china + custom, which left
|
||||
* gfwlist / proxy_mainland_china / global with cold-start DNS on every
|
||||
* reload and made the difference between modes hard to explain.
|
||||
*
|
||||
* Note: docs/linux.json 与 pro 的差距分析.md §2.5 once asked for an
|
||||
* explicit `reverse_mapping: true` here (so a future sing-box change to
|
||||
* the default would not silently move us off the mapping table). The
|
||||
* sing-box 1.14.0-r1 we test against rejects that field as
|
||||
* "json: unknown field" - the field is recognised by newer builds but
|
||||
* not the floor pinned by arch-guard 31. sing-box's default is already
|
||||
* `true`, so leaving it implicit matches runtime behaviour and keeps the
|
||||
* generated config compatible with the test target. Revisit if the
|
||||
* floor moves past the release where the field was introduced. */
|
||||
export function attachExperimental(config, routing_mode, dns_store_dns) {
|
||||
config.experimental = {
|
||||
cache_file: {
|
||||
enabled: true,
|
||||
path: '/etc/homeproxy-pro/cache.db',
|
||||
store_dns: (dns_store_dns === '1') ? true : null
|
||||
}
|
||||
};
|
||||
return config;
|
||||
};
|
||||
Reference in new issue
Block a user