Sfoglia il codice sorgente

Added update restart func

Toby Chui 3 anni fa
parent
commit
55ca192292

+ 3 - 3
build.sh

@@ -18,9 +18,9 @@ GOOS=linux GOARCH=arm64 go build
 mv arozos ../aroz_online_autorelease/arozos_linux_arm64
 
 #Currently not CGO is required to build arozos. May remove dependencies later in the future
-#echo "Building OpenWRT"
-#GOOS=linux GOARCH=mipsle GOMIPS=softfloat CGO_ENABLED=0 go build
-#mv arozos ../aroz_online_autorelease/arozos_linux_mipsle
+echo "Building OpenWRT"
+GOOS=linux GOARCH=mipsle GOMIPS=softfloat CGO_ENABLED=0 go build
+mv arozos ../aroz_online_autorelease/arozos_linux_mipsle
 
 echo "Building windows"
 #GOOS=windows GOARCH=386 go build

+ 85 - 6
mod/updates/handler.go

@@ -4,7 +4,9 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"time"
 
+	"github.com/gorilla/websocket"
 	"imuslab.com/arozos/mod/common"
 )
 
@@ -32,21 +34,98 @@ func HandleUpdateCheckSize(w http.ResponseWriter, r *http.Request) {
 }
 
 func HandleUpdateDownloadRequest(w http.ResponseWriter, r *http.Request) {
-	webpack, err := common.Mv(r, "webpack", true)
+	webpack, err := common.Mv(r, "webpack", false)
 	if err != nil {
 		common.SendErrorResponse(w, "Invalid or empty webpack download URL")
 		return
 	}
 
-	binary, err := common.Mv(r, "binary", true)
+	binary, err := common.Mv(r, "binary", false)
 	if err != nil {
 		common.SendErrorResponse(w, "Invalid or empty binary download URL")
 		return
 	}
 
-	err = DownloadUpdatesFromURL(binary, webpack, func(stage float64, progress float64, statusText string) {
-		fmt.Println(stage, progress, statusText)
-	})
+	//Update the connection to websocket
+	requireWebsocket, _ := common.Mv(r, "ws", false)
+	if requireWebsocket == "true" {
+		//Upgrade to websocket
+		var upgrader = websocket.Upgrader{}
+		upgrader.CheckOrigin = func(r *http.Request) bool { return true }
+		c, err := upgrader.Upgrade(w, r, nil)
+		if err != nil {
+			common.SendErrorResponse(w, "Upgrade websocket failed: "+err.Error())
+			return
+		}
+
+		type Progress struct {
+			Stage      int
+			Progress   float64
+			StatusText string
+		}
+		err = DownloadUpdatesFromURL(binary, webpack, func(stage int, progress float64, statusText string) {
+			thisProgress := Progress{
+				Stage:      stage,
+				Progress:   progress,
+				StatusText: statusText,
+			}
+			js, _ := json.Marshal(thisProgress)
+			c.WriteMessage(1, js)
+		})
+		if err != nil {
+			//Finish with error
+			c.WriteMessage(1, []byte("{\"error\":\""+err.Error()+"\""))
+		} else {
+			//Done without error
+			c.WriteMessage(1, []byte("OK"))
+		}
+
+		//Close WebSocket connection after finished
+		c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
+		c.Close()
+
+	} else {
+		//Just download and return ok after finish
+		err = DownloadUpdatesFromURL(binary, webpack, func(stage int, progress float64, statusText string) {
+			fmt.Println("Downloading Update, Stage: ", stage, " Progress: ", progress, " Status: ", statusText)
+		})
+		if err != nil {
+			common.SendErrorResponse(w, err.Error())
+		} else {
+			common.SendOK(w)
+		}
+	}
+
+}
+
+/*
+func HandleLauncherRestart(w http.ResponseWriter, r *http.Request) {
+	//Check if there is a launcher listening to port 25576
+	resp, err := http.Get("http://127.0.0.1:25576/chk")
+	if err != nil {
+		common.SendErrorResponse(w, "No launcher found. Unable to restart")
+		return
+	}
+
+	content, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		common.SendErrorResponse(w, "Read launcher response failed")
+		return
+	}
+
+	execute, _ := common.Mv(r, "exec", true)
+	if execute == "true" && r.Method == http.MethodPost {
+		//Do the update
+		log.Println("REQUESTING LAUNCHER FOR UPDATE RESTART")
+
+		common.SendOK(w)
+	} else if execute == "true" {
+		w.WriteHeader(http.StatusMethodNotAllowed)
+		w.Write([]byte("405 - Method Not Allowed"))
+	} else {
+		//Return the launcher message
+		common.SendTextResponse(w, string(content))
+	}
 
-	common.SendOK(w)
 }
+*/

+ 1 - 1
mod/updates/internal.go

@@ -60,7 +60,7 @@ func downloadFile(url string, dest string) error {
 	return nil
 }
 
-func extractTarGz(gzipStream io.Reader, unzipPath string, progressUpdateFunction func(float64, float64, string)) error {
+func extractTarGz(gzipStream io.Reader, unzipPath string, progressUpdateFunction func(int, float64, string)) error {
 	uncompressedStream, err := gzip.NewReader(gzipStream)
 	if err != nil {
 		return err

+ 1 - 1
mod/updates/updates.go

@@ -8,7 +8,7 @@ import (
 )
 
 //Download updates from given URL, return real time progress of stage (int),  progress (int) and status text (string)
-func DownloadUpdatesFromURL(binaryURL string, webpackURL string, progressUpdateFunction func(float64, float64, string)) error {
+func DownloadUpdatesFromURL(binaryURL string, webpackURL string, progressUpdateFunction func(int, float64, string)) error {
 	//Create the update download folder
 	os.RemoveAll("./updates")
 	os.MkdirAll("./updates", 0755)

+ 33 - 0
system.info.go

@@ -2,11 +2,13 @@ package main
 
 import (
 	"encoding/json"
+	"io/ioutil"
 	"log"
 	"net/http"
 	"runtime"
 	"time"
 
+	"imuslab.com/arozos/mod/common"
 	info "imuslab.com/arozos/mod/info/hardwareinfo"
 	usage "imuslab.com/arozos/mod/info/usageinfo"
 	prout "imuslab.com/arozos/mod/prouter"
@@ -122,6 +124,37 @@ func SystemInfoInit() {
 	//Handle updates
 	adminRouter.HandleFunc("/system/update/download", updates.HandleUpdateDownloadRequest)
 	adminRouter.HandleFunc("/system/update/checksize", updates.HandleUpdateCheckSize)
+
+	//Special function for handling launcher restart, must be in this scope
+	adminRouter.HandleFunc("/system/update/restart", func(w http.ResponseWriter, r *http.Request) {
+		//Check if there is a launcher listening to port 25576
+		resp, err := http.Get("http://127.0.0.1:25576/chk")
+		if err != nil {
+			common.SendErrorResponse(w, "No launcher found. Unable to restart")
+			return
+		}
+
+		content, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			common.SendErrorResponse(w, "Read launcher response failed")
+			return
+		}
+
+		execute, _ := common.Mv(r, "exec", true)
+		if execute == "true" && r.Method == http.MethodPost {
+			//Do the update
+			log.Println("REQUESTING LAUNCHER FOR UPDATE RESTART")
+			executeShutdownSequence()
+			common.SendOK(w)
+		} else if execute == "true" {
+			w.WriteHeader(http.StatusMethodNotAllowed)
+			w.Write([]byte("405 - Method Not Allowed"))
+		} else {
+			//Return the launcher message
+			common.SendTextResponse(w, string(content))
+		}
+
+	})
 }
 
 func InfoHandleGetRuntimeInfo(w http.ResponseWriter, r *http.Request) {

+ 216 - 2
web/SystemAO/updates/index.html

@@ -7,6 +7,7 @@
     <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
     <script type="text/javascript" src="../../script/jquery.min.js"></script>
     <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
+    <script type="text/javascript" src="../../script/ao_module.js"></script>
 </head>
 <body>
     <div class="ui container">
@@ -19,19 +20,99 @@
                 </div>
             </h3>
             <div class="ui divider"></div>
+            <!-- Status Messages-->
+            <div id="warning" class="ui negative visible icon message">
+                <i class="red exclamation circle icon"></i>
+                <div class="content">
+                    <div class="header">
+                        WARNING
+                    </div>
+                    <p>Backup all the important files before performing system update</p>
+                </div>
+            </div>
+            <div id="checking" class="ui icon blue message" style="display:none;">
+                <i class="notched circle loading icon"></i>
+                <div class="content">
+                    <div class="header">
+                    Connectiong to Download Server
+                    </div>
+                    <p>We're fetching that content for you.</p>
+                </div>
+            </div>
+            <div id="confirmDownload" class="ui icon yellow message" style="display:none;">
+                <i class="yellow exclamation icon"></i>
+                <div class="content">
+                    <div class="header">
+                    Confirm Update?
+                    </div>
+                    <p><span>This updates will take up </span><span id="spaceEst" style="font-weight: bold;"></span> <span>of space.</span></p>
+                    <p><b>Please make sure you have back up all important files and config files before you proceeds.</b></p>
+                    <div class="ui buttons">
+                        <button class="ui yellow button" onclick="confirmURLUpdate();">Confirm Update</button>
+                        <button class="ui button" onclick="cancelUpdateStatus();"><i class="ui remove icon"></i> Cancel Update</button>
+                    </div>
+                </div>
+            </div>
+            <div id="downloading" class="ui icon blue message" style="display:none;">
+                <i class="notched circle loading icon"></i>
+                <div class="content">
+                    <div class="header" id="downloadStatusText">
+                        Starting Download Session
+                    </div>
+                    <br>
+                    <p id="fallbackmodeExp" style="display:none;">You are seeing this message is because the websocket connection to your host failed to establish. No worry, updates can still be done using AJAX fallback mode, just without the real time progress updates. <br>Please wait until the download complete before closing this page.</br></p>
+                    <div class="ui blue active progress">
+                        <div id="downloadProgressBar" class="bar">
+                            <div class="progress">0.00%</div>
+                        </div>
+                      </div>
+                </div>
+            </div>
+            <div id="success" class="ui icon green message" style="display:none;">
+                <i class="green checkmark icon"></i>
+                <div class="content">
+                    <div class="header" id="downloadStatusText">
+                        Update Download Succeed
+                    </div>
+                    <p>Restart ArozOS using the launcher or apply manual update files overwrite to finish the update process.</p>
+                </div>
+            </div>
+            <div id="failed" class="ui icon red message" style="display:none;">
+                <i class="red remove icon"></i>
+                <div class="content">
+                    <div class="header" id="downloadStatusText">
+                        Update Download Failed
+                    </div>
+                    <p id="failedErrorMessage">Unknown Error Occured</p>
+                </div>
+            </div>
+            <div id="restartPanel" class="ui inversed red message" style="display:none;">
+                <div class="header" >
+                    Launcher Exists: <span id="launcherName"></span>
+                </div>
+                <p>You will need to restart ArozOS in order to apply the updates.</p>
+                <p>Warning! Make sure you have physical access to this ArozOS Host before pressing the restart button.<br> Update failure might require a manual restart of the ArozOS system host and its launcher.</p>
+                <button class="ui red button" onclick="restartArozOS();">RESTART ArozOS WITH LAUNCHER</button>
+            </div>
+            <!-- End of Status Messages -->
             <h4>Update via Download</h4>
             <p>Binary Executable Download URL</p>
             <div class="ui fluid input">
-                <input type="text" placeholder="Binary Download URL">
+                <input id="burl" type="text" placeholder="Binary Download URL">
             </div>
             <small>Usually with pattern like: arozos_{platform}_{cpu_arch}</small>
             <br><br>
             <p>Webpack Download URL</p>
             <div class="ui fluid input">
-                <input type="text" placeholder="Webpack Download URL">
+                <input id="wurl" type="text" placeholder="Webpack Download URL">
             </div>
             <small>Usually with named as: webpack.tar.gz</small>
+            <br><br>
+            <button class="ui red button" onclick="updateViaURL();"><i class="cloud upload icon"></i> Execute Update</button>
+            
             <div class="ui divider"></div>
+
+
             <div class="ui message">
                 <h4><i class="info circle icon"></i>Update Instruction</h4>
                 <p>To update your ArozOS system, you will need two files: A compiled binary of the newer version of ArozOS and the webpack compress file in .tar.gz format.
@@ -75,6 +156,139 @@
     </div>
     <script>
         $(".accordion").accordion();
+
+        function updateViaURL(){
+            let binaryDownloadURL = $("#burl").val().trim();
+            let webpackDownloadURL = $("#wurl").val().trim();
+            if (binaryDownloadURL == "" || webpackDownloadURL == ""){
+                alert("Invalid or Empty URL given");
+                return
+            }
+            //Check space need
+            $("#checking").slideDown("fast");
+            $("#warning").slideUp("fast");
+            $.get(`/system/update/checksize?webpack=${webpackDownloadURL}&binary=${binaryDownloadURL}`, function(data){
+                if (data.error != undefined){
+                    cancelUpdateStatus();
+                    alert("Update failed: " + data.error)
+                }else{
+                    let totalDownloadBytes = data[0] + data[1];
+                    $("#spaceEst").text(ao_module_utils.formatBytes(totalDownloadBytes, 2));
+                    $("#confirmDownload").slideDown("fast");
+                    $("#checking").slideUp("fast");
+                    console.log(data);
+                }
+               
+            })
+        }
+
+        function confirmURLUpdate(){
+            let binaryDownloadURL = $("#burl").val().trim();
+            let webpackDownloadURL = $("#wurl").val().trim();
+            if (binaryDownloadURL == "" || webpackDownloadURL == ""){
+                alert("Invalid or Empty URL given");
+                return
+            }
+
+            var wsroot = ao_module_utils.getWebSocketEndpoint();
+            var requestEndpoint = wsroot + `/system/update/download?webpack=${webpackDownloadURL}&binary=${binaryDownloadURL}&ws=true`
+            console.log("Connecting to: ", requestEndpoint);
+
+            hideAllStatus();
+            $("#downloading").slideDown("fast");
+
+            let socket = new WebSocket(requestEndpoint);
+
+            socket.onopen = function(e) {
+                $("#downloadStatusText").text("Download Started");
+            };
+
+            socket.onmessage = function(event) {
+                let status = JSON.parse(event.data);
+                if (status.error !== undefined){
+                    hideAllStatus();
+                    $("#failed").slideDown();
+                    $("#failedErrorMessage").text(status.error);
+                }else{
+                    //Progressing
+                    let progressText = status.Progress.toFixed(2) + "%";
+                    $("#downloadProgressBar").find(".progress").text(progressText);
+                    $("#downloadProgressBar").css("width",status.Progress + "%");
+                    $("#downloadStatusText").text(`[${status.Stage}] ${status.StatusText}`);
+                }
+                console.log(event.data);
+            };
+
+            socket.onclose = function(event) {
+                hideAllStatus();
+                $("#success").slideDown();
+                checkLauncher();
+            };
+
+            socket.onerror = function(error) {
+                console.log("Websocket Connection Error: ", error, " Running in fallback mode")
+                downloadUpdateFallbackMode(binaryDownloadURL, webpackDownloadURL);
+            };
+        }
+
+        function downloadUpdateFallbackMode(binaryDownloadURL, webpackDownloadURL){
+            hideAllStatus();
+            $("#downloading").slideDown("fast");
+            $("#downloadStatusText").text("Waiting for Download Complete (Fallback Mode)");
+            $("#fallbackmodeExp").show();
+            $("#downloadProgressBar").find(".progress").text("Downloading");
+            $("#downloadProgressBar").css("width", "50%");
+            $.get(`../../system/update/download?webpack=${webpackDownloadURL}&binary=${binaryDownloadURL}`, function(data){
+                if (data.error !== undefined){
+                    hideAllStatus();
+                    $("#failed").slideDown();
+                    $("#failedErrorMessage").text(data.error);
+                }else{
+                    hideAllStatus();
+                    $("#success").slideDown();
+                    checkLauncher();
+                }
+            })
+        }
+
+        function checkLauncher(){
+            $.get("/system/update/restart", function(data){
+                if (data.error !== undefined){
+                    //No launcher
+                }else{
+                    $("#launcherName").text(data);
+                }
+                console.log("Launcher check: ", data);
+            })
+            $("#restartPanel").show();
+        }
+
+        function restartArozOS(){
+            if (confirm("CONFIRM RESTART?")){
+                $.ajax({
+                    url: "/system/update/restart",
+                    method: "POST",
+                    data: {exec: true},
+                    success: function(data){
+                        console.log(data);
+                    }
+                });
+            }
+        }
+
+        function cancelUpdateStatus(){
+            hideAllStatus();
+            $("#warning").slideDown("fast");
+        }
+
+        function hideAllStatus(){
+            $("#warning").slideUp("fast");
+            $("#confirmDownload").slideUp("fast");
+            $("#checking").slideUp("fast");
+            $("#downloading").slideUp("fast");
+            $("#success").slideUp("fast");
+            $("#failed").slideUp("fast");
+        }
     </script>
 </body>
 </html>  

+ 2 - 2
web/script/ao_module.js

@@ -952,7 +952,7 @@ class ao_module_utils{
         if (location.protocol !== 'https:') {
             protocol = "ws://";
         }
-        var port = window.location.port;
+        let port = window.location.port;
         if (window.location.port == ""){
             if (location.protocol !== 'https:') {
                 port = "80";
@@ -961,7 +961,7 @@ class ao_module_utils{
             }
             
         }
-        wsept = (protocol + window.location.hostname + ":" + port);
+        let wsept = (protocol + window.location.hostname + ":" + port);
         return wsept;
     }