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:
2026-06-19 00:41:24 +08:00
commit 4c53474ffe
10 changed files with 5552 additions and 0 deletions
+28
View File
@@ -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
+36
View File
@@ -0,0 +1,36 @@
# AGENTS.md
## 项目概述
本项目为**个人电脑助手**,协助完成软件编译构建、软件安装配置、系统故障排查、环境调试等日常开发运维任务。
## 运行环境
- **操作系统**WindowsPowerShell 7+
- **包管理**scoop / winget / choco 视系统已有工具选用
- **Shell**pwshPowerShell 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. **中文优先**:思考过程与回复均使用中文,技术术语可保留英文原名
+51
View File
@@ -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")}')
+29
View File
@@ -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!')
+107
View File
@@ -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
+105
View File
@@ -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
+29
View File
@@ -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"
}
+324
View File
@@ -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+ / LuCIjs)按 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