mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-13 11:54:29 +08:00
🎄 Sync 2026-08-30 03:21:07
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
#
|
||||
# Copyright (C) 2026 jjm2473@gmail.com
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
AGENTFLOW_ARCH_x86_64:=amd64
|
||||
AGENTFLOW_ARCH_aarch64:=arm64
|
||||
AGENTFLOW_ARCH:=$(AGENTFLOW_ARCH_$(ARCH))
|
||||
|
||||
AGENTFLOW_HASH_x86_64:=8ba4f42dc45a38ee54d9716cbe39201bb3d9e947c4a6075d9d7af999ab5d098e
|
||||
AGENTFLOW_HASH_aarch64:=beb19ca378ea1f15a90a907c4de3244353188cf01287b85bfea68b80b1ed8925
|
||||
|
||||
PKG_NAME:=agentflow
|
||||
PKG_VERSION:=0.3.0-20260824-c588d794
|
||||
PKG_RELEASE:=1
|
||||
|
||||
AGENTFLOW_SOURCE:=agentflow-linux-$(AGENTFLOW_ARCH)
|
||||
AGENTFLOW_DOWNLOAD:=$(PKG_NAME)-$(PKG_VERSION)-linux-$(AGENTFLOW_ARCH)
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
STRIP:=true
|
||||
RSTRIP:=:
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Download/agentflow
|
||||
URL:=https://fw.koolcenter.com/binary/geili/agentflow/build/
|
||||
URL_FILE:=$(AGENTFLOW_SOURCE)
|
||||
FILE:=$(AGENTFLOW_DOWNLOAD)
|
||||
HASH:=skip
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=AgentFlow
|
||||
DEPENDS:=@(x86_64||aarch64) +git +mise
|
||||
URL:=https://agentflow.geili.ai/
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
AgentFlow provides a Web UI for orchestrating coding agents and workflows.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/agentflow
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(INSTALL_DIR) $(PKG_BUILD_DIR)
|
||||
$(CP) $(DL_DIR)/$(AGENTFLOW_DOWNLOAD) $(PKG_BUILD_DIR)/agentflow
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin $(1)/etc/init.d $(1)/etc/uci-defaults $(1)/etc/config
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/agentflow $(1)/usr/bin/agentflow
|
||||
$(INSTALL_BIN) ./files/agentflow.init $(1)/etc/init.d/agentflow
|
||||
$(INSTALL_BIN) ./files/agentflow.uci-default $(1)/etc/uci-defaults/09-agentflow
|
||||
$(INSTALL_CONF) ./files/agentflow.config $(1)/etc/config/agentflow
|
||||
endef
|
||||
|
||||
$(eval $(call Download,agentflow))
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -0,0 +1,5 @@
|
||||
config agentflow
|
||||
option enabled '0'
|
||||
option data_dir ''
|
||||
option host '0.0.0.0'
|
||||
option port '9000'
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=93
|
||||
USE_PROCD=1
|
||||
|
||||
get_config() {
|
||||
config_get_bool enabled "$1" enabled 0
|
||||
config_get data_dir "$1" data_dir ""
|
||||
config_get host "$1" host "0.0.0.0"
|
||||
config_get port "$1" port "9000"
|
||||
}
|
||||
|
||||
set_mise_env() {
|
||||
procd_set_param env \
|
||||
"$@" \
|
||||
MISE_INSTALLS_DIR="$data_dir/globalmise/mise-installs" \
|
||||
MISE_DATA_DIR="$data_dir/globalmise/mise-data" \
|
||||
MISE_CACHE_DIR="$data_dir/globalmise/mise-cache" \
|
||||
MISE_STATE_DIR="$data_dir/globalmise/mise-state" \
|
||||
NPM_CONFIG_CACHE="$data_dir/globalmise/npm-cache" \
|
||||
NPM_CONFIG_USERCONFIG="$data_dir/globalmise/.npmrc" \
|
||||
PATH="$data_dir/globalmise/mise-data/shims:$PATH"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load agentflow
|
||||
config_foreach get_config agentflow
|
||||
[ "$enabled" = 1 ] || return 1
|
||||
|
||||
[ -x /usr/bin/agentflow ] || {
|
||||
logger -t agentflow "missing executable: /usr/bin/agentflow"
|
||||
return 1
|
||||
}
|
||||
mkdir -p "$data_dir" || return 1
|
||||
|
||||
logger -t agentflow "starting AgentFlow on $host:$port"
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/bin/agentflow
|
||||
set_mise_env \
|
||||
"AGENT_FLOW_DATA=$data_dir/data" \
|
||||
"AGENT_FLOW_HOST=$host" \
|
||||
"AGENT_FLOW_PORT=$port"
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "agentflow"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
[ ! -f "/usr/share/ucitrack/luci-app-agentflow.json" ] && {
|
||||
cat > /usr/share/ucitrack/luci-app-agentflow.json << EEOF
|
||||
{
|
||||
"config": "agentflow",
|
||||
"init": "agentflow"
|
||||
}
|
||||
EEOF
|
||||
}
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@agentflow[-1]
|
||||
add ucitrack agentflow
|
||||
set ucitrack.@agentflow[-1].init=agentflow
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
/etc/init.d/agentflow enable
|
||||
/etc/init.d/agentflow start
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=AgentFlow
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
LUCI_DEPENDS:=+agentflow +luci-compat
|
||||
LUCI_MINIFY_CSS:=0
|
||||
LUCI_MINIFY_JS:=0
|
||||
|
||||
define Package/luci-app-agentflow/conffiles
|
||||
/etc/config/agentflow
|
||||
endef
|
||||
|
||||
define Package/luci-app-agentflow/postrm
|
||||
#!/bin/sh
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,29 @@
|
||||
local http = require "luci.http"
|
||||
|
||||
module("luci.controller.agentflow", package.seeall)
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/agentflow") then
|
||||
return
|
||||
end
|
||||
|
||||
local page = entry({"admin", "services", "agentflow"}, cbi("agentflow"), _("AgentFlow"), 100)
|
||||
page.dependent = true
|
||||
entry({"admin", "services", "agentflow_status"}, call("agentflow_status"))
|
||||
end
|
||||
|
||||
function agentflow_status()
|
||||
local sys = require "luci.sys"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port = tonumber(uci:get_first("agentflow", "agentflow", "port")) or 9000
|
||||
if port < 1 or port > 65535 then
|
||||
port = 9000
|
||||
end
|
||||
|
||||
local status = {
|
||||
running = (sys.call("pidof agentflow >/dev/null") == 0),
|
||||
port = port
|
||||
}
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(status)
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
local jsonc = require "luci.jsonc"
|
||||
|
||||
local agentflow = {}
|
||||
|
||||
agentflow.blocks = function()
|
||||
local f = io.popen("lsblk -s -f -b -o NAME,FSSIZE,MOUNTPOINT --json", "r")
|
||||
local vals = {}
|
||||
if f then
|
||||
local ret = f:read("*all")
|
||||
f:close()
|
||||
local obj = jsonc.parse(ret)
|
||||
for _, val in pairs(obj and obj["blockdevices"] or {}) do
|
||||
local fsize = val["fssize"]
|
||||
if fsize ~= nil and string.len(fsize) > 10 and val["mountpoint"] then
|
||||
vals[#vals + 1] = val["mountpoint"]
|
||||
end
|
||||
end
|
||||
end
|
||||
return vals
|
||||
end
|
||||
|
||||
agentflow.home = function()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local home_dirs = {}
|
||||
home_dirs["main_dir"] = uci:get_first("quickstart", "main", "main_dir", "/root")
|
||||
home_dirs["Configs"] = uci:get_first("quickstart", "main", "conf_dir", home_dirs["main_dir"] .. "/Configs")
|
||||
return home_dirs
|
||||
end
|
||||
|
||||
agentflow.find_paths = function(blocks, home_dirs)
|
||||
local default_path = home_dirs["Configs"] .. "/AgentFlow"
|
||||
local paths = {}
|
||||
|
||||
if #blocks == 0 then
|
||||
table.insert(paths, default_path)
|
||||
else
|
||||
for _, val in pairs(blocks) do
|
||||
table.insert(paths, val .. "/Configs/AgentFlow")
|
||||
end
|
||||
if default_path == "/root/Configs/AgentFlow" then
|
||||
default_path = paths[1]
|
||||
end
|
||||
end
|
||||
|
||||
return paths, default_path
|
||||
end
|
||||
|
||||
return agentflow
|
||||
@@ -0,0 +1,32 @@
|
||||
local m, s
|
||||
|
||||
m = Map("agentflow", translate("AgentFlow"), translate("AgentFlow provides a Web UI for orchestrating coding agents and workflows."))
|
||||
m:section(SimpleSection).template = "agentflow/status"
|
||||
|
||||
s = m:section(TypedSection, "agentflow", translate("Global settings"))
|
||||
s.addremove = false
|
||||
s.anonymous = true
|
||||
|
||||
s:option(Flag, "enabled", translate("Enable")).rmempty = false
|
||||
|
||||
local agentflow_model = require "luci.model.agentflow"
|
||||
local blocks = agentflow_model.blocks()
|
||||
local home = agentflow_model.home()
|
||||
|
||||
local data_dir = s:option(Value, "data_dir", translate("Data directory"))
|
||||
data_dir.rmempty = false
|
||||
data_dir.description = translate("Required. AgentFlow stores its configuration, database and workspace data under this directory.")
|
||||
|
||||
local paths, default_path = agentflow_model.find_paths(blocks, home)
|
||||
for _, val in pairs(paths) do
|
||||
data_dir:value(val, val)
|
||||
end
|
||||
data_dir.default = default_path
|
||||
|
||||
local port = s:option(Value, "port", translate("Listen port"))
|
||||
port.default = "9000"
|
||||
port.rmempty = false
|
||||
port.datatype = "port"
|
||||
port.description = translate("Port for the AgentFlow HTTP server.")
|
||||
|
||||
return m
|
||||
@@ -0,0 +1,20 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(5, '<%=url("admin/services/agentflow_status")%>', null, function(x, st) {
|
||||
var el = document.getElementById('agentflow_status');
|
||||
if (st && el) {
|
||||
if (!st.running) {
|
||||
el.innerHTML = '<br/><em style="color:red"><%:The AgentFlow service is not running.%></em>';
|
||||
} else {
|
||||
el.innerHTML = '<br/><em style="color:green"><%:The AgentFlow service is running.%></em>'
|
||||
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open AgentFlow%> \" onclick=\"window.open('http://" + window.location.hostname + ":" + st.port + "/')\"/>";
|
||||
}
|
||||
}
|
||||
});
|
||||
//]]></script>
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
<legend><%:Status%></legend>
|
||||
<p id="agentflow_status">
|
||||
<em><%:Collecting data...%></em>
|
||||
</p>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,41 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
msgid "AgentFlow"
|
||||
msgstr "AgentFlow"
|
||||
|
||||
msgid "AgentFlow provides a Web UI for orchestrating coding agents and workflows."
|
||||
msgstr "AgentFlow 提供用于编排编码智能体和工作流的 Web 管理界面。"
|
||||
|
||||
msgid "Click to open AgentFlow"
|
||||
msgstr "点击打开 AgentFlow"
|
||||
|
||||
msgid "Collecting data..."
|
||||
msgstr "正在获取数据……"
|
||||
|
||||
msgid "Data directory"
|
||||
msgstr "数据目录"
|
||||
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Global settings"
|
||||
msgstr "全局设置"
|
||||
|
||||
msgid "Listen port"
|
||||
msgstr "监听端口"
|
||||
|
||||
msgid "Port for the AgentFlow HTTP server."
|
||||
msgstr "AgentFlow HTTP 服务的端口号。"
|
||||
|
||||
msgid "Required. AgentFlow stores its configuration, database and workspace data under this directory."
|
||||
msgstr "必需。AgentFlow 在此目录中保存配置、数据库和工作区数据。"
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
msgid "The AgentFlow service is not running."
|
||||
msgstr "AgentFlow 服务未运行。"
|
||||
|
||||
msgid "The AgentFlow service is running."
|
||||
msgstr "AgentFlow 服务运行中。"
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
zh-cn
|
||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.8.27
|
||||
PKG_RELEASE:=104
|
||||
PKG_RELEASE:=105
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -685,6 +685,8 @@ function rollback_rules()
|
||||
local geo_dir = (uci_get("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/")
|
||||
fs.move(bak_dir .. arg_type .. ".dat", geo_dir .. arg_type .. ".dat")
|
||||
fs.rmdir(bak_dir)
|
||||
uci_set("@global[0]", "flush_set", "1")
|
||||
uci_save(true)
|
||||
http_write_json_ok()
|
||||
end
|
||||
|
||||
|
||||
@@ -164,6 +164,22 @@ node_socks_bind_local:depends({ node = "", ["!reverse"] = true })
|
||||
|
||||
s:tab("DNS", translate("DNS"))
|
||||
|
||||
o = s:taboption("DNS", ListValue, "direct_dns_protocol", translate("Direct DNS Protocol"))
|
||||
o:value("", translate("Auto"))
|
||||
--o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
|
||||
o = s:taboption("DNS", Value, "direct_dns", translate("Direct DNS"))
|
||||
o.datatype = "or(ipaddr,ipaddrport(1))"
|
||||
o.default = "223.5.5.5"
|
||||
o:value("223.5.5.5")
|
||||
o:value("223.6.6.6")
|
||||
o:value("114.114.114.114")
|
||||
o:value("119.29.29.29")
|
||||
o:value("180.76.76.76")
|
||||
o:depends("direct_dns_protocol", "tcp")
|
||||
o:depends("direct_dns_protocol", "udp")
|
||||
|
||||
o = s:taboption("DNS", ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy"))
|
||||
o.default = "UseIP"
|
||||
o:value("UseIP")
|
||||
|
||||
@@ -419,6 +419,10 @@ if singbox_tags:find("with_quic") then
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
|
||||
o.default = "0"
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Value, "hysteria2_auth_password", translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
@@ -355,6 +355,10 @@ o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
|
||||
o.default = "0"
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Value, "hysteria2_auth_password", translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
@@ -183,6 +183,10 @@ if singbox_tags:find("with_quic") then
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
|
||||
o.default = "0"
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
|
||||
@@ -148,6 +148,10 @@ o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
|
||||
o.default = "0"
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
|
||||
@@ -68,13 +68,6 @@ local function convert_geofile()
|
||||
end
|
||||
local function convert(file_path, prefix, tags)
|
||||
if next(tags) and fs.access(file_path) then
|
||||
local md5_file = GEO_VAR.TO_SRS_PATH .. prefix .. ".dat.md5"
|
||||
local new_md5 = sys.exec("md5sum " .. file_path .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "")
|
||||
local old_md5 = sys.exec("[ -f " .. md5_file .. " ] && head -n 1 " .. md5_file .. " | tr -d ' \t\n' || echo ''")
|
||||
if new_md5 ~= "" and new_md5 ~= old_md5 then
|
||||
sys.call("printf '%s' " .. new_md5 .. " > " .. md5_file)
|
||||
sys.call("rm -rf " .. GEO_VAR.TO_SRS_PATH .. prefix .. "-*.srs" )
|
||||
end
|
||||
for k in pairs(tags) do
|
||||
geo_convert_srs({
|
||||
["geo_path"] = file_path,
|
||||
@@ -624,6 +617,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
realm.scheme = nil
|
||||
realm.address = nil
|
||||
realm.port = nil
|
||||
realm.port_mapping = (node.hysteria2_realm_upnp == "1") and { enabled = true } or nil
|
||||
return realm
|
||||
end
|
||||
return nil
|
||||
@@ -980,6 +974,7 @@ function gen_config_server(node)
|
||||
realm.address = nil
|
||||
realm.port = nil
|
||||
realm.stun_domain_resolver = "direct"
|
||||
realm.port_mapping = (node.hysteria2_realm_upnp == "1") and { enabled = true } or nil
|
||||
return realm
|
||||
end
|
||||
return nil
|
||||
@@ -1122,6 +1117,8 @@ function gen_config(var)
|
||||
local dns_listen_port = var["dns_listen_port"]
|
||||
local direct_dns_udp_server = var["direct_dns_udp_server"]
|
||||
local direct_dns_udp_port = var["direct_dns_udp_port"]
|
||||
local direct_dns_tcp_server = var["direct_dns_tcp_server"]
|
||||
local direct_dns_tcp_port = var["direct_dns_tcp_port"]
|
||||
local direct_dns_query_strategy = var["direct_dns_query_strategy"]
|
||||
local direct_ipset = var["direct_ipset"]
|
||||
local direct_nftset = var["direct_nftset"]
|
||||
@@ -1834,10 +1831,18 @@ function gen_config(var)
|
||||
server_port = tonumber(direct_dns_udp_port) or 53,
|
||||
detour = "direct",
|
||||
})
|
||||
elseif direct_dns_tcp_server then
|
||||
table.insert(dns.servers, {
|
||||
tag = "direct",
|
||||
type = "tcp",
|
||||
server = direct_dns_tcp_server,
|
||||
server_port = tonumber(direct_dns_tcp_port) or 53,
|
||||
detour = "direct",
|
||||
})
|
||||
end
|
||||
|
||||
for i, v in pairs(GLOBAL.DNS_SERVER) do
|
||||
if direct_dns_udp_server then
|
||||
if direct_dns_udp_server or direct_dns_tcp_server then
|
||||
v.server.domain_resolver = "direct"
|
||||
end
|
||||
table.insert(dns.servers, v.server)
|
||||
@@ -1902,7 +1907,7 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
|
||||
if direct_dns_udp_server then
|
||||
if direct_dns_udp_server or direct_dns_tcp_server then
|
||||
local nodes_domain = {}
|
||||
local nodes_domain_text = sys.exec('uci show passwall2 | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u')
|
||||
string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w)
|
||||
@@ -2089,6 +2094,7 @@ function gen_config(var)
|
||||
fakedns_dns_rule.query_type = { "A", "AAAA" }
|
||||
end
|
||||
fakedns_dns_rule.server = fakedns_tag
|
||||
fakedns_dns_rule.rewrite_ttl = 1
|
||||
fakedns_dns_rule.disable_cache = true
|
||||
fakedns_dns_rule.client_subnet = nil
|
||||
table.insert(dns.rules, fakedns_dns_rule)
|
||||
@@ -2125,7 +2131,7 @@ function gen_config(var)
|
||||
query_type = dns_rule_query_type,
|
||||
server = fakedns_tag,
|
||||
disable_cache = true,
|
||||
rewrite_ttl = tonumber(remote_rewrite_ttl)
|
||||
rewrite_ttl = 1
|
||||
}
|
||||
table.insert(dns.rules, fakedns_dns_rule)
|
||||
end
|
||||
|
||||
@@ -318,7 +318,8 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
type = "realm",
|
||||
settings = {
|
||||
url = url,
|
||||
stunServers = stun
|
||||
stunServers = stun,
|
||||
portMapping = (node.hysteria2_realm_upnp == "1") and { enabled = true } or nil
|
||||
}
|
||||
}
|
||||
udp[#udp+1] = r
|
||||
@@ -788,7 +789,8 @@ function gen_config_server(node)
|
||||
type = "realm",
|
||||
settings = {
|
||||
url = url,
|
||||
stunServers = stun
|
||||
stunServers = stun,
|
||||
portMapping = (node.hysteria2_realm_upnp == "1") and { enabled = true } or nil
|
||||
}
|
||||
}
|
||||
udp[#udp+1] = r
|
||||
@@ -887,6 +889,8 @@ function gen_config(var)
|
||||
local dns_listen_port = var["dns_listen_port"]
|
||||
local direct_dns_udp_server = var["direct_dns_udp_server"]
|
||||
local direct_dns_udp_port = var["direct_dns_udp_port"]
|
||||
local direct_dns_tcp_server = var["direct_dns_tcp_server"]
|
||||
local direct_dns_tcp_port = var["direct_dns_tcp_port"]
|
||||
local direct_dns_query_strategy = var["direct_dns_query_strategy"]
|
||||
local direct_ipset = var["direct_ipset"]
|
||||
local direct_nftset = var["direct_nftset"]
|
||||
@@ -1560,13 +1564,24 @@ function gen_config(var)
|
||||
port = tonumber(direct_dns_udp_port) or 53,
|
||||
queryStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP"
|
||||
}
|
||||
|
||||
if _direct_dns.address then
|
||||
table.insert(dns_servers, {
|
||||
outboundTag = "direct",
|
||||
server = _direct_dns
|
||||
})
|
||||
table.insert(dns_servers, {
|
||||
outboundTag = "direct",
|
||||
server = _direct_dns
|
||||
})
|
||||
elseif direct_dns_tcp_server then
|
||||
if api.is_ipv6(direct_dns_tcp_server) then
|
||||
direct_dns_tcp_server = api.get_ipv6_full(direct_dns_tcp_server)
|
||||
end
|
||||
_direct_dns = {
|
||||
tag = direct_dns_tag,
|
||||
address = "tcp://" .. direct_dns_tcp_server .. ":" .. tonumber(direct_dns_tcp_port) or 53,
|
||||
port = tonumber(direct_dns_tcp_port) or 53,
|
||||
queryStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP"
|
||||
}
|
||||
table.insert(dns_servers, {
|
||||
outboundTag = "direct",
|
||||
server = _direct_dns
|
||||
})
|
||||
end
|
||||
|
||||
if next(GLOBAL.DNS_HOSTNAME) then
|
||||
@@ -1687,7 +1702,7 @@ function gen_config(var)
|
||||
})
|
||||
end
|
||||
|
||||
if direct_dns_udp_server then
|
||||
if direct_dns_udp_server or direct_dns_tcp_server then
|
||||
local domain = {}
|
||||
local nodes_domain_text = sys.exec('uci show passwall2 | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u')
|
||||
string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w)
|
||||
|
||||
@@ -23,8 +23,8 @@ local map = self.map
|
||||
<% if luci.http.formvalue("cbi.apply") == "1" and map.is_js_luci then -%>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("luci-loaded", function() {
|
||||
L.env.apply_display = 2; //Write whatever you want, it's all just a fake progress bar anyway.
|
||||
L.env.apply_holdoff = 1;
|
||||
//L.env.apply_display = 2; //Write whatever you want, it's all just a fake progress bar anyway.
|
||||
//L.env.apply_holdoff = 1;
|
||||
|
||||
let tt;
|
||||
let flag = 2;
|
||||
@@ -50,10 +50,12 @@ local map = self.map
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
<% if not map.redirect then -%>
|
||||
if (tt)
|
||||
window.clearTimeout(tt);
|
||||
L.ui.changes.displayStatus(false);
|
||||
window.location = window.location.href.split('#')[0];
|
||||
L.ui.changes.displayStatus(false);
|
||||
<% if not map.redirect then -%>
|
||||
//window.location = window.location.href.split('#')[0];
|
||||
window.location = "<%=map.api.url("log")%>";
|
||||
<%- else %>
|
||||
window.location.href = "<%=map.redirect%>";
|
||||
<%- end %>
|
||||
|
||||
@@ -2420,6 +2420,9 @@ msgstr "اندازه بسته Gecko (دقیقه)"
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "اندازه بسته Gecko (حداکثر)"
|
||||
|
||||
msgid "Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."
|
||||
msgstr "برای بهبود موفقیت در پانچ کردن حفره، نگاشت پورت UPnP/NAT-PMP را روی گیتوی خود فعال کنید."
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "زمان معتبر (ساعت:میلیمتر)"
|
||||
|
||||
|
||||
@@ -2418,6 +2418,9 @@ msgstr "Размер упаковки Gecko (мин)"
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "Размер упаковки Gecko (макс)"
|
||||
|
||||
msgid "Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."
|
||||
msgstr "Включите сопоставление портов UPnP/NAT-PMP на вашем шлюзе, чтобы повысить вероятность успешного выполнения операций "пробивания дыр"."
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "Время действия (чч:мм)"
|
||||
|
||||
|
||||
@@ -2406,6 +2406,9 @@ msgstr "Gecko 包大小(最小)"
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "Gecko 包大小(最大)"
|
||||
|
||||
msgid "Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."
|
||||
msgstr "在网关上启用 UPnP/NAT-PMP 端口映射以提升打洞成功率。"
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "有效时间(hh:mm)"
|
||||
|
||||
|
||||
@@ -2412,6 +2412,9 @@ msgstr "Gecko 包大小(最小)"
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "Gecko 包大小(最大)"
|
||||
|
||||
msgid "Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."
|
||||
msgstr "在網關上啟用 UPnP/NAT-PMP 連接埠對映以提升打洞成功率。"
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "有效時間(hh:mm)"
|
||||
|
||||
|
||||
@@ -99,8 +99,11 @@ run_xray() {
|
||||
json_add_string "local_http_password" "${http_password}"
|
||||
}
|
||||
}
|
||||
local direct_dns_proto=${DIRECT_DNS_PROTO}
|
||||
local direct_dns_server=${DIRECT_DNS_SERVER}
|
||||
local direct_dns_port=${DIRECT_DNS_PORT}
|
||||
[ -n "$dns_listen_port" ] && {
|
||||
local dns_msg="DNS[${dns_listen_port}]:($(i18n "Direct DNS: %s" "${AUTO_DNS}")"
|
||||
local dns_msg="DNS[${dns_listen_port}]:($(i18n "Direct DNS: %s" "${direct_dns_proto}://${direct_dns_server}:${direct_dns_port}")"
|
||||
json_add_string "dns_listen_port" "${dns_listen_port}"
|
||||
[ -n "$dns_cache" ] && json_add_string "dns_cache" "${dns_cache}"
|
||||
[ "${node_protocol}" = "_shunt" ] && local write_ipset_direct=$(config_n_get $node write_ipset_direct 0)
|
||||
@@ -116,9 +119,10 @@ run_xray() {
|
||||
local direct_ipset6="psw2_${node}_white6"
|
||||
local direct_ipset="${direct_ipset4},${direct_ipset6}"
|
||||
fi
|
||||
run_ipset_dns_server listen_port=${direct_dnsmasq_listen_port} server_dns=${AUTO_DNS} ipset="${direct_ipset}" nftset="${direct_nftset}" config_file=${direct_ipset_conf}
|
||||
DIRECT_DNS_UDP_PORT=${direct_dnsmasq_listen_port}
|
||||
DIRECT_DNS_UDP_SERVER="127.0.0.1"
|
||||
run_ipset_dns_server listen_port=${direct_dnsmasq_listen_port} proto=${direct_dns_proto} server_dns="${direct_dns_server}#${direct_dns_port}" ipset="${direct_ipset}" nftset="${direct_nftset}" config_file=${direct_ipset_conf}
|
||||
direct_dns_proto="udp"
|
||||
direct_dns_server="127.0.0.1"
|
||||
direct_dns_port=${direct_dnsmasq_listen_port}
|
||||
[ -n "${direct_ipset}" ] && {
|
||||
json_add_string "direct_ipset" "${direct_ipset}"
|
||||
set_cache_var "node_${node}_direct_ipset4" "${direct_ipset4}"
|
||||
@@ -168,8 +172,8 @@ run_xray() {
|
||||
[ -n "$remote_dns_client_ip" ] && json_add_string "remote_dns_client_ip" "${remote_dns_client_ip}"
|
||||
log_out="${dns_msg})"
|
||||
}
|
||||
json_add_string "direct_dns_udp_port" "${DIRECT_DNS_UDP_PORT}"
|
||||
json_add_string "direct_dns_udp_server" "${DIRECT_DNS_UDP_SERVER}"
|
||||
json_add_string "direct_dns_${direct_dns_proto}_server" "${direct_dns_server}"
|
||||
json_add_string "direct_dns_${direct_dns_proto}_port" "${direct_dns_port}"
|
||||
json_add_string "direct_dns_query_strategy" "${direct_dns_query_strategy}"
|
||||
|
||||
[ -n "${redir_port}" ] && {
|
||||
@@ -241,8 +245,11 @@ run_singbox() {
|
||||
json_add_string "local_http_password" "${http_password}"
|
||||
}
|
||||
}
|
||||
local direct_dns_proto=${DIRECT_DNS_PROTO}
|
||||
local direct_dns_server=${DIRECT_DNS_SERVER}
|
||||
local direct_dns_port=${DIRECT_DNS_PORT}
|
||||
[ -n "$dns_listen_port" ] && {
|
||||
local dns_msg="DNS[${dns_listen_port}]:($(i18n "Direct DNS: %s" "${AUTO_DNS}")"
|
||||
local dns_msg="DNS[${dns_listen_port}]:($(i18n "Direct DNS: %s" "${direct_dns_proto}://${direct_dns_server}:${direct_dns_port}")"
|
||||
json_add_string "dns_listen_port" "${dns_listen_port}"
|
||||
[ -n "$dns_cache" ] && json_add_string "dns_cache" "${dns_cache}"
|
||||
[ "${node_protocol}" = "_shunt" ] && local write_ipset_direct=$(config_n_get $node write_ipset_direct 0)
|
||||
@@ -258,9 +265,10 @@ run_singbox() {
|
||||
local direct_ipset6="psw2_${node}_white6"
|
||||
local direct_ipset="${direct_ipset4},${direct_ipset6}"
|
||||
fi
|
||||
run_ipset_dns_server listen_port=${direct_dnsmasq_listen_port} server_dns=${AUTO_DNS} ipset="${direct_ipset}" nftset="${direct_nftset}" config_file=${direct_ipset_conf}
|
||||
DIRECT_DNS_UDP_PORT=${direct_dnsmasq_listen_port}
|
||||
DIRECT_DNS_UDP_SERVER="127.0.0.1"
|
||||
run_ipset_dns_server listen_port=${direct_dnsmasq_listen_port} proto=${direct_dns_proto} server_dns="${direct_dns_server}#${direct_dns_port}" ipset="${direct_ipset}" nftset="${direct_nftset}" config_file=${direct_ipset_conf}
|
||||
direct_dns_proto="udp"
|
||||
direct_dns_server="127.0.0.1"
|
||||
direct_dns_port=${direct_dnsmasq_listen_port}
|
||||
[ -n "${direct_ipset}" ] && {
|
||||
json_add_string "direct_ipset" "${direct_ipset}"
|
||||
set_cache_var "node_${node}_direct_ipset4" "${direct_ipset4}"
|
||||
@@ -318,8 +326,8 @@ run_singbox() {
|
||||
[ -n "$remote_rewrite_ttl" ] && json_add_string "remote_rewrite_ttl" "${remote_rewrite_ttl}"
|
||||
log_out="${dns_msg})"
|
||||
}
|
||||
json_add_string "direct_dns_udp_port" "${DIRECT_DNS_UDP_PORT}"
|
||||
json_add_string "direct_dns_udp_server" "${DIRECT_DNS_UDP_SERVER}"
|
||||
json_add_string "direct_dns_${direct_dns_proto}_server" "${direct_dns_server}"
|
||||
json_add_string "direct_dns_${direct_dns_proto}_port" "${direct_dns_port}"
|
||||
json_add_string "direct_dns_query_strategy" "${direct_dns_query_strategy}"
|
||||
|
||||
[ -n "${redir_port}" ] && {
|
||||
@@ -423,8 +431,8 @@ run_socks() {
|
||||
json_add_string "flag" "${flag}"
|
||||
json_add_string "local_socks_address" "${bind}"
|
||||
json_add_string "local_socks_port" "${socks_port}"
|
||||
json_add_string "direct_dns_udp_port" "${DIRECT_DNS_UDP_PORT}"
|
||||
json_add_string "direct_dns_udp_server" "${DIRECT_DNS_UDP_SERVER}"
|
||||
json_add_string "direct_dns_${DIRECT_DNS_PROTO}_server" "${DIRECT_DNS_SERVER}"
|
||||
json_add_string "direct_dns_${DIRECT_DNS_PROTO}_port" "${DIRECT_DNS_PORT}"
|
||||
json_add_string "direct_dns_query_strategy" "${DIRECT_DNS_QUERY_STRATEGY}"
|
||||
local _json_arg="$(json_dump)"
|
||||
lua $UTIL_SINGBOX gen_config "${_json_arg}" > $config_file
|
||||
@@ -450,8 +458,8 @@ run_socks() {
|
||||
json_add_string "flag" "${flag}"
|
||||
json_add_string "local_socks_address" "${bind}"
|
||||
json_add_string "local_socks_port" "${socks_port}"
|
||||
json_add_string "direct_dns_udp_port" "${DIRECT_DNS_UDP_PORT}"
|
||||
json_add_string "direct_dns_udp_server" "${DIRECT_DNS_UDP_SERVER}"
|
||||
json_add_string "direct_dns_${DIRECT_DNS_PROTO}_server" "${DIRECT_DNS_SERVER}"
|
||||
json_add_string "direct_dns_${DIRECT_DNS_PROTO}_port" "${DIRECT_DNS_PORT}"
|
||||
json_add_string "direct_dns_query_strategy" "${DIRECT_DNS_QUERY_STRATEGY}"
|
||||
local _json_arg="$(json_dump)"
|
||||
lua $UTIL_XRAY gen_config "${_json_arg}" > $config_file
|
||||
@@ -759,7 +767,7 @@ run_ipset_dns_server() {
|
||||
}
|
||||
|
||||
run_ipset_chinadns_ng() {
|
||||
local listen_port server_dns ipset nftset config_file
|
||||
local listen_port proto server_dns ipset nftset config_file
|
||||
eval_set_val $@
|
||||
[ ! -s "$TMP_ACL_PATH/vpslist" ] && {
|
||||
node_servers=$(uci show "${CONFIG}" | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2)
|
||||
@@ -777,14 +785,14 @@ run_ipset_chinadns_ng() {
|
||||
cat <<-EOF > $config_file
|
||||
bind-addr 127.0.0.1
|
||||
bind-port ${listen_port}
|
||||
china-dns ${server_dns}
|
||||
trust-dns ${server_dns}
|
||||
china-dns ${proto}://${server_dns}
|
||||
trust-dns ${proto}://${server_dns}
|
||||
filter-qtype 65
|
||||
add-tagchn-ip ${set_names}
|
||||
default-tag chn
|
||||
group vpslist
|
||||
group-dnl $TMP_ACL_PATH/vpslist
|
||||
group-upstream ${server_dns}
|
||||
group-upstream ${proto}://${server_dns}
|
||||
group-ipset ${vps_set_names}
|
||||
EOF
|
||||
ln_run 0 "$(first_type chinadns-ng)" "chinadns-ng" "/dev/null" -C $config_file -v
|
||||
@@ -1004,13 +1012,27 @@ get_direct_dns() {
|
||||
DEFAULT_DNS="${DNSMASQ_UPSTREAM_DNS}"
|
||||
[ -z "${DEFAULT_DNS}" ] && DEFAULT_DNS=$(echo -n $ISP_DNS | tr ' ' '\n' | head -2 | tr '\n' ',' | sed 's/,$//')
|
||||
AUTO_DNS=${DEFAULT_DNS:-119.29.29.29}
|
||||
RETURN_DNS=${AUTO_DNS}
|
||||
|
||||
local AUTO_DNS_1=$(echo ${AUTO_DNS} | awk -F ',' '{print $1}')
|
||||
local AUTO_DNS_2=$(echo ${AUTO_DNS} | awk -F ',' '{print $2}')
|
||||
local AUTO_DNS_ADDRESS=$(echo ${AUTO_DNS_1} | awk -F '#' '{print $1}')
|
||||
local AUTO_DNS_PORT=$(echo ${AUTO_DNS_1} | awk -F '#' '{print $2}')
|
||||
DIRECT_DNS_UDP_SERVER=${AUTO_DNS_ADDRESS}
|
||||
DIRECT_DNS_UDP_PORT=${AUTO_DNS_PORT}
|
||||
|
||||
DIRECT_DNS_PROTO="udp"
|
||||
DIRECT_DNS_SERVER=$(echo ${AUTO_DNS_1} | awk -F '#' '{print $1}')
|
||||
DIRECT_DNS_PORT=$(echo ${AUTO_DNS_1} | awk -F '#' '{print $2}')
|
||||
DIRECT_DNS_PORT=${DIRECT_DNS_PORT:-53}
|
||||
|
||||
local direct_dns_protocol=$(config_n_get @global[0] direct_dns_protocol)
|
||||
if [ "${direct_dns_protocol}" = "tcp" ] || [ "${direct_dns_protocol}" = "udp" ]; then
|
||||
local DIRECT_DNS=$(config_n_get @global[0] direct_dns)
|
||||
local result=$(lua_api "parseDNS(\"${DIRECT_DNS}\")")
|
||||
[ "${result}" != "nil" ] && {
|
||||
DIRECT_DNS_PROTO="${direct_dns_protocol}"
|
||||
DIRECT_DNS_SERVER=$(echo ${result} | awk '{print $1}')
|
||||
DIRECT_DNS_PORT=$(echo ${result} | awk '{print $2}')
|
||||
RETURN_DNS="${RETURN_DNS},${DIRECT_DNS_SERVER}#${DIRECT_DNS_PORT}#${DIRECT_DNS_PROTO}"
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
get_config() {
|
||||
|
||||
@@ -809,15 +809,17 @@ add_firewall_rule() {
|
||||
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_VPS6) -j RETURN
|
||||
$ip6t_m -A PSW2_OUTPUT -m conntrack --ctdir REPLY -j RETURN
|
||||
|
||||
[ -n "$AUTO_DNS" ] && {
|
||||
for auto_dns in $(echo $AUTO_DNS | tr ',' ' '); do
|
||||
local dns_address=$(echo $auto_dns | awk -F '#' '{print $1}')
|
||||
local dns_port=$(echo $auto_dns | awk -F '#' '{print $2}')
|
||||
[ -n "$RETURN_DNS" ] && {
|
||||
for _dns in $(echo $RETURN_DNS | tr ',' ' '); do
|
||||
local dns_address=$(echo $_dns | awk -F '#' '{print $1}')
|
||||
local dns_port=$(echo $_dns | awk -F '#' '{print $2}')
|
||||
local dns_proto=$(echo $_dns | awk -F '#' '{print $3}')
|
||||
dns_proto=${dns_proto:-udp}
|
||||
if [[ "$dns_address" == *::* ]]; then
|
||||
$ip6t_m -I PSW2_OUTPUT -p udp -d ${dns_address} --dport ${dns_port:-53} -j RETURN
|
||||
$ip6t_m -I PSW2_OUTPUT -p ${dns_proto} -d ${dns_address} --dport ${dns_port:-53} -j RETURN
|
||||
log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "ip6tables" "[${dns_address}]:${dns_port:-53}")"
|
||||
else
|
||||
$ipt_m -I PSW2_OUTPUT -p udp -d ${dns_address} --dport ${dns_port:-53} -j RETURN
|
||||
$ipt_m -I PSW2_OUTPUT -p ${dns_proto} -d ${dns_address} --dport ${dns_port:-53} -j RETURN
|
||||
log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "iptables" "${dns_address}:${dns_port:-53}")"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -862,15 +862,17 @@ add_firewall_rule() {
|
||||
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ct direction reply counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta mark 255 counter return"
|
||||
|
||||
[ -n "$AUTO_DNS" ] && {
|
||||
for auto_dns in $(echo $AUTO_DNS | tr ',' ' '); do
|
||||
local dns_address=$(echo $auto_dns | awk -F '#' '{print $1}')
|
||||
local dns_port=$(echo $auto_dns | awk -F '#' '{print $2}')
|
||||
[ -n "$RETURN_DNS" ] && {
|
||||
for _dns in $(echo $RETURN_DNS | tr ',' ' '); do
|
||||
local dns_address=$(echo $_dns | awk -F '#' '{print $1}')
|
||||
local dns_port=$(echo $_dns | awk -F '#' '{print $2}')
|
||||
local dns_proto=$(echo $_dns | awk -F '#' '{print $3}')
|
||||
dns_proto=${dns_proto:-udp}
|
||||
if [[ "$dns_address" == *::* ]]; then
|
||||
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr ${dns_address} $(factor ${dns_port:-53} "udp dport") counter return"
|
||||
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto ${dns_proto} ip6 daddr ${dns_address} $(factor ${dns_port:-53} "${dns_proto} dport") counter return"
|
||||
log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "nftables" "[${dns_address}]:${dns_port:-53}")"
|
||||
else
|
||||
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr ${dns_address} $(factor ${dns_port:-53} "udp dport") counter return"
|
||||
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol ${dns_proto} ip daddr ${dns_address} $(factor ${dns_port:-53} "${dns_proto} dport") counter return"
|
||||
log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "nftables" "${dns_address}:${dns_port:-53}")"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=42
|
||||
PKG_RELEASE:=43
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
|
||||
@@ -99,27 +99,95 @@ function run() {
|
||||
*
|
||||
* Wrapping `dom.content()` itself also works, at the price of patching a luci-base API every app
|
||||
* shares and up to seven read/write pairs per call. */
|
||||
/* The three things `dom.content()` is called on: a section body, a table, and a TABLE'S BODY. The
|
||||
* third was missing and cost a release: on 24.10's Overview the section is a table, so nothing here
|
||||
* matched, the floor held nothing, and a poll emptying it took 58px off the document under the
|
||||
* reader — on ImmortalWrt 24.10 with a webkit engine, where no CI job looks. tools/scroll-anchor.mjs
|
||||
* looks for the same three and says why. */
|
||||
/* A section body and a table: what `dom.content()` is called on and this can hold. Its third target,
|
||||
* a table's BODY, is deliberately absent — a floor there holds nothing. `min-height` is undefined on
|
||||
* a table box (CSS 2.1 §10.7) and WebKit acts on that: a `.table.cbi-section-table` carrying a 313px
|
||||
* floor still collapsed to 30px and the document lost 284px (webkit, /admin/network/firewall, 24.10
|
||||
* and 25.12 alike; Chromium holds the 313px), and writing the floor on the `.tbody` instead loses
|
||||
* the same 284px. 24.10 has no `.tbody` at all: its Overview renders `<table class="table">` with the
|
||||
* rows directly inside, and the container a poll empties there is `.cbi-section > div`.
|
||||
* tools/scroll-anchor.mjs looks for all three when it picks a box to collapse, which is a different
|
||||
* question from which box can carry a floor. */
|
||||
const SHRINKS = '.cbi-section > div, .table';
|
||||
|
||||
/* The floor is the height the next tick may not go below, one per container. Cleared before the
|
||||
* read, or each floor measures itself and never comes down; batched into one clear, one read pass
|
||||
* and one write pass, so the whole sweep costs a single forced layout rather than one per element.
|
||||
/* What the container would stand at with no floor under it, asked of the CONTENT rather than by
|
||||
* taking the floor off and re-measuring.
|
||||
*
|
||||
* Not while the reader scrolls: clearing to re-measure is a layout read, and a floor staying where
|
||||
* it was is still a floor. */
|
||||
* Clearing `min-height` to re-measure was the obvious way and is the expensive one: `min-height` is
|
||||
* a scroll-anchoring suppression trigger on the path from the anchor to the scroller
|
||||
* (css-scroll-anchoring-1 §3.2), so a clear, a forced layout and a write back tell the engine to
|
||||
* drop its own compensation for that frame — the theme switching off the very thing it relies on,
|
||||
* once per tick.
|
||||
*
|
||||
* The span of the children answers the same question with reads alone. It is the box's content
|
||||
* height plus what the box adds below it; a collapsed margin on the last child can put it a few
|
||||
* pixels out, which a floor can afford — the floor is a lower bound on a height that is about to be
|
||||
* replaced, not a layout the page is drawn to.
|
||||
*
|
||||
* An empty container answers 0, and the caller keeps the standing floor for exactly that reason:
|
||||
* `dom.content()` empties before it refills, and 0 is the number the floor exists to refuse. */
|
||||
function naturalHeight(el) {
|
||||
const last = el.lastElementChild;
|
||||
if (!last) return 0;
|
||||
const box = el.getBoundingClientRect();
|
||||
const end = last.getBoundingClientRect();
|
||||
if (!end.height && !end.width) return 0; /* a last child out of the flow says nothing */
|
||||
const cs = window.getComputedStyle(el);
|
||||
const below = parseFloat(cs.paddingBottom) + parseFloat(cs.borderBottomWidth);
|
||||
return Math.round(end.bottom - box.top + (below || 0));
|
||||
}
|
||||
|
||||
/* The floor is the height the next tick may not go below, one per container. Read in one pass and
|
||||
* written in another, so the sweep costs a single forced layout rather than one per element.
|
||||
*
|
||||
* WRITTEN ONLY WHERE THE VALUE CHANGES, which is the difference between a floor and a page the
|
||||
* engine refuses to anchor: see naturalHeight() above. Measured over 25 s of real polling on the
|
||||
* Overview at 390px, the clear-and-rewrite shape wrote style 1550 times on 25.12 and 170 times on
|
||||
* ImmortalWrt 24.10, of which 75 and 62 carried a value that had actually moved — the rest were
|
||||
* suppression bought for nothing.
|
||||
*
|
||||
* Not while the reader scrolls: a rect read is a forced layout, and a floor staying where it was is
|
||||
* still a floor. */
|
||||
/* The floor is the height the next tick may not go below, one per box. Read in one pass and written
|
||||
* in another, so the sweep costs a single forced layout rather than one per element.
|
||||
*
|
||||
* WRITTEN ONLY WHERE THE VALUE CHANGES, which is the difference between a floor and a page the
|
||||
* engine refuses to anchor: see naturalHeight() above. Measured over 25 s of real polling on the
|
||||
* Overview at 390px, the clear-and-rewrite shape wrote style 1550 times on 25.12 and 170 times on
|
||||
* ImmortalWrt 24.10, of which 75 and 62 carried a value that had actually moved — the rest were
|
||||
* suppression bought for nothing. It is 45 writes, all of them real, on both.
|
||||
*
|
||||
* AND NOT ON A TABLE BOX, which cannot hold it: `min-height` is undefined there (CSS 2.1 §10.7) and
|
||||
* WebKit acts on that — a `.table.cbi-section-table` wearing a 313px floor still collapsed to 30px
|
||||
* when its rows went, and the document lost 284px on /admin/network/firewall, the same on 24.10 and
|
||||
* 25.12, while Chromium held the 313px. The `.tbody` inside it is a table box too and loses the same
|
||||
* 284px. So the floor climbs to the first box that is not one — the section — where the same
|
||||
* emptied `.tbody` costs the document 0px on both engines. `getComputedStyle` resolves style, not
|
||||
* layout, so the climb adds no forced layout of its own.
|
||||
*
|
||||
* Not while the reader scrolls: a rect read is a forced layout, and a floor staying where it was is
|
||||
* still a floor. */
|
||||
function holdFloor() {
|
||||
if (scrolling()) return;
|
||||
const host = document.getElementById('view');
|
||||
if (!host) return; /* the login page has no view */
|
||||
const els = host.querySelectorAll(SHRINKS), hs = [];
|
||||
els.forEach((el) => { el.style.minHeight = ''; });
|
||||
els.forEach((el) => hs.push(el.offsetHeight));
|
||||
els.forEach((el, i) => { if (hs[i] > 0) el.style.minHeight = hs[i] + 'px'; });
|
||||
const boxes = [], hs = [];
|
||||
host.querySelectorAll(SHRINKS).forEach((el) => {
|
||||
let box = el;
|
||||
while (box && box !== host && window.getComputedStyle(box).display.startsWith('table'))
|
||||
box = box.parentElement;
|
||||
/* several tables in one section climb to the same box; it needs one floor, not one each */
|
||||
if (!box || box === host || boxes.indexOf(box) !== -1) return;
|
||||
boxes.push(box);
|
||||
hs.push(naturalHeight(box));
|
||||
});
|
||||
boxes.forEach((box, i) => {
|
||||
/* zero is an empty box, and the floor it already wears is what holds it up — the moment this
|
||||
* whole mechanism exists for */
|
||||
if (hs[i] <= 0) return;
|
||||
const px = hs[i] + 'px';
|
||||
if (box.style.minHeight !== px) box.style.minHeight = px;
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- is the page moving right now? asked of the position, never of the events ----
|
||||
@@ -202,6 +270,11 @@ function sampleMotion() {
|
||||
/* the page has held still for SCROLL_IDLE: whatever was put off may run now */
|
||||
if (_deferred) {
|
||||
_deferred = false;
|
||||
/* where the reference stands BEFORE the put-off pass re-lays the page — see settleDrift() */
|
||||
const settled = _rest;
|
||||
const before = (settled && settled.el && settled.el.isConnected)
|
||||
? settled.el.getBoundingClientRect().top
|
||||
: ((settled && settled.sec && settled.sec.isConnected) ? settled.sec.getBoundingClientRect().top : null);
|
||||
/* No correction for this batch. Both available references are wrong for a page the reader
|
||||
* has just scrolled through: a fresh one is read against an offset WebKit may not have laid
|
||||
* out yet (the theme then undoes the reader's own move), and the one from the last still
|
||||
@@ -210,9 +283,59 @@ function sampleMotion() {
|
||||
* the fitters re-measure what the scroll already showed rather than growing the page — and
|
||||
* the next mutation corrects against a reference taken while the page was still. */
|
||||
run();
|
||||
if (ENGINE_ANCHORS) settleDrift(settled, before);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- the put-off pass moves the page too, and nothing was looking ----
|
||||
*
|
||||
* A tick landing while the offset is in motion leaves its measurements to the block above, and that
|
||||
* pass then re-lays the tables it could not measure. An anchoring engine answers that layout change
|
||||
* the way it answers any other — and `min-height`, which the floor writes on every container, is
|
||||
* itself a suppression trigger on the path to the anchor (css-scroll-anchoring-1 §3.2), so the
|
||||
* engine's compensation can be switched off by the very pass that needs it. Measured on
|
||||
* ImmortalWrt 24.10/WebKit: this pass's 88 `min-height` writes and a 58px jump of the offset land in
|
||||
* the SAME frame, 429 ms after the mutation (@390, top layout, large density, Overview).
|
||||
*
|
||||
* `lateDrift()` cannot see it: it is scheduled from the mutation on the same SCROLL_IDLE, so it
|
||||
* measures ALONGSIDE this pass rather than after it — it read a drift of zero three milliseconds
|
||||
* before the page moved, and scroll-anchor reported those 58px on that cell alone out of 48.
|
||||
*
|
||||
* MEASURED SYNCHRONOUSLY AROUND THE PASS, not a frame or an idle window later. Two reasons, and the
|
||||
* second cost a run: the reader cannot scroll between two statements, so what this sees is the
|
||||
* pass's doing and nothing else — a version that looked two frames later corrected inside a flick on
|
||||
* three cells of the same sweep. And `getBoundingClientRect()` is exactly the operation the spec
|
||||
* makes the engine flush a pending adjustment before, so the read after the pass sees the engine's
|
||||
* answer rather than racing it (§2.2: the suppression window ends at the end of the event loop
|
||||
* iteration, or before the next operation whose result would differ, whichever is sooner). */
|
||||
function settleDrift(ref, before) {
|
||||
if (before == null || !ref) return;
|
||||
if (!anchorEnabled() || Date.now() < _userUntil) return;
|
||||
if (_restPage !== pageStamp()) return;
|
||||
const el = (ref.el && ref.el.isConnected) ? ref.el : ((ref.sec && ref.sec.isConnected) ? ref.sec : null);
|
||||
if (el) putBack(el, before);
|
||||
}
|
||||
|
||||
/* Give the reader back what moved under them: the one write both corrections make, and the rules
|
||||
* that write obeys.
|
||||
*
|
||||
* A drift under a pixel is rounding, and an engine that answered for it reads the same. One
|
||||
* viewport is the ceiling, a drift that size being a view that replaced its whole subtree rather
|
||||
* than a tick — anchorFor() raises it for the one drift that big with a receipt, a measured clamp.
|
||||
*
|
||||
* The write moves the page by exactly the drift measured, so the reference is back at the top it
|
||||
* was remembered at and the next tick measures zero. Only `_restAt` moves, and the write may have
|
||||
* been clamped short, so it is re-read rather than assumed; `rememberRest()` cannot do it, since
|
||||
* the write starts the motion sampler and that function returns early while the page moves. */
|
||||
function putBack(el, was) {
|
||||
const drift = el.getBoundingClientRect().top - was;
|
||||
if (Math.abs(drift) < 1 || Math.abs(drift) > (window.innerHeight || 800)) return;
|
||||
const sc = scroller();
|
||||
const at = sc ? sc.scrollTop : window.scrollY;
|
||||
if (sc) sc.scrollTop = at + drift; else window.scrollTo(0, at + drift);
|
||||
_restAt = scrollTop();
|
||||
}
|
||||
|
||||
function noteMotion() {
|
||||
_movingUntil = Date.now() + SCROLL_IDLE;
|
||||
if (_sampling) return;
|
||||
@@ -563,23 +686,8 @@ function lateDrift(ref) {
|
||||
if (scrollTop() !== seen) return;
|
||||
/* the tick usually replaces the element this was taken on, so without the section
|
||||
* fallback the correction does nothing on the tick it exists for */
|
||||
let el = ref.el, was = ref.top;
|
||||
if (!el || !el.isConnected) {
|
||||
if (!ref.sec || !ref.sec.isConnected || ref.secTop == null) return;
|
||||
el = ref.sec; was = ref.secTop;
|
||||
}
|
||||
const drift = el.getBoundingClientRect().top - was;
|
||||
if (Math.abs(drift) < 1) return; /* the engine put it back */
|
||||
if (Math.abs(drift) > (window.innerHeight || 800)) return;
|
||||
const sc = scroller();
|
||||
const at = sc ? sc.scrollTop : window.scrollY;
|
||||
if (sc) sc.scrollTop = at + drift; else window.scrollTo(0, at + drift);
|
||||
/* The write moves the page by exactly the drift measured, which puts the reference back
|
||||
* at the top it was remembered at, so `_rest.top` still holds and the next tick
|
||||
* measures zero. Only `_restAt` changes, and the write may have been clamped short, so
|
||||
* it is re-read rather than assumed; `rememberRest()` cannot do it, since the write
|
||||
* starts the motion sampler and that function returns early while the page moves. */
|
||||
_restAt = scrollTop();
|
||||
if (ref.el && ref.el.isConnected) putBack(ref.el, ref.top);
|
||||
else if (ref.sec && ref.sec.isConnected && ref.secTop != null) putBack(ref.sec, ref.secTop);
|
||||
}, SCROLL_IDLE);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user