<%:Database Reset Successful%>
diff --git a/luci-app-easytier/root/etc/init.d/easytier b/luci-app-easytier/root/etc/init.d/easytier
index 323b1be79..0bfa70dc9 100755
--- a/luci-app-easytier/root/etc/init.d/easytier
+++ b/luci-app-easytier/root/etc/init.d/easytier
@@ -466,10 +466,10 @@ start_et() {
fi
if [ ! -z "$proxy_network" ] ; then
if [[ "$(grep "list proxy_network" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
- core_cmd="${core_cmd} -n $proxy_network"
+ core_cmd="${core_cmd} -n '$proxy_network'"
else
for proxy_networks in $(cat /etc/config/easytier | grep 'list proxy_network' | awk -F 'list proxy_network' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
- core_cmd="${core_cmd} -n $proxy_networks"
+ core_cmd="${core_cmd} -n '$proxy_networks'"
done
fi
fi
diff --git a/luci-app-modeminfo/Makefile b/luci-app-modeminfo/Makefile
index 01c4ecff8..b5bb013e9 100644
--- a/luci-app-modeminfo/Makefile
+++ b/luci-app-modeminfo/Makefile
@@ -4,7 +4,7 @@ LUCI_TITLE:=Information dashboard for 3G/LTE dongle
LUCI_DEPENDS:=+modeminfo
PKG_LICENSE:=GPLv3
PKG_VERSION:=0.4.8
-PKG_RELEASE:=10
+PKG_RELEASE:=11
include $(TOPDIR)/feeds/luci/luci.mk
diff --git a/luci-app-modeminfo/htdocs/luci-static/resources/view/modem/main.js b/luci-app-modeminfo/htdocs/luci-static/resources/view/modem/main.js
index da974c075..5679fa2e8 100644
--- a/luci-app-modeminfo/htdocs/luci-static/resources/view/modem/main.js
+++ b/luci-app-modeminfo/htdocs/luci-static/resources/view/modem/main.js
@@ -434,8 +434,8 @@ return view.extend({
updateSignalBar(
'sinr', i, modem.sinr, ' dB',
- netmode === 'LTE' ? 'sinr' : 'ecio',
- netmode === 'LTE' ? -20 : -24
+ /LTE|NR/.test(netmode) ? 'sinr' : 'ecio',
+ /LTE|NR/.test(netmode) ? -20 : -24
);
updateSignalBar('rsrp', i, modem.rsrp, ' dBm', 'rsrp', -140);
diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile
index dc2e67735..700827cb6 100644
--- a/luci-app-passwall/Makefile
+++ b/luci-app-passwall/Makefile
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.6.2
-PKG_RELEASE:=165
+PKG_RELEASE:=166
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
index fc0886df2..53f06f01a 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
@@ -49,6 +49,12 @@ o.rewrite_option = o.option
o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o.rewrite_option = o.option
o:depends({ [_n("realms")] = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
index affbeeb12..bc25597a7 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
@@ -351,6 +351,12 @@ end
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends({ [_n("hysteria2_realms")] = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
index 172fe20be..5ebab8fc9 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
@@ -375,6 +375,12 @@ if singbox_tags:find("with_quic") then
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends({ [_n("hysteria2_realms")] = "1" })
+ o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+ end
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
index a59c78b16..6d80af230 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
@@ -36,6 +36,12 @@ o:depends({ [_n("custom")] = false })
o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o.rewrite_option = o.option
o:depends({ [_n("realms")] = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
index c36ad6e17..85499e0bb 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
@@ -130,6 +130,12 @@ o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends({ [_n("hysteria2_realms")] = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
index 562e9035f..572478acf 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
@@ -163,6 +163,12 @@ if singbox_tags:find("with_quic") then
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends({ [_n("hysteria2_realms")] = "1" })
+ o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+ end
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
diff --git a/luci-app-passwall/luasrc/passwall/api.lua b/luci-app-passwall/luasrc/passwall/api.lua
index bcd98e696..372133a31 100644
--- a/luci-app-passwall/luasrc/passwall/api.lua
+++ b/luci-app-passwall/luasrc/passwall/api.lua
@@ -1605,12 +1605,20 @@ function vps_domain_exclude(domain)
end
function parse_realm_uri(uri)
- if type(uri) ~= "string" then return nil end
+ uri = trim(uri)
+ if uri == "" then return nil end
-- realm[+http]://token@server/realm_id?query
- local scheme, token, server_url, realm_id, query = trim(uri):match("^(realm%+http|realm)://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
- if not scheme or not token or not server_url or not realm_id then return nil end
+ local scheme = (uri:match("^realm%+http://") and "realm+http") or (uri:match("^realm://") and "realm")
+ if not scheme then return nil end
+ uri = uri:gsub("^realm%+http://", ""):gsub("^realm://", "")
+ local token, server_url, realm_id, query = uri:match("^([^@]+)@([^/]+)/([^?]*)%??(.*)$")
+ if not token or not server_url or not realm_id then return nil end
realm_id = realm_id:gsub("/+$", "")
- local address, port = server_url:match("^%[?([^%]]+)%]?:?(%d*)$")
+ local address, port = server_url:match("^%[([^%]]+)%]:(%d+)$") --ipv6:port
+ if not address then
+ address, port = server_url:match("^([^:]+):(%d+)$") --ipv4[domain]:port
+ end
+ address = address or server_url:match("^%[([^%]]+)%]$") or server_url
port = tonumber(port) or (scheme == "realm+http" and 80 or 443)
local realm = {
scheme = scheme,
@@ -1622,9 +1630,9 @@ function parse_realm_uri(uri)
}
-- 解析 query 中的 stun=
local stun_servers
- for value in (query or ""):gmatch("[?&]?[Ss][Tt][Uu][Nn]=([^&]+)") do
- if not stun_servers then stun_servers = {} end
- stun_servers[#stun_servers + 1] = value
+ for v in (query or ""):gmatch("[Ss][Tt][Uu][Nn]=([^&]+)") do
+ stun_servers = stun_servers or {}
+ stun_servers[#stun_servers + 1] = v
end
realm.stun_servers = stun_servers
return realm
diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po
index 05a774baf..f2d8ae6bc 100644
--- a/luci-app-passwall/po/zh-cn/passwall.po
+++ b/luci-app-passwall/po/zh-cn/passwall.po
@@ -2167,3 +2167,6 @@ msgstr "并发隧道连接数"
msgid "Processing, please wait…"
msgstr "正在处理,请稍候…"
+
+msgid "Invalid Realm URL."
+msgstr "Realm URL 不正确。"
diff --git a/luci-app-run/Makefile b/luci-app-run/Makefile
index d1b1e934c..e20e0623a 100644
--- a/luci-app-run/Makefile
+++ b/luci-app-run/Makefile
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-run
PKG_VERSION:=1.0.0
-PKG_RELEASE:=5
+PKG_RELEASE:=6
LUCI_TITLE:=LuCI support for running installers and packages
LUCI_DESCRIPTION:=Upload and execute .run, .sh, .ipk and .apk files from LuCI.
@@ -16,6 +16,8 @@ define Package/luci-app-run/postinst
#!/bin/sh
# 设置版本号到临时文件,用于前端资源缓存刷新
echo "$(PKG_VERSION)-$(PKG_RELEASE)" > /tmp/luci-app-run.version
+# 重启 rpcd 服务以加载新插件
+/etc/init.d/rpcd restart >/dev/null 2>&1 || true
exit 0
endef
diff --git a/luci-app-run/htdocs/luci-static/resources/view/run/i18n.json b/luci-app-run/htdocs/luci-static/resources/view/run/i18n.json
index f0f718761..59a60529a 100644
--- a/luci-app-run/htdocs/luci-static/resources/view/run/i18n.json
+++ b/luci-app-run/htdocs/luci-static/resources/view/run/i18n.json
@@ -23,7 +23,12 @@
"running": "安装器正在运行。",
"last_file": "上一次安装包:%s",
"err_no_opkg": "您的系统不支持 ipk 包安装,请选择 apk 包。",
- "err_no_apk": "您的系统不支持 apk 包安装,请选择 ipk 包。"
+ "err_no_apk": "您的系统不支持 apk 包安装,请选择 ipk 包。",
+ "script_args": "脚本参数(如 -q -h)",
+ "args_hint": "您可以输入脚本参数或留空",
+ "cancel": "取消",
+ "confirm": "确定",
+ "auto_cleaned": "执行完毕,已自动清理临时文件。"
},
"en": {
"title": "Run Installer",
@@ -49,6 +54,11 @@
"running": "Installer is running.",
"last_file": "Last installer: %s",
"err_no_opkg": "Your system does not support ipk package installation. Please use .apk packages instead.",
- "err_no_apk": "Your system does not support apk package installation. Please use .ipk packages instead."
+ "err_no_apk": "Your system does not support apk package installation. Please use .ipk packages instead.",
+ "script_args": "Script arguments (e.g., -q -h)",
+ "args_hint": "You can enter script arguments or leave it blank",
+ "cancel": "Cancel",
+ "confirm": "OK",
+ "auto_cleaned": "Execution complete, temporary files cleaned automatically."
}
}
\ No newline at end of file
diff --git a/luci-app-run/htdocs/luci-static/resources/view/run/index.js b/luci-app-run/htdocs/luci-static/resources/view/run/index.js
index 91fb656b2..8c0ceb999 100644
--- a/luci-app-run/htdocs/luci-static/resources/view/run/index.js
+++ b/luci-app-run/htdocs/luci-static/resources/view/run/index.js
@@ -48,12 +48,50 @@ function getLang() {
function _(key) {
if (!I18N)
- return key;
+ return getDefaultText(key);
var lang = getLang();
- var str = I18N[lang]?.[key] || I18N.zh[key] || key;
+ var str = (I18N[lang] && I18N[lang][key]) || (I18N.zh && I18N.zh[key]) || getDefaultText(key) || key;
var args = Array.prototype.slice.call(arguments, 1);
- return str.format.apply(str, args);
+ if (args.length > 0) {
+ return str.format.apply(str, args);
+ }
+ return str;
+}
+
+function getDefaultText(key) {
+ var defaults = {
+ 'title': 'Run安装器',
+ 'desc': '在路由器上上传并执行脚本或安装包,注意架构务必匹配。',
+ 'drop_tip': '拖入文件,或从电脑选择。',
+ 'choose_file': '选择 .run 或 .sh 文件',
+ 'choose_ipk': '选择 .ipk 包',
+ 'choose_apk': '选择 .apk 包',
+ 'execute': '执行',
+ 'clean_up': '清理',
+ 'upload_title': '上传文件',
+ 'log_title': '执行日志',
+ 'clean_done': '临时文件与日志已清理。',
+ 'only_supported': '仅支持 .run、.sh、.ipk 和 .apk 文件。',
+ 'prepare_upload': '准备上传:%s (%s)',
+ 'upload_failed': '上传失败。',
+ 'uploading': '正在上传 %s:%d%%',
+ 'upload_err': '上传请求失败。',
+ 'upload_invalid': '上传返回格式无效。',
+ 'upload_done': '上传完成:%s (%s)',
+ 'starting': '正在启动安装器...',
+ 'started': '安装器已启动,PID %d。',
+ 'running': '安装器正在运行。',
+ 'last_file': '上一次安装包:%s',
+ 'err_no_opkg': '您的系统不支持 ipk 包安装,请选择 apk 包。',
+ 'err_no_apk': '您的系统不支持 apk 包安装,请选择 ipk 包。',
+ 'script_args': '脚本参数(如 -q -h)',
+ 'args_hint': '您可以输入脚本参数或留空',
+ 'cancel': '取消',
+ 'confirm': '确定',
+ 'auto_cleaned': '执行完毕,已自动清理临时文件。'
+ };
+ return defaults[key] || null;
}
var uploadStart = rpc.declare({
@@ -77,7 +115,7 @@ var uploadFinish = rpc.declare({
var runInstaller = rpc.declare({
object: 'luci-app-run',
method: 'run',
- params: ['id']
+ params: ['id', 'args']
});
var getStatus = rpc.declare({
@@ -134,6 +172,7 @@ return view.extend({
logOffset: 0,
currentUploadId: null,
+ currentFileType: null,
appVersion: 'unknown',
capabilities: { opkg: 1, apk: 1 },
@@ -257,7 +296,18 @@ return view.extend({
style: 'min-width:140px;margin-left:15px;background:#7B1FA2!important;background-color:#7B1FA2!important;background-image:none!important;color:#fff!important;border-color:#7B1FA2!important;box-shadow:none!important;text-shadow:none!important;opacity:1!important',
click: function (ev) {
ev.preventDefault();
- self.startRun(runButton, state);
+
+ if (self.currentFileType === '.sh') {
+ self.showArgsDialog(function (args) {
+ if (args !== null) {
+ log.textContent = '';
+ self.startRun(runButton, state, args.trim());
+ }
+ });
+ } else {
+ log.textContent = '';
+ self.startRun(runButton, state, '');
+ }
}
}, [_('execute')]);
@@ -272,6 +322,8 @@ return view.extend({
self.currentUploadId = null;
self.logOffset = 0;
+ self.prevRunning = false;
+ self.autoCleanType = null;
runButton.disabled = true;
log.textContent = '';
progress.style.display = 'none';
@@ -358,6 +410,10 @@ return view.extend({
return Promise.reject();
}
+ // 记录文件类型
+ var ext = file.name.match(/\.(run|sh|ipk|apk)$/i);
+ self.currentFileType = ext ? ext[0].toLowerCase() : null;
+
progress.style.display = '';
progress.value = 0;
runButton.disabled = true;
@@ -414,15 +470,16 @@ return view.extend({
});
},
- startRun: function (runButton, state) {
+ startRun: function (runButton, state, args) {
var self = this;
if (!this.currentUploadId) return;
runButton.disabled = true;
state.textContent = _('starting');
+ self.autoCleanType = null;
- return runInstaller(this.currentUploadId).then(function (res) {
+ return runInstaller(this.currentUploadId, args || '').then(function (res) {
if (res && res.error) {
var errorMsg = res.error;
if (res.error === 'ERR_NO_OPKG') {
@@ -434,6 +491,10 @@ return view.extend({
}
self.logOffset = 0;
+ self.prevRunning = true;
+ if (self.currentFileType === '.sh' || self.currentFileType === '.run') {
+ self.autoCleanType = self.currentFileType;
+ }
state.textContent = _('started', res.pid);
}).catch(function (err) {
runButton.disabled = false;
@@ -441,6 +502,47 @@ return view.extend({
});
},
+ showArgsDialog: function (callback) {
+ var dialog = E('div', {
+ style: 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999'
+ }, [
+ E('div', {
+ style: 'background:#fff;border-radius:8px;padding:20px;width:400px;box-shadow:0 4px 20px rgba(0,0,0,0.3)'
+ }, [
+ E('input', {
+ type: 'text',
+ placeholder: _('args_hint'),
+ style: 'width:100%;padding:10px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;font-size:14px',
+ id: 'run-args-dialog-input'
+ }),
+ E('div', {
+ style: 'display:flex;justify-content:flex-end;gap:10px'
+ }, [
+ E('button', {
+ class: 'cbi-button cbi-button-reset',
+ style: 'padding:8px 20px;text-transform:none',
+ click: function () {
+ document.body.removeChild(dialog);
+ callback(null);
+ }
+ }, [_('cancel')]),
+ E('button', {
+ class: 'cbi-button cbi-button-action',
+ style: 'padding:8px 20px;text-transform:none',
+ click: function () {
+ var args = document.getElementById('run-args-dialog-input').value;
+ document.body.removeChild(dialog);
+ callback(args);
+ }
+ }, [_('confirm')])
+ ])
+ ])
+ ]);
+
+ document.body.appendChild(dialog);
+ document.getElementById('run-args-dialog-input').focus();
+ },
+
refreshLog: function (log, state) {
var self = this;
@@ -454,8 +556,24 @@ return view.extend({
self.logOffset = res.offset || self.logOffset;
- if (res.running)
+ if (res.running) {
state.textContent = _('running');
+ self.prevRunning = true;
+ } else if (self.prevRunning) {
+ // Installer just finished
+ self.prevRunning = false;
+
+ // Auto-cleanup for .sh and .run files
+ if (self.autoCleanType) {
+ self.autoCleanType = null;
+ cleanup().then(function () {
+ self.currentUploadId = null;
+ state.textContent = _('auto_cleaned');
+ }).catch(function () {
+ state.textContent = _('clean_done');
+ });
+ }
+ }
}).catch(function () { });
}
});
\ No newline at end of file
diff --git a/luci-app-run/root/etc/rpcd.d/luci-app-run.json b/luci-app-run/root/etc/rpcd.d/luci-app-run.json
new file mode 100644
index 000000000..8ba2ba175
--- /dev/null
+++ b/luci-app-run/root/etc/rpcd.d/luci-app-run.json
@@ -0,0 +1,6 @@
+{
+ "luci-app-run": {
+ "description": "LuCI app run service",
+ "script": "/usr/libexec/rpcd/luci-app-run"
+ }
+}
diff --git a/luci-app-run/root/usr/libexec/rpcd/luci-app-run b/luci-app-run/root/usr/libexec/rpcd/luci-app-run
index e32c7c564..80e9f708b 100755
--- a/luci-app-run/root/usr/libexec/rpcd/luci-app-run
+++ b/luci-app-run/root/usr/libexec/rpcd/luci-app-run
@@ -89,7 +89,28 @@ read_state() {
is_running() {
local pid="$1"
- [ -n "$pid" ] && [ -d "/proc/$pid" ]
+ [ -n "$pid" ] || return 1
+
+ [ -d "/proc/$pid" ] || return 1
+
+ local cmdline
+ cmdline="$(cat "/proc/$pid/cmdline" 2>/dev/null | tr '\0' ' ')"
+
+ echo "$cmdline" | grep -q "luci-app-run" && return 0
+
+ # Check for .run files (must be followed by space or end of string)
+ case "$cmdline" in
+ *".run "*) return 0 ;;
+ *".run") return 0 ;;
+ esac
+
+ # Check for .sh files (must be followed by space or end of string)
+ case "$cmdline" in
+ *".sh "*) return 0 ;;
+ *".sh") return 0 ;;
+ esac
+
+ return 1
}
method_list() {
@@ -108,7 +129,8 @@ method_list() {
"id": "String"
},
"run": {
- "id": "String"
+ "id": "String",
+ "args": "String"
},
"status": {},
"read_log": {
@@ -217,6 +239,7 @@ upload_finish() {
run_installer() {
load_input
get_string id
+ get_string args
valid_id "$id" || { json_error "Invalid upload id"; return; }
@@ -227,6 +250,11 @@ run_installer() {
file="$dir/$name"
read_state
+ if [ -n "$PID" ] && ! is_running "$PID"; then
+ rm -f "$STATE_FILE"
+ unset PID
+ fi
+
if is_running "$PID"; then
json_error "Another installer is already running"
return
@@ -279,8 +307,8 @@ run_installer() {
(
cd "$work" || exit 1
printf 'luci-app-run: started %s\n' "$now"
- printf 'luci-app-run: executing %s\n\n' "$file"
- "$file"
+ printf 'luci-app-run: executing %s %s\n\n' "$file" "$args"
+ "$file" $args
rc=$?
printf '\nluci-app-run: exited with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
exit "$rc"
@@ -356,8 +384,19 @@ cleanup() {
return
fi
- rm -rf "$UPLOAD_DIR" "$RUN_DIR" "$STATE_FILE"
- mkdir -p "$UPLOAD_DIR" "$RUN_DIR"
+ rm -rf "$UPLOAD_DIR"
+ mkdir -p "$UPLOAD_DIR"
+
+ # Keep state file but clear PID and FILE, preserve LOG path
+ if [ -n "$LOG" ]; then
+ echo "PID=" > "$STATE_FILE"
+ echo "FILE=" >> "$STATE_FILE"
+ echo "LOG=$LOG" >> "$STATE_FILE"
+ echo "STARTED=" >> "$STATE_FILE"
+ else
+ > "$STATE_FILE"
+ fi
+
json_ok "Cleaned"
}
diff --git a/openwrt-fastfetch/Makefile b/openwrt-fastfetch/Makefile
index c3a2684fa..952730163 100644
--- a/openwrt-fastfetch/Makefile
+++ b/openwrt-fastfetch/Makefile
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=fastfetch
-PKG_VERSION:=2.64.2
-PKG_RELEASE:=9
+PKG_VERSION:=2.65.1
+PKG_RELEASE:=10
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/fastfetch-cli/fastfetch/tar.gz/$(PKG_VERSION)?