Add FastLink_Lite custom rules enhancement
- Add custom rules editor to LUCI web interface - Support 32 rule types (DOMAIN, IP-CIDR, GEOSITE, etc.) - Rule CRUD, sorting, batch import, templates - netflow-rules script for mihomo config injection - Standalone IPK repackaging support
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
# Large binary files
|
||||
*.ipk
|
||||
*.tar.gz
|
||||
*.dat
|
||||
*.mmdb
|
||||
*.metadb
|
||||
*.exe
|
||||
*.dll
|
||||
|
||||
# Temp / build dirs
|
||||
_tmp_extract/
|
||||
_clean/
|
||||
_repack_verify/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# External projects
|
||||
clash-verge-rev-dev/
|
||||
zashboard-main/
|
||||
|
||||
# Personal files
|
||||
*.backup.json
|
||||
reorganize*.ps1
|
||||
reorganize*.py
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,36 @@
|
||||
# AGENTS.md
|
||||
|
||||
## 项目概述
|
||||
|
||||
本项目为**个人电脑助手**,协助完成软件编译构建、软件安装配置、系统故障排查、环境调试等日常开发运维任务。
|
||||
|
||||
## 运行环境
|
||||
|
||||
- **操作系统**:Windows(PowerShell 7+)
|
||||
- **包管理**:scoop / winget / choco 视系统已有工具选用
|
||||
- **Shell**:pwsh(PowerShell 7+),避免 cmd / bash 语法
|
||||
|
||||
## 网络代理
|
||||
|
||||
遇到 GitHub、npm、cargo、pip、curl 等网络连接问题(如超时、443 错误、resolve 失败)时,通过以下代理访问:
|
||||
|
||||
```
|
||||
http://10.10.10.2:7890
|
||||
```
|
||||
|
||||
使用方式示例:
|
||||
- Git: `git config --global http.proxy http://10.10.10.2:7890`
|
||||
- 环境变量: `$env:HTTP_PROXY='http://10.10.10.2:7890'; $env:HTTPS_PROXY='http://10.10.10.2:7890'`
|
||||
- curl/wget: `--proxy http://10.10.10.2:7890`
|
||||
- npm: `npm config set proxy http://10.10.10.2:7890`
|
||||
|
||||
无需代理时记得取消设置。
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. **安全优先**:不执行危险命令(rm -rf 等)不确认用户意图之前,下载/安装软件前说明来源
|
||||
2. **解释操作**:运行非琐碎命令时,简要说明该命令做什么
|
||||
3. **诊断先行**:排查问题先收集信息(日志、配置、版本),再给方案
|
||||
4. **最小改动**:修改文件优先用编辑工具而非整体重写
|
||||
5. **遇到权限问题**:提示用户使用管理员权限运行,或提供提权命令
|
||||
6. **中文优先**:思考过程与回复均使用中文,技术术语可保留英文原名
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Build LuCI template version from working file."""
|
||||
with open(r'E:\AI Project\WangAgent\_tmp_extract\router_live.htm', 'r', encoding='utf-8') as f:
|
||||
c = f.read()
|
||||
|
||||
# Remove standalone HTML wrapper (everything before <style> and after </script>)
|
||||
# Current file structure: <!DOCTYPE html>\n<html...><head...><style>...</style>...<script>...</script></body></html>
|
||||
# LuCI template needs: <%+header%>\n<style>...</style>\n...\n<script>...</script>\n<%+footer%>
|
||||
|
||||
# Find the actual content
|
||||
style_start = c.find('<style>')
|
||||
script_end = c.find('</script>') + 9
|
||||
|
||||
# Remove the outer HTML wrapper
|
||||
content = c[style_start:script_end]
|
||||
|
||||
# Add LuCI template tags
|
||||
content = '<%+header%>\n' + content + '\n<%+footer%>'
|
||||
|
||||
# Replace API URLs
|
||||
old_api = "var apiUrl = '<%=luci.dispatcher.build_url(\"admin\", \"services\", \"netflow\", \"api\")%>';"
|
||||
new_api = "var apiUrl = '/cgi-bin/luci/admin/services/netflow/api';"
|
||||
if old_api not in content:
|
||||
# It was already hardcoded, update to LuCI format
|
||||
content = content.replace(
|
||||
"var apiUrl = '/cgi-bin/luci/admin/services/netflow/api';",
|
||||
"var apiUrl = '<%=luci.dispatcher.build_url(\"admin\", \"services\", \"netflow\", \"api\")%>';"
|
||||
)
|
||||
|
||||
old_upload = "xhr.open('POST', '<%=luci.dispatcher.build_url(\"admin\",\"services\",\"netflow\",\"upload_core\")%>', true);"
|
||||
new_upload = "xhr.open('POST', '/cgi-bin/luci/admin/services/netflow/upload_core', true);"
|
||||
# The upload URL is usually hardcoded now, let's use LuCI format instead
|
||||
content = content.replace(new_upload, old_upload)
|
||||
|
||||
# Clean up old tab navigation remnants
|
||||
import re
|
||||
content = re.sub(r'/\* Tab Navigation \*/\s*\nfunction nfSwitchTab\(btn\)\{.*?\n\}', '', content, flags=re.DOTALL)
|
||||
content = re.sub(r'\s*<!-- Tab Navigation -->\s*\n\s*<div id="nf-tab-bar">.*?</div>\s*\n', '', content, flags=re.DOTALL)
|
||||
content = re.sub(r'\s*<!-- Tab content wrappers -->\s*\n\s*(<div class="nf-tab-full-card".*?</div>\s*\n)+', '', content, flags=re.DOTALL)
|
||||
|
||||
# Ensure LF
|
||||
content = content.replace('\r\n', '\n').replace('\r', '\n')
|
||||
|
||||
out = r'E:\AI Project\WangAgent\_tmp_extract\router_main_lf.htm'
|
||||
with open(out, 'w', encoding='utf-8', newline='') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f'Template: {len(content)} bytes, LF: {chr(13)+chr(10) not in content[:300]}')
|
||||
print(f'Has <%+header%>: {"<%+header%>" in content}')
|
||||
print(f'Has <%+footer%>: {"<%+footer%>" in content}')
|
||||
print(f'Has standalone DOCTYPE: {"<!DOCTYPE" in content}')
|
||||
print(f'nf-ipv6-proxy-toggle: {content.count("nf-ipv6-proxy-toggle")}')
|
||||
@@ -0,0 +1,29 @@
|
||||
import paramiko, os, time
|
||||
time.sleep(3)
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.get_host_keys().clear()
|
||||
c.connect('10.10.10.3', 22, 'root', 'zxc9607.', timeout=30, allow_agent=False, look_for_keys=False)
|
||||
print('Connected')
|
||||
|
||||
with c.open_sftp() as s:
|
||||
s.put(r'E:\AI Project\WangAgent\luci\luci-app-flclient-v2.ipk', '/tmp/v2.ipk')
|
||||
|
||||
cmds = (
|
||||
'opkg remove luci-app-flclient 2>/dev/null; '
|
||||
'rm -rf /tmp/luci-* 2>/dev/null; '
|
||||
'opkg install /tmp/v2.ipk 2>&1; '
|
||||
'/etc/init.d/uhttpd restart 2>&1; '
|
||||
'echo DONE'
|
||||
)
|
||||
si, so, se = c.exec_command(cmds)
|
||||
print(so.read().decode('utf-8', 'replace'))
|
||||
err = se.read().decode('utf-8', 'replace')
|
||||
if err.strip(): print('ERR:', err)
|
||||
|
||||
# Verify files
|
||||
si, so, se = c.exec_command('ls -la /www/netflow/index.html /usr/lib/lua/luci/controller/netflow.lua 2>/dev/null')
|
||||
print(so.read().decode().strip())
|
||||
|
||||
c.close()
|
||||
print('Deployed!')
|
||||
@@ -0,0 +1,107 @@
|
||||
# repack_netflow_ipk.py — 将修改后的 control/ 和 data/ 目录重新打包为 IPK (tar.gz 格式)
|
||||
# OpenWrt IPK 格式: 外层 tar.gz,内含 debian-binary + control.tar.gz + data.tar.gz
|
||||
# 用法: python repack_netflow_ipk.py <data_dir> <control_dir> <output.ipk>
|
||||
|
||||
import os
|
||||
import sys
|
||||
import io
|
||||
import tarfile
|
||||
|
||||
# 需要在 tar 中设为 755 权限的文件
|
||||
EXEC_FILES = {
|
||||
'./usr/bin/netflow',
|
||||
'./usr/bin/netflow-rules',
|
||||
'./usr/bin/netflow_x86_64',
|
||||
'./usr/bin/netflow_aarch64',
|
||||
'./usr/bin/netflow_arm',
|
||||
'./usr/bin/netflow_mips',
|
||||
'./usr/bin/netflow_mipsel',
|
||||
'./etc/init.d/netflow',
|
||||
'./etc/uci-defaults/luci-app-netflow',
|
||||
}
|
||||
|
||||
# control.tar.gz 中的脚本全部需要 755
|
||||
CONTROL_SCRIPTS = {'./preinst', './postinst', './prerm', './postrm'}
|
||||
|
||||
|
||||
def fix_perms(tarinfo, is_control=False):
|
||||
"""设置 Unix 权限"""
|
||||
if tarinfo.isdir():
|
||||
tarinfo.mode = 0o755
|
||||
elif is_control and tarinfo.name in CONTROL_SCRIPTS:
|
||||
tarinfo.mode = 0o755
|
||||
elif tarinfo.name in EXEC_FILES:
|
||||
tarinfo.mode = 0o755
|
||||
else:
|
||||
tarinfo.mode = 0o644
|
||||
return tarinfo
|
||||
|
||||
|
||||
def make_tar_gz_bytes(source_dir, is_control=False):
|
||||
"""将目录打包为 tar.gz,返回 bytes"""
|
||||
buf = io.BytesIO()
|
||||
with tarfile.open(fileobj=buf, mode='w:gz', format=tarfile.GNU_FORMAT) as tf:
|
||||
for root, dirs, files in os.walk(source_dir):
|
||||
for d in sorted(dirs):
|
||||
fpath = os.path.join(root, d)
|
||||
arcname = './' + os.path.relpath(fpath, source_dir).replace('\\', '/')
|
||||
ti = tf.gettarinfo(fpath, arcname)
|
||||
fix_perms(ti, is_control)
|
||||
ti.uid = ti.gid = 0
|
||||
ti.uname = ti.gname = 'root'
|
||||
tf.addfile(ti)
|
||||
|
||||
for fn in sorted(files):
|
||||
fpath = os.path.join(root, fn)
|
||||
arcname = './' + os.path.relpath(fpath, source_dir).replace('\\', '/')
|
||||
ti = tf.gettarinfo(fpath, arcname)
|
||||
fix_perms(ti, is_control)
|
||||
ti.uid = ti.gid = 0
|
||||
ti.uname = ti.gname = 'root'
|
||||
with open(fpath, 'rb') as f:
|
||||
tf.addfile(ti, f)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print('用法: python repack_netflow_ipk.py <data_dir> <control_dir> <output.ipk>')
|
||||
sys.exit(1)
|
||||
|
||||
data_dir = sys.argv[1]
|
||||
control_dir = sys.argv[2]
|
||||
output_ipk = sys.argv[3]
|
||||
|
||||
# 1. 生成 debian-binary
|
||||
debian_data = b'2.0\n'
|
||||
|
||||
# 2. 打包 control.tar.gz
|
||||
control_tgz = make_tar_gz_bytes(control_dir, is_control=True)
|
||||
|
||||
# 3. 打包 data.tar.gz
|
||||
data_tgz = make_tar_gz_bytes(data_dir)
|
||||
|
||||
# 4. 创建外层 tar.gz (IPK 格式)
|
||||
with tarfile.open(output_ipk, 'w:gz', format=tarfile.GNU_FORMAT) as outer:
|
||||
for name, data, mode in [
|
||||
('./debian-binary', debian_data, 0o644),
|
||||
('./control.tar.gz', control_tgz, 0o644),
|
||||
('./data.tar.gz', data_tgz, 0o644),
|
||||
]:
|
||||
ti = tarfile.TarInfo(name)
|
||||
ti.size = len(data)
|
||||
ti.mode = mode
|
||||
ti.uid = ti.gid = 0
|
||||
ti.uname = ti.gname = 'root'
|
||||
ti.mtime = 0
|
||||
outer.addfile(ti, io.BytesIO(data))
|
||||
|
||||
size = os.path.getsize(output_ipk)
|
||||
print(f'IPK 打包完成: {output_ipk}')
|
||||
print(f' 大小: {size:,} bytes')
|
||||
print(f' 控制文件: {len(control_tgz):,} bytes')
|
||||
print(f' 数据文件: {len(data_tgz):,} bytes')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
# netflow-rules — 从 UCI 读取自定义规则,注入 mihomo config.yaml
|
||||
# 用法: netflow-rules → 只生成 /tmp/netflow_custom_rules.yaml
|
||||
# netflow-rules inject → 生成并注入到 config.yaml 中
|
||||
# netflow-rules reload → 注入后通过 mihomo API 热重载
|
||||
|
||||
CONFIG="/etc/netflow/config.yaml"
|
||||
CUSTOM_RULES="/tmp/netflow_custom_rules.yaml"
|
||||
BAK="${CONFIG}.bak"
|
||||
|
||||
API_PORT=$(uci -q get netflow.config.api_port 2>/dev/null || echo "9091")
|
||||
API_SECRET=$(uci -q get netflow.config.api_secret 2>/dev/null || echo "netflow_secret")
|
||||
|
||||
generate() {
|
||||
>"$CUSTOM_RULES"
|
||||
count=0
|
||||
for sec in $(uci -q show netflow 2>/dev/null | grep '^netflow\.@custom_rule\[' | sed 's/=.*//'); do
|
||||
t=$(uci -q get "${sec}.type" 2>/dev/null)
|
||||
v=$(uci -q get "${sec}.value" 2>/dev/null)
|
||||
p=$(uci -q get "${sec}.policy" 2>/dev/null)
|
||||
if [ -n "$t" ] && [ -n "$v" ] && [ -n "$p" ]; then
|
||||
echo " - ${t},${v},${p}" >> "$CUSTOM_RULES"
|
||||
count=$((count + 1))
|
||||
fi
|
||||
done
|
||||
return $count
|
||||
}
|
||||
|
||||
inject() {
|
||||
generate
|
||||
if [ $? -eq 0 ]; then
|
||||
logger -t netflow-rules "no custom rules defined, skipping"
|
||||
rm -f "$CUSTOM_RULES"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$CONFIG" ] || [ ! -s "$CONFIG" ]; then
|
||||
logger -t netflow-rules "config.yaml not found or empty, skip inject"
|
||||
return 0
|
||||
fi
|
||||
|
||||
MATCH_LINE=$(grep -n -m1 'MATCH,' "$CONFIG" 2>/dev/null | cut -d: -f1)
|
||||
if [ -z "$MATCH_LINE" ]; then
|
||||
logger -t netflow-rules "no MATCH rule found in config.yaml, skip inject"
|
||||
return 0
|
||||
fi
|
||||
|
||||
TMP_CONFIG="/tmp/netflow_config_patched.yaml"
|
||||
cp "$CONFIG" "$BAK"
|
||||
|
||||
head -n $((MATCH_LINE - 1)) "$CONFIG" > "$TMP_CONFIG"
|
||||
cat "$CUSTOM_RULES" >> "$TMP_CONFIG"
|
||||
tail -n +${MATCH_LINE} "$CONFIG" >> "$TMP_CONFIG"
|
||||
|
||||
if [ -s "$TMP_CONFIG" ]; then
|
||||
mv "$TMP_CONFIG" "$CONFIG"
|
||||
logger -t netflow-rules "custom rules injected into config.yaml"
|
||||
else
|
||||
logger -t netflow-rules "ERROR: patched config is empty, restoring backup"
|
||||
cp "$BAK" "$CONFIG"
|
||||
fi
|
||||
|
||||
rm -f "$CUSTOM_RULES"
|
||||
}
|
||||
|
||||
reload_mihomo() {
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -s -X PUT \
|
||||
-H "Authorization: Bearer ${API_SECRET}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"path\":\"${CONFIG}\"}" \
|
||||
"http://127.0.0.1:${API_PORT}/configs" >/dev/null 2>&1
|
||||
ret=$?
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO- --method=PUT \
|
||||
--header="Authorization: Bearer ${API_SECRET}" \
|
||||
--header="Content-Type: application/json" \
|
||||
--body-data="{\"path\":\"${CONFIG}\"}" \
|
||||
"http://127.0.0.1:${API_PORT}/configs" >/dev/null 2>&1
|
||||
ret=$?
|
||||
else
|
||||
logger -t netflow-rules "curl/wget not found, cannot reload mihomo"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ $ret -eq 0 ]; then
|
||||
logger -t netflow-rules "mihomo config reloaded"
|
||||
else
|
||||
logger -t netflow-rules "mihomo reload failed (code=${ret})"
|
||||
fi
|
||||
return $ret
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
inject)
|
||||
inject
|
||||
;;
|
||||
reload)
|
||||
inject
|
||||
reload_mihomo
|
||||
;;
|
||||
*)
|
||||
generate
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=10
|
||||
USE_PROCD=1
|
||||
|
||||
start_service() {
|
||||
if [ ! -x "/usr/bin/netflow" ]; then
|
||||
logger -t netflow "binary not found: /usr/bin/netflow"
|
||||
return 1
|
||||
fi
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/bin/netflow
|
||||
procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
|
||||
logger -t netflow "service started"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
logger -t netflow "service stopping"
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "netflow"
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
module("luci.controller.netflow", package.seeall)
|
||||
|
||||
local sys = require "luci.sys"
|
||||
local http = require "luci.http"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
--- 调用本机 netflow 后端:优先 curl(与 Makefile 依赖一致),失败时用 wget 兜底(部分魔改固件无 curl)。
|
||||
local function call_backend_post(action, json_file_path)
|
||||
local port = uci:get("netflow", "config", "backend_port") or "9190"
|
||||
if not port:match("^%d+$") then port = "9190" end
|
||||
local url = string.format("http://127.0.0.1:%s/api/%s", port, action)
|
||||
local curl = string.format(
|
||||
"curl -sS --connect-timeout 8 --max-time 45 -X POST '%s' -H 'Content-Type: application/json' -d @%s 2>/dev/null",
|
||||
url, json_file_path
|
||||
)
|
||||
local out = sys.exec(curl)
|
||||
if out and out ~= "" then
|
||||
return out
|
||||
end
|
||||
local wget = string.format(
|
||||
"wget -qO- --timeout=50 --header='Content-Type: application/json' --post-file=%s '%s' 2>/dev/null",
|
||||
json_file_path, url
|
||||
)
|
||||
return sys.exec(wget) or ""
|
||||
end
|
||||
|
||||
function index()
|
||||
-- OpenWrt 25+ / LuCI(js)按 rpcd ACL 过滤菜单;键名须与 /usr/share/rpcd/acl.d/*.json 顶层键一致(CI 在 pkg_tag≠netflow 时改为 luci-app-<tag>)
|
||||
entry({"admin", "services", "netflow"}, template("netflow/main"), _("FastLink_Lite"), 80).acl_depends = { "luci-app-flclient" }
|
||||
entry({"admin", "services", "netflow", "api"}, call("action_api"), nil).acl_depends = { "luci-app-flclient" }
|
||||
entry({"admin", "services", "netflow", "upload_core"}, call("action_upload_core"), nil).acl_depends = { "luci-app-flclient" }
|
||||
end
|
||||
|
||||
function action_api()
|
||||
local action = http.formvalue("action") or ""
|
||||
|
||||
if not action:match("^[%w_]+$") then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"invalid action"}')
|
||||
return
|
||||
end
|
||||
|
||||
-- 自定义规则 API(本地 UCI 操作,不经过 Go 后端)
|
||||
if action == "rule_list" then
|
||||
action_rule_list_local()
|
||||
return
|
||||
elseif action == "rule_add" then
|
||||
action_rule_add_local()
|
||||
return
|
||||
elseif action == "rule_delete" then
|
||||
action_rule_delete_local()
|
||||
return
|
||||
elseif action == "rule_update" then
|
||||
action_rule_update_local()
|
||||
return
|
||||
elseif action == "rule_sort" then
|
||||
action_rule_sort_local()
|
||||
return
|
||||
elseif action == "rule_import" then
|
||||
action_rule_import_local()
|
||||
return
|
||||
elseif action == "rule_templates" then
|
||||
action_rule_templates_local()
|
||||
return
|
||||
elseif action == "rule_apply" then
|
||||
action_rule_apply_local()
|
||||
return
|
||||
end
|
||||
|
||||
local function urldecode(s)
|
||||
if not s then return s end
|
||||
s = s:gsub('+', ' ')
|
||||
s = s:gsub('%%(%x%x)', function(h) return string.char(tonumber(h, 16)) end)
|
||||
return s
|
||||
end
|
||||
|
||||
local params = {}
|
||||
local known_keys = {
|
||||
"email", "password", "group", "node", "mode", "state", "run_mode", "source"
|
||||
}
|
||||
for _, key in ipairs(known_keys) do
|
||||
local val = http.formvalue(key)
|
||||
if val and val ~= "" then
|
||||
params[key] = urldecode(val)
|
||||
end
|
||||
end
|
||||
|
||||
local jsonc = require "luci.jsonc"
|
||||
local json_body = jsonc.stringify(params) or "{}"
|
||||
|
||||
local tmp = os.tmpname()
|
||||
local f = io.open(tmp, "w")
|
||||
if f then
|
||||
f:write(json_body)
|
||||
f:close()
|
||||
end
|
||||
|
||||
local result = call_backend_post(action, tmp)
|
||||
os.remove(tmp)
|
||||
|
||||
if not result or result == "" then
|
||||
result = '{"status":"error","message":"后端未响应,请检查服务是否运行(或安装 curl/wget)"}'
|
||||
end
|
||||
|
||||
http.prepare_content("application/json")
|
||||
http.write(result)
|
||||
end
|
||||
|
||||
-- ===== 自定义规则 API =====
|
||||
|
||||
function action_rule_list_local()
|
||||
local rules = {}
|
||||
uci:foreach("netflow", "custom_rule", function(s)
|
||||
if s[".name"] then
|
||||
table.insert(rules, {id = s[".name"], type = s.type or "", value = s.value or "", policy = s.policy or ""})
|
||||
end
|
||||
end)
|
||||
local jsonc = require "luci.jsonc"
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonc.stringify({status = "success", rules = rules}))
|
||||
end
|
||||
|
||||
function action_rule_add_local()
|
||||
local t = http.formvalue("type") or ""
|
||||
local v = http.formvalue("value") or ""
|
||||
local p = http.formvalue("policy") or ""
|
||||
if t == "" or v == "" or p == "" then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"参数不完整"}')
|
||||
return
|
||||
end
|
||||
local section = uci:add("netflow", "custom_rule")
|
||||
uci:set("netflow", section, "type", t)
|
||||
uci:set("netflow", section, "value", v)
|
||||
uci:set("netflow", section, "policy", p)
|
||||
uci:commit("netflow")
|
||||
local jsonc = require "luci.jsonc"
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonc.stringify({status = "success", message = "规则已添加", id = section}))
|
||||
end
|
||||
|
||||
function action_rule_delete_local()
|
||||
local id = http.formvalue("id") or ""
|
||||
if id == "" then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"缺少规则ID"}')
|
||||
return
|
||||
end
|
||||
uci:delete("netflow", id)
|
||||
uci:commit("netflow")
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"success","message":"规则已删除"}')
|
||||
end
|
||||
|
||||
function action_rule_update_local()
|
||||
local id = http.formvalue("id") or ""
|
||||
local t = http.formvalue("type") or ""
|
||||
local v = http.formvalue("value") or ""
|
||||
local p = http.formvalue("policy") or ""
|
||||
if id == "" or t == "" or v == "" or p == "" then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"参数不完整"}')
|
||||
return
|
||||
end
|
||||
uci:set("netflow", id, "type", t)
|
||||
uci:set("netflow", id, "value", v)
|
||||
uci:set("netflow", id, "policy", p)
|
||||
uci:commit("netflow")
|
||||
local jsonc = require "luci.jsonc"
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonc.stringify({status = "success", message = "规则已更新"}))
|
||||
end
|
||||
|
||||
function action_rule_sort_local()
|
||||
local id = http.formvalue("id") or ""
|
||||
local dir = http.formvalue("dir") or ""
|
||||
if id == "" or (dir ~= "up" and dir ~= "down") then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"参数错误"}')
|
||||
return
|
||||
end
|
||||
local rules = {}
|
||||
uci:foreach("netflow", "custom_rule", function(s)
|
||||
if s[".name"] then
|
||||
table.insert(rules, {id = s[".name"], type = s.type or "", value = s.value or "", policy = s.policy or ""})
|
||||
end
|
||||
end)
|
||||
local idx = nil
|
||||
for i, r in ipairs(rules) do if r.id == id then idx = i; break end end
|
||||
if not idx then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"规则不存在"}')
|
||||
return
|
||||
end
|
||||
local swap = nil
|
||||
if dir == "up" and idx > 1 then swap = idx - 1
|
||||
elseif dir == "down" and idx < #rules then swap = idx + 1 end
|
||||
if not swap then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"已在边界"}')
|
||||
return
|
||||
end
|
||||
rules[idx], rules[swap] = rules[swap], rules[idx]
|
||||
uci:foreach("netflow", "custom_rule", function(s)
|
||||
if s[".name"] then uci:delete("netflow", s[".name"]) end
|
||||
end)
|
||||
for _, r in ipairs(rules) do
|
||||
local section = uci:add("netflow", "custom_rule")
|
||||
uci:set("netflow", section, "type", r.type)
|
||||
uci:set("netflow", section, "value", r.value)
|
||||
uci:set("netflow", section, "policy", r.policy)
|
||||
end
|
||||
uci:commit("netflow")
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"success","message":"排序已更新"}')
|
||||
end
|
||||
|
||||
function action_rule_import_local()
|
||||
local text = http.formvalue("text") or ""
|
||||
if text == "" then
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"error","message":"内容为空"}')
|
||||
return
|
||||
end
|
||||
local count = 0
|
||||
for line in text:gmatch("[^\r\n]+") do
|
||||
line = line:match("^%s*(.-)%s*$")
|
||||
if line ~= "" and line:sub(1,1) ~= "#" then
|
||||
local parts = {}
|
||||
for p in line:gmatch("[^,]+") do table.insert(parts, p:match("^%s*(.-)%s*$")) end
|
||||
local tp, val, pol
|
||||
if #parts >= 3 then tp, val, pol = parts[1], parts[2], parts[3]
|
||||
elseif #parts == 2 then tp, val, pol = "DOMAIN-SUFFIX", parts[1], parts[2] end
|
||||
if tp and val and pol then
|
||||
local section = uci:add("netflow", "custom_rule")
|
||||
uci:set("netflow", section, "type", tp)
|
||||
uci:set("netflow", section, "value", val)
|
||||
uci:set("netflow", section, "policy", pol)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
uci:commit("netflow")
|
||||
local jsonc = require "luci.jsonc"
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonc.stringify({status = "success", message = "已导入 " .. count .. " 条规则", count = count}))
|
||||
end
|
||||
|
||||
function action_rule_templates_local()
|
||||
local templates = {
|
||||
{name = "屏蔽常见广告域名", rules = {
|
||||
{type = "DOMAIN-SUFFIX", value = "doubleclick.net", policy = "REJECT"},
|
||||
{type = "DOMAIN-SUFFIX", value = "googlesyndication.com", policy = "REJECT"},
|
||||
{type = "DOMAIN-SUFFIX", value = "googleadservices.com", policy = "REJECT"},
|
||||
{type = "DOMAIN-SUFFIX", value = "adservice.google.com", policy = "REJECT"},
|
||||
{type = "DOMAIN-SUFFIX", value = "googletagmanager.com", policy = "REJECT"},
|
||||
}},
|
||||
{name = "屏蔽 iOS 更新", rules = {
|
||||
{type = "DOMAIN-SUFFIX", value = "mesu.apple.com", policy = "REJECT"},
|
||||
{type = "DOMAIN-SUFFIX", value = "appldnld.apple.com", policy = "REJECT"},
|
||||
{type = "DOMAIN-SUFFIX", value = "gdmf.apple.com", policy = "REJECT"},
|
||||
}},
|
||||
{name = "国内直连", rules = {
|
||||
{type = "DOMAIN-SUFFIX", value = "cn", policy = "DIRECT"},
|
||||
{type = "DOMAIN-KEYWORD", value = "baidu", policy = "DIRECT"},
|
||||
{type = "DOMAIN-KEYWORD", value = "taobao", policy = "DIRECT"},
|
||||
{type = "DOMAIN-KEYWORD", value = "alibaba", policy = "DIRECT"},
|
||||
{type = "DOMAIN-SUFFIX", value = "189.cn", policy = "DIRECT"},
|
||||
}},
|
||||
{name = "AI 工具走代理", rules = {
|
||||
{type = "DOMAIN-SUFFIX", value = "openai.com", policy = "Proxy"},
|
||||
{type = "DOMAIN-SUFFIX", value = "claude.ai", policy = "Proxy"},
|
||||
{type = "DOMAIN-SUFFIX", value = "chatgpt.com", policy = "Proxy"},
|
||||
{type = "DOMAIN-SUFFIX", value = "anthropic.com", policy = "Proxy"},
|
||||
{type = "DOMAIN-SUFFIX", value = "gemini.google.com", policy = "Proxy"},
|
||||
{type = "DOMAIN-SUFFIX", value = "perplexity.ai", policy = "Proxy"},
|
||||
}},
|
||||
}
|
||||
local jsonc = require "luci.jsonc"
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonc.stringify({status = "success", templates = templates}))
|
||||
end
|
||||
|
||||
function action_rule_apply_local()
|
||||
sys.call("/usr/bin/netflow-rules reload")
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"status":"success","message":"规则已应用并热重载"}')
|
||||
end
|
||||
|
||||
function action_upload_core()
|
||||
local fp
|
||||
local upload_path = "/tmp/mihomo_upload"
|
||||
|
||||
http.setfilehandler(function(meta, chunk, eof)
|
||||
if not fp and meta and meta.name == "corefile" then
|
||||
fp = io.open(upload_path, "w")
|
||||
end
|
||||
if fp and chunk then
|
||||
fp:write(chunk)
|
||||
end
|
||||
if fp and eof then
|
||||
fp:close()
|
||||
end
|
||||
end)
|
||||
|
||||
http.formvalue("corefile")
|
||||
|
||||
local empty_json = os.tmpname()
|
||||
local ef = io.open(empty_json, "w")
|
||||
if ef then
|
||||
ef:write("{}")
|
||||
ef:close()
|
||||
end
|
||||
|
||||
local result = call_backend_post("core_install_upload", empty_json)
|
||||
os.remove(empty_json)
|
||||
|
||||
if not result or result == "" then
|
||||
result = '{"status":"error","message":"后端未响应,请检查服务是否运行(或安装 curl/wget)"}'
|
||||
end
|
||||
|
||||
http.prepare_content("application/json")
|
||||
http.write(result)
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user