Files
NeoTachyon/tests/predecessors_migration.sh
T

265 lines
12 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'
option block_doh '1'
list applied_migrations 'interface_sections'
list applied_migrations 'ensure_dns_server_defaults'
config section 'my_proxy'
option enabled '1'
option action 'connection'
option ip_cidr '17.0.0.0/8'
option selector_proxy_links_text 'vless://example'
list selector_proxy_links 'vless://example'
EOF
cat >"$WORK_DIR/mock_tachyon_minimal.conf" <<'EOF'
config settings 'settings'
option enabled '1'
option block_doh '1'
config section 'my_proxy'
option enabled '1'
option action 'connection'
option ip_cidr '192.168.10.0/24'
option domain 'chatgpt.com, openai.com, oaistatic.com'
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);
let tc_min_src = migration.detect_config_migration_source(ARGV[4]);
assert(tc_min_src == "tachyon", "Minimal Tachyon config with ip_cidr must be detected as tachyon format, got: " + tc_min_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" \
"$WORK_DIR/mock_tachyon_minimal.conf"
printf 'ALL predecessor migration tests passed successfully.\n'