mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-13 20:04:45 +08:00
533 lines
13 KiB
Bash
Executable File
533 lines
13 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
. /usr/share/libubox/jshn.sh
|
|
|
|
BASE_DIR="/tmp/luci-app-run"
|
|
UPLOAD_DIR="$BASE_DIR/uploads"
|
|
RUN_DIR="$BASE_DIR/runs"
|
|
STATE_FILE="$BASE_DIR/state"
|
|
MAX_SIZE=$((256 * 1024 * 1024))
|
|
|
|
umask 077
|
|
|
|
mkdir -p "$UPLOAD_DIR" "$RUN_DIR"
|
|
|
|
reply() {
|
|
printf '%s\n' "$1"
|
|
}
|
|
|
|
json_error() {
|
|
local msg="$1"
|
|
json_init
|
|
json_add_int code 1
|
|
json_add_string error "$msg"
|
|
json_dump
|
|
}
|
|
|
|
json_ok() {
|
|
json_init
|
|
json_add_int code 0
|
|
[ -n "$1" ] && json_add_string message "$1"
|
|
json_dump
|
|
}
|
|
|
|
sanitize_name() {
|
|
local name="$1"
|
|
name="${name##*/}"
|
|
name="${name##*\\}"
|
|
printf '%s' "$name" | tr -c 'A-Za-z0-9._-' '_'
|
|
}
|
|
|
|
valid_id() {
|
|
case "$1" in
|
|
""|*[!A-Za-z0-9_-]*) return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
load_input() {
|
|
local input
|
|
input="$(cat)"
|
|
json_load "$input" >/dev/null 2>&1
|
|
}
|
|
|
|
get_string() {
|
|
json_get_var "$1" "$1"
|
|
}
|
|
|
|
get_int() {
|
|
json_get_var "$1" "$1"
|
|
}
|
|
|
|
new_token() {
|
|
local token
|
|
token="$(hexdump -n 16 -e '16/1 "%02x"' /dev/urandom 2>/dev/null)"
|
|
[ -n "$token" ] || token="$(date +%s)_$$_$(awk 'BEGIN{srand();print int(rand()*1000000)}')"
|
|
printf '%s' "$token" | tr -c 'A-Za-z0-9_' '_'
|
|
}
|
|
|
|
session_dir() {
|
|
printf '%s/%s' "$UPLOAD_DIR" "$1"
|
|
}
|
|
|
|
write_state() {
|
|
local pid="$1"
|
|
local file="$2"
|
|
local log="$3"
|
|
local started="$4"
|
|
local type="$5"
|
|
{
|
|
printf 'PID=%s\n' "$pid"
|
|
printf 'FILE=%s\n' "$file"
|
|
printf 'LOG=%s\n' "$log"
|
|
printf 'STARTED=%s\n' "$started"
|
|
printf 'TYPE=%s\n' "$type"
|
|
} > "$STATE_FILE"
|
|
}
|
|
|
|
read_state() {
|
|
[ -f "$STATE_FILE" ] && . "$STATE_FILE"
|
|
}
|
|
|
|
is_running() {
|
|
local pid="$1"
|
|
[ -n "$pid" ] || return 1
|
|
|
|
[ -d "/proc/$pid" ] || return 1
|
|
|
|
local cmdline
|
|
cmdline="$(cat "/proc/$pid/cmdline" 2>/dev/null | tr '\0' ' ')"
|
|
|
|
echo "$cmdline" | grep -q "luci-app-run" && return 0
|
|
|
|
# Check for .run files (must be followed by space or end of string)
|
|
case "$cmdline" in
|
|
*".run "*) return 0 ;;
|
|
*".run") return 0 ;;
|
|
esac
|
|
|
|
# Check for .sh files (must be followed by space or end of string)
|
|
case "$cmdline" in
|
|
*".sh "*) return 0 ;;
|
|
*".sh") return 0 ;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
method_list() {
|
|
cat <<'JSON'
|
|
{
|
|
"upload_start": {
|
|
"filename": "String",
|
|
"size": "Integer"
|
|
},
|
|
"upload_chunk": {
|
|
"id": "String",
|
|
"data": "String",
|
|
"index": "Integer"
|
|
},
|
|
"upload_finish": {
|
|
"id": "String"
|
|
},
|
|
"run": {
|
|
"id": "String",
|
|
"args": "String"
|
|
},
|
|
"status": {},
|
|
"read_log": {
|
|
"offset": "Integer"
|
|
},
|
|
"download_run": {
|
|
"url": "String"
|
|
},
|
|
"cleanup": {},
|
|
"version": {},
|
|
"capabilities": {}
|
|
}
|
|
JSON
|
|
}
|
|
|
|
upload_start() {
|
|
load_input
|
|
get_string filename
|
|
get_int size
|
|
|
|
[ -n "$filename" ] || { json_error "Missing filename"; return; }
|
|
[ -n "$size" ] || size=0
|
|
[ "$size" -le "$MAX_SIZE" ] 2>/dev/null || { json_error "File is larger than 256 MiB"; return; }
|
|
|
|
local clean id token dir file
|
|
clean="$(sanitize_name "$filename")"
|
|
case "$clean" in
|
|
*.run|*.sh|*.ipk|*.apk) ;;
|
|
*) json_error "Only .run, .sh, .ipk and .apk files are accepted"; return ;;
|
|
esac
|
|
|
|
id="$(date +%s)_$$"
|
|
token="$(new_token)"
|
|
dir="$(session_dir "$id")"
|
|
file="$dir/$clean"
|
|
mkdir -p "$dir" || { json_error "Unable to create upload directory"; return; }
|
|
: > "$file" || { json_error "Unable to create upload file"; return; }
|
|
printf '%s\n' "$clean" > "$dir/name"
|
|
printf '%s\n' "$size" > "$dir/size"
|
|
printf '%s\n' "$token" > "$dir/token"
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_string id "$id"
|
|
json_add_string token "$token"
|
|
json_add_string filename "$clean"
|
|
json_dump
|
|
}
|
|
|
|
upload_chunk() {
|
|
load_input
|
|
get_string id
|
|
get_string data
|
|
get_int index
|
|
|
|
valid_id "$id" || { json_error "Invalid upload id"; return; }
|
|
[ -n "$data" ] || { json_error "Missing chunk data"; return; }
|
|
|
|
local dir name file
|
|
dir="$(session_dir "$id")"
|
|
[ -d "$dir" ] || { json_error "Unknown upload id"; return; }
|
|
name="$(cat "$dir/name" 2>/dev/null)"
|
|
file="$dir/$name"
|
|
|
|
printf '%s' "$data" | base64 -d >> "$file" 2>/dev/null || {
|
|
json_error "Unable to decode upload chunk"
|
|
return
|
|
}
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_int index "$index"
|
|
json_add_int received "$(wc -c < "$file" 2>/dev/null)"
|
|
json_dump
|
|
}
|
|
|
|
upload_finish() {
|
|
load_input
|
|
get_string id
|
|
|
|
valid_id "$id" || { json_error "Invalid upload id"; return; }
|
|
|
|
local dir name file expected actual
|
|
dir="$(session_dir "$id")"
|
|
[ -d "$dir" ] || { json_error "Unknown upload id"; return; }
|
|
name="$(cat "$dir/name" 2>/dev/null)"
|
|
expected="$(cat "$dir/size" 2>/dev/null)"
|
|
file="$dir/$name"
|
|
actual="$(wc -c < "$file" 2>/dev/null)"
|
|
|
|
[ -s "$file" ] || { json_error "Uploaded file is empty"; return; }
|
|
if [ -n "$expected" ] && [ "$expected" -gt 0 ] 2>/dev/null; then
|
|
[ "$actual" -eq "$expected" ] 2>/dev/null || {
|
|
json_error "Upload size mismatch"
|
|
return
|
|
}
|
|
fi
|
|
|
|
chmod 700 "$file" || { json_error "Unable to chmod uploaded file"; return; }
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_string id "$id"
|
|
json_add_string filename "$name"
|
|
json_add_int size "$actual"
|
|
json_dump
|
|
}
|
|
|
|
download_run() {
|
|
load_input
|
|
get_string url
|
|
|
|
[ -n "$url" ] || { json_error "Missing URL"; return; }
|
|
|
|
local filename id dir file log work pid now
|
|
|
|
# Extract filename from URL - get everything after the last /
|
|
filename="${url##*/}"
|
|
# Remove query parameters if any
|
|
filename="${filename%%\?*}"
|
|
|
|
case "$filename" in
|
|
*.run) ;;
|
|
*) json_error "Only .run files are supported for download"; return ;;
|
|
esac
|
|
|
|
read_state
|
|
if [ -n "$PID" ] && ! is_running "$PID"; then
|
|
rm -f "$STATE_FILE"
|
|
unset PID
|
|
fi
|
|
|
|
if is_running "$PID"; then
|
|
json_error "Another installer is already running"
|
|
return
|
|
fi
|
|
|
|
id="download_$(date +%s)"
|
|
dir="$RUN_DIR/$id"
|
|
file="$dir/$filename"
|
|
log="$dir/output.log"
|
|
|
|
mkdir -p "$dir" || { json_error "Unable to create download directory"; return; }
|
|
|
|
now="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
(
|
|
cd "$dir" || exit 1
|
|
printf 'luci-app-run: started %s\n' "$now"
|
|
printf 'luci-app-run: downloading %s\n\n' "$url"
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -L -o "$filename" "$url"
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
wget -O "$filename" "$url"
|
|
else
|
|
printf 'luci-app-run: error: neither curl nor wget is available\n'
|
|
exit 1
|
|
fi
|
|
|
|
rc=$?
|
|
if [ "$rc" -ne 0 ]; then
|
|
printf '\nluci-app-run: download failed with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit "$rc"
|
|
fi
|
|
|
|
printf '\nluci-app-run: download completed at %s\n' "$(date '+%Y-%m-%d %H:%M:%S')"
|
|
printf '\nluci-app-run: executing %s\n\n' "$file"
|
|
|
|
chmod 700 "$file" 2>/dev/null || true
|
|
|
|
"$file"
|
|
rc=$?
|
|
printf '\nluci-app-run: exited with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit "$rc"
|
|
) > "$log" 2>&1 &
|
|
|
|
pid="$!"
|
|
write_state "$pid" "$file" "$log" "$now" "download"
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_int pid "$pid"
|
|
json_add_string log "$log"
|
|
json_add_string filename "$filename"
|
|
json_dump
|
|
}
|
|
|
|
run_installer() {
|
|
load_input
|
|
get_string id
|
|
get_string args
|
|
|
|
valid_id "$id" || { json_error "Invalid upload id"; return; }
|
|
|
|
local dir name file log work pid now
|
|
dir="$(session_dir "$id")"
|
|
[ -d "$dir" ] || { json_error "Unknown upload id"; return; }
|
|
name="$(cat "$dir/name" 2>/dev/null)"
|
|
file="$dir/$name"
|
|
|
|
read_state
|
|
if [ -n "$PID" ] && ! is_running "$PID"; then
|
|
rm -f "$STATE_FILE"
|
|
unset PID
|
|
fi
|
|
|
|
if is_running "$PID"; then
|
|
json_error "Another installer is already running"
|
|
return
|
|
fi
|
|
|
|
now="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
work="$RUN_DIR/$id"
|
|
log="$work/output.log"
|
|
mkdir -p "$work" || { json_error "Unable to create run directory"; return; }
|
|
|
|
case "$name" in
|
|
*.ipk)
|
|
if ! command -v opkg >/dev/null 2>&1; then
|
|
json_error "ERR_NO_OPKG"
|
|
return
|
|
fi
|
|
(
|
|
cd "$work" || exit 1
|
|
printf 'luci-app-run: started %s\n' "$now"
|
|
printf 'luci-app-run: installing ipk package %s\n\n' "$file"
|
|
opkg install "$file"
|
|
rc=$?
|
|
printf '\nluci-app-run: opkg exited with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit "$rc"
|
|
) > "$log" 2>&1 &
|
|
;;
|
|
*.apk)
|
|
if ! command -v apk >/dev/null 2>&1; then
|
|
json_error "ERR_NO_APK"
|
|
return
|
|
fi
|
|
(
|
|
cd "$work" || exit 1
|
|
printf 'luci-app-run: started %s\n' "$now"
|
|
printf 'luci-app-run: installing apk package %s\n\n' "$file"
|
|
apk add --allow-untrusted "$file"
|
|
rc=$?
|
|
printf '\nluci-app-run: apk exited with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit "$rc"
|
|
) > "$log" 2>&1 &
|
|
;;
|
|
*)
|
|
chmod 700 "$file" 2>/dev/null || true
|
|
|
|
[ -x "$file" ] || {
|
|
json_error "Uploaded file is not executable. Please check file permissions."
|
|
return
|
|
}
|
|
|
|
(
|
|
cd "$work" || exit 1
|
|
printf 'luci-app-run: started %s\n' "$now"
|
|
printf 'luci-app-run: executing %s %s\n\n' "$file" "$args"
|
|
"$file" $args
|
|
rc=$?
|
|
printf '\nluci-app-run: exited with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit "$rc"
|
|
) > "$log" 2>&1 &
|
|
;;
|
|
esac
|
|
|
|
pid="$!"
|
|
write_state "$pid" "$file" "$log" "$now" "upload"
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_int pid "$pid"
|
|
json_add_string log "$log"
|
|
json_dump
|
|
}
|
|
|
|
status() {
|
|
read_state
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
if is_running "$PID"; then
|
|
json_add_boolean running 1
|
|
else
|
|
json_add_boolean running 0
|
|
fi
|
|
[ -n "$PID" ] && json_add_int pid "$PID"
|
|
[ -n "$FILE" ] && json_add_string file "$FILE"
|
|
[ -n "$LOG" ] && json_add_string log "$LOG"
|
|
[ -n "$STARTED" ] && json_add_string started "$STARTED"
|
|
json_dump
|
|
}
|
|
|
|
read_log() {
|
|
load_input
|
|
get_int offset
|
|
[ -n "$offset" ] || offset=0
|
|
|
|
read_state
|
|
[ -n "$LOG" ] && [ -f "$LOG" ] || {
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_string data ""
|
|
json_add_int offset 0
|
|
json_add_boolean running 0
|
|
json_dump
|
|
return
|
|
}
|
|
|
|
local size data start
|
|
size="$(wc -c < "$LOG" 2>/dev/null)"
|
|
[ "$offset" -le "$size" ] 2>/dev/null || offset=0
|
|
start=$((offset + 1))
|
|
data="$(tail -c +"$start" "$LOG" 2>/dev/null)"
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_string data "$data"
|
|
json_add_int offset "$size"
|
|
if is_running "$PID"; then
|
|
json_add_boolean running 1
|
|
else
|
|
json_add_boolean running 0
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
cleanup() {
|
|
read_state
|
|
if is_running "$PID"; then
|
|
json_error "Cannot clean up while installer is running"
|
|
return
|
|
fi
|
|
|
|
rm -rf "$UPLOAD_DIR"
|
|
mkdir -p "$UPLOAD_DIR"
|
|
|
|
# Keep state file but clear PID and FILE, preserve LOG path
|
|
if [ -n "$LOG" ]; then
|
|
echo "PID=" > "$STATE_FILE"
|
|
echo "FILE=" >> "$STATE_FILE"
|
|
echo "LOG=$LOG" >> "$STATE_FILE"
|
|
echo "STARTED=" >> "$STATE_FILE"
|
|
else
|
|
> "$STATE_FILE"
|
|
fi
|
|
|
|
json_ok "Cleaned"
|
|
}
|
|
|
|
version() {
|
|
local ver="unknown"
|
|
[ -f "/tmp/luci-app-run.version" ] && ver="$(cat /tmp/luci-app-run.version)"
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_string version "$ver"
|
|
json_dump
|
|
}
|
|
|
|
capabilities() {
|
|
local has_opkg=0
|
|
local has_apk=0
|
|
|
|
command -v opkg >/dev/null 2>&1 && has_opkg=1
|
|
command -v apk >/dev/null 2>&1 && has_apk=1
|
|
|
|
json_init
|
|
json_add_int code 0
|
|
json_add_int opkg "$has_opkg"
|
|
json_add_int apk "$has_apk"
|
|
json_dump
|
|
}
|
|
|
|
case "$1" in
|
|
list)
|
|
method_list
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
upload_start) upload_start ;;
|
|
upload_chunk) upload_chunk ;;
|
|
upload_finish) upload_finish ;;
|
|
run) run_installer ;;
|
|
download_run) download_run ;;
|
|
status) status ;;
|
|
read_log) read_log ;;
|
|
cleanup) cleanup ;;
|
|
version) version ;;
|
|
capabilities) capabilities ;;
|
|
*) json_error "Unknown method" ;;
|
|
esac
|
|
;;
|
|
*)
|
|
reply '{}'
|
|
;;
|
|
esac |