mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 20:52:52 +08:00
230 lines
9.5 KiB
Plaintext
Executable File
230 lines
9.5 KiB
Plaintext
Executable File
'use strict';
|
|
|
|
import { access, readfile, writefile, stat, popen } from 'fs';
|
|
import { cursor } from 'uci';
|
|
import { connect } from 'ubus';
|
|
|
|
const UCI_CONFIG = 'flowproxy';
|
|
const STATE_FILE = '/var/run/flowproxy.running';
|
|
const IP_CACHE = '/tmp/flowproxy/lan_ip.cache';
|
|
const LOG_FILE = '/var/log/flowproxy.log';
|
|
const NFT_TABLE = 'flowproxy';
|
|
const DNS_DROPIN_FILE = '/tmp/dnsmasq.d/flowproxy.conf';
|
|
|
|
// 辅助函数:安全 trim
|
|
function safe_trim(str) {
|
|
return (type(str) === 'string') ? trim(str) : '';
|
|
}
|
|
|
|
const methods = {
|
|
get_status: {
|
|
call: function() {
|
|
// 1. 快速探测标记文件
|
|
let is_running = access(STATE_FILE, 'f');
|
|
|
|
// 2. 自愈:检查内核状态 (nft 链与策略路由)
|
|
let tcp_active = (system('nft list chain inet ' + NFT_TABLE + ' LAN_MARKFLOW_TCP >/dev/null 2>&1') === 0);
|
|
let udp_active = (system('nft list chain inet ' + NFT_TABLE + ' LAN_MARKFLOW_UDP >/dev/null 2>&1') === 0);
|
|
let real_running = (tcp_active || udp_active);
|
|
|
|
if (real_running && !is_running) {
|
|
try { writefile(STATE_FILE, ''); } catch(e) {}
|
|
} else if (!real_running && is_running) {
|
|
try { system('rm -f ' + STATE_FILE); } catch(e) {}
|
|
}
|
|
|
|
// 3. IP 获取 (带缓存)
|
|
let lan_ip = readfile(IP_CACHE) || '';
|
|
if (!lan_ip) {
|
|
try {
|
|
let ubus_conn = connect();
|
|
if (ubus_conn) {
|
|
let ifaces = ubus_conn.call('network.interface', 'dump');
|
|
if (type(ifaces?.interface) === 'array') {
|
|
let fallback_ip = '';
|
|
for (let iface in ifaces.interface) {
|
|
if (iface.interface === 'lan' && iface['ipv4-address']?.[0]?.address) {
|
|
lan_ip = iface['ipv4-address'][0].address;
|
|
break;
|
|
}
|
|
if (!fallback_ip && iface.interface !== 'loopback' && iface['ipv4-address']?.[0]?.address) {
|
|
fallback_ip = iface['ipv4-address'][0].address;
|
|
}
|
|
}
|
|
if (!lan_ip) lan_ip = fallback_ip;
|
|
}
|
|
|
|
if (lan_ip) {
|
|
system('mkdir -p /tmp/flowproxy');
|
|
writefile(IP_CACHE, lan_ip);
|
|
}
|
|
ubus_conn.disconnect();
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
let uci_cursor = cursor();
|
|
let proxy_server_ip_addr = uci_cursor.get(UCI_CONFIG, 'global', 'proxy_server_ip_addr') || '';
|
|
let dns_running = access(DNS_DROPIN_FILE, 'f') ? 1 : 0;
|
|
|
|
return {
|
|
running: real_running ? 1 : 0,
|
|
dns_running: dns_running,
|
|
enabled: int(uci_cursor.get(UCI_CONFIG, 'global', 'enabled') || '0'),
|
|
proxy_server_ip_addr: proxy_server_ip_addr,
|
|
proxy_server_dns_port: uci_cursor.get(UCI_CONFIG, 'global', 'proxy_server_dns_port') || '5353',
|
|
lan_ip: safe_trim(lan_ip),
|
|
interface: uci_cursor.get(UCI_CONFIG, 'global', 'interface') || 'br-lan',
|
|
traffic_mark: uci_cursor.get(UCI_CONFIG, 'global', 'traffic_mark') || '0x666',
|
|
nft: { tcp: tcp_active ? 1 : 0, udp: udp_active ? 1 : 0 }
|
|
};
|
|
}
|
|
},
|
|
|
|
get_logs: {
|
|
args: { lines: 100 },
|
|
call: function(request) {
|
|
let lines = request.args?.lines ?? 100;
|
|
let logs = [];
|
|
try {
|
|
if (access(LOG_FILE, 'r')) {
|
|
let log_stat = stat(LOG_FILE);
|
|
let content = '';
|
|
if (log_stat && log_stat.size > 65536) {
|
|
let fd = popen('tail -c 65536 ' + LOG_FILE);
|
|
if (fd) {
|
|
content = fd.read('all');
|
|
fd.close();
|
|
}
|
|
} else {
|
|
content = readfile(LOG_FILE);
|
|
}
|
|
if (content) {
|
|
let log_lines = split(content, "\n");
|
|
let total = length(log_lines);
|
|
let start = (total > lines) ? (total - lines) : 0;
|
|
for (let i = start; i < total; i++) {
|
|
if (log_lines[i]) push(logs, log_lines[i]);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
return { logs };
|
|
}
|
|
},
|
|
|
|
get_dataset_info: {
|
|
args: { file_path: 'string' },
|
|
call: function(request) {
|
|
let file_path = request.args?.file_path || '/usr/share/flowproxy/chnroute.txt';
|
|
if (!match(file_path, /^\/(usr\/share\/flowproxy|tmp\/flowproxy)\/[a-zA-Z0-9_.-]+$/)) {
|
|
return { error: 'Invalid path' };
|
|
}
|
|
if (!access(file_path, 'r')) {
|
|
return { exists: false, lines: 0, size: 0 };
|
|
}
|
|
let s = stat(file_path);
|
|
let lines = 0;
|
|
let fd = popen(sprintf("wc -l '%s' 2>/dev/null", file_path));
|
|
if (fd) {
|
|
let out = fd.read('all');
|
|
fd.close();
|
|
if (out) {
|
|
let parts = split(trim(out), /\s+/);
|
|
lines = int(parts[0]) || 0;
|
|
}
|
|
}
|
|
return { exists: true, lines: lines, size: s ? s.size : 0 };
|
|
}
|
|
},
|
|
|
|
update_dataset: {
|
|
args: { url: 'string', file_path: 'string' },
|
|
call: function(request) {
|
|
let url = request.args?.url;
|
|
let file_path = request.args?.file_path || '/usr/share/flowproxy/chnroute.txt';
|
|
if (!url || !match(url, /^https?:\/\/[A-Za-z0-9_.\/:~%#?&=+-]+$/)) {
|
|
return { success: false, error: 'Invalid URL' };
|
|
}
|
|
if (!match(file_path, /^\/(usr\/share\/flowproxy|tmp\/flowproxy)\/[a-zA-Z0-9_.-]+$/)) {
|
|
return { success: false, error: 'Invalid target file path' };
|
|
}
|
|
let tmp_file = file_path + '.tmp';
|
|
let cmd = sprintf("wget -q -O '%s' '%s' --timeout=15 --no-check-certificate 2>&1", tmp_file, url);
|
|
let rc = system(cmd);
|
|
if (rc === 0 && access(tmp_file, 'f')) {
|
|
let s = stat(tmp_file);
|
|
if (s && s.size > 0) {
|
|
system(sprintf("mv -f '%s' '%s'", tmp_file, file_path));
|
|
let lines = 0;
|
|
let fd = popen(sprintf("wc -l '%s' 2>/dev/null", file_path));
|
|
if (fd) {
|
|
let out = fd.read('all');
|
|
fd.close();
|
|
if (out) {
|
|
let parts = split(trim(out), /\s+/);
|
|
lines = int(parts[0]) || 0;
|
|
}
|
|
}
|
|
return { success: true, lines: lines };
|
|
}
|
|
}
|
|
system(sprintf("rm -f '%s'", tmp_file));
|
|
return { success: false, error: 'Download failed or file empty' };
|
|
}
|
|
},
|
|
|
|
generate_nft_config: {
|
|
call: function() {
|
|
system('mkdir -p /tmp/flowproxy');
|
|
system('FLOWPROXY_PREVIEW=1 /usr/share/flowproxy/generate_nft.sh 2>&1');
|
|
return { config: safe_trim(readfile('/tmp/flowproxy/nft.conf') || '') };
|
|
}
|
|
},
|
|
|
|
get_runtime_config: {
|
|
call: function() {
|
|
let res_file = '/tmp/flowproxy/runtime.tmp';
|
|
system('mkdir -p /tmp/flowproxy');
|
|
system('/usr/share/flowproxy/generate_nft.sh runtime > ' + res_file + ' 2>&1');
|
|
let nft_runtime = readfile(res_file) || "(failed)";
|
|
system('rm -f ' + res_file);
|
|
|
|
let dns_runtime = "\n--- [ DNSmasq Drop-in Runtime Configuration ] ---\n";
|
|
let dns_content = readfile(DNS_DROPIN_FILE);
|
|
if (dns_content) {
|
|
dns_runtime += dns_content;
|
|
} else {
|
|
dns_runtime += "(no active dnsmasq drop-in overrides)";
|
|
}
|
|
|
|
return { runtime: safe_trim(nft_runtime + dns_runtime) };
|
|
}
|
|
},
|
|
|
|
get_interfaces: {
|
|
call: function() {
|
|
let interfaces = [];
|
|
try {
|
|
let ubus_conn = connect();
|
|
if (ubus_conn) {
|
|
let dev_status = ubus_conn.call('network.device', 'status');
|
|
if (dev_status) {
|
|
for (let name in dev_status) {
|
|
push(interfaces, { name: name });
|
|
}
|
|
}
|
|
ubus_conn.disconnect();
|
|
}
|
|
} catch (e) {}
|
|
return { interfaces };
|
|
}
|
|
},
|
|
|
|
start_service: { call: function() { return { result: (system('/etc/init.d/flowproxy start >/dev/null 2>&1') === 0) }; } },
|
|
stop_service: { call: function() { return { result: (system('/etc/init.d/flowproxy stop >/dev/null 2>&1') === 0) }; } },
|
|
restart_service: { call: function() { return { result: (system('/etc/init.d/flowproxy restart >/dev/null 2>&1') === 0) }; } },
|
|
clear_logs: { call: function() { try { writefile(LOG_FILE, ''); return { result: true }; } catch(e) { return { result: false }; } } }
|
|
};
|
|
|
|
return { 'luci.flowproxy': methods }; |