mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 18:44:03 +08:00
Compare commits
15
Commits
7658fdeeae
...
d733dbc21e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d733dbc21e | ||
|
|
74fb8aab69 | ||
|
|
bc48d0d1d3 | ||
|
|
9e39c8f144 | ||
|
|
9c6642b69b | ||
|
|
2a3f0fa893 | ||
|
|
6a028a2c76 | ||
|
|
74e8f32f02 | ||
|
|
5c2e942e2f | ||
|
|
9894670f08 | ||
|
|
91946c7de3 | ||
|
|
417e630d13 | ||
|
|
7172e6af38 | ||
|
|
805e42256f | ||
|
|
cbc4bfdd98 |
No files matched your search
@@ -1,6 +1,12 @@
|
||||
--- a/luci-mod-dashboard/root/usr/share/luci/menu.d/luci-mod-dashboard.json
|
||||
+++ b/luci-mod-dashboard/root/usr/share/luci/menu.d/luci-mod-dashboard.json
|
||||
@@ -7,7 +7,10 @@
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"admin/dashboard": {
|
||||
- "title": "Dashboard",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "dashboard/index"
|
||||
},
|
||||
"depends": {
|
||||
|
||||
@@ -451,3 +451,22 @@
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
--- a/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc
|
||||
+++ b/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc
|
||||
@@ -316,6 +316,15 @@ function run_ttyd(request) {
|
||||
return { error: 'Container ID is required' };
|
||||
}
|
||||
|
||||
+ // Strictly validate all user-supplied values before they are interpolated
|
||||
+ // into a shell command string to prevent command injection.
|
||||
+ if (!match(id, /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/) ||
|
||||
+ !match(`${port}`, /^[0-9]{1,5}$/) || int(port) < 1 || int(port) > 65535 ||
|
||||
+ (uid && !match(`${uid}`, /^[a-zA-Z0-9_-]+$/)) ||
|
||||
+ !match(cmd, /^[a-zA-Z0-9_\/.\- ]+$/)) {
|
||||
+ return { error: 'Invalid parameter supplied' };
|
||||
+ }
|
||||
+
|
||||
let ttyd_cmd = `ttyd -q -d 2 --once --writable -p ${port} docker`;
|
||||
const sock_addr = ds.get_socket_dest();
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
--- a/luci-app-ssrserver-python/root/usr/share/ssr/utils/autoban.py
|
||||
+++ b/luci-app-ssrserver-python/root/usr/share/ssr/utils/autoban.py
|
||||
@@ -25,9 +25,12 @@
|
||||
with_statement
|
||||
|
||||
import os
|
||||
+import re
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
+_VALID_IP_RE = re.compile(r'^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$')
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='See README')
|
||||
parser.add_argument('-c', '--count', default=3, type=int,
|
||||
@@ -45,7 +48,7 @@
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
ips[ip] += 1
|
||||
- if ip not in banned and ips[ip] >= config.count:
|
||||
+ if ip not in banned and ips[ip] >= config.count and _VALID_IP_RE.match(ip):
|
||||
banned.add(ip)
|
||||
cmd = 'iptables -A INPUT -s %s -j DROP' % ip
|
||||
print(cmd, file=sys.stderr)
|
||||
+3
-3
@@ -10,12 +10,12 @@ AGENTFLOW_ARCH_x86_64:=amd64
|
||||
AGENTFLOW_ARCH_aarch64:=arm64
|
||||
AGENTFLOW_ARCH:=$(AGENTFLOW_ARCH_$(ARCH))
|
||||
|
||||
AGENTFLOW_HASH_x86_64:=95cb43ba4e2ce5f060e3609d050caf25170dc151ba882a3a626494b7750d5fbc
|
||||
AGENTFLOW_HASH_aarch64:=93e92628b26087b87d8571400e83046197167e4795593e757d4dcf7dac5f8231
|
||||
AGENTFLOW_HASH_x86_64:=478fdb17cfb2b1664e7bc01315186e71f228871c0d20c816d8b3b8fe789bdb4d
|
||||
AGENTFLOW_HASH_aarch64:=257faeaad2445dd14f87c1e1d9ded869fd301c5df2c2c9af817e70303eab2512
|
||||
|
||||
PKG_NAME:=agentflow
|
||||
PKG_VERSION:=0.3.3
|
||||
PKG_RELEASE:=11
|
||||
PKG_RELEASE:=12
|
||||
|
||||
AGENTFLOW_URL_FILE:=agentflow-linux-$(AGENTFLOW_ARCH)
|
||||
AGENTFLOW_DOWNLOAD:=$(AGENTFLOW_URL_FILE)-$(AGENTFLOW_HASH_$(ARCH))
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=AgentFlow
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=5
|
||||
LUCI_DEPENDS:=+agentflow +luci-compat
|
||||
PKG_VERSION:=1.0.1
|
||||
PKG_RELEASE:=6
|
||||
LUCI_DEPENDS:=+agentflow +luci-compat +luci-lib-taskd
|
||||
LUCI_MINIFY_CSS:=0
|
||||
LUCI_MINIFY_JS:=0
|
||||
|
||||
|
||||
@@ -17,10 +17,38 @@ function index()
|
||||
return
|
||||
end
|
||||
|
||||
local install = entry({"admin", "services", "agentflow", "agent_install"}, call("agentflow_agent_install"))
|
||||
install.leaf = true
|
||||
|
||||
local page = entry({"admin", "services", "agentflow"}, cbi("agentflow"), _("AgentFlow"), 100)
|
||||
page.dependent = true
|
||||
end
|
||||
|
||||
local function write_json(obj)
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(obj)
|
||||
end
|
||||
|
||||
local function require_post_csrf()
|
||||
local dispatcher = require "luci.dispatcher"
|
||||
local context = dispatcher.context or {}
|
||||
local expected = context.authtoken or context.token
|
||||
|
||||
if (http.getenv("REQUEST_METHOD") or "") ~= "POST" then
|
||||
http.status(405, "Method Not Allowed")
|
||||
write_json({ ok = false, error = "method not allowed" })
|
||||
return false
|
||||
end
|
||||
|
||||
if not context.authsession or not expected or http.formvalue("token") ~= expected then
|
||||
http.status(403, "Forbidden")
|
||||
write_json({ ok = false, error = "invalid csrf token" })
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function uhttpd_has_apps_proxy_prefix()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local mappings = uci:get_list("uhttpd", "main", "proxy_prefix") or {}
|
||||
@@ -119,8 +147,97 @@ function agentflow_status()
|
||||
proxy_prefix_enabled = uhttpd_apps_proxy_available(),
|
||||
linkeasefull_running = linkeasefull_running()
|
||||
}
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(status)
|
||||
write_json(status)
|
||||
end
|
||||
|
||||
function agentflow_agent_install()
|
||||
local fs = require "nixio.fs"
|
||||
local jsonc = require "luci.jsonc"
|
||||
local sys = require "luci.sys"
|
||||
local util = require "luci.util"
|
||||
local task_id = "agentflow-agent-install"
|
||||
local task_script = "/tmp/agentflow-agent-install.sh"
|
||||
local installer_url = "https://fw.koolcenter.com/binary/geili/agentflow/releases/installapp/installapp-mise.sh"
|
||||
local agents = {
|
||||
codexcli = true,
|
||||
["claude-code"] = true,
|
||||
opencode = true,
|
||||
kimi = true,
|
||||
reasonix = true
|
||||
}
|
||||
|
||||
if not require_post_csrf() then
|
||||
return
|
||||
end
|
||||
|
||||
local agent = http.formvalue("agent") or ""
|
||||
if not agents[agent] then
|
||||
write_json({ ok = false, error = "unsupported agent" })
|
||||
return
|
||||
end
|
||||
if not fs.access("/etc/init.d/tasks") then
|
||||
write_json({ ok = false, error = "taskd is not available" })
|
||||
return
|
||||
end
|
||||
if not fs.access("/lib/functions/mise.sh") then
|
||||
write_json({ ok = false, error = "mise environment helper is not available" })
|
||||
return
|
||||
end
|
||||
|
||||
local task_status = sys.exec("/etc/init.d/tasks task_status " .. task_id .. " 2>/dev/null")
|
||||
local status = jsonc.parse(task_status) or {}
|
||||
local running = type(status) == "table" and status.running
|
||||
if running == true or running == 1 or running == "1" or running == "true" then
|
||||
write_json({ ok = true, busy = true, task_id = task_id })
|
||||
return
|
||||
end
|
||||
|
||||
local install_script = table.concat({
|
||||
"set -e",
|
||||
"agent=" .. util.shellquote(agent),
|
||||
"installer_url=" .. util.shellquote(installer_url),
|
||||
'installer="/tmp/agentflow-installapp-mise.$$"',
|
||||
'cleanup() { rm -f "$installer" "$0"; }',
|
||||
"trap cleanup 0 HUP INT TERM",
|
||||
'. /lib/functions/mise.sh',
|
||||
'if ! istore_runtime_env; then echo "[agentflow] Failed to initialize the shared runtime environment" >&2; exit 1; fi',
|
||||
"set -u",
|
||||
"export MISE_YES=1",
|
||||
'echo "[agentflow] Downloading installer: $installer_url"',
|
||||
'if command -v wget >/dev/null 2>&1; then',
|
||||
'\twget -O "$installer" "$installer_url"',
|
||||
'elif command -v curl >/dev/null 2>&1; then',
|
||||
'\tcurl -fL -o "$installer" "$installer_url"',
|
||||
'elif command -v uclient-fetch >/dev/null 2>&1; then',
|
||||
'\tuclient-fetch -O "$installer" "$installer_url"',
|
||||
"else",
|
||||
'\techo "[agentflow] No HTTPS download tool is available" >&2',
|
||||
"\texit 1",
|
||||
"fi",
|
||||
'if [ ! -s "$installer" ]; then echo "[agentflow] Failed to download the agent installer" >&2; exit 1; fi',
|
||||
'chmod 0700 "$installer"',
|
||||
'echo "[agentflow] Running installapp-mise.sh for $agent in $HOME"',
|
||||
'/bin/sh "$installer" "$agent"'
|
||||
}, "\n")
|
||||
if not fs.writefile(task_script, install_script .. "\n") then
|
||||
write_json({ ok = false, error = "failed to create install task", task_id = task_id })
|
||||
return
|
||||
end
|
||||
if sys.call("chmod 0600 " .. util.shellquote(task_script)) ~= 0 then
|
||||
fs.unlink(task_script)
|
||||
write_json({ ok = false, error = "failed to secure install task", task_id = task_id })
|
||||
return
|
||||
end
|
||||
|
||||
local command = "/bin/sh " .. util.shellquote(task_script)
|
||||
local rc = sys.call("/etc/init.d/tasks task_add " .. task_id .. " " .. util.shellquote(command) .. " >/dev/null 2>&1")
|
||||
if rc ~= 0 then
|
||||
fs.unlink(task_script)
|
||||
write_json({ ok = false, error = "failed to start install task", task_id = task_id })
|
||||
return
|
||||
end
|
||||
|
||||
write_json({ ok = true, task_id = task_id })
|
||||
end
|
||||
|
||||
function agentflow_open()
|
||||
|
||||
@@ -1,11 +1,80 @@
|
||||
<%+tasks/embed%>
|
||||
|
||||
<style type="text/css">
|
||||
.agentflow-actions { display:flex; gap:10px; flex-wrap:wrap; margin:12px; }
|
||||
.agentflow-actions .btn, .agentflow-agent-dialog-actions .btn { min-height:40px; }
|
||||
.agentflow-agent-modal { position:fixed; inset:0; z-index:10000; display:none; align-items:center; justify-content:center; padding:20px; box-sizing:border-box; background:rgba(15,23,42,.45); }
|
||||
.agentflow-agent-modal.open { display:flex; }
|
||||
.agentflow-agent-dialog, body[theme="light"] .agentflow-agent-dialog { width:min(520px, 100%); padding:20px; box-sizing:border-box; border:1px solid #d0d7de; border-radius:12px; background:#fff; color:inherit; box-shadow:0 18px 48px rgba(15,23,42,.24); }
|
||||
.agentflow-agent-dialog h3 { margin:0 0 8px; text-wrap:balance; }
|
||||
.agentflow-agent-dialog > p, body[theme="light"] .agentflow-agent-dialog > p { margin:0 0 16px; color:#666; text-wrap:pretty; }
|
||||
.agentflow-agent-field label { display:block; margin-bottom:6px; font-weight:600; }
|
||||
.agentflow-agent-field select { width:100%; min-height:40px; box-sizing:border-box; }
|
||||
.agentflow-agent-dialog-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:18px; }
|
||||
body[theme="dark"] .agentflow-agent-dialog { border-color:#465166; background:#1f2937; color:#e5e7eb; }
|
||||
body[theme="dark"] .agentflow-agent-dialog > p { color:#aeb8c7; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body:not([theme]) .agentflow-agent-dialog { border-color:#465166; background:#1f2937; color:#e5e7eb; }
|
||||
body:not([theme]) .agentflow-agent-dialog > p { color:#aeb8c7; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var agentflowOpenUrl = '<%=url("admin/services/agentflow/open")%>';
|
||||
var agentflowAgentInstallUrl = '<%=url("admin/services/agentflow/agent_install")%>';
|
||||
var agentflowCsrfToken = (window.taskd && window.taskd.csrfToken) || '';
|
||||
|
||||
function agentflowAgentDialog(open) {
|
||||
var modal = document.getElementById('agentflow-agent-modal');
|
||||
var select = document.getElementById('agentflow-agent-select');
|
||||
var trigger = document.getElementById('agentflow-agent-open');
|
||||
if (!modal) return;
|
||||
if (open) {
|
||||
modal.classList.add('open');
|
||||
if (select) select.focus();
|
||||
} else {
|
||||
modal.classList.remove('open');
|
||||
if (trigger) trigger.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function agentflowInstallAgent() {
|
||||
var selected = document.getElementById('agentflow-agent-select');
|
||||
var button = document.getElementById('agentflow-agent-install-now');
|
||||
if (!selected || !button) return;
|
||||
if (!window.taskd || !window.taskd.show_log) {
|
||||
alert('<%:Task log window is not available.%>');
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
(new XHR()).post(agentflowAgentInstallUrl, {
|
||||
agent: selected.value,
|
||||
token: agentflowCsrfToken
|
||||
}, function(x, data) {
|
||||
button.disabled = false;
|
||||
if (!data) {
|
||||
try { data = JSON.parse(x.responseText || '{}'); }
|
||||
catch (e) { data = {}; }
|
||||
}
|
||||
if (!data.ok) {
|
||||
alert(data.error || '<%:Failed to start agent installation.%>');
|
||||
return;
|
||||
}
|
||||
agentflowAgentDialog(false);
|
||||
window.taskd.show_log(data.task_id || 'agentflow-agent-install');
|
||||
});
|
||||
}
|
||||
|
||||
XHR.poll(5, '<%=url("admin/services/agentflow_status")%>', null, function(x, st) {
|
||||
var el = document.getElementById('agentflow_status');
|
||||
var openButton = document.getElementById('agentflow-open');
|
||||
if (st && el) {
|
||||
if (!st.running) {
|
||||
if (openButton) openButton.style.display = 'none';
|
||||
el.innerHTML = '<br/><em style="color:red"><%:The AgentFlow service is not running.%></em>';
|
||||
} else {
|
||||
if (openButton) openButton.style.display = '';
|
||||
var accessMode = '';
|
||||
if (st.proxy_prefix_enabled && st.linkeasefull_running) {
|
||||
accessMode = '<br/><em><%:Access mode: LinkEase Desktop /apps proxy.%></em>';
|
||||
@@ -13,11 +82,14 @@ XHR.poll(5, '<%=url("admin/services/agentflow_status")%>', null, function(x, st)
|
||||
accessMode = '<br/><em><%:Access mode: external port.%></em>';
|
||||
}
|
||||
el.innerHTML = '<br/><em style="color:green"><%:The AgentFlow service is running.%></em>'
|
||||
+ accessMode
|
||||
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open AgentFlow%> \" onclick=\"window.open('" + agentflowOpenUrl + "')\"/>";
|
||||
+ accessMode;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(ev) {
|
||||
if (ev.key === 'Escape') agentflowAgentDialog(false);
|
||||
});
|
||||
//]]></script>
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
@@ -25,4 +97,29 @@ XHR.poll(5, '<%=url("admin/services/agentflow_status")%>', null, function(x, st)
|
||||
<p id="agentflow_status">
|
||||
<em><%:Collecting data...%></em>
|
||||
</p>
|
||||
<div class="agentflow-actions">
|
||||
<input id="agentflow-open" class="btn cbi-button cbi-button-apply" type="button" value="<%:Click to open AgentFlow%>" onclick="window.open(agentflowOpenUrl)" style="display:none">
|
||||
<input id="agentflow-agent-open" class="btn cbi-button cbi-button-apply" type="button" value="<%:Install agent%>" onclick="agentflowAgentDialog(true)">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div id="agentflow-agent-modal" class="agentflow-agent-modal" role="dialog" aria-modal="true" aria-labelledby="agentflow-agent-title" onclick="if (event.target === this) agentflowAgentDialog(false)">
|
||||
<div class="agentflow-agent-dialog">
|
||||
<h3 id="agentflow-agent-title"><%:Install agent%></h3>
|
||||
<p><%:Select a coding agent to install into the shared AgentFlow runtime.%></p>
|
||||
<div class="agentflow-agent-field">
|
||||
<label for="agentflow-agent-select"><%:Agent%></label>
|
||||
<select id="agentflow-agent-select" class="cbi-input-select">
|
||||
<option value="codexcli">Codex</option>
|
||||
<option value="claude-code">Claude Code</option>
|
||||
<option value="opencode">OpenCode</option>
|
||||
<option value="kimi">Kimi</option>
|
||||
<option value="reasonix">Reasonix</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="agentflow-agent-dialog-actions">
|
||||
<input class="btn cbi-button" type="button" value="<%:Cancel%>" onclick="agentflowAgentDialog(false)">
|
||||
<input id="agentflow-agent-install-now" class="btn cbi-button cbi-button-apply" type="button" value="<%:Install now%>" onclick="agentflowInstallAgent()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,3 +51,24 @@ msgstr "AgentFlow 服务未运行。"
|
||||
|
||||
msgid "The AgentFlow service is running."
|
||||
msgstr "AgentFlow 服务运行中。"
|
||||
|
||||
msgid "Install agent"
|
||||
msgstr "安装智能体"
|
||||
|
||||
msgid "Agent"
|
||||
msgstr "智能体"
|
||||
|
||||
msgid "Select a coding agent to install into the shared AgentFlow runtime."
|
||||
msgstr "选择要安装到 AgentFlow 共享运行环境中的编码智能体。"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
msgid "Install now"
|
||||
msgstr "立即安装"
|
||||
|
||||
msgid "Task log window is not available."
|
||||
msgstr "任务日志窗口不可用。"
|
||||
|
||||
msgid "Failed to start agent installation."
|
||||
msgstr "启动智能体安装失败。"
|
||||
@@ -0,0 +1,69 @@
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
APP_DIR = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class AgentInstallContractTest(unittest.TestCase):
|
||||
def read(self, relative):
|
||||
return (APP_DIR / relative).read_text(encoding="utf-8")
|
||||
|
||||
def test_luci_loads_task_window_and_starts_whitelisted_installer(self):
|
||||
makefile = self.read("Makefile")
|
||||
controller = self.read("luasrc/controller/agentflow.lua")
|
||||
status = self.read("luasrc/view/agentflow/status.htm")
|
||||
|
||||
self.assertIn("+luci-lib-taskd", makefile)
|
||||
self.assertIn('<%+tasks/embed%>', status)
|
||||
self.assertIn('<select id="agentflow-agent-select"', status)
|
||||
for agent in ("codexcli", "claude-code", "opencode", "kimi", "reasonix"):
|
||||
self.assertIn(f'<option value="{agent}">', status)
|
||||
self.assertNotIn('type="radio"', status)
|
||||
self.assertIn('id="agentflow-open"', status)
|
||||
self.assertIn('id="agentflow-agent-open"', status)
|
||||
self.assertGreater(status.index('id="agentflow-agent-open"'), status.index('id="agentflow_status"'))
|
||||
self.assertIn('margin:12px;', status)
|
||||
self.assertIn("var selected = document.getElementById('agentflow-agent-select')", status)
|
||||
self.assertIn("window.taskd.show_log(data.task_id", status)
|
||||
self.assertNotIn('local token =', status)
|
||||
self.assertIn("window.taskd.csrfToken", status)
|
||||
self.assertIn("token: agentflowCsrfToken", status)
|
||||
self.assertIn('http.formvalue("agent")', controller)
|
||||
self.assertIn('codexcli = true', controller)
|
||||
self.assertIn('["claude-code"] = true', controller)
|
||||
self.assertIn('opencode = true', controller)
|
||||
self.assertIn('kimi = true', controller)
|
||||
self.assertIn('reasonix = true', controller)
|
||||
self.assertIn('context.authtoken or context.token', controller)
|
||||
self.assertIn('http.formvalue("token") ~= expected', controller)
|
||||
self.assertIn('/etc/init.d/tasks task_add ', controller)
|
||||
self.assertIn('task_id = "agentflow-agent-install"', controller)
|
||||
|
||||
def test_modal_theme_prefers_body_attribute_then_system_theme(self):
|
||||
status = self.read("luasrc/view/agentflow/status.htm")
|
||||
|
||||
self.assertIn('body[theme="light"] .agentflow-agent-dialog', status)
|
||||
self.assertIn('body[theme="dark"] .agentflow-agent-dialog', status)
|
||||
self.assertIn('@media (prefers-color-scheme: dark)', status)
|
||||
self.assertIn('body:not([theme]) .agentflow-agent-dialog', status)
|
||||
|
||||
def test_controller_runs_remote_installer_inside_taskd(self):
|
||||
controller = self.read("luasrc/controller/agentflow.lua")
|
||||
|
||||
self.assertFalse((APP_DIR / "root/usr/libexec/istorec/agentflow-agent.sh").exists())
|
||||
self.assertNotIn("/usr/libexec/istorec/agentflow-agent.sh", controller)
|
||||
self.assertIn("installapp/installapp-mise.sh", controller)
|
||||
self.assertIn("istore_runtime_env", controller)
|
||||
self.assertIn('wget -O "$installer" "$installer_url"', controller)
|
||||
self.assertIn('/bin/sh "$installer" "$agent"', controller)
|
||||
self.assertIn('task_script = "/tmp/agentflow-agent-install.sh"', controller)
|
||||
self.assertIn("fs.writefile(task_script", controller)
|
||||
self.assertIn('rm -f "$installer" "$0"', controller)
|
||||
self.assertIn('local command = "/bin/sh "', controller)
|
||||
self.assertNotIn('local command = "/bin/sh -c "', controller)
|
||||
self.assertLess(controller.index("istore_runtime_env"), controller.index('"set -u"'))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -316,6 +316,15 @@ function run_ttyd(request) {
|
||||
return { error: 'Container ID is required' };
|
||||
}
|
||||
|
||||
// Strictly validate all user-supplied values before they are interpolated
|
||||
// into a shell command string to prevent command injection.
|
||||
if (!match(id, /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/) ||
|
||||
!match(`${port}`, /^[0-9]{1,5}$/) || int(port) < 1 || int(port) > 65535 ||
|
||||
(uid && !match(`${uid}`, /^[a-zA-Z0-9_-]+$/)) ||
|
||||
!match(cmd, /^[a-zA-Z0-9_\/.\- ]+$/)) {
|
||||
return { error: 'Invalid parameter supplied' };
|
||||
}
|
||||
|
||||
let ttyd_cmd = `ttyd -q -d 2 --once --writable -p ${port} docker`;
|
||||
const sock_addr = ds.get_socket_dest();
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Firewall Live View
|
||||
LUCI_DESCRIPTION:=Live firewall log table (nftables/fw4 and iptables LOG) with filters
|
||||
LUCI_DESCRIPTION:=Live firewall log table (firewall4/nft; iptables-tagged lines still classify) with filters
|
||||
LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.44
|
||||
PKG_RELEASE:=27
|
||||
PKG_RELEASE:=28
|
||||
|
||||
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
||||
ifdef SOURCE_DATE_EPOCH
|
||||
|
||||
@@ -599,9 +599,10 @@ collect_logging_warnings() {
|
||||
command -v timeout >/dev/null 2>&1 || logging_warnings_append 'timeout_missing'
|
||||
|
||||
# Diagnostic only: supported releases use nftables, but a registered legacy
|
||||
# iptables table can still exist in the network namespace visible to rpcd.
|
||||
# Keep this ubus-only for now; it must not affect readiness or the enable
|
||||
# gate, and the UI has no remediation flow for legacy tables yet.
|
||||
# iptables table can still exist in the namespace visible to rpcd. LuCI
|
||||
# surfaces this warning in the backend label; it must not affect readiness
|
||||
# or the enable gate. A registered table can come from a loaded module even
|
||||
# when it has no rules, so this is inventory evidence, not proof of traffic.
|
||||
legacy_iptables_active && logging_warnings_append 'legacy_iptables_detected'
|
||||
|
||||
# Report via LOGGING_WARNINGS, not exit status.
|
||||
|
||||
@@ -12,7 +12,7 @@ LUCI_PKGARCH:=all
|
||||
|
||||
PKG_NAME:=luci-app-ssrserver-python
|
||||
PKG_VERSION:=3.2.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=3
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@@ -25,9 +25,12 @@ from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
_VALID_IP_RE = re.compile(r'^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$')
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='See README')
|
||||
parser.add_argument('-c', '--count', default=3, type=int,
|
||||
@@ -45,7 +48,7 @@ if __name__ == '__main__':
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
ips[ip] += 1
|
||||
if ip not in banned and ips[ip] >= config.count:
|
||||
if ip not in banned and ips[ip] >= config.count and _VALID_IP_RE.match(ip):
|
||||
banned.add(ip)
|
||||
cmd = 'iptables -A INPUT -s %s -j DROP' % ip
|
||||
print(cmd, file=sys.stderr)
|
||||
|
||||
@@ -71,6 +71,7 @@ function getRadioBand(devName) {
|
||||
return view.extend({
|
||||
initialValues: {},
|
||||
initialShortcuts: [],
|
||||
originalLanIp: null, // 持久快照,防止被 handleSave 覆盖
|
||||
hasWireless: false,
|
||||
hasNginx: false,
|
||||
map: null,
|
||||
@@ -96,7 +97,17 @@ return view.extend({
|
||||
|
||||
// 2. 统一聚合读取系统当前各子模块的实时配置
|
||||
getSystemConfig: function() {
|
||||
var isSideRouter = !!uci.get('network', 'lan', 'gateway') && uci.get('network', 'wan', 'auto') === '0';
|
||||
var lanGw = getFirstIp(uci.get('network', 'lan', 'gateway'), '');
|
||||
var wanSec = uci.get('network', 'wan');
|
||||
|
||||
// 判定 WAN 接口是否不存在或处于停用状态
|
||||
var isWanDisabled = !wanSec ||
|
||||
uci.get('network', 'wan', 'auto') === '0' ||
|
||||
uci.get('network', 'wan', 'disabled') === '1' ||
|
||||
uci.get('network', 'wan', 'proto') === 'none';
|
||||
|
||||
// 核心判定:配置了局域网网关,且 WAN 口被停用或不存在,即确认处于旁路由模式
|
||||
var isSideRouter = !!lanGw && isWanDisabled;
|
||||
|
||||
var ap = null;
|
||||
if (this.hasWireless) {
|
||||
@@ -118,7 +129,7 @@ return view.extend({
|
||||
wan_pppoe_user: uci.get('network', 'wan', 'username') || '',
|
||||
wan_pppoe_pass: uci.get('network', 'wan', 'password') || '',
|
||||
lan_ipaddr: getFirstIp(uci.get('network', 'lan', 'ipaddr'), '10.0.0.1'),
|
||||
lan_gateway: getFirstIp(uci.get('network', 'lan', 'gateway'), ''),
|
||||
lan_gateway: lanGw,
|
||||
lan_dns: toArray(uci.get('network', 'lan', 'dns')),
|
||||
dhcp: uci.get('dhcp', 'lan', 'ignore') === '1' ? '0' : '1',
|
||||
ipv6: uci.get('network', 'wan6', 'auto') === '0' ? '0' : '1',
|
||||
@@ -138,6 +149,7 @@ return view.extend({
|
||||
|
||||
var sys = this.getSystemConfig();
|
||||
this.initialValues = Object.assign({}, sys);
|
||||
this.originalLanIp = sys.lan_ipaddr; // 记录不可变初始 IP
|
||||
|
||||
// 缓存初始 shortcuts 配置快照,用于保存时比对差异
|
||||
var shortcuts = uci.sections('wizard', 'shortcuts') || [];
|
||||
@@ -403,16 +415,34 @@ return view.extend({
|
||||
}
|
||||
}
|
||||
|
||||
// D. 自定义 LAN DNS
|
||||
if (has(changed, 'lan_dns')) {
|
||||
if (cur.lan_dns && cur.lan_dns.length > 0) {
|
||||
uci.set('network', 'lan', 'dns', cur.lan_dns);
|
||||
// D. 自定义 LAN DNS(支持旁路由模式自动回落至网关或公网兜底 DNS)
|
||||
if (has(changed, 'lan_dns') || has(changed, 'wan_proto') || has(changed, 'lan_gateway')) {
|
||||
if (cur.wan_proto === 'siderouter') {
|
||||
var effectiveDns = toArray(cur.lan_dns);
|
||||
|
||||
// 若 DNS 列表为空,按优先级进行智能兜底
|
||||
if (effectiveDns.length === 0) {
|
||||
var cleanGw = getFirstIp(cur.lan_gateway, '');
|
||||
if (cleanGw) {
|
||||
// 优先级 1:回落至主路由网关 IP
|
||||
effectiveDns.push(cleanGw);
|
||||
} else {
|
||||
// 优先级 2:若网关也未填写,使用常用可靠公网 DNS 兜底
|
||||
effectiveDns.push('223.5.5.5', '119.29.29.29');
|
||||
}
|
||||
}
|
||||
uci.set('network', 'lan', 'dns', effectiveDns);
|
||||
} else {
|
||||
safeUnset('network', 'lan', 'dns');
|
||||
// 常规主路由模式:用户未配则清除,让系统通过 WAN 动态获取
|
||||
if (cur.lan_dns && cur.lan_dns.length > 0) {
|
||||
uci.set('network', 'lan', 'dns', cur.lan_dns);
|
||||
} else {
|
||||
safeUnset('network', 'lan', 'dns');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// E. LAN 网关(独立变更或切换至旁路由模式时更新)
|
||||
// E. LAN 网关
|
||||
if (has(changed, 'lan_gateway') || has(changed, 'wan_proto')) {
|
||||
if (cur.wan_proto === 'siderouter') {
|
||||
if (cur.lan_gateway) {
|
||||
@@ -475,32 +505,59 @@ return view.extend({
|
||||
});
|
||||
},
|
||||
|
||||
// 5. 重写 View 级别的 handleSaveApply:应用生效并实现 LAN IP 变更平滑迁移与自动跳转
|
||||
// 5. 重写 View 级别的 handleSaveApply:前置检测 LAN IP 变动,强制非回滚提交并精准弹出倒计时
|
||||
handleSaveApply: function(ev, mode) {
|
||||
var self = this;
|
||||
|
||||
// 关键修复:在 handleSave 覆盖 initialValues 之前,立即计算并锁定是否修改了 LAN IP
|
||||
var oldIp = (self.originalLanIp || self.initialValues.lan_ipaddr || '').split('/')[0].trim();
|
||||
var newIp = (self.optMap['lan_ipaddr'] ? self.optMap['lan_ipaddr'].formvalue('default') : '') || '';
|
||||
newIp = newIp.split('/')[0].trim();
|
||||
var ipChanged = !!(newIp && oldIp && (newIp !== oldIp));
|
||||
|
||||
return this.handleSave(ev).then(function(hasChanges) {
|
||||
if (!hasChanges) return false;
|
||||
|
||||
var oldIp = self.initialValues.lan_ipaddr;
|
||||
var newIp = (self.optMap['lan_ipaddr'].formvalue('default') || '').split('/')[0].trim();
|
||||
var ipChanged = newIp && (newIp !== oldIp);
|
||||
if (ipChanged) {
|
||||
var sec = 15;
|
||||
var targetUrl = window.location.protocol + '//' + newIp + (window.location.pathname || '/cgi-bin/luci/');
|
||||
var countSpan = E('strong', {}, String(sec));
|
||||
|
||||
return ui.changes.apply(mode == '0').then(function() {
|
||||
if (ipChanged) {
|
||||
var sec = 15;
|
||||
ui.showModal(_('LAN IP Address Changed'), [
|
||||
E('p', _('LAN IP changed to %s. Redirecting in %d seconds...').format(newIp, sec)),
|
||||
E('div', { 'class': 'spinning', 'style': 'margin: 1em auto;' })
|
||||
]);
|
||||
var timer = window.setInterval(function() {
|
||||
sec--;
|
||||
if (sec <= 0) {
|
||||
window.clearInterval(timer);
|
||||
window.location.href = window.location.protocol + '//' + newIp + window.location.pathname;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
// 1. 核心操作:调用 apply_unchecked,进行强制永久提交,彻底绕过 90 秒回滚保护机制!
|
||||
L.post(L.url('admin', 'uci', 'apply_unchecked')).catch(function() {});
|
||||
|
||||
// 2. 立即在前端呼出弹窗遮罩层
|
||||
ui.showModal(_('LAN IP Address Changed'), [
|
||||
E('p', {}, [_('LAN IP has been changed to '), E('strong', {}, newIp), '.']),
|
||||
E('p', {}, [_('Applying changes without rollback. Redirecting to the new address in '), countSpan, _(' seconds...')]),
|
||||
E('p', { 'class': 'alert-message notice', 'style': 'margin-top: 1em;' },
|
||||
_('提示:更改网段后,若未能自动加载新页面,请尝试重新插拔网线或断开重连 Wi-Fi,以获取新网段的 IP 地址。')),
|
||||
E('div', { 'class': 'spinning', 'style': 'margin: 1.5em auto;' }),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
}, _('Redirect Now'))
|
||||
])
|
||||
]);
|
||||
|
||||
// 3. 启动倒计时并自动跳转
|
||||
var timer = window.setInterval(function() {
|
||||
sec--;
|
||||
countSpan.textContent = String(sec);
|
||||
if (sec <= 0) {
|
||||
window.clearInterval(timer);
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// 未改动 LAN IP 的常规操作,保留原生的回滚自愈保护
|
||||
return ui.changes.apply(mode == '0');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -4,7 +4,25 @@ local uci = luci.model.uci.cursor()
|
||||
local http = require "luci.http"
|
||||
|
||||
function index()
|
||||
entry({"admin", "index"}, call("landing_page"), _("Home") , 1).dependent = false
|
||||
entry({"admin", "index"}, call("landing_page"), _("Home") , 0).dependent = false
|
||||
|
||||
-- 2. 核心:直接改写 admin 根节点的调度目标
|
||||
-- 这样当用户访问 /admin、登录后跳转或访问根目录时,无需经过 firstchild(),直接执行 landing_page()
|
||||
local admin = node("admin")
|
||||
if admin then
|
||||
admin.target = call("landing_page")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_wifi()
|
||||
local has_wifi = false
|
||||
pcall(function()
|
||||
uci:foreach("wireless", "wifi-device", function(s)
|
||||
has_wifi = true
|
||||
return false -- 检测到任意 wifi-device 即终止遍历
|
||||
end)
|
||||
end)
|
||||
return has_wifi
|
||||
end
|
||||
|
||||
function landing_page()
|
||||
@@ -22,7 +40,13 @@ function landing_page()
|
||||
http.redirect(luci.dispatcher.build_url("admin","quickstart"));
|
||||
end
|
||||
else
|
||||
http.redirect(luci.dispatcher.build_url("admin","status", "overview"))
|
||||
end
|
||||
if check_wifi() then
|
||||
-- 具备无线功能,跳转至常用无线仪表盘界面
|
||||
http.redirect(luci.dispatcher.build_url("admin", "dashboard"))
|
||||
else
|
||||
-- 无无线设备(纯有线环境),跳转至原生状态概览
|
||||
http.redirect(luci.dispatcher.build_url("admin", "status", "overview"))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,92 +1,109 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
||||
msgstr ""
|
||||
"Project-Id-Version: luci-app-wizard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-20 20:00+0800\n"
|
||||
"PO-Revision-Date: 2026-09-20 20:00+0800\n"
|
||||
"Last-Translator: Eric\n"
|
||||
"Language-Team: Chinese (Simplified) <zh_Hans>\n"
|
||||
"Language: zh_Hans\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
msgid "Home"
|
||||
msgstr "首页"
|
||||
|
||||
msgid "Setup Wizard"
|
||||
msgstr "设置向导"
|
||||
msgstr "快速设置向导"
|
||||
|
||||
msgid "Quickly configure common network, wireless, and system settings."
|
||||
msgstr "快速配置常用网络、无线与系统设置。"
|
||||
msgstr "快速配置常用网络、无线与系统运行设置。"
|
||||
|
||||
msgid "Net Settings"
|
||||
msgstr "网络设置"
|
||||
|
||||
msgid "Firmware Settings"
|
||||
msgstr "固件与系统"
|
||||
msgstr "固件设置"
|
||||
|
||||
msgid "Wireless Settings"
|
||||
msgstr "无线设置"
|
||||
|
||||
msgid "Shortcuts"
|
||||
msgstr "快捷方式"
|
||||
msgstr "域名快捷跳转"
|
||||
|
||||
msgid "比如设置google.com的快捷方式为字母g,则在此路由器网络的任何浏览器中输入g/即可访问google.com"
|
||||
msgstr "比如设置 google.com 的快捷方式为字母 g,则在此路由器网络的任何浏览器中输入 g/ 即可访问 google.com"
|
||||
|
||||
msgid "WAN Protocol / Mode"
|
||||
msgstr "WAN 协议 / 工作模式"
|
||||
|
||||
msgid "DHCP Client (Default Router)"
|
||||
msgstr "DHCP 客户端 (标准主路由模式)"
|
||||
msgstr "DHCP 客户端(标准主路由模式)"
|
||||
|
||||
msgid "PPPoE Dial-up"
|
||||
msgstr "PPPoE 拨号上网"
|
||||
msgstr "PPPoE 拨号"
|
||||
|
||||
msgid "Side-Router Mode (Bypass Gateway)"
|
||||
msgstr "旁路由模式 (网关旁路)"
|
||||
msgstr "旁路由模式(网关旁路)"
|
||||
|
||||
msgid "PPPoE Username"
|
||||
msgstr "宽带拨号账号"
|
||||
msgstr "PPPoE 用户名"
|
||||
|
||||
msgid "PPPoE Password"
|
||||
msgstr "宽带拨号密码"
|
||||
msgstr "PPPoE 密码"
|
||||
|
||||
msgid "LAN IPv4 Address"
|
||||
msgstr "LAN 口 IPv4 地址"
|
||||
msgstr "LAN IPv4 地址"
|
||||
|
||||
msgid "Custom DNS Server(s)"
|
||||
msgstr "自定义 DNS 服务器,留空默认运营商DNS"
|
||||
msgstr "自定义 DNS 服务器"
|
||||
|
||||
msgid "Gateway Address"
|
||||
msgstr "网关地址"
|
||||
|
||||
msgid "Primary router IP address used when operating in side-router mode."
|
||||
msgstr "处于旁路由模式时的主路由 IP 地址。"
|
||||
msgstr "在旁路由模式下工作时填写的上级主路由 IP 地址。"
|
||||
|
||||
msgid "Enable DHCP Server"
|
||||
msgstr "启用 DHCP 服务"
|
||||
msgstr "启用 DHCP 服务器"
|
||||
|
||||
msgid "Usually disabled in side-router mode to avoid IP address assignment conflicts with the main router."
|
||||
msgstr "在旁路由模式下通常关闭此项,避免与主路由的 IP 分配冲突。"
|
||||
msgid "Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts)."
|
||||
msgstr "开启或关闭局域网 DHCP 服务(旁路由模式下通常建议关闭,避免局域网 IP 分配冲突)。"
|
||||
|
||||
msgid "IPv6 Support"
|
||||
msgstr "IPv6 支持"
|
||||
|
||||
msgid "Enable or disable IPv6 router advertisements and DHCPv6."
|
||||
msgstr "启用或关闭 IPv6 路由通告与 DHCPv6 服务。"
|
||||
msgstr "开启或关闭 IPv6 路由通告 (RA) 与 DHCPv6 服务。"
|
||||
|
||||
msgid "Firmware Upgrade Notice"
|
||||
msgstr "固件升级提示"
|
||||
msgstr "固件更新检测提示"
|
||||
|
||||
msgid "Check and display notices for newer firmware versions."
|
||||
msgstr "检查并显示新版本固件更新提示。"
|
||||
msgstr "开机或访问时检测并提示可用的新版本固件。"
|
||||
|
||||
msgid "Run CoreMark on Boot"
|
||||
msgstr "开机 CoreMark 跑分"
|
||||
msgstr "开机自动运行 CoreMark 跑分"
|
||||
|
||||
msgid "Run CPU benchmark asynchronously upon router initialization."
|
||||
msgstr "在路由器初始化启动时运行 CPU 基准测试。"
|
||||
msgstr "路由器启动完成后在后台异步运行 CPU 性能基准测试。"
|
||||
|
||||
msgid "Persistent Cookie Session"
|
||||
msgstr "持久 Cookie 登录"
|
||||
msgstr "保持登录会话 (Persistent Cookie)"
|
||||
|
||||
msgid "Maintain persistent login sessions in the web browser."
|
||||
msgstr "在浏览器中保持登录会话状态,避免频繁重复登录。"
|
||||
msgstr "关闭浏览器后仍保持管理后台登录状态。"
|
||||
|
||||
msgid "Enforce HTTPS Access"
|
||||
msgstr "强制 HTTPS 访问"
|
||||
|
||||
msgid "Automatically redirect HTTP requests to secure HTTPS."
|
||||
msgstr "将所有 HTTP 请求自动重定向至安全加密的 HTTPS。"
|
||||
msgstr "自动将普通 HTTP 请求重定向到加密的 HTTPS 连接。"
|
||||
|
||||
msgid "Landing Dashboard Mode"
|
||||
msgstr "主页展示模式"
|
||||
msgstr "管理首页展示模式"
|
||||
|
||||
msgid "Default"
|
||||
msgstr "默认"
|
||||
@@ -95,46 +112,67 @@ msgid "RouterDog"
|
||||
msgstr "路由狗"
|
||||
|
||||
msgid "NAS"
|
||||
msgstr "NAS 模式"
|
||||
msgstr "NAS"
|
||||
|
||||
msgid "Next-NAS"
|
||||
msgstr "Next-NAS 模式"
|
||||
msgstr "Next-NAS"
|
||||
|
||||
msgid "Router"
|
||||
msgstr "路由器模式"
|
||||
msgstr "Router"
|
||||
|
||||
msgid "Wireless Network Name (SSID)"
|
||||
msgstr "无线网络名称 (SSID)"
|
||||
|
||||
msgid "无线名称基准前缀,系统将自动识别并为 2.4G 追加 _2.4G、5G 追加 _5G、6G 追加 _6G。"
|
||||
msgstr "无线名称基准前缀,系统将自动识别并为 2.4G 追加 _2.4G、5G 追加 _5G、6G 追加 _6G。"
|
||||
|
||||
msgid "Wireless Password (Key)"
|
||||
msgstr "无线网络密码"
|
||||
msgstr "无线密码"
|
||||
|
||||
msgid "Leave empty for open network or 8+ characters"
|
||||
msgstr "留空为开放无密码网络,或输入 8 位以上密码"
|
||||
msgstr "留空表示无密码开放网络,或输入至少 8 位密码"
|
||||
|
||||
msgid "URL Shortcuts"
|
||||
msgstr "快捷方式列表"
|
||||
msgid "Wireless key must be between 8 and 64 characters long"
|
||||
msgstr "无线密码长度必须在 8 到 64 个字符之间"
|
||||
|
||||
msgid "Applying Changes"
|
||||
msgstr "正在应用修改"
|
||||
msgid "Shortcut"
|
||||
msgstr "快捷短语"
|
||||
|
||||
msgid "Applying modified settings: %s..."
|
||||
msgstr "正在应用已修改的设置项: %s..."
|
||||
msgid "Shortcut phrase cannot be empty"
|
||||
msgstr "快捷短语不能为空"
|
||||
|
||||
msgid "No settings were modified. Services remain uninterrupted."
|
||||
msgstr "未检测到任何修改,无需保存或重启服务。"
|
||||
msgid "Only alphanumeric characters, dashes and underscores are allowed"
|
||||
msgstr "仅允许输入字母、数字、破折号 (-) 和下划线 (_)"
|
||||
|
||||
msgid "Settings successfully applied: %s"
|
||||
msgstr "已成功应用并重载修改项: %s"
|
||||
msgid "Target URL"
|
||||
msgstr "目标网址 (URL)"
|
||||
|
||||
msgid "Failed to apply settings: %s"
|
||||
msgstr "应用设置失败: %s"
|
||||
msgid "Please enter a valid URL starting with http:// or https://"
|
||||
msgstr "请输入以 http:// 或 https:// 开头的有效网址"
|
||||
|
||||
msgid "Comments"
|
||||
msgstr "备注说明"
|
||||
|
||||
msgid "Optional"
|
||||
msgstr "可选"
|
||||
|
||||
msgid "There are no changes to apply"
|
||||
msgstr "没有需要应用的更改"
|
||||
|
||||
msgid "LAN IP Address Changed"
|
||||
msgstr "LAN 口 IP 地址已变更"
|
||||
|
||||
msgid "LAN IP changed to %s. Redirecting to the new address in %d seconds..."
|
||||
msgstr "LAN 口 IP 已变更为 %s。将在 %d 秒后自动跳转至新地址..."
|
||||
msgid "LAN IP has been changed to "
|
||||
msgstr "LAN 口 IP 地址已更改为 "
|
||||
|
||||
msgid "Applying changes without rollback. Redirecting to the new address in "
|
||||
msgstr "正在永久应用更改(已禁用回滚保护)。将在 "
|
||||
|
||||
msgid " seconds..."
|
||||
msgstr " 秒后自动跳转至新地址..."
|
||||
|
||||
msgid "提示:更改网段后,若未能自动加载新页面,请尝试重新插拔网线或断开重连 Wi-Fi,以获取新网段的 IP 地址。"
|
||||
msgstr "提示:更改网段后,若未能自动加载新页面,请尝试重新插拔网线或断开重连 Wi-Fi,以获取新网段的 IP 地址。"
|
||||
|
||||
msgid "Redirect Now"
|
||||
msgstr "立即跳转"
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"admin/dashboard": {
|
||||
"title": "Dashboard",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "view",
|
||||
|
||||
@@ -10,7 +10,7 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=my-default-settings
|
||||
PKG_VERSION:=2
|
||||
PKG_RELEASE:=11
|
||||
PKG_RELEASE:=16
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
|
||||
@@ -1,246 +1,149 @@
|
||||
#!/bin/sh
|
||||
|
||||
sed -i "s/op.supes.top/dl.openwrt.ai/" /etc/opkg/distfeeds.conf
|
||||
|
||||
[ -x /bin/bash ] && grep -q "/bin/ash" /etc/passwd && {
|
||||
sed -i 's|/bin/ash|/bin/bash|g' "/etc/passwd"
|
||||
ln -sf /bin/bash /bin/sh
|
||||
touch /root/.bash_history
|
||||
ln -sf /root/.bash_history /tmp/.busybox_ash_history
|
||||
}
|
||||
# 1. 基础系统与组件链接修正
|
||||
sed -i 's|op.supes.top|dl.openwrt.ai|g' /etc/opkg/distfeeds.conf 2>/dev/null
|
||||
[ -x /bin/bash ] && sed -i 's|/bin/ash|/bin/bash|g' /etc/passwd 2>/dev/null && ln -sf /bin/bash /bin/sh 2>/dev/null
|
||||
[ -x /usr/bin/wget ] && [ ! -x /usr/bin/wget-ssl ] && ln -sf /usr/bin/wget /usr/bin/wget-ssl
|
||||
sed -i -e '/profile.d/d' /lib/upgrade/keep.d/base-files \
|
||||
-e 's/git-.*-\(.*\)/git-\1/g' /usr/lib/lua/luci/version.lua 2>/dev/null
|
||||
|
||||
# 2. 版本与环境参数提取
|
||||
version=$(uci -q get base_config.@status[0].version)
|
||||
case "$version" in ''|*[!0-9]*) version=0 ;; esac
|
||||
|
||||
hostname=$(uci -q get system.@system[0].hostname)
|
||||
[ -n "${hostname}" ] || hostname="Kwrt"
|
||||
for ip in $(uci -q get network.lan.ipaddr); do
|
||||
case "$ip" in *.*) lan_ip="${ip%%/*}"; break ;; esac
|
||||
done
|
||||
lan_ip="${lan_ip:-10.0.0.1}"
|
||||
|
||||
if [ "$(uci -q get dhcp.@dnsmasq[0].port)" != "53" ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].port='53'
|
||||
uci commit dhcp
|
||||
fi
|
||||
[ -z "$(uci -q get system.@system[0].hostname)" ] && uci -q set system.@system[0].hostname='Kwrt'
|
||||
[ "$(uci -q get dropbear.main._direct)" != "1" ] && uci -q delete dropbear.main.Interface
|
||||
case "$(uci -q get uhttpd.main.index_page)" in *cgi-bin/luci*) ;; *) uci -q add_list uhttpd.main.index_page='cgi-bin/luci' ;; esac
|
||||
|
||||
sed -i '/profile.d/d' /lib/upgrade/keep.d/base-files
|
||||
proc=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)
|
||||
[ "${proc:-0}" -gt 0 ] 2>/dev/null || proc=3
|
||||
sed -i "s/^threads =.*/threads = $proc/" /etc/uwsgi/vassals/luci-webui.ini 2>/dev/null
|
||||
|
||||
if ! echo "$(uci -q get uhttpd.main.index_page)" | grep -q "cgi-bin/luci"; then
|
||||
uci -q add_list uhttpd.main.index_page='cgi-bin/luci'
|
||||
uci commit uhttpd
|
||||
fi
|
||||
# 3. 内存分级计算
|
||||
mem=0
|
||||
[ -r /proc/meminfo ] && read -r _ mem _ < /proc/meminfo
|
||||
case "$mem" in ''|*[!0-9]*) mem=0 ;; esac
|
||||
|
||||
if [ "$(uci -q get dropbear.main._direct)" != "1" ] && [ -n "$(uci -q get dropbear.main.Interface)" ]; then
|
||||
uci del dropbear.main.Interface
|
||||
uci commit dropbear
|
||||
fi
|
||||
c=1024; nf=16384
|
||||
[ "$mem" -ge 65536 ] && { c=2048; nf=32768; }
|
||||
[ "$mem" -ge 131072 ] && { c=4096; nf=65536; }
|
||||
[ "$mem" -ge 262144 ] && { c=8192; nf=65536; }
|
||||
[ "$mem" -ge 524288 ] && { c=10000; nf=131072; }
|
||||
[ "$mem" -ge 1048576 ] && { c=10000; nf=262144; }
|
||||
|
||||
[ -x /usr/bin/wget ] && [ ! -x /usr/bin/wget-ssl ] && \
|
||||
ln -s /usr/bin/wget /usr/bin/wget-ssl
|
||||
# 4. 首次开机参数统一批量预设
|
||||
if [ "$version" -lt 1 ]; then
|
||||
uci -q batch <<-EOF
|
||||
set luci.main.lang='auto'
|
||||
set uhttpd.main.rfc1918_filter='0'
|
||||
set uhttpd.main.redirect_https='0'
|
||||
set uhttpd.main.script_timeout='120'
|
||||
set upnpd.config.enabled='1'
|
||||
set firewall.@defaults[0].flow_offloading='1'
|
||||
set firewall.@defaults[0].flow_offloading_hw='1'
|
||||
set fstab.@global[0].anon_mount='1'
|
||||
set network.globals.packet_steering='1'
|
||||
set nft-qos.default.limit_enable='0'
|
||||
set system.@system[0].zonename='Asia/Shanghai'
|
||||
set system.@system[0].timezone='CST-8'
|
||||
set system.@system[0].ttylogin='1'
|
||||
set system.@system[0].zram_size_mb='$((mem / 1024 / 3))'
|
||||
del system.ntp.server
|
||||
add_list system.ntp.server='ntp.aliyun.com'
|
||||
add_list system.ntp.server='0.openwrt.pool.ntp.org'
|
||||
set dhcp.@dnsmasq[0].port='53'
|
||||
set dhcp.@dnsmasq[0].dns_redirect='1'
|
||||
set dhcp.@dnsmasq[0].allservers='1'
|
||||
del dhcp.@dnsmasq[0].noresolv
|
||||
set dhcp.@dnsmasq[0].cachesize='$c'
|
||||
set dhcp.@dnsmasq[0].dnsforwardmax='$c'
|
||||
set dhcp.@dnsmasq[0].min_ttl='3600'
|
||||
set dhcp.lan.force='1'
|
||||
set dhcp._ta=domain
|
||||
set dhcp._ta.name='time.android.com'
|
||||
set dhcp._ta.ip='203.107.6.88'
|
||||
set dhcp._ta.comments='time.android'
|
||||
EOF
|
||||
|
||||
sed -i "s/git-.*-\(.*\)/git-\1/g" /usr/lib/lua/luci/version.lua
|
||||
(echo root; echo root) | passwd root >/dev/null 2>&1
|
||||
[ -f /usr/share/rpcd/acl.d/luci-theme-argon.json ] && uci -q set luci.main.mediaurlbase='/luci-static/argon'
|
||||
grep -q 'log-facility' /etc/dnsmasq.conf 2>/dev/null || echo "log-facility=/dev/null" >> /etc/dnsmasq.conf
|
||||
|
||||
if [ -f /etc/uwsgi/vassals/luci-webui.ini ]; then
|
||||
processor=$(grep -c 'processor' /proc/cpuinfo)
|
||||
[ -n "$processor" ] && [ "$processor" -gt 0 ] || processor=3
|
||||
sed -i "/^threads =/c\threads = $processor" /etc/uwsgi/vassals/luci-webui.ini
|
||||
fi
|
||||
|
||||
if [ -f /etc/config/attendedsysupgrade ]; then
|
||||
uci -q set attendedsysupgrade.server.url='https://openwrt.ai'
|
||||
uci commit attendedsysupgrade
|
||||
fi
|
||||
|
||||
if [ -z "$version" ] || [ "$version" -lt 1 ]; then
|
||||
uci -q set luci.main.lang='auto'
|
||||
if [ -f /usr/share/rpcd/acl.d/luci-theme-footstrap.json ]; then
|
||||
uci -q set luci.main.mediaurlbase=/luci-static/footstrap
|
||||
fi
|
||||
uci commit luci
|
||||
|
||||
if uci -q get system.@system[0] >/dev/null; then
|
||||
uci -q set system.@system[0].zonename='Asia/Shanghai'
|
||||
uci -q set system.@system[0].timezone='CST-8'
|
||||
uci -q set system.@system[0].conloglevel='4'
|
||||
uci -q set system.@system[0].cronloglevel='8'
|
||||
uci -q set system.@system[0].ttylogin='1'
|
||||
uci -q del system.ntp.server
|
||||
uci -q add_list system.ntp.server='ntp.aliyun.com'
|
||||
uci -q add_list system.ntp.server='0.openwrt.pool.ntp.org'
|
||||
uci commit system
|
||||
fi
|
||||
|
||||
uci -q set uhttpd.main.rfc1918_filter=0
|
||||
uci -q set uhttpd.main.redirect_https=0
|
||||
uci -q set uhttpd.main.script_timeout='120'
|
||||
uci commit uhttpd
|
||||
|
||||
uci -q set upnpd.config.enabled='1'
|
||||
uci commit upnpd
|
||||
|
||||
(echo "root"; sleep 1; echo "root") | /bin/busybox passwd root >/dev/null 2>&1 &
|
||||
|
||||
uci -q set firewall.@defaults[0].flow_offloading='1'
|
||||
uci -q set firewall.@defaults[0].flow_offloading_hw='1'
|
||||
uci commit firewall
|
||||
|
||||
uci -q set upnpd.config.enabled='1'
|
||||
uci commit upnpd
|
||||
|
||||
grep -q log-facility /etc/dnsmasq.conf || \
|
||||
echo "log-facility=/dev/null" >> /etc/dnsmasq.conf
|
||||
|
||||
#kernel_version=$(uname -r)
|
||||
#if [ -f "/lib/modules/$kernel_version/xt_FULLCONENAT.ko" ] || [ -f "/lib/modules/$kernel_version/nft_fullcone.ko" ]; then
|
||||
# uci -q set firewall.@defaults[0].fullcone='1'
|
||||
# uci commit firewall
|
||||
#fi
|
||||
|
||||
uci -q set fstab.@global[0].anon_mount=1
|
||||
uci commit fstab
|
||||
|
||||
uci -q set network.globals.packet_steering=1
|
||||
uci -q set network.@device[0].promisc=1
|
||||
uci commit network
|
||||
|
||||
uci -q set dhcp.@dnsmasq[0].dns_redirect='1'
|
||||
uci -q set dhcp.@dnsmasq[0].allservers='1'
|
||||
uci -q del dhcp.@dnsmasq[0].noresolv
|
||||
uci -q set dhcp.@dnsmasq[0].cachesize='1500'
|
||||
uci -q set dhcp.@dnsmasq[0].min_ttl='3600'
|
||||
uci -q set dhcp.lan.force='1'
|
||||
uci -q set dhcp.lan.ra='hybrid'
|
||||
uci -q set dhcp.lan.ndp='hybrid'
|
||||
uci -q set dhcp.lan.dhcpv6='hybrid'
|
||||
uci commit dhcp
|
||||
|
||||
uci -q set nft-qos.default.limit_enable='0'
|
||||
uci commit nft-qos
|
||||
fi
|
||||
|
||||
# kB
|
||||
memtotal=$(grep MemTotal /proc/meminfo | awk '{print $2}')
|
||||
if [ "$memtotal" -ge 1048576 ]; then
|
||||
# > 1024M
|
||||
cachesize=10000
|
||||
dnsforwardmax=10000
|
||||
nf_conntrack_max=262144
|
||||
elif [ "$memtotal" -ge 524288 ]; then
|
||||
# <= 1024M
|
||||
cachesize=10000
|
||||
dnsforwardmax=10000
|
||||
nf_conntrack_max=131072
|
||||
elif [ "$memtotal" -ge 262144 ]; then
|
||||
# <= 512M
|
||||
cachesize=8192
|
||||
dnsforwardmax=8192
|
||||
nf_conntrack_max=65536
|
||||
elif [ "$memtotal" -ge 131072 ]; then
|
||||
# <= 256M
|
||||
cachesize=4096
|
||||
dnsforwardmax=4096
|
||||
nf_conntrack_max=65536
|
||||
elif [ "$memtotal" -ge 65536 ]; then
|
||||
# <= 128M
|
||||
cachesize=2048
|
||||
dnsforwardmax=2048
|
||||
nf_conntrack_max=32768
|
||||
else
|
||||
# < 64M
|
||||
cachesize=1024
|
||||
dnsforwardmax=1024
|
||||
nf_conntrack_max=16384
|
||||
fi
|
||||
|
||||
if [ -z "$version" ] || [ "$version" -lt 1 ]; then
|
||||
uci -q get dhcp.@dnsmasq[0] || uci -q add dhcp dnsmasq
|
||||
uci -q set dhcp.@dnsmasq[0].cachesize="$cachesize"
|
||||
uci -q set dhcp.@dnsmasq[0].dnsforwardmax="$dnsforwardmax"
|
||||
uci -q set dhcp.@dnsmasq[0].localservice='0'
|
||||
uci -q set dhcp.@dnsmasq[0].localise_queries='1'
|
||||
uci -q set dhcp.@dnsmasq[0].rebind_protection='0'
|
||||
uci -q set dhcp.@dnsmasq[0].rebind_localhost='1'
|
||||
uci commit dhcp
|
||||
|
||||
uci -q set system.@system[0].zram_size_mb="$((memtotal / 1024 / 3))"
|
||||
uci commit system
|
||||
|
||||
# sysctl overwrite
|
||||
SYSCTL_LOCAL=/etc/sysctl.d/50-local.conf
|
||||
mkdir -p /etc/sysctl.d
|
||||
> "$SYSCTL_LOCAL"
|
||||
echo "net.nf_conntrack_max=$nf_conntrack_max" >> "$SYSCTL_LOCAL"
|
||||
echo "net.core.rmem_max=$nf_conntrack_max" >> "$SYSCTL_LOCAL"
|
||||
printf "net.nf_conntrack_max=%s\nnet.core.rmem_max=%s\n" "$nf" "$nf" > /etc/sysctl.d/50-local.conf
|
||||
|
||||
if [ -n "$(command -v nginx)" ]; then
|
||||
uci add wizard shortcuts
|
||||
uci set wizard.@shortcuts[-1].shortcut='pw'
|
||||
uci set wizard.@shortcuts[-1].to_url='http://10.0.0.1/cgi-bin/luci/admin/services/passwall'
|
||||
uci set wizard.@shortcuts[-1].comments='passwall'
|
||||
uci commit wizard
|
||||
|
||||
uci -q set nginx._sc_pw=server
|
||||
uci -q set nginx._sc_pw.server_name="pw"
|
||||
uci -q add_list nginx._sc_pw.listen="80"
|
||||
uci -q add_list nginx._sc_pw.listen="443"
|
||||
uci -q set nginx._sc_pw.return="302 http://10.0.0.1/cgi-bin/luci/admin/services/passwall"
|
||||
|
||||
uci -q set dhcp._sc_pw=domain
|
||||
uci -q set dhcp._sc_pw.name="pw"
|
||||
uci -q set dhcp._sc_pw.ip="10.0.0.1"
|
||||
uci -q set dhcp._sc_pw.comments="PassWall"
|
||||
uci commit dhcp
|
||||
fi
|
||||
|
||||
if [ -z "$(uci -q get dhcp._ta)" ]; then
|
||||
uci -q set dhcp._ta=domain
|
||||
uci -q set dhcp._ta.name="time.android.com"
|
||||
uci -q set dhcp._ta.ip="203.107.6.88"
|
||||
uci -q set dhcp._ta.comments="time.android"
|
||||
uci commit dhcp
|
||||
if command -v nginx >/dev/null 2>&1 && [ -z "$(uci -q get wizard.pw)" ]; then
|
||||
uci -q batch <<-EOF
|
||||
set wizard.pw=shortcuts
|
||||
set wizard.pw.shortcut='pw'
|
||||
set wizard.pw.to_url='http://${lan_ip}/cgi-bin/luci/admin/services/passwall'
|
||||
set wizard.pw.comments='passwall'
|
||||
set nginx._sc_pw=server
|
||||
set nginx._sc_pw.server_name='pw'
|
||||
add_list nginx._sc_pw.listen='80'
|
||||
add_list nginx._sc_pw.listen='443'
|
||||
set nginx._sc_pw.return='302 http://${lan_ip}/cgi-bin/luci/admin/services/passwall'
|
||||
set dhcp._sc_pw=domain
|
||||
set dhcp._sc_pw.name='pw'
|
||||
set dhcp._sc_pw.ip='${lan_ip}'
|
||||
set dhcp._sc_pw.comments='PassWall'
|
||||
EOF
|
||||
fi
|
||||
version=1
|
||||
fi
|
||||
|
||||
if [ -z "$(uci -q get dhcp.default_server)" ]; then
|
||||
uci -q set dhcp.default_server=domain
|
||||
uci -q set dhcp.default_server.name='kwrt'
|
||||
uci -q set dhcp.default_server.ip='10.0.0.1'
|
||||
uci -q set dhcp.default_server.comments='后台地址'
|
||||
uci commit dhcp
|
||||
# 5. 默认后台域名映射与 Nginx 规则
|
||||
[ -z "$(uci -q get dhcp.default_server)" ] && uci -q batch <<-EOF
|
||||
set dhcp.default_server=domain
|
||||
set dhcp.default_server.name='kwrt'
|
||||
set dhcp.default_server.ip='${lan_ip}'
|
||||
set dhcp.default_server.comments='后台地址'
|
||||
EOF
|
||||
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
[ -z "$(uci -q get nginx.default_server)" ] && uci -q batch <<-EOF
|
||||
set nginx.default_server=server
|
||||
set nginx.default_server.server_name='kwrt'
|
||||
add_list nginx.default_server.listen='80 default_server'
|
||||
add_list nginx.default_server.listen='[::]:80 default_server'
|
||||
add_list nginx.default_server.include='conf.d/*.locations'
|
||||
set nginx.default_server.access_log='off; # logd openwrt'
|
||||
EOF
|
||||
|
||||
if [ -f /etc/nginx/conf.d/luci.locations ]; then
|
||||
grep -q 'uwsgi_read_timeout' /etc/nginx/conf.d/luci.locations || sed -i '/include uwsgi_params;/a\\t\tuwsgi_read_timeout 300s;' /etc/nginx/conf.d/luci.locations
|
||||
grep -q 'ubus_script_timeout' /etc/nginx/conf.d/luci.locations || sed -i '/ubus_parallel_req/a\ ubus_script_timeout 600;' /etc/nginx/conf.d/luci.locations
|
||||
fi
|
||||
|
||||
if [ -f /etc/nginx/uci.conf.template ]; then
|
||||
sed -i -e 's/128M/3072M/' -e 's/2 1k/4 512k/' /etc/nginx/uci.conf.template
|
||||
grep -q 'client_body_buffer_size' /etc/nginx/uci.conf.template || sed -i '/large_client_header_buffers/a\\tclient_body_buffer_size 128K;' /etc/nginx/uci.conf.template
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v nginx)" ]; then
|
||||
if [ -z "$(uci -q get nginx.default_server)" ]; then
|
||||
uci -q set nginx.default_server=server
|
||||
uci -q set nginx.default_server.server_name='kwrt'
|
||||
uci -q add_list nginx.default_server.listen='80 default_server'
|
||||
uci -q add_list nginx.default_server.listen='[::]:80 default_server'
|
||||
uci -q add_list nginx.default_server.include='conf.d/*.locations'
|
||||
uci -q set nginx.default_server.access_log='off; # logd openwrt'
|
||||
uci commit nginx
|
||||
fi
|
||||
|
||||
grep -q "uwsgi_read_timeout" /etc/nginx/conf.d/luci.locations 2>/dev/null || {
|
||||
sed -i "s/include uwsgi_params;/include uwsgi_params;\n\t\tuwsgi_read_timeout 300s;/" /etc/nginx/conf.d/luci.locations
|
||||
sed -i '/ubus_parallel_req/a\ ubus_script_timeout 600;' /etc/nginx/conf.d/luci.locations
|
||||
}
|
||||
# 6. uWSGI 超时与 FullCone 探测(兼容 fw3/fw4)
|
||||
for ini in /etc/uwsgi/vassals/luci-*.ini; do
|
||||
[ -f "$ini" ] && ! grep -q 'cgi-timeout' "$ini" && echo "cgi-timeout = 500" >> "$ini"
|
||||
done
|
||||
sed -i 's/limit-as = 1000/limit-as = 5000/g' /etc/uwsgi/vassals/luci-webui.ini 2>/dev/null
|
||||
|
||||
sed -i \
|
||||
-e "s/client_max_body_size 128M/client_max_body_size 3072M/" \
|
||||
-e "s/large_client_header_buffers 2 1k/large_client_header_buffers 4 512k/" \
|
||||
/etc/nginx/uci.conf.template
|
||||
|
||||
grep -q "client_body_buffer_size" /etc/nginx/uci.conf.template || \
|
||||
sed -i "/large_client_header_buffers/a\ client_body_buffer_size 128K;" /etc/nginx/uci.conf.template
|
||||
fi
|
||||
|
||||
rm -rf /tmp/luci-*
|
||||
uci -q set base_config.@status[0].version="$version"
|
||||
uci commit base_config
|
||||
|
||||
grep -q "cgi-timeout" /etc/uwsgi/vassals/luci-*.ini 2>/dev/null || sed -i '$a cgi-timeout = 500' /etc/uwsgi/vassals/luci-*.ini
|
||||
sed -i "s/limit-as = 1000/limit-as = 5000/g" /etc/uwsgi/vassals/luci-webui.ini
|
||||
|
||||
modprobe xt_FULLCONENAT || {
|
||||
if ! iptables -L -t nat 2>/dev/null | grep -q "FULLCONENAT"; then
|
||||
uci -q set firewall.@defaults[0].fullcone='0'
|
||||
uci commit firewall
|
||||
fi
|
||||
modprobe xt_FULLCONENAT >/dev/null 2>&1 || modprobe nft_fullcone >/dev/null 2>&1 || {
|
||||
iptables -L -t nat 2>/dev/null | grep -q 'FULLCONENAT' || \
|
||||
nft list ruleset 2>/dev/null | grep -q 'fullcone' || \
|
||||
uci -q set firewall.@defaults[0].fullcone='0'
|
||||
}
|
||||
|
||||
# 7. 更新版本状态、一次性提交所有 UCI 配置并安全退出
|
||||
[ -f /etc/config/base_config ] && {
|
||||
uci -q get base_config.@status[0] >/dev/null || uci -q add base_config status >/dev/null
|
||||
uci -q set base_config.@status[0].version="$version"
|
||||
}
|
||||
|
||||
uci -q commit
|
||||
rm -rf /tmp/luci-*
|
||||
exit 0
|
||||
@@ -1,55 +1,284 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Optimized & Refactored OpenWrt WiFi Configuration Script
|
||||
# Source Reference: https://github.com/kiddin9/my-packages/blob/master/my-default-settings/files/sbin/mywifi
|
||||
#
|
||||
|
||||
board_name() {
|
||||
[ -e /tmp/sysinfo/board_name ] && cat /tmp/sysinfo/board_name || echo "generic"
|
||||
show_help() {
|
||||
cat << 'EOF_HELP'
|
||||
用法:
|
||||
mywifi [选项] [WiFi名称] [WiFi密码]
|
||||
|
||||
参数:
|
||||
WiFi名称 基础SSID名称(缺省时自动使用系统 hostname)
|
||||
WiFi密码 无线密码(缺省或未指定时默认无密码,指定时需 8-63 位)
|
||||
|
||||
选项:
|
||||
-s, --ssid <名称> 指定基础WiFi名称
|
||||
-p, --password <密码> 指定WiFi密码
|
||||
-h, --help 查看帮助信息
|
||||
|
||||
WiFi命名规范:
|
||||
2.4 GHz -> <WiFi名>_2.4G
|
||||
5 GHz -> <WiFi名>_5G
|
||||
6 GHz -> <WiFi名>_6G
|
||||
|
||||
使用示例:
|
||||
mywifi # 默认使用 hostname,无密码
|
||||
mywifi "HomeWiFi" # HomeWiFi_2.4G / HomeWiFi_5G,无密码
|
||||
mywifi "HomeWiFi" "12345678" # HomeWiFi_2.4G / HomeWiFi_5G,密码 12345678
|
||||
mywifi -s "HomeWiFi" -p "12345678" # 选项传参
|
||||
EOF_HELP
|
||||
}
|
||||
|
||||
wifi_setup_radio(){
|
||||
local radio=$1
|
||||
htmode="$(uci -q get wireless.${radio}.htmode)"
|
||||
if uci -q get wireless.${radio} >/dev/null; then
|
||||
uci -q del wireless.${radio}.disabled
|
||||
uci -q set wireless.${radio}.country='CN'
|
||||
|
||||
if [ "$(uci -q get wireless.${radio}.band)" = "5g" ]; then
|
||||
uci -q set wireless.default_${radio}.ssid="${SSID}_5G"
|
||||
if [[ "$DISTRIB_TARGET" == *ipq60xx ]]; then
|
||||
uci set wireless.${radio}.htmode='HE80'
|
||||
fi
|
||||
elif [ "$(uci -q get wireless.${radio}.band)" = "2g" ]; then
|
||||
uci -q set wireless.default_${radio}.ssid="${SSID}_2.4G"
|
||||
fi
|
||||
uci -q set wireless.default_${radio}.device="${radio}"
|
||||
SSID_PASSWD="${SSID_PASSWD:-$(uci -q get wireless.default_${radio}.key)}"
|
||||
if [ "${SSID_PASSWD}" ]; then
|
||||
uci -q set wireless.default_${radio}.encryption='psk2'
|
||||
uci -q set wireless.default_${radio}.key="${SSID_PASSWD}"
|
||||
else
|
||||
uci -q set wireless.default_${radio}.encryption='none'
|
||||
fi
|
||||
fi
|
||||
}
|
||||
# 1. 解析命令行参数(同时兼顾位置参数与选项参数)
|
||||
CUSTOM_SSID=""
|
||||
CUSTOM_PASSWD=""
|
||||
HAS_PASSWD=0
|
||||
|
||||
for i in $(seq 1 10); do
|
||||
[[ "$(uci -q get base_config.@status[0].inited)" == 1 && "$(cat /etc/config/wireless)" ]] && break
|
||||
[ "$(cat /etc/config/wireless)" ] || /sbin/wifi config
|
||||
sleep 1s
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-s|--ssid)
|
||||
shift
|
||||
CUSTOM_SSID="$1"
|
||||
;;
|
||||
-p|--password)
|
||||
shift
|
||||
CUSTOM_PASSWD="$1"
|
||||
HAS_PASSWD=1
|
||||
;;
|
||||
*)
|
||||
if [ -z "$CUSTOM_SSID" ]; then
|
||||
CUSTOM_SSID="$1"
|
||||
elif [ $HAS_PASSWD -eq 0 ]; then
|
||||
CUSTOM_PASSWD="$1"
|
||||
HAS_PASSWD=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "$(cat /etc/config/wireless)" ]]; then
|
||||
. /etc/openwrt_release
|
||||
SSID="$(uci -q get wizard.default.wifi_ssid || uci -q get system.@system[0].hostname || echo Kwrt)"
|
||||
SSID_PASSWD="$(uci -q get wizard.default.wifi_key)"
|
||||
sed -i '/option disabled/d' /etc/config/wireless
|
||||
# 密码合法性校验
|
||||
if [ $HAS_PASSWD -eq 1 ] && [ -n "$CUSTOM_PASSWD" ]; then
|
||||
pwd_len=${#CUSTOM_PASSWD}
|
||||
if [ "$pwd_len" -lt 8 ] || [ "$pwd_len" -gt 63 ]; then
|
||||
echo "[ERROR] WiFi 密码长度必须在 8 到 63 个字符之间!" >&2
|
||||
exit 1
|
||||
fi
|
||||
SSID_PASSWD="$CUSTOM_PASSWD"
|
||||
else
|
||||
SSID_PASSWD=""
|
||||
fi
|
||||
|
||||
[ "$(uci -q get wireless.radio1.band)" == "5g" ] && uci -q set wireless.radio0.band="2g"
|
||||
widx=0
|
||||
while uci rename wireless.@wifi-iface[$widx]=default_radio$widx >/dev/null 2>&1; do widx=$((widx+1)); done
|
||||
for radio in radio0 radio1 radio2 radio3; do
|
||||
wifi_setup_radio ${radio}
|
||||
done
|
||||
uci commit wireless
|
||||
uci -q set base_config.@status[0].wifi=1
|
||||
uci commit base_config
|
||||
/sbin/wifi reload
|
||||
fi
|
||||
# 2. 深度就绪检测:确保内核PHY驱动、多频硬件、系统初始化状态和UCI配置全部真正就绪
|
||||
wait_wifi_ready() {
|
||||
local max_wait=15
|
||||
local waited=0
|
||||
|
||||
while [ $waited -lt $max_wait ]; do
|
||||
local phy_count=0
|
||||
local uci_count=0
|
||||
|
||||
# B. 检查内核物理无线设备 (PHY)
|
||||
local phys="$(ls -d /sys/class/ieee80211/phy* 2>/dev/null)"
|
||||
[ -n "$phys" ] && phy_count=$(echo "$phys" | wc -w)
|
||||
|
||||
# C. 统计 UCI wireless 中实际已解析出的 wifi-device 节点数量(非文件大小)
|
||||
if [ -s /etc/config/wireless ]; then
|
||||
local uci_radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
||||
[ -n "$uci_radios" ] && uci_count=$(echo "$uci_radios" | wc -w)
|
||||
fi
|
||||
|
||||
# D. 自动探测与配置补全:
|
||||
# - 物理网卡已加载,但 UCI 配置中的 radio 数量少于物理网卡数量(多频网卡尚未完全生成配置)
|
||||
# - 或者配置文件缺失/没有 radio 配置
|
||||
if [ $phy_count -gt 0 ] && [ $uci_count -lt $phy_count ]; then
|
||||
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
|
||||
elif [ $phy_count -eq 0 ] && [ $uci_count -eq 0 ] && [ ! -s /etc/config/wireless ]; then
|
||||
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# 重新同步一次 UCI radio 数量
|
||||
if [ -s /etc/config/wireless ]; then
|
||||
local uci_radios_check="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
||||
[ -n "$uci_radios_check" ] && uci_count=$(echo "$uci_radios_check" | wc -w)
|
||||
fi
|
||||
|
||||
# E. 判定就绪标准:
|
||||
# 1) UCI 中有效 wifi-device 数量 > 0
|
||||
# 2) 若检测到物理 PHY,UCI radio 数量必须完全覆盖检测到的 PHY 数量
|
||||
if [ $uci_count -gt 0 ]; then
|
||||
if [ $phy_count -eq 0 ] || [ $uci_count -ge $phy_count ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
|
||||
# 超时保护:只要至少有一个 radio 节点,允许降级继续
|
||||
if [ $uci_count -gt 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# 执行深度就绪等待
|
||||
if ! wait_wifi_ready; then
|
||||
echo "[ERROR] 未检测到可用无线硬件或无线配置生成超时!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. 识别射频频段函数 (2g, 5g, 6g)
|
||||
detect_band() {
|
||||
local radio="$1"
|
||||
local band="$(uci -q get wireless.${radio}.band 2>/dev/null | tr 'A-Z' 'a-z')"
|
||||
|
||||
case "$band" in
|
||||
*2g*|*2.4g*) echo "2g"; return ;;
|
||||
*5g*) echo "5g"; return ;;
|
||||
*6g*) echo "6g"; return ;;
|
||||
esac
|
||||
|
||||
# 信道 (channel) 推断
|
||||
local channel="$(uci -q get wireless.${radio}.channel 2>/dev/null)"
|
||||
if [ -n "$channel" ] && [ "$channel" -eq "$channel" 2>/dev/null ]; then
|
||||
if [ "$channel" -ge 1 ] && [ "$channel" -le 14 ]; then
|
||||
echo "2g"; return
|
||||
elif [ "$channel" -ge 32 ] && [ "$channel" -le 177 ]; then
|
||||
echo "5g"; return
|
||||
elif [ "$channel" -ge 180 ]; then
|
||||
echo "6g"; return
|
||||
fi
|
||||
fi
|
||||
|
||||
# hwmode 推断
|
||||
local hwmode="$(uci -q get wireless.${radio}.hwmode 2>/dev/null | tr 'A-Z' 'a-z')"
|
||||
case "$hwmode" in
|
||||
*a*) echo "5g"; return ;;
|
||||
*b*|*g*) echo "2g"; return ;;
|
||||
esac
|
||||
|
||||
# 双频常规配对推断
|
||||
if [ "$radio" = "radio0" ]; then
|
||||
local r1_band="$(uci -q get wireless.radio1.band 2>/dev/null | tr 'A-Z' 'a-z')"
|
||||
[ "$r1_band" = "5g" ] && echo "2g" && return
|
||||
elif [ "$radio" = "radio1" ]; then
|
||||
local r0_band="$(uci -q get wireless.radio0.band 2>/dev/null | tr 'A-Z' 'a-z')"
|
||||
[ "$r0_band" = "2g" ] && echo "5g" && return
|
||||
fi
|
||||
|
||||
echo "2g"
|
||||
}
|
||||
|
||||
# 4. 加载系统发行版信息(若存在)
|
||||
[ -f /etc/openwrt_release ] && . /etc/openwrt_release
|
||||
|
||||
# 5. 确定基础 WiFi 名称
|
||||
if [ -n "$CUSTOM_SSID" ]; then
|
||||
SSID="$CUSTOM_SSID"
|
||||
else
|
||||
SSID="$(uci -q get system.@system[0].hostname 2>/dev/null)"
|
||||
[ -z "$SSID" ] && [ -r /proc/sys/kernel/hostname ] && SSID="$(cat /proc/sys/kernel/hostname)"
|
||||
[ -z "$SSID" ] && SSID="Kwrt"
|
||||
fi
|
||||
|
||||
# 移除禁用标记
|
||||
sed -i '/option disabled/d' /etc/config/wireless 2>/dev/null
|
||||
|
||||
# 修正双频标记
|
||||
if [ "$(uci -q get wireless.radio1.band 2>/dev/null)" = "5g" ]; then
|
||||
[ -z "$(uci -q get wireless.radio0.band 2>/dev/null)" ] && uci -q set wireless.radio0.band="2g"
|
||||
fi
|
||||
|
||||
# 重命名匿名接口 section 为 default_radioX
|
||||
widx=0
|
||||
while uci rename wireless.@wifi-iface[$widx]=default_radio$widx >/dev/null 2>&1; do
|
||||
widx=$((widx + 1))
|
||||
done
|
||||
|
||||
# 6. 配置各 Radio 参数
|
||||
wifi_setup_radio() {
|
||||
local radio="$1"
|
||||
|
||||
uci -q get wireless.${radio} >/dev/null 2>&1 || return 0
|
||||
|
||||
uci -q del wireless.${radio}.disabled
|
||||
uci -q set wireless.${radio}.country='CN'
|
||||
|
||||
local band="$(detect_band "$radio")"
|
||||
local ssid_suffix=""
|
||||
case "$band" in
|
||||
2g) ssid_suffix="_2.4G" ;;
|
||||
5g) ssid_suffix="_5G" ;;
|
||||
6g) ssid_suffix="_6G" ;;
|
||||
*) ssid_suffix="_${band}" ;;
|
||||
esac
|
||||
|
||||
local full_ssid="${SSID}${ssid_suffix}"
|
||||
|
||||
# 特定硬件架构适配(如高通 ipq60xx)
|
||||
if [ "$band" = "5g" ]; then
|
||||
case "$DISTRIB_TARGET" in
|
||||
*ipq60xx*)
|
||||
uci -q set wireless.${radio}.htmode='HE80'
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
local iface="default_${radio}"
|
||||
uci -q get wireless.${iface} >/dev/null 2>&1 || uci -q set wireless.${iface}=wifi-iface
|
||||
|
||||
uci -q set wireless.${iface}.device="${radio}"
|
||||
uci -q set wireless.${iface}.mode='ap'
|
||||
uci -q set wireless.${iface}.network='lan'
|
||||
uci -q set wireless.${iface}.ssid="${full_ssid}"
|
||||
|
||||
# 密码与加密策略配置
|
||||
if [ -n "$SSID_PASSWD" ]; then
|
||||
if [ "$band" = "6g" ]; then
|
||||
# 6GHz 频段按标准强制要求 WPA3-SAE
|
||||
uci -q set wireless.${iface}.encryption='sae'
|
||||
else
|
||||
uci -q set wireless.${iface}.encryption='psk2'
|
||||
fi
|
||||
uci -q set wireless.${iface}.key="${SSID_PASSWD}"
|
||||
echo "[INFO] ${radio} (${band}): SSID='${full_ssid}', 加密='$(uci -q get wireless.${iface}.encryption)'"
|
||||
else
|
||||
uci -q set wireless.${iface}.encryption='none'
|
||||
uci -q del wireless.${iface}.key
|
||||
echo "[INFO] ${radio} (${band}): SSID='${full_ssid}', 无密码 (开放网络)"
|
||||
fi
|
||||
}
|
||||
|
||||
# 7. 动态遍历所有射频设备并应用设置
|
||||
radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
||||
[ -z "$radios" ] && radios="radio0 radio1 radio2 radio3"
|
||||
|
||||
for radio in $radios; do
|
||||
wifi_setup_radio "$radio"
|
||||
done
|
||||
|
||||
# 提交配置
|
||||
uci commit wireless 2>/dev/null
|
||||
|
||||
# 若存在 base_config(kiddin9 固件状态记录),更新状态
|
||||
if uci -q get base_config.@status[0] >/dev/null 2>&1; then
|
||||
uci -q set base_config.@status[0].wifi=1
|
||||
uci commit base_config 2>/dev/null
|
||||
fi
|
||||
|
||||
# 重载无线网络
|
||||
if command -v wifi >/dev/null 2>&1; then
|
||||
wifi reload
|
||||
elif [ -x /sbin/wifi ]; then
|
||||
/sbin/wifi reload
|
||||
fi
|
||||
|
||||
echo "[SUCCESS] 无线配置已更新并生效。"
|
||||
+3
-3
@@ -50,12 +50,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_VERSION:=1.6.7_p55
|
||||
PKG_RELEASE:=48
|
||||
PKG_VERSION:=1.6.7_p58
|
||||
PKG_RELEASE:=50
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
PKG_SOURCE_VERSION:=aaf2ba064bbf751eaa0237e00f94c1619f05b648
|
||||
PKG_SOURCE_VERSION:=1b575d41dd35684e035541afe1f129da072275dd
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
|
||||
Reference in new issue
Block a user