@@ -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
7780end
7881
7982function act_status ()
@@ -749,3 +752,389 @@ function toggle_web()
749752 luci .http .prepare_content (" application/json" )
750753 luci .http .write_json ({success = true })
751754end
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