246 lines
11 KiB
Bash
246 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
|
MIGRATION="$TACHYON_LIB/config/migration.uc"
|
|
WORK_DIR="$(mktemp -d)"
|
|
|
|
cleanup() {
|
|
rm -rf "$WORK_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
fail() {
|
|
printf 'FAIL: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
[ -s "$MIGRATION" ] || fail "runtime configuration migration module is missing"
|
|
|
|
# ─── 1. Test NetShift and Early Podkop Fixture Migration ─────────────────────
|
|
node >"$WORK_DIR/predecessors_fixture.json" <<'NODE'
|
|
const fixture = {
|
|
settings: {
|
|
'.name': 'settings',
|
|
'.type': 'settings',
|
|
dns_via_outbound: '1',
|
|
dns_outbound_section: 'sec_proxy',
|
|
block_doh: '1',
|
|
dns_server: '8.8.8.8, 1.1.1.1',
|
|
bootstrap_dns_server: '77.88.8.8 9.9.9.9'
|
|
},
|
|
rule: [
|
|
{
|
|
'.name': 'sec_proxy',
|
|
'.type': 'rule',
|
|
enabled: '1',
|
|
connection_type: 'proxy',
|
|
proxy_config_type: 'selector_text',
|
|
selector_proxy_links_text: 'vless://b1234567-89ab-cdef-0123-456789abcdef@proxy1.example.com:443?encryption=none&security=reality&sni=test.com&fp=chrome&pbk=publicKey&type=tcp#ProxyOne\n\n# comment\nss://YWVzLTEyOC1nY206cGFzc3dvcmQ@proxy2.example.com:8388#ProxyTwo'
|
|
},
|
|
{
|
|
'.name': 'sec_urltest',
|
|
'.type': 'rule',
|
|
enabled: '1',
|
|
connection_type: 'proxy',
|
|
proxy_config_type: 'urltest_text',
|
|
urltest_proxy_links_text: 'trojan://password@proxy3.example.com:443#TrojanOne\nvmess://eyJ2IjoiMiIsInBzIjoiVm1lc3NPbmUiLCJhZGQiOiJwcm94eTQuZXhhbXBsZS5jb20iLCJwb3J0IjoiNDQzIiwiaWQiOiJiMTIzNDU2Ny04OWFiLWNkZWYtMDEyMy00NTY3ODlhYmNkZWYiLCJhaWQiOiIwIiwic2N5IjoiYXV0byIsIm5ldCI6IndzIiwidHlwZSI6Im5vbmUiLCJob3N0IjoidGVzdC5jb20iLCJwYXRoIjoiL3dzIiwidGxzIjoidGxzIn0=',
|
|
urltest_idle_timeout: '25m',
|
|
urltest_interrupt_exist_connections: '1',
|
|
urltest_interval: '15m'
|
|
},
|
|
{
|
|
'.name': 'sec_sub',
|
|
'.type': 'rule',
|
|
enabled: '1',
|
|
connection_type: 'proxy',
|
|
proxy_config_type: 'subscription',
|
|
subscription_url: [
|
|
'https://feed1.example.com/sub.txt',
|
|
'https://feed2.example.com/sub.txt'
|
|
],
|
|
subscription_insecure: '1',
|
|
subscription_format_preference: 'auto',
|
|
subscription_group_mode: 'none',
|
|
subscription_group_prefix_len: '2',
|
|
subscription_filter_include_keywords: 'US|NL',
|
|
subscription_filter_exclude_keywords: 'RU|BY',
|
|
global_proxy: '1'
|
|
},
|
|
{
|
|
'.name': 'sec_early_podkop',
|
|
'.type': 'rule',
|
|
enabled: '1',
|
|
connection_type: 'proxy',
|
|
proxy_config_type: 'url',
|
|
proxy_string: 'vless://b1234567-89ab-cdef-0123-456789abcdef@single.example.com:443?encryption=none&security=reality&sni=test.com&fp=chrome&pbk=publicKey&type=tcp#Single',
|
|
domain_list_urls: [
|
|
'https://antifilter.download/list/domains.lst'
|
|
],
|
|
subnet_list_urls: [
|
|
'https://antifilter.download/list/ip.lst'
|
|
]
|
|
}
|
|
]
|
|
};
|
|
|
|
process.stdout.write(`${JSON.stringify(fixture, null, 2)}\n`);
|
|
NODE
|
|
|
|
TACHYON_LIB="$TACHYON_LIB" ucode -L "$TACHYON_LIB" "$MIGRATION" migrate-fixture "$WORK_DIR/predecessors_fixture.json" podkop >"$WORK_DIR/output.json"
|
|
|
|
node - "$WORK_DIR/output.json" <<'NODE'
|
|
const fs = require('fs');
|
|
const out = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
const config = out.config;
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
console.error(`ASSERTION FAILED: ${message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// 1. Settings checks
|
|
assert(config.settings.dns_detour_enabled === '1', 'NetShift dns_via_outbound must map to dns_detour_enabled=1');
|
|
assert(config.settings.dns_detour_section === 'sec_proxy', 'NetShift dns_outbound_section must map to dns_detour_section');
|
|
assert(config.settings.block_doh === undefined, 'NetShift block_doh must be cleaned up');
|
|
assert(Array.isArray(config.settings.dns_server) && config.settings.dns_server.length === 2, 'Comma-separated dns_server must be normalized to array');
|
|
assert(config.settings.dns_server[0] === '8.8.8.8' && config.settings.dns_server[1] === '1.1.1.1', 'dns_server entries must match split elements');
|
|
assert(Array.isArray(config.settings.bootstrap_dns_server) && config.settings.bootstrap_dns_server.length === 2, 'Space-separated bootstrap_dns_server must be normalized to array');
|
|
assert(config.settings.bootstrap_dns_server[0] === '77.88.8.8' && config.settings.bootstrap_dns_server[1] === '9.9.9.9', 'bootstrap_dns_server entries must match split elements');
|
|
|
|
const sections = Object.fromEntries(config.section.map(s => [s['.name'], s]));
|
|
|
|
// 2. NetShift selector_text rule migrated to connection with selector_proxy_links
|
|
const secProxy = sections['sec_proxy'];
|
|
assert(secProxy !== undefined, 'sec_proxy section must exist');
|
|
assert(secProxy.action === 'connection', 'sec_proxy must be migrated to action=connection');
|
|
assert(secProxy.proxy_config_type === undefined, 'proxy_config_type must be deleted in Tachyon unified schema');
|
|
assert(Array.isArray(secProxy.selector_proxy_links) && secProxy.selector_proxy_links.length === 2, `Expected 2 selector_proxy_links for sec_proxy, got ${JSON.stringify(secProxy.selector_proxy_links)}`);
|
|
assert(secProxy.selector_proxy_links[0].startsWith('vless://'), 'First proxy link must be preserved');
|
|
assert(secProxy.selector_proxy_links[1].startsWith('ss://'), 'Second proxy link must be preserved');
|
|
|
|
// 3. NetShift urltest_text rule migrated to connection with child urltest
|
|
const secUrltest = sections['sec_urltest'];
|
|
assert(secUrltest !== undefined, 'sec_urltest section must exist');
|
|
assert(secUrltest.action === 'connection', 'sec_urltest must be migrated to action=connection');
|
|
assert(secUrltest.proxy_config_type === undefined, 'proxy_config_type must be deleted in Tachyon unified schema');
|
|
assert(Array.isArray(secUrltest.selector_proxy_links) && secUrltest.selector_proxy_links.length === 2, `Expected 2 selector_proxy_links for sec_urltest, got ${JSON.stringify(secUrltest.selector_proxy_links)}`);
|
|
const childUrltests = config.urltest || [];
|
|
const urltestItem = childUrltests.find(u => u.section === 'sec_urltest');
|
|
assert(urltestItem !== undefined, 'child urltest section must exist for sec_urltest');
|
|
assert(urltestItem.idle_timeout === '25m', 'urltest_idle_timeout must be inherited into child urltest');
|
|
assert(urltestItem.interrupt_exist_connections === '1', 'urltest_interrupt_exist_connections must be inherited into child urltest');
|
|
assert(urltestItem.check_interval === '15m', 'urltest check_interval must be preserved in child urltest');
|
|
|
|
// 4. NetShift multi-feed subscription_url
|
|
const secSub = sections['sec_sub'];
|
|
assert(secSub !== undefined, 'sec_sub section must exist');
|
|
assert(secSub.action === 'connection', 'sec_sub must be migrated to action=connection');
|
|
assert(secSub.proxy_config_type === undefined, 'proxy_config_type must be deleted in Tachyon unified schema');
|
|
const childSubs = config.subscription_url || [];
|
|
const subItems = childSubs.filter(s => s.section === 'sec_sub');
|
|
assert(subItems.length === 2, `Multi-feed subscription_url must generate 2 child subscription_url sections, got ${subItems.length}`);
|
|
assert(subItems[0].url === 'https://feed1.example.com/sub.txt', 'First subscription URL must be preserved');
|
|
assert(subItems[1].url === 'https://feed2.example.com/sub.txt', 'Second subscription URL must be preserved');
|
|
|
|
// Verify obsolete NetShift options were cleaned up
|
|
assert(secSub.subscription_insecure === undefined, 'subscription_insecure must be cleaned up');
|
|
assert(secSub.subscription_format_preference === undefined, 'subscription_format_preference must be cleaned up');
|
|
assert(secSub.subscription_group_mode === undefined, 'subscription_group_mode must be cleaned up');
|
|
assert(secSub.global_proxy === undefined, 'global_proxy must be cleaned up');
|
|
|
|
// 5. Early Podkop domain_list_urls and subnet_list_urls
|
|
const secEarly = sections['sec_early_podkop'];
|
|
assert(secEarly !== undefined, 'sec_early_podkop section must exist');
|
|
assert(Array.isArray(secEarly.remote_domain_lists) && secEarly.remote_domain_lists.includes('https://antifilter.download/list/domains.lst'), 'domain_list_urls must migrate to remote_domain_lists');
|
|
assert(Array.isArray(secEarly.remote_subnet_lists) && secEarly.remote_subnet_lists.includes('https://antifilter.download/list/ip.lst'), 'subnet_list_urls must migrate to remote_subnet_lists');
|
|
assert(secEarly.domain_list_urls === undefined, 'domain_list_urls key must be deleted');
|
|
assert(secEarly.subnet_list_urls === undefined, 'subnet_list_urls key must be deleted');
|
|
|
|
console.log('Predecessor fixtures assertion passed successfully.');
|
|
NODE
|
|
|
|
# ─── 2. Test Format Detection for All Predecessors ───────────────────────────
|
|
cat >"$WORK_DIR/mock_netshift.conf" <<'EOF'
|
|
config settings 'settings'
|
|
option enabled '1'
|
|
option dns_via_outbound '1'
|
|
option dns_outbound_section 'main_proxy'
|
|
|
|
config rule 'main_proxy'
|
|
option enabled '1'
|
|
option proxy_config_type 'selector_text'
|
|
option selector_proxy_links_text 'vless://example'
|
|
EOF
|
|
|
|
cat >"$WORK_DIR/mock_forkop.conf" <<'EOF'
|
|
config settings 'settings'
|
|
option enabled '1'
|
|
|
|
config section 'my_wg'
|
|
option enabled '1'
|
|
option connection_type 'vpn'
|
|
option proxy_config_type 'interface'
|
|
option interface 'wg0'
|
|
EOF
|
|
|
|
cat >"$WORK_DIR/mock_podkop.conf" <<'EOF'
|
|
config settings 'settings'
|
|
option enabled '1'
|
|
|
|
config rule 'antifilter'
|
|
option enabled '1'
|
|
option connection_type 'proxy'
|
|
option proxy_config_type 'url'
|
|
list domain_list_urls 'https://antifilter.download/list/domains.lst'
|
|
EOF
|
|
|
|
cat >"$WORK_DIR/mock_tachyon.conf" <<'EOF'
|
|
config settings 'settings'
|
|
option enabled '1'
|
|
option config_version '1.0.5'
|
|
list applied_migrations 'interface_sections'
|
|
list applied_migrations 'ensure_dns_server_defaults'
|
|
|
|
config section 'my_proxy'
|
|
option enabled '1'
|
|
option action 'connection'
|
|
list selector_proxy_links 'vless://example'
|
|
EOF
|
|
|
|
cat >"$WORK_DIR/detect_test.uc" <<'UCODE'
|
|
let migration = require("config.migration");
|
|
|
|
function assert(val, msg) {
|
|
if (!val) {
|
|
warn("ASSERTION FAILED: " + msg + "\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
let ns_src = migration.detect_config_migration_source(ARGV[0]);
|
|
assert(ns_src == "podkop", "NetShift config must be detected as legacy podkop format, got: " + ns_src);
|
|
|
|
let fk_src = migration.detect_config_migration_source(ARGV[1]);
|
|
assert(fk_src == "podkop", "Forkop config must be detected as legacy podkop format, got: " + fk_src);
|
|
|
|
let pk_src = migration.detect_config_migration_source(ARGV[2]);
|
|
assert(pk_src == "podkop", "Podkop config must be detected as legacy podkop format, got: " + pk_src);
|
|
|
|
let tc_src = migration.detect_config_migration_source(ARGV[3]);
|
|
assert(tc_src == "tachyon", "Tachyon config must be detected as native tachyon format, got: " + tc_src);
|
|
|
|
print("Format detection tests passed successfully.\n");
|
|
UCODE
|
|
|
|
ucode -L "$TACHYON_LIB" "$WORK_DIR/detect_test.uc" \
|
|
"$WORK_DIR/mock_netshift.conf" \
|
|
"$WORK_DIR/mock_forkop.conf" \
|
|
"$WORK_DIR/mock_podkop.conf" \
|
|
"$WORK_DIR/mock_tachyon.conf"
|
|
|
|
printf 'ALL predecessor migration tests passed successfully.\n'
|