mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-13 20:08:05 +08:00
173 lines
8.3 KiB
JavaScript
173 lines
8.3 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { spawnSync } = require('node:child_process');
|
|
|
|
const root = path.resolve(__dirname, '..');
|
|
const script = path.join(root, 'root/usr/lib/minigate/proxy.sh');
|
|
const HEADER = '# MiniGate 局域网直连解析,由 proxy.sh 自动生成';
|
|
|
|
const SITES = {
|
|
'000_default_2000_1_1_.conf': 'server {\n listen unix:/var/run/minigate/https_2000.sock default_server ssl proxy_protocol;\n server_name _;\n return 444;\n}\n',
|
|
'site_1.conf': 'server {\n listen unix:/var/run/minigate/https_2000.sock ssl proxy_protocol;\n server_name ly.example.com;\n}\nserver {\n listen unix:/var/run/minigate/http_2000.sock proxy_protocol;\n server_name ly.example.com;\n return 308 https://ly.example.com:2000$request_uri;\n}\n',
|
|
'site_2.conf': 'server {\n listen 2000 ssl;\n server_name box.example.com;\n}\n',
|
|
'site_3.conf': 'server {\n listen 443 ssl;\n server_name *.wild.example.com;\n}\n'
|
|
};
|
|
|
|
function sandbox({ sites = SITES, hosts } = {}) {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'minigate-lan-dns-test-'));
|
|
const sitesDir = path.join(dir, 'sites');
|
|
fs.mkdirSync(sitesDir);
|
|
for (const [name, content] of Object.entries(sites)) fs.writeFileSync(path.join(sitesDir, name), content);
|
|
const hostsFile = path.join(dir, 'hosts', 'minigate');
|
|
if (hosts !== undefined) {
|
|
fs.mkdirSync(path.dirname(hostsFile));
|
|
fs.writeFileSync(hostsFile, hosts);
|
|
}
|
|
return { dir, sitesDir, hostsFile, hupLog: path.join(dir, 'hup.log'), logFile: path.join(dir, 'proxy.log') };
|
|
}
|
|
|
|
// 以库模式加载 proxy.sh,替换 uci/ubus/pidof/send_hup 后执行一段命令
|
|
function runLibrary(box, body, { lanDns = '1', lanIp = '', ifaceIp = '192.168.2.2', networkIp = '' } = {}) {
|
|
const command = `
|
|
set -u
|
|
export MINIGATE_PROXY_LIBRARY_ONLY=1
|
|
export MINIGATE_SITES_DIR=${JSON.stringify(box.sitesDir)}
|
|
export MINIGATE_LAN_HOSTS_FILE=${JSON.stringify(box.hostsFile)}
|
|
export MINIGATE_LOGFILE=${JSON.stringify(box.logFile)}
|
|
. ${JSON.stringify(script)}
|
|
uci() {
|
|
[ "\${1:-}" = "-q" ] && shift
|
|
case "\${1:-}:\${2:-}" in
|
|
get:minigate.global.lan_dns) printf '%s\\n' ${JSON.stringify(lanDns)} ;;
|
|
get:minigate.global.lan_ip) [ -n ${JSON.stringify(lanIp)} ] && printf '%s\\n' ${JSON.stringify(lanIp)} ;;
|
|
get:network.lan.ipaddr) [ -n ${JSON.stringify(networkIp)} ] && printf '%s\\n' ${JSON.stringify(networkIp)} ;;
|
|
esac
|
|
return 0
|
|
}
|
|
ubus() { [ -n ${JSON.stringify(ifaceIp)} ] && printf '%s\\n' ${JSON.stringify(ifaceIp)}; }
|
|
jsonfilter() { cat; }
|
|
pidof() { printf '101 102\\n'; }
|
|
send_hup() { printf '%s\\n' "$*" >> ${JSON.stringify(box.hupLog)}; }
|
|
${body}
|
|
`;
|
|
return spawnSync('/bin/sh', ['-c', command], { encoding: 'utf8' });
|
|
}
|
|
|
|
const read = file => (fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null);
|
|
const hups = box => (read(box.hupLog) || '').split('\n').filter(Boolean);
|
|
|
|
test('enabled sync maps every concrete site domain to the LAN address and reloads dnsmasq once', () => {
|
|
const box = sandbox();
|
|
let result = runLibrary(box, 'sync_lan_hosts');
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(read(box.hostsFile), `${HEADER}\n192.168.2.2 box.example.com\n192.168.2.2 ly.example.com\n`);
|
|
assert.deepEqual(hups(box), ['101 102']);
|
|
assert.match(read(box.logFile), /局域网直连解析 -> 192\.168\.2\.2: box\.example\.com ly\.example\.com/);
|
|
|
|
// 内容不变时不再重载,避免每次保存都清空 dnsmasq 缓存
|
|
result = runLibrary(box, 'sync_lan_hosts');
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.deepEqual(hups(box), ['101 102']);
|
|
});
|
|
|
|
test('a changed address rewrites the file and reloads again', () => {
|
|
const box = sandbox({ hosts: `${HEADER}\n192.168.2.9 box.example.com\n192.168.2.9 ly.example.com\n` });
|
|
const result = runLibrary(box, 'sync_lan_hosts');
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.match(read(box.hostsFile), /^192\.168\.2\.2 box\.example\.com$/m);
|
|
assert.deepEqual(hups(box), ['101 102']);
|
|
});
|
|
|
|
test('a manual LAN address takes priority over the interface address', () => {
|
|
const box = sandbox();
|
|
const result = runLibrary(box, 'sync_lan_hosts', { lanIp: '10.0.0.5' });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(read(box.hostsFile), `${HEADER}\n10.0.0.5 box.example.com\n10.0.0.5 ly.example.com\n`);
|
|
});
|
|
|
|
test('without interface status the first network.lan.ipaddr entry is used without its prefix', () => {
|
|
const box = sandbox();
|
|
const result = runLibrary(box, 'get_lan_ip', { ifaceIp: '', networkIp: '192.168.9.1/24 192.168.10.1/24' });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(result.stdout.trim(), '192.168.9.1');
|
|
});
|
|
|
|
test('an invalid LAN address writes nothing and removes a stale file', () => {
|
|
const box = sandbox({ hosts: `${HEADER}\n192.168.2.2 box.example.com\n` });
|
|
const result = runLibrary(box, 'sync_lan_hosts', { lanIp: '192.168.1.300' });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(read(box.hostsFile), null);
|
|
assert.deepEqual(hups(box), ['101 102']);
|
|
assert.match(read(box.logFile), /未取得有效的 LAN IPv4 地址/);
|
|
});
|
|
|
|
test('disabling removes the file once and does not reload when nothing was written', () => {
|
|
const box = sandbox({ hosts: `${HEADER}\n192.168.2.2 box.example.com\n` });
|
|
let result = runLibrary(box, 'sync_lan_hosts', { lanDns: '0' });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(read(box.hostsFile), null);
|
|
assert.deepEqual(hups(box), ['101 102']);
|
|
|
|
result = runLibrary(box, 'sync_lan_hosts', { lanDns: '0' });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.deepEqual(hups(box), ['101 102']);
|
|
});
|
|
|
|
test('no generated sites means no hosts file', () => {
|
|
const box = sandbox({ sites: { '000_default_443_1_0_.conf': 'server {\n server_name _;\n}\n' } });
|
|
const result = runLibrary(box, 'sync_lan_hosts');
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(read(box.hostsFile), null);
|
|
assert.deepEqual(hups(box), []);
|
|
});
|
|
|
|
test('reload syncs LAN hosts only after nginx accepted the new configuration', () => {
|
|
const box = sandbox();
|
|
const body = ok => `
|
|
PID_FILE=${JSON.stringify(path.join(box.dir, 'missing.pid'))}
|
|
prepare_config() { return 0; }
|
|
start_prepared() { return ${ok ? 0 : 1}; }
|
|
sync_lan_hosts() { echo synced; }
|
|
do_reload
|
|
`;
|
|
let result = runLibrary(box, body(true));
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(result.stdout.trim(), 'synced');
|
|
|
|
result = runLibrary(box, body(false));
|
|
assert.equal(result.status, 1);
|
|
assert.equal(result.stdout.trim(), '');
|
|
});
|
|
|
|
test('proxy.sh stop removes the LAN hosts file', () => {
|
|
const box = sandbox({ hosts: `${HEADER}\n192.168.2.2 box.example.com\n` });
|
|
const bin = path.join(box.dir, 'bin');
|
|
fs.mkdirSync(bin);
|
|
fs.writeFileSync(path.join(bin, 'pidof'), '#!/bin/sh\nexit 1\n', { mode: 0o755 });
|
|
const result = spawnSync('/bin/sh', [script, 'stop'], {
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
PATH: `${bin}:${process.env.PATH}`,
|
|
MINIGATE_PID_FILE: path.join(box.dir, 'missing.pid'),
|
|
MINIGATE_LAN_HOSTS_FILE: box.hostsFile,
|
|
MINIGATE_LOGFILE: box.logFile
|
|
}
|
|
});
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(read(box.hostsFile), null);
|
|
assert.match(read(box.logFile), /局域网直连解析已移除/);
|
|
});
|
|
|
|
test('LuCI exposes the toggle and an optional IPv4 address, disabled by default', () => {
|
|
const page = fs.readFileSync(path.join(root, 'luasrc/model/cbi/minigate/proxy.lua'), 'utf8');
|
|
const config = fs.readFileSync(path.join(root, 'root/etc/config/minigate'), 'utf8');
|
|
assert.match(page, /s:option\(Flag, "lan_dns", "启用局域网直连解析"\)\no\.default = "0"/);
|
|
assert.match(page, /s:option\(Value, "lan_ip",[^\n]*\)\no\.datatype = "ip4addr"/);
|
|
assert.match(page, /o:depends\("lan_dns", "1"\)/);
|
|
assert.match(config, /option lan_dns '0'/);
|
|
});
|