update 2025-02-16 22:24:52
This commit is contained in:
parent
6c79d18ef4
commit
a2e77a7982
21
istore/LICENSE
Normal file
21
istore/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 易有云团队
|
||||||
|
|
||||||
|
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.
|
4
istore/README.en.md
Normal file
4
istore/README.en.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# iStore
|
||||||
|
|
||||||
|
## Introduce
|
||||||
|
The iStore is a app store for OpenWRT, it is implemented using Shell scripts, only depends on OpenWRT standard components, and does not contain binary files itself
|
51
istore/README.md
Normal file
51
istore/README.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# iStore
|
||||||
|
|
||||||
|
## 介绍
|
||||||
|
|
||||||
|
iStore 是一个[开源](https://github.com/linkease/istore)且标准的 OpenWRT 软件中心实现。
|
||||||
|
|
||||||
|
是属于易有云团队的固件 [iStoreOS](https://github.com/istoreos) 的一部分。
|
||||||
|
|
||||||
|
iStore 的设计目标:
|
||||||
|
|
||||||
|
1. 方便用户安装插件
|
||||||
|
2. 每个插件都有教程,方便初学者入门使用插件
|
||||||
|
3. 适配所有的 OpenWRT 皮肤,以及移动端
|
||||||
|
4. 全部基于 OpenWRT 的标准接口开发,不用以前 KoolShare LEDE 的特有的插件风格的软件中心
|
||||||
|
|
||||||
|
iStore 的无法弥补的缺陷:
|
||||||
|
|
||||||
|
1. 因为 OpenWRT 版本特别多,导致不同平台的插件依赖不一样。所以即使你的系统能安装 iStore,iStore 里面的插件不一定能安装
|
||||||
|
2. 我们需要固件开发者自主集成 iStore,并解决 iStore 里面的各种插件的依赖
|
||||||
|
|
||||||
|
我们建议固件开发者集成 iStore,这样固件开发者只需要发布一个精简固件,用户可以按需安装自己喜欢的插件。还能共享插件的教程,独立更新某个插件版本。
|
||||||
|
|
||||||
|
> 21版本的固件安装 iStore 需要依赖 `luci-compat`
|
||||||
|
|
||||||
|
## 安装 iStore 到 OpenWRT 官方固件
|
||||||
|
只支持 x86_64 和 arm64 设备。
|
||||||
|
|
||||||
|
```
|
||||||
|
opkg update || exit 1
|
||||||
|
cd /tmp
|
||||||
|
wget https://github.com/linkease/openwrt-app-actions/raw/main/applications/luci-app-systools/root/usr/share/systools/istore-reinstall.run
|
||||||
|
chmod 755 istore-reinstall.run
|
||||||
|
./istore-reinstall.run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 集成到自己编译的固件中
|
||||||
|
iStore 官方的软件仓库支持 `x86_64`,`arm64` 两个架构,这两个架构的基于 **OpenWRT** 固件都可以直接集成 iStore
|
||||||
|
|
||||||
|
只需在固件编译目录下执行:
|
||||||
|
```shell
|
||||||
|
echo >> feeds.conf.default
|
||||||
|
echo 'src-git istore https://github.com/linkease/istore;main' >> feeds.conf.default
|
||||||
|
./scripts/feeds update istore
|
||||||
|
./scripts/feeds install -d y -p istore luci-app-store
|
||||||
|
```
|
||||||
|
|
||||||
|
然后正常编译固件即可
|
||||||
|
|
||||||
|
## 功能预览
|
||||||
|
|
||||||
|

|
161
istore/luci/luci-app-store/API.md
Normal file
161
istore/luci/luci-app-store/API.md
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
### 路由器端API:
|
||||||
|
0. 获取csrfToken(用于POST请求)
|
||||||
|
```
|
||||||
|
GET /cgi-bin/luci/admin/store/token
|
||||||
|
|
||||||
|
|
||||||
|
{"token":"xxx"}
|
||||||
|
```
|
||||||
|
1. 已安装软件列表
|
||||||
|
```
|
||||||
|
GET /cgi-bin/luci/admin/store/installed
|
||||||
|
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"description": "DDNS.TO内网穿透",
|
||||||
|
"tags": [
|
||||||
|
"net",
|
||||||
|
"tool"
|
||||||
|
],
|
||||||
|
"entry": "/cgi-bin/luci/admin/services/ddnsto",
|
||||||
|
"author": "xiaobao",
|
||||||
|
"depends": [
|
||||||
|
"ddnsto",
|
||||||
|
"luci-app-ddnsto",
|
||||||
|
"luci-i18n-ddnsto-zh-cn"
|
||||||
|
],
|
||||||
|
"title": "DDNS.TO",
|
||||||
|
"time": 1629356347,
|
||||||
|
"release": 1,
|
||||||
|
"website": "https://www.ddnsto.com/",
|
||||||
|
"name": "ddnsto",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
2. 安装软件
|
||||||
|
```
|
||||||
|
POST /cgi-bin/luci/admin/store/install
|
||||||
|
token=xxx&package=upnp&autoconf=1&path=/mnt/nvme&enable=1
|
||||||
|
|
||||||
|
autoconf=1&path=/mnt/nvme&enable=1 是可选参数,表示安装完以后是否自动配置插件
|
||||||
|
|
||||||
|
{"code":0, "stdout":"", "stderr":""}
|
||||||
|
```
|
||||||
|
3. 更新软件
|
||||||
|
```
|
||||||
|
POST /cgi-bin/luci/admin/store/upgrade
|
||||||
|
token=xxx&package=upnp
|
||||||
|
|
||||||
|
|
||||||
|
{"code":0, "stdout":"", "stderr":""}
|
||||||
|
```
|
||||||
|
4. 卸载软件
|
||||||
|
```
|
||||||
|
POST /cgi-bin/luci/admin/store/remove
|
||||||
|
token=xxx&package=upnp
|
||||||
|
|
||||||
|
|
||||||
|
{"code":0, "stdout":"", "stderr":""}
|
||||||
|
```
|
||||||
|
5. 刷新可用软件列表
|
||||||
|
```
|
||||||
|
POST /cgi-bin/luci/admin/store/update
|
||||||
|
token=xxx
|
||||||
|
|
||||||
|
|
||||||
|
{"code":0, "stdout":"", "stderr":""}
|
||||||
|
```
|
||||||
|
6. 查询特定软件状态
|
||||||
|
```
|
||||||
|
GET /cgi-bin/luci/admin/store/status?package=ddnsto
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"description": "DDNS.TO内网穿透",
|
||||||
|
"tags": [
|
||||||
|
"net",
|
||||||
|
"tool"
|
||||||
|
],
|
||||||
|
"entry": "/cgi-bin/luci/admin/services/ddnsto",
|
||||||
|
"author": "xiaobao",
|
||||||
|
"depends": [
|
||||||
|
"ddnsto",
|
||||||
|
"luci-app-ddnsto",
|
||||||
|
"luci-i18n-ddnsto-zh-cn"
|
||||||
|
],
|
||||||
|
"installed": true,
|
||||||
|
"title": "DDNS.TO",
|
||||||
|
"time": "1629356347",
|
||||||
|
"release": 1,
|
||||||
|
"website": "https://www.ddnsto.com/",
|
||||||
|
"name": "ddnsto",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{"installed":false}
|
||||||
|
```
|
||||||
|
7. 任务状态(日志),已废弃,使用luci-lib-taskd提供的封装
|
||||||
|
```
|
||||||
|
GET /cgi-bin/luci/admin/store/log
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"stdout": "Installing app-meta-ddnsto (1.0.0) to root...\nDownloading http://192.168.9.168:9999/packages/aarch64_cortex-a53/meta/app-meta-ddnsto_1.0.0_all.ipk\nConfiguring app-meta-ddnsto.\n",
|
||||||
|
"stderr": "",
|
||||||
|
"code": 206
|
||||||
|
}
|
||||||
|
|
||||||
|
{"stdout":"","stderr":"","code":0}
|
||||||
|
```
|
||||||
|
8. 上传安装
|
||||||
|
```
|
||||||
|
POST /cgi-bin/luci/admin/store/upload
|
||||||
|
|
||||||
|
|
||||||
|
(文件上传表单,支持文件扩展名".ipk,.run")
|
||||||
|
|
||||||
|
|
||||||
|
{"code":0, "stdout":"", "stderr":""}
|
||||||
|
```
|
||||||
|
|
||||||
|
9. 检查iStore自身更新
|
||||||
|
```
|
||||||
|
GET /cgi-bin/luci/admin/store/check_self_upgrade
|
||||||
|
|
||||||
|
|
||||||
|
{"code":500, "msg":"Internal Error"}
|
||||||
|
{"code":200, "msg":"1.1.2"}
|
||||||
|
{"code":304, "msg":""}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 更新iStore自身
|
||||||
|
> 检查iStore自身更新接口返回code为200时才调用这个接口
|
||||||
|
```
|
||||||
|
POST /cgi-bin/luci/admin/store/do_self_upgrade
|
||||||
|
token=xxx
|
||||||
|
|
||||||
|
|
||||||
|
{"code":0, "stdout":"", "stderr":""}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 枚举块设备
|
||||||
|
```
|
||||||
|
GET /cgi-bin/luci/admin/store/get_block_devices
|
||||||
|
|
||||||
|
|
||||||
|
{"code":500, "msg":"Unable to execute block utility"}
|
||||||
|
{"code":200, "data":{"sda1":{"dev":"\/dev\/sda1","uuid":"f54566dd-ec58-4e24-9451-bbf75834add3","version":"1.0","type":"ext4","size":"238.46 GB"},"mmcblk0p2":{"dev":"\/dev\/mmcblk0p2","uuid":"dba3d0dc-f072-4e81-a0ac-ac35197fb286","version":"1.0","label":"etc","mount":"\/overlay","type":"ext4","size":"6.87 GB"},"mmcblk0p1":{"dev":"\/dev\/mmcblk0p1","uuid":"8f9564a1-68006e25-c4c26df6-de88ef16","version":"4.0","mount":"\/rom","type":"squashfs","size":"127.99 MB"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 检查插件是否已经配置过
|
||||||
|
```
|
||||||
|
GET /cgi-bin/luci/admin/store/configured?uci=ddnsto
|
||||||
|
|
||||||
|
uci参数是uci配置文件的名称,不是包名,不过大部分情况下都是一致的。
|
||||||
|
|
||||||
|
{"code":200}
|
||||||
|
{"code":200, "configured":true}
|
||||||
|
```
|
39
istore/luci/luci-app-store/Makefile
Normal file
39
istore/luci/luci-app-store/Makefile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Copyright (C) 2016 Openwrt.org
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
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-lib-ipkg +tar +libuci-lua +mount-utils +luci-lib-taskd
|
||||||
|
LUCI_EXTRA_DEPENDS:=luci-lib-taskd (>=1.0.19)
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
|
PKG_VERSION:=0.1.27-4
|
||||||
|
# PKG_RELEASE MUST be empty for luci.mk
|
||||||
|
PKG_RELEASE:=
|
||||||
|
|
||||||
|
ISTORE_UI_VERSION:=0.1.27
|
||||||
|
ISTORE_UI_RELEASE:=2
|
||||||
|
PKG_HASH:=7fb8e9983dd33b14c43de3caf6a6121df0727e0adac6c666354e0d8a0cffa0b8
|
||||||
|
|
||||||
|
PKG_SOURCE_URL_FILE:=v$(ISTORE_UI_VERSION)-$(ISTORE_UI_RELEASE).tar.gz
|
||||||
|
PKG_SOURCE:=istore-ui-$(PKG_SOURCE_URL_FILE)
|
||||||
|
PKG_SOURCE_URL:=https://github.com/linkease/istore-ui/archive/refs/tags
|
||||||
|
|
||||||
|
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
|
||||||
|
|
||||||
|
TARGET_CONFIGURE_OPTS= FRONTEND_DIST="$(BUILD_DIR)/istore-ui-$(ISTORE_UI_VERSION)-$(ISTORE_UI_RELEASE)/app-store-ui/src/dist" APP_STORE_VERSION="$(PKG_VERSION)"
|
||||||
|
TARGET_CONFIGURE_OPTS+= SED="$(SED)"
|
||||||
|
|
||||||
|
define Package/luci-app-store/conffiles
|
||||||
|
/etc/.app_store.id
|
||||||
|
/etc/config/istore
|
||||||
|
endef
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
|
|
3
istore/luci/luci-app-store/README.md
Normal file
3
istore/luci/luci-app-store/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# luci-app-store
|
||||||
|
OpenWRT应用商店
|
||||||
|
|
971
istore/luci/luci-app-store/luasrc/controller/store.lua
Normal file
971
istore/luci/luci-app-store/luasrc/controller/store.lua
Normal file
@ -0,0 +1,971 @@
|
|||||||
|
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()
|
||||||
|
local function store_api(action, onlypost)
|
||||||
|
local e = entry({"admin", "store", action}, onlypost and post("store_action", {action = action}) or call("store_action", {action = action}))
|
||||||
|
e.dependent = false -- 父节点不是必须的
|
||||||
|
e.leaf = true -- 没有子节点
|
||||||
|
end
|
||||||
|
|
||||||
|
local action
|
||||||
|
|
||||||
|
entry({"admin", "store"}, call("redirect_index"), _("iStore"), 31)
|
||||||
|
entry({"admin", "store", "pages"}, call("store_index")).leaf = true
|
||||||
|
if nixio.fs.access("/usr/lib/lua/luci/view/store/main_dev.htm") then
|
||||||
|
entry({"admin", "store", "dev"}, call("store_dev")).leaf = true
|
||||||
|
end
|
||||||
|
entry({"admin", "store", "token"}, call("store_token"))
|
||||||
|
entry({"admin", "store", "log"}, call("store_log"))
|
||||||
|
entry({"admin", "store", "uid"}, call("action_user_id"))
|
||||||
|
entry({"admin", "store", "upload"}, post("store_upload"))
|
||||||
|
entry({"admin", "store", "check_self_upgrade"}, call("check_self_upgrade"))
|
||||||
|
entry({"admin", "store", "do_self_upgrade"}, post("do_self_upgrade"))
|
||||||
|
entry({"admin", "store", "toggle_docker"}, post("toggle_docker"))
|
||||||
|
entry({"admin", "store", "toggle_arch"}, post("toggle_arch"))
|
||||||
|
entry({"admin", "store", "get_block_devices"}, call("get_block_devices"))
|
||||||
|
|
||||||
|
entry({"admin", "store", "configured"}, call("configured"))
|
||||||
|
entry({"admin", "store", "entrysh"}, post("entrysh"))
|
||||||
|
|
||||||
|
-- docker
|
||||||
|
entry({"admin", "store", "docker_check_dir"}, call("docker_check_dir"))
|
||||||
|
entry({"admin", "store", "docker_check_migrate"}, call("docker_check_migrate"))
|
||||||
|
entry({"admin", "store", "docker_migrate"}, post("docker_migrate"))
|
||||||
|
|
||||||
|
-- package
|
||||||
|
for _, action in ipairs({"update", "install", "upgrade", "remove", "autoconf"}) do
|
||||||
|
store_api(action, true)
|
||||||
|
end
|
||||||
|
for _, action in ipairs({"status", "installed"}) do
|
||||||
|
store_api(action, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- backup
|
||||||
|
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"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function user_id()
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
local json_parse = jsonc.parse
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local data = fs.readfile("/etc/.app_store.id")
|
||||||
|
|
||||||
|
local id
|
||||||
|
if data ~= nil then
|
||||||
|
id = json_parse(data)
|
||||||
|
end
|
||||||
|
if id == nil then
|
||||||
|
fs.unlink("/etc/.app_store.id")
|
||||||
|
id = {arch="",uid=""}
|
||||||
|
end
|
||||||
|
|
||||||
|
id.version = (fs.readfile("/etc/.app_store.version") or "?"):gsub("[\r\n]", "")
|
||||||
|
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
local function user_config()
|
||||||
|
local uci = require "luci.model.uci".cursor()
|
||||||
|
|
||||||
|
local data = {
|
||||||
|
hide_docker = uci:get("istore", "istore", "hide_docker") == "1",
|
||||||
|
ignore_arch = uci:get("istore", "istore", "ignore_arch") == "1",
|
||||||
|
last_path = uci:get("istore", "istore", "last_path"),
|
||||||
|
super_arch = uci:get("istore", "istore", "super_arch"),
|
||||||
|
channel = uci:get("istore", "istore", "channel")
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
local function vue_lang()
|
||||||
|
local i18n = require("luci.i18n")
|
||||||
|
local lang = i18n.translate("istore_vue_lang")
|
||||||
|
if lang == "istore_vue_lang" or lang == "" then
|
||||||
|
lang = "en"
|
||||||
|
end
|
||||||
|
return lang
|
||||||
|
end
|
||||||
|
|
||||||
|
local function flock(file, type)
|
||||||
|
local nixio = require "nixio"
|
||||||
|
local oflags = nixio.open_flags("wronly", "creat")
|
||||||
|
local lock, code, msg = nixio.open(file, oflags)
|
||||||
|
if not lock then
|
||||||
|
return nil, "Open lock failed: " .. msg
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Acquire lock
|
||||||
|
local stat, code, msg = lock:lock(type)
|
||||||
|
if not stat then
|
||||||
|
lock:close()
|
||||||
|
return nil, "Lock failed: " .. msg
|
||||||
|
end
|
||||||
|
return lock, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_exec(cmd, async)
|
||||||
|
local nixio = require "nixio"
|
||||||
|
local os = require "os"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local rshift = nixio.bit.rshift
|
||||||
|
|
||||||
|
local lock, msg = flock("/var/lock/istore.lock", "tlock")
|
||||||
|
if lock == nil then
|
||||||
|
return 255, "", 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")
|
||||||
|
|
||||||
|
fs.unlink("/var/log/istore.stderr")
|
||||||
|
fs.unlink("/var/log/istore.stdout")
|
||||||
|
|
||||||
|
lock:lock("ulock")
|
||||||
|
lock:close()
|
||||||
|
|
||||||
|
e = e or ""
|
||||||
|
if r == 256 and e == "" then
|
||||||
|
e = "os.execute exit code 1"
|
||||||
|
end
|
||||||
|
return rshift(r,8), o or "", e or ""
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect_index()
|
||||||
|
luci.http.redirect(luci.dispatcher.build_url(unpack(page_index)))
|
||||||
|
end
|
||||||
|
|
||||||
|
function store_index()
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local features = { "_lua_force_array_" }
|
||||||
|
if fs.access("/usr/libexec/istore/backup") then
|
||||||
|
features[#features+1] = "backup"
|
||||||
|
end
|
||||||
|
if luci.sys.call("which docker >/dev/null 2>&1") == 0 then
|
||||||
|
features[#features+1] = "docker"
|
||||||
|
end
|
||||||
|
if luci.sys.call("[ -d /ext_overlay ] >/dev/null 2>&1") == 0 then
|
||||||
|
features[#features+1] = "sandbox"
|
||||||
|
end
|
||||||
|
if luci.sys.call("[ -f /www/luci-static/resources/luci.js ] >/dev/null 2>&1") == 0 then
|
||||||
|
features[#features+1] = "luci-js"
|
||||||
|
end
|
||||||
|
luci.template.render("store/main", {prefix=luci.dispatcher.build_url(unpack(page_index)),id=user_id(),lang=vue_lang(),user_config=user_config(),features=features})
|
||||||
|
end
|
||||||
|
|
||||||
|
function store_dev()
|
||||||
|
luci.template.render("store/main_dev", {prefix=luci.dispatcher.build_url(unpack({"admin", "store", "dev"})),id=user_id(),lang=vue_lang(),user_config=user_config()})
|
||||||
|
end
|
||||||
|
|
||||||
|
function store_log()
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local code = 0
|
||||||
|
local e = fs.readfile("/var/log/istore.stderr")
|
||||||
|
local o = fs.readfile("/var/log/istore.stdout")
|
||||||
|
if o ~= nil then
|
||||||
|
code = 206
|
||||||
|
end
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code=code,stdout=o or "",stderr=e or ""})
|
||||||
|
end
|
||||||
|
|
||||||
|
function action_user_id()
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(user_id())
|
||||||
|
end
|
||||||
|
|
||||||
|
function check_self_upgrade()
|
||||||
|
local ret = {
|
||||||
|
code = 500,
|
||||||
|
msg = "Unknown"
|
||||||
|
}
|
||||||
|
local r,o,e = is_exec(myopkg .. " check_self_upgrade")
|
||||||
|
if r ~= 0 then
|
||||||
|
ret.msg = e
|
||||||
|
else
|
||||||
|
ret.code = o == "" and 304 or 200
|
||||||
|
ret.msg = o:gsub("[\r\n]", "")
|
||||||
|
end
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
function do_self_upgrade()
|
||||||
|
local code, out, err, ret
|
||||||
|
code,out,err = is_exec(myopkg .. " do_self_upgrade")
|
||||||
|
ret = {
|
||||||
|
code = code,
|
||||||
|
stdout = out,
|
||||||
|
stderr = err
|
||||||
|
}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Internal action function
|
||||||
|
local function _action(exe, cmd, ...)
|
||||||
|
|
||||||
|
local pkg = ""
|
||||||
|
for k, v in pairs({...}) do
|
||||||
|
pkg = pkg .. " " .. luci.util.shellquote(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local c = "%s %s %s" %{ exe, cmd, pkg }
|
||||||
|
|
||||||
|
return is_exec(c, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function validate_pkgname(val)
|
||||||
|
return (val ~= nil and val:match("^[a-zA-Z0-9_-]+$") ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_installed_and_cache()
|
||||||
|
local metadir = "/usr/lib/opkg/meta"
|
||||||
|
local cachedir = "/tmp/cache/istore"
|
||||||
|
local cachefile = cachedir .. "/installed.json"
|
||||||
|
local metapkgpre = "app-meta-"
|
||||||
|
local nixio = require "nixio"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local ipkg = require "luci.model.ipkg"
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
local result = {}
|
||||||
|
local lock, msg = flock("/var/lock/istore-installed.lock", "lock")
|
||||||
|
local ms = fs.stat(metadir)
|
||||||
|
local cs = fs.stat(cachefile)
|
||||||
|
if not ms then
|
||||||
|
result = {}
|
||||||
|
elseif not cs or ms["mtime"] > cs["mtime"] then
|
||||||
|
local itr = fs.dir(metadir)
|
||||||
|
local data = {}
|
||||||
|
if itr then
|
||||||
|
local i18n = require("luci.i18n")
|
||||||
|
local pkg
|
||||||
|
for pkg in itr do
|
||||||
|
if pkg:match("^.*%.json$") then
|
||||||
|
local metadata = fs.readfile(metadir .. "/" .. pkg)
|
||||||
|
if metadata ~= nil then
|
||||||
|
local meta = jsonc.parse(metadata)
|
||||||
|
if meta == nil then
|
||||||
|
local name = pkg:gsub("^(.-)%.json$", "%1")
|
||||||
|
meta = {
|
||||||
|
name = name,
|
||||||
|
title = "{ " .. name .. " }",
|
||||||
|
author = "<UNKNOWN>",
|
||||||
|
version = "0.0.0",
|
||||||
|
description = i18n.translate("This package is broken! Please reinstall or uninstall it."),
|
||||||
|
depends = {},
|
||||||
|
tags = {"broken"},
|
||||||
|
broken = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local metapkg = metapkgpre .. meta.name
|
||||||
|
local status = ipkg.status(metapkg)
|
||||||
|
if next(status) ~= nil then
|
||||||
|
meta.time = tonumber(status[metapkg]["Installed-Time"])
|
||||||
|
data[#data+1] = meta
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result = data
|
||||||
|
fs.mkdirr(cachedir)
|
||||||
|
local oflags = nixio.open_flags("rdwr", "creat")
|
||||||
|
local mfile, code, msg = nixio.open(cachefile, oflags)
|
||||||
|
mfile:writeall(jsonc.stringify(result))
|
||||||
|
mfile:close()
|
||||||
|
else
|
||||||
|
result = jsonc.parse(fs.readfile(cachefile) or "")
|
||||||
|
end
|
||||||
|
lock:lock("ulock")
|
||||||
|
lock:close()
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function store_action(param)
|
||||||
|
local metadir = "/usr/lib/opkg/meta"
|
||||||
|
local metapkgpre = "app-meta-"
|
||||||
|
local code, out, err, ret
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local ipkg = require "luci.model.ipkg"
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
local json_parse = jsonc.parse
|
||||||
|
local action = param.action or ""
|
||||||
|
|
||||||
|
if action == "status" then
|
||||||
|
local pkg = luci.http.formvalue("package")
|
||||||
|
if not validate_pkgname(pkg) then
|
||||||
|
luci.http.status(400, "Bad Request")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local metapkg = metapkgpre .. pkg
|
||||||
|
local meta = {}
|
||||||
|
local metadata = fs.readfile(metadir .. "/" .. pkg .. ".json")
|
||||||
|
|
||||||
|
if metadata ~= nil then
|
||||||
|
meta = json_parse(metadata) or {}
|
||||||
|
end
|
||||||
|
meta.installed = false
|
||||||
|
local status = ipkg.status(metapkg)
|
||||||
|
if next(status) ~= nil then
|
||||||
|
meta.installed=true
|
||||||
|
meta.time=tonumber(status[metapkg]["Installed-Time"])
|
||||||
|
end
|
||||||
|
|
||||||
|
ret = meta
|
||||||
|
elseif action == "installed" then
|
||||||
|
local data = get_installed_and_cache()
|
||||||
|
ret = data
|
||||||
|
else
|
||||||
|
local pkg = luci.http.formvalue("package")
|
||||||
|
if not validate_pkgname(pkg) then
|
||||||
|
luci.http.status(400, "Bad Request")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local metapkg = pkg and (metapkgpre .. pkg) or ""
|
||||||
|
if action == "update" or pkg then
|
||||||
|
if action == "update" or action == "install" or action == "autoconf" then
|
||||||
|
if (action == "install" and "1" == luci.http.formvalue("autoconf")) or action == "autoconf" then
|
||||||
|
local autoenv = "AUTOCONF=" .. pkg
|
||||||
|
local autopath = luci.http.formvalue("path")
|
||||||
|
local autoenable = luci.http.formvalue("enable")
|
||||||
|
if autopath ~= nil then
|
||||||
|
autoenv = autoenv .. " path=" .. luci.util.shellquote(autopath)
|
||||||
|
local uci = require "luci.model.uci".cursor()
|
||||||
|
uci:set("istore", "istore", "last_path", autopath)
|
||||||
|
uci:commit("istore")
|
||||||
|
end
|
||||||
|
if autoenable ~= nil then
|
||||||
|
autoenv = autoenv .. " enable=" .. autoenable
|
||||||
|
end
|
||||||
|
code, out, err = _action(myopkg, luci.util.shellquote(autoenv), action, metapkg)
|
||||||
|
else
|
||||||
|
code, out, err = _action(myopkg, action, metapkg)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local meta = json_parse(fs.readfile(metadir .. "/" .. pkg .. ".json"))
|
||||||
|
local pkgs = {}
|
||||||
|
if meta == nil then
|
||||||
|
meta = {
|
||||||
|
depends = {},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if action == "upgrade" then
|
||||||
|
pkgs = meta.depends
|
||||||
|
table.insert(pkgs, metapkg)
|
||||||
|
code, out, err = _action(myopkg, action, unpack(pkgs))
|
||||||
|
else -- remove
|
||||||
|
for _, dep in ipairs(meta.depends) do
|
||||||
|
if dep ~= "docker-deps" and dep ~= "luci-js-deps" then
|
||||||
|
pkgs[#pkgs+1] = dep
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(pkgs, metapkg)
|
||||||
|
code, out, err = _action(myopkg, action, unpack(pkgs))
|
||||||
|
fs.unlink("/tmp/luci-indexcache")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
code = 400
|
||||||
|
err = "package is null"
|
||||||
|
end
|
||||||
|
|
||||||
|
ret = {
|
||||||
|
code = code,
|
||||||
|
stdout = out,
|
||||||
|
stderr = err
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
function store_token()
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
require "luci.template".render_string("{\"token\":\"<%=token%>\"}")
|
||||||
|
end
|
||||||
|
|
||||||
|
function store_upload()
|
||||||
|
local fd
|
||||||
|
local path
|
||||||
|
local finished = false
|
||||||
|
local tmpdir = "/tmp/is-root/tmp"
|
||||||
|
luci.http.setfilehandler(
|
||||||
|
function(meta, chunk, eof)
|
||||||
|
if not fd then
|
||||||
|
path = tmpdir .. "/" .. meta.file
|
||||||
|
nixio.fs.mkdirr(tmpdir)
|
||||||
|
fd = io.open(path, "w")
|
||||||
|
end
|
||||||
|
if chunk then
|
||||||
|
fd:write(chunk)
|
||||||
|
end
|
||||||
|
if eof then
|
||||||
|
fd:close()
|
||||||
|
finished = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
local code, out, err
|
||||||
|
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\"; RET=$?; rm -f \"%s\"; exit $RET" %{ path, path, path, path, path })
|
||||||
|
else
|
||||||
|
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)
|
||||||
|
local ret = {
|
||||||
|
code = code,
|
||||||
|
stdout = out,
|
||||||
|
stderr = err
|
||||||
|
}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
function configured()
|
||||||
|
local uci = luci.http.formvalue("uci")
|
||||||
|
if not validate_pkgname(uci) then
|
||||||
|
luci.http.status(400, "Bad Request")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local configured = nixio.fs.access("/etc/config/" .. uci)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code=200, configured=configured})
|
||||||
|
end
|
||||||
|
|
||||||
|
function entrysh()
|
||||||
|
local package = luci.http.formvalue("package")
|
||||||
|
local update = luci.http.formvalue("update")
|
||||||
|
local hostname = luci.http.formvalue("hostname")
|
||||||
|
if hostname == nil or hostname == "" or not hostname:match("^[a-zA-Z0-9_%[][a-zA-Z0-9_%-%.%:%]]*$") then
|
||||||
|
luci.http.status(400, "Bad Request")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local nixio = require "nixio"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local hostnameq = luci.util.shellquote(hostname)
|
||||||
|
local cachedir = "/tmp/cache/istore/entrysh/" .. hostname
|
||||||
|
fs.mkdirr(cachedir)
|
||||||
|
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
local results = {}
|
||||||
|
local errors = {}
|
||||||
|
local force = update == "1"
|
||||||
|
local candidate = nil
|
||||||
|
if package ~= nil and package ~= "" then
|
||||||
|
candidate = luci.util.split(package, ",")
|
||||||
|
end
|
||||||
|
local installed = get_installed_and_cache()
|
||||||
|
local lock, msg = flock("/var/lock/istore-entrysh.lock", "lock")
|
||||||
|
local meta
|
||||||
|
for _, meta in ipairs(installed) do
|
||||||
|
if meta.flags ~= nil and meta.uci ~= nil and luci.util.contains(meta.flags, "entrysh")
|
||||||
|
and (candidate == nil or luci.util.contains(candidate, meta.name)) then
|
||||||
|
local entryfile = "/usr/libexec/istoree/" .. meta.name .. ".sh"
|
||||||
|
local ucifile = "/etc/config/" .. meta.uci
|
||||||
|
local cachefile = cachedir .. "/" .. meta.name .. ".json"
|
||||||
|
local status = nil
|
||||||
|
if not force then
|
||||||
|
local us = fs.stat(ucifile)
|
||||||
|
local cs = fs.stat(cachefile)
|
||||||
|
if cs ~= nil and us["mtime"] <= cs["mtime"] then
|
||||||
|
status = jsonc.parse(fs.readfile(cachefile) or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if status ~= nil then
|
||||||
|
results[#results+1] = status
|
||||||
|
elseif fs.access(entryfile) then
|
||||||
|
local o = luci.util.exec(entryfile .. " status " .. hostnameq)
|
||||||
|
if o == nil or o == "" then
|
||||||
|
errors[#errors+1] = {app=meta.name, code=500, msg="entrysh execute failed"}
|
||||||
|
else
|
||||||
|
status = jsonc.parse(o)
|
||||||
|
if status == nil then
|
||||||
|
errors[#errors+1] = {app=meta.name, code=500, msg="json parse failed: " .. o}
|
||||||
|
else
|
||||||
|
results[#results+1] = status
|
||||||
|
local oflags = nixio.open_flags("rdwr", "creat")
|
||||||
|
local mfile, code, msg = nixio.open(cachefile, oflags)
|
||||||
|
mfile:writeall(jsonc.stringify(status))
|
||||||
|
mfile:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
errors[#errors+1] = {app=meta.name, code=404, msg="entrysh of this package not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lock:lock("ulock")
|
||||||
|
lock:close()
|
||||||
|
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code=200, status=results, errors=errors})
|
||||||
|
end
|
||||||
|
|
||||||
|
function docker_check_dir()
|
||||||
|
local docker_on_system = luci.sys.call("/usr/libexec/istore/docker check_dir >/dev/null 2>&1") ~= 0
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code=200, docker_on_system=docker_on_system})
|
||||||
|
end
|
||||||
|
|
||||||
|
function docker_check_migrate()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
if path == nil or path == "" then
|
||||||
|
luci.http.status(400, "Bad Request")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r,o,e = is_exec("/usr/libexec/istore/docker migrate_check " .. luci.util.shellquote(path))
|
||||||
|
local result = "good"
|
||||||
|
if r == 1 then
|
||||||
|
result = "bad"
|
||||||
|
elseif r == 2 then
|
||||||
|
result = "existed"
|
||||||
|
end
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code=200, result=result, error=e})
|
||||||
|
end
|
||||||
|
|
||||||
|
function docker_migrate()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
if path == nil or path == "" then
|
||||||
|
luci.http.status(400, "Bad Request")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local action = "migrate"
|
||||||
|
local overwrite = luci.http.formvalue("overwrite")
|
||||||
|
if overwrite == "chdir" then
|
||||||
|
action = "change_dir"
|
||||||
|
end
|
||||||
|
local r,o,e = is_exec("/usr/libexec/istore/docker " .. action .. " " .. luci.util.shellquote(path), true)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code=r, stdout=o, stderr=e})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function split(str,reps)
|
||||||
|
local resultStrList = {}
|
||||||
|
string.gsub(str,'[^'..reps..']+',function (w)
|
||||||
|
table.insert(resultStrList,w)
|
||||||
|
end)
|
||||||
|
return resultStrList
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ltn12_popen(command)
|
||||||
|
|
||||||
|
local fdi, fdo = nixio.pipe()
|
||||||
|
local pid = nixio.fork()
|
||||||
|
|
||||||
|
if pid > 0 then
|
||||||
|
fdo:close()
|
||||||
|
local close
|
||||||
|
return function()
|
||||||
|
local buffer = fdi:read(2048)
|
||||||
|
local wpid, stat = nixio.waitpid(pid, "nohang")
|
||||||
|
if not close and wpid and stat == "exited" then
|
||||||
|
close = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if buffer and #buffer > 0 then
|
||||||
|
return buffer
|
||||||
|
elseif close then
|
||||||
|
fdi:close()
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif pid == 0 then
|
||||||
|
nixio.dup(fdo, nixio.stdout)
|
||||||
|
fdi:close()
|
||||||
|
fdo:close()
|
||||||
|
nixio.exec("/bin/sh", "-c", command)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- call get_support_backup_features
|
||||||
|
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(is_backup .. " get_support_backup_features")
|
||||||
|
if r ~= 0 then
|
||||||
|
error_ret.msg = e
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
else
|
||||||
|
success_ret.code = 200
|
||||||
|
success_ret.msg = jsonc.stringify(split(o,'\n'))
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(success_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- post light_backup
|
||||||
|
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(is_backup .. " backup")
|
||||||
|
|
||||||
|
if r ~= 0 then
|
||||||
|
error_ret.msg = e
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
else
|
||||||
|
success_ret.code = 200
|
||||||
|
success_ret.msg = o:gsub("[\r\n]", "")
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(success_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- call get_light_backup_file
|
||||||
|
function get_light_backup_file()
|
||||||
|
local light_backup_cmd = "tar -c %s | gzip 2>/dev/null"
|
||||||
|
local loght_backup_filelist = "/etc/istore/app.list"
|
||||||
|
local reader = ltn12_popen(light_backup_cmd:format(loght_backup_filelist))
|
||||||
|
luci.http.header('Content-Disposition', 'attachment; filename="light-backup-%s-%s.tar.gz"' % {
|
||||||
|
luci.sys.hostname(), os.date("%Y-%m-%d")})
|
||||||
|
luci.http.prepare_content("application/x-targz")
|
||||||
|
luci.ltn12.pump.all(reader, luci.http.write)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function update_local_backup_path(path)
|
||||||
|
local uci = require "uci"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local x = uci.cursor()
|
||||||
|
local local_backup_path
|
||||||
|
|
||||||
|
if fs.access("/etc/config/istore") then
|
||||||
|
local_backup_path = x:get("istore","istore","local_backup_path")
|
||||||
|
else
|
||||||
|
--create config file
|
||||||
|
local f=io.open("/etc/config/istore","a+")
|
||||||
|
f:write("config istore \'istore\'\n\toption local_backup_path \'\'")
|
||||||
|
f:flush()
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
if path ~= local_backup_path then
|
||||||
|
-- set uci config
|
||||||
|
x:set("istore","istore","local_backup_path",path)
|
||||||
|
x:commit("istore")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- post local_backup
|
||||||
|
function local_backup()
|
||||||
|
local code, out, err, ret
|
||||||
|
local error_ret
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
if path ~= "" then
|
||||||
|
-- judge path
|
||||||
|
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, 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 = _action(is_backup, "backup", path)
|
||||||
|
ret = {
|
||||||
|
code = code,
|
||||||
|
stdout = out,
|
||||||
|
stderr = err
|
||||||
|
}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- error
|
||||||
|
error_ret = {code = 500, stderr = "Path Unknown"}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- post light_restore
|
||||||
|
function light_restore()
|
||||||
|
local fd
|
||||||
|
local path
|
||||||
|
local finished = false
|
||||||
|
local tmpdir = "/tmp/"
|
||||||
|
luci.http.setfilehandler(
|
||||||
|
function(meta, chunk, eof)
|
||||||
|
if not fd then
|
||||||
|
path = tmpdir .. "/" .. meta.file
|
||||||
|
fd = io.open(path, "w")
|
||||||
|
end
|
||||||
|
if chunk then
|
||||||
|
fd:write(chunk)
|
||||||
|
end
|
||||||
|
if eof then
|
||||||
|
fd:close()
|
||||||
|
finished = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
local code, out, err, ret
|
||||||
|
|
||||||
|
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 = _action(is_backup, "restore")
|
||||||
|
ret = {
|
||||||
|
code = code,
|
||||||
|
stdout = out,
|
||||||
|
stderr = err
|
||||||
|
}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
else
|
||||||
|
local error_ret = {code = 500, stderr = "File is error!"}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ret = {code = 500, stderr = "upload failed!"}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- post local_restore
|
||||||
|
function local_restore()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
local code, out, err, ret
|
||||||
|
if path ~= "" then
|
||||||
|
code,out,err = _action(is_backup, "restore", path)
|
||||||
|
ret = {
|
||||||
|
code = code,
|
||||||
|
stdout = out,
|
||||||
|
stderr = err
|
||||||
|
}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(ret)
|
||||||
|
else
|
||||||
|
-- error
|
||||||
|
error_ret = {code = 500, stderr = "Path Unknown"}
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- call get_backup_app_list_file_path
|
||||||
|
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(is_backup .. " get_backup_app_list_file_path")
|
||||||
|
if r ~= 0 then
|
||||||
|
error_ret.msg = e
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
else
|
||||||
|
success_ret.code = 200
|
||||||
|
success_ret.msg = o:gsub("[\r\n]", "")
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(success_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- call get_backup_app_list
|
||||||
|
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(is_backup .. " get_backup_app_list")
|
||||||
|
if r ~= 0 then
|
||||||
|
error_ret.msg = e
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
else
|
||||||
|
success_ret.code = 200
|
||||||
|
success_ret.msg = jsonc.stringify(split(o,'\n'))
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(success_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- call get_available_backup_file_list
|
||||||
|
function get_available_backup_file_list()
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
local error_ret = {code = 500, msg = "Unknown"}
|
||||||
|
local success_ret = {code = 200,msg = "Unknown"}
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
local r,o,e
|
||||||
|
|
||||||
|
if path ~= "" then
|
||||||
|
-- update local backup path
|
||||||
|
update_local_backup_path(path)
|
||||||
|
r,o,e = is_exec(is_backup .. " get_available_backup_file_list " .. luci.util.shellquote(path))
|
||||||
|
if r ~= 0 then
|
||||||
|
error_ret.msg = e
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
else
|
||||||
|
success_ret.code = 200
|
||||||
|
success_ret.msg = jsonc.stringify(split(o,'\n'))
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(success_ret)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- set error code
|
||||||
|
error_ret.msg = "Path Unknown"
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- post set_local_backup_dir_path
|
||||||
|
function set_local_backup_dir_path()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
local success_ret = {code = 200, msg = "Success"}
|
||||||
|
local error_ret = {code = 500, msg = "Unknown"}
|
||||||
|
|
||||||
|
if path ~= "" then
|
||||||
|
-- update local backup path
|
||||||
|
update_local_backup_path(path)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(success_ret)
|
||||||
|
else
|
||||||
|
-- set error code
|
||||||
|
error_ret.msg = "Path Unknown"
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- call get_local_backup_dir_path
|
||||||
|
function get_local_backup_dir_path()
|
||||||
|
local uci = require "uci"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local x = uci.cursor()
|
||||||
|
local local_backup_path = nil
|
||||||
|
local success_ret = {code = 200,msg = "Unknown"}
|
||||||
|
local error_ret = {code = 500, msg = "Path Unknown"}
|
||||||
|
|
||||||
|
if fs.access("/etc/config/istore") then
|
||||||
|
local_backup_path = x:get("istore","istore","local_backup_path")
|
||||||
|
if local_backup_path == nil then
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
else
|
||||||
|
success_ret.msg = local_backup_path:gsub("[\r\n]", "")
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(success_ret)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- copy from /usr/lib/lua/luci/model/diskman.lua
|
||||||
|
local function byte_format(byte)
|
||||||
|
local suff = {"B", "KB", "MB", "GB", "TB"}
|
||||||
|
for i=1, 5 do
|
||||||
|
if byte > 1024 and i < 5 then
|
||||||
|
byte = byte / 1024
|
||||||
|
else
|
||||||
|
return string.format("%.2f %s", byte, suff[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- copy from /usr/libexec/rpcd/luci
|
||||||
|
local function getBlockDevices()
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
|
||||||
|
local block = io.popen("/sbin/block info", "r")
|
||||||
|
if block then
|
||||||
|
local rv = {}
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local ln = block:read("*l")
|
||||||
|
if not ln then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local dev = ln:match("^/dev/(.-):")
|
||||||
|
if dev then
|
||||||
|
local s = tonumber((fs.readfile("/sys/class/block/" .. dev .."/size")))
|
||||||
|
local e = {
|
||||||
|
dev = "/dev/" .. dev,
|
||||||
|
size = s and byte_format(s * 512)
|
||||||
|
}
|
||||||
|
|
||||||
|
local key, val = { }
|
||||||
|
for key, val in ln:gmatch([[(%w+)="(.-)"]]) do
|
||||||
|
e[key:lower()] = val
|
||||||
|
end
|
||||||
|
|
||||||
|
rv[dev] = e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
block:close()
|
||||||
|
|
||||||
|
return rv
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_block_devices()
|
||||||
|
local error_ret = {code = 500, msg = "Unable to execute block utility"}
|
||||||
|
local devices = getBlockDevices()
|
||||||
|
if devices ~= nil then
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code = 200, data = devices})
|
||||||
|
else
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(error_ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function toggle_docker()
|
||||||
|
local uci = require "luci.model.uci".cursor()
|
||||||
|
local hide = luci.http.formvalue("hide")
|
||||||
|
uci:set("istore", "istore", "hide_docker", hide == "true" and "1" or "0")
|
||||||
|
uci:commit("istore")
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code = 200, msg = "Success"})
|
||||||
|
end
|
||||||
|
|
||||||
|
function toggle_arch()
|
||||||
|
local uci = require "luci.model.uci".cursor()
|
||||||
|
local ignore = luci.http.formvalue("ignore")
|
||||||
|
uci:set("istore", "istore", "ignore_arch", ignore == "true" and "1" or "0")
|
||||||
|
uci:commit("istore")
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({code = 200, msg = "Success"})
|
||||||
|
end
|
63
istore/luci/luci-app-store/luasrc/view/store/main.htm
Normal file
63
istore/luci/luci-app-store/luasrc/view/store/main.htm
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<%+header%>
|
||||||
|
<%
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
%>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var vue_prefix="<%=prefix%>";
|
||||||
|
var myurl = window.location.pathname;
|
||||||
|
window.addEventListener('popstate', function(){
|
||||||
|
if (myurl != window.location.pathname
|
||||||
|
&& window.location.pathname != vue_prefix
|
||||||
|
&& ! window.location.pathname.startsWith(vue_prefix+'/')) {
|
||||||
|
window.location.href = window.location.pathname;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.vue_base = vue_prefix + '/';
|
||||||
|
// window.istore_api_base = "https://istore.istoreos.com";
|
||||||
|
window.vue_lang_data = '/luci-static/istore/i18n/<%=lang%>.json?v=<%=id.version%>';
|
||||||
|
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)%>.filter(f => f !== '_lua_force_array_');
|
||||||
|
window.istore_config = <%=jsonc.stringify(user_config or {})%>;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<h2 name="content"><%:iStore%>
|
||||||
|
<a onclick="void(0)" href="https://github.com/linkease/istore/issues/22" target="_blank" style="text-decoration: none;">
|
||||||
|
v<%=id.version%>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<link rel="stylesheet" href="/luci-static/istore/style.css?v=<%=id.version%>">
|
||||||
|
<div id="app">
|
||||||
|
</div>
|
||||||
|
<%+tasks/embed%>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
let beforeunloadRegistered = false;
|
||||||
|
taskd.show_mask_on_stopped = true;
|
||||||
|
window.istore_log = function(flush_menu_onclose, onExit) {
|
||||||
|
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, onExit);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
<%
|
||||||
|
local taskd = require "luci.model.tasks"
|
||||||
|
local status = taskd.status("istore")
|
||||||
|
if status.running or status.exit_code ~= 404 then
|
||||||
|
-%>
|
||||||
|
window.istore_log(true);
|
||||||
|
<%
|
||||||
|
end
|
||||||
|
%>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="module" crossorigin src="/luci-static/istore/index.js?v=<%=id.version%>"></script>
|
||||||
|
<%+footer%>
|
291
istore/luci/luci-app-store/root/bin/is-opkg
Executable file
291
istore/luci/luci-app-store/root/bin/is-opkg
Executable file
@ -0,0 +1,291 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# this script MUST supports executting without luci-app-store installed,
|
||||||
|
# so we can use this script to install luci-app-store itself
|
||||||
|
|
||||||
|
action=${1}
|
||||||
|
shift
|
||||||
|
if [ "${action:0:9}" = "AUTOCONF=" ]; then
|
||||||
|
export "ISTORE_${action}"
|
||||||
|
exec "$0" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
OPKG_CONF_DIR_M=${IS_ROOT}/etc/opkg_m
|
||||||
|
FEEDS_SERVER=https://istore.istoreos.com/repo
|
||||||
|
FEEDS_SERVER_MIRRORS="https://repo.istoreos.com/repo"
|
||||||
|
DISABLE_MIRROR=false
|
||||||
|
ARCH=`sed -n -e 's/^Architecture: *\([^ ]\+\) *$/\1/p' /rom/usr/lib/opkg/info/libc.control /usr/lib/opkg/info/libc.control 2>/dev/null | head -1`
|
||||||
|
|
||||||
|
# for istore self upgrade
|
||||||
|
ISTORE_PKG=luci-app-store
|
||||||
|
ISTORE_DEP_PKGS="luci-lib-taskd luci-lib-xterm taskd"
|
||||||
|
ISTORE_INDEX=https://istore.istoreos.com/repo/all/store/Packages.gz
|
||||||
|
|
||||||
|
is_init() {
|
||||||
|
mkdir -p ${DL_DIR} ${LISTS_DIR} ${IS_ROOT}/etc ${IS_ROOT}/var
|
||||||
|
|
||||||
|
cat /etc/opkg.conf | grep -Fv lists_dir | grep -Fv check_signature > ${IS_ROOT}/etc/opkg.conf
|
||||||
|
|
||||||
|
cp ${IS_ROOT}/etc/opkg.conf ${IS_ROOT}/etc/opkg_o.conf
|
||||||
|
|
||||||
|
echo >> ${IS_ROOT}/etc/opkg.conf
|
||||||
|
echo "lists_dir ext ${LISTS_DIR}" >> ${IS_ROOT}/etc/opkg.conf
|
||||||
|
# create opkg_o.conf for executting 'opkg update' with offline-root, so we don't overwrite system opkg list
|
||||||
|
echo >> ${IS_ROOT}/etc/opkg_o.conf
|
||||||
|
echo "lists_dir ext ${LISTS_DIR_O}" >> ${IS_ROOT}/etc/opkg_o.conf
|
||||||
|
|
||||||
|
cp -au /etc/opkg ${IS_ROOT}/etc/
|
||||||
|
[ -e ${IS_ROOT}/var/lock ] || ln -s /var/lock ${IS_ROOT}/var/lock
|
||||||
|
}
|
||||||
|
|
||||||
|
opkg_wrap() {
|
||||||
|
OPKG_CONF_DIR=${OPKG_CONF_DIR} opkg -f ${IS_ROOT}/etc/opkg.conf "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
opkg_wrap_mirrors() {
|
||||||
|
local server
|
||||||
|
local file
|
||||||
|
if ! $DISABLE_MIRROR; then
|
||||||
|
for server in $FEEDS_SERVER_MIRRORS ; do
|
||||||
|
rm -rf "${OPKG_CONF_DIR_M}" 2>/dev/null
|
||||||
|
mkdir -p "${OPKG_CONF_DIR_M}" 2>/dev/null
|
||||||
|
ls "${OPKG_CONF_DIR}/" | while read; do
|
||||||
|
file="$REPLY"
|
||||||
|
if [ -f "${OPKG_CONF_DIR}/$file" -a "${file: -5}" = ".conf" ]; then
|
||||||
|
sed "s#$FEEDS_SERVER/#$server/#g" "${OPKG_CONF_DIR}/$file" >"${OPKG_CONF_DIR_M}/$file"
|
||||||
|
touch -r "${OPKG_CONF_DIR}/$file" "${OPKG_CONF_DIR_M}/$file" 2>/dev/null
|
||||||
|
else
|
||||||
|
cp -a "${OPKG_CONF_DIR}/$file" "${OPKG_CONF_DIR_M}/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "Try mirror server $server"
|
||||||
|
OPKG_CONF_DIR=${OPKG_CONF_DIR_M} opkg -f ${IS_ROOT}/etc/opkg.conf "$@" && return 0
|
||||||
|
done
|
||||||
|
DISABLE_MIRROR=true
|
||||||
|
fi
|
||||||
|
echo "Try origin server $FEEDS_SERVER"
|
||||||
|
OPKG_CONF_DIR=${OPKG_CONF_DIR} opkg -f ${IS_ROOT}/etc/opkg.conf "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
alias fcurl='curl -L --fail --show-error'
|
||||||
|
|
||||||
|
check_space() {
|
||||||
|
local free="$((`df -kP / | awk 'NR==2 {print $4}'` >> 10 ))"
|
||||||
|
if [ "$free" -lt 1 ]; then
|
||||||
|
echo "Root disk full!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if [ -z "${ARCH}" ]; then
|
||||||
|
echo "Get architecture failed" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Fetch feed list for ${ARCH}"
|
||||||
|
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/meta.conf "${FEEDS_SERVER}/all/meta.conf" && \
|
||||||
|
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/all.conf "${FEEDS_SERVER}/all/isfeeds.conf" && \
|
||||||
|
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/arch.conf "${FEEDS_SERVER}/${ARCH}/isfeeds.conf" || \
|
||||||
|
return 1
|
||||||
|
|
||||||
|
echo "Update feeds index"
|
||||||
|
opkg -f ${IS_ROOT}/etc/opkg_o.conf --offline-root ${IS_ROOT} update
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
update_if_outdate() {
|
||||||
|
local idle_t=$((`date '+%s'` - `date -r ${IS_ROOT}/.last_force_ts '+%s' 2>/dev/null || echo '0'`))
|
||||||
|
[ $idle_t -gt ${1:-120} ] || return 2
|
||||||
|
update || return 1
|
||||||
|
touch ${IS_ROOT}/.last_force_ts
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_self_upgrade() {
|
||||||
|
local newest=`curl --connect-timeout 2 --max-time 5 -s ${ISTORE_INDEX} | gunzip | grep -FA10 "Package: ${ISTORE_PKG}" | grep -Fm1 'Version: ' | sed 's/^Version: //'`
|
||||||
|
local current=`grep -Fm1 'Version: ' /usr/lib/opkg/info/${ISTORE_PKG}.control | sed 's/^Version: //'`
|
||||||
|
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
||||||
|
echo "Check version failed!" >&2
|
||||||
|
exit 255
|
||||||
|
fi
|
||||||
|
if [ "$newest" != "$current" ]; then
|
||||||
|
echo "$newest"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
do_self_upgrade_0() {
|
||||||
|
opkg_wrap upgrade ${ISTORE_DEP_PKGS} && opkg_wrap upgrade ${ISTORE_PKG}
|
||||||
|
}
|
||||||
|
|
||||||
|
do_self_upgrade() {
|
||||||
|
check_mtime || return 1
|
||||||
|
local newest=`curl --connect-timeout 2 --max-time 5 -s ${ISTORE_INDEX} | gunzip | grep -FA10 "Package: ${ISTORE_PKG}" | grep -Fm1 'Version: ' | sed 's/^Version: //'`
|
||||||
|
local current=`grep -Fm1 'Version: ' /usr/lib/opkg/info/${ISTORE_PKG}.control | sed 's/^Version: //'`
|
||||||
|
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
||||||
|
echo "Check version failed!" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "$newest" = "$current" ]; then
|
||||||
|
echo "Already the latest version!" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if opkg_wrap info ${ISTORE_PKG} | grep -qFm1 "Version: $newest"; then
|
||||||
|
do_self_upgrade_0 && return 0
|
||||||
|
update_if_outdate || return 1
|
||||||
|
do_self_upgrade_0
|
||||||
|
else
|
||||||
|
update_if_outdate || return 1
|
||||||
|
do_self_upgrade_0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_mtime() {
|
||||||
|
find ${OPKG_CONF_DIR}/arch.conf -mtime -1 2>/dev/null | grep -q . || update
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped_in_update() {
|
||||||
|
check_mtime || return 1
|
||||||
|
eval "$@" && return 0
|
||||||
|
update_if_outdate || return 1
|
||||||
|
eval "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
step_upgrade() {
|
||||||
|
local pkg
|
||||||
|
local pkgs=""
|
||||||
|
local metapkg=""
|
||||||
|
for pkg in $@; do
|
||||||
|
if [[ $pkg == app-meta-* ]]; then
|
||||||
|
metapkg="$metapkg $pkg"
|
||||||
|
else
|
||||||
|
pkgs="$pkgs $pkg"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -n "$pkgs" ]; then
|
||||||
|
opkg_wrap_mirrors upgrade $pkgs || return 1
|
||||||
|
fi
|
||||||
|
if [ -n "$metapkg" ]; then
|
||||||
|
opkg_wrap_mirrors upgrade $metapkg || return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
new_upgrade() {
|
||||||
|
check_mtime || return 1
|
||||||
|
local metapkg=`echo "$@" | sed 's/ /\n/g' | grep -F app-meta-`
|
||||||
|
if [ -z "$metapkg" ] || opkg_wrap info $metapkg | grep -qF not-installed ; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
update_if_outdate
|
||||||
|
fi
|
||||||
|
wrapped_in_update step_upgrade "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
opkg_wrap --autoremove --force-removal-of-dependent-packages remove "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
autoconf_to_env() {
|
||||||
|
local autoconf path enable
|
||||||
|
eval "local autoconf=$ISTORE_AUTOCONF"
|
||||||
|
export -n ISTORE_AUTOCONF
|
||||||
|
export -n ISTORE_DONT_START
|
||||||
|
export -n ISTORE_CONF_DIR
|
||||||
|
export -n ISTORE_CACHE_DIR
|
||||||
|
export -n ISTORE_PUBLIC_DIR
|
||||||
|
export -n ISTORE_DL_DIR
|
||||||
|
|
||||||
|
ISTORE_AUTOCONF=$autoconf
|
||||||
|
|
||||||
|
if [ -n "$path" ]; then
|
||||||
|
export ISTORE_CONF_DIR="$path/Configs"
|
||||||
|
export ISTORE_CACHE_DIR="$path/Caches"
|
||||||
|
export ISTORE_PUBLIC_DIR="$path/Public"
|
||||||
|
export ISTORE_DL_DIR="$ISTORE_PUBLIC_DIR/Downloads"
|
||||||
|
fi
|
||||||
|
[ "$enable" = 0 ] && export ISTORE_DONT_START="1"
|
||||||
|
}
|
||||||
|
|
||||||
|
try_autoconf() {
|
||||||
|
[ -n "$ISTORE_AUTOCONF" ] || return 0
|
||||||
|
autoconf_to_env
|
||||||
|
[ -n "$ISTORE_AUTOCONF" ] || return 1
|
||||||
|
echo "Auto configure $ISTORE_AUTOCONF"
|
||||||
|
/usr/libexec/istorea/${ISTORE_AUTOCONF}.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
try_upgrade_depends() {
|
||||||
|
local pkg="$1"
|
||||||
|
if [[ $pkg == app-meta-* ]]; then
|
||||||
|
local deps=$(grep '^Depends: ' /usr/lib/opkg/info/$pkg.control | busybox sed -e 's/^Depends: //' -e 's/,/\n/g' -e 's/ //g' | grep -vFw libc | xargs echo)
|
||||||
|
[ -z "$deps" ] || opkg_wrap_mirrors install $deps
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "usage: is-opkg sub-command [arguments...]"
|
||||||
|
echo "where sub-command is one of:"
|
||||||
|
echo " update Update list of available packages"
|
||||||
|
echo " upgrade <pkgs> Upgrade package(s)"
|
||||||
|
echo " install <pkgs> Install package(s)"
|
||||||
|
echo " remove <pkgs|regexp> Remove package(s)"
|
||||||
|
echo " info [pkg|regexp] Display all info for <pkg>"
|
||||||
|
echo " list-upgradable List installed and upgradable packages"
|
||||||
|
echo " check_self_upgrade Check iStore upgrade"
|
||||||
|
echo " do_self_upgrade Upgrade iStore"
|
||||||
|
echo " arch Show libc architecture"
|
||||||
|
echo " opkg sys opkg wrap"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_init >/dev/null 2>&1
|
||||||
|
|
||||||
|
case $action in
|
||||||
|
"update")
|
||||||
|
update
|
||||||
|
;;
|
||||||
|
"install")
|
||||||
|
check_space
|
||||||
|
wrapped_in_update opkg_wrap_mirrors install "$@" && try_upgrade_depends "$1" && try_autoconf
|
||||||
|
;;
|
||||||
|
"autoconf")
|
||||||
|
try_autoconf
|
||||||
|
;;
|
||||||
|
"upgrade")
|
||||||
|
new_upgrade "$@"
|
||||||
|
;;
|
||||||
|
"remove")
|
||||||
|
remove "$@" || remove "$@"
|
||||||
|
;;
|
||||||
|
"info")
|
||||||
|
opkg_wrap info "$@"
|
||||||
|
;;
|
||||||
|
"list-upgradable")
|
||||||
|
opkg_wrap list-upgradable
|
||||||
|
;;
|
||||||
|
"check_self_upgrade")
|
||||||
|
check_self_upgrade
|
||||||
|
;;
|
||||||
|
"do_self_upgrade")
|
||||||
|
check_space
|
||||||
|
do_self_upgrade
|
||||||
|
;;
|
||||||
|
"arch")
|
||||||
|
echo "$ARCH"
|
||||||
|
;;
|
||||||
|
"opkg")
|
||||||
|
opkg_wrap "$@"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
7
istore/luci/luci-app-store/root/etc/config/istore
Normal file
7
istore/luci/luci-app-store/root/etc/config/istore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
config istore 'istore'
|
||||||
|
# option hide_docker '0'
|
||||||
|
# option ignore_arch '0'
|
||||||
|
# option last_path '/mnt/nvme'
|
||||||
|
# option channel 'istore'
|
||||||
|
# option super_arch 'x86_64'
|
||||||
|
# option super_arch 'aarch64'
|
54
istore/luci/luci-app-store/root/etc/init.d/istore
Executable file
54
istore/luci/luci-app-store/root/etc/init.d/istore
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# Copyright (C) 2016 OpenWrt.org
|
||||||
|
|
||||||
|
START=45
|
||||||
|
|
||||||
|
boot() {
|
||||||
|
local ARCH=`/bin/is-opkg arch`
|
||||||
|
generate_store_id $ARCH
|
||||||
|
generate_super_arch $ARCH
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_store_id() {
|
||||||
|
[ -s /etc/.app_store.id ] && return 0
|
||||||
|
local ARCH=$1
|
||||||
|
local iface HASH
|
||||||
|
|
||||||
|
for iface in eth0 br-lan; do
|
||||||
|
if [ -e /sys/class/net/$iface/address ]; then
|
||||||
|
HASH=`md5sum /sys/class/net/$iface/address | cut -d ' ' -f1`
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$HASH" ]; then
|
||||||
|
HASH=`dd if=/dev/urandom bs=512 count=1 2>/dev/null | md5sum | cut -d ' ' -f1`
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "{\"arch\":\"${ARCH}\", \"uid\":\"${HASH}\"}" > /etc/.app_store.id
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_super_arch() {
|
||||||
|
local ARCH=$1
|
||||||
|
local super_arch
|
||||||
|
case "$ARCH" in
|
||||||
|
arm*)
|
||||||
|
super_arch="arm"
|
||||||
|
;;
|
||||||
|
i386)
|
||||||
|
super_arch="x86"
|
||||||
|
;;
|
||||||
|
x86_64)
|
||||||
|
super_arch="x86_64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
super_arch="${ARCH%%_*}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
local old=`uci -q get istore.istore.super_arch`
|
||||||
|
[ "$old" = "$super_arch" ] && return 0
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
set istore.istore.super_arch=$super_arch
|
||||||
|
commit istore
|
||||||
|
EOF
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
/etc/init.d/istore boot
|
||||||
|
|
||||||
|
if [ -z "`uci -q get istore.istore.hide_docker`" ] && ! which docker >/dev/null; then
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
set istore.istore.hide_docker=1
|
||||||
|
commit istore
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
|
295
istore/luci/luci-app-store/root/usr/libexec/istore/backup
Executable file
295
istore/luci/luci-app-store/root/usr/libexec/istore/backup
Executable 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
|
114
istore/luci/luci-app-store/root/usr/libexec/istore/docker
Executable file
114
istore/luci/luci-app-store/root/usr/libexec/istore/docker
Executable file
@ -0,0 +1,114 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
handle_part() {
|
||||||
|
case "$MOUNT" in
|
||||||
|
"/overlay")
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_dir() {
|
||||||
|
local data_root=$(uci -q get dockerd.globals.data_root)
|
||||||
|
[ -n "$data_root" ] || return 0
|
||||||
|
local block_dev=$(findmnt -T "$data_root" -v -o SOURCE | sed -n 2p)
|
||||||
|
[ -n "$block_dev" ] || return 0
|
||||||
|
[ "overlayfs:/overlay" = "$block_dev" ] && return 1
|
||||||
|
local line=$(block info "$block_dev" 2>/dev/null)
|
||||||
|
[ -n "$line" ] || return 0
|
||||||
|
eval "${line##*: } handle_part ${line%%: *}"
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_check(){
|
||||||
|
local dest="$1"
|
||||||
|
[ -n "$dest" ] || {
|
||||||
|
echo "dest dir not specified!" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
local data_root="$2"
|
||||||
|
[ -n "$data_root" ] || data_root=$(uci -q get dockerd.globals.data_root)
|
||||||
|
[ -n "$data_root" ] || {
|
||||||
|
echo "get docker data_root failed!" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$data_root" = "/" ] || data_root="${data_root%%/}"
|
||||||
|
[ "$dest" = "/" ] || dest="${dest%%/}"
|
||||||
|
|
||||||
|
[ "$data_root" = "$dest" ] && {
|
||||||
|
echo "dest dir is the same as data_root!" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ 1 = "$FORCE" ] && return 0
|
||||||
|
[ -e "$dest" ] || return 0
|
||||||
|
[ -d "$dest" ] || {
|
||||||
|
echo "$dest is existed and it's not a dir, use FORCE=1 to overwrite it" >&2
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
[ 0 = "$(ls -A "$dest" | head -1 | wc -l)" ] || {
|
||||||
|
echo "$dest is existed and it's not empty, use FORCE=1 to overwrite it" >&2
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate() {
|
||||||
|
local dest="$1"
|
||||||
|
local data_root=$(uci -q get dockerd.globals.data_root)
|
||||||
|
[ -n "$data_root" ] || {
|
||||||
|
echo "get docker data_root failed!" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
[ "$data_root" = "/" ] || data_root="${data_root%%/}"
|
||||||
|
[ "$dest" = "/" ] || dest="${dest%%/}"
|
||||||
|
|
||||||
|
FORCE=1 migrate_check "$dest" "$data_root"
|
||||||
|
local check_result=$?
|
||||||
|
[ 0 = $check_result ] || return $check_result
|
||||||
|
|
||||||
|
if [ "$UCI_ONLY" != 1 ]; then
|
||||||
|
rm -rf "$dest"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
echo "Copy $data_root to $dest ..."
|
||||||
|
cp -a "$data_root/." "$dest/" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Change dockerd data_root to $dest and restart"
|
||||||
|
uci set dockerd.globals.data_root="$dest"
|
||||||
|
uci commit dockerd
|
||||||
|
/etc/init.d/dockerd restart
|
||||||
|
|
||||||
|
echo "Done"
|
||||||
|
}
|
||||||
|
|
||||||
|
action=${1}
|
||||||
|
shift
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "usage: $1 sub-command [arguments...]"
|
||||||
|
echo "where sub-command is one of:"
|
||||||
|
echo " check_dir Check docker data_root is on extrnal disk"
|
||||||
|
echo " migrate_check {target_dir} Check target_dir is valid for migration, return 2 if target_dir existed and not empty"
|
||||||
|
echo " migrate {target_dir} Migrate docker data_root to target_dir"
|
||||||
|
echo " change_dir {target_dir} Migrate docker data_root to target_dir but change dir only (no data copy)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case $action in
|
||||||
|
"check_dir")
|
||||||
|
check_dir
|
||||||
|
;;
|
||||||
|
"migrate")
|
||||||
|
migrate "$@"
|
||||||
|
;;
|
||||||
|
"migrate_check")
|
||||||
|
migrate_check "$@"
|
||||||
|
;;
|
||||||
|
"change_dir")
|
||||||
|
UCI_ONLY=1 migrate "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage "$0"
|
||||||
|
;;
|
||||||
|
esac
|
200
istore/luci/luci-app-store/root/usr/libexec/istore/ipkg-build
Executable file
200
istore/luci/luci-app-store/root/usr/libexec/istore/ipkg-build
Executable file
@ -0,0 +1,200 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# ipkg-build -- construct a .ipk from a directory
|
||||||
|
# Carl Worth <cworth@east.isi.edu>
|
||||||
|
# based on a script by Steve Redler IV, steve@sr-tech.com 5-21-2001
|
||||||
|
# 2003-04-25 rea@sr.unh.edu
|
||||||
|
# Updated to work on Familiar Pre0.7rc1, with busybox tar.
|
||||||
|
# Note it Requires: binutils-ar (since the busybox ar can't create)
|
||||||
|
# For UID debugging it needs a better "find".
|
||||||
|
set -e
|
||||||
|
|
||||||
|
version=1.0
|
||||||
|
FIND="$(command -v find)"
|
||||||
|
FIND="${FIND:-$(command -v gfind)}"
|
||||||
|
TAR="${TAR:-$(command -v tar)}"
|
||||||
|
GZIP="$(command -v gzip)"
|
||||||
|
|
||||||
|
# try to use fixed source epoch
|
||||||
|
if [ -n "$PKG_SOURCE_DATE_EPOCH" ]; then
|
||||||
|
TIMESTAMP=$(date --date="@$PKG_SOURCE_DATE_EPOCH")
|
||||||
|
elif [ -n "$SOURCE_DATE_EPOCH" ]; then
|
||||||
|
TIMESTAMP=$(date --date="@$SOURCE_DATE_EPOCH")
|
||||||
|
else
|
||||||
|
TIMESTAMP=$(date)
|
||||||
|
fi
|
||||||
|
|
||||||
|
ipkg_extract_value() {
|
||||||
|
sed -e "s/^[^:]*:[[:space:]]*//"
|
||||||
|
}
|
||||||
|
|
||||||
|
required_field() {
|
||||||
|
field=$1
|
||||||
|
|
||||||
|
grep "^$field:" < $CONTROL/control | ipkg_extract_value
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg_appears_sane() {
|
||||||
|
local pkg_dir=$1
|
||||||
|
|
||||||
|
local owd=$PWD
|
||||||
|
cd $pkg_dir
|
||||||
|
|
||||||
|
PKG_ERROR=0
|
||||||
|
pkg=`required_field Package`
|
||||||
|
version=`required_field Version | sed 's/Version://; s/^.://g;'`
|
||||||
|
arch=`required_field Architecture`
|
||||||
|
|
||||||
|
if echo $pkg | grep '[^a-zA-Z0-9_.+-]'; then
|
||||||
|
echo "*** Error: Package name $name contains illegal characters, (other than [a-z0-9.+-])" >&2
|
||||||
|
PKG_ERROR=1;
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f $CONTROL/conffiles ]; then
|
||||||
|
rm -f $CONTROL/conffiles.resolved
|
||||||
|
|
||||||
|
for cf in `$FIND $(sed -e "s!^/!$pkg_dir/!" $CONTROL/conffiles) -type f`; do
|
||||||
|
echo "${cf#$pkg_dir}" >> $CONTROL/conffiles.resolved
|
||||||
|
done
|
||||||
|
|
||||||
|
rm $CONTROL/conffiles
|
||||||
|
if [ -f $CONTROL/conffiles.resolved ]; then
|
||||||
|
mv $CONTROL/conffiles.resolved $CONTROL/conffiles
|
||||||
|
chmod 0644 $CONTROL/conffiles
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $owd
|
||||||
|
return $PKG_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_file_mode_id() {
|
||||||
|
local var=$1 type=$2 name=$3 id
|
||||||
|
|
||||||
|
case "$name" in
|
||||||
|
root)
|
||||||
|
id=0
|
||||||
|
;;
|
||||||
|
*[!0-9]*)
|
||||||
|
id=$(sed -ne "s#^$type $name \\([0-9]\\+\\)\\b.*\$#\\1#p" "$TOPDIR/tmp/.packageusergroup" 2>/dev/null)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
id=$name
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
export "$var=$id"
|
||||||
|
|
||||||
|
[ -n "$id" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# ipkg-build "main"
|
||||||
|
###
|
||||||
|
file_modes=""
|
||||||
|
usage="Usage: $0 [-v] [-h] [-m] <pkg_directory> [<destination_directory>]"
|
||||||
|
while getopts "hvm:" opt; do
|
||||||
|
case $opt in
|
||||||
|
v ) echo $version
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
h ) echo $usage >&2 ;;
|
||||||
|
m ) file_modes=$OPTARG ;;
|
||||||
|
\? ) echo $usage >&2
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
shift $(($OPTIND - 1))
|
||||||
|
|
||||||
|
# continue on to process additional arguments
|
||||||
|
|
||||||
|
case $# in
|
||||||
|
1)
|
||||||
|
dest_dir=$PWD
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
dest_dir=$2
|
||||||
|
if [ "$dest_dir" = "." -o "$dest_dir" = "./" ] ; then
|
||||||
|
dest_dir=$PWD
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo $usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
pkg_dir=$1
|
||||||
|
|
||||||
|
if [ ! -d $pkg_dir ]; then
|
||||||
|
echo "*** Error: Directory $pkg_dir does not exist" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CONTROL is second so that it takes precedence
|
||||||
|
CONTROL=
|
||||||
|
[ -d $pkg_dir/CONTROL ] && CONTROL=CONTROL
|
||||||
|
if [ -z "$CONTROL" ]; then
|
||||||
|
echo "*** Error: Directory $pkg_dir has no CONTROL subdirectory." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pkg_appears_sane $pkg_dir; then
|
||||||
|
echo >&2
|
||||||
|
echo "ipkg-build: Please fix the above errors and try again." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_dir=$dest_dir/IPKG_BUILD.$$
|
||||||
|
mkdir $tmp_dir
|
||||||
|
|
||||||
|
echo $CONTROL > $tmp_dir/tarX
|
||||||
|
cd $pkg_dir
|
||||||
|
for file_mode in $file_modes; do
|
||||||
|
case $file_mode in
|
||||||
|
/*:*:*:*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: file modes must use absolute path and contain user:group:mode"
|
||||||
|
echo "$file_mode"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mode=${file_mode##*:}; path=${file_mode%:*}
|
||||||
|
group=${path##*:}; path=${path%:*}
|
||||||
|
user=${path##*:}; path=${path%:*}
|
||||||
|
|
||||||
|
if ! resolve_file_mode_id uid user "$user"; then
|
||||||
|
echo "ERROR: unable to resolve uid of $user" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! resolve_file_mode_id gid group "$group"; then
|
||||||
|
echo "ERROR: unable to resolve gid of $group" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chown "$uid:$gid" "$pkg_dir/$path"
|
||||||
|
chmod "$mode" "$pkg_dir/$path"
|
||||||
|
done
|
||||||
|
$TAR -X $tmp_dir/tarX --format=gnu --sort=name -cpf - --mtime="$TIMESTAMP" . | $GZIP -n - > $tmp_dir/data.tar.gz
|
||||||
|
|
||||||
|
installed_size=`ls -ln $tmp_dir/data.tar.gz | awk 'NR==1 {print $5}'`
|
||||||
|
sed -i -e "s/^Installed-Size: .*/Installed-Size: $installed_size/" \
|
||||||
|
$pkg_dir/$CONTROL/control
|
||||||
|
|
||||||
|
( cd $pkg_dir/$CONTROL && $TAR --format=gnu --sort=name -cf - --mtime="$TIMESTAMP" . | $GZIP -n - > $tmp_dir/control.tar.gz )
|
||||||
|
rm $tmp_dir/tarX
|
||||||
|
|
||||||
|
echo "2.0" > $tmp_dir/debian-binary
|
||||||
|
|
||||||
|
pkg_file=$dest_dir/${pkg}_${version}_${arch}.ipk
|
||||||
|
rm -f $pkg_file
|
||||||
|
( cd $tmp_dir && $TAR --format=gnu --sort=name -cf - --mtime="$TIMESTAMP" ./debian-binary ./data.tar.gz ./control.tar.gz | $GZIP -n - > $pkg_file )
|
||||||
|
|
||||||
|
rm $tmp_dir/debian-binary $tmp_dir/data.tar.gz $tmp_dir/control.tar.gz
|
||||||
|
rmdir $tmp_dir
|
||||||
|
|
||||||
|
echo "Packaged contents of $pkg_dir into $pkg_file"
|
7
istore/luci/luci-app-store/root/usr/share/opkg/intercept/rm
Executable file
7
istore/luci/luci-app-store/root/usr/share/opkg/intercept/rm
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
for i in "$@"; do
|
||||||
|
echo "$i" | grep -s -q "/etc/uci-defaults/" \
|
||||||
|
&& mkdir -p /etc/istore/uci-defaults_bak \
|
||||||
|
&& cp -f "$i" /etc/istore/uci-defaults_bak/
|
||||||
|
done
|
||||||
|
/bin/rm "$@"
|
23
istore/luci/luci-app-store/src/Makefile
Normal file
23
istore/luci/luci-app-store/src/Makefile
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
clean:
|
||||||
|
compile:
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_NAME:=luci-lib-dummy
|
||||||
|
INCLUDE_DIR:=./dummy
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
install:
|
||||||
|
[ -n "$(APP_STORE_VERSION)" ]
|
||||||
|
mkdir -p "$(DESTDIR)/www/luci-static"
|
||||||
|
cp -a "$(FRONTEND_DIST)/luci-static/istore" "$(DESTDIR)/www/luci-static/"
|
||||||
|
$(SED) 's#\.js"#.js?v=$(APP_STORE_VERSION)"#g' "$(DESTDIR)/www/luci-static/istore/index.js"
|
||||||
|
mkdir -p "$(DESTDIR)/etc/opkg/keys"
|
||||||
|
echo "$(APP_STORE_VERSION)" > "$(DESTDIR)/etc/.app_store.version"
|
||||||
|
cp -a ./compat.conf "$(DESTDIR)/etc/opkg/compatfeeds.conf"
|
||||||
|
cp -a ./key-build.pub $(DESTDIR)/etc/opkg/keys/`$(STAGING_DIR_HOST)/bin/usign -F -p ./key-build.pub`
|
||||||
|
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;))
|
1
istore/luci/luci-app-store/src/compat.conf
Normal file
1
istore/luci/luci-app-store/src/compat.conf
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/gz istore_compat https://istore.istoreos.com/repo/all/compat
|
2
istore/luci/luci-app-store/src/dummy/package.mk
Normal file
2
istore/luci/luci-app-store/src/dummy/package.mk
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
define BuildPackage
|
||||||
|
endef
|
2
istore/luci/luci-app-store/src/key-build.pub
Normal file
2
istore/luci/luci-app-store/src/key-build.pub
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
untrusted comment: istore key
|
||||||
|
RWSlbxYnTG1Ia0BvB+xd7YdP7QDQACljfpve7sx9KYq94QgIqtlljuME
|
11
istore/luci/luci-app-store/src/po/templates/iStore.pot
Normal file
11
istore/luci/luci-app-store/src/po/templates/iStore.pot
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
|
msgid "istore_vue_lang"
|
||||||
|
msgstr "en"
|
||||||
|
|
||||||
|
msgid "iStore"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This package is broken! Please reinstall or uninstall it."
|
||||||
|
msgstr ""
|
11
istore/luci/luci-app-store/src/po/zh-cn/iStore.po
Normal file
11
istore/luci/luci-app-store/src/po/zh-cn/iStore.po
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
|
msgid "istore_vue_lang"
|
||||||
|
msgstr "zh-cn"
|
||||||
|
|
||||||
|
msgid "iStore"
|
||||||
|
msgstr "iStore"
|
||||||
|
|
||||||
|
msgid "This package is broken! Please reinstall or uninstall it."
|
||||||
|
msgstr "此软件包已损坏!请重新安装或卸载它。"
|
11
istore/luci/luci-app-store/src/po/zh-tw/iStore.po
Normal file
11
istore/luci/luci-app-store/src/po/zh-tw/iStore.po
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
|
msgid "istore_vue_lang"
|
||||||
|
msgstr "zh-cn"
|
||||||
|
|
||||||
|
msgid "iStore"
|
||||||
|
msgstr "iStore"
|
||||||
|
|
||||||
|
msgid "This package is broken! Please reinstall or uninstall it."
|
||||||
|
msgstr "此軟體包已損壞!請重新安裝或卸載它。"
|
513
istore/luci/luci-app-store/swagger.yaml
Normal file
513
istore/luci/luci-app-store/swagger.yaml
Normal file
@ -0,0 +1,513 @@
|
|||||||
|
swagger: "2.0"
|
||||||
|
info:
|
||||||
|
description: Store API V1
|
||||||
|
title: Store API V1
|
||||||
|
version: 3.0.0
|
||||||
|
paths:
|
||||||
|
/cgi-bin/luci/admin/store/token:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- token
|
||||||
|
summary: 获取csrfToken(用于POST请求)
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStoreToken"
|
||||||
|
/cgi-bin/luci/admin/store/installed:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- installed
|
||||||
|
summary: 获取已安装的插件
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStoreInstalled"
|
||||||
|
/cgi-bin/luci/admin/store/install:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- install
|
||||||
|
summary: 安装插件。安装过程中使用taskd接口获取日志
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "package"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "autoconf"
|
||||||
|
type: string
|
||||||
|
description: "可选参数,表示安装完以后是否自动配置插件"
|
||||||
|
- in: "query"
|
||||||
|
name: "path"
|
||||||
|
type: string
|
||||||
|
description: "可选参数,表示安装完以后是否自动配置插件"
|
||||||
|
- in: "query"
|
||||||
|
name: "enable"
|
||||||
|
type: string
|
||||||
|
description: "可选参数,表示安装完以后是否自动配置插件"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/autoconf:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- install
|
||||||
|
summary: 自动配置插件
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "package"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "path"
|
||||||
|
type: string
|
||||||
|
description: "可选参数"
|
||||||
|
- in: "query"
|
||||||
|
name: "enable"
|
||||||
|
type: string
|
||||||
|
description: "可选参数"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/remove:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- remove
|
||||||
|
summary: 卸载插件
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "package"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/upgrade:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- upgrade
|
||||||
|
summary: 更新插件
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "package"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/upload:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- upload
|
||||||
|
summary: 上传插件
|
||||||
|
description: "(文件上传表单,支持文件扩展名 .ipk,.run )"
|
||||||
|
consumes:
|
||||||
|
- multipart/form-data
|
||||||
|
parameters:
|
||||||
|
- in: formData
|
||||||
|
name: file
|
||||||
|
type: file
|
||||||
|
description: The file to upload.
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/update:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- update
|
||||||
|
summary: 刷新可用软件列表
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/status:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- status
|
||||||
|
summary: 查询特定软件状态
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "package"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStoreStatus"
|
||||||
|
/cgi-bin/luci/admin/store/log:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- log
|
||||||
|
summary: 任务日志,已废弃
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/check_self_upgrade:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- check_self_upgrade
|
||||||
|
summary: 检查iStore自身更新
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStoreCheckSelfUpgrade"
|
||||||
|
/cgi-bin/luci/admin/store/do_self_upgrade:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- do_self_upgrade
|
||||||
|
summary: 更新iStore自身
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
/cgi-bin/luci/admin/store/get_block_devices:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- get_block_devices
|
||||||
|
summary: 枚举块设备
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStoreGetBlockDevices"
|
||||||
|
/cgi-bin/luci/admin/store/configured:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- configured
|
||||||
|
summary: 检查插件是否已经配置过
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "uci"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: "要检查的uci配置文件名称"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStoreConfigured"
|
||||||
|
/cgi-bin/luci/admin/store/entrysh:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- entrysh
|
||||||
|
summary: 查询插件运行状态,主要为了获取入口信息。
|
||||||
|
description: 调用前应该检查meta数据中flags数组包含entrysh,注意如果是安装插件则检查服务器端的flags,如果是调用已安装插件则检查路由器端的flags。
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "package"
|
||||||
|
type: string
|
||||||
|
description: "包名,例如aria2。支持英文逗号分隔多个包名。如果未提供,则返回所有支持entrysh的包"
|
||||||
|
- in: "query"
|
||||||
|
name: "hostname"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: "主机名,不包含端口。前端应该使用location.hostname获取"
|
||||||
|
- in: "query"
|
||||||
|
name: "update"
|
||||||
|
type: integer
|
||||||
|
description: "如果为1则强制更新缓存"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStoreEntrysh"
|
||||||
|
"400":
|
||||||
|
description: Bad Request。hostname不合法
|
||||||
|
/cgi-bin/luci/admin/store/docker_check_dir:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- docker_check_dir
|
||||||
|
summary: 检查docker目录是否在系统盘
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseDockerCheckDir"
|
||||||
|
/cgi-bin/luci/admin/store/docker_check_migrate:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- docker_check_migrate
|
||||||
|
summary: 检查docker迁移目标目录是否有效
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "path"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: "完整目标路径"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseDockerCheckMigrate"
|
||||||
|
/cgi-bin/luci/admin/store/docker_migrate:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- docker_migrate
|
||||||
|
summary: docker迁移到目标目录,异步
|
||||||
|
description: 当返回code为0的时候,可以使用taskd接口展示日志,跟安装插件时一样
|
||||||
|
parameters:
|
||||||
|
- in: "query"
|
||||||
|
name: "token"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: "query"
|
||||||
|
name: "path"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: "完整目标路径"
|
||||||
|
- in: "query"
|
||||||
|
name: "overwrite"
|
||||||
|
type: string
|
||||||
|
description: "如果docker_check_migrate返回的result是existed,使用此参数指定解决方案:‘true’表示覆盖目标目录;‘chdir’表示只修改路径不复制文件"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ResponseStore"
|
||||||
|
|
||||||
|
definitions:
|
||||||
|
ResponseStoreToken:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
StoreInstalledApp:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
tutorial:
|
||||||
|
type: string
|
||||||
|
description: "教程"
|
||||||
|
arch:
|
||||||
|
type: array
|
||||||
|
description: "适用的架构平台"
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
entry:
|
||||||
|
type: string
|
||||||
|
description: "对于的页面地址"
|
||||||
|
author:
|
||||||
|
type: string
|
||||||
|
description: "作者"
|
||||||
|
depends:
|
||||||
|
type: array
|
||||||
|
description: "关联的依赖"
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
time:
|
||||||
|
type: integer
|
||||||
|
description: "更新日期"
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
description: "标签"
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description_en:
|
||||||
|
type: string
|
||||||
|
description: "简介,英文"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: "简介,英文"
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
description: "标题"
|
||||||
|
release:
|
||||||
|
type: integer
|
||||||
|
description: "发布"
|
||||||
|
website:
|
||||||
|
type: string
|
||||||
|
description: "官网"
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
description: "版本号"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: "名称"
|
||||||
|
ResponseStoreInstalled:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/StoreInstalledApp"
|
||||||
|
ResponseStore:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
stdout:
|
||||||
|
type: string
|
||||||
|
stderr:
|
||||||
|
type: string
|
||||||
|
ResponseStoreStatus:
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/definitions/StoreInstalledApp"
|
||||||
|
properties:
|
||||||
|
installed:
|
||||||
|
type: boolean
|
||||||
|
ResponseStoreCheckSelfUpgrade:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "为200时,msg会显示版本号"
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
description: "内容"
|
||||||
|
GetBlockDevice:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
dev:
|
||||||
|
type: string
|
||||||
|
uuid:
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
label:
|
||||||
|
type: string
|
||||||
|
mount:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
size:
|
||||||
|
type: string
|
||||||
|
ResponseStoreGetBlockDevices:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "为200时"
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
description: "内容"
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: "#/definitions/GetBlockDevice"
|
||||||
|
ResponseStoreConfigured:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "为200时"
|
||||||
|
configured:
|
||||||
|
type: boolean
|
||||||
|
ResponseDockerCheckDir:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "为200时"
|
||||||
|
docker_on_system:
|
||||||
|
type: boolean
|
||||||
|
description: "docker数据在系统盘"
|
||||||
|
ResponseDockerCheckMigrate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "为200时"
|
||||||
|
result:
|
||||||
|
type: string
|
||||||
|
description: "检查结果。可能值‘good’,‘bad’,‘existed’"
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
description: "当result为bad时,此处返回错误日志"
|
||||||
|
StoreEntrysh:
|
||||||
|
type: object
|
||||||
|
description: "状态和入口信息,不同插件可能有些不一样,仅列出常用公共参数"
|
||||||
|
properties:
|
||||||
|
app:
|
||||||
|
type: string
|
||||||
|
description: "插件名称"
|
||||||
|
docker:
|
||||||
|
type: boolean
|
||||||
|
description: "如果是docker插件"
|
||||||
|
running:
|
||||||
|
type: boolean
|
||||||
|
description: "是否运行中"
|
||||||
|
deployed:
|
||||||
|
type: boolean
|
||||||
|
description: "如果是docker插件且未运行,则是否已经部署"
|
||||||
|
web:
|
||||||
|
type: string
|
||||||
|
description: "url"
|
||||||
|
href:
|
||||||
|
type: string
|
||||||
|
description: "web端直接跳转url"
|
||||||
|
|
||||||
|
ResponseStoreEntrysh:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "为200时"
|
||||||
|
status:
|
||||||
|
description: "处理成功的结果集"
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/StoreEntrysh"
|
||||||
|
errors:
|
||||||
|
description: "处理失败的结果集"
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
description: "失败信息"
|
||||||
|
properties:
|
||||||
|
app:
|
||||||
|
type: string
|
||||||
|
description: "插件名称"
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "404或500"
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
description: "错误信息"
|
||||||
|
|
20
istore/luci/luci-lib-taskd/Makefile
Normal file
20
istore/luci/luci-lib-taskd/Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#
|
||||||
|
# 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_EXTRA_DEPENDS:=taskd (>=1.0.3-1)
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
|
PKG_VERSION:=1.0.22
|
||||||
|
PKG_RELEASE:=
|
||||||
|
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
@ -0,0 +1,151 @@
|
|||||||
|
[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-width: 100%;
|
||||||
|
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 {
|
||||||
|
position: relative;
|
||||||
|
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, .tasks_unknown #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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_unknown #tasks_dialog {
|
||||||
|
border-color: darkorange;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasks_result_mask {
|
||||||
|
display: none;
|
||||||
|
background-color: #000d;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_stopped #tasks_result_mask {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_result_success, .tasks_stopped.tasks_failed .tasks_result_success, .tasks_result_failed {
|
||||||
|
display: none;
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_stopped .tasks_result_success {
|
||||||
|
display: initial;
|
||||||
|
color: #27c840;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_failed .tasks_result_failed {
|
||||||
|
display: initial;
|
||||||
|
color: darkorange;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes border-blink { 50% { border-color:#fff ; } }
|
@ -0,0 +1,241 @@
|
|||||||
|
|
||||||
|
(function(){
|
||||||
|
const taskd={};
|
||||||
|
const $gettext = function(str) {
|
||||||
|
return taskd.i18n[str] || str;
|
||||||
|
};
|
||||||
|
const retryPromise = function(fn) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const retry = function() {
|
||||||
|
fn(resolve, reject, retry);
|
||||||
|
};
|
||||||
|
retry();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const retry403XHR = function(url, method, responseType) {
|
||||||
|
return retryPromise((resolve, reject, retry) => {
|
||||||
|
var oReq = new XMLHttpRequest();
|
||||||
|
oReq.onerror = reject;
|
||||||
|
oReq.open(method || 'GET', url, true);
|
||||||
|
if (responseType) {
|
||||||
|
oReq.responseType = responseType;
|
||||||
|
}
|
||||||
|
oReq.onload = function (oEvent) {
|
||||||
|
if (oReq.status == 403) {
|
||||||
|
alert($gettext("Lost login status"));
|
||||||
|
location.href = location.href;
|
||||||
|
} else if (oReq.status >= 400) {
|
||||||
|
reject(oEvent);
|
||||||
|
} else {
|
||||||
|
resolve(oReq);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (method=='POST') {
|
||||||
|
oReq.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||||
|
}
|
||||||
|
oReq.send(method=='POST'?("token="+taskd.csrfToken):null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const request = function(url, method) {
|
||||||
|
return retry403XHR(url, method).then(oReq => oReq.responseText);
|
||||||
|
};
|
||||||
|
const getBin = function(url) {
|
||||||
|
return retry403XHR(url, null, "arraybuffer").then(oReq => {return {status: oReq.status, buffer: new Uint8Array(oReq.response)}});
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const tasks_result_mask = container.querySelector("#tasks_result_mask");
|
||||||
|
if (taskd.show_mask_on_stopped) {
|
||||||
|
tasks_result_mask.onclick = function(){
|
||||||
|
tasks_result_mask.hidden=true;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
tasks_result_mask.hidden=true;
|
||||||
|
}
|
||||||
|
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, onExit) {
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
onExit && onExit(data.exit_code);
|
||||||
|
}
|
||||||
|
// last pull
|
||||||
|
return showing;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let logoffset = 0;
|
||||||
|
const pulllog = function(check) {
|
||||||
|
let starter = Promise.resolve(showing);
|
||||||
|
if (check) {
|
||||||
|
starter = checkTask();
|
||||||
|
}
|
||||||
|
starter.then(again => {
|
||||||
|
if (again)
|
||||||
|
return getBin("/cgi-bin/luci/admin/system/tasks/log?task_id="+task_id+"&offset="+logoffset);
|
||||||
|
else
|
||||||
|
return {status: 204};
|
||||||
|
}).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);
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
if (showing) {
|
||||||
|
if (err.target.status == 0 || err.target.status == 502) {
|
||||||
|
title_view.innerText = task_id + ' (' + $gettext("Fetch log failed, retrying...") + ')';
|
||||||
|
setTimeout(()=>pulllog(true), 1000);
|
||||||
|
} else if (err.target.status == 403 || err.target.status == 404) {
|
||||||
|
title_view.innerText = task_id + ' (' + $gettext(err.target.status == 403?"Lost login status":"Task does not exist or has been deleted") + ')';
|
||||||
|
container.querySelector(".dialog-icon-close").hidden = true;
|
||||||
|
container.classList.add('tasks_unknown');
|
||||||
|
} else {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
pulllog(true);
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
98
istore/luci/luci-lib-taskd/luasrc/controller/tasks-lib.lua
Executable file
98
istore/luci/luci-lib-taskd/luasrc/controller/tasks-lib.lua
Executable file
@ -0,0 +1,98 @@
|
|||||||
|
|
||||||
|
module("luci.controller.tasks-lib", package.seeall)
|
||||||
|
|
||||||
|
|
||||||
|
function index()
|
||||||
|
entry({"admin", "system", "tasks"}, call("tasks_ping")).dependent=false -- just for compatible
|
||||||
|
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_ping()
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({})
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
100
istore/luci/luci-lib-taskd/luasrc/model/tasks.lua
Normal file
100
istore/luci/luci-lib-taskd/luasrc/model/tasks.lua
Normal 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=404}
|
||||||
|
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
|
56
istore/luci/luci-lib-taskd/luasrc/view/tasks/docker.htm
Normal file
56
istore/luci/luci-lib-taskd/luasrc/view/tasks/docker.htm
Normal 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%>…" 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
|
||||||
|
%>
|
40
istore/luci/luci-lib-taskd/luasrc/view/tasks/embed.htm
Normal file
40
istore/luci/luci-lib-taskd/luasrc/view/tasks/embed.htm
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<%+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:")
|
||||||
|
tr("Lost login status")
|
||||||
|
tr("Fetch log failed, retrying...")
|
||||||
|
tr("Task does not exist or has been deleted")
|
||||||
|
-%>
|
||||||
|
<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 id="tasks_result_mask">
|
||||||
|
<span class="tasks_result_success"><%:Task execution successful !%></span>
|
||||||
|
<span class="tasks_result_failed"><%:Task execution failed !%></span>
|
||||||
|
<br/>
|
||||||
|
<span><%:Click the button in the upper right corner of the dialog box to close it, or click here to view the log%></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
</script>
|
15
istore/luci/luci-lib-taskd/src/Makefile
Normal file
15
istore/luci/luci-lib-taskd/src/Makefile
Normal 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;))
|
2
istore/luci/luci-lib-taskd/src/dummy/package.mk
Normal file
2
istore/luci/luci-lib-taskd/src/dummy/package.mk
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
define BuildPackage
|
||||||
|
endef
|
50
istore/luci/luci-lib-taskd/src/po/zh-cn/lib-tasks.po
Normal file
50
istore/luci/luci-lib-taskd/src/po/zh-cn/lib-tasks.po
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
|
msgid "Stop running task?"
|
||||||
|
msgstr "删除运行中的任务?"
|
||||||
|
|
||||||
|
msgid "Finished at:"
|
||||||
|
msgstr "完成于:"
|
||||||
|
|
||||||
|
msgid "Failed at:"
|
||||||
|
msgstr "失败于:"
|
||||||
|
|
||||||
|
msgid "Lost login status"
|
||||||
|
msgstr "丢失登陆状态"
|
||||||
|
|
||||||
|
msgid "Fetch log failed, retrying..."
|
||||||
|
msgstr "拉取日志失败,正在重试..."
|
||||||
|
|
||||||
|
msgid "Task does not exist or has been deleted"
|
||||||
|
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 "任务执行中"
|
||||||
|
|
||||||
|
msgid "Task execution successful !"
|
||||||
|
msgstr "任务执行成功!"
|
||||||
|
|
||||||
|
msgid "Task execution failed !"
|
||||||
|
msgstr "任务执行失败!"
|
||||||
|
|
||||||
|
msgid "Click the button in the upper right corner of the dialog box to close it, or click here to view the log"
|
||||||
|
msgstr "点击对话框右上角按钮关闭,或者点此查看日志"
|
21
istore/luci/luci-lib-xterm/Makefile
Normal file
21
istore/luci/luci-lib-xterm/Makefile
Normal 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
|
@ -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
3
istore/luci/luci-lib-xterm/luasrc/view/xterm/embed.htm
Normal file
3
istore/luci/luci-lib-xterm/luasrc/view/xterm/embed.htm
Normal 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
istore/luci/taskd/Makefile
Normal file
41
istore/luci/taskd/Makefile
Normal 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:=2
|
||||||
|
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 +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
istore/luci/taskd/files/taskd.sh
Executable file
16
istore/luci/taskd/files/taskd.sh
Executable 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
istore/luci/taskd/files/tasks.init
Executable file
157
istore/luci/taskd/files/tasks.init
Executable 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"
|
||||||
|
}
|
BIN
istore/preview/istore-preview.png
Normal file
BIN
istore/preview/istore-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
414
istore/translations/en/app.po
Normal file
414
istore/translations/en/app.po
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: istore 0.1\n"
|
||||||
|
"Last-Translator: jjm2473\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: en\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:25
|
||||||
|
msgid "%{ num } download"
|
||||||
|
msgid_plural "%{ num } downloads"
|
||||||
|
msgstr[0] "%{ num } download"
|
||||||
|
msgstr[1] "%{ num } downloads"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:32
|
||||||
|
msgid "%{ num } like"
|
||||||
|
msgid_plural "%{ num } likes"
|
||||||
|
msgstr[0] "%{ num } like"
|
||||||
|
msgstr[1] "%{ num } likes"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:27
|
||||||
|
msgid "all app"
|
||||||
|
msgstr "all app"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:12
|
||||||
|
msgid "app backup"
|
||||||
|
msgstr "app backup"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:168
|
||||||
|
msgid "are you sure you want to uninstall %{name}?"
|
||||||
|
msgstr "are you sure you want to uninstall %{name}?"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:51
|
||||||
|
msgid "author"
|
||||||
|
msgstr "author"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:41
|
||||||
|
msgid "backup fail"
|
||||||
|
msgstr "backup fail"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:198
|
||||||
|
#: src/pages/maintance/localBackup.vue:203
|
||||||
|
msgid "backup fail with error code"
|
||||||
|
msgstr "backup fail with error code"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:18
|
||||||
|
#: src/pages/maintance/localBackup.vue:34
|
||||||
|
msgid "backup now"
|
||||||
|
msgstr "backup now"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:61
|
||||||
|
msgid ""
|
||||||
|
"backup now backs up installed software (unlimited installation sources) to "
|
||||||
|
"external storage"
|
||||||
|
msgstr ""
|
||||||
|
"backup now backs up installed software (unlimited installation sources) to "
|
||||||
|
"external storage"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:9
|
||||||
|
msgid "backup now will download a list of iStore installed software"
|
||||||
|
msgstr "backup now will download a list of iStore installed software"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:40
|
||||||
|
msgid "backup success"
|
||||||
|
msgstr "backup success"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:39
|
||||||
|
msgid "backuping"
|
||||||
|
msgstr "backuping"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:45
|
||||||
|
msgid "Broken"
|
||||||
|
msgstr "Broken"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:24
|
||||||
|
msgid "By download"
|
||||||
|
msgstr "By download"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:28
|
||||||
|
msgid "By rating"
|
||||||
|
msgstr "By rating"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:4
|
||||||
|
msgid "checking for latest version"
|
||||||
|
msgstr "checking for latest version"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:22
|
||||||
|
msgid "choose"
|
||||||
|
msgstr "choose"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:41
|
||||||
|
msgid "choose backup file"
|
||||||
|
msgstr "choose backup file"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:42
|
||||||
|
msgid "click to download"
|
||||||
|
msgstr "click to download"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:11
|
||||||
|
msgid "click to update"
|
||||||
|
msgstr "click to update"
|
||||||
|
|
||||||
|
#: src/components/toast/index.vue:3
|
||||||
|
msgid "closed"
|
||||||
|
msgstr "closed"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:8
|
||||||
|
msgid "config"
|
||||||
|
msgstr "config"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:26
|
||||||
|
msgid "customize"
|
||||||
|
msgstr "customize"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:20 src/pages/store/components/tag.vue:8
|
||||||
|
msgid "default"
|
||||||
|
msgstr "default"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:148
|
||||||
|
msgid "Docker is not installed on the system, try to install it?"
|
||||||
|
msgstr "Docker is not installed on the system, try to install it?"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:38
|
||||||
|
msgid "download"
|
||||||
|
msgstr "Download"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:4 src/pages/maintance/localBackup.vue:4
|
||||||
|
msgid "explain"
|
||||||
|
msgstr "explain"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:16
|
||||||
|
msgid "external storage directory"
|
||||||
|
msgstr "external storage directory"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:5
|
||||||
|
msgid "hide docker packages"
|
||||||
|
msgstr "hide docker packages"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:7
|
||||||
|
msgid "hide incompatible packages"
|
||||||
|
msgstr "hide incompatible packages"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:6
|
||||||
|
msgid "Icon is gone"
|
||||||
|
msgstr "Icon is gone"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:6
|
||||||
|
msgid "in this page, you can upload and install packages"
|
||||||
|
msgstr "in this page, you can upload and install packages"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:89
|
||||||
|
msgid "install"
|
||||||
|
msgstr "install"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:154 src/components/app/index.vue:164
|
||||||
|
msgid "installation failed, error code"
|
||||||
|
msgstr "installation failed, error code"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:23
|
||||||
|
msgid "installed"
|
||||||
|
msgstr "installed"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:9
|
||||||
|
msgid "last version"
|
||||||
|
msgstr "last version"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:32
|
||||||
|
msgid "Latest update"
|
||||||
|
msgstr "Latest date"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:17
|
||||||
|
msgid "light backup"
|
||||||
|
msgstr "light backup"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:41
|
||||||
|
msgid "like"
|
||||||
|
msgstr "like"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:38
|
||||||
|
msgid "liked"
|
||||||
|
msgstr "liked"
|
||||||
|
|
||||||
|
#: src/pages/store/components/apps.vue:6
|
||||||
|
msgid "loading data"
|
||||||
|
msgstr "loading data"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:22
|
||||||
|
msgid "local backup"
|
||||||
|
msgstr "local backup"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:35
|
||||||
|
msgid "maintain"
|
||||||
|
msgstr "maintain"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:31
|
||||||
|
msgid "manual install"
|
||||||
|
msgstr "manual install"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:42
|
||||||
|
msgid "monitor"
|
||||||
|
msgstr "Monitor"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:40
|
||||||
|
msgid "multimedia"
|
||||||
|
msgstr "Multimedia"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:41
|
||||||
|
msgid "nas"
|
||||||
|
msgstr "NAS"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:36
|
||||||
|
msgid "net"
|
||||||
|
msgstr "Net"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:44
|
||||||
|
msgid "networking"
|
||||||
|
msgstr "Networking"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:17 src/pages/upload/index.vue:18
|
||||||
|
msgid "no files selected"
|
||||||
|
msgstr "no files selected"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:12
|
||||||
|
msgid "no internet required for recovery"
|
||||||
|
msgstr "no internet required for recovery"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:4
|
||||||
|
msgid "offline install"
|
||||||
|
msgstr "offline install"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:92 src/pages/upload/index.vue:97
|
||||||
|
msgid "offline installation failed with error code"
|
||||||
|
msgstr "offline installation failed with error code"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:80
|
||||||
|
msgid "open"
|
||||||
|
msgstr "open"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:3
|
||||||
|
msgid "order"
|
||||||
|
msgstr "order"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:12
|
||||||
|
msgid "packages installed offline may not appear in iStore"
|
||||||
|
msgstr "packages installed offline may not appear in iStore"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:29
|
||||||
|
msgid "please enter an absolute path starting with %{name}"
|
||||||
|
msgstr "please enter an absolute path starting with %{name}"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:139
|
||||||
|
msgid "please enter the path, give up the input, please clear the input box"
|
||||||
|
msgstr "please enter the path, give up the input, please clear the input box"
|
||||||
|
|
||||||
|
#: src/pages/store/components/search.vue:6
|
||||||
|
msgid "please enter the search keyword"
|
||||||
|
msgstr "please enter the search keyword"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:157
|
||||||
|
#: src/pages/maintance/localBackup.vue:191
|
||||||
|
msgid "please fill in the external storage directory"
|
||||||
|
msgstr "please fill in the external storage directory"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:218
|
||||||
|
msgid "please select a backup file to restore"
|
||||||
|
msgstr "please select a backup file to restore"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:21
|
||||||
|
#: src/pages/maintance/localBackup.vue:36
|
||||||
|
msgid "restore backup"
|
||||||
|
msgstr "restore backup"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:76
|
||||||
|
#: src/pages/maintance/lightBackup.vue:81
|
||||||
|
#: src/pages/maintance/localBackup.vue:224
|
||||||
|
#: src/pages/maintance/localBackup.vue:229
|
||||||
|
msgid "restore failed with error code"
|
||||||
|
msgstr "restore failed with error code"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:47
|
||||||
|
msgid "restore now"
|
||||||
|
msgstr "restore now"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:20
|
||||||
|
msgid "select or drag and drop files"
|
||||||
|
msgstr "select or drag and drop files"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:39
|
||||||
|
msgid "service"
|
||||||
|
msgstr "Service"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:5
|
||||||
|
msgid "show docker packages"
|
||||||
|
msgstr "show docker packages"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:7
|
||||||
|
msgid "show incompatible packages"
|
||||||
|
msgstr "show incompatible packages"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:92
|
||||||
|
msgid "Stick to the top"
|
||||||
|
msgstr "Stick to the top"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:9
|
||||||
|
msgid "supports .ipk packages, .run self-extracting packages"
|
||||||
|
msgstr "supports .ipk packages, .run self-extracting packages"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:43
|
||||||
|
msgid "system"
|
||||||
|
msgstr "System"
|
||||||
|
|
||||||
|
#: src/pages/store/components/tag.vue:4
|
||||||
|
msgid "tag"
|
||||||
|
msgstr "Tag"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:13
|
||||||
|
msgid "the is last version"
|
||||||
|
msgstr "the is last version"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:167
|
||||||
|
msgid "there are no backup files in this path"
|
||||||
|
msgstr "there are no backup files in this path"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:37
|
||||||
|
msgid "tool"
|
||||||
|
msgstr "Tool"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:62
|
||||||
|
msgid "tutorial"
|
||||||
|
msgstr "tutorial"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:83 src/components/app/index.vue:84
|
||||||
|
msgid "uninstall"
|
||||||
|
msgstr "uninstall"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:177 src/components/app/index.vue:182
|
||||||
|
msgid "uninstall failed, error code"
|
||||||
|
msgstr "uninstall failed, error code"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:55
|
||||||
|
msgid "update date"
|
||||||
|
msgstr "update date"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:49
|
||||||
|
msgid "update error"
|
||||||
|
msgstr "update error"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:138 src/components/app/index.vue:143
|
||||||
|
msgid "update failed with error code"
|
||||||
|
msgstr "update failed with error code"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:37
|
||||||
|
msgid "update success, reloading"
|
||||||
|
msgstr "update success, reloading"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:32
|
||||||
|
msgid "updateing"
|
||||||
|
msgstr "updateing"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:74 src/pages/maintance/index.vue:4
|
||||||
|
msgid "upgrade"
|
||||||
|
msgstr "upgrade"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:13
|
||||||
|
msgid "upload software list and install from iStore when restoring backup"
|
||||||
|
msgstr "upload software list and install from iStore when restoring backup"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:72 src/pages/upload/index.vue:88
|
||||||
|
msgid "uploading"
|
||||||
|
msgstr "uploading"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:65
|
||||||
|
msgid "website"
|
||||||
|
msgstr "website"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:201
|
||||||
|
msgid "you have already liked"
|
||||||
|
msgstr "you have already liked"
|
||||||
|
|
||||||
|
#~ msgid "do not refresh this page during plugin installation/uninstallation"
|
||||||
|
#~ msgstr "do not refresh this page during plugin installation/uninstallation"
|
||||||
|
|
||||||
|
#~ msgid "installation is complete, please close the current window"
|
||||||
|
#~ msgstr "installation is complete, please close the current window"
|
||||||
|
|
||||||
|
#~ msgid "installing plugin"
|
||||||
|
#~ msgstr "installing plugin"
|
||||||
|
|
||||||
|
#~ msgid "offline installation is successful, please close the current window"
|
||||||
|
#~ msgstr "offline installation is successful, please close the current window"
|
||||||
|
|
||||||
|
#~ msgid "restoring backup"
|
||||||
|
#~ msgstr "restoring backup"
|
||||||
|
|
||||||
|
#~ msgid "the backup was restored fail"
|
||||||
|
#~ msgstr "the backup was restored fail"
|
||||||
|
|
||||||
|
#~ msgid "the backup was restored success"
|
||||||
|
#~ msgstr "the backup was restored success"
|
||||||
|
|
||||||
|
#~ msgid "the update is success, please close the current window"
|
||||||
|
#~ msgstr "the update is success, please close the current window"
|
||||||
|
|
||||||
|
#~ msgid "uninstallation succeeded, please close the current window"
|
||||||
|
#~ msgstr "uninstallation succeeded, please close the current window"
|
||||||
|
|
||||||
|
#~ msgid "uninstalling plugin"
|
||||||
|
#~ msgstr "uninstalling plugin"
|
||||||
|
|
||||||
|
#~ msgid "updating plugin"
|
||||||
|
#~ msgstr "updating plugin"
|
380
istore/translations/templates/app.pot
Normal file
380
istore/translations/templates/app.pot
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:25
|
||||||
|
msgid "%{ num } download"
|
||||||
|
msgid_plural "%{ num } downloads"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:32
|
||||||
|
msgid "%{ num } like"
|
||||||
|
msgid_plural "%{ num } likes"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:27
|
||||||
|
msgid "all app"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:12
|
||||||
|
msgid "app backup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:168
|
||||||
|
msgid "are you sure you want to uninstall %{name}?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:51
|
||||||
|
msgid "author"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:41
|
||||||
|
msgid "backup fail"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:198
|
||||||
|
#: src/pages/maintance/localBackup.vue:203
|
||||||
|
msgid "backup fail with error code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:18
|
||||||
|
#: src/pages/maintance/localBackup.vue:34
|
||||||
|
msgid "backup now"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:61
|
||||||
|
msgid "backup now backs up installed software (unlimited installation sources) to external storage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:9
|
||||||
|
msgid "backup now will download a list of iStore installed software"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:40
|
||||||
|
msgid "backup success"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:39
|
||||||
|
msgid "backuping"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:45
|
||||||
|
msgid "Broken"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:24
|
||||||
|
msgid "By download"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:28
|
||||||
|
msgid "By rating"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:4
|
||||||
|
msgid "checking for latest version"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:22
|
||||||
|
msgid "choose"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:41
|
||||||
|
msgid "choose backup file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:42
|
||||||
|
msgid "click to download"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:11
|
||||||
|
msgid "click to update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/toast/index.vue:3
|
||||||
|
msgid "closed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:8
|
||||||
|
msgid "config"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:26
|
||||||
|
msgid "customize"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:20
|
||||||
|
#: src/pages/store/components/tag.vue:8
|
||||||
|
msgid "default"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:148
|
||||||
|
msgid "Docker is not installed on the system, try to install it?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:38
|
||||||
|
msgid "download"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:4
|
||||||
|
#: src/pages/maintance/localBackup.vue:4
|
||||||
|
msgid "explain"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:16
|
||||||
|
msgid "external storage directory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:5
|
||||||
|
msgid "hide docker packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:7
|
||||||
|
msgid "hide incompatible packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:6
|
||||||
|
msgid "Icon is gone"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:6
|
||||||
|
msgid "in this page, you can upload and install packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:89
|
||||||
|
msgid "install"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:154
|
||||||
|
#: src/components/app/index.vue:164
|
||||||
|
msgid "installation failed, error code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:23
|
||||||
|
msgid "installed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:9
|
||||||
|
msgid "last version"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:32
|
||||||
|
msgid "Latest update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:17
|
||||||
|
msgid "light backup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:41
|
||||||
|
msgid "like"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:38
|
||||||
|
msgid "liked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/apps.vue:6
|
||||||
|
msgid "loading data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:22
|
||||||
|
msgid "local backup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:35
|
||||||
|
msgid "maintain"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:31
|
||||||
|
msgid "manual install"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:42
|
||||||
|
msgid "monitor"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:40
|
||||||
|
msgid "multimedia"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:41
|
||||||
|
msgid "nas"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:36
|
||||||
|
msgid "net"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:44
|
||||||
|
msgid "networking"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:17
|
||||||
|
#: src/pages/upload/index.vue:18
|
||||||
|
msgid "no files selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:12
|
||||||
|
msgid "no internet required for recovery"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:4
|
||||||
|
msgid "offline install"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:92
|
||||||
|
#: src/pages/upload/index.vue:97
|
||||||
|
msgid "offline installation failed with error code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:80
|
||||||
|
msgid "open"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:3
|
||||||
|
msgid "order"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:12
|
||||||
|
msgid "packages installed offline may not appear in iStore"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:29
|
||||||
|
msgid "please enter an absolute path starting with %{name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:139
|
||||||
|
msgid "please enter the path, give up the input, please clear the input box"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/search.vue:6
|
||||||
|
msgid "please enter the search keyword"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:157
|
||||||
|
#: src/pages/maintance/localBackup.vue:191
|
||||||
|
msgid "please fill in the external storage directory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:218
|
||||||
|
msgid "please select a backup file to restore"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:21
|
||||||
|
#: src/pages/maintance/localBackup.vue:36
|
||||||
|
msgid "restore backup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:76
|
||||||
|
#: src/pages/maintance/lightBackup.vue:81
|
||||||
|
#: src/pages/maintance/localBackup.vue:224
|
||||||
|
#: src/pages/maintance/localBackup.vue:229
|
||||||
|
msgid "restore failed with error code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:47
|
||||||
|
msgid "restore now"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:20
|
||||||
|
msgid "select or drag and drop files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:39
|
||||||
|
msgid "service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:5
|
||||||
|
msgid "show docker packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:7
|
||||||
|
msgid "show incompatible packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:92
|
||||||
|
msgid "Stick to the top"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:9
|
||||||
|
msgid "supports .ipk packages, .run self-extracting packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:43
|
||||||
|
msgid "system"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/store/components/tag.vue:4
|
||||||
|
msgid "tag"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:13
|
||||||
|
msgid "the is last version"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:167
|
||||||
|
msgid "there are no backup files in this path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:37
|
||||||
|
msgid "tool"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:62
|
||||||
|
msgid "tutorial"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:83
|
||||||
|
#: src/components/app/index.vue:84
|
||||||
|
msgid "uninstall"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:177
|
||||||
|
#: src/components/app/index.vue:182
|
||||||
|
msgid "uninstall failed, error code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:55
|
||||||
|
msgid "update date"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:49
|
||||||
|
msgid "update error"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:138
|
||||||
|
#: src/components/app/index.vue:143
|
||||||
|
msgid "update failed with error code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:37
|
||||||
|
msgid "update success, reloading"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:32
|
||||||
|
msgid "updateing"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:74
|
||||||
|
#: src/pages/maintance/index.vue:4
|
||||||
|
msgid "upgrade"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:13
|
||||||
|
msgid "upload software list and install from iStore when restoring backup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:72
|
||||||
|
#: src/pages/upload/index.vue:88
|
||||||
|
msgid "uploading"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:65
|
||||||
|
msgid "website"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:201
|
||||||
|
msgid "you have already liked"
|
||||||
|
msgstr ""
|
411
istore/translations/zh-cn/app.po
Normal file
411
istore/translations/zh-cn/app.po
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: istore 0.1\n"
|
||||||
|
"Last-Translator: jjm2473\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: zh-cn\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:25
|
||||||
|
msgid "%{ num } download"
|
||||||
|
msgid_plural "%{ num } downloads"
|
||||||
|
msgstr[0] "%{ num } 次下载"
|
||||||
|
msgstr[1] "%{ num } 次下载"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:32
|
||||||
|
msgid "%{ num } like"
|
||||||
|
msgid_plural "%{ num } likes"
|
||||||
|
msgstr[0] "%{ num } 次点赞"
|
||||||
|
msgstr[1] "%{ num } 次点赞"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:27
|
||||||
|
msgid "all app"
|
||||||
|
msgstr "全部软件"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:12
|
||||||
|
msgid "app backup"
|
||||||
|
msgstr "软件备份"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:168
|
||||||
|
msgid "are you sure you want to uninstall %{name}?"
|
||||||
|
msgstr "确定要卸载 %{name} 吗?"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:51
|
||||||
|
msgid "author"
|
||||||
|
msgstr "作者"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:41
|
||||||
|
msgid "backup fail"
|
||||||
|
msgstr "备份失败"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:198
|
||||||
|
#: src/pages/maintance/localBackup.vue:203
|
||||||
|
msgid "backup fail with error code"
|
||||||
|
msgstr "备份失败,错误代码"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:18
|
||||||
|
#: src/pages/maintance/localBackup.vue:34
|
||||||
|
msgid "backup now"
|
||||||
|
msgstr "立即备份"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:61
|
||||||
|
msgid ""
|
||||||
|
"backup now backs up installed software (unlimited installation sources) to "
|
||||||
|
"external storage"
|
||||||
|
msgstr "立即备份将备份已安装软件(不限安装来源)到外部存储"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:9
|
||||||
|
msgid "backup now will download a list of iStore installed software"
|
||||||
|
msgstr "立即备份将下载iStore已安装的软件列表"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:40
|
||||||
|
msgid "backup success"
|
||||||
|
msgstr "备份成功"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:39
|
||||||
|
msgid "backuping"
|
||||||
|
msgstr "正在备份"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:45
|
||||||
|
msgid "Broken"
|
||||||
|
msgstr "已损坏"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:24
|
||||||
|
msgid "By download"
|
||||||
|
msgstr "按下载"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:28
|
||||||
|
msgid "By rating"
|
||||||
|
msgstr "按评分"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:4
|
||||||
|
msgid "checking for latest version"
|
||||||
|
msgstr "正在检查最新版本"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:22
|
||||||
|
msgid "choose"
|
||||||
|
msgstr "请选择"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:41
|
||||||
|
msgid "choose backup file"
|
||||||
|
msgstr "选择备份文件"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:42
|
||||||
|
msgid "click to download"
|
||||||
|
msgstr "点此下载"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:11
|
||||||
|
msgid "click to update"
|
||||||
|
msgstr "点我更新"
|
||||||
|
|
||||||
|
#: src/components/toast/index.vue:3
|
||||||
|
msgid "closed"
|
||||||
|
msgstr "关闭"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:8
|
||||||
|
msgid "config"
|
||||||
|
msgstr "配置"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:26
|
||||||
|
msgid "customize"
|
||||||
|
msgstr "自定义"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:20 src/pages/store/components/tag.vue:8
|
||||||
|
msgid "default"
|
||||||
|
msgstr "默认"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:148
|
||||||
|
msgid "Docker is not installed on the system, try to install it?"
|
||||||
|
msgstr "系统中未安装Docker,是否尝试安装?"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:38
|
||||||
|
msgid "download"
|
||||||
|
msgstr "下载"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:4 src/pages/maintance/localBackup.vue:4
|
||||||
|
msgid "explain"
|
||||||
|
msgstr "说明"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:16
|
||||||
|
msgid "external storage directory"
|
||||||
|
msgstr "外部存储目录"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:5
|
||||||
|
msgid "hide docker packages"
|
||||||
|
msgstr "隐藏 Docker 插件"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:7
|
||||||
|
msgid "hide incompatible packages"
|
||||||
|
msgstr "隐藏架构不兼容插件"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:6
|
||||||
|
msgid "Icon is gone"
|
||||||
|
msgstr "图标出走啦"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:6
|
||||||
|
msgid "in this page, you can upload and install packages"
|
||||||
|
msgstr "通过本页面,你可以上传并安装插件包"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:89
|
||||||
|
msgid "install"
|
||||||
|
msgstr "安装"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:154 src/components/app/index.vue:164
|
||||||
|
msgid "installation failed, error code"
|
||||||
|
msgstr "安装失败,错误码"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:23
|
||||||
|
msgid "installed"
|
||||||
|
msgstr "已安装"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:9
|
||||||
|
msgid "last version"
|
||||||
|
msgstr "最新版本"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:32
|
||||||
|
msgid "Latest update"
|
||||||
|
msgstr "最近更新"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:17
|
||||||
|
msgid "light backup"
|
||||||
|
msgstr "轻量备份"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:41
|
||||||
|
msgid "like"
|
||||||
|
msgstr "点赞"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:38
|
||||||
|
msgid "liked"
|
||||||
|
msgstr "已点赞"
|
||||||
|
|
||||||
|
#: src/pages/store/components/apps.vue:6
|
||||||
|
msgid "loading data"
|
||||||
|
msgstr "正在努力的获取数据"
|
||||||
|
|
||||||
|
#: src/pages/maintance/index.vue:22
|
||||||
|
msgid "local backup"
|
||||||
|
msgstr "本地备份"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:35
|
||||||
|
msgid "maintain"
|
||||||
|
msgstr "维护"
|
||||||
|
|
||||||
|
#: src/components/tabs/index.vue:31
|
||||||
|
msgid "manual install"
|
||||||
|
msgstr "手动安装"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:42
|
||||||
|
msgid "monitor"
|
||||||
|
msgstr "监控"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:40
|
||||||
|
msgid "multimedia"
|
||||||
|
msgstr "多媒体"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:41
|
||||||
|
msgid "nas"
|
||||||
|
msgstr "NAS"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:36
|
||||||
|
msgid "net"
|
||||||
|
msgstr "网络"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:44
|
||||||
|
msgid "networking"
|
||||||
|
msgstr "组网"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:17 src/pages/upload/index.vue:18
|
||||||
|
msgid "no files selected"
|
||||||
|
msgstr "未选择任何文件"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:12
|
||||||
|
msgid "no internet required for recovery"
|
||||||
|
msgstr "恢复时不需要网络"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:4
|
||||||
|
msgid "offline install"
|
||||||
|
msgstr "离线安装"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:92 src/pages/upload/index.vue:97
|
||||||
|
msgid "offline installation failed with error code"
|
||||||
|
msgstr "离线安装失败,错误码"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:80
|
||||||
|
msgid "open"
|
||||||
|
msgstr "打开"
|
||||||
|
|
||||||
|
#: src/pages/store/components/sort.vue:3
|
||||||
|
msgid "order"
|
||||||
|
msgstr "排序"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:12
|
||||||
|
msgid "packages installed offline may not appear in iStore"
|
||||||
|
msgstr "离线安装的插件可能不会出现在iStore中"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:29
|
||||||
|
msgid "please enter an absolute path starting with %{name}"
|
||||||
|
msgstr "请输入 %{name} 开头的绝对路径"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:139
|
||||||
|
msgid "please enter the path, give up the input, please clear the input box"
|
||||||
|
msgstr "请输入路径,放弃输入请清空输入框"
|
||||||
|
|
||||||
|
#: src/pages/store/components/search.vue:6
|
||||||
|
msgid "please enter the search keyword"
|
||||||
|
msgstr "请输入搜索关键词"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:157
|
||||||
|
#: src/pages/maintance/localBackup.vue:191
|
||||||
|
msgid "please fill in the external storage directory"
|
||||||
|
msgstr "请填写外部存储目录"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:218
|
||||||
|
msgid "please select a backup file to restore"
|
||||||
|
msgstr "请选择要恢复的备份文件"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:21
|
||||||
|
#: src/pages/maintance/localBackup.vue:36
|
||||||
|
msgid "restore backup"
|
||||||
|
msgstr "恢复备份"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:76
|
||||||
|
#: src/pages/maintance/lightBackup.vue:81
|
||||||
|
#: src/pages/maintance/localBackup.vue:224
|
||||||
|
#: src/pages/maintance/localBackup.vue:229
|
||||||
|
msgid "restore failed with error code"
|
||||||
|
msgstr "恢复失败,错误代码"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:47
|
||||||
|
msgid "restore now"
|
||||||
|
msgstr "立即恢复"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:20
|
||||||
|
msgid "select or drag and drop files"
|
||||||
|
msgstr "选择或拖放文件"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:39
|
||||||
|
msgid "service"
|
||||||
|
msgstr "服务"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:5
|
||||||
|
msgid "show docker packages"
|
||||||
|
msgstr "显示 Docker 插件"
|
||||||
|
|
||||||
|
#: src/pages/maintance/config.vue:7
|
||||||
|
msgid "show incompatible packages"
|
||||||
|
msgstr "显示架构不兼容插件"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:92
|
||||||
|
msgid "Stick to the top"
|
||||||
|
msgstr "置顶"
|
||||||
|
|
||||||
|
#: src/pages/upload/index.vue:9
|
||||||
|
msgid "supports .ipk packages, .run self-extracting packages"
|
||||||
|
msgstr "支持 .ipk 包,.run 自解压格式"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:43
|
||||||
|
msgid "system"
|
||||||
|
msgstr "系统"
|
||||||
|
|
||||||
|
#: src/pages/store/components/tag.vue:4
|
||||||
|
msgid "tag"
|
||||||
|
msgstr "标签"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:13
|
||||||
|
msgid "the is last version"
|
||||||
|
msgstr "当前已经是最新版本"
|
||||||
|
|
||||||
|
#: src/pages/maintance/localBackup.vue:167
|
||||||
|
msgid "there are no backup files in this path"
|
||||||
|
msgstr "此路径下没有备份文件"
|
||||||
|
|
||||||
|
#: src/plugins/i18n/index.ts:37
|
||||||
|
msgid "tool"
|
||||||
|
msgstr "工具"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:62
|
||||||
|
msgid "tutorial"
|
||||||
|
msgstr "教程"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:83 src/components/app/index.vue:84
|
||||||
|
msgid "uninstall"
|
||||||
|
msgstr "卸载"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:177 src/components/app/index.vue:182
|
||||||
|
msgid "uninstall failed, error code"
|
||||||
|
msgstr "卸载失败,错误码"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:55
|
||||||
|
msgid "update date"
|
||||||
|
msgstr "更新日期"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:49
|
||||||
|
msgid "update error"
|
||||||
|
msgstr "更新失败"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:138 src/components/app/index.vue:143
|
||||||
|
msgid "update failed with error code"
|
||||||
|
msgstr "更新失败,错误代码"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:37
|
||||||
|
msgid "update success, reloading"
|
||||||
|
msgstr "更新成功,重新加载页面"
|
||||||
|
|
||||||
|
#: src/pages/maintance/update.vue:32
|
||||||
|
msgid "updateing"
|
||||||
|
msgstr "正在更新中"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:74 src/pages/maintance/index.vue:4
|
||||||
|
msgid "upgrade"
|
||||||
|
msgstr "更新"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:13
|
||||||
|
msgid "upload software list and install from iStore when restoring backup"
|
||||||
|
msgstr "恢复备份时上传软件列表并从iStore安装"
|
||||||
|
|
||||||
|
#: src/pages/maintance/lightBackup.vue:72 src/pages/upload/index.vue:88
|
||||||
|
msgid "uploading"
|
||||||
|
msgstr "上传中"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:65
|
||||||
|
msgid "website"
|
||||||
|
msgstr "官网"
|
||||||
|
|
||||||
|
#: src/components/app/index.vue:201
|
||||||
|
msgid "you have already liked"
|
||||||
|
msgstr "您已经点赞过啦"
|
||||||
|
|
||||||
|
#~ msgid "do not refresh this page during plugin installation/uninstallation"
|
||||||
|
#~ msgstr "插件安装/卸载过程中请勿刷新此页面"
|
||||||
|
|
||||||
|
#~ msgid "installation is complete, please close the current window"
|
||||||
|
#~ msgstr "安装完成,请关闭当前窗口"
|
||||||
|
|
||||||
|
#~ msgid "installing plugin"
|
||||||
|
#~ msgstr "正在安装插件"
|
||||||
|
|
||||||
|
#~ msgid "offline installation is successful, please close the current window"
|
||||||
|
#~ msgstr "离线安装成功,请关闭当前窗口"
|
||||||
|
|
||||||
|
#~ msgid "restoring backup"
|
||||||
|
#~ msgstr "正在恢复备份"
|
||||||
|
|
||||||
|
#~ msgid "the backup was restored fail"
|
||||||
|
#~ msgstr "恢复备份失败"
|
||||||
|
|
||||||
|
#~ msgid "the backup was restored success"
|
||||||
|
#~ msgstr "恢复备份成功"
|
||||||
|
|
||||||
|
#~ msgid "the update is success, please close the current window"
|
||||||
|
#~ msgstr "更新成功,请关闭当前窗口"
|
||||||
|
|
||||||
|
#~ msgid "uninstallation succeeded, please close the current window"
|
||||||
|
#~ msgstr "卸载完成,请关闭当前窗口"
|
||||||
|
|
||||||
|
#~ msgid "uninstalling plugin"
|
||||||
|
#~ msgstr "正在卸载插件"
|
||||||
|
|
||||||
|
#~ msgid "updating plugin"
|
||||||
|
#~ msgstr "正在更新插件"
|
Loading…
Reference in New Issue
Block a user