From ddce8f0cb06016d20c4f1910693d5258f95ff275 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 28 Jan 2023 19:00:13 +0800
Subject: [PATCH] Fix plugin installation

---
 docker/alpine-base.dockerfile             |  2 +-
 docker/debian-base.dockerfile             |  2 +-
 server/plugins-manager.js                 | 37 ++++++++++++++++++-----
 server/socket-handlers/plugins-handler.js |  8 +++--
 src/components/settings/Plugins.vue       |  2 +-
 5 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/docker/alpine-base.dockerfile b/docker/alpine-base.dockerfile
index 82bc7bb05..276d6e450 100644
--- a/docker/alpine-base.dockerfile
+++ b/docker/alpine-base.dockerfile
@@ -3,6 +3,6 @@ FROM node:16-alpine3.12
 WORKDIR /app
 
 # Install apprise, iputils for non-root ping, setpriv
-RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
+RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib git && \
     pip3 --no-cache-dir install apprise==1.2.1 && \
     rm -rf /root/.cache
diff --git a/docker/debian-base.dockerfile b/docker/debian-base.dockerfile
index d94b4c7fe..026189c47 100644
--- a/docker/debian-base.dockerfile
+++ b/docker/debian-base.dockerfile
@@ -10,7 +10,7 @@ WORKDIR /app
 # Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
 RUN apt update && \
     apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
-        sqlite3 iputils-ping util-linux dumb-init && \
+        sqlite3 iputils-ping util-linux dumb-init git && \
     pip3 --no-cache-dir install apprise==1.2.1 && \
     rm -rf /var/lib/apt/lists/* && \
     apt --yes autoremove
diff --git a/server/plugins-manager.js b/server/plugins-manager.js
index e48c53c89..674ab9691 100644
--- a/server/plugins-manager.js
+++ b/server/plugins-manager.js
@@ -72,6 +72,12 @@ class PluginsManager {
      * @param {string} name Directory name, also known as plugin unique name
      */
     downloadPlugin(repoURL, name) {
+        if (fs.existsSync(this.pluginsDir + name)) {
+            log.info("plugin", "Plugin folder already exists? Removing...");
+            fs.rmSync(this.pluginsDir + name, {
+                recursive: true
+            });
+        }
         log.info("plugin", "Installing plugin: " + name + " " + repoURL);
         let result = Git.clone(repoURL, this.pluginsDir, name);
         log.info("plugin", "Install result: " + result);
@@ -115,13 +121,19 @@ class PluginsManager {
      * @returns {Promise<[]>}
      */
     async fetchPluginList() {
-        const res = await axios.get("https://uptime.kuma.pet/c/plugins.json");
-        const list = res.data.pluginList;
+        let remotePluginList;
+        try {
+            const res = await axios.get("https://uptime.kuma.pet/c/plugins.json");
+            remotePluginList = res.data.pluginList;
+        } catch (e) {
+            log.error("plugin", "Failed to fetch plugin list: " + e.message);
+            remotePluginList = [];
+        }
 
         for (let plugin of this.pluginList) {
             let find = false;
             // Try to merge
-            for (let remotePlugin of list) {
+            for (let remotePlugin of remotePluginList) {
                 if (remotePlugin.name === plugin.info.name) {
                     find = true;
                     remotePlugin.installed = true;
@@ -136,17 +148,17 @@ class PluginsManager {
             // Local plugin
             if (!find) {
                 plugin.info.local = true;
-                list.push(plugin.info);
+                remotePluginList.push(plugin.info);
             }
         }
 
         // Sort Installed first, then sort by name
-        return list.sort((a, b) => {
+        return remotePluginList.sort((a, b) => {
             if (a.installed === b.installed) {
-                if ( a.fullName < b.fullName ) {
+                if (a.fullName < b.fullName) {
                     return -1;
                 }
-                if ( a.fullName > b.fullName ) {
+                if (a.fullName > b.fullName) {
                     return 1;
                 }
                 return 0;
@@ -191,15 +203,24 @@ class PluginWrapper {
         let indexFile = this.pluginDir + "/index.js";
         let packageJSON = this.pluginDir + "/package.json";
 
+        log.info("plugin", "Installing dependencies");
+
         if (fs.existsSync(indexFile)) {
             // Install dependencies
-            childProcess.execSync("npm install", {
+            let result = childProcess.spawnSync("npm", [ "install" ], {
                 cwd: this.pluginDir,
                 env: {
+                    ...process.env,
                     PLAYWRIGHT_BROWSERS_PATH: "../../browsers",    // Special handling for read-browser-monitor
                 }
             });
 
+            if (result.stdout) {
+                log.info("plugin", "Install dependencies result: " + result.stdout.toString("utf-8"));
+            } else {
+                log.warn("plugin", "Install dependencies result: no output");
+            }
+
             this.pluginClass = require(path.join(process.cwd(), indexFile));
 
             let pluginClassType = typeof this.pluginClass;
diff --git a/server/socket-handlers/plugins-handler.js b/server/socket-handlers/plugins-handler.js
index 4ee712c79..533da309b 100644
--- a/server/socket-handlers/plugins-handler.js
+++ b/server/socket-handlers/plugins-handler.js
@@ -1,5 +1,6 @@
 const { checkLogin } = require("../util-server");
-const { PluginManager } = require("../plugins-manager");
+const { PluginsManager } = require("../plugins-manager");
+const { log } = require("../../src/util.js");
 
 /**
  * Handlers for plugins
@@ -15,7 +16,9 @@ module.exports.pluginsHandler = (socket, server) => {
         try {
             checkLogin(socket);
 
-            if (PluginManager.disable) {
+            log.debug("plugin", "PluginManager.disable: " + PluginsManager.disable);
+
+            if (PluginsManager.disable) {
                 throw new Error("Plugin Disabled: In order to enable plugin feature, you need to use the default data directory: ./data/");
             }
 
@@ -25,6 +28,7 @@ module.exports.pluginsHandler = (socket, server) => {
                 pluginList,
             });
         } catch (error) {
+            log.warn("plugin", "Error: " + error.message);
             callback({
                 ok: false,
                 msg: error.message,
diff --git a/src/components/settings/Plugins.vue b/src/components/settings/Plugins.vue
index ca39e7adc..614034fcb 100644
--- a/src/components/settings/Plugins.vue
+++ b/src/components/settings/Plugins.vue
@@ -48,7 +48,7 @@ export default {
                     this.remotePluginList = res.pluginList;
                     this.remotePluginListMsg = "";
                 } else {
-                    this.remotePluginListMsg = this.$t("loadingError") + " " + res.message;
+                    this.remotePluginListMsg = this.$t("loadingError") + " " + res.msg;
                 }
             });
         }