mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 00:55:13 +08:00
🤞 Sync 2026-09-27 22:16:29
This commit is contained in:
1 parent
8ede4a8320
commit
618f51e9dc
459 files changed
+44817
-2191
No files matched your search
+181
@@ -0,0 +1,181 @@
|
||||
#!/bin/sh
|
||||
# Download and install Dashboard for HONK
|
||||
|
||||
TARGET_DIR="${1:-/etc/honk/dashboard}"
|
||||
DOWNLOAD_URL="${2:-https://github.com/Zephyruso/zashboard/releases/latest/download/dist-no-fonts.zip}"
|
||||
|
||||
LOG_FILE="/tmp/honk_dashboard_download.log"
|
||||
STATUS_FILE="/tmp/honk_dashboard_download.status"
|
||||
TMP_DIR="/tmp/dashboard_dl_$$"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
set_status() {
|
||||
echo "$1" > "$STATUS_FILE"
|
||||
log "STATUS -> $1"
|
||||
}
|
||||
|
||||
mkdir -p /tmp
|
||||
: > "$LOG_FILE"
|
||||
set_status "DOWNLOADING"
|
||||
|
||||
log "Starting Dashboard deployment to: $TARGET_DIR"
|
||||
log "Download URL: $DOWNLOAD_URL"
|
||||
|
||||
download_file() {
|
||||
local url="$1" output="$2"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -s -S -k -L -f --connect-timeout 15 --max-time 180 -o "$output" "$url" >> "$LOG_FILE" 2>&1
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget --no-check-certificate -q -T 15 -t 3 -O "$output" "$url" >> "$LOG_FILE" 2>&1
|
||||
elif command -v uclient-fetch >/dev/null 2>&1; then
|
||||
uclient-fetch --no-check-certificate -q --timeout 15 -O "$output" "$url" >> "$LOG_FILE" 2>&1
|
||||
else
|
||||
log "Error: No download tool found (curl, wget, or uclient-fetch)!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_text() {
|
||||
local url="$1"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -s -k -L -f --connect-timeout 8 --max-time 15 "$url" 2>/dev/null
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget --no-check-certificate -q -T 8 -t 1 -O - "$url" 2>/dev/null
|
||||
elif command -v uclient-fetch >/dev/null 2>&1; then
|
||||
uclient-fetch --no-check-certificate -q --timeout 8 -O - "$url" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
case "$DOWNLOAD_URL" in
|
||||
*github.com/Zakkaus/doona*)
|
||||
PREFIX="${DOWNLOAD_URL%%https://github.com/*}"
|
||||
DEFAULT_TAG="v0.1.0-beta.3"
|
||||
FALLBACK_URL="${PREFIX}https://github.com/Zakkaus/doona/releases/download/${DEFAULT_TAG}/doona-${DEFAULT_TAG}.tar.gz"
|
||||
log "Checking for latest Doona release online..."
|
||||
|
||||
RESOLVED_URL=""
|
||||
RELEASES_HTML=$(fetch_text "https://github.com/Zakkaus/doona/releases")
|
||||
LATEST_TAG=$(printf "%s" "$RELEASES_HTML" | grep -o 'releases/tag/[^"/]*' | head -n 1 | sed 's|releases/tag/||')
|
||||
|
||||
if [ -n "$LATEST_TAG" ]; then
|
||||
log "Detected latest release tag: $LATEST_TAG"
|
||||
ASSETS_HTML=$(fetch_text "https://github.com/Zakkaus/doona/releases/expanded_assets/$LATEST_TAG")
|
||||
ASSET_PATH=$(printf "%s" "$ASSETS_HTML" | grep -o '/Zakkaus/doona/releases/download/[^"]*\.tar\.gz' | grep -v 'fonts' | head -n 1)
|
||||
if [ -n "$ASSET_PATH" ]; then
|
||||
RESOLVED_URL="https://github.com${ASSET_PATH}"
|
||||
log "Found release package: $ASSET_PATH"
|
||||
else
|
||||
RESOLVED_URL="https://github.com/Zakkaus/doona/releases/download/${LATEST_TAG}/doona-${LATEST_TAG}.tar.gz"
|
||||
log "Standard release package URL inferred: $RESOLVED_URL"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$RESOLVED_URL" ]; then
|
||||
DOWNLOAD_URL="${PREFIX}${RESOLVED_URL}"
|
||||
log "Adaptive resolution succeeded: $DOWNLOAD_URL"
|
||||
else
|
||||
log "Warning: Online tag resolution unavailable. Falling back to verified release ($DEFAULT_TAG)."
|
||||
DOWNLOAD_URL="$FALLBACK_URL"
|
||||
log "Fallback download URL: $DOWNLOAD_URL"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Downloading package..."
|
||||
|
||||
rm -rf "$TMP_DIR"
|
||||
mkdir -p "$TMP_DIR"
|
||||
ARCHIVE_FILE="$TMP_DIR/package.tmp"
|
||||
|
||||
|
||||
|
||||
if ! download_file "$DOWNLOAD_URL" "$ARCHIVE_FILE" || [ ! -s "$ARCHIVE_FILE" ]; then
|
||||
ALT_URL=""
|
||||
case "$DOWNLOAD_URL" in
|
||||
*doona-v*)
|
||||
ALT_URL=$(echo "$DOWNLOAD_URL" | sed 's/doona-v/doona-/')
|
||||
;;
|
||||
*doona-[0-9]*)
|
||||
ALT_URL=$(echo "$DOWNLOAD_URL" | sed 's/doona-/doona-v/')
|
||||
;;
|
||||
esac
|
||||
if [ -n "$ALT_URL" ] && [ "$ALT_URL" != "$DOWNLOAD_URL" ]; then
|
||||
log "Retrying with alternative naming format: $ALT_URL"
|
||||
download_file "$ALT_URL" "$ARCHIVE_FILE" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -s "$ARCHIVE_FILE" ]; then
|
||||
log "Error: Download failed or file is empty."
|
||||
set_status "FAILED"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Download completed successfully. Extracting archive..."
|
||||
set_status "EXTRACTING"
|
||||
EXTRACT_DIR="$TMP_DIR/extracted"
|
||||
mkdir -p "$EXTRACT_DIR"
|
||||
|
||||
if case "$DOWNLOAD_URL" in *.tar.gz|*.tgz) true ;; *) false ;; esac; then
|
||||
if ! command -v tar >/dev/null 2>&1; then
|
||||
log "Error: 'tar' command not found!"
|
||||
set_status "FAILED"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if ! tar -xzf "$ARCHIVE_FILE" -C "$EXTRACT_DIR" >> "$LOG_FILE" 2>&1; then
|
||||
log "Error: Failed to extract tar.gz archive."
|
||||
set_status "FAILED"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! command -v unzip >/dev/null 2>&1; then
|
||||
log "Error: 'unzip' command not found! Please run 'opkg update && opkg install unzip'."
|
||||
set_status "FAILED"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if ! unzip -o -q "$ARCHIVE_FILE" -d "$EXTRACT_DIR" >> "$LOG_FILE" 2>&1; then
|
||||
log "Error: Failed to unzip archive."
|
||||
set_status "FAILED"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
DEPLOY_SRC="$EXTRACT_DIR"
|
||||
if [ ! -f "$DEPLOY_SRC/index.html" ] && [ -f "$EXTRACT_DIR/dist/index.html" ]; then
|
||||
DEPLOY_SRC="$EXTRACT_DIR/dist"
|
||||
fi
|
||||
|
||||
if [ ! -f "$DEPLOY_SRC/index.html" ]; then
|
||||
log "Error: index.html not found in extracted package."
|
||||
set_status "FAILED"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Deploying new files to $TARGET_DIR..."
|
||||
mkdir -p "$TARGET_DIR"
|
||||
if [ -n "$TARGET_DIR" ] && [ "$TARGET_DIR" != "/" ] && [ "$TARGET_DIR" != "/etc" ] && [ "$TARGET_DIR" != "/tmp" ]; then
|
||||
rm -rf "${TARGET_DIR:?}"/* "${TARGET_DIR:?}"/.[!.]* 2>/dev/null || true
|
||||
fi
|
||||
cp -rf "$DEPLOY_SRC/"* "$TARGET_DIR/"
|
||||
chmod -R 755 "$TARGET_DIR"
|
||||
|
||||
if [ -f "$TARGET_DIR/index.html" ]; then
|
||||
log "Installation completed successfully!"
|
||||
set_status "SUCCESS"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 0
|
||||
else
|
||||
log "Error: Failed to deploy files to $TARGET_DIR."
|
||||
set_status "FAILED"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"admin/services/honk": {
|
||||
"title": "HONK",
|
||||
"order": -1,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-honk" ],
|
||||
"uci": { "honk": true }
|
||||
}
|
||||
},
|
||||
"admin/services/honk/global": {
|
||||
"title": "Global Settings",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "honk/global"
|
||||
}
|
||||
},
|
||||
"admin/services/honk/dns": {
|
||||
"title": "DNS Settings",
|
||||
"order": 2,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "honk/dns"
|
||||
}
|
||||
},
|
||||
"admin/services/honk/node": {
|
||||
"title": "Node Settings",
|
||||
"order": 3,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "honk/node"
|
||||
}
|
||||
},
|
||||
"admin/services/honk/route": {
|
||||
"title": "Routing Settings",
|
||||
"order": 4,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "honk/route"
|
||||
}
|
||||
},
|
||||
"admin/services/honk/dashboard": {
|
||||
"title": "Dashboard",
|
||||
"order": 5,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "honk/dashboard"
|
||||
}
|
||||
},
|
||||
"admin/services/honk/log": {
|
||||
"title": "Logs",
|
||||
"order": 6,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "honk/log"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"luci-app-honk": {
|
||||
"description": "Grant access for luci-app-honk",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.honk": [ "*" ]
|
||||
},
|
||||
"uci": [ "honk" ],
|
||||
"file": [
|
||||
"/etc/honk/config.dae",
|
||||
"/etc/honk/config.d/dns.dae",
|
||||
"/etc/honk/config.d/node.dae",
|
||||
"/etc/honk/config.d/route.dae",
|
||||
"/etc/honk/dashboard/*",
|
||||
"/etc/honk/zashboard/*"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.honk": [ "*" ]
|
||||
},
|
||||
"uci": [ "honk" ],
|
||||
"file": [
|
||||
"/etc/honk/config.dae",
|
||||
"/etc/honk/config.d/dns.dae",
|
||||
"/etc/honk/config.d/node.dae",
|
||||
"/etc/honk/config.d/route.dae",
|
||||
"/etc/honk/dashboard/*",
|
||||
"/etc/honk/zashboard/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
#!/usr/bin/ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { readfile, writefile, popen, stat } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
|
||||
function strip_dae_comments(content) {
|
||||
if (!content) return "";
|
||||
let lines = split(content, /[\r\n]+/);
|
||||
let clean_lines = [];
|
||||
for (let idx, line in lines) {
|
||||
let in_single = false;
|
||||
let in_double = false;
|
||||
let clean = "";
|
||||
let len = length(line);
|
||||
for (let i = 0; i < len; i++) {
|
||||
let c = substr(line, i, 1);
|
||||
let next_c = (i + 1 < len) ? substr(line, i + 1, 1) : "";
|
||||
if (c == "'" && !in_double) {
|
||||
in_single = !in_single;
|
||||
clean += c;
|
||||
} else if (c == '"' && !in_single) {
|
||||
in_double = !in_double;
|
||||
clean += c;
|
||||
} else if (!in_single && !in_double) {
|
||||
if (c == '#' || (c == '/' && next_c == '/')) {
|
||||
break;
|
||||
} else {
|
||||
clean += c;
|
||||
}
|
||||
} else {
|
||||
clean += c;
|
||||
}
|
||||
}
|
||||
if (match(clean, /\S/)) {
|
||||
push(clean_lines, clean);
|
||||
}
|
||||
}
|
||||
return join("\n", clean_lines);
|
||||
}
|
||||
|
||||
function parse_clash_api(clean_content) {
|
||||
let api_m = match(clean_content, /clash_api\s*\{([^}]+)\}/);
|
||||
if (!api_m) return null;
|
||||
let block = api_m[1];
|
||||
|
||||
let ec_m = match(block, /external_controller\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
|
||||
let ui_m = match(block, /external_ui\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
|
||||
let sec_m = match(block, /secret\s*:\s*['"]?([^'" \t\r\n]*)['"]?/);
|
||||
let dm_m = match(block, /default_mode\s*:\s*['"]?([^'" \t\r\n]*)['"]?/);
|
||||
|
||||
let res = {
|
||||
external_controller: ec_m ? ec_m[1] : "",
|
||||
external_ui: ui_m ? ui_m[1] : "",
|
||||
secret: sec_m ? sec_m[1] : "",
|
||||
default_mode: dm_m ? dm_m[1] : "Rule",
|
||||
host: "",
|
||||
port: ""
|
||||
};
|
||||
|
||||
let ec = res.external_controller;
|
||||
if (ec) {
|
||||
let m_v6 = match(ec, /^\[([^\]]+)\]:([0-9]+)$/);
|
||||
let m_v4 = match(ec, /^([^:]+):([0-9]+)$/);
|
||||
let m_p = match(ec, /^:([0-9]+)$/);
|
||||
if (m_v6) {
|
||||
res.host = m_v6[1];
|
||||
res.port = m_v6[2];
|
||||
} else if (m_v4) {
|
||||
res.host = m_v4[1];
|
||||
res.port = m_v4[2];
|
||||
} else if (m_p) {
|
||||
res.host = "0.0.0.0";
|
||||
res.port = m_p[1];
|
||||
} else if (match(ec, /^[0-9]+$/)) {
|
||||
res.host = "0.0.0.0";
|
||||
res.port = ec;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function parse_native_api(clean_content) {
|
||||
let api_m = match(clean_content, /native_api\s*\{([^}]+)\}/);
|
||||
if (!api_m) return null;
|
||||
let block = api_m[1];
|
||||
|
||||
let listen_m = match(block, /listen\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
|
||||
let ui_m = match(block, /ui\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
|
||||
let sec_m = match(block, /secret\s*:\s*['"]?([^'" \t\r\n]*)['"]?/);
|
||||
let en_m = match(block, /enabled\s*:\s*['"]?(true|false)['"]?/);
|
||||
|
||||
let res = {
|
||||
enabled: en_m ? (en_m[1] == "true") : true,
|
||||
listen: listen_m ? listen_m[1] : "",
|
||||
ui: ui_m ? ui_m[1] : "",
|
||||
secret: sec_m ? sec_m[1] : "",
|
||||
host: "",
|
||||
port: ""
|
||||
};
|
||||
|
||||
let listen = res.listen;
|
||||
if (listen) {
|
||||
let m_v6 = match(listen, /^\[([^\]]+)\]:([0-9]+)$/);
|
||||
let m_v4 = match(listen, /^([^:]+):([0-9]+)$/);
|
||||
let m_p = match(listen, /^:([0-9]+)$/);
|
||||
if (m_v6) {
|
||||
res.host = m_v6[1];
|
||||
res.port = m_v6[2];
|
||||
} else if (m_v4) {
|
||||
res.host = m_v4[1];
|
||||
res.port = m_v4[2];
|
||||
} else if (m_p) {
|
||||
res.host = "0.0.0.0";
|
||||
res.port = m_p[1];
|
||||
} else if (match(listen, /^[0-9]+$/)) {
|
||||
res.host = "0.0.0.0";
|
||||
res.port = listen;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function get_config_file_path() {
|
||||
let u = cursor();
|
||||
let p = u ? u.get("honk", "config", "config_file") : null;
|
||||
return p || "/etc/honk/config.dae";
|
||||
}
|
||||
|
||||
function remove_bracket_block(content, header_regex) {
|
||||
let lines = split(content, "\n");
|
||||
let new_lines = [];
|
||||
let in_block = false;
|
||||
let depth = 0;
|
||||
|
||||
for (let idx, line in lines) {
|
||||
let trimmed = trim(line);
|
||||
if (!in_block && match(trimmed, header_regex)) {
|
||||
in_block = true;
|
||||
depth = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_block) {
|
||||
if (match(trimmed, /\{/)) depth++;
|
||||
if (match(trimmed, /\}/)) depth--;
|
||||
if (depth <= 0) {
|
||||
in_block = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
push(new_lines, line);
|
||||
}
|
||||
return join("\n", new_lines);
|
||||
}
|
||||
|
||||
function get_dashboard_info(req) {
|
||||
let u = cursor();
|
||||
let requested_type = (req && req.args) ? req.args.type : null;
|
||||
if (!requested_type && u) {
|
||||
requested_type = u.get("honk", "config", "dashboard");
|
||||
if (!requested_type) {
|
||||
u.load("honk");
|
||||
u.foreach("honk", "honk", function(s) {
|
||||
if (s.dashboard) requested_type = s.dashboard;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!requested_type) requested_type = "doona";
|
||||
|
||||
let config_file = get_config_file_path();
|
||||
let content = readfile(config_file) || "";
|
||||
let clean = strip_dae_comments(content);
|
||||
let parsed_clash = parse_clash_api(clean);
|
||||
let parsed_native = parse_native_api(clean);
|
||||
|
||||
let p = popen("pidof honk-core 2>/dev/null");
|
||||
let pids = p ? p.read("all") : "";
|
||||
if (p) p.close();
|
||||
let running = !!match(pids, /[0-9]+/);
|
||||
|
||||
let res = {
|
||||
dashboard_type: requested_type,
|
||||
configured: false,
|
||||
has_ui: false,
|
||||
running: running,
|
||||
config_file: config_file,
|
||||
external_controller: "",
|
||||
host: "",
|
||||
port: "",
|
||||
secret: "",
|
||||
external_ui: "",
|
||||
default_mode: "Rule",
|
||||
has_clash_api: !!parsed_clash,
|
||||
has_native_api: !!(parsed_native && parsed_native.enabled)
|
||||
};
|
||||
|
||||
if (requested_type == "doona") {
|
||||
if (!parsed_native || !parsed_native.enabled) {
|
||||
return res;
|
||||
}
|
||||
res.configured = true;
|
||||
res.external_controller = parsed_native.listen;
|
||||
res.host = parsed_native.host;
|
||||
res.port = parsed_native.port || "9527";
|
||||
res.secret = parsed_native.secret;
|
||||
res.external_ui = parsed_native.ui;
|
||||
} else {
|
||||
if (!parsed_clash) {
|
||||
return res;
|
||||
}
|
||||
res.configured = true;
|
||||
res.external_controller = parsed_clash.external_controller;
|
||||
res.host = parsed_clash.host;
|
||||
res.port = parsed_clash.port || "9090";
|
||||
res.secret = parsed_clash.secret;
|
||||
res.external_ui = parsed_clash.external_ui;
|
||||
res.default_mode = parsed_clash.default_mode;
|
||||
}
|
||||
|
||||
if (res.external_ui) {
|
||||
let index_path = res.external_ui + "/index.html";
|
||||
let s = stat(index_path);
|
||||
if (s && s.type == "file") {
|
||||
res.has_ui = true;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
function download_dashboard(req) {
|
||||
let requested_type = req.args ? req.args.type : null;
|
||||
let url = req.args ? req.args.url : null;
|
||||
if (!url) {
|
||||
if (requested_type == "doona") {
|
||||
url = "https://github.com/Zakkaus/doona/releases/download/v0.1.0-beta.3/doona-v0.1.0-beta.3.tar.gz";
|
||||
} else {
|
||||
url = "https://github.com/Zephyruso/zashboard/releases/latest/download/dist-no-fonts.zip";
|
||||
}
|
||||
}
|
||||
|
||||
if (!match(url, /^https?:\/\//) || match(url, /[ \t\r\n'"`]/)) {
|
||||
return { success: false, message: "Invalid URL" };
|
||||
}
|
||||
|
||||
let config_file = get_config_file_path();
|
||||
let content = readfile(config_file) || "";
|
||||
let clean = strip_dae_comments(content);
|
||||
let parsed_clash = parse_clash_api(clean);
|
||||
let parsed_native = parse_native_api(clean);
|
||||
|
||||
let target_dir;
|
||||
if (requested_type == "doona") {
|
||||
target_dir = (parsed_native && parsed_native.ui) ? parsed_native.ui : "/etc/honk/dashboard";
|
||||
} else if (requested_type == "zashboard") {
|
||||
target_dir = (parsed_clash && parsed_clash.external_ui) ? parsed_clash.external_ui : "/etc/honk/zashboard";
|
||||
} else {
|
||||
if (parsed_native && parsed_native.ui) {
|
||||
target_dir = parsed_native.ui;
|
||||
} else if (parsed_clash && parsed_clash.external_ui) {
|
||||
target_dir = parsed_clash.external_ui;
|
||||
} else {
|
||||
target_dir = "/etc/honk/dashboard";
|
||||
}
|
||||
}
|
||||
|
||||
let script = "/usr/share/honk/download_dashboard.sh";
|
||||
let s = stat(script);
|
||||
if (!s) {
|
||||
return { success: false, message: "Script not found: " + script };
|
||||
}
|
||||
|
||||
let safe_script = replace(script, "'", "'\\''");
|
||||
let safe_target = replace(target_dir, "'", "'\\''");
|
||||
let safe_url = replace(url, "'", "'\\''");
|
||||
|
||||
let cmd = sprintf("/bin/sh '%s' '%s' '%s' >/dev/null 2>&1 &", safe_script, safe_target, safe_url);
|
||||
system(cmd);
|
||||
|
||||
return { success: true, target_dir: target_dir, url: url };
|
||||
}
|
||||
|
||||
function switch_dashboard_api(target_type) {
|
||||
let config_file = get_config_file_path();
|
||||
let content = readfile(config_file);
|
||||
if (!content) {
|
||||
return { success: false, message: "Config file not found: " + config_file };
|
||||
}
|
||||
|
||||
let clean = strip_dae_comments(content);
|
||||
let parsed_clash = parse_clash_api(clean);
|
||||
let parsed_native = parse_native_api(clean);
|
||||
|
||||
if (target_type == "doona") {
|
||||
if (parsed_native && parsed_native.enabled && parsed_native.secret == "honk" && !parsed_clash) {
|
||||
return { success: true, type: target_type, noop: true };
|
||||
}
|
||||
} else {
|
||||
if (parsed_clash && (!parsed_native || !parsed_native.enabled)) {
|
||||
return { success: true, type: target_type, noop: true };
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Remove both blocks to guarantee mutual exclusivity
|
||||
let cleaned = remove_bracket_block(content, /^[#\/]*\s*clash_api\s*\{/);
|
||||
cleaned = remove_bracket_block(cleaned, /^[#\/]*\s*native_api\s*\{/);
|
||||
cleaned = replace(cleaned, /experimental\s*\{\s*\}/, "experimental {\n}");
|
||||
|
||||
// 2. Select block according to target_type
|
||||
let api_inner;
|
||||
if (target_type == "doona") {
|
||||
api_inner =
|
||||
" native_api {\n" +
|
||||
" enabled: true\n" +
|
||||
" listen: '0.0.0.0:9527'\n" +
|
||||
" secret: 'honk'\n" +
|
||||
" ui: '/etc/honk/dashboard'\n" +
|
||||
" }\n";
|
||||
} else {
|
||||
api_inner =
|
||||
" clash_api {\n" +
|
||||
" external_controller: '0.0.0.0:9090'\n" +
|
||||
" external_ui: '/etc/honk/zashboard'\n" +
|
||||
" secret: ''\n" +
|
||||
" default_mode: 'Rule'\n" +
|
||||
" }\n";
|
||||
}
|
||||
|
||||
let default_block = "experimental {\n" + api_inner + "}\n";
|
||||
|
||||
let has_active_exp = match(cleaned, /(^|\n)[ \t]*experimental\s*\{/);
|
||||
let new_content;
|
||||
if (has_active_exp) {
|
||||
new_content = replace(cleaned, /(experimental\s*\{[^\n]*\n?)/, "$1" + api_inner);
|
||||
} else {
|
||||
cleaned = remove_bracket_block(cleaned, /^[#\/]+\s*experimental\s*\{/);
|
||||
new_content = rtrim(cleaned, "\r\n\t ") + "\n\n" + default_block;
|
||||
}
|
||||
|
||||
writefile(config_file, new_content);
|
||||
system("/etc/init.d/honk restart >/dev/null 2>&1 &");
|
||||
|
||||
return { success: true, type: target_type };
|
||||
}
|
||||
|
||||
return {
|
||||
"luci.honk": {
|
||||
status: {
|
||||
call: function(req) {
|
||||
let p = popen("pidof honk-core 2>/dev/null");
|
||||
let pids = p ? p.read("all") : "";
|
||||
if (p) p.close();
|
||||
let m = match(pids, /([0-9]+)/);
|
||||
let pid = m ? m[1] : null;
|
||||
let running = !!pid;
|
||||
let memory = null;
|
||||
if (running) {
|
||||
let status_str = readfile("/proc/" + pid + "/status");
|
||||
if (status_str) {
|
||||
let rss_m = match(status_str, /VmRSS:\s+([0-9]+)\s+kB/);
|
||||
if (rss_m) {
|
||||
memory = sprintf("%.1f MB", +rss_m[1] / 1024);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { running: running, memory: memory };
|
||||
}
|
||||
},
|
||||
|
||||
reload: {
|
||||
call: function(req) {
|
||||
system("/etc/init.d/honk hot_reload >/dev/null 2>&1 &");
|
||||
return { success: true };
|
||||
}
|
||||
},
|
||||
|
||||
get_log: {
|
||||
call: function(req) {
|
||||
let p = popen("tail -n 1000 /var/log/honk/honk.log 2>/dev/null");
|
||||
let content = p ? p.read("all") : "";
|
||||
if (p) p.close();
|
||||
return { log: content || "" };
|
||||
}
|
||||
},
|
||||
|
||||
clear_log: {
|
||||
call: function(req) {
|
||||
system("true > /var/log/honk/honk.log");
|
||||
return { success: true };
|
||||
}
|
||||
},
|
||||
|
||||
get_dashboard_info: {
|
||||
args: { type: "string" },
|
||||
call: get_dashboard_info
|
||||
},
|
||||
|
||||
get_zashboard_info: {
|
||||
call: get_dashboard_info
|
||||
},
|
||||
|
||||
download_dashboard: {
|
||||
args: { url: "string", type: "string" },
|
||||
call: download_dashboard
|
||||
},
|
||||
|
||||
download_zashboard: {
|
||||
args: { url: "string", type: "string" },
|
||||
call: download_dashboard
|
||||
},
|
||||
|
||||
download_status: {
|
||||
call: function(req) {
|
||||
let status_raw = readfile("/tmp/honk_dashboard_download.status") || "IDLE";
|
||||
let log = readfile("/tmp/honk_dashboard_download.log") || "";
|
||||
let status_m = match(status_raw, /\S+/);
|
||||
let status = status_m ? status_m[0] : "IDLE";
|
||||
return { status: status, log: log };
|
||||
}
|
||||
},
|
||||
|
||||
enable_clash_api: {
|
||||
call: function(req) {
|
||||
return switch_dashboard_api("zashboard");
|
||||
}
|
||||
},
|
||||
|
||||
enable_native_api: {
|
||||
call: function(req) {
|
||||
return switch_dashboard_api("doona");
|
||||
}
|
||||
},
|
||||
|
||||
switch_dashboard_api: {
|
||||
args: { type: "string" },
|
||||
call: function(req) {
|
||||
let t = req.args ? req.args.type : "doona";
|
||||
return switch_dashboard_api(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user