update 2025-02-16 22:32:46

This commit is contained in:
actions-user 2025-02-16 22:32:46 +08:00
parent a2e77a7982
commit 0b9a54b8bc
16 changed files with 602 additions and 0 deletions

View File

@ -0,0 +1,24 @@
## API
这里列出的接口都是 lua 实现的,对于 POST 请求都是提交表单( `multipart/form-data` 或者 `application/x-www-form-urlencoded` ),而不是 JSON并且 POST 请求必须提供 `token` 参数用于防止 CSRF`token` 的值可以从全局变量 `window.token` 取得。
1. 自动安装配置软件包
```
POST /cgi-bin/luci/admin/nas/quickstart/auto_setup
token=xxx&packages=aria2&packages=qbittorrent
{"success":0}
{"success":1, "scope":"taskd", "error":"task already running"}
```
这是个异步接口除非任务已经在运行否则都会成功success=0。`packages` 是需要安装配置的软件包列表与元数据的id对应
2. 获取安装配置结果
```
GET /cgi-bin/luci/admin/nas/quickstart/setup_result
{"success":0, "result": {"ongoing": true, "packages": ["aria2", "qbittorrent"], "success":["aria2"], "failed":[]} }
{"success":404, "scope":"taskd", "error":"task not found"}
```
用于在安装过程中或者安装完成时获取当前状态。
安装过程中或者安装完成时,`success` 都是 0`result.ongoing` 表示是否在安装过程中,`result.packages` 是提交的任务列表,`result.success` 是已成功的任务列表,`result.failed` 是已失败的任务列表

View File

@ -0,0 +1,22 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for quickstart
LUCI_DEPENDS:=+quickstart +luci-app-store
LUCI_PKGARCH:=all
PKG_VERSION:=0.8.16-1
# PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:=
LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"zh-cn":{}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,189 @@
-- Copyright 2022 xiaobao <xiaobao@linkease.com>
-- Licensed to the public under the MIT License
local http = require "luci.http"
local nixio = require "nixio"
local ltn12 = require "luci.ltn12"
local table = require "table"
local util = require "luci.util"
module("luci.controller.istore_backend", package.seeall)
local BLOCKSIZE = 2048
local ISTOREOS_PORT = 3038
function index()
entry({"istore"}, call("istore_backend")).leaf=true
end
local function sink_socket(sock, io_err)
if sock then
return function(chunk, err)
if not chunk then
return 1
else
return sock:send(chunk)
end
end
else
return ltn12.sink.error(io_err or "unable to send socket")
end
end
local function session_retrieve(sid, allowed_users)
local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
if type(sdat) == "table" and
type(sdat.values) == "table" and
type(sdat.values.token) == "string" and
(not allowed_users or
util.contains(allowed_users, sdat.values.username))
then
return sid, sdat.values
end
return nil, nil
end
local function get_session()
local sid
local key
local sdat
for _, key in ipairs({"sysauth_https", "sysauth_http", "sysauth"}) do
sid = http.getcookie(key)
if sid then
sid, sdat = session_retrieve(sid, nil)
if sid and sdat then
return sid, sdat
end
end
end
return nil, nil
end
local function chunksource(sock, buffer)
buffer = buffer or ""
return function()
local output
local _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
while not count and #buffer <= 1024 do
local newblock, code = sock:recv(1024 - #buffer)
if not newblock then
return nil, code
end
buffer = buffer .. newblock
_, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
end
count = tonumber(count, 16)
if not count then
return nil, -1, "invalid encoding"
elseif count == 0 then
return nil
elseif count + 2 <= #buffer - endp then
output = buffer:sub(endp+1, endp+count)
buffer = buffer:sub(endp+count+3)
return output
else
output = buffer:sub(endp+1, endp+count)
buffer = ""
if count - #output > 0 then
local remain, code = sock:recvall(count-#output)
if not remain then
return nil, code
end
output = output .. remain
count, code = sock:recvall(2)
else
count, code = sock:recvall(count+2-#buffer+endp)
end
if not count then
return nil, code
end
return output
end
end
end
function istore_backend()
local sock = nixio.connect("127.0.0.1", ISTOREOS_PORT)
if not sock then
http.status(500, "connect failed")
return
end
local input = {}
input[#input+1] = http.getenv("REQUEST_METHOD") .. " " .. http.getenv("REQUEST_URI") .. " HTTP/1.1"
local req = http.context.request
local start = "HTTP_"
local start_len = string.len(start)
local ctype = http.getenv("CONTENT_TYPE")
if ctype then
input[#input+1] = "Content-Type: " .. ctype
end
for k, v in pairs(req.message.env) do
if string.sub(k, 1, start_len) == start and not string.find(k, "FORWARDED") then
input[#input+1] = string.sub(k, start_len+1, string.len(k)) .. ": " .. v
end
end
local sid, sdat = get_session()
if sdat ~= nil then
input[#input+1] = "X-Forwarded-Sid: " .. sid
input[#input+1] = "X-Forwarded-Token: " .. sdat.token
end
-- input[#input+1] = "X-Forwarded-For: " .. http.getenv("REMOTE_HOST") ..":".. http.getenv("REMOTE_PORT")
local num = tonumber(http.getenv("CONTENT_LENGTH")) or 0
input[#input+1] = "Content-Length: " .. tostring(num)
input[#input+1] = "\r\n"
local source = ltn12.source.cat(ltn12.source.string(table.concat(input, "\r\n")), http.source())
local ret = ltn12.pump.all(source, sink_socket(sock, "write sock error"))
if ret ~= 1 then
sock:close()
http.status(500, "proxy error")
return
end
local linesrc = sock:linesource()
local line, code, error = linesrc()
if not line then
sock:close()
http.status(500, "response parse failed")
return
end
local protocol, status, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
if not protocol then
sock:close()
http.status(500, "response protocol error")
return
end
num = tonumber(status) or 0
http.status(num, msg)
local chunked = 0
line = linesrc()
while line and line ~= "" do
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
if key and key ~= "Status" then
if key == "Transfer-Encoding" and val == "chunked" then
chunked = 1
end
if key ~= "Connection" and key ~= "Transfer-Encoding" and key ~= "Content-Length" then
http.header(key, val)
end
end
line = linesrc()
end
if not line then
sock:close()
http.status(500, "parse header failed")
return
end
local body_buffer = linesrc(true)
if chunked == 1 then
ltn12.pump.all(chunksource(sock, body_buffer), http.write)
else
local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), sock:blocksource())
ltn12.pump.all(body_source, http.write)
end
sock:close()
end

View File

@ -0,0 +1,161 @@
local http = require "luci.http"
module("luci.controller.quickstart", package.seeall)
function index()
if luci.sys.call("pgrep quickstart >/dev/null") == 0 then
entry({"admin", "quickstart"}, template("quickstart/home"), _("QuickStart"), 1).leaf = true
entry({"admin", "network_guide"}, call("networkguide_index"), _("NetworkGuide"), 2)
entry({"admin", "network_guide", "pages"}, call("quickstart_index", {index={"admin", "network_guide", "pages"}})).leaf = true
if nixio.fs.access("/usr/lib/lua/luci/view/quickstart/main_dev.htm") then
entry({"admin", "quickstart_dev"}, call("quickstart_dev", {index={"admin", "quickstart_dev"}})).leaf = true
end
entry({"admin", "nas", "raid"}, call("quickstart_index", {index={"admin", "nas"}}), _("RAID"), 10).leaf = true
entry({"admin", "nas", "smart"}, call("quickstart_index", {index={"admin", "nas"}}), _("S.M.A.R.T."), 11).leaf = true
entry({"admin", "network", "interfaceconfig"}, call("quickstart_index", {index={"admin", "network"}}), _("NetworkPort"), 11).leaf = true
entry({"admin", "nas", "quickstart"}).dependent = false
entry({"admin", "nas", "quickstart", "auto_setup"}, post("auto_setup"))
entry({"admin", "nas", "quickstart", "setup_result"}, call("setup_result"))
else
entry({"admin", "quickstart"}, call("redirect_fallback")).leaf = true
end
end
function networkguide_index()
luci.http.redirect(luci.dispatcher.build_url("admin", "network_guide", "pages", "network"))
end
function redirect_fallback()
luci.http.redirect(luci.dispatcher.build_url("admin", "status"))
end
local function vue_lang()
local i18n = require("luci.i18n")
local lang = i18n.translate("quickstart_vue_lang")
if lang == "quickstart_vue_lang" or lang == "" then
lang = "en"
end
return lang
end
function quickstart_index(param)
luci.template.render("quickstart/main", {prefix=luci.dispatcher.build_url(unpack(param.index)),lang=vue_lang()})
end
function quickstart_dev(param)
luci.template.render("quickstart/main_dev", {prefix=luci.dispatcher.build_url(unpack(param.index)),lang=vue_lang()})
end
function auto_setup()
local os = require "os"
local fs = require "nixio.fs"
local rshift = nixio.bit.rshift
-- json style
-- local jsonc = require "luci.jsonc"
-- local json_parse = jsonc.parse
-- local req = json_parse(luci.http.content())
-- local pkgs = ""
-- for k, v in pairs(req.packages) do
-- pkgs = pkgs .. " " .. luci.util.shellquote(v)
-- end
-- form style
local packages = luci.http.formvalue("packages")
local pkgs = ""
if type(packages) == "table" then
if #packages > 0 then
for k, v in pairs(packages) do
pkgs = pkgs .. " " .. luci.util.shellquote(v)
end
end
else
if packages ~= nil and packages ~= "" then
pkgs = luci.util.shellquote(packages)
end
end
local ret
if pkgs == "" then
ret = {
success = 1,
scope = "params",
error = "Parameter 'packages' undefined!",
}
else
local cmd = "/usr/libexec/quickstart/auto_setup.sh " .. pkgs
cmd = "/etc/init.d/tasks task_add auto_setup " .. luci.util.shellquote(cmd)
local r = os.execute(cmd .. " >/var/log/auto_setup.stdout 2>/var/log/auto_setup.stderr")
local e = fs.readfile("/var/log/auto_setup.stderr")
local o = fs.readfile("/var/log/auto_setup.stdout")
fs.unlink("/var/log/auto_setup.stderr")
fs.unlink("/var/log/auto_setup.stdout")
e = e or ""
if r == 256 and e == "" then
e = "os.execute exit code 1"
end
r = rshift(r,8)
ret = {
success = r,
scope = "taskd",
error = e,
detail = o,
}
end
luci.http.prepare_content("application/json")
luci.http.write_json(ret)
end
function setup_result()
local fs = require "nixio.fs"
local taskd = require "luci.model.tasks"
local packages = nil
local success = nil
local failed = nil
local status = taskd.status("auto_setup")
local ret = {
}
if status.running or status.exit_code ~= 404 then
local item
local po = fs.readfile("/var/log/auto_setup.input") or ""
for item in po:gmatch("[^\n]+") do
if packages then
packages[#packages+1] = item
else
packages = {item}
end
end
local so = fs.readfile("/var/log/auto_setup.success") or ""
for item in so:gmatch("[^\n]+") do
if success then
success[#success+1] = item
else
success = {item}
end
end
local fo = fs.readfile("/var/log/auto_setup.failed") or ""
for item in fo:gmatch("[^\n]+") do
if failed then
failed[#failed+1] = item
else
failed = {item}
end
end
ret.success = 0
ret.result = {
ongoing = status.running,
packages = packages,
success = success,
failed = failed,
}
else
ret.success = 404
ret.scope = "taskd"
ret.error = "task not found"
end
luci.http.prepare_content("application/json")
luci.http.write_json(ret)
end

View File

@ -0,0 +1,11 @@
<%
local function vue_lang()
local i18n = require("luci.i18n")
local lang = i18n.translate("quickstart_vue_lang")
if lang == "quickstart_vue_lang" or lang == "" then
lang = "en"
end
return lang
end
-%>
<% luci.template.render("quickstart/main", {prefix=luci.dispatcher.build_url("admin", "quickstart"),lang=vue_lang()}) %>

View File

@ -0,0 +1,51 @@
<%+header%>
<%
local jsonc = require "luci.jsonc"
local features = { "_lua_force_array_" }
local configs = {}
if luci.sys.call("which ota >/dev/null 2>&1 && ota >/dev/null 2>&1") == 0 then
features[#features+1] = "ota"
end
if luci.sys.call("[ -d /ext_overlay ] >/dev/null 2>&1") == 0 then
features[#features+1] = "sandbox"
end
if luci.sys.call("[ -e /etc/init.d/dockerd ] >/dev/null 2>&1") == 0 then
features[#features+1] = "dockerd"
end
if luci.sys.call("[ -e /etc/init.d/unishare ] >/dev/null 2>&1") == 0 then
features[#features+1] = "unishare"
end
if luci.sys.call("/etc/init.d/ttyd running >/dev/null 2>&1") == 0 then
features[#features+1] = "ttyd"
local uci = require "luci.model.uci".cursor()
local port = uci:get_first("ttyd", "ttyd", "port") or "7681"
local ssl = uci:get_first("ttyd", "ttyd", "ssl") or "0"
configs["ttyd"] = {
port = tonumber(port),
ssl = ssl == "1"
}
end
-%>
<script>
(function(){
window.token = "<%=token%>";
var vue_prefix="<%=prefix%>";
window.vue_base = vue_prefix + '/';
window.vue_lang_data = '/luci-static/quickstart/i18n/<%=lang%>.json<%# ?v=PKG_VERSION %>';
window.vue_lang = '<%=lang%>';
window.quickstart_features = <%=jsonc.stringify(features)%>;
window.quickstart_configs = <%=jsonc.stringify(configs)%>;
if (location.pathname != vue_prefix && !location.pathname.startsWith(window.vue_base)) {
if (window.history && window.history.replaceState) {
window.history.replaceState({}, null, vue_prefix);
} else {
location.href = vue_prefix;
}
}
})();
</script>
<div id="app">
</div>
<script type="module" crossorigin src="/luci-static/quickstart/index.js<%# ?v=PKG_VERSION %>"></script>
<link rel="stylesheet" href="/luci-static/quickstart/style.css<%# ?v=PKG_VERSION %>">
<%+footer%>

View File

@ -0,0 +1,14 @@
msgid "NetworkGuide"
msgstr "网络向导"
msgid "QuickStart"
msgstr "首页"
msgid "RAID"
msgstr "磁盘阵列"
msgid "NetworkPort"
msgstr "网口配置"
msgid "quickstart_vue_lang"
msgstr "zh-cn"

View File

@ -0,0 +1 @@
zh-cn

View File

@ -0,0 +1,4 @@
#!/bin/sh
rm -f /tmp/luci-indexcache
exit 0

View File

@ -0,0 +1,61 @@
#!/bin/sh
> /var/log/auto_setup.success
> /var/log/auto_setup.failed
> /var/log/auto_setup.input
save_input() {
local pkg
for pkg in $@; do
echo "$pkg" >> /var/log/auto_setup.input
done
}
save_input "$@"
. /lib/functions.sh
load_quickstart_cfg() {
config_load quickstart || return $?
local main_dir conf_dir pub_dir dl_dir tmp_dir
config_get main_dir "main" main_dir
[ -z "$main_dir" ] && { echo "Home dir not configured!" >&2 ; return 1 ; }
config_get conf_dir "main" conf_dir "$main_dir/Configs"
config_get pub_dir "main" pub_dir "$main_dir/Public"
config_get tmp_dir "main" tmp_dir "$main_dir/Caches"
config_get dl_dir "main" dl_dir "$pub_dir/Downloads"
export ISTORE_CONF_DIR="$conf_dir"
export ISTORE_DL_DIR="$dl_dir"
export ISTORE_CACHE_DIR="$tmp_dir"
export ISTORE_PUBLIC_DIR="$pub_dir"
mkdir -p "$ISTORE_CONF_DIR" "$ISTORE_DL_DIR" "$ISTORE_CACHE_DIR" "$ISTORE_PUBLIC_DIR"
chmod 777 "$ISTORE_DL_DIR"
}
auto_setup_app() {
local pkg=$1
is-opkg install "app-meta-$pkg" || return 1
sh -c ". '/usr/libexec/istorea/$pkg.sh'"
}
auto_setup_apps() {
local pkg
for pkg in $@; do
echo "Setting up $pkg..."
if auto_setup_app $pkg; then
echo "Set up $pkg success"
echo "$pkg" >> /var/log/auto_setup.success
else
echo "Set up $pkg failed"
echo "$pkg" >> /var/log/auto_setup.failed
fi
done
}
load_quickstart_cfg || exit $?
auto_setup_apps "$@"
[ ! -s /var/log/auto_setup.failed ]

View File

@ -0,0 +1,10 @@
{
"admin/quickstart/*": {
"title": "QuickStart",
"order": 1,
"action": {
"type": "template",
"path": "quickstart/home"
}
}
}