mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-01 00:25:13 +08:00
2070 lines
59 KiB
Bash
Executable File
2070 lines
59 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
set -eu
|
|
|
|
UCI_PACKAGE="oxidns"
|
|
UCI_SECTION="main"
|
|
OXIDNS_BIN="/usr/bin/oxidns"
|
|
WEBUI_DIR="/usr/share/oxidns/webui"
|
|
TARGETS_FILE="${OXIDNS_TARGETS_FILE:-/usr/share/oxidns/targets.json}"
|
|
DEFAULT_CONFIG_PATH="/etc/oxidns/config.yaml"
|
|
DEFAULT_WORKING_DIR="/var/lib/oxidns"
|
|
DEFAULT_SERVICE_NAME="oxidns"
|
|
DEFAULT_CORE_REPOSITORY="svenshi/oxidns"
|
|
DEFAULT_CORE_BUNDLE="full"
|
|
GITHUB_API_BASE="https://api.github.com"
|
|
TMP_ROOT="${TMPDIR:-/tmp}"
|
|
CORE_PROGRESS_DIR="$TMP_ROOT/luci-oxidns"
|
|
CORE_PROGRESS_LOG="$CORE_PROGRESS_DIR/core-progress.log"
|
|
CORE_LOCK_DIR="$CORE_PROGRESS_DIR/core-operation.lock"
|
|
CORE_LOCK_HELD=0
|
|
|
|
CONFIG_PATH="$DEFAULT_CONFIG_PATH"
|
|
WORKING_DIR="$DEFAULT_WORKING_DIR"
|
|
SERVICE_NAME="$DEFAULT_SERVICE_NAME"
|
|
CORE_REPOSITORY="$DEFAULT_CORE_REPOSITORY"
|
|
CORE_BUNDLE="$DEFAULT_CORE_BUNDLE"
|
|
LOG_SYSLOG=1
|
|
DOWNLOAD_PROXY=""
|
|
GITHUB_TOKEN=""
|
|
DOWNLOAD_ERROR=""
|
|
DOWNLOAD_RC=""
|
|
|
|
json_escape_stream() {
|
|
awk 'BEGIN { ORS = "" }
|
|
{
|
|
if (NR > 1)
|
|
printf "\\n";
|
|
for (i = 1; i <= length($0); i++) {
|
|
c = substr($0, i, 1);
|
|
if (c == "\\")
|
|
printf "\\\\";
|
|
else if (c == "\"")
|
|
printf "\\\"";
|
|
else if (c == "\t")
|
|
printf "\\t";
|
|
else if (c == "\r")
|
|
printf "\\r";
|
|
else
|
|
printf "%s", c;
|
|
}
|
|
}'
|
|
}
|
|
|
|
json_string() {
|
|
printf '"'
|
|
printf '%s' "$1" | json_escape_stream
|
|
printf '"'
|
|
}
|
|
|
|
json_file_string() {
|
|
printf '"'
|
|
if [ -r "$1" ]; then
|
|
json_escape_stream < "$1"
|
|
fi
|
|
printf '"'
|
|
}
|
|
|
|
json_bool() {
|
|
if [ "${1:-0}" = "1" ]; then
|
|
printf 'true'
|
|
else
|
|
printf 'false'
|
|
fi
|
|
}
|
|
|
|
json_error() {
|
|
printf '{'
|
|
printf '"ok":false,'
|
|
printf '"code":'
|
|
json_string "$1"
|
|
printf ',"message":'
|
|
json_string "$2"
|
|
printf '}\n'
|
|
}
|
|
|
|
json_detailed_error() {
|
|
CODE="$1"
|
|
MESSAGE="$2"
|
|
STAGE="${3:-}"
|
|
DETAIL="${4:-}"
|
|
|
|
printf '{'
|
|
printf '"ok":false,'
|
|
printf '"code":'
|
|
json_string "$CODE"
|
|
printf ',"message":'
|
|
json_string "$MESSAGE"
|
|
if [ -n "$STAGE" ]; then
|
|
printf ',"stage":'
|
|
json_string "$STAGE"
|
|
fi
|
|
if [ -n "$DETAIL" ]; then
|
|
printf ',"detail":'
|
|
json_string "$DETAIL"
|
|
fi
|
|
printf '}\n'
|
|
}
|
|
|
|
sanitize_progress_text() {
|
|
awk -v token="$GITHUB_TOKEN" -v proxy="$DOWNLOAD_PROXY" '
|
|
function redact(value, secret, out, pos) {
|
|
if (secret == "")
|
|
return value;
|
|
out = "";
|
|
while ((pos = index(value, secret)) > 0) {
|
|
out = out substr(value, 1, pos - 1) "[redacted]";
|
|
value = substr(value, pos + length(secret));
|
|
}
|
|
return out value;
|
|
}
|
|
{
|
|
line = redact(redact($0, token), proxy);
|
|
gsub(/[[:cntrl:]]/, " ", line);
|
|
print line;
|
|
}
|
|
'
|
|
}
|
|
|
|
prepare_core_progress_dir() {
|
|
if [ -L "$CORE_PROGRESS_DIR" ] || { [ -e "$CORE_PROGRESS_DIR" ] && [ ! -d "$CORE_PROGRESS_DIR" ]; }; then
|
|
return 1
|
|
fi
|
|
if [ ! -d "$CORE_PROGRESS_DIR" ]; then
|
|
mkdir "$CORE_PROGRESS_DIR" 2>/dev/null || return 1
|
|
fi
|
|
if command -v chown >/dev/null 2>&1; then
|
|
chown 0:0 "$CORE_PROGRESS_DIR" 2>/dev/null || true
|
|
fi
|
|
chmod 700 "$CORE_PROGRESS_DIR" 2>/dev/null || true
|
|
return 0
|
|
}
|
|
|
|
prepare_core_progress_file() {
|
|
RESET_FILE="${1:-0}"
|
|
prepare_core_progress_dir || return 1
|
|
if [ -L "$CORE_PROGRESS_LOG" ] || { [ -e "$CORE_PROGRESS_LOG" ] && [ ! -f "$CORE_PROGRESS_LOG" ]; }; then
|
|
rm -f "$CORE_PROGRESS_LOG" 2>/dev/null || return 1
|
|
fi
|
|
if [ "$RESET_FILE" = "1" ] || [ ! -e "$CORE_PROGRESS_LOG" ]; then
|
|
: > "$CORE_PROGRESS_LOG" 2>/dev/null || return 1
|
|
fi
|
|
[ ! -L "$CORE_PROGRESS_LOG" ] || return 1
|
|
if command -v chown >/dev/null 2>&1; then
|
|
chown 0:0 "$CORE_PROGRESS_LOG" 2>/dev/null || true
|
|
fi
|
|
chmod 600 "$CORE_PROGRESS_LOG" 2>/dev/null || true
|
|
return 0
|
|
}
|
|
|
|
progress_timestamp() {
|
|
date '+%H:%M:%S' 2>/dev/null || printf '--:--:--'
|
|
}
|
|
|
|
core_progress_log() {
|
|
PROGRESS_KIND="$1"
|
|
PROGRESS_MESSAGE="$2"
|
|
PROGRESS_TEXT="$(printf '%s' "$PROGRESS_MESSAGE" | sanitize_progress_text)"
|
|
[ -n "$PROGRESS_TEXT" ] || PROGRESS_TEXT="-"
|
|
prepare_core_progress_file 0 || return 0
|
|
printf '[%s] %s %s\n' "$(progress_timestamp)" "$PROGRESS_KIND" "$PROGRESS_TEXT" >> "$CORE_PROGRESS_LOG" 2>/dev/null || true
|
|
}
|
|
|
|
core_progress_command() {
|
|
core_progress_log "command" "+ $1"
|
|
}
|
|
|
|
core_progress_reset() {
|
|
prepare_core_progress_file 1 || return 0
|
|
core_progress_log "info" "$1"
|
|
}
|
|
|
|
pid_is_running() {
|
|
PID_VALUE="$1"
|
|
case "$PID_VALUE" in
|
|
''|*[!0-9]*)
|
|
return 1
|
|
;;
|
|
esac
|
|
kill -0 "$PID_VALUE" 2>/dev/null
|
|
}
|
|
|
|
clear_stale_core_lock() {
|
|
[ -d "$CORE_LOCK_DIR" ] && [ ! -L "$CORE_LOCK_DIR" ] || return 1
|
|
LOCK_PID="$(sed -n '1p' "$CORE_LOCK_DIR/pid" 2>/dev/null || true)"
|
|
if pid_is_running "$LOCK_PID"; then
|
|
return 1
|
|
fi
|
|
rm -f "$CORE_LOCK_DIR/pid" 2>/dev/null || return 1
|
|
rmdir "$CORE_LOCK_DIR" 2>/dev/null || return 1
|
|
}
|
|
|
|
acquire_core_lock() {
|
|
prepare_core_progress_dir || {
|
|
json_error "core_lock_failed" "failed to prepare core operation lock directory"
|
|
return 1
|
|
}
|
|
if mkdir "$CORE_LOCK_DIR" 2>/dev/null || { clear_stale_core_lock && mkdir "$CORE_LOCK_DIR" 2>/dev/null; }; then
|
|
CORE_LOCK_HELD=1
|
|
printf '%s\n' "$$" > "$CORE_LOCK_DIR/pid" 2>/dev/null || true
|
|
chmod 700 "$CORE_LOCK_DIR" 2>/dev/null || true
|
|
return 0
|
|
fi
|
|
core_progress_log "error" "another core operation is already running"
|
|
json_error "core_operation_busy" "another OxiDNS core operation is already running"
|
|
return 1
|
|
}
|
|
|
|
release_core_lock() {
|
|
if [ "${CORE_LOCK_HELD:-0}" = "1" ]; then
|
|
rm -f "$CORE_LOCK_DIR/pid" 2>/dev/null || true
|
|
rmdir "$CORE_LOCK_DIR" 2>/dev/null || true
|
|
CORE_LOCK_HELD=0
|
|
fi
|
|
}
|
|
|
|
run_with_core_lock() {
|
|
acquire_core_lock || return 1
|
|
trap 'release_core_lock' EXIT
|
|
trap 'release_core_lock; exit 1' HUP INT TERM
|
|
"$@"
|
|
CORE_ACTION_RC="$?"
|
|
release_core_lock
|
|
trap - EXIT HUP INT TERM
|
|
return "$CORE_ACTION_RC"
|
|
}
|
|
|
|
file_size_bytes() {
|
|
[ -f "$1" ] || {
|
|
printf '0'
|
|
return 0
|
|
}
|
|
wc -c < "$1" 2>/dev/null | awk '{ print $1 + 0 }'
|
|
}
|
|
|
|
emit_core_progress() {
|
|
if ! prepare_core_progress_file 0; then
|
|
printf '{"ok":true,"path":'
|
|
json_string "$CORE_PROGRESS_LOG"
|
|
printf ',"mtime":"","size":"0","text":""}\n'
|
|
return 0
|
|
fi
|
|
printf '{"ok":true,"path":'
|
|
json_string "$CORE_PROGRESS_LOG"
|
|
printf ',"mtime":'
|
|
json_string "$(stat_mtime "$CORE_PROGRESS_LOG")"
|
|
printf ',"size":'
|
|
json_string "$(file_size_bytes "$CORE_PROGRESS_LOG")"
|
|
printf ',"text":'
|
|
json_file_string "$CORE_PROGRESS_LOG"
|
|
printf '}\n'
|
|
}
|
|
|
|
uci_get() {
|
|
KEY="$1"
|
|
DEFAULT="$2"
|
|
if command -v uci >/dev/null 2>&1; then
|
|
uci -q get "$UCI_PACKAGE.$UCI_SECTION.$KEY" 2>/dev/null || printf '%s' "$DEFAULT"
|
|
else
|
|
printf '%s' "$DEFAULT"
|
|
fi
|
|
}
|
|
|
|
load_settings() {
|
|
SERVICE_NAME="$DEFAULT_SERVICE_NAME"
|
|
CONFIG_PATH="$(uci_get config_path "$DEFAULT_CONFIG_PATH")"
|
|
WORKING_DIR="$(uci_get working_dir "$DEFAULT_WORKING_DIR")"
|
|
CORE_REPOSITORY="$(uci_get core_repository "$DEFAULT_CORE_REPOSITORY")"
|
|
CORE_BUNDLE="$(uci_get core_bundle "$DEFAULT_CORE_BUNDLE")"
|
|
LOG_SYSLOG="$(uci_get log_syslog 1)"
|
|
case "$LOG_SYSLOG" in
|
|
0|off|false|no) LOG_SYSLOG=0 ;;
|
|
*) LOG_SYSLOG=1 ;;
|
|
esac
|
|
DOWNLOAD_PROXY="$(uci_get download_proxy '')"
|
|
GITHUB_TOKEN="$(uci_get github_token '')"
|
|
}
|
|
|
|
init_script() {
|
|
printf '/etc/init.d/%s' "$SERVICE_NAME"
|
|
}
|
|
|
|
service_enabled() {
|
|
INIT="$(init_script)"
|
|
[ -x "$INIT" ] && "$INIT" enabled >/dev/null 2>&1
|
|
}
|
|
|
|
service_running() {
|
|
INIT="$(init_script)"
|
|
if [ -x "$INIT" ] && "$INIT" status >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
if command -v pgrep >/dev/null 2>&1 && pgrep -x oxidns >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
yaml_scalar_clean() {
|
|
sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/^"//; s/"$//; s/^'\''//; s/'\''$//'
|
|
}
|
|
|
|
config_api_listen() {
|
|
[ -r "$CONFIG_PATH" ] || return 1
|
|
awk '
|
|
/^[^[:space:]#][^:]*:/ {
|
|
top = $0;
|
|
sub(/:.*/, "", top);
|
|
second = "";
|
|
}
|
|
top == "api" && /^[[:space:]]+http:[[:space:]]*[^[:space:]#]/ {
|
|
sub(/^[[:space:]]+http:[[:space:]]*/, "", $0);
|
|
print $0;
|
|
exit;
|
|
}
|
|
top == "api" && /^[[:space:]]+http:[[:space:]]*$/ {
|
|
second = "http";
|
|
next;
|
|
}
|
|
top == "api" && second == "http" && /^[[:space:]]+listen:[[:space:]]*/ {
|
|
sub(/^[[:space:]]+listen:[[:space:]]*/, "", $0);
|
|
print $0;
|
|
exit;
|
|
}
|
|
' "$CONFIG_PATH" | yaml_scalar_clean
|
|
}
|
|
|
|
webui_url_from_config() {
|
|
LISTEN="$(config_api_listen || true)"
|
|
[ -n "$LISTEN" ] || return 1
|
|
LISTEN="${LISTEN#http://}"
|
|
LISTEN="${LISTEN#https://}"
|
|
case "$LISTEN" in
|
|
*:*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
HOST="${LISTEN%:*}"
|
|
PORT="${LISTEN##*:}"
|
|
case "$HOST" in
|
|
''|'0.0.0.0'|'::'|'[::]')
|
|
HOST="127.0.0.1"
|
|
;;
|
|
esac
|
|
[ -n "$PORT" ] || return 1
|
|
printf 'http://%s:%s/' "$HOST" "$PORT"
|
|
}
|
|
|
|
listen_host_from_config() {
|
|
LISTEN="$(config_api_listen || true)"
|
|
[ -n "$LISTEN" ] || return 1
|
|
LISTEN="${LISTEN#http://}"
|
|
LISTEN="${LISTEN#https://}"
|
|
case "$LISTEN" in
|
|
*:*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
printf '%s' "${LISTEN%:*}"
|
|
}
|
|
|
|
is_loopback_host() {
|
|
case "$1" in
|
|
localhost|127.*|'::1'|'[::1]')
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
is_wildcard_host() {
|
|
case "$1" in
|
|
''|'0.0.0.0'|'::'|'[::]')
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
build_info_json() {
|
|
if [ -x "$OXIDNS_BIN" ]; then
|
|
"$OXIDNS_BIN" build-info 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
json_value_from_string() {
|
|
KEY="$1"
|
|
JSON="$2"
|
|
if command -v jsonfilter >/dev/null 2>&1; then
|
|
printf '%s' "$JSON" | jsonfilter -e "@.$KEY" 2>/dev/null || true
|
|
else
|
|
printf '%s' "$JSON" | sed -n "s/.*\"$KEY\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | sed -n '1p'
|
|
fi
|
|
}
|
|
|
|
detect_rust_target_from_file() {
|
|
TARGET_FILE="$1"
|
|
ARCH="$2"
|
|
[ -r "$TARGET_FILE" ] || return 1
|
|
awk -v arch="$ARCH" '
|
|
function field_value(line, value) {
|
|
value = line;
|
|
sub(/^.*:[[:space:]]*"/, "", value);
|
|
sub(/".*$/, "", value);
|
|
return value;
|
|
}
|
|
/"rust_target"[[:space:]]*:/ {
|
|
target = field_value($0);
|
|
next;
|
|
}
|
|
target != "" && (/"uname_arches"[[:space:]]*:/ || /"aliases"[[:space:]]*:/) {
|
|
line = $0;
|
|
gsub(/[\[\],"]/, " ", line);
|
|
n = split(line, parts, /[[:space:]]+/);
|
|
for (i = 1; i <= n; i++) {
|
|
if (parts[i] == arch) {
|
|
print target;
|
|
found = 1;
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
END { exit found ? 0 : 1 }
|
|
' "$TARGET_FILE"
|
|
}
|
|
|
|
detect_rust_target() {
|
|
ARCH="$(uname -m 2>/dev/null || printf unknown)"
|
|
if detect_rust_target_from_file "$TARGETS_FILE" "$ARCH"; then
|
|
return 0
|
|
fi
|
|
case "$ARCH" in
|
|
x86_64|amd64)
|
|
printf 'x86_64-unknown-linux-musl'
|
|
;;
|
|
aarch64|arm64)
|
|
printf 'aarch64-unknown-linux-musl'
|
|
;;
|
|
i386|i686)
|
|
printf 'i686-unknown-linux-musl'
|
|
;;
|
|
armv7l|armv7)
|
|
printf 'armv7-unknown-linux-musleabihf'
|
|
;;
|
|
armv6l|armv6)
|
|
printf 'arm-unknown-linux-musleabihf'
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
emit_status() {
|
|
load_settings
|
|
ENABLED=0
|
|
RUNNING=0
|
|
CORE_INSTALLED=0
|
|
WEBUI_INSTALLED=0
|
|
WEBUI_LOCAL_ONLY=0
|
|
WEBUI_WILDCARD=0
|
|
BUILD_JSON="$(build_info_json)"
|
|
CORE_VERSION="$(json_value_from_string version "$BUILD_JSON")"
|
|
CORE_BUNDLE_VALUE="$(json_value_from_string bundle "$BUILD_JSON")"
|
|
CORE_TARGET="$(detect_rust_target 2>/dev/null || printf unknown)"
|
|
WEBUI_URL="$(webui_url_from_config 2>/dev/null || true)"
|
|
WEBUI_LISTEN="$(config_api_listen 2>/dev/null || true)"
|
|
WEBUI_HOST="$(listen_host_from_config 2>/dev/null || true)"
|
|
|
|
if [ -x "$OXIDNS_BIN" ]; then CORE_INSTALLED=1; fi
|
|
if [ -d "$WEBUI_DIR" ]; then WEBUI_INSTALLED=1; fi
|
|
if is_loopback_host "$WEBUI_HOST"; then WEBUI_LOCAL_ONLY=1; fi
|
|
if is_wildcard_host "$WEBUI_HOST"; then WEBUI_WILDCARD=1; fi
|
|
if service_enabled; then ENABLED=1; fi
|
|
if service_running; then RUNNING=1; fi
|
|
|
|
printf '{'
|
|
printf '"ok":true,'
|
|
printf '"backend":"rpcd",'
|
|
printf '"core":{"installed":'
|
|
json_bool "$CORE_INSTALLED"
|
|
printf ',"version":'
|
|
json_string "$CORE_VERSION"
|
|
printf ',"bundle":'
|
|
json_string "$CORE_BUNDLE_VALUE"
|
|
printf ',"target":'
|
|
json_string "$CORE_TARGET"
|
|
printf ',"binary_path":'
|
|
json_string "$OXIDNS_BIN"
|
|
printf '}'
|
|
printf ',"service":'
|
|
json_string "$SERVICE_NAME"
|
|
printf ',"config_path":'
|
|
json_string "$CONFIG_PATH"
|
|
printf ',"working_dir":'
|
|
json_string "$WORKING_DIR"
|
|
printf ',"service_status":{"running":'
|
|
json_bool "$RUNNING"
|
|
printf ',"enabled":'
|
|
json_bool "$ENABLED"
|
|
printf ',"init_script":'
|
|
json_string "$(init_script)"
|
|
printf '}'
|
|
printf ',"webui":{"installed":'
|
|
json_bool "$WEBUI_INSTALLED"
|
|
printf ',"url":'
|
|
json_string "$WEBUI_URL"
|
|
printf ',"listen":'
|
|
json_string "$WEBUI_LISTEN"
|
|
printf ',"local_only":'
|
|
json_bool "$WEBUI_LOCAL_ONLY"
|
|
printf ',"wildcard":'
|
|
json_bool "$WEBUI_WILDCARD"
|
|
printf ',"path":'
|
|
json_string "$WEBUI_DIR"
|
|
printf '}'
|
|
printf ',"build":'
|
|
if printf '%s' "$BUILD_JSON" | grep -q '^{'; then
|
|
printf '%s' "$BUILD_JSON"
|
|
else
|
|
printf 'null'
|
|
fi
|
|
printf '}\n'
|
|
}
|
|
|
|
syslog_service_event() {
|
|
command -v logger >/dev/null 2>&1 || return 0
|
|
logger -t luci-app-oxidns "$1" >/dev/null 2>&1 || true
|
|
return 0
|
|
}
|
|
|
|
# procd stop is asynchronous. Starting before the old instance is removed
|
|
# reuses it, and procd's instance update retains its stdout/stderr settings.
|
|
stop_service_and_wait() {
|
|
local init_path="$1"
|
|
local stop_deadline service_json root_type service_type instances_type instances
|
|
if ! command -v ubus >/dev/null 2>&1 || ! command -v jsonfilter >/dev/null 2>&1; then
|
|
printf 'cannot wait for OxiDNS to stop: ubus and jsonfilter are required\n' >&2
|
|
return 1
|
|
fi
|
|
"$init_path" stop || return 1
|
|
stop_deadline=$(($(date +%s) + 10))
|
|
while :; do
|
|
if ! service_json="$(ubus -t 2 call service list '{"name":"oxidns"}' 2>/dev/null)" ||
|
|
! root_type="$(printf '%s' "$service_json" | jsonfilter -t '@' 2>/dev/null)"; then
|
|
printf 'failed to query OxiDNS service state while stopping\n' >&2
|
|
return 1
|
|
fi
|
|
if [ "$root_type" != object ]; then
|
|
printf 'invalid OxiDNS service state while stopping\n' >&2
|
|
return 1
|
|
fi
|
|
service_type="$(printf '%s' "$service_json" | jsonfilter -t '@.oxidns' 2>/dev/null || true)"
|
|
instances_type="$(printf '%s' "$service_json" | jsonfilter -t '@.oxidns.instances' 2>/dev/null || true)"
|
|
case "$service_type:$instances_type" in
|
|
:|object:|object:object) ;;
|
|
*) printf 'invalid OxiDNS service instance state while stopping\n' >&2; return 1 ;;
|
|
esac
|
|
# Missing service/instances and an empty instances object are all stopped.
|
|
# Keep waiting even for running:false while the old instance still exists.
|
|
instances="$(printf '%s' "$service_json" | jsonfilter -t '@.oxidns.instances[*]' 2>/dev/null || true)"
|
|
[ -n "$instances" ] || return 0
|
|
if [ "$(date +%s)" -ge "$stop_deadline" ]; then
|
|
printf 'timed out waiting for the old OxiDNS service instance to be removed\n' >&2
|
|
return 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
}
|
|
|
|
run_init_action() {
|
|
case "$2" in
|
|
restart)
|
|
stop_service_and_wait "$1" && "$1" start
|
|
;;
|
|
stop)
|
|
stop_service_and_wait "$1"
|
|
;;
|
|
*)
|
|
"$1" "$2"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
run_service() {
|
|
load_settings
|
|
ACTION="$1"
|
|
INIT="$(init_script)"
|
|
case "$ACTION" in
|
|
start|restart|enable)
|
|
if [ ! -x "$OXIDNS_BIN" ]; then
|
|
syslog_service_event "service $ACTION ignored: OxiDNS core is not installed"
|
|
json_error "core_not_installed" "Install the OxiDNS core before using service controls"
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
if [ ! -x "$INIT" ]; then
|
|
syslog_service_event "service $ACTION failed: init script not found: $INIT"
|
|
json_error "service_unavailable" "init script not found: $INIT"
|
|
return 1
|
|
fi
|
|
SERVICE_OUT="$(mktemp "$TMP_ROOT/luci-oxidns-service.XXXXXX")"
|
|
if run_init_action "$INIT" "$ACTION" >"$SERVICE_OUT" 2>&1; then
|
|
rm -f "$SERVICE_OUT"
|
|
syslog_service_event "service $ACTION succeeded"
|
|
printf '{"ok":true,"action":'
|
|
json_string "$ACTION"
|
|
printf '}\n'
|
|
else
|
|
MSG="$(read_error_detail_file "$SERVICE_OUT" 2>/dev/null || true)"
|
|
rm -f "$SERVICE_OUT"
|
|
SHORT_MSG="$(printf '%.200s' "${MSG:-service action failed}")"
|
|
syslog_service_event "service $ACTION failed: $SHORT_MSG"
|
|
json_error "service_failed" "${MSG:-service action failed}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
stat_mtime() {
|
|
stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || printf ''
|
|
}
|
|
|
|
emit_config_read() {
|
|
load_settings
|
|
if [ ! -r "$CONFIG_PATH" ]; then
|
|
printf '{"ok":false,"code":"config_not_found","message":"configuration file not found","path":'
|
|
json_string "$CONFIG_PATH"
|
|
printf '}\n'
|
|
return 1
|
|
fi
|
|
printf '{'
|
|
printf '"ok":true,'
|
|
printf '"path":'
|
|
json_string "$CONFIG_PATH"
|
|
printf ',"mtime":'
|
|
json_string "$(stat_mtime "$CONFIG_PATH")"
|
|
printf ',"content":'
|
|
json_file_string "$CONFIG_PATH"
|
|
printf '}\n'
|
|
}
|
|
|
|
safe_single_line_value() {
|
|
printf '%s' "$1" | awk '
|
|
BEGIN { ok = 1; cr = sprintf("%c", 13) }
|
|
NR > 1 { ok = 0 }
|
|
index($0, cr) { ok = 0 }
|
|
END { exit ok ? 0 : 1 }
|
|
'
|
|
}
|
|
|
|
json_field_string() {
|
|
NAME="$1"
|
|
VALUE="$2"
|
|
printf '"%s":' "$NAME"
|
|
json_string "$VALUE"
|
|
}
|
|
|
|
emit_settings_read() {
|
|
load_settings
|
|
TOKEN_SET=0
|
|
PROXY_SET=0
|
|
if [ -n "$GITHUB_TOKEN" ]; then TOKEN_SET=1; fi
|
|
if [ -n "$DOWNLOAD_PROXY" ]; then PROXY_SET=1; fi
|
|
printf '{'
|
|
printf '"ok":true,'
|
|
json_field_string core_repository "$CORE_REPOSITORY"
|
|
printf ','
|
|
json_field_string core_bundle "$CORE_BUNDLE"
|
|
printf ','
|
|
json_field_string config_path "$CONFIG_PATH"
|
|
printf ','
|
|
json_field_string working_dir "$WORKING_DIR"
|
|
printf ',"log_syslog":'
|
|
json_bool "$LOG_SYSLOG"
|
|
printf ','
|
|
json_field_string download_proxy "$DOWNLOAD_PROXY"
|
|
printf ','
|
|
json_field_string github_token ''
|
|
printf ',"download_proxy_set":'
|
|
json_bool "$PROXY_SET"
|
|
printf ',"github_token_set":'
|
|
json_bool "$TOKEN_SET"
|
|
printf '}\n'
|
|
}
|
|
|
|
settings_value() {
|
|
KEY="$1"
|
|
INPUT="$2"
|
|
VALUE="$(input_value "$KEY" "$INPUT")"
|
|
if ! safe_single_line_value "$VALUE"; then
|
|
return 1
|
|
fi
|
|
printf '%s' "$VALUE"
|
|
}
|
|
|
|
emit_settings_save() {
|
|
INPUT="$(cat)"
|
|
if ! command -v uci >/dev/null 2>&1; then
|
|
json_error "uci_unavailable" "uci command not found"
|
|
return 1
|
|
fi
|
|
load_settings
|
|
|
|
if ! REPOSITORY_VALUE="$(settings_value core_repository "$INPUT")" ||
|
|
! BUNDLE_VALUE="$(settings_value core_bundle "$INPUT")" ||
|
|
! CONFIG_VALUE="$(settings_value config_path "$INPUT")" ||
|
|
! WORKING_VALUE="$(settings_value working_dir "$INPUT")" ||
|
|
! PROXY_VALUE="$(settings_value download_proxy "$INPUT")"; then
|
|
json_error "invalid_settings" "settings values must be single-line strings"
|
|
return 1
|
|
fi
|
|
|
|
[ -n "$REPOSITORY_VALUE" ] || REPOSITORY_VALUE="$DEFAULT_CORE_REPOSITORY"
|
|
[ -n "$BUNDLE_VALUE" ] || BUNDLE_VALUE="$DEFAULT_CORE_BUNDLE"
|
|
[ -n "$CONFIG_VALUE" ] || CONFIG_VALUE="$DEFAULT_CONFIG_PATH"
|
|
[ -n "$WORKING_VALUE" ] || WORKING_VALUE="$DEFAULT_WORKING_DIR"
|
|
LOG_SYSLOG_VALUE="$LOG_SYSLOG"
|
|
if input_has_key log_syslog "$INPUT"; then
|
|
LOG_SYSLOG_VALUE="$(input_value log_syslog "$INPUT")"
|
|
case "$LOG_SYSLOG_VALUE" in
|
|
0|1) ;;
|
|
*)
|
|
json_error "invalid_log_syslog" "log_syslog must be 0 or 1"
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
case "$REPOSITORY_VALUE" in
|
|
*/*) ;;
|
|
*)
|
|
json_error "invalid_core_repository" "core_repository must use owner/repo format"
|
|
return 1
|
|
;;
|
|
esac
|
|
if printf '%s' "$REPOSITORY_VALUE" | grep -q '[^A-Za-z0-9_.\/-]'; then
|
|
json_error "invalid_core_repository" "core_repository contains unsupported characters"
|
|
return 1
|
|
fi
|
|
|
|
case "$BUNDLE_VALUE" in
|
|
full|standard|minimal) ;;
|
|
*)
|
|
json_error "invalid_core_bundle" "core_bundle must be one of full, standard, minimal"
|
|
return 1
|
|
;;
|
|
esac
|
|
if ! valid_config_path_setting "$CONFIG_VALUE"; then
|
|
json_error "invalid_config_path" "config_path must be an absolute OxiDNS-related file path"
|
|
return 1
|
|
fi
|
|
if ! valid_working_dir_setting "$WORKING_VALUE"; then
|
|
json_error "invalid_working_dir" "working_dir must be an absolute OxiDNS-related directory path"
|
|
return 1
|
|
fi
|
|
|
|
uci -q set oxidns.main=settings
|
|
uci -q set "oxidns.main.core_repository=$REPOSITORY_VALUE"
|
|
uci -q set "oxidns.main.core_bundle=$BUNDLE_VALUE"
|
|
uci -q set "oxidns.main.config_path=$CONFIG_VALUE"
|
|
uci -q set "oxidns.main.working_dir=$WORKING_VALUE"
|
|
uci -q set "oxidns.main.log_syslog=$LOG_SYSLOG_VALUE"
|
|
uci -q delete oxidns.main.api_base_url >/dev/null 2>&1 || true
|
|
uci -q delete oxidns.main.install_mode >/dev/null 2>&1 || true
|
|
uci -q delete oxidns.main.service_name >/dev/null 2>&1 || true
|
|
uci -q delete oxidns.main.package_feed_url >/dev/null 2>&1 || true
|
|
uci -q delete oxidns.main.manifest_url >/dev/null 2>&1 || true
|
|
|
|
CLEAR_PROXY="$(input_bool clear_download_proxy "$INPUT")"
|
|
if [ "$CLEAR_PROXY" = "1" ]; then
|
|
uci -q delete oxidns.main.download_proxy >/dev/null 2>&1 || true
|
|
elif [ -n "$PROXY_VALUE" ]; then
|
|
uci -q set "oxidns.main.download_proxy=$PROXY_VALUE"
|
|
elif [ -z "$DOWNLOAD_PROXY" ]; then
|
|
uci -q delete oxidns.main.download_proxy >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
NEW_TOKEN="$(input_value github_token "$INPUT")"
|
|
CLEAR_TOKEN="$(input_bool clear_github_token "$INPUT")"
|
|
if [ "$CLEAR_TOKEN" = "1" ]; then
|
|
uci -q delete oxidns.main.github_token >/dev/null 2>&1 || true
|
|
elif [ -n "$NEW_TOKEN" ]; then
|
|
if ! safe_single_line_value "$NEW_TOKEN"; then
|
|
json_error "invalid_token" "github_token must be a single-line value"
|
|
return 1
|
|
fi
|
|
uci -q set oxidns.main.github_token="$NEW_TOKEN"
|
|
fi
|
|
|
|
if uci -q commit oxidns; then
|
|
emit_settings_read
|
|
else
|
|
json_error "settings_save_failed" "failed to commit oxidns settings"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
input_value() {
|
|
KEY="$1"
|
|
INPUT="$2"
|
|
if command -v jsonfilter >/dev/null 2>&1; then
|
|
printf '%s' "$INPUT" | jsonfilter -e "@.$KEY" 2>/dev/null || true
|
|
else
|
|
printf '%s' "$INPUT" | sed -n "s/.*\"$KEY\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p"
|
|
fi
|
|
}
|
|
|
|
input_has_key() {
|
|
KEY="$1"
|
|
INPUT="$2"
|
|
printf '%s' "$INPUT" | grep -q "\"$KEY\"[[:space:]]*:"
|
|
}
|
|
|
|
input_bool() {
|
|
KEY="$1"
|
|
INPUT="$2"
|
|
VALUE="$(input_value "$KEY" "$INPUT")"
|
|
case "$VALUE" in
|
|
1|true|TRUE|yes|YES|on|ON)
|
|
printf '1'
|
|
;;
|
|
*)
|
|
printf '0'
|
|
;;
|
|
esac
|
|
}
|
|
|
|
path_is_under() {
|
|
CHILD="$1"
|
|
PARENT="$2"
|
|
[ -n "$CHILD" ] && [ -n "$PARENT" ] || return 1
|
|
case "$CHILD" in
|
|
"$PARENT"/*)
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
oxidns_named_path() {
|
|
PATH_VALUE="$1"
|
|
BASE_NAME="$(basename "$PATH_VALUE" 2>/dev/null || printf '')"
|
|
DIR_NAME="$(dirname "$PATH_VALUE" 2>/dev/null || printf '')"
|
|
DIR_BASE="$(basename "$DIR_NAME" 2>/dev/null || printf '')"
|
|
case "$BASE_NAME:$DIR_BASE" in
|
|
*oxidns*|*:oxidns*)
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
safe_remove_config_path() {
|
|
PATH_VALUE="$1"
|
|
[ -n "$PATH_VALUE" ] || return 1
|
|
case "$PATH_VALUE" in
|
|
/*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
[ -d "$PATH_VALUE" ] && return 1
|
|
oxidns_named_path "$PATH_VALUE"
|
|
}
|
|
|
|
safe_remove_working_dir() {
|
|
PATH_VALUE="$1"
|
|
[ -n "$PATH_VALUE" ] || return 1
|
|
case "$PATH_VALUE" in
|
|
/*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
case "$PATH_VALUE" in
|
|
/|/bin|/dev|/etc|/lib|/mnt|/overlay|/proc|/rom|/root|/sbin|/sys|/tmp|/usr|/var|/www)
|
|
return 1
|
|
;;
|
|
esac
|
|
oxidns_named_path "$PATH_VALUE"
|
|
}
|
|
|
|
path_has_parent_ref() {
|
|
case "$1" in
|
|
..|../*|*/..|*/../*)
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
valid_config_path_setting() {
|
|
PATH_VALUE="$1"
|
|
path_has_parent_ref "$PATH_VALUE" && return 1
|
|
safe_remove_config_path "$PATH_VALUE"
|
|
}
|
|
|
|
valid_working_dir_setting() {
|
|
PATH_VALUE="$1"
|
|
path_has_parent_ref "$PATH_VALUE" && return 1
|
|
safe_remove_working_dir "$PATH_VALUE"
|
|
}
|
|
|
|
validate_config_file() {
|
|
FILE="$1"
|
|
load_settings
|
|
if [ ! -x "$OXIDNS_BIN" ]; then
|
|
printf 'oxidns binary not found or not executable: %s' "$OXIDNS_BIN"
|
|
return 1
|
|
fi
|
|
mkdir -p "$WORKING_DIR"
|
|
"$OXIDNS_BIN" check -c "$FILE" -d "$WORKING_DIR" 2>&1
|
|
}
|
|
|
|
emit_config_validate() {
|
|
INPUT="$(cat)"
|
|
CONTENT="$(input_value content "$INPUT")"
|
|
TMP_FILE="$(mktemp "$TMP_ROOT/oxidns-config.XXXXXX")"
|
|
if input_has_key content "$INPUT"; then
|
|
if [ -z "$CONTENT" ]; then
|
|
rm -f "$TMP_FILE"
|
|
json_error "missing_content" "configuration content is required"
|
|
return 1
|
|
fi
|
|
printf '%s' "$CONTENT" > "$TMP_FILE"
|
|
elif [ -r "$CONFIG_PATH" ]; then
|
|
cp "$CONFIG_PATH" "$TMP_FILE"
|
|
else
|
|
rm -f "$TMP_FILE"
|
|
json_error "config_missing" "no configuration content or file available"
|
|
return 1
|
|
fi
|
|
if OUTPUT="$(validate_config_file "$TMP_FILE")"; then
|
|
rm -f "$TMP_FILE"
|
|
printf '{"ok":true,"message":'
|
|
json_string "${OUTPUT:-configuration is valid}"
|
|
printf '}\n'
|
|
else
|
|
rm -f "$TMP_FILE"
|
|
json_error "config_invalid" "$OUTPUT"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
emit_config_save() {
|
|
load_settings
|
|
INPUT="$(cat)"
|
|
CONTENT="$(input_value content "$INPUT")"
|
|
BASE_MTIME="$(input_value base_mtime "$INPUT")"
|
|
RESTART="$(input_value restart "$INPUT")"
|
|
[ -n "$RESTART" ] || RESTART="$(input_value reload "$INPUT")"
|
|
|
|
if [ -z "$CONTENT" ]; then
|
|
json_error "missing_content" "configuration content is required"
|
|
return 1
|
|
fi
|
|
if [ -n "$BASE_MTIME" ] && [ -e "$CONFIG_PATH" ] && [ "$BASE_MTIME" != "$(stat_mtime "$CONFIG_PATH")" ]; then
|
|
json_error "config_conflict" "configuration file changed since it was loaded"
|
|
return 1
|
|
fi
|
|
|
|
TMP_FILE="$(mktemp "$TMP_ROOT/oxidns-config.XXXXXX")"
|
|
printf '%s' "$CONTENT" > "$TMP_FILE"
|
|
if ! OUTPUT="$(validate_config_file "$TMP_FILE")"; then
|
|
rm -f "$TMP_FILE"
|
|
json_error "config_invalid" "$OUTPUT"
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$CONFIG_PATH")"
|
|
if [ -e "$CONFIG_PATH" ]; then
|
|
mkdir -p "$WORKING_DIR/config-backups"
|
|
cp "$CONFIG_PATH" "$WORKING_DIR/config-backups/config.yaml.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
|
|
fi
|
|
mv "$TMP_FILE" "$CONFIG_PATH"
|
|
|
|
RESTART_STATUS="not_requested"
|
|
MESSAGE="configuration saved"
|
|
if [ "$RESTART" = "true" ] || [ "$RESTART" = "1" ]; then
|
|
INIT="$(init_script)"
|
|
SERVICE_OUT="$(mktemp "$TMP_ROOT/oxidns-service.XXXXXX")"
|
|
if [ ! -x "$INIT" ]; then
|
|
rm -f "$SERVICE_OUT"
|
|
json_error "service_unavailable" "configuration saved, but init script was not found"
|
|
return 1
|
|
fi
|
|
if run_init_action "$INIT" restart >"$SERVICE_OUT" 2>&1; then
|
|
RESTART_STATUS="requested"
|
|
MESSAGE="configuration saved and service restarted"
|
|
else
|
|
MSG="$(read_error_detail_file "$SERVICE_OUT" 2>/dev/null || true)"
|
|
rm -f "$SERVICE_OUT"
|
|
json_error "service_restart_failed" "configuration saved, but service restart failed: ${MSG:-service restart failed}"
|
|
return 1
|
|
fi
|
|
rm -f "$SERVICE_OUT"
|
|
fi
|
|
|
|
printf '{"ok":true,"path":'
|
|
json_string "$CONFIG_PATH"
|
|
printf ',"mtime":'
|
|
json_string "$(stat_mtime "$CONFIG_PATH")"
|
|
printf ',"restart":'
|
|
json_string "$RESTART_STATUS"
|
|
printf ',"message":'
|
|
json_string "$MESSAGE"
|
|
printf '}\n'
|
|
}
|
|
|
|
emit_log_lines_json() {
|
|
SOURCE="$1"
|
|
FILE="$2"
|
|
printf '{"ok":true,"source":'
|
|
json_string "$SOURCE"
|
|
printf ',"lines":['
|
|
SEP=""
|
|
while IFS= read -r LINE; do
|
|
printf '%s' "$SEP"
|
|
json_string "$LINE"
|
|
SEP=","
|
|
done < "$FILE"
|
|
printf ']}\n'
|
|
}
|
|
|
|
read_logread_logs() {
|
|
LIMIT="$1"
|
|
OUT_FILE="$2"
|
|
SCAN_LIMIT="$((LIMIT * 4))"
|
|
: > "$OUT_FILE"
|
|
if ! command -v logread >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
if [ "$SCAN_LIMIT" -lt 100 ]; then
|
|
SCAN_LIMIT=100
|
|
elif [ "$SCAN_LIMIT" -gt 2000 ]; then
|
|
SCAN_LIMIT=2000
|
|
fi
|
|
|
|
logread -l "$SCAN_LIMIT" -e oxidns 2>/dev/null | awk '
|
|
BEGIN {
|
|
esc = sprintf("%c", 27);
|
|
csi = esc "\\[[0-9;?]*[ -/]*[@-~]";
|
|
}
|
|
/oxidns\[[0-9]+\]:/ {
|
|
gsub(csi, "");
|
|
gsub(/\r/, "");
|
|
sub(/^.* oxidns\[[0-9]+\]:[[:space:]]*/, "");
|
|
print;
|
|
}
|
|
' | tail -n "$LIMIT" > "$OUT_FILE" || true
|
|
}
|
|
|
|
emit_logs_recent() {
|
|
INPUT="$(cat)"
|
|
LIMIT="$(input_value limit "$INPUT")"
|
|
[ -n "$LIMIT" ] || LIMIT=200
|
|
case "$LIMIT" in
|
|
*[!0-9]*|'') LIMIT=200 ;;
|
|
esac
|
|
TMP_FILE="$(mktemp "$TMP_ROOT/oxidns-logs.XXXXXX")"
|
|
read_logread_logs "$LIMIT" "$TMP_FILE" || true
|
|
emit_log_lines_json "logread" "$TMP_FILE"
|
|
rm -f "$TMP_FILE"
|
|
}
|
|
|
|
read_error_detail_file() {
|
|
DETAIL_FILE="$1"
|
|
[ -r "$DETAIL_FILE" ] || return 0
|
|
awk -v token="$GITHUB_TOKEN" -v proxy="$DOWNLOAD_PROXY" '
|
|
function redact(value, secret, out, pos) {
|
|
if (secret == "")
|
|
return value;
|
|
out = "";
|
|
while ((pos = index(value, secret)) > 0) {
|
|
out = out substr(value, 1, pos - 1) "[redacted]";
|
|
value = substr(value, pos + length(secret));
|
|
}
|
|
return out value;
|
|
}
|
|
{
|
|
line = redact(redact($0, token), proxy);
|
|
gsub(/[[:cntrl:]]/, " ", line);
|
|
print line;
|
|
}
|
|
' "$DETAIL_FILE" | tail -c 1200
|
|
}
|
|
|
|
set_download_error_from_file() {
|
|
ERROR_FILE="$1"
|
|
ERROR_RC="$2"
|
|
ERROR_TOOL="$3"
|
|
ERROR_DETAIL="$(read_error_detail_file "$ERROR_FILE" 2>/dev/null || true)"
|
|
ERROR_DETAIL="$(printf '%s' "$ERROR_DETAIL" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
[ -n "$ERROR_DETAIL" ] || ERROR_DETAIL="$ERROR_TOOL exited with code $ERROR_RC"
|
|
DOWNLOAD_RC="$ERROR_RC"
|
|
DOWNLOAD_ERROR="$ERROR_TOOL exited with code $ERROR_RC: $ERROR_DETAIL"
|
|
rm -f "$ERROR_FILE"
|
|
}
|
|
|
|
run_download_command() {
|
|
DOWNLOAD_TOOL="$1"
|
|
DOWNLOAD_DISPLAY="$2"
|
|
DOWNLOAD_OUT="$3"
|
|
shift 3
|
|
DOWNLOAD_ERR_FILE="$(mktemp "$TMP_ROOT/oxidns-download.XXXXXX")"
|
|
DOWNLOAD_DONE_FILE="$(mktemp "$TMP_ROOT/oxidns-download-done.XXXXXX")"
|
|
rm -f "$DOWNLOAD_DONE_FILE"
|
|
core_progress_command "$DOWNLOAD_DISPLAY"
|
|
case "$-" in
|
|
*e*) DOWNLOAD_RESTORE_ERREXIT=1 ;;
|
|
*) DOWNLOAD_RESTORE_ERREXIT=0 ;;
|
|
esac
|
|
set +e
|
|
DOWNLOAD_LAST_SIZE=""
|
|
(
|
|
while [ ! -f "$DOWNLOAD_DONE_FILE" ]; do
|
|
if [ -f "$DOWNLOAD_OUT" ]; then
|
|
DOWNLOAD_SIZE="$(file_size_bytes "$DOWNLOAD_OUT")"
|
|
if [ "$DOWNLOAD_SIZE" != "$DOWNLOAD_LAST_SIZE" ]; then
|
|
core_progress_log "progress" "$DOWNLOAD_TOOL downloaded $DOWNLOAD_SIZE bytes"
|
|
DOWNLOAD_LAST_SIZE="$DOWNLOAD_SIZE"
|
|
fi
|
|
fi
|
|
sleep 1
|
|
done
|
|
) &
|
|
DOWNLOAD_MONITOR_PID="$!"
|
|
"$@" >/dev/null 2>"$DOWNLOAD_ERR_FILE"
|
|
DOWNLOAD_CMD_RC="$?"
|
|
: > "$DOWNLOAD_DONE_FILE"
|
|
wait "$DOWNLOAD_MONITOR_PID" 2>/dev/null || true
|
|
rm -f "$DOWNLOAD_DONE_FILE"
|
|
if [ "$DOWNLOAD_RESTORE_ERREXIT" = "1" ]; then
|
|
set -e
|
|
else
|
|
set +e
|
|
fi
|
|
if [ "$DOWNLOAD_CMD_RC" -eq 0 ]; then
|
|
rm -f "$DOWNLOAD_ERR_FILE"
|
|
DOWNLOAD_ERROR=""
|
|
DOWNLOAD_RC=0
|
|
core_progress_log "success" "$DOWNLOAD_TOOL download complete: $(file_size_bytes "$DOWNLOAD_OUT") bytes"
|
|
return 0
|
|
fi
|
|
set_download_error_from_file "$DOWNLOAD_ERR_FILE" "$DOWNLOAD_CMD_RC" "$DOWNLOAD_TOOL"
|
|
core_progress_log "error" "$DOWNLOAD_ERROR"
|
|
return "$DOWNLOAD_CMD_RC"
|
|
}
|
|
|
|
download_file() {
|
|
URL="$1"
|
|
OUT="$2"
|
|
DOWNLOAD_ERROR=""
|
|
DOWNLOAD_RC=""
|
|
if command -v curl >/dev/null 2>&1; then
|
|
if [ -n "$DOWNLOAD_PROXY" ]; then
|
|
if [ -n "$GITHUB_TOKEN" ]; then
|
|
run_download_command curl "curl -fsSL --proxy [configured] -H 'User-Agent: luci-app-oxidns' -H 'Authorization: Bearer [redacted]' -o $OUT $URL" "$OUT" curl -fsSL --proxy "$DOWNLOAD_PROXY" -H "User-Agent: luci-app-oxidns" -H "Authorization: Bearer $GITHUB_TOKEN" -o "$OUT" "$URL"
|
|
else
|
|
run_download_command curl "curl -fsSL --proxy [configured] -H 'User-Agent: luci-app-oxidns' -o $OUT $URL" "$OUT" curl -fsSL --proxy "$DOWNLOAD_PROXY" -H "User-Agent: luci-app-oxidns" -o "$OUT" "$URL"
|
|
fi
|
|
elif [ -n "$GITHUB_TOKEN" ]; then
|
|
run_download_command curl "curl -fsSL -H 'User-Agent: luci-app-oxidns' -H 'Authorization: Bearer [redacted]' -o $OUT $URL" "$OUT" curl -fsSL -H "User-Agent: luci-app-oxidns" -H "Authorization: Bearer $GITHUB_TOKEN" -o "$OUT" "$URL"
|
|
else
|
|
run_download_command curl "curl -fsSL -H 'User-Agent: luci-app-oxidns' -o $OUT $URL" "$OUT" curl -fsSL -H "User-Agent: luci-app-oxidns" -o "$OUT" "$URL"
|
|
fi
|
|
elif command -v uclient-fetch >/dev/null 2>&1; then
|
|
if [ -n "$GITHUB_TOKEN" ]; then
|
|
run_download_command uclient-fetch "uclient-fetch -O $OUT --header='User-Agent: luci-app-oxidns' --header='Authorization: Bearer [redacted]' $URL" "$OUT" uclient-fetch -O "$OUT" --header="User-Agent: luci-app-oxidns" --header="Authorization: Bearer $GITHUB_TOKEN" "$URL"
|
|
else
|
|
run_download_command uclient-fetch "uclient-fetch -O $OUT --header='User-Agent: luci-app-oxidns' $URL" "$OUT" uclient-fetch -O "$OUT" --header="User-Agent: luci-app-oxidns" "$URL"
|
|
fi
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
if [ -n "$GITHUB_TOKEN" ]; then
|
|
run_download_command wget "wget --header='User-Agent: luci-app-oxidns' --header='Authorization: Bearer [redacted]' -O $OUT $URL" "$OUT" wget --header="User-Agent: luci-app-oxidns" --header="Authorization: Bearer $GITHUB_TOKEN" -O "$OUT" "$URL"
|
|
else
|
|
run_download_command wget "wget --header='User-Agent: luci-app-oxidns' -O $OUT $URL" "$OUT" wget --header="User-Agent: luci-app-oxidns" -O "$OUT" "$URL"
|
|
fi
|
|
else
|
|
DOWNLOAD_RC=127
|
|
DOWNLOAD_ERROR="none of curl, uclient-fetch, or wget is installed"
|
|
core_progress_log "error" "$DOWNLOAD_ERROR"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
require_download_transport() {
|
|
if [ -n "$DOWNLOAD_PROXY" ] && ! command -v curl >/dev/null 2>&1; then
|
|
core_progress_log "error" "download_proxy requires curl, but curl is not installed"
|
|
json_detailed_error "download_proxy_requires_curl" "download_proxy is configured, but curl is not installed; install curl or clear download_proxy" "prepare_download" "download_proxy only works with curl on OpenWrt"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
sha256_file() {
|
|
sha256sum "$1" 2>/dev/null | awk '{ print $1 }'
|
|
}
|
|
|
|
asset_name_for_bundle() {
|
|
BUNDLE="$1"
|
|
RUST_TARGET="$2"
|
|
case "$BUNDLE" in
|
|
full)
|
|
printf 'oxidns-%s.tar.gz' "$RUST_TARGET"
|
|
;;
|
|
standard|minimal)
|
|
printf 'oxidns-%s-%s.tar.gz' "$BUNDLE" "$RUST_TARGET"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
release_api_url() {
|
|
REPOSITORY="$1"
|
|
TARGET="$2"
|
|
if [ "$TARGET" = "latest" ]; then
|
|
printf '%s/repos/%s/releases/latest' "$GITHUB_API_BASE" "$REPOSITORY"
|
|
else
|
|
printf '%s/repos/%s/releases/tags/%s' "$GITHUB_API_BASE" "$REPOSITORY" "$TARGET"
|
|
fi
|
|
}
|
|
|
|
version_tag() {
|
|
VERSION="$1"
|
|
case "$VERSION" in
|
|
v*) printf '%s' "$VERSION" ;;
|
|
*) printf 'v%s' "$VERSION" ;;
|
|
esac
|
|
}
|
|
|
|
select_release_asset() {
|
|
RELEASE_FILE="$1"
|
|
WANT_ASSET="$2"
|
|
|
|
SELECT_TAG=""
|
|
SELECT_ASSET_NAME=""
|
|
SELECT_URL=""
|
|
SELECT_SHA256=""
|
|
SELECT_SIZE=""
|
|
|
|
if ! command -v jsonfilter >/dev/null 2>&1; then
|
|
return 2
|
|
fi
|
|
|
|
SELECT_TAG="$(jsonfilter -i "$RELEASE_FILE" -e '@.tag_name' 2>/dev/null || true)"
|
|
[ -n "$SELECT_TAG" ] || return 1
|
|
|
|
NAME_FILE="$(mktemp "$TMP_ROOT/oxidns-asset-name.XXXXXX")"
|
|
URL_FILE="$(mktemp "$TMP_ROOT/oxidns-asset-url.XXXXXX")"
|
|
DIGEST_FILE="$(mktemp "$TMP_ROOT/oxidns-asset-digest.XXXXXX")"
|
|
SIZE_FILE="$(mktemp "$TMP_ROOT/oxidns-asset-size.XXXXXX")"
|
|
SELECT_FILE="$(mktemp "$TMP_ROOT/oxidns-asset-selected.XXXXXX")"
|
|
|
|
if ! jsonfilter -i "$RELEASE_FILE" -e '@.assets[*].name' > "$NAME_FILE" 2>/dev/null ||
|
|
! jsonfilter -i "$RELEASE_FILE" -e '@.assets[*].browser_download_url' > "$URL_FILE" 2>/dev/null ||
|
|
! jsonfilter -i "$RELEASE_FILE" -e '@.assets[*].digest' > "$DIGEST_FILE" 2>/dev/null ||
|
|
! jsonfilter -i "$RELEASE_FILE" -e '@.assets[*].size' > "$SIZE_FILE" 2>/dev/null; then
|
|
rm -f "$NAME_FILE" "$URL_FILE" "$DIGEST_FILE" "$SIZE_FILE" "$SELECT_FILE"
|
|
return 1
|
|
fi
|
|
|
|
if ! awk -v want="$WANT_ASSET" -v url_file="$URL_FILE" -v digest_file="$DIGEST_FILE" -v size_file="$SIZE_FILE" '
|
|
BEGIN {
|
|
while ((getline url < url_file) > 0)
|
|
urls[++url_count] = url;
|
|
while ((getline digest < digest_file) > 0)
|
|
digests[++digest_count] = digest;
|
|
while ((getline size < size_file) > 0)
|
|
sizes[++size_count] = size;
|
|
}
|
|
{
|
|
idx++;
|
|
if ($0 == want) {
|
|
print $0;
|
|
print urls[idx];
|
|
print digests[idx];
|
|
print sizes[idx];
|
|
found = 1;
|
|
exit;
|
|
}
|
|
}
|
|
END {
|
|
exit found ? 0 : 1;
|
|
}
|
|
' "$NAME_FILE" > "$SELECT_FILE"; then
|
|
rm -f "$NAME_FILE" "$URL_FILE" "$DIGEST_FILE" "$SIZE_FILE" "$SELECT_FILE"
|
|
return 1
|
|
fi
|
|
|
|
SELECT_ASSET_NAME="$(sed -n '1p' "$SELECT_FILE")"
|
|
SELECT_URL="$(sed -n '2p' "$SELECT_FILE")"
|
|
SELECT_SHA256="$(sed -n '3p' "$SELECT_FILE" | sed 's/^sha256://')"
|
|
SELECT_SIZE="$(sed -n '4p' "$SELECT_FILE")"
|
|
rm -f "$NAME_FILE" "$URL_FILE" "$DIGEST_FILE" "$SIZE_FILE" "$SELECT_FILE"
|
|
[ -n "$SELECT_ASSET_NAME" ] && [ -n "$SELECT_URL" ]
|
|
}
|
|
|
|
valid_sha256() {
|
|
printf '%s' "$1" | grep -Eq '^[0-9a-fA-F]{64}$'
|
|
}
|
|
|
|
validate_safe_archive() {
|
|
ARCHIVE="$1"
|
|
if ! tar -tzf "$ARCHIVE" | awk '
|
|
{
|
|
path = $0;
|
|
while (path ~ /^\.\//)
|
|
sub(/^\.\//, "", path);
|
|
while (path ~ /\/$/)
|
|
sub(/\/$/, "", path);
|
|
if (path == "" || path ~ /^\//)
|
|
bad = 1;
|
|
n = split(path, parts, "/");
|
|
for (i = 1; i <= n; i++) {
|
|
if (parts[i] == "" || parts[i] == "..")
|
|
bad = 1;
|
|
}
|
|
}
|
|
END { exit bad ? 1 : 0 }
|
|
'; then
|
|
return 1
|
|
fi
|
|
|
|
if ! tar -tvzf "$ARCHIVE" | awk '
|
|
{
|
|
type = substr($0, 1, 1);
|
|
if (type != "-" && type != "d")
|
|
bad = 1;
|
|
}
|
|
END { exit bad ? 1 : 0 }
|
|
'; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
valid_ipv4() {
|
|
printf '%s' "$1" | awk -F. '
|
|
NF != 4 { exit 1 }
|
|
{
|
|
for (i = 1; i <= 4; i++) {
|
|
if ($i !~ /^[0-9]+$/ || $i < 0 || $i > 255)
|
|
exit 1;
|
|
}
|
|
}
|
|
'
|
|
}
|
|
|
|
lan_ipv4_from_uci() {
|
|
command -v uci >/dev/null 2>&1 || return 1
|
|
LAN_IP="$(uci -q get network.lan.ipaddr 2>/dev/null || true)"
|
|
if valid_ipv4 "$LAN_IP"; then
|
|
printf '%s' "$LAN_IP"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
lan_ipv4_from_interface() {
|
|
IFACE="${1:-br-lan}"
|
|
command -v ip >/dev/null 2>&1 || return 1
|
|
ip -4 addr show dev "$IFACE" 2>/dev/null | awk '
|
|
/ inet / {
|
|
addr = $2;
|
|
sub(/\/.*/, "", addr);
|
|
print addr;
|
|
exit;
|
|
}
|
|
'
|
|
}
|
|
|
|
default_api_listen() {
|
|
LAN_IP="$(lan_ipv4_from_uci 2>/dev/null || true)"
|
|
if [ -z "$LAN_IP" ]; then
|
|
LAN_IP="$(lan_ipv4_from_interface br-lan 2>/dev/null || true)"
|
|
fi
|
|
if ! valid_ipv4 "$LAN_IP"; then
|
|
LAN_IP="0.0.0.0"
|
|
fi
|
|
printf '%s:9199' "$LAN_IP"
|
|
}
|
|
|
|
write_openwrt_default_config() {
|
|
SOURCE_FILE="$1"
|
|
OUT_FILE="$2"
|
|
DEFAULT_LISTEN="$(default_api_listen)"
|
|
awk -v default_listen="$DEFAULT_LISTEN" '
|
|
BEGIN {
|
|
top = "";
|
|
second = "";
|
|
third = "";
|
|
listen_done = 0;
|
|
root_done = 0;
|
|
}
|
|
/^[^[:space:]#][^:]*:/ {
|
|
top = $0;
|
|
sub(/:.*/, "", top);
|
|
second = "";
|
|
third = "";
|
|
}
|
|
top == "api" && /^[[:space:]][[:space:]]http:[[:space:]]*$/ {
|
|
second = "http";
|
|
third = "";
|
|
print;
|
|
next;
|
|
}
|
|
top == "api" && second == "http" && /^[[:space:]][[:space:]][[:space:]][[:space:]]webui:[[:space:]]*$/ {
|
|
third = "webui";
|
|
print;
|
|
next;
|
|
}
|
|
top == "api" && second == "http" && /^[[:space:]][[:space:]][[:space:]][[:space:]]listen:[[:space:]]*/ {
|
|
print " listen: \"" default_listen "\"";
|
|
listen_done = 1;
|
|
next;
|
|
}
|
|
top == "api" && second == "http" && third == "webui" && /^[[:space:]][[:space:]][[:space:]][[:space:]][[:space:]][[:space:]]root:[[:space:]]*/ {
|
|
print " root: \"/usr/share/oxidns/webui\"";
|
|
root_done = 1;
|
|
next;
|
|
}
|
|
{ print }
|
|
END {
|
|
if (!listen_done || !root_done)
|
|
exit 42;
|
|
}
|
|
' "$SOURCE_FILE" > "$OUT_FILE"
|
|
}
|
|
|
|
core_install_error() {
|
|
ERROR_CODE="$1"
|
|
ERROR_MESSAGE="$2"
|
|
ERROR_STAGE="${3:-}"
|
|
ERROR_DETAIL="${4:-}"
|
|
rm -rf "${CORE_TMP_DIR:-}" 2>/dev/null || true
|
|
core_progress_log "error" "$ERROR_CODE: $ERROR_MESSAGE"
|
|
[ -n "$ERROR_STAGE" ] && core_progress_log "error" "stage=$ERROR_STAGE"
|
|
[ -n "$ERROR_DETAIL" ] && core_progress_log "error" "$ERROR_DETAIL"
|
|
json_detailed_error "$ERROR_CODE" "$ERROR_MESSAGE" "$ERROR_STAGE" "$ERROR_DETAIL"
|
|
return 1
|
|
}
|
|
|
|
install_core_from_release() {
|
|
ACTION="$1"
|
|
RELEASE_TARGET="$2"
|
|
REQUEST_BUNDLE="$3"
|
|
load_settings
|
|
require_download_transport || return 1
|
|
|
|
case "$CORE_REPOSITORY" in
|
|
*/*) ;;
|
|
*)
|
|
core_progress_log "error" "invalid core_repository: $CORE_REPOSITORY"
|
|
json_error "invalid_core_repository" "core_repository must use owner/repo format"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
core_progress_command "uname -m"
|
|
if ! RUST_TARGET="$(detect_rust_target 2>/dev/null)"; then
|
|
core_progress_log "error" "unsupported CPU architecture"
|
|
json_error "unsupported_target" "current CPU architecture does not match a published OxiDNS Linux musl release"
|
|
return 1
|
|
fi
|
|
core_progress_log "info" "target=$RUST_TARGET"
|
|
if ! ASSET_NAME="$(asset_name_for_bundle "$REQUEST_BUNDLE" "$RUST_TARGET")"; then
|
|
core_progress_log "error" "unsupported bundle: $REQUEST_BUNDLE"
|
|
json_error "unsupported_core_bundle" "core bundle must be one of full, standard, minimal"
|
|
return 1
|
|
fi
|
|
core_progress_log "info" "repository=$CORE_REPOSITORY bundle=$REQUEST_BUNDLE asset=$ASSET_NAME"
|
|
|
|
CORE_TMP_DIR="$(mktemp -d "$TMP_ROOT/oxidns-core.XXXXXX")"
|
|
RELEASE_FILE="$CORE_TMP_DIR/release.json"
|
|
ARCHIVE_FILE="$CORE_TMP_DIR/$ASSET_NAME"
|
|
UNPACK_DIR="$CORE_TMP_DIR/unpack"
|
|
RELEASE_URL="$(release_api_url "$CORE_REPOSITORY" "$RELEASE_TARGET")"
|
|
|
|
core_progress_log "info" "fetch release metadata: $RELEASE_URL"
|
|
if ! download_file "$RELEASE_URL" "$RELEASE_FILE"; then
|
|
core_install_error "release_fetch_failed" "failed to fetch OxiDNS release metadata" "download_metadata" "repository=$CORE_REPOSITORY release=$RELEASE_TARGET url=$RELEASE_URL; ${DOWNLOAD_ERROR:-unknown download error}"
|
|
return 1
|
|
fi
|
|
core_progress_command "jsonfilter release metadata for $ASSET_NAME"
|
|
set +e
|
|
select_release_asset "$RELEASE_FILE" "$ASSET_NAME"
|
|
SELECT_RELEASE_RC="$?"
|
|
set -e
|
|
if [ "$SELECT_RELEASE_RC" -ne 0 ]; then
|
|
if [ "$SELECT_RELEASE_RC" -eq 2 ]; then
|
|
SELECT_RELEASE_DETAIL="jsonfilter is not installed or unavailable; wanted asset=$ASSET_NAME repository=$CORE_REPOSITORY release=$RELEASE_TARGET"
|
|
else
|
|
SELECT_RELEASE_DETAIL="wanted asset=$ASSET_NAME repository=$CORE_REPOSITORY release=$RELEASE_TARGET"
|
|
fi
|
|
core_install_error "release_asset_not_found" "selected OxiDNS release does not contain the required archive" "select_release_asset" "$SELECT_RELEASE_DETAIL"
|
|
return 1
|
|
fi
|
|
core_progress_log "success" "selected release=$SELECT_TAG asset=$SELECT_ASSET_NAME size=${SELECT_SIZE:-unknown}"
|
|
if ! valid_sha256 "$SELECT_SHA256"; then
|
|
core_install_error "release_digest_missing" "selected OxiDNS release asset does not include a valid SHA256 digest" "verify_release_digest" "asset=$SELECT_ASSET_NAME digest=${SELECT_SHA256:-missing}"
|
|
return 1
|
|
fi
|
|
core_progress_log "info" "download archive: $SELECT_URL"
|
|
if ! download_file "$SELECT_URL" "$ARCHIVE_FILE"; then
|
|
core_install_error "core_download_failed" "failed to download OxiDNS core archive" "download_archive" "asset=$SELECT_ASSET_NAME url=$SELECT_URL; ${DOWNLOAD_ERROR:-unknown download error}"
|
|
return 1
|
|
fi
|
|
|
|
core_progress_command "sha256sum $ARCHIVE_FILE"
|
|
ACTUAL_SHA256="$(sha256_file "$ARCHIVE_FILE" || true)"
|
|
if [ -z "$ACTUAL_SHA256" ] || [ "$ACTUAL_SHA256" != "$SELECT_SHA256" ]; then
|
|
core_install_error "core_checksum_failed" "OxiDNS core archive SHA256 does not match the release digest" "verify_checksum" "asset=$SELECT_ASSET_NAME expected=$SELECT_SHA256 actual=${ACTUAL_SHA256:-missing}"
|
|
return 1
|
|
fi
|
|
core_progress_log "success" "checksum verified: $ACTUAL_SHA256"
|
|
core_progress_command "tar -tzf $ARCHIVE_FILE"
|
|
if ! validate_safe_archive "$ARCHIVE_FILE"; then
|
|
core_install_error "core_archive_unsafe" "OxiDNS core archive contains unsafe paths or links" "validate_archive" "asset=$SELECT_ASSET_NAME"
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p "$UNPACK_DIR"
|
|
TAR_OUT="$(mktemp "$TMP_ROOT/oxidns-tar.XXXXXX")"
|
|
core_progress_command "tar -xzf $ARCHIVE_FILE -C $UNPACK_DIR"
|
|
if ! tar -xzf "$ARCHIVE_FILE" -C "$UNPACK_DIR" >"$TAR_OUT" 2>&1; then
|
|
TAR_DETAIL="$(read_error_detail_file "$TAR_OUT" 2>/dev/null || true)"
|
|
rm -f "$TAR_OUT"
|
|
core_install_error "core_unpack_failed" "failed to unpack OxiDNS core archive" "unpack_archive" "${TAR_DETAIL:-tar extraction failed}"
|
|
return 1
|
|
fi
|
|
rm -f "$TAR_OUT"
|
|
if [ ! -f "$UNPACK_DIR/oxidns" ]; then
|
|
core_install_error "core_archive_invalid" "OxiDNS core archive does not contain an oxidns binary" "install_files" "asset=$SELECT_ASSET_NAME"
|
|
return 1
|
|
fi
|
|
core_progress_log "success" "archive unpacked"
|
|
|
|
CONFIG_TMP=""
|
|
if [ ! -e "$CONFIG_PATH" ]; then
|
|
if [ ! -f "$UNPACK_DIR/config.yaml" ]; then
|
|
core_install_error "core_archive_invalid" "OxiDNS core archive does not contain config.yaml" "install_files" "asset=$SELECT_ASSET_NAME"
|
|
return 1
|
|
fi
|
|
CONFIG_TMP="$CORE_TMP_DIR/config.yaml.openwrt"
|
|
core_progress_command "adapt config.yaml for OpenWrt defaults"
|
|
if ! write_openwrt_default_config "$UNPACK_DIR/config.yaml" "$CONFIG_TMP"; then
|
|
core_install_error "default_config_unsupported" "release config.yaml cannot be adapted for OpenWrt defaults" "install_files" "config=$UNPACK_DIR/config.yaml"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
SERVICE_RESTART="not_requested"
|
|
SERVICE_WAS_RUNNING=0
|
|
if service_running; then SERVICE_WAS_RUNNING=1; fi
|
|
INIT="$(init_script)"
|
|
if [ "$SERVICE_WAS_RUNNING" = "1" ] && [ -x "$INIT" ]; then
|
|
core_progress_command "$INIT stop"
|
|
SERVICE_OUT="$(mktemp "$TMP_ROOT/oxidns-service.XXXXXX")"
|
|
if ! run_init_action "$INIT" stop >"$SERVICE_OUT" 2>&1; then
|
|
MSG="$(read_error_detail_file "$SERVICE_OUT" 2>/dev/null || true)"
|
|
rm -f "$SERVICE_OUT"
|
|
core_install_error "service_stop_failed" "core was not replaced because the service could not be stopped" "stop_service" "${MSG:-service stop failed}"
|
|
return 1
|
|
fi
|
|
rm -f "$SERVICE_OUT"
|
|
fi
|
|
|
|
core_progress_command "install -m 755 $UNPACK_DIR/oxidns $OXIDNS_BIN"
|
|
mkdir -p "$(dirname "$OXIDNS_BIN")" "$WORKING_DIR"
|
|
cp "$UNPACK_DIR/oxidns" "$CORE_TMP_DIR/oxidns.new"
|
|
chmod 755 "$CORE_TMP_DIR/oxidns.new"
|
|
mv "$CORE_TMP_DIR/oxidns.new" "$OXIDNS_BIN"
|
|
|
|
if [ -d "$UNPACK_DIR/webui" ]; then
|
|
core_progress_command "copy webui files to $WEBUI_DIR"
|
|
WEBUI_TMP="$CORE_TMP_DIR/webui.new"
|
|
rm -rf "$WEBUI_TMP"
|
|
mkdir -p "$(dirname "$WEBUI_DIR")" "$WEBUI_TMP"
|
|
cp -R "$UNPACK_DIR/webui/." "$WEBUI_TMP/"
|
|
rm -rf "$WEBUI_DIR"
|
|
mv "$WEBUI_TMP" "$WEBUI_DIR"
|
|
fi
|
|
|
|
if [ -n "$CONFIG_TMP" ]; then
|
|
core_progress_command "install default config to $CONFIG_PATH"
|
|
mkdir -p "$(dirname "$CONFIG_PATH")"
|
|
mv "$CONFIG_TMP" "$CONFIG_PATH"
|
|
elif [ -f "$UNPACK_DIR/config.yaml" ]; then
|
|
core_progress_command "copy config example to $WORKING_DIR/config.yaml.example"
|
|
cp "$UNPACK_DIR/config.yaml" "$WORKING_DIR/config.yaml.example" 2>/dev/null || true
|
|
fi
|
|
|
|
CONFIG_CHECK_OK=0
|
|
core_progress_command "$OXIDNS_BIN check -c $CONFIG_PATH -d $WORKING_DIR"
|
|
if CONFIG_CHECK_OUTPUT="$(validate_config_file "$CONFIG_PATH" 2>&1)"; then
|
|
CONFIG_CHECK_OK=1
|
|
core_progress_log "success" "$CONFIG_CHECK_OUTPUT"
|
|
else
|
|
CONFIG_CHECK_OUTPUT="${CONFIG_CHECK_OUTPUT:-configuration check failed}"
|
|
core_progress_log "error" "$CONFIG_CHECK_OUTPUT"
|
|
fi
|
|
if [ "$SERVICE_WAS_RUNNING" = "1" ] && [ -x "$INIT" ]; then
|
|
SERVICE_OUT="$(mktemp "$TMP_ROOT/oxidns-service.XXXXXX")"
|
|
core_progress_command "$INIT start"
|
|
if "$INIT" start >"$SERVICE_OUT" 2>&1; then
|
|
SERVICE_RESTART="requested"
|
|
core_progress_log "success" "service restarted"
|
|
else
|
|
MSG="$(read_error_detail_file "$SERVICE_OUT" 2>/dev/null || true)"
|
|
rm -f "$SERVICE_OUT"
|
|
core_install_error "service_restart_failed" "core installed, but service restart failed" "restart_service" "${MSG:-service restart failed}"
|
|
return 1
|
|
fi
|
|
rm -f "$SERVICE_OUT"
|
|
fi
|
|
|
|
rm -rf "$CORE_TMP_DIR"
|
|
core_progress_log "success" "core $ACTION completed: version=$SELECT_TAG bundle=$REQUEST_BUNDLE target=$RUST_TARGET"
|
|
printf '{"ok":true,"action":'
|
|
json_string "$ACTION"
|
|
printf ',"version":'
|
|
json_string "$SELECT_TAG"
|
|
printf ',"bundle":'
|
|
json_string "$REQUEST_BUNDLE"
|
|
printf ',"target":'
|
|
json_string "$RUST_TARGET"
|
|
printf ',"asset":'
|
|
json_string "$SELECT_ASSET_NAME"
|
|
printf ',"binary_path":'
|
|
json_string "$OXIDNS_BIN"
|
|
printf ',"webui_path":'
|
|
json_string "$WEBUI_DIR"
|
|
printf ',"service_restart":'
|
|
json_string "$SERVICE_RESTART"
|
|
printf ',"config_check":{"ok":'
|
|
json_bool "$CONFIG_CHECK_OK"
|
|
printf ',"message":'
|
|
json_string "$CONFIG_CHECK_OUTPUT"
|
|
printf '}}\n'
|
|
}
|
|
|
|
emit_core_install_locked() {
|
|
load_settings
|
|
core_progress_reset "install OxiDNS core"
|
|
if [ -x "$OXIDNS_BIN" ]; then
|
|
core_progress_log "error" "core already installed"
|
|
json_error "core_already_installed" "OxiDNS core is already installed; use repair reinstall instead"
|
|
return 1
|
|
fi
|
|
install_core_from_release install latest "$CORE_BUNDLE"
|
|
}
|
|
|
|
emit_core_install() {
|
|
run_with_core_lock emit_core_install_locked
|
|
}
|
|
|
|
emit_core_reinstall_locked() {
|
|
load_settings
|
|
core_progress_reset "repair reinstall OxiDNS core"
|
|
BUILD_JSON="$(build_info_json)"
|
|
CURRENT_VERSION="$(json_value_from_string version "$BUILD_JSON")"
|
|
CURRENT_BUNDLE="$(json_value_from_string bundle "$BUILD_JSON")"
|
|
if [ ! -x "$OXIDNS_BIN" ] || [ -z "$CURRENT_VERSION" ]; then
|
|
core_progress_log "error" "core is not installed or cannot report its version"
|
|
json_error "core_not_installed" "OxiDNS core is not installed or cannot report its version"
|
|
return 1
|
|
fi
|
|
core_progress_log "info" "current_version=$CURRENT_VERSION current_bundle=${CURRENT_BUNDLE:-$CORE_BUNDLE}"
|
|
[ -n "$CURRENT_BUNDLE" ] || CURRENT_BUNDLE="$CORE_BUNDLE"
|
|
install_core_from_release reinstall "$(version_tag "$CURRENT_VERSION")" "$CURRENT_BUNDLE"
|
|
}
|
|
|
|
emit_core_reinstall() {
|
|
run_with_core_lock emit_core_reinstall_locked
|
|
}
|
|
|
|
upload_core_error() {
|
|
ERROR_CODE="$1"
|
|
ERROR_MESSAGE="$2"
|
|
ERROR_STAGE="${3:-}"
|
|
ERROR_DETAIL="${4:-}"
|
|
rm -rf "${CORE_TMP_DIR:-}" 2>/dev/null || true
|
|
[ -n "${UPLOAD_PATH:-}" ] && rm -f "$UPLOAD_PATH" 2>/dev/null || true
|
|
core_progress_log "error" "$ERROR_CODE: $ERROR_MESSAGE"
|
|
[ -n "$ERROR_STAGE" ] && core_progress_log "error" "stage=$ERROR_STAGE"
|
|
[ -n "$ERROR_DETAIL" ] && core_progress_log "error" "$ERROR_DETAIL"
|
|
json_detailed_error "$ERROR_CODE" "$ERROR_MESSAGE" "$ERROR_STAGE" "$ERROR_DETAIL"
|
|
return 1
|
|
}
|
|
|
|
valid_upload_path() {
|
|
case "$1" in
|
|
/tmp/oxidns-core-upload-*) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
validate_uploaded_binary() {
|
|
BIN="$1"
|
|
chmod 755 "$BIN"
|
|
if command -v hexdump >/dev/null 2>&1; then
|
|
MAGIC="$(dd if="$BIN" bs=4 count=1 2>/dev/null | hexdump -v -e '1/1 "%02x"')"
|
|
[ "$MAGIC" = "7f454c46" ] || return 1
|
|
elif command -v od >/dev/null 2>&1; then
|
|
MAGIC="$(dd if="$BIN" bs=4 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
|
[ "$MAGIC" = "7f454c46" ] || return 1
|
|
fi
|
|
CHECK_BIN="$(dirname "$OXIDNS_BIN")/.oxidns-upload-check.$$"
|
|
mkdir -p "$(dirname "$OXIDNS_BIN")"
|
|
cp "$BIN" "$CHECK_BIN" || return 1
|
|
chmod 755 "$CHECK_BIN"
|
|
set +e
|
|
"$CHECK_BIN" build-info >/dev/null 2>&1
|
|
VALIDATE_RC="$?"
|
|
set -e
|
|
rm -f "$CHECK_BIN"
|
|
[ "$VALIDATE_RC" -eq 0 ]
|
|
}
|
|
|
|
install_uploaded_core_from_unpack() {
|
|
UPLOAD_KIND="$1"
|
|
UNPACK_DIR="$2"
|
|
HAS_WEBUI=0
|
|
CONFIG_CHECK_OK=0
|
|
CONFIG_CHECK_OUTPUT=""
|
|
SERVICE_RESTART="not_requested"
|
|
|
|
if [ ! -f "$UNPACK_DIR/oxidns" ]; then
|
|
upload_core_error "uploaded_core_invalid" "uploaded archive does not contain an oxidns binary" "validate_uploaded_core" "expected file: oxidns"
|
|
return 1
|
|
fi
|
|
core_progress_command "validate uploaded oxidns binary"
|
|
if ! validate_uploaded_binary "$UNPACK_DIR/oxidns"; then
|
|
upload_core_error "uploaded_core_invalid" "uploaded oxidns binary cannot report build-info" "validate_uploaded_core" "the uploaded binary either is not executable for this target or build-info failed"
|
|
return 1
|
|
fi
|
|
core_progress_log "success" "uploaded binary passed validation"
|
|
|
|
CONFIG_TMP=""
|
|
if [ ! -e "$CONFIG_PATH" ] && [ -f "$UNPACK_DIR/config.yaml" ]; then
|
|
CONFIG_TMP="$CORE_TMP_DIR/config.yaml.openwrt"
|
|
core_progress_command "adapt uploaded config.yaml for OpenWrt defaults"
|
|
if ! write_openwrt_default_config "$UNPACK_DIR/config.yaml" "$CONFIG_TMP"; then
|
|
upload_core_error "default_config_unsupported" "uploaded config.yaml cannot be adapted for OpenWrt defaults" "install_files" "config=$UNPACK_DIR/config.yaml"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
SERVICE_WAS_RUNNING=0
|
|
if service_running; then SERVICE_WAS_RUNNING=1; fi
|
|
INIT="$(init_script)"
|
|
if [ "$SERVICE_WAS_RUNNING" = "1" ] && [ -x "$INIT" ]; then
|
|
core_progress_command "$INIT stop"
|
|
SERVICE_OUT="$(mktemp "$TMP_ROOT/oxidns-service.XXXXXX")"
|
|
if ! run_init_action "$INIT" stop >"$SERVICE_OUT" 2>&1; then
|
|
MSG="$(read_error_detail_file "$SERVICE_OUT" 2>/dev/null || true)"
|
|
rm -f "$SERVICE_OUT"
|
|
upload_core_error "service_stop_failed" "core was not replaced because the service could not be stopped" "stop_service" "${MSG:-service stop failed}"
|
|
return 1
|
|
fi
|
|
rm -f "$SERVICE_OUT"
|
|
fi
|
|
|
|
core_progress_command "install -m 755 $UNPACK_DIR/oxidns $OXIDNS_BIN"
|
|
mkdir -p "$(dirname "$OXIDNS_BIN")" "$WORKING_DIR"
|
|
cp "$UNPACK_DIR/oxidns" "$CORE_TMP_DIR/oxidns.new"
|
|
chmod 755 "$CORE_TMP_DIR/oxidns.new"
|
|
mv "$CORE_TMP_DIR/oxidns.new" "$OXIDNS_BIN"
|
|
|
|
if [ -d "$UNPACK_DIR/webui" ]; then
|
|
core_progress_command "copy webui files to $WEBUI_DIR"
|
|
WEBUI_TMP="$CORE_TMP_DIR/webui.new"
|
|
rm -rf "$WEBUI_TMP"
|
|
mkdir -p "$(dirname "$WEBUI_DIR")" "$WEBUI_TMP"
|
|
cp -R "$UNPACK_DIR/webui/." "$WEBUI_TMP/"
|
|
rm -rf "$WEBUI_DIR"
|
|
mv "$WEBUI_TMP" "$WEBUI_DIR"
|
|
HAS_WEBUI=1
|
|
fi
|
|
|
|
if [ -n "$CONFIG_TMP" ]; then
|
|
core_progress_command "install default config to $CONFIG_PATH"
|
|
mkdir -p "$(dirname "$CONFIG_PATH")"
|
|
mv "$CONFIG_TMP" "$CONFIG_PATH"
|
|
elif [ -f "$UNPACK_DIR/config.yaml" ]; then
|
|
core_progress_command "copy config example to $WORKING_DIR/config.yaml.example"
|
|
cp "$UNPACK_DIR/config.yaml" "$WORKING_DIR/config.yaml.example" 2>/dev/null || true
|
|
fi
|
|
|
|
if [ -e "$CONFIG_PATH" ]; then
|
|
core_progress_command "$OXIDNS_BIN check -c $CONFIG_PATH -d $WORKING_DIR"
|
|
if CONFIG_CHECK_OUTPUT="$(validate_config_file "$CONFIG_PATH" 2>&1)"; then
|
|
CONFIG_CHECK_OK=1
|
|
core_progress_log "success" "$CONFIG_CHECK_OUTPUT"
|
|
else
|
|
CONFIG_CHECK_OUTPUT="${CONFIG_CHECK_OUTPUT:-configuration check failed}"
|
|
core_progress_log "error" "$CONFIG_CHECK_OUTPUT"
|
|
fi
|
|
else
|
|
CONFIG_CHECK_OUTPUT="configuration file is not installed"
|
|
core_progress_log "error" "$CONFIG_CHECK_OUTPUT"
|
|
fi
|
|
|
|
if [ "$SERVICE_WAS_RUNNING" = "1" ] && [ -x "$INIT" ]; then
|
|
SERVICE_OUT="$(mktemp "$TMP_ROOT/oxidns-service.XXXXXX")"
|
|
core_progress_command "$INIT start"
|
|
if "$INIT" start >"$SERVICE_OUT" 2>&1; then
|
|
SERVICE_RESTART="requested"
|
|
core_progress_log "success" "service restarted"
|
|
else
|
|
MSG="$(read_error_detail_file "$SERVICE_OUT" 2>/dev/null || true)"
|
|
rm -f "$SERVICE_OUT"
|
|
upload_core_error "service_restart_failed" "core installed, but service restart failed" "restart_service" "${MSG:-service restart failed}"
|
|
return 1
|
|
fi
|
|
rm -f "$SERVICE_OUT"
|
|
fi
|
|
|
|
BUILD_JSON="$(build_info_json)"
|
|
VERSION="$(json_value_from_string version "$BUILD_JSON")"
|
|
BUNDLE="$(json_value_from_string bundle "$BUILD_JSON")"
|
|
rm -f "$UPLOAD_PATH"
|
|
rm -rf "$CORE_TMP_DIR"
|
|
core_progress_log "success" "uploaded core installed: version=${VERSION:-unknown} bundle=${BUNDLE:-unknown}"
|
|
printf '{"ok":true,"action":"upload_install","kind":'
|
|
json_string "$UPLOAD_KIND"
|
|
printf ',"version":'
|
|
json_string "$VERSION"
|
|
printf ',"bundle":'
|
|
json_string "$BUNDLE"
|
|
printf ',"binary_path":'
|
|
json_string "$OXIDNS_BIN"
|
|
printf ',"webui_installed":'
|
|
json_bool "$HAS_WEBUI"
|
|
printf ',"webui_path":'
|
|
json_string "$WEBUI_DIR"
|
|
printf ',"service_restart":'
|
|
json_string "$SERVICE_RESTART"
|
|
printf ',"config_check":{"ok":'
|
|
json_bool "$CONFIG_CHECK_OK"
|
|
printf ',"message":'
|
|
json_string "$CONFIG_CHECK_OUTPUT"
|
|
printf '}}\n'
|
|
}
|
|
|
|
emit_core_upload_install_locked() {
|
|
load_settings
|
|
core_progress_reset "install uploaded OxiDNS core"
|
|
INPUT="$(cat)"
|
|
UPLOAD_PATH="$(input_value path "$INPUT")"
|
|
core_progress_log "info" "uploaded_path=${UPLOAD_PATH:-missing}"
|
|
|
|
if ! valid_upload_path "$UPLOAD_PATH" || [ ! -f "$UPLOAD_PATH" ]; then
|
|
core_progress_log "error" "invalid upload path"
|
|
json_detailed_error "invalid_upload_path" "uploaded file must be a readable /tmp/oxidns-core-upload-* file" "validate_uploaded_core" "path=${UPLOAD_PATH:-missing}"
|
|
return 1
|
|
fi
|
|
|
|
CORE_TMP_DIR="$(mktemp -d "$TMP_ROOT/oxidns-core-upload.XXXXXX")"
|
|
UNPACK_DIR="$CORE_TMP_DIR/unpack"
|
|
mkdir -p "$UNPACK_DIR"
|
|
|
|
core_progress_command "tar -tzf $UPLOAD_PATH"
|
|
if tar -tzf "$UPLOAD_PATH" >/dev/null 2>&1; then
|
|
if ! validate_safe_archive "$UPLOAD_PATH"; then
|
|
upload_core_error "uploaded_archive_unsafe" "uploaded archive contains unsafe paths or links" "validate_archive" "archive=$UPLOAD_PATH"
|
|
return 1
|
|
fi
|
|
TAR_OUT="$(mktemp "$TMP_ROOT/oxidns-upload-tar.XXXXXX")"
|
|
core_progress_command "tar -xzf $UPLOAD_PATH -C $UNPACK_DIR"
|
|
if ! tar -xzf "$UPLOAD_PATH" -C "$UNPACK_DIR" >"$TAR_OUT" 2>&1; then
|
|
TAR_DETAIL="$(read_error_detail_file "$TAR_OUT" 2>/dev/null || true)"
|
|
rm -f "$TAR_OUT"
|
|
upload_core_error "uploaded_archive_unpack_failed" "failed to unpack uploaded OxiDNS archive" "unpack_archive" "${TAR_DETAIL:-tar extraction failed}"
|
|
return 1
|
|
fi
|
|
rm -f "$TAR_OUT"
|
|
install_uploaded_core_from_unpack "archive" "$UNPACK_DIR"
|
|
return $?
|
|
fi
|
|
|
|
core_progress_command "cp $UPLOAD_PATH $UNPACK_DIR/oxidns"
|
|
cp "$UPLOAD_PATH" "$UNPACK_DIR/oxidns"
|
|
install_uploaded_core_from_unpack "binary" "$UNPACK_DIR"
|
|
}
|
|
|
|
emit_core_upload_install() {
|
|
run_with_core_lock emit_core_upload_install_locked
|
|
}
|
|
|
|
emit_core_remove_locked() {
|
|
INPUT="$(cat)"
|
|
load_settings
|
|
core_progress_reset "remove OxiDNS core"
|
|
REMOVE_CONFIG="$(input_bool remove_config "$INPUT")"
|
|
REMOVE_WORKDIR="$(input_bool remove_workdir "$INPUT")"
|
|
CONFIG_REMOVED=0
|
|
WORKDIR_REMOVED=0
|
|
CONFIG_BACKUP=""
|
|
|
|
if [ "$REMOVE_CONFIG" = "1" ] && ! safe_remove_config_path "$CONFIG_PATH"; then
|
|
core_progress_log "error" "refusing unsafe config path: $CONFIG_PATH"
|
|
json_detailed_error "unsafe_config_path" "refusing to delete unsafe configuration path" "remove_config" "config=$CONFIG_PATH"
|
|
return 1
|
|
fi
|
|
if [ "$REMOVE_WORKDIR" = "1" ] && ! safe_remove_working_dir "$WORKING_DIR"; then
|
|
core_progress_log "error" "refusing unsafe working directory: $WORKING_DIR"
|
|
json_detailed_error "unsafe_working_dir" "refusing to delete unsafe working directory" "remove_working_dir" "working_dir=$WORKING_DIR"
|
|
return 1
|
|
fi
|
|
if [ "$REMOVE_WORKDIR" = "1" ] && [ "$REMOVE_CONFIG" != "1" ] && path_is_under "$CONFIG_PATH" "$WORKING_DIR" && [ -e "$CONFIG_PATH" ]; then
|
|
CONFIG_BACKUP="$(mktemp "$TMP_ROOT/oxidns-config-preserve.XXXXXX")"
|
|
core_progress_command "preserve $CONFIG_PATH before removing $WORKING_DIR"
|
|
if cp "$CONFIG_PATH" "$CONFIG_BACKUP" 2>/dev/null; then
|
|
:
|
|
else
|
|
rm -f "$CONFIG_BACKUP"
|
|
CONFIG_BACKUP=""
|
|
core_progress_log "error" "failed to preserve config before removing working directory"
|
|
json_detailed_error "config_preserve_failed" "failed to preserve configuration before removing working directory" "remove_working_dir" "config=$CONFIG_PATH working_dir=$WORKING_DIR"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
INIT="$(init_script)"
|
|
if [ -x "$INIT" ]; then
|
|
core_progress_command "$INIT stop"
|
|
"$INIT" stop >/dev/null 2>&1 || true
|
|
core_progress_command "$INIT disable"
|
|
"$INIT" disable >/dev/null 2>&1 || true
|
|
fi
|
|
core_progress_command "rm -f $OXIDNS_BIN"
|
|
rm -f "$OXIDNS_BIN"
|
|
core_progress_command "rm -rf $WEBUI_DIR"
|
|
rm -rf "$WEBUI_DIR"
|
|
|
|
if [ "$REMOVE_CONFIG" = "1" ]; then
|
|
core_progress_command "rm -f $CONFIG_PATH"
|
|
rm -f "$CONFIG_PATH"
|
|
CONFIG_REMOVED=1
|
|
fi
|
|
if [ "$REMOVE_WORKDIR" = "1" ]; then
|
|
core_progress_command "rm -rf $WORKING_DIR"
|
|
rm -rf "$WORKING_DIR"
|
|
WORKDIR_REMOVED=1
|
|
fi
|
|
if [ -n "$CONFIG_BACKUP" ] && [ -f "$CONFIG_BACKUP" ]; then
|
|
core_progress_command "restore preserved config to $CONFIG_PATH"
|
|
mkdir -p "$(dirname "$CONFIG_PATH")"
|
|
mv "$CONFIG_BACKUP" "$CONFIG_PATH"
|
|
fi
|
|
core_progress_log "success" "core removed"
|
|
printf '{"ok":true,"action":"remove","binary_path":'
|
|
json_string "$OXIDNS_BIN"
|
|
printf ',"webui_path":'
|
|
json_string "$WEBUI_DIR"
|
|
printf ',"config_path":'
|
|
json_string "$CONFIG_PATH"
|
|
printf ',"working_dir":'
|
|
json_string "$WORKING_DIR"
|
|
printf ',"config_removed":'
|
|
json_bool "$CONFIG_REMOVED"
|
|
printf ',"working_dir_removed":'
|
|
json_bool "$WORKDIR_REMOVED"
|
|
printf ',"config_preserved":'
|
|
if [ "$CONFIG_REMOVED" = "1" ]; then json_bool 0; else json_bool 1; fi
|
|
printf ',"working_dir_preserved":'
|
|
if [ "$WORKDIR_REMOVED" = "1" ]; then json_bool 0; else json_bool 1; fi
|
|
printf '}\n'
|
|
}
|
|
|
|
emit_core_remove() {
|
|
run_with_core_lock emit_core_remove_locked
|
|
}
|
|
|
|
case "${1:-}" in
|
|
list)
|
|
printf '{'
|
|
printf '"status":{},'
|
|
printf '"core_install":{},'
|
|
printf '"core_reinstall":{},'
|
|
printf '"core_upload_install":{"path":"string"},'
|
|
printf '"core_progress":{},'
|
|
printf '"core_remove":{"remove_config":"boolean","remove_workdir":"boolean"},'
|
|
printf '"service_start":{},'
|
|
printf '"service_stop":{},'
|
|
printf '"service_restart":{},'
|
|
printf '"service_enable":{},'
|
|
printf '"service_disable":{},'
|
|
printf '"config_read":{},'
|
|
printf '"config_validate":{"content":"string"},'
|
|
printf '"config_save":{"content":"string","base_mtime":"string","restart":"boolean"},'
|
|
printf '"settings_read":{},'
|
|
printf '"settings_save":{"core_repository":"string","core_bundle":"string","config_path":"string","working_dir":"string","download_proxy":"string","github_token":"string","clear_download_proxy":"boolean","clear_github_token":"boolean","log_syslog":"string"},'
|
|
printf '"logs_recent":{"limit":"number"}'
|
|
printf '}\n'
|
|
;;
|
|
call)
|
|
case "${2:-}" in
|
|
status)
|
|
emit_status
|
|
;;
|
|
core_install)
|
|
emit_core_install
|
|
;;
|
|
core_reinstall)
|
|
emit_core_reinstall
|
|
;;
|
|
core_upload_install)
|
|
emit_core_upload_install
|
|
;;
|
|
core_progress)
|
|
emit_core_progress
|
|
;;
|
|
core_remove)
|
|
emit_core_remove
|
|
;;
|
|
service_start)
|
|
run_service start
|
|
;;
|
|
service_stop)
|
|
run_service stop
|
|
;;
|
|
service_restart)
|
|
run_service restart
|
|
;;
|
|
service_enable)
|
|
run_service enable
|
|
;;
|
|
service_disable)
|
|
run_service disable
|
|
;;
|
|
config_read)
|
|
emit_config_read
|
|
;;
|
|
config_validate)
|
|
emit_config_validate
|
|
;;
|
|
config_save)
|
|
emit_config_save
|
|
;;
|
|
settings_read)
|
|
emit_settings_read
|
|
;;
|
|
settings_save)
|
|
emit_settings_save
|
|
;;
|
|
logs_recent)
|
|
emit_logs_recent
|
|
;;
|
|
*)
|
|
printf '{"ok":false,"error":"unknown method"}\n'
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
printf '{"ok":false,"error":"usage: luci.oxidns list|call <method>"}\n'
|
|
exit 1
|
|
;;
|
|
esac
|