update 2022-07-18 20:18:11

This commit is contained in:
github-actions[bot] 2022-07-18 20:18:11 +08:00
parent 088d77842e
commit 694fac32eb
26 changed files with 1456 additions and 323 deletions

View File

@ -71,7 +71,7 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan
bool "Include Trojan"
default n
default y if i386||x86_64||arm||aarch64
config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
bool "Include NaiveProxy"

View File

@ -161,7 +161,7 @@ gen_config_file(){
tcp|udp|nf) smode=nat;;
socks) smode=client;;
esac
lua $BIN_DIR/gentrojanconfig $1 $smode $lport $serv_ip $threads >$config_file
lua $BIN_DIR/gen_config $1 $smode $lport $ssport $serv_ip >$config_file
sed -i 's/\\//g' $config_file
;;
esac

View File

@ -296,7 +296,7 @@ local hysteria = {
protocol = server.hysteria_protocol,
up_mbps = tonumber(server.uplink_capacity),
down_mbps = tonumber(server.downlink_capacity),
socks5 = (proto:find("tcp") and tonumber(socks_port) and tonumber(socks_port) ~= "0") and {
socks5 = (proto:find("tcp") and tonumber(socks_port) and tonumber(socks_port) ~= 0) and {
listen = "0.0.0.0:" .. tonumber(socks_port),
timeout = 300,
disable_udp = false

View File

@ -7,16 +7,16 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI based ipk store
LUCI_DESCRIPTION:=luci-app-store is a ipk store developed by LinkEase team
LUCI_DEPENDS:=+curl +opkg +luci-base +tar +coreutils +coreutils-stat +libuci-lua +mount-utils
LUCI_DEPENDS:=+curl +opkg +luci-base +tar +coreutils +coreutils-stat +libuci-lua +mount-utils +luci-lib-taskd
LUCI_PKGARCH:=all
PKG_VERSION:=0.1.10-7
PKG_VERSION:=0.1.11-1
# PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:=
ISTORE_UI_VERSION:=0.1.10
ISTORE_UI_RELEASE:=7
PKG_HASH:=ba784985b9b3aa90c704c03b16611922e7df2e1f0e18c4a4ec9bff0e09688426
ISTORE_UI_VERSION:=0.1.11
ISTORE_UI_RELEASE:=1
PKG_HASH:=e78b07c257c38892f028faeaea3a64ee47e11137523e1c046df95c87be7abf90
PKG_SOURCE_URL_FILE:=v$(ISTORE_UI_VERSION)-$(ISTORE_UI_RELEASE).tar.gz
PKG_SOURCE:=istore-ui-$(PKG_SOURCE_URL_FILE)

View File

@ -1,6 +1,7 @@
module("luci.controller.store", package.seeall)
local myopkg = "is-opkg"
local is_backup = "/usr/libexec/istore/backup"
local page_index = {"admin", "store", "pages"}
function index()
@ -24,25 +25,26 @@ function index()
entry({"admin", "store", "check_self_upgrade"}, call("check_self_upgrade"))
entry({"admin", "store", "do_self_upgrade"}, post("do_self_upgrade"))
entry({"admin", "store", "get_support_backup_features"}, call("get_support_backup_features"))
entry({"admin", "store", "light_backup"}, post("light_backup"))
entry({"admin", "store", "get_light_backup_file"}, call("get_light_backup_file"))
entry({"admin", "store", "local_backup"}, post("local_backup"))
entry({"admin", "store", "light_restore"}, post("light_restore"))
entry({"admin", "store", "local_restore"}, post("local_restore"))
entry({"admin", "store", "get_backup_app_list_file_path"}, call("get_backup_app_list_file_path"))
entry({"admin", "store", "get_backup_app_list"}, call("get_backup_app_list"))
entry({"admin", "store", "get_available_backup_file_list"}, call("get_available_backup_file_list"))
entry({"admin", "store", "set_local_backup_dir_path"}, post("set_local_backup_dir_path"))
entry({"admin", "store", "get_local_backup_dir_path"}, call("get_local_backup_dir_path"))
entry({"admin", "store", "get_block_devices"}, call("get_block_devices"))
for _, action in ipairs({"update", "install", "upgrade", "remove"}) do
store_api(action, true)
end
for _, action in ipairs({"status", "installed"}) do
store_api(action, false)
end
if nixio.fs.access("/usr/libexec/istore/backup") then
entry({"admin", "store", "get_support_backup_features"}, call("get_support_backup_features"))
entry({"admin", "store", "light_backup"}, post("light_backup"))
entry({"admin", "store", "get_light_backup_file"}, call("get_light_backup_file"))
entry({"admin", "store", "local_backup"}, post("local_backup"))
entry({"admin", "store", "light_restore"}, post("light_restore"))
entry({"admin", "store", "local_restore"}, post("local_restore"))
entry({"admin", "store", "get_backup_app_list_file_path"}, call("get_backup_app_list_file_path"))
entry({"admin", "store", "get_backup_app_list"}, call("get_backup_app_list"))
entry({"admin", "store", "get_available_backup_file_list"}, call("get_available_backup_file_list"))
entry({"admin", "store", "set_local_backup_dir_path"}, post("set_local_backup_dir_path"))
entry({"admin", "store", "get_local_backup_dir_path"}, call("get_local_backup_dir_path"))
entry({"admin", "store", "get_block_devices"}, call("get_block_devices"))
end
end
local function user_id()
@ -74,7 +76,7 @@ local function vue_lang()
return lang
end
local function is_exec(cmd)
local function is_exec(cmd, async)
local nixio = require "nixio"
local os = require "os"
local fs = require "nixio.fs"
@ -93,6 +95,9 @@ local function is_exec(cmd)
return 255, "", "Lock failed: " .. msg
end
if async then
cmd = "/etc/init.d/tasks task_add istore " .. luci.util.shellquote(cmd)
end
local r = os.execute(cmd .. " >/var/log/istore.stdout 2>/var/log/istore.stderr")
local e = fs.readfile("/var/log/istore.stderr")
local o = fs.readfile("/var/log/istore.stdout")
@ -115,7 +120,13 @@ function redirect_index()
end
function store_index()
luci.template.render("store/main", {prefix=luci.dispatcher.build_url(unpack(page_index)),id=user_id(),lang=vue_lang()})
local fs = require "nixio.fs"
local features = { "_lua_force_array_" }
if fs.access("/usr/libexec/istore/backup") then
features[#features+1] = "backup"
end
luci.template.render("store/main", {prefix=luci.dispatcher.build_url(unpack(page_index)),id=user_id(),lang=vue_lang(),features=features})
end
function store_dev()
@ -169,17 +180,15 @@ end
-- Internal action function
local function _action(exe, cmd, ...)
local os = require "os"
local fs = require "nixio.fs"
local pkg = ""
for k, v in pairs({...}) do
pkg = pkg .. " '" .. v:gsub("'", "") .. "'"
pkg = pkg .. " " .. luci.util.shellquote(v)
end
local c = "%s %s %s" %{ exe, cmd, pkg }
return is_exec(c)
return is_exec(c, true)
end
function store_action(param)
@ -241,11 +250,6 @@ function store_action(param)
code, out, err = _action(myopkg, action, unpack(pkgs))
else -- remove
code, out, err = _action(myopkg, action, unpack(pkgs))
if code ~= 0 then
code, out0, err0 = _action(myopkg, action, unpack(pkgs))
out = out .. out0
err = err .. err0
end
fs.unlink("/tmp/luci-indexcache")
end
end
@ -295,15 +299,15 @@ function store_upload()
out = ""
if finished then
if string.lower(string.sub(path, -4, -1)) == ".run" then
code, out, err = _action("sh", "-c", "ls -l \"%s\"; md5sum \"%s\" 2>/dev/null; chmod 755 \"%s\" && \"%s\"" %{ path, path, path, path })
code, out, err = _action("sh", "-c", "ls -l \"%s\"; md5sum \"%s\" 2>/dev/null; chmod 755 \"%s\" && \"%s\"; RET=$?; rm -f \"%s\"; exit $RET" %{ path, path, path, path, path })
else
code, out, err = _action("opkg", "install", path)
code, out, err = _action("sh", "-c", "opkg install \"%s\"; RET=$?; rm -f \"%s\"; exit $RET" %{ path, path })
end
else
code = 500
err = "upload failed!"
end
nixio.fs.unlink(path)
--nixio.fs.unlink(path)
local ret = {
code = code,
stdout = out,
@ -355,8 +359,8 @@ end
function get_support_backup_features()
local jsonc = require "luci.jsonc"
local error_ret = {code = 500, msg = "Unknown"}
local success_ret = {code = 200,msg = "Unknown"}
local r,o,e = is_exec(myopkg .. " get_support_backup_features")
local success_ret = {code = 200, msg = "Unknown"}
local r,o,e = is_exec(is_backup .. " get_support_backup_features")
if r ~= 0 then
error_ret.msg = e
luci.http.prepare_content("application/json")
@ -374,7 +378,7 @@ function light_backup()
local jsonc = require "luci.jsonc"
local error_ret = {code = 500, msg = "Unknown"}
local success_ret = {code = 200,msg = "Unknown"}
local r,o,e = is_exec(myopkg .. " backup")
local r,o,e = is_exec(is_backup .. " backup")
if r ~= 0 then
error_ret.msg = e
@ -432,13 +436,13 @@ function local_backup()
code,out,err = is_exec("findmnt -T " .. path .. " -o TARGET|sed -n 2p")
if out:gsub("[\r\n]", "") == "/" or out:gsub("[\r\n]", "") == "/tmp" then
-- error
error_ret = {code = 500, msg = "Path Error,Can not be / or tmp."}
error_ret = {code = 500, stderr = "Path Error,Can not be / or tmp."}
luci.http.prepare_content("application/json")
luci.http.write_json(error_ret)
else
-- update local backup path
update_local_backup_path(path)
code,out,err = is_exec(myopkg .. " backup " .. path)
code,out,err = _action(is_backup, "backup", path)
ret = {
code = code,
stdout = out,
@ -449,7 +453,7 @@ function local_backup()
end
else
-- error
error_ret = {code = 500, msg = "Path Unknown"}
error_ret = {code = 500, stderr = "Path Unknown"}
luci.http.prepare_content("application/json")
luci.http.write_json(error_ret)
end
@ -481,8 +485,9 @@ function light_restore()
if finished then
is_exec("rm /etc/istore/app.list;tar -xzf " .. path .. " -C /")
nixio.fs.unlink(path)
if nixio.fs.access("/etc/istore/app.list") then
code,out,err = is_exec(myopkg .. " restore")
code,out,err = _action(is_backup, "restore")
ret = {
code = code,
stdout = out,
@ -491,14 +496,12 @@ function light_restore()
luci.http.prepare_content("application/json")
luci.http.write_json(ret)
else
local error_ret = {code = 500, msg = "File is error!"}
local error_ret = {code = 500, stderr = "File is error!"}
luci.http.prepare_content("application/json")
luci.http.write_json(error_ret)
end
-- remove file
is_exec("rm " .. path)
else
ret = {code = 500, msg = "upload failed!"}
ret = {code = 500, stderr = "upload failed!"}
luci.http.prepare_content("application/json")
luci.http.write_json(ret)
end
@ -509,7 +512,7 @@ function local_restore()
local path = luci.http.formvalue("path")
local code, out, err, ret
if path ~= "" then
code,out,err = is_exec(myopkg .. " restore " .. path)
code,out,err = _action(is_backup, "restore", path)
ret = {
code = code,
stdout = out,
@ -519,7 +522,7 @@ function local_restore()
luci.http.write_json(ret)
else
-- error
error_ret = {code = 500, msg = "Path Unknown"}
error_ret = {code = 500, stderr = "Path Unknown"}
luci.http.prepare_content("application/json")
luci.http.write_json(error_ret)
end
@ -530,7 +533,7 @@ function get_backup_app_list_file_path()
local jsonc = require "luci.jsonc"
local error_ret = {code = 500, msg = "Unknown"}
local success_ret = {code = 200,msg = "Unknown"}
local r,o,e = is_exec(myopkg .. " get_backup_app_list_file_path")
local r,o,e = is_exec(is_backup .. " get_backup_app_list_file_path")
if r ~= 0 then
error_ret.msg = e
luci.http.prepare_content("application/json")
@ -548,7 +551,7 @@ function get_backup_app_list()
local jsonc = require "luci.jsonc"
local error_ret = {code = 500, msg = "Unknown"}
local success_ret = {code = 200,msg = "Unknown"}
local r,o,e = is_exec(myopkg .. " get_backup_app_list")
local r,o,e = is_exec(is_backup .. " get_backup_app_list")
if r ~= 0 then
error_ret.msg = e
luci.http.prepare_content("application/json")
@ -572,7 +575,7 @@ function get_available_backup_file_list()
if path ~= "" then
-- update local backup path
update_local_backup_path(path)
r,o,e = is_exec(myopkg .. " get_available_backup_file_list " .. path)
r,o,e = is_exec(is_backup .. " get_available_backup_file_list " .. path)
if r ~= 0 then
error_ret.msg = e
luci.http.prepare_content("application/json")

View File

@ -1,4 +1,7 @@
<%+header%>
<%
local jsonc = require "luci.jsonc"
%>
<script>
(function(){
var vue_prefix="<%=prefix%>";
@ -16,6 +19,7 @@
window.vue_lang = '<%=lang%>';
window.token = "<%=token%>";
window.device_id = {arch:"<%=id.arch%>",uid:"<%=id.uid%>",version:"<%=id.version%>"};
window.istore_features = <%=jsonc.stringify(features)%>;
})();
</script>
<h2 name="content"><%:iStore%>
@ -26,5 +30,32 @@
<link rel="stylesheet" href="/luci-static/istore/style.css?v=<%=id.version%>">
<div id="app">
</div>
<%+tasks/embed%>
<script>
(function() {
let beforeunloadRegistered = false;
window.istore_log = function(flush_menu_onclose) {
if (flush_menu_onclose && !beforeunloadRegistered) {
beforeunloadRegistered = true;
window.addEventListener("beforeunload", function(event) {
try { window.L.ui.menu.flushCache() } catch (e) { }
return true;
});
}
taskd.show_log("istore", true);
};
})();
<%
local taskd = require "luci.model.tasks"
local status = taskd.status("istore")
if status.running then
-%>
window.istore_log(true);
<%
end
%>
</script>
<script type="module" crossorigin src="/luci-static/istore/index.js?v=<%=id.version%>"></script>
<%+footer%>

View File

@ -1,14 +1,10 @@
#!/bin/sh
#set -x
#IS_DEBUG=1
IS_ROOT=/tmp/is-root
DL_DIR=${IS_ROOT}/tmp/dl
LISTS_DIR_O=/tmp/opkg-lists
LISTS_DIR=${IS_ROOT}${LISTS_DIR_O}
OPKG_CONF_DIR=${IS_ROOT}/etc/opkg
APP_LIST_FILE=/etc/istore/app.list
BACKUP_CONFIG_FILE=/etc/config/istore
FEEDS_SERVER=https://istore.linkease.com/repo
ARCH=`jsonfilter -i /etc/.app_store.id -e '$.arch'`
@ -126,249 +122,8 @@ new_upgrade() {
wrapped_in_update upgrade "$@"
}
opkg_list_installed_packages() {
target=$1
case $target in
"preinstalled")
OPKG_INFO_DIR="/rom/usr/lib/opkg/info"
;;
"userinstalled")
OPKG_INFO_DIR="/overlay/upper/usr/lib/opkg/info"
;;
"allinstalled")
OPKG_INFO_DIR="/usr/lib/opkg/info"
;;
*)
echo "invalid target"
exit
;;
esac
(cd $OPKG_INFO_DIR && find . -depth -maxdepth 1 -name "*.list" -type f | sed 's#^\./\(.*\)\.list$#\1#g')
}
ipk_build() {
PKG_NAME_TEMP=$1
IPK_OUTPUT_DIR=$2
UCI_BAK_DIR="/etc/istore/uci-defaults_bak/"
UCI_DEF_DIR="etc/uci-defaults"
OPKG_INFO_DIR="/usr/lib/opkg/info/"
[ -n "${PKG_NAME_TEMP}" ] || exit 1
#get real pkg name in opkg
PKG_NAME_TEMP=`cat ${IS_ROOT}/all_installed_package.list | sort -u | grep "^${PKG_NAME_TEMP}" | head -n 1`
[ -n "${PKG_NAME_TEMP}" ] || exit 1
PKG_NAME=`cat ${OPKG_INFO_DIR}${PKG_NAME_TEMP}.control | grep "^Package: " | cut -d ' ' -f2`
PKG_VER=`cat ${OPKG_INFO_DIR}${PKG_NAME}.control | grep "^Version: " | cut -d ' ' -f2`
PKG_ARCH=`cat ${OPKG_INFO_DIR}${PKG_NAME}.control | grep "^Architecture: " | cut -d ' ' -f2`
IPK_FILE_NAME="${PKG_NAME}_${PKG_VER}_${PKG_ARCH}"
rm -rf ${IS_ROOT}/${IPK_FILE_NAME}
mkdir -p ${IS_ROOT}/${IPK_FILE_NAME}
#(1)make CONTROL dir; (2)copy control file to dir
cd ${IS_ROOT}/${IPK_FILE_NAME}
mkdir -p CONTROL
for control_file in `ls ${OPKG_INFO_DIR}${PKG_NAME}.* | grep -v ".list$"`; do
file=${control_file##*/}
suffix=${file##*.}
cp ${control_file} CONTROL/${suffix}
done
#(1)make DATA depend dir; (2)copy uci-defaults_bak file to dir; (3)copy other file to dir
for pkgfile in `cat ${OPKG_INFO_DIR}${PKG_NAME}.list | cut -b 2-`; do
file=${pkgfile##*/}
path=${pkgfile%/*}
mkdir -p ${path}
if [ `echo "${path}" | grep "^${UCI_DEF_DIR}"` ]; then
cp "${UCI_BAK_DIR}${file}" "${pkgfile}"
else
cp "/${pkgfile}" "${pkgfile}"
fi
done
#call ipkg-build script to build ipk
ipkg-build ${IS_ROOT}/${IPK_FILE_NAME} ${IPK_OUTPUT_DIR}
echo "${IPK_FILE_NAME}.ipk" >> ${IPK_OUTPUT_DIR}/appdepipk.list
[ -n "${IS_DEBUG}" ] || rm -rf ${IS_ROOT}/${IPK_FILE_NAME}
}
# if arg is NULL, use light backup, otherwise use local backup
backup() {
[ -n "$1" ] && BACKUP_PATH=$1
#1.add all istore self data to sysupgrade config file,
#sysupgrade will backup/restore it auto when flash new firmware
echo "/etc/.app_store.id" > /lib/upgrade/keep.d/luci-app-store
cat /usr/lib/opkg/info/luci-app-store.list >> /lib/upgrade/keep.d/luci-app-store
echo "/etc/rc.d/S45istore" >> /lib/upgrade/keep.d/luci-app-store
echo "/etc/istore/uci-defaults_bak" >> /lib/upgrade/keep.d/luci-app-store
echo "${APP_LIST_FILE}" >> /lib/upgrade/keep.d/luci-app-store
echo "${BACKUP_CONFIG_FILE}" >> /lib/upgrade/keep.d/luci-app-store
#write user installed package list to file
opkg_list_installed_packages "userinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/user_installed_package.list
#write installed package list by istore feed to file
cat ${IS_ROOT}/user_installed_package.list | \
grep '^app-meta-' > ${IS_ROOT}/istore_installed_package.list
#if no input backup path, only back app.list
mkdir -p /etc/istore
cp ${IS_ROOT}/istore_installed_package.list ${APP_LIST_FILE}
echo "backup installed package list to ${APP_LIST_FILE}"
if [ ! -n "${BACKUP_PATH}" ]; then
echo "backup success"
exit 0
fi
if [ ! -d "${BACKUP_PATH}" ] && ! mkdir -p "${BACKUP_PATH}" ; then
echo "invalid backup path, can not backup ipk"
exit 1
fi
#write all installed package list to file
opkg_list_installed_packages "allinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/all_installed_package.list
#write system pre installed package list to file
opkg_list_installed_packages "preinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/pre_installed_package.list
#write installed packages and depends list by istore feed to file by depend sequence
appdep_list=""
temp_list=`cat ${IS_ROOT}/istore_installed_package.list | sed 's/^/\t/'`
while [ -n "${temp_list}" ]
do
#get real pkg name
for PKG_NAME_TEMP in ${temp_list}; do
REAL_PKG_NAME=`cat ${IS_ROOT}/all_installed_package.list | sort -u | grep "^${PKG_NAME_TEMP}" | head -n 1`
if [ "${REAL_PKG_NAME}" != "${PKG_NAME_TEMP}" ]; then
temp_list=`echo "${temp_list}" | sed 's/^\t'"${PKG_NAME_TEMP}"'$/\t'"${REAL_PKG_NAME}"'/'`
fi
done
appdep_list=`echo -e "${temp_list}\n${appdep_list}"`
[ -n "${IS_DEBUG}" ] && echo -e "temp_list:\n""${temp_list}"
[ -n "${IS_DEBUG}" ] && echo -e "appdep_list:\n""${appdep_list}"
temp_list=`echo "${temp_list}" | xargs opkg depends | grep -v "depends on:" | grep -v " (>= " | grep -v " (= " | sort -u`
done
appdep_list_all=`echo "${appdep_list}" | cut -f2 | grep -v "^$" | awk '!seen[$0]++'`
[ -n "${IS_DEBUG}" ] && echo -e "appdep_list_all:\n""${appdep_list_all}"
echo "${appdep_list_all}" > ${IS_ROOT}/appdep.list
#3.rebuild all istore installed package to ipk and backup to userdata partation
# 4. create dir
date=$(date +%Y-%m%d-%H%M)
if [ ! -d "$BACKUP_PATH/backup_istore_$date" ];then
mkdir $BACKUP_PATH/backup_istore_$date
fi
cp ${IS_ROOT}/istore_installed_package.list $BACKUP_PATH/backup_istore_$date/app.list
cp ${IS_ROOT}/appdep.list $BACKUP_PATH/backup_istore_$date/appdep.list
#only backup non pre installed ipk
cp ${IS_ROOT}/appdep.list ${IS_ROOT}/appdep_strip.list
for pre_installed_pkg in `cat ${IS_ROOT}/appdep.list ${IS_ROOT}/pre_installed_package.list | sort -n | uniq -d`; do
sed -i '/^'"$pre_installed_pkg"'$/d' ${IS_ROOT}/appdep_strip.list
done
rm -f $BACKUP_PATH/backup_istore_$date/appdepipk.list
echo "build ipk"
for pkg_name in `cat ${IS_ROOT}/appdep_strip.list`; do
ipk_build ${pkg_name} $BACKUP_PATH/backup_istore_$date
done
# 5. create tar.gz file,and remove fir
cd $BACKUP_PATH
echo "write backup file to $BACKUP_PATH/backup_istore_$date.backup.tar.gz"
tar -czf $BACKUP_PATH/backup_istore_$date.backup.tar.gz backup_istore_$date
rm -rf $BACKUP_PATH/backup_istore_$date
echo "backup success"
}
# if arg is NULL, use light backup, otherwise use local backup
restore() {
if [ -n "$1" ]; then
BACKUP_PATH_FILE=$1
else
echo "install package by ${APP_LIST_FILE}"
update
for app in `cat ${APP_LIST_FILE}`; do
#skip resotre istore self
[ "A${app}" == "A""luci-app-store" ] && continue
opkg_wrap install ${app}
done
exit 0
fi
if [ ! -f "${BACKUP_PATH_FILE}" ];then
echo "invalid backup file, can not restore ipk"
exit 1
fi
#1. Unzip file to dir
BACKUP_PATH_FILE_NAME=${BACKUP_PATH_FILE##*/}
BACKUP_PATH=/tmp/${BACKUP_PATH_FILE_NAME%.backup.tar.gz*}
if [ -d "$BACKUP_PATH" ];then
rm -rf $BACKUP_PATH
fi
mkdir -p $BACKUP_PATH
echo "unpack input file..."
# fix tar path error
tar -zxf ${BACKUP_PATH_FILE} -C /tmp/
echo "check file"
if [ ! -f "${BACKUP_PATH}/appdep.list" ];then
echo "no available appdep.list, can not restore ipk"
exit 1
fi
echo "check success"
#2. install ipk by backup path
echo "restore begin"
for app in `cat ${BACKUP_PATH}/appdepipk.list`; do
opkg_wrap install ${BACKUP_PATH}/${app}
done
#3. rm dir
rm -rf ${BACKUP_PATH}
echo "restore success"
}
get_support_backup_features() {
echo "light_backup"
#istore custom img mean support local_backup
if [ -f /etc/istore_img_flag ];then
echo "local_backup"
fi
}
get_backup_app_list_file_path() {
echo "${APP_LIST_FILE}"
}
get_backup_app_list() {
if [ ! -f "${APP_LIST_FILE}" ];then
echo "no app.list, can not get backup app list"
exit 1
fi
cat ${APP_LIST_FILE}
}
get_available_backup_file_list() {
if [ -n "$1" ]; then
for backup_file in `ls $1/*.backup.tar.gz`; do
filename=${backup_file##*/}
echo "${filename}"
done
else
echo "input backup path is null"
exit 1
fi
remove() {
opkg_wrap --autoremove --force-removal-of-dependent-packages remove "$@"
}
usage() {
@ -382,12 +137,6 @@ usage() {
echo " list-upgradable List installed and upgradable packages"
echo " check_self_upgrade Check iStore upgrade"
echo " do_self_upgrade Upgrade iStore"
echo " backup [dir] Backup all installed package(s) to [directory]"
echo " restore [dir] Restore package(s) by [directory]"
echo " get_support_backup_features get device support backup features"
echo " get_backup_app_list_file_path get light backup app list file path"
echo " get_backup_app_list get light backup app list"
echo " get_available_backup_file_list get local available backup file list"
echo " opkg sys opkg wrap"
}
@ -404,7 +153,7 @@ case $action in
new_upgrade "$@"
;;
"remove")
opkg_wrap --autoremove --force-removal-of-dependent-packages remove "$@"
remove "$@" || remove "$@"
;;
"info")
opkg_wrap info "$@"
@ -418,24 +167,6 @@ case $action in
"do_self_upgrade")
do_self_upgrade
;;
"get_support_backup_features")
get_support_backup_features
;;
"backup")
backup "$@"
;;
"restore")
restore "$@"
;;
"get_backup_app_list_file_path")
get_backup_app_list_file_path
;;
"get_backup_app_list")
get_backup_app_list
;;
"get_available_backup_file_list")
get_available_backup_file_list "$@"
;;
"opkg")
opkg_wrap "$@"
;;

View File

@ -0,0 +1,295 @@
#!/bin/sh
#set -x
#IS_DEBUG=1
IS_ROOT=/tmp/is-backup
APP_LIST_FILE=/etc/istore/app.list
BACKUP_CONFIG_FILE=/etc/config/istore
action=${1}
shift
is_init() {
mkdir -p ${IS_ROOT}
}
opkg_list_installed_packages() {
target=$1
case $target in
"preinstalled")
OPKG_INFO_DIR="/rom/usr/lib/opkg/info"
;;
"userinstalled")
OPKG_INFO_DIR="/overlay/upper/usr/lib/opkg/info"
;;
"allinstalled")
OPKG_INFO_DIR="/usr/lib/opkg/info"
;;
*)
echo "invalid target"
exit
;;
esac
(cd $OPKG_INFO_DIR && find . -depth -maxdepth 1 -name "*.list" -type f | sed 's#^\./\(.*\)\.list$#\1#g')
}
ipk_build() {
PKG_NAME_TEMP=$1
IPK_OUTPUT_DIR=$2
UCI_BAK_DIR="/etc/istore/uci-defaults_bak/"
UCI_DEF_DIR="etc/uci-defaults"
OPKG_INFO_DIR="/usr/lib/opkg/info/"
[ -n "${PKG_NAME_TEMP}" ] || exit 1
#get real pkg name in opkg
PKG_NAME_TEMP=`cat ${IS_ROOT}/all_installed_package.list | sort -u | grep "^${PKG_NAME_TEMP}" | head -n 1`
[ -n "${PKG_NAME_TEMP}" ] || exit 1
PKG_NAME=`cat ${OPKG_INFO_DIR}${PKG_NAME_TEMP}.control | grep "^Package: " | cut -d ' ' -f2`
PKG_VER=`cat ${OPKG_INFO_DIR}${PKG_NAME}.control | grep "^Version: " | cut -d ' ' -f2`
PKG_ARCH=`cat ${OPKG_INFO_DIR}${PKG_NAME}.control | grep "^Architecture: " | cut -d ' ' -f2`
IPK_FILE_NAME="${PKG_NAME}_${PKG_VER}_${PKG_ARCH}"
rm -rf ${IS_ROOT}/${IPK_FILE_NAME}
mkdir -p ${IS_ROOT}/${IPK_FILE_NAME}
#(1)make CONTROL dir; (2)copy control file to dir
cd ${IS_ROOT}/${IPK_FILE_NAME}
mkdir -p CONTROL
for control_file in `ls ${OPKG_INFO_DIR}${PKG_NAME}.* | grep -v ".list$"`; do
file=${control_file##*/}
suffix=${file##*.}
cp ${control_file} CONTROL/${suffix}
done
#(1)make DATA depend dir; (2)copy uci-defaults_bak file to dir; (3)copy other file to dir
for pkgfile in `cat ${OPKG_INFO_DIR}${PKG_NAME}.list | cut -b 2-`; do
file=${pkgfile##*/}
path=${pkgfile%/*}
mkdir -p ${path}
if [ `echo "${path}" | grep "^${UCI_DEF_DIR}"` ]; then
cp "${UCI_BAK_DIR}${file}" "${pkgfile}"
else
cp "/${pkgfile}" "${pkgfile}"
fi
done
#call ipkg-build script to build ipk
/usr/libexec/istore/ipkg-build ${IS_ROOT}/${IPK_FILE_NAME} ${IPK_OUTPUT_DIR}
echo "${IPK_FILE_NAME}.ipk" >> ${IPK_OUTPUT_DIR}/appdepipk.list
[ -n "${IS_DEBUG}" ] || rm -rf ${IS_ROOT}/${IPK_FILE_NAME}
}
# if arg is NULL, use light backup, otherwise use local backup
backup() {
[ -n "$1" ] && BACKUP_PATH=$1
#1.add all istore self data to sysupgrade config file,
#sysupgrade will backup/restore it auto when flash new firmware
echo "/etc/.app_store.id" > /lib/upgrade/keep.d/luci-app-store
cat /usr/lib/opkg/info/luci-app-store.list >> /lib/upgrade/keep.d/luci-app-store
echo "/etc/rc.d/S45istore" >> /lib/upgrade/keep.d/luci-app-store
echo "/etc/istore/uci-defaults_bak" >> /lib/upgrade/keep.d/luci-app-store
echo "${APP_LIST_FILE}" >> /lib/upgrade/keep.d/luci-app-store
echo "${BACKUP_CONFIG_FILE}" >> /lib/upgrade/keep.d/luci-app-store
#write user installed package list to file
opkg_list_installed_packages "userinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/user_installed_package.list
#write installed package list by istore feed to file
cat ${IS_ROOT}/user_installed_package.list | \
grep '^app-meta-' > ${IS_ROOT}/istore_installed_package.list
#if no input backup path, only back app.list
mkdir -p /etc/istore
cp ${IS_ROOT}/istore_installed_package.list ${APP_LIST_FILE}
echo "backup installed package list to ${APP_LIST_FILE}"
if [ ! -n "${BACKUP_PATH}" ]; then
echo "backup success"
exit 0
fi
if [ ! -d "${BACKUP_PATH}" ] && ! mkdir -p "${BACKUP_PATH}" ; then
echo "invalid backup path, can not backup ipk"
exit 1
fi
#write all installed package list to file
opkg_list_installed_packages "allinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/all_installed_package.list
#write system pre installed package list to file
opkg_list_installed_packages "preinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/pre_installed_package.list
#write installed packages and depends list by istore feed to file by depend sequence
appdep_list=""
temp_list=`cat ${IS_ROOT}/istore_installed_package.list | sed 's/^/\t/'`
while [ -n "${temp_list}" ]
do
#get real pkg name
for PKG_NAME_TEMP in ${temp_list}; do
REAL_PKG_NAME=`cat ${IS_ROOT}/all_installed_package.list | sort -u | grep "^${PKG_NAME_TEMP}" | head -n 1`
if [ "${REAL_PKG_NAME}" != "${PKG_NAME_TEMP}" ]; then
temp_list=`echo "${temp_list}" | sed 's/^\t'"${PKG_NAME_TEMP}"'$/\t'"${REAL_PKG_NAME}"'/'`
fi
done
appdep_list=`echo -e "${temp_list}\n${appdep_list}"`
[ -n "${IS_DEBUG}" ] && echo -e "temp_list:\n""${temp_list}"
[ -n "${IS_DEBUG}" ] && echo -e "appdep_list:\n""${appdep_list}"
temp_list=`echo "${temp_list}" | xargs opkg depends | grep -v "depends on:" | grep -v " (>= " | grep -v " (= " | sort -u`
done
appdep_list_all=`echo "${appdep_list}" | cut -f2 | grep -v "^$" | awk '!seen[$0]++'`
[ -n "${IS_DEBUG}" ] && echo -e "appdep_list_all:\n""${appdep_list_all}"
echo "${appdep_list_all}" > ${IS_ROOT}/appdep.list
#3.rebuild all istore installed package to ipk and backup to userdata partation
# 4. create dir
date=$(date +%Y-%m%d-%H%M)
if [ ! -d "$BACKUP_PATH/backup_istore_$date" ];then
mkdir $BACKUP_PATH/backup_istore_$date
fi
cp ${IS_ROOT}/istore_installed_package.list $BACKUP_PATH/backup_istore_$date/app.list
cp ${IS_ROOT}/appdep.list $BACKUP_PATH/backup_istore_$date/appdep.list
#only backup non pre installed ipk
cp ${IS_ROOT}/appdep.list ${IS_ROOT}/appdep_strip.list
for pre_installed_pkg in `cat ${IS_ROOT}/appdep.list ${IS_ROOT}/pre_installed_package.list | sort -n | uniq -d`; do
sed -i '/^'"$pre_installed_pkg"'$/d' ${IS_ROOT}/appdep_strip.list
done
rm -f $BACKUP_PATH/backup_istore_$date/appdepipk.list
echo "build ipk"
for pkg_name in `cat ${IS_ROOT}/appdep_strip.list`; do
ipk_build ${pkg_name} $BACKUP_PATH/backup_istore_$date
done
# 5. create tar.gz file,and remove fir
cd $BACKUP_PATH
echo "write backup file to $BACKUP_PATH/backup_istore_$date.backup.tar.gz"
tar -czf $BACKUP_PATH/backup_istore_$date.backup.tar.gz backup_istore_$date
rm -rf $BACKUP_PATH/backup_istore_$date
echo "backup success"
}
# if arg is NULL, use light backup, otherwise use local backup
restore() {
if [ -n "$1" ]; then
BACKUP_PATH_FILE=$1
else
echo "install package by ${APP_LIST_FILE}"
is-opkg update
for app in `cat ${APP_LIST_FILE}`; do
#skip resotre istore self
[ "A${app}" == "A""luci-app-store" ] && continue
is-opkg install ${app}
done
exit 0
fi
if [ ! -f "${BACKUP_PATH_FILE}" ];then
echo "invalid backup file, can not restore ipk"
exit 1
fi
#1. Unzip file to dir
BACKUP_PATH_FILE_NAME=${BACKUP_PATH_FILE##*/}
BACKUP_PATH=/tmp/${BACKUP_PATH_FILE_NAME%.backup.tar.gz*}
if [ -d "$BACKUP_PATH" ];then
rm -rf $BACKUP_PATH
fi
mkdir -p $BACKUP_PATH
echo "unpack input file..."
# fix tar path error
tar -zxf ${BACKUP_PATH_FILE} -C /tmp/
echo "check file"
if [ ! -f "${BACKUP_PATH}/appdep.list" ];then
echo "no available appdep.list, can not restore ipk"
exit 1
fi
echo "check success"
#2. install ipk by backup path
echo "restore begin"
( cd ${BACKUP_PATH}; opkg install `cat ${BACKUP_PATH}/appdepipk.list` )
#3. rm dir
rm -rf ${BACKUP_PATH}
echo "restore success"
}
get_support_backup_features() {
echo "light_backup"
#istore custom img mean support local_backup
if [ -f /etc/istore_img_flag ];then
echo "local_backup"
fi
}
get_backup_app_list_file_path() {
echo "${APP_LIST_FILE}"
}
get_backup_app_list() {
if [ ! -f "${APP_LIST_FILE}" ];then
echo "no app.list, can not get backup app list"
exit 1
fi
cat ${APP_LIST_FILE}
}
get_available_backup_file_list() {
if [ -n "$1" ]; then
for backup_file in `ls $1/*.backup.tar.gz`; do
filename=${backup_file##*/}
echo "${filename}"
done
else
echo "input backup path is null"
exit 1
fi
}
usage() {
echo "usage: backup sub-command [arguments...]"
echo "where sub-command is one of:"
echo " backup [dir] Backup all installed package(s) to [directory]"
echo " restore [dir] Restore package(s) by [directory]"
echo " get_support_backup_features get device support backup features"
echo " get_backup_app_list_file_path get light backup app list file path"
echo " get_backup_app_list get light backup app list"
echo " get_available_backup_file_list get local available backup file list"
}
is_init >/dev/null 2>&1
case $action in
"get_support_backup_features")
get_support_backup_features
;;
"backup")
backup "$@"
;;
"restore")
restore "$@"
;;
"get_backup_app_list_file_path")
get_backup_app_list_file_path
;;
"get_backup_app_list")
get_backup_app_list
;;
"get_available_backup_file_list")
get_available_backup_file_list "$@"
;;
*)
usage
;;
esac

19
luci-lib-taskd/Makefile Normal file
View File

@ -0,0 +1,19 @@
#
# Copyright (C) 2022 jjm2473 <jjm2473@gmail.com>
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Task library
LUCI_DEPENDS:=+luci-lib-xterm +taskd
LUCI_PKGARCH:=all
PKG_VERSION:=1.0.13
PKG_RELEASE:=
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,107 @@
[hidden] {
display: none !important;
}
#tasks_detail_container {
position: fixed;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #0008;
}
#tasks_dialog {
position: absolute;
width: 770px;
max-height: 100%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #000;
border-radius: 10px;
box-shadow: 2px 2px 6px #000a;
padding: 20px;
color: white;
}
.dialog-title-bar {
margin-top: -10px;
margin-right: -10px;
margin-bottom: 5px;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: nowrap;
align-items: center;
}
.dialog-title-bar .dialog-title {
word-break: break-all;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.dialog-content {
max-height: 500px;
overflow-y: scroll;
margin-right: -10px;
}
.dialog-icons {
align-self: center;
display: flex;
justify-content: flex-end;
}
.dialog-icon {
display: flex;
align-items: center;
flex-wrap: nowrap;
width: 20px;
height: 20px;
background-color: white;
color: black;
border-radius: 50%;
font-size: 10px;
font-weight: bold;
user-select: none;
margin-left: 10px;
line-height: 1;
font-family: sans-serif;
justify-content: center;
cursor: pointer;
}
.dialog-icon.dialog-icon-min {
background-color: darkorange;
}
.dialog-icon.dialog-icon-close {
background-color: #ff5f56;
}
.dialog-icons:hover .dialog-icon.dialog-icon-min:before {
content: "_";
}
.dialog-icons:hover .dialog-icon.dialog-icon-close:before {
content: "X";
}
.tasks_stopped .dialog-icon.dialog-icon-close {
background-color: #27c840;
}
.tasks_stopped #tasks_dialog {
padding: 19px;
border: 1px #27c840 solid;
animation: border-blink 1s;
animation-iteration-count: infinite;
}
.tasks_failed #tasks_dialog {
border-color: #ff0000;
}
.tasks_failed .dialog-icon.dialog-icon-close {
background-color: #ff0000;
}
@keyframes border-blink { 50% { border-color:#fff ; } }

View File

@ -0,0 +1,199 @@
(function(){
const taskd={};
const $gettext = function(str) {
return taskd.i18n[str] || str;
};
const request = function(url, method, data) {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
oReq.open(method || 'GET', url, true);
oReq.onload = function (oEvent) {
resolve(oReq.responseText);
};
if (method=='POST') {
oReq.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
oReq.send(data || method=='POST'?"token="+taskd.csrfToken:null);
});
};
const getBin = function(url) {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
resolve({status: oReq.status, buffer: new Uint8Array(oReq.response)});
};
oReq.send(null);
});
};
const getTaskDetail = function(task_id) {
return request("/cgi-bin/luci/admin/system/tasks/status?task_id="+task_id).then(data=>JSON.parse(data));
};
const create_dialog = function(cfg) {
const container = document.createElement('div');
container.id = "tasks_detail_container";
container.innerHTML = taskd.dialog_template;
document.body.appendChild(container);
const title_view = container.querySelector(".dialog-title-bar .dialog-title");
title_view.innerText = cfg.title;
const term = new Terminal({convertEol: cfg.convertEol||false});
if (cfg.nohide) {
container.querySelector(".dialog-icon-min").hidden = true;
} else {
container.querySelector(".dialog-icon-min").onclick = function(){
container.hidden=true;
term.dispose();
document.body.removeChild(container);
cfg.onhide && cfg.onhide();
return false;
};
}
term.open(document.getElementById("tasks_xterm_log"));
return {term,container};
};
const show_log_txt = function(title, content, onclose) {
const dialog = create_dialog({title, convertEol:true, onhine:onclose});
const container = dialog.container;
const term = dialog.term;
container.querySelector(".dialog-icon-close").hidden = true;
term.write(content);
};
const show_log = function(task_id, nohide) {
let showing = true;
let running = true;
const dialog = create_dialog({title:task_id, nohide, onhide:function(){showing=false;}});
const container = dialog.container;
const term = dialog.term;
const title_view = container.querySelector(".dialog-title-bar .dialog-title");
container.querySelector(".dialog-icon-close").onclick = function(){
if (!running || confirm($gettext("Stop running task?"))) {
running=false;
showing=false;
del_task(task_id).then(()=>{
location.href = location.href;
});
}
return false;
};
const checkTask = function() {
return getTaskDetail(task_id).then(data=>{
if (!running) {
return false;
}
running = data.running;
let title = task_id;
if (!data.running && data.stop) {
title += " (" + (data.exit_code?$gettext("Failed at:"):$gettext("Finished at:")) + " " + new Date(data.stop * 1000).toLocaleString() + ")";
}
title += " > " + (data.command || '');
title_view.title = title;
title_view.innerText = title;
if (!data.running) {
container.classList.add('tasks_stopped')
if (data.exit_code) {
container.classList.add('tasks_failed')
}
}
// last pull
return showing;
});
};
let logoffset = 0;
const pulllog = function() {
getBin("/cgi-bin/luci/admin/system/tasks/log?task_id="+task_id+"&offset="+logoffset).then(function(res){
if (!showing) {
return false;
}
switch(res.status){
case 205:
term.reset();
logoffset = 0;
return running;
break;
case 204:
return running && checkTask();
break;
case 200:
logoffset += res.buffer.byteLength;
term.write(res.buffer);
return running;
break;
}
}).then(again => {
if (again) {
setTimeout(pulllog, 0);
}
});
};
checkTask().then(pulllog);
};
const del_task = function(task_id) {
return request("/cgi-bin/luci/admin/system/tasks/stop?task_id="+task_id, "POST");
};
taskd.show_log = show_log;
taskd.remove = del_task;
taskd.show_log_txt = show_log_txt;
window.taskd=taskd;
})();
(function(){
// compat
if (typeof(window.findParent) !== 'function') {
const elem = function(e) {
return (e != null && typeof(e) == 'object' && 'nodeType' in e);
};
const matches = function(node, selector) {
var m = elem(node) ? node.matches || node.msMatchesSelector : null;
return m ? m.call(node, selector) : false;
};
window.findParent = function (node, selector) {
if (elem(node) && node.closest)
return node.closest(selector);
while (elem(node))
if (matches(node, selector))
return node;
else
node = node.parentNode;
return null;
};
}
if (typeof(window.cbi_submit) !== 'function') {
const makeHidden = function(name) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
return input;
};
window.cbi_submit = function(elem, name, value, action) {
var form = elem.form || findParent(elem, 'form');
if (!form)
return false;
if (action)
form.action = action;
if (name) {
var hidden = form.querySelector('input[type="hidden"][name="%s"]'.format(name)) ||
makeHidden(name);
hidden.value = value || '1';
form.appendChild(hidden);
}
form.submit();
return true;
};
}
})();

View File

@ -0,0 +1,92 @@
module("luci.controller.tasks-lib", package.seeall)
function index()
entry({"admin", "system", "tasks", "status"}, call("tasks_status")).dependent=false
entry({"admin", "system", "tasks", "log"}, call("tasks_log")).dependent=false
entry({"admin", "system", "tasks", "stop"}, post("tasks_stop")).dependent=false
end
local util = require "luci.util"
local jsonc = require "luci.jsonc"
local ltn12 = require "luci.ltn12"
local taskd = require "luci.model.tasks"
function tasks_status()
local data = taskd.status(luci.http.formvalue("task_id"))
luci.http.prepare_content("application/json")
luci.http.write_json(data)
end
function tasks_log()
local wait = 107
local task_id = luci.http.formvalue("task_id")
local offset = luci.http.formvalue("offset")
offset = offset and tonumber(offset) or 0
local logpath = "/var/log/tasks/"..task_id..".log"
local i
local logfd = io.open(logpath, "rb")
if logfd == nil then
luci.http.status(404)
luci.http.write("log not found")
return
end
local size = logfd:seek("end")
if size < offset then
luci.http.status(205, "Reset Content")
luci.http.write("reset offset")
return
end
i = 0
while (i < wait)
do
if size > offset then
break
end
nixio.nanosleep(0, 10000000) -- sleep 10ms
size = logfd:seek("end")
i = i+1
end
if i == wait then
logfd:close()
luci.http.status(204)
luci.http.prepare_content("application/octet-stream")
return
end
logfd:seek("set", offset)
local write_log = function()
local buffer = logfd:read(4096)
if buffer and #buffer > 0 then
return buffer
else
logfd:close()
return nil
end
end
luci.http.prepare_content("application/octet-stream")
if logfd then
ltn12.pump.all(write_log, luci.http.write)
end
end
function tasks_stop()
local sys = require("luci.sys")
local task_id = luci.http.formvalue("task_id") or ""
if task_id == "" then
luci.http.status(400)
luci.http.write("task_id is empty")
return
end
if sys.call("/etc/init.d/tasks task_del "..task_id.." >/dev/null 2>&1") ~= 0 then
nixio.nanosleep(2, 10000000)
end
luci.http.status(204)
end

View File

@ -0,0 +1,100 @@
local util = require "luci.util"
local jsonc = require "luci.jsonc"
local taskd = {}
local function output(data)
local ret={}
ret.running=data.running
if not data.running then
ret.exit_code=data.exit_code
if nil == ret.exit_code then
if data["data"] and data["data"]["exit_code"] and data["data"]["exit_code"] ~= "" then
ret.exit_code=tonumber(data["data"]["exit_code"])
else
ret.exit_code=143
end
end
end
ret.command=data["command"] and data["command"][4] or '#'
if data["data"] then
ret.start=tonumber(data["data"]["start"])
if not data.running and data["data"]["stop"] then
ret.stop=tonumber(data["data"]["stop"])
end
end
return ret
end
taskd.status = function (task_id)
task_id = task_id or ""
local data = util.trim(util.exec("/etc/init.d/tasks task_status "..task_id.." 2>/dev/null")) or ""
if data ~= "" then
data = jsonc.parse(data)
else
if task_id == "" then
data = {}
else
data = {running=false, exit_code=255}
end
end
if task_id ~= "" then
return output(data)
end
local ary={}
for k, v in pairs(data) do
ary[k] = output(v)
end
return ary
end
taskd.docker_map = function(config, task_id, script_path, title, desc)
require("luci.cbi")
require("luci.http")
require("luci.sys")
local translate = require("luci.i18n").translate
local m
m = luci.cbi.Map(config, title, desc)
m.template = "tasks/docker"
-- hide default buttons
m.pageaction = false
-- we want hook 'on_after_apply' works, 'apply_on_parse' can be true (rollback) or false (no rollback),
-- but 'apply_on_parse' must be true for luci 17.01 and below
m.apply_on_parse = true
m.script_path = script_path
m.task_id = task_id
m.auto_show_task = true
m.on_before_apply = function(self)
if self.uci.rollback then
-- luci 18.06+ has 'rollback' function
-- rollback dialog will show because 'apply_on_parse' is true,
-- hide rollback dialog by hook 'apply' function
local apply = self.uci.apply
self.uci.apply = function(uci, rollback)
apply(uci, false)
end
end
end
m.on_after_apply = function(self)
local cmd
local action = luci.http.formvalue("cbi.apply") or "null"
if "upgrade" == action or "install" == action
or "start" == action or "stop" == action or "restart" == action or "rm" == action then
cmd = string.format("\"%s\" %s", script_path, action)
end
if cmd then
if luci.sys.call("/etc/init.d/tasks task_add " .. task_id .. " " .. luci.util.shellquote(cmd) .. " >/dev/null 2>&1") ~= 0 then
self.task_start_failed = true
self.message = translate("Config saved, but apply failed")
end
else
self.message = translate("Unknown command: ") .. action
end
if self.message then
self.auto_show_task = false
end
end
return m
end
return taskd

View File

@ -0,0 +1,56 @@
<% if self.task_start_failed then %>
<div class="alert-message warning"><%:Another task running, try again later.%> <a href="javascript:void(taskd.show_log('<%=self.task_id%>'))"><%:Click here to check running task%></a></div>
<% end %>
<%+cbi/map%>
<%
local task_running = false
local taskd = require "luci.model.tasks"
local status = taskd.status(self.task_id)
task_running = status.running
-%>
<div class="cbi-page-actions control-group">
<%
if not task_running then
%>
<%
local util = require "luci.util"
local container_status = util.trim(util.exec(self.script_path.." status"))
local container_install = (string.len(container_status) > 0)
local container_running = container_status == "running"
if container_install then
-%>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Upgrade%>/<%:Apply%>" onclick="cbi_submit(this, 'cbi.apply', 'upgrade')" />
<%
if container_running then
-%>
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Stop%>" onclick="cbi_submit(this, 'cbi.apply', 'stop')" />
<input class="btn cbi-button cbi-button-reload" type="button" value="<%:Restart%>" onclick="cbi_submit(this, 'cbi.apply', 'restart')" />
<% else %>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Start%>" onclick="cbi_submit(this, 'cbi.apply', 'start')" />
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Remove%>" onclick="cbi_submit(this, 'cbi.apply', 'rm')" />
<% end
else %>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Install%>" onclick="cbi_submit(this, 'cbi.apply', 'install')" />
<% end
else
%>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Task Running%>&hellip;" onclick="taskd.show_log('<%=self.task_id%>')" />
<%
end
%>
</div>
<%+tasks/embed%>
<%
if self.auto_show_task and task_running then
-%>
<script>
taskd.show_log("<%=self.task_id%>");
</script>
<%
end
%>

View File

@ -0,0 +1,31 @@
<%+xterm/embed%>
<link rel="stylesheet" href="<%=resource%>/tasks/tasks.css<%# ?v=PKG_VERSION %>">
<script src="<%=resource%>/tasks/tasks.js<%# ?v=PKG_VERSION %>"></script>
<%
local i18n = {}
local function tr(str)
i18n[str]=translate(str)
end
tr("Stop running task?")
tr("Stopped at:")
tr("Finished at:")
tr("Failed at:")
-%>
<script>
window.taskd.csrfToken="<%=token%>";
window.taskd.i18n=<% luci.http.write_json(i18n) %>;
window.taskd.dialog_template=`
<div id="tasks_dialog">
<div class="dialog-title-bar">
<span class="dialog-title" id="tasks_id"></span>
<span class="dialog-icons">
<span class="dialog-icon dialog-icon-close" title="<%:Stop and Remove%>"></span>
<span class="dialog-icon dialog-icon-min" title="<%:Hide%>"></span>
</span>
</div>
<div class="dialog-content">
<div id="tasks_xterm_log"></div>
</div>
</div>
`;
</script>

View File

@ -0,0 +1,15 @@
clean:
compile:
include $(TOPDIR)/rules.mk
LUCI_NAME:=luci-lib-dummy
INCLUDE_DIR:=./dummy
include $(TOPDIR)/feeds/luci/luci.mk
install:
mkdir -p "$(DESTDIR)$(LUCI_LIBRARYDIR)/i18n"
$(foreach lang,$(LUCI_LANGUAGES),$(foreach po,$(wildcard ${CURDIR}/po/$(lang)/*.po), \
po2lmo $(po) \
$(DESTDIR)$(LUCI_LIBRARYDIR)/i18n/$(basename $(notdir $(po))).$(lang).lmo;))

View File

@ -0,0 +1,2 @@
define BuildPackage
endef

View File

@ -0,0 +1,32 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Stop running task?"
msgstr "删除运行中的任务?"
msgid "Finished at:"
msgstr "完成于:"
msgid "Failed at:"
msgstr "失败于:"
msgid "Stop and Remove"
msgstr "停止并删除"
msgid "Hide"
msgstr "隐藏"
msgid "Config saved, but apply failed"
msgstr "配置已保存,但应用失败"
msgid "Unknown command: "
msgstr "未知命令:"
msgid "Another task running, try again later."
msgstr "已有后台任务运行中,请稍后重试。"
msgid "Click here to check running task"
msgstr "点此查看运行中的任务"
msgid "Task Running"
msgstr "任务执行中"

21
luci-lib-xterm/Makefile Normal file
View File

@ -0,0 +1,21 @@
#
# Copyright (c) 2017-2019, The xterm.js authors (MIT License)
# Copyright (c) 2014-2017, SourceLair, Private Company (www.sourcelair.com) (MIT License)
# Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Xterm.js library
LUCI_DEPENDS:=
PKG_LICENSE:=MIT
PKG_VERSION:=4.18.0
PKG_RELEASE:=
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,180 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer,
.xterm .xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline {
text-decoration: underline;
}
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
<link rel="stylesheet" href="<%=resource%>/xterm/xterm.css<%# ?v=PKG_VERSION %>">
<script src="<%=resource%>/xterm/xterm.js<%# ?v=PKG_VERSION %>"></script>

41
taskd/Makefile Normal file
View File

@ -0,0 +1,41 @@
#
# Copyright (C) 2022 jjm2473 <jjm2473@gmail.com>
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=taskd
PKG_VERSION:=1.0.3
PKG_RELEASE:=1
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Simple Task Manager
DEPENDS:=+procd +script-utils +coreutils-stty
PKGARCH:=all
endef
define Package/$(PKG_NAME)/description
Simple Task Manager based on procd
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/libexec
$(INSTALL_BIN) ./files/tasks.init $(1)/etc/init.d/tasks
$(INSTALL_BIN) ./files/taskd.sh $(1)/usr/libexec/taskd
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

16
taskd/files/taskd.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
TASK_ID="$1"
TASK_CMD="$2"
exec </dev/null >>"/var/log/tasks/$TASK_ID.log" 2>&1
export HOME=/root
export TERM=xterm-256color
exec script -efqc 'onexit() {
/etc/init.d/tasks _task_onstop "'"$TASK_ID"'" "$?"
}
trap onexit EXIT;
stty cols 80 rows 24;
'"$TASK_CMD" /dev/null

157
taskd/files/tasks.init Executable file
View File

@ -0,0 +1,157 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2022 jjm2473@gmail.com
USE_PROCD=1
START=49
extra_command "task_add" "<task_id> <task_cmd> [<time_wait>] Add and run a task, time_wait is wait time before auto delete stopped task, in seconds, -1 means forever"
extra_command "task_del" "<task_id> Stop and delete task"
extra_command "task_status" "[<task_id>] Dump task status, dump all tasks if no task_id specified"
extra_command "task_gc" "Auto delete exipred (stopped and after timw_wait) tasks"
extra_command "_task_onstop" "<task_id> Update stop time, for internal usage"
_task_add() {
local task_id="${1}"
local task_cmd="${2}"
local time_wait="${3}"
> "/var/log/tasks/$task_id.log"
procd_open_instance "$task_id"
procd_set_param data start=`date +'%s'` time_wait="$time_wait"
procd_set_param command sh -c "exec /usr/libexec/taskd '$task_id' \"\$0\"" "$task_cmd"
procd_set_param stderr 1
procd_close_instance
}
task_add() {
local task_id="${1}"
local task_cmd="${2}"
local time_wait="${3}"
[ -z "$task_id" -o -z "$task_cmd" ] && return 127
if service_running "$task_id"; then
echo "already running" >&2
return 1
fi
if ! mkdir -p /var/log/tasks; then
echo "create /var/log/tasks failed!" >&2
return 1
fi
rc_procd _task_add "$task_id" "$task_cmd" "$time_wait"
return 0
}
_task_del() {
local service="${1}"
local task_id="${2}"
procd_kill "$service" "$task_id"
> "/var/log/tasks/$task_id.log"
rm -f "/var/log/tasks/$task_id.log"
}
task_del() {
local task_id="${1}"
[ -z "$task_id" ] && return 127
procd_lock
_task_del "$(basename ${basescript:-$initscript})" "$task_id"
if [ "$(_task_status "$task_id" | jsonfilter -e '$.running' 2>/dev/null)" = "true" ]; then
return 1
else
return 0
fi
}
_task_status() {
local service="$(basename ${basescript:-$initscript})"
local instance="$1"
local data
json_init
json_add_string name "$service"
data=$(_procd_ubus_call list | jsonfilter -e '@["'"$service"'"]')
[ -z "$data" ] && return 1
data=$(echo "$data" | jsonfilter -e '$.instances')
if [ -z "$data" ]; then
if [ -z "$instance" ]; then
echo "{}"
return 0
fi
return 1
fi
if [ -z "$instance" ]; then
echo "$data"
else
instance="\"$instance\""
echo "$data" | jsonfilter -e '$['"$instance"']'
fi
return 0
}
task_status() {
local task_id="${1}"
_task_status "$task_id"
}
task_gc() {
local service="$(basename ${basescript:-$initscript})"
local task_id instance time_wait
local data
json_init
[ -n "$service" ] && json_add_string name "$service"
data=$(_procd_ubus_call list | jsonfilter -e '@["'"$service"'"]')
[ -z "$data" ] && return 1
data=$(echo "$data" | jsonfilter -e '$.instances')
[ -z "$data" ] && return 1
procd_lock
ls /var/log/tasks/ | sed 's/.log$//g' | while read task_id; do
instance=$(echo "$data" | jsonfilter -e '$["'"$task_id"'"]')
[ "$(echo "$instance" | jsonfilter -e '$.running')" = "false" ] || continue
time_wait=$(echo "$instance" | jsonfilter -e '$.data.time_wait')
[ "$time_wait" = "-1" ] && continue
[ $(($(date +'%s' -r "/var/log/tasks/$task_id.log") + ${time_wait:-0})) -lt `date +'%s'` ] && _task_del "$service" "$task_id"
done
}
_insert_exit() {
local exit_code="$2"
eval "`jshn -r "$1" | grep -v json_init`"
json_select data || {
_procd_set_param data stop=`date +'%s'` exit_code="$exit_code"
return
}
json_add_string stop `date +'%s'`
json_add_string exit_code "$exit_code"
json_select ..
}
_task_exit() {
local task_id="$1"
local exit_code="$2"
local inst_json="$3"
_procd_call json_add_object "$task_id"
_procd_call _insert_exit "$inst_json" "$exit_code"
_procd_call json_close_object
}
_task_onstop() {
local task_id="${1}"
local exit_code="${2}"
[ -z "$task_id" ] && return 127
local service="$(basename ${basescript:-$initscript})"
json_init
json_add_string name "$service"
data=$(_procd_ubus_call list | jsonfilter -e '@["'"$service"'"].instances["'"$task_id"'"]')
[ -z "$data" ] && return 1
json_cleanup
rc_procd _task_exit "$task_id" "$exit_code" "$data"
}