Skip to content

Commit 38eb541

Browse files
committed
添加在线下载程序功能
1 parent e1e68a1 commit 38eb541

9 files changed

Lines changed: 1521 additions & 63 deletions

File tree

luci-app-easytier/luasrc/controller/easytier.lua

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ function index()
7474
entry({"admin", "vpn", "easytier", "restart_service"}, call("restart_service")).leaf = true
7575
entry({"admin", "vpn", "easytier", "toggle_core"}, call("toggle_core")).leaf = true
7676
entry({"admin", "vpn", "easytier", "toggle_web"}, call("toggle_web")).leaf = true
77+
entry({"admin", "vpn", "easytier", "download_easytier"}, call("download_easytier")).leaf = true
78+
entry({"admin", "vpn", "easytier", "download_progress"}, call("download_progress")).leaf = true
79+
entry({"admin", "vpn", "easytier", "cancel_download"}, call("cancel_download")).leaf = true
7780
end
7881

7982
function act_status()
@@ -749,3 +752,389 @@ function toggle_web()
749752
luci.http.prepare_content("application/json")
750753
luci.http.write_json({success = true})
751754
end
755+
-- 检测CPU架构
756+
local function detect_arch()
757+
local cputype = safe_exec("uname -ms | tr ' ' '_' | tr '[A-Z]' '[a-z]'")
758+
local cpucore = ""
759+
760+
if cputype:match("linux.*armv.*") then
761+
cpucore = "arm"
762+
end
763+
if cputype:match("linux.*armv7.*") and safe_exec("cat /proc/cpuinfo | grep vfp") ~= "" then
764+
cpucore = "armv7"
765+
end
766+
if cputype:match("linux.*aarch64.*") or cputype:match("linux.*armv8.*") then
767+
cpucore = "aarch64"
768+
end
769+
if cputype:match("linux.*86.*") then
770+
cpucore = "i386"
771+
end
772+
if cputype:match("linux.*86_64.*") then
773+
cpucore = "x86_64"
774+
end
775+
if cputype:match("linux.*mips.*") then
776+
local mipstype = safe_exec("echo -n I | hexdump -o 2>/dev/null | awk '{ print substr($2,6,1); exit}'")
777+
if mipstype == "0" then
778+
cpucore = "mips"
779+
else
780+
cpucore = "mipsel"
781+
end
782+
end
783+
784+
return cpucore
785+
end
786+
787+
-- 检查依赖
788+
local function check_dependencies()
789+
local arch = detect_arch()
790+
if arch == "" then
791+
return false, i18n.translate("Unable to detect CPU architecture")
792+
end
793+
if arch == "i386" then
794+
return false, i18n.translate("No x86-32bit program available")
795+
end
796+
if safe_exec("which unzip") == "" then
797+
return false, i18n.translate("System lacks unzip, cannot download and extract")
798+
end
799+
return true, arch
800+
end
801+
802+
-- 获取GitHub镜像列表
803+
local function get_github_proxies()
804+
local uci = require "luci.model.uci".cursor()
805+
local proxies = {}
806+
807+
-- 从UCI配置读取代理列表
808+
local proxy_list = uci:get("easytier", "@easytier[0]", "github_proxys")
809+
810+
if proxy_list then
811+
if type(proxy_list) == "table" then
812+
-- 多个代理
813+
for _, proxy in ipairs(proxy_list) do
814+
if proxy and proxy ~= "" then
815+
table.insert(proxies, proxy)
816+
end
817+
end
818+
else
819+
-- 单个代理
820+
if proxy_list ~= "" then
821+
table.insert(proxies, proxy_list)
822+
end
823+
end
824+
end
825+
826+
-- 如果没有配置代理,使用默认值
827+
if #proxies == 0 then
828+
proxies = {
829+
"https://ghproxy.net/",
830+
"https://gh-proxy.com/",
831+
"https://cdn.gh-proxy.com/",
832+
"https://ghfast.top/"
833+
}
834+
end
835+
836+
-- 添加不使用代理的选项(空字符串表示直连)
837+
table.insert(proxies, "")
838+
839+
return proxies
840+
end
841+
842+
-- 下载文件
843+
local function download_file(url, output_path, progress_callback)
844+
local download_tools = {"curl", "wget"}
845+
846+
for _, tool in ipairs(download_tools) do
847+
if safe_exec("which " .. tool) ~= "" then
848+
local cmd = ""
849+
if tool == "curl" then
850+
cmd = string.format("curl -L -k --connect-timeout 30 --max-time 300 -o '%s' '%s'", output_path, url)
851+
elseif tool == "wget" then
852+
cmd = string.format("wget --no-check-certificate --timeout=30 --tries=3 -O '%s' '%s'", output_path, url)
853+
end
854+
855+
if progress_callback then
856+
progress_callback(50, i18n.translate("Downloading with") .. " " .. tool .. "...")
857+
end
858+
859+
local result = os.execute(cmd)
860+
if result == 0 and nixio.fs.access(output_path) then
861+
-- 检查文件大小,确保下载完整
862+
local stat = nixio.fs.stat(output_path)
863+
if stat and stat.size > 3 * 1024 * 1024 then -- 至少3MB
864+
return true
865+
end
866+
end
867+
end
868+
end
869+
870+
return false
871+
end
872+
873+
function download_easytier()
874+
luci.http.prepare_content("application/json")
875+
876+
local json = require "luci.jsonc"
877+
local progress_file = "/tmp/easytier_download_progress"
878+
879+
-- 检查是否已有下载任务
880+
local existing_progress = safe_read_file(progress_file)
881+
if existing_progress then
882+
local progress_data = json.parse(existing_progress)
883+
if progress_data and progress_data.progress > 0 and progress_data.progress < 100 and not progress_data.error then
884+
luci.http.write_json({
885+
success = true,
886+
progress = progress_data.progress,
887+
message = i18n.translate("Download task already exists"),
888+
url = progress_data.url or ""
889+
})
890+
return
891+
end
892+
end
893+
894+
local req_data = json.parse(luci.http.content())
895+
if not req_data or not req_data.version then
896+
luci.http.write_json({success = false, message = i18n.translate("Missing version parameter")})
897+
return
898+
end
899+
900+
local version = req_data.version
901+
902+
-- 1. 检查依赖和架构
903+
local ok, arch_or_error = check_dependencies()
904+
if not ok then
905+
luci.http.write_json({success = false, message = arch_or_error})
906+
return
907+
end
908+
909+
local arch = arch_or_error
910+
local proxies = get_github_proxies()
911+
local download_dir = "/tmp/easytier_download"
912+
local zip_file = download_dir .. "/easytier-linux-" .. arch .. "-" .. version .. ".zip"
913+
914+
-- 创建下载目录
915+
os.execute("mkdir -p " .. download_dir)
916+
917+
-- 创建取消标志文件
918+
local cancel_file = "/tmp/easytier_download_cancel"
919+
os.execute("rm -f " .. cancel_file)
920+
921+
-- 创建进度文件标记任务开始
922+
local f = io.open(progress_file, "w")
923+
if f then
924+
f:write(json.stringify({
925+
progress = 1,
926+
message = "Downloading...",
927+
url = "",
928+
error = false
929+
}))
930+
f:close()
931+
end
932+
933+
-- 检查是否被取消
934+
local function check_cancelled()
935+
return nixio.fs.access(cancel_file)
936+
end
937+
938+
-- 2. 循环尝试不同的镜像地址下载、解压、验证
939+
local download_success = false
940+
local download_url = ""
941+
local core_file, cli_file, web_file
942+
943+
for _, proxy in ipairs(proxies) do
944+
-- 检查是否被取消
945+
if check_cancelled() then
946+
os.execute("rm -rf " .. download_dir)
947+
os.execute("rm -f " .. progress_file)
948+
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
949+
return
950+
end
951+
952+
download_url = proxy .. "https://github.com/EasyTier/EasyTier/releases/download/" .. version .. "/easytier-linux-" .. arch .. "-" .. version .. ".zip"
953+
954+
-- 删除之前的失败文件
955+
os.execute("rm -f " .. zip_file)
956+
os.execute("rm -rf " .. download_dir .. "/extracted")
957+
958+
-- 下载
959+
if download_file(download_url, zip_file) then
960+
local stat = nixio.fs.stat(zip_file)
961+
if stat and stat.size > 10240 then
962+
-- 检查是否被取消
963+
if check_cancelled() then
964+
os.execute("rm -rf " .. download_dir)
965+
os.execute("rm -f " .. progress_file)
966+
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
967+
return
968+
end
969+
970+
-- 解压
971+
local extract_dir = download_dir .. "/extracted"
972+
os.execute("mkdir -p " .. extract_dir)
973+
local unzip_result = os.execute("cd " .. extract_dir .. " && unzip -o " .. zip_file .. " >/dev/null 2>&1")
974+
975+
if unzip_result == 0 then
976+
-- 检查是否被取消
977+
if check_cancelled() then
978+
os.execute("rm -rf " .. download_dir)
979+
os.execute("rm -f " .. progress_file)
980+
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
981+
return
982+
end
983+
984+
-- 查找解压后的目录(使用通配符)
985+
local find_cmd = "find " .. extract_dir .. " -maxdepth 1 -type d -name 'easytier-linux-*' 2>/dev/null | head -1"
986+
local handle = io.popen(find_cmd)
987+
local subdir = handle:read("*a"):match("^%s*(.-)%s*$")
988+
handle:close()
989+
990+
if subdir and subdir ~= "" then
991+
core_file = subdir .. "/easytier-core"
992+
cli_file = subdir .. "/easytier-cli"
993+
web_file = subdir .. "/easytier-web-embed"
994+
995+
local files_ok = nixio.fs.access(core_file) and nixio.fs.access(cli_file)
996+
if arch ~= "mips" and arch ~= "mipsel" then
997+
files_ok = files_ok and nixio.fs.access(web_file)
998+
end
999+
1000+
if files_ok then
1001+
-- 检查是否被取消
1002+
if check_cancelled() then
1003+
os.execute("rm -rf " .. download_dir)
1004+
os.execute("rm -f " .. progress_file)
1005+
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
1006+
return
1007+
end
1008+
1009+
-- 先赋予执行权限,再测试程序
1010+
local test_files = {core_file, cli_file}
1011+
if arch ~= "mips" and arch ~= "mipsel" then
1012+
table.insert(test_files, web_file)
1013+
end
1014+
1015+
local test_ok = true
1016+
for _, file in ipairs(test_files) do
1017+
os.execute("chmod +x " .. file)
1018+
if not test_binary(file) then
1019+
test_ok = false
1020+
break
1021+
end
1022+
end
1023+
1024+
if test_ok then
1025+
download_success = true
1026+
break
1027+
end
1028+
end
1029+
end
1030+
end
1031+
end
1032+
end
1033+
1034+
-- 删除失败的文件,继续下一个地址
1035+
os.execute("rm -f " .. zip_file)
1036+
end
1037+
1038+
if not download_success then
1039+
os.execute("rm -rf " .. download_dir)
1040+
os.execute("rm -f " .. progress_file)
1041+
luci.http.write_json({success = false, message = i18n.translate("All download attempts failed")})
1042+
return
1043+
end
1044+
1045+
-- 检查是否被取消(替换前)
1046+
if check_cancelled() then
1047+
os.execute("rm -rf " .. download_dir)
1048+
os.execute("rm -f " .. progress_file)
1049+
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
1050+
return
1051+
end
1052+
1053+
-- 3. 从UCI获取路径并安装
1054+
local uci = require "luci.model.uci".cursor()
1055+
local easytierbin = uci:get_first("easytier", "easytier", "easytierbin") or "/usr/bin/easytier-core"
1056+
local easytierwebbin = uci:get_first("easytier", "easytier", "easytierwebbin") or "/usr/bin/easytier-web"
1057+
1058+
local core_dir = easytierbin:match("^(.*/)[^/]+$") or "/usr/bin/"
1059+
1060+
local programs = {
1061+
{src = core_file, dest = easytierbin},
1062+
{src = cli_file, dest = core_dir .. "easytier-cli"}
1063+
}
1064+
1065+
if arch ~= "mips" and arch ~= "mipsel" then
1066+
table.insert(programs, {src = web_file, dest = easytierwebbin})
1067+
end
1068+
1069+
-- 安装并验证
1070+
for _, prog in ipairs(programs) do
1071+
local result = os.execute("cp " .. prog.src .. " " .. prog.dest .. " 2>/dev/null")
1072+
if result ~= 0 then
1073+
os.execute("rm -rf " .. download_dir)
1074+
os.execute("rm -f " .. progress_file)
1075+
luci.http.write_json({success = false, message = i18n.translate("Failed to install program") .. ": " .. prog.dest})
1076+
return
1077+
end
1078+
1079+
os.execute("chmod +x " .. prog.dest)
1080+
1081+
-- 再次验证安装后的程序
1082+
if not test_binary(prog.dest) then
1083+
os.execute("rm -rf " .. download_dir)
1084+
os.execute("rm -f " .. progress_file)
1085+
luci.http.write_json({success = false, message = i18n.translate("Program is incomplete or architecture mismatch")})
1086+
return
1087+
end
1088+
end
1089+
1090+
-- 7. 清理临时文件和版本缓存
1091+
os.execute("rm -rf " .. download_dir)
1092+
os.execute("rm -f " .. cancel_file)
1093+
os.execute("rm -f " .. progress_file)
1094+
nixio.fs.remove("/tmp/easytier.tag")
1095+
nixio.fs.remove("/tmp/easytierweb.tag")
1096+
1097+
luci.http.write_json({
1098+
success = true,
1099+
progress = 100,
1100+
message = i18n.translate("Download and installation completed")
1101+
})
1102+
end
1103+
1104+
function download_progress()
1105+
luci.http.prepare_content("application/json")
1106+
1107+
local progress_file = "/tmp/easytier_download_progress"
1108+
local content = safe_read_file(progress_file)
1109+
1110+
if content then
1111+
local json = require "luci.jsonc"
1112+
local progress_data = json.parse(content)
1113+
if progress_data then
1114+
luci.http.write_json({
1115+
success = true,
1116+
progress = progress_data.progress or 0,
1117+
message = progress_data.message or "",
1118+
url = progress_data.url or "",
1119+
error = progress_data.error or false
1120+
})
1121+
return
1122+
end
1123+
end
1124+
1125+
luci.http.write_json({
1126+
success = true,
1127+
progress = 0,
1128+
message = i18n.translate("Preparing..."),
1129+
url = "",
1130+
error = false
1131+
})
1132+
end
1133+
function cancel_download()
1134+
luci.http.prepare_content("application/json")
1135+
1136+
-- 创建取消标志文件
1137+
os.execute("touch /tmp/easytier_download_cancel")
1138+
1139+
luci.http.write_json({success = true})
1140+
end

0 commit comments

Comments
 (0)