mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 23:05:13 +08:00
🎉 Sync 2026-10-01 14:45:51
This commit is contained in:
1 parent
f1518a803c
commit
620e59fee5
124 files changed
+21594
-1
No files matched your search
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"admin/system/liquid/save_config": {
|
||||
"title": "Liquid Config",
|
||||
"order": 100,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.liquid",
|
||||
"function": "act_save_config"
|
||||
}
|
||||
},
|
||||
"admin/system/liquid/version": {
|
||||
"title": "Liquid Version",
|
||||
"order": 101,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.liquid",
|
||||
"function": "act_version"
|
||||
}
|
||||
},
|
||||
"admin/system/liquid/detect_pkgmgr": {
|
||||
"title": "Liquid OTA Detect PkgMgr",
|
||||
"order": 102,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.liquid",
|
||||
"function": "act_detect_pkgmgr"
|
||||
}
|
||||
},
|
||||
"admin/system/liquid/ota_download": {
|
||||
"title": "Liquid OTA Download",
|
||||
"order": 103,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.liquid",
|
||||
"function": "act_ota_download"
|
||||
}
|
||||
},
|
||||
"admin/system/liquid/ota_download_progress": {
|
||||
"title": "Liquid OTA Download Progress",
|
||||
"order": 104,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.liquid",
|
||||
"function": "act_ota_download_progress"
|
||||
}
|
||||
},
|
||||
"admin/system/liquid/ota_install": {
|
||||
"title": "Liquid OTA Install",
|
||||
"order": 105,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.liquid",
|
||||
"function": "act_ota_install"
|
||||
}
|
||||
},
|
||||
"admin/system/liquid/ota_clear": {
|
||||
"title": "Liquid OTA Clear",
|
||||
"order": 106,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.liquid",
|
||||
"function": "act_ota_clear"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
'use strict';
|
||||
/* Liquid theme: save theme config (mode / accent / bing) directly.
|
||||
Called via XHR POST to /cgi-bin/luci/admin/system/liquid/save_config
|
||||
(like luci-app-pushbot's save_config), bypassing the uci rpc which is
|
||||
unreliable across LuCI versions. Writes /etc/config/liquid with fs
|
||||
(system()/uci CLI are not available in the ucode dispatcher sandbox). */
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { popen } from 'fs';
|
||||
|
||||
return {
|
||||
act_version: function() {
|
||||
let ver = "";
|
||||
/* mtime 校验 + /tmp 缓存(与 header/footer 模板同一套机制),避免每次 fork */
|
||||
let db_file = '/lib/apk/db/installed';
|
||||
let db_mtime = 0;
|
||||
let st = fs.stat(db_file);
|
||||
if (st && st.type == 'file') {
|
||||
db_mtime = st.mtime;
|
||||
} else {
|
||||
db_file = '/usr/lib/opkg/status';
|
||||
st = fs.stat(db_file);
|
||||
if (st && st.type == 'file')
|
||||
db_mtime = st.mtime;
|
||||
}
|
||||
if (db_mtime > 0) {
|
||||
let cm = match(fs.readfile('/tmp/liquid-version.cache') ?? '', /^(\d+) (.*)$/);
|
||||
if (cm !== null && sprintf('%d', db_mtime) == cm[1])
|
||||
ver = cm[2];
|
||||
}
|
||||
if (ver == "") {
|
||||
/* apk (OpenWrt 24.10+): /lib/apk/db/installed */
|
||||
let f = popen("awk '/^P:luci-theme-liquid$/{f=1;next} f&&/^V:/{print substr($0,3);exit}' " + db_file + " 2>/dev/null", "r");
|
||||
if (f) { ver = replace(f.read("all"), /\s+/, ""); f.close(); }
|
||||
/* opkg (legacy): /usr/lib/opkg/status */
|
||||
if (ver == "") {
|
||||
f = popen("awk '/^Package: luci-theme-liquid$/{f=1;next} f&&/^Version:/{print $2;exit}' /usr/lib/opkg/status 2>/dev/null", "r");
|
||||
if (f) { ver = replace(f.read("all"), /\s+/, ""); f.close(); }
|
||||
}
|
||||
if (ver != "" && db_mtime > 0)
|
||||
fs.writefile('/tmp/liquid-version.cache', sprintf('%d %s', db_mtime, ver));
|
||||
}
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ version: ver });
|
||||
},
|
||||
|
||||
act_save_config: function() {
|
||||
http.prepare_content("application/json");
|
||||
|
||||
let body;
|
||||
try { body = http.content(); } catch { body = null; }
|
||||
if (!body) {
|
||||
http.write_json({ ok: false, error: "no data" });
|
||||
return;
|
||||
}
|
||||
|
||||
let data;
|
||||
try { data = json(body); } catch { data = null; }
|
||||
if (type(data) != 'object') {
|
||||
http.write_json({ ok: false, error: "invalid json" });
|
||||
return;
|
||||
}
|
||||
|
||||
/* 白名单字段 + 值校验 */
|
||||
let writes = {};
|
||||
for (let k in data) {
|
||||
let v = sprintf("%s", data[k]);
|
||||
if (k == 'mode' && match(v, /^(light|dark|auto)$/))
|
||||
writes['mode'] = v;
|
||||
else if (k == 'accent' && match(v, /^(blue|magenta|amber|purple|lime|custom)$/))
|
||||
writes['accent'] = v;
|
||||
else if (k == 'accent_custom' && match(v, /^#[0-9a-fA-F]{6}$/))
|
||||
writes['accent_custom'] = v;
|
||||
else if (k == 'bing' && match(v, /^(0|1)$/))
|
||||
writes['bing'] = v;
|
||||
else if (k == 'glass_opacity') {
|
||||
let n = int(v, 10);
|
||||
if (n >= 0 && n <= 100)
|
||||
writes['glass_opacity'] = sprintf('%d', n);
|
||||
}
|
||||
}
|
||||
|
||||
let cfg_path = '/etc/config/liquid';
|
||||
let cfg = fs.readfile(cfg_path) ?? '';
|
||||
let lines = split(cfg, '\n');
|
||||
let out = [];
|
||||
if (!match(cfg, /config theme/))
|
||||
push(out, "config theme 'theme'");
|
||||
|
||||
let inSection = false;
|
||||
for (let i = 0; i < length(lines); i++) {
|
||||
let line = lines[i];
|
||||
if (match(line, /^config/))
|
||||
inSection = true;
|
||||
let m = match(line, /^[ \t]*option[ \t]+([a-z_]+)[ \t]+/);
|
||||
if (inSection && m !== null && m[1] in writes) {
|
||||
push(out, sprintf("\toption %s '%s'", m[1], writes[m[1]]));
|
||||
delete writes[m[1]];
|
||||
continue;
|
||||
}
|
||||
/* 丢弃纯空行(避免历史累积空行污染) */
|
||||
if (trim(line) == '')
|
||||
continue;
|
||||
push(out, line);
|
||||
}
|
||||
|
||||
/* 追加尚未出现的 option */
|
||||
for (let k in writes)
|
||||
push(out, sprintf("\toption %s '%s'", k, writes[k]));
|
||||
|
||||
fs.writefile(cfg_path, join('\n', out) + '\n');
|
||||
http.write_json({ ok: true, saved: true });
|
||||
},
|
||||
|
||||
/* ── OTA: detect package manager (apk vs opkg),与 pushbot 同款 ── */
|
||||
act_detect_pkgmgr: function() {
|
||||
let mgr = "opkg";
|
||||
let f = popen("command -v apk 2>/dev/null", "r");
|
||||
if (f) { let o = f.read("all"); f.close(); if (o && length(replace(o, /\s+/, "")) > 0) mgr = "apk"; }
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ pkgmgr: mgr });
|
||||
},
|
||||
|
||||
/* ── OTA: 后台下载 Release 包(tag 方案 luci-theme-liquid-vX.Y-rN),与 pushbot 同款重试逻辑 ── */
|
||||
act_ota_download: function() {
|
||||
let ver = http.formvalue("ver") ?? "";
|
||||
let rel = http.formvalue("rel") ?? "";
|
||||
if (ver == "" || rel == "") {
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ ok: false, error: "missing ver/rel" });
|
||||
return;
|
||||
}
|
||||
/* 防注入:只放行数字与点 */
|
||||
ver = replace(ver, /[^0-9.]/g, "");
|
||||
rel = replace(rel, /[^0-9]/g, "");
|
||||
if (ver == "" || rel == "") {
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ ok: false, error: "invalid ver/rel" });
|
||||
return;
|
||||
}
|
||||
|
||||
/* 检测包管理器 */
|
||||
let mgr = "opkg";
|
||||
let f0 = popen("command -v apk 2>/dev/null", "r");
|
||||
if (f0) { let o = f0.read("all"); f0.close(); if (o && length(replace(o, /\s+/, "")) > 0) mgr = "apk"; }
|
||||
|
||||
/* 组装下载地址(主题无 po/i18n 子包,单文件) */
|
||||
let base = "https://github.com/zzsj0928/luci-theme-liquid/releases/download/luci-theme-liquid-v" + ver + "-r" + rel + "/";
|
||||
let files;
|
||||
if (mgr == "apk") {
|
||||
files = ["luci-theme-liquid-" + ver + "-r" + rel + ".apk"];
|
||||
} else {
|
||||
files = ["luci-theme-liquid_" + ver + "-r" + rel + "_all.ipk"];
|
||||
}
|
||||
|
||||
/* 进度文件 */
|
||||
let pfile = "/tmp/liquid/ota_progress";
|
||||
system("mkdir -p /tmp/liquid && echo '0' > " + pfile + " 2>/dev/null");
|
||||
|
||||
/* 后台下载脚本:每个文件最多重试 3 次,>10KB 视为有效 */
|
||||
let dl_script = "#!/bin/sh\n"
|
||||
+ "PFILE='" + pfile + "'\n"
|
||||
+ "BASE='" + base + "'\n"
|
||||
+ "MAX_RETRY=3\n"
|
||||
+ "TOTAL=" + length(files) + "\n"
|
||||
+ "OK=0\n"
|
||||
+ "for f in " + join(" ", files) + "; do\n"
|
||||
+ " URL=\"${BASE}${f}\"\n"
|
||||
+ " DEST=\"/tmp/${f}\"\n"
|
||||
+ " ATTEMPT=0\n"
|
||||
+ " while [ $ATTEMPT -lt $MAX_RETRY ]; do\n"
|
||||
+ " ATTEMPT=$((ATTEMPT+1))\n"
|
||||
+ " curl -k -Lf --connect-timeout 15 --max-time 120 -o \"${DEST}\" \"${URL}\" 2>/dev/null\n"
|
||||
+ " if [ $? -eq 0 ] && [ -s \"${DEST}\" ] && [ $(wc -c < \"${DEST}\") -gt 10000 ]; then\n"
|
||||
+ " OK=$((OK+1))\n"
|
||||
+ " echo \"$((OK * 100 / TOTAL))\" > \"${PFILE}\"\n"
|
||||
+ " [ $OK -lt $TOTAL ] && sleep 1\n"
|
||||
+ " break\n"
|
||||
+ " fi\n"
|
||||
+ " rm -f \"${DEST}\"\n"
|
||||
+ " sleep 2\n"
|
||||
+ " done\n"
|
||||
+ "done\n"
|
||||
+ "if [ $OK -eq $TOTAL ]; then\n"
|
||||
+ " sleep 1\n"
|
||||
+ " echo 'done' > \"${PFILE}\"\n"
|
||||
+ "else\n"
|
||||
+ " echo 'fail' > \"${PFILE}\"\n"
|
||||
+ "fi\n";
|
||||
|
||||
fs.writefile("/tmp/liquid/ota_download.sh", dl_script);
|
||||
system("chmod +x /tmp/liquid/ota_download.sh && /tmp/liquid/ota_download.sh &");
|
||||
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ ok: true });
|
||||
},
|
||||
|
||||
/* ── OTA: 轮询下载进度(0-100 / done / fail) ── */
|
||||
act_ota_download_progress: function() {
|
||||
let pfile = "/tmp/liquid/ota_progress";
|
||||
let progress = "0";
|
||||
let f = popen("cat " + pfile + " 2>/dev/null || echo '0'", "r");
|
||||
if (f) { progress = replace(f.read("all"), /\s+/, ""); f.close(); }
|
||||
if (progress == "") progress = "0";
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ progress: progress });
|
||||
},
|
||||
|
||||
/* ── OTA: 安装已下载的包(apk/opkg 双支持) ── */
|
||||
act_ota_install: function() {
|
||||
let mgr = "opkg";
|
||||
let f0 = popen("command -v apk 2>/dev/null", "r");
|
||||
if (f0) { let o = f0.read("all"); f0.close(); if (o && length(replace(o, /\s+/, "")) > 0) mgr = "apk"; }
|
||||
|
||||
let ifile = "/tmp/liquid/ota_install.log";
|
||||
let trust = "";
|
||||
let cmd;
|
||||
if (mgr == "apk") {
|
||||
/* 装前检测 Zed 自签公钥:无则写入(添加不覆盖),随后安装
|
||||
免 --allow-untrusted(老设备/新用户首次走 OTA 时设备上还没
|
||||
有公钥,靠这步建立信任;包内也带同一公钥
|
||||
root/etc/apk/keys/zed-openwrt-apk.pem,装完即常驻信任列表,
|
||||
仅装本主题的用户同样获得信任)。 */
|
||||
trust = "[ -f /etc/apk/keys/zed-openwrt-apk.pem ] || { mkdir -p /etc/apk/keys; "
|
||||
+ "printf '%s" + "\\n" + "' '-----BEGIN PUBLIC KEY-----' "
|
||||
+ "'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE16+nzzY9Lx5wvzZoWs/18vZxsNZD' "
|
||||
+ "'jv+CqECJLUj+fA7J228Iu13DVUO8CK9jQyLHtqkw0f4/X2bKLlLiz281zQ==' "
|
||||
+ "'-----END PUBLIC KEY-----' > /etc/apk/keys/zed-openwrt-apk.pem; }; ";
|
||||
cmd = "apk add /tmp/luci-theme-liquid-*.apk";
|
||||
} else {
|
||||
/* opkg 同版本会 up to date 跳过,需 --force-reinstall 覆盖 */
|
||||
cmd = "opkg install --force-reinstall /tmp/luci-theme-liquid_*.ipk";
|
||||
}
|
||||
/* 后台安装 + 结果标记(postinst 自动清 luci 缓存并 reload rpcd)。
|
||||
world 哈希锁清理【装前 + 装后各一次】:
|
||||
- 装前:即使安装链中途被杀(重启/OOM),锁也已经清掉,
|
||||
不会留下"中毒 world 卡死后续所有 apk 事务"的状态
|
||||
- 装后:本次安装(本地文件)自己又会写一把新锁,再清一次
|
||||
降级为裸包名(语义等价"保持安装",且这些包不在官方源、
|
||||
无被替换风险)。
|
||||
整链必须放进单个 ( ... ) & 后台执行:否则 system() 会同步
|
||||
等安装结束,阻塞 rpcd 处理器(全站请求卡住)。 */
|
||||
let heal = "[ -f /etc/apk/world ] && sed -i '/></ s/>.*$//' /etc/apk/world; ";
|
||||
let install_cmd = "( "
|
||||
+ trust
|
||||
+ heal
|
||||
+ cmd + " > " + ifile + " 2>&1; "
|
||||
+ "RC=$?; "
|
||||
+ heal
|
||||
+ "if [ $RC -eq 0 ]; then echo 'ok' >> " + ifile + "; "
|
||||
+ "else echo 'fail' >> " + ifile + "; fi ) &";
|
||||
system("mkdir -p /tmp/liquid && " + install_cmd);
|
||||
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ ok: true, pkgmgr: mgr });
|
||||
},
|
||||
|
||||
/* ── OTA: 清理已下载的包与进度文件 ── */
|
||||
act_ota_clear: function() {
|
||||
let patterns = [
|
||||
"/tmp/luci-theme-liquid-*.apk",
|
||||
"/tmp/luci-theme-liquid_*.ipk",
|
||||
"/tmp/liquid/ota_progress",
|
||||
"/tmp/liquid/ota_install.log",
|
||||
"/tmp/liquid/ota_download.sh"
|
||||
];
|
||||
system("rm -f " + join(" ", patterns) + " 2>/dev/null");
|
||||
http.prepare_content("application/json");
|
||||
http.write_json({ ok: true });
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user