From 7d3cbff79475c7e32818e7c77a208c94c165a0dd Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Tue, 29 Mar 2022 02:24:10 +0800
Subject: [PATCH 1/6] [Cloudflared] Install into base docker

---
 docker/debian-base.dockerfile | 14 +++++++++++
 extra/download-cloudflared.js | 44 +++++++++++++++++++++++++++++++++++
 package.json                  |  1 +
 3 files changed, 59 insertions(+)
 create mode 100644 extra/download-cloudflared.js

diff --git a/docker/debian-base.dockerfile b/docker/debian-base.dockerfile
index 9a8c759b..62889dc9 100644
--- a/docker/debian-base.dockerfile
+++ b/docker/debian-base.dockerfile
@@ -1,8 +1,11 @@
 # DON'T UPDATE TO node:14-bullseye-slim, see #372.
 # If the image changed, the second stage image should be changed too
 FROM node:16-buster-slim
+ARG TARGETPLATFORM
+
 WORKDIR /app
 
+# Install Curl
 # Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
 # 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 && \
@@ -10,3 +13,14 @@ RUN apt update && \
         sqlite3 iputils-ping util-linux dumb-init && \
     pip3 --no-cache-dir install apprise==0.9.7 && \
     rm -rf /var/lib/apt/lists/*
+
+# Install cloudflared
+# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
+COPY extra/download-cloudflared.js ./extra/download-cloudflared.js
+RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
+    dpkg --add-architecture arm && \
+    apt update && \
+    apt --yes --no-install-recommends install ./cloudflared.deb && \
+    rm -rf /var/lib/apt/lists/* && \
+    rm -f cloudflared.deb
+
diff --git a/extra/download-cloudflared.js b/extra/download-cloudflared.js
new file mode 100644
index 00000000..41519b7c
--- /dev/null
+++ b/extra/download-cloudflared.js
@@ -0,0 +1,44 @@
+//
+
+const http = require("https"); // or 'https' for https:// URLs
+const fs = require("fs");
+
+const platform = process.argv[2];
+
+if (!platform) {
+    console.error("No platform??");
+    process.exit(1);
+}
+
+let arch = null;
+
+if (platform === "linux/amd64") {
+    arch = "amd64";
+} else if (platform === "linux/arm64") {
+    arch = "arm64";
+} else if (platform === "linux/arm/v7") {
+    arch = "arm";
+} else {
+    console.error("Invalid platform?? " + platform);
+}
+
+const file = fs.createWriteStream("cloudflared.deb");
+get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
+
+function get(url) {
+    http.get(url, function (res) {
+        if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
+            console.log("Redirect to " + res.headers.location);
+            get(res.headers.location);
+        } else if (res.statusCode >= 200 && res.statusCode < 300) {
+            res.pipe(file);
+
+            res.on("end", function () {
+                console.log("Downloaded");
+            });
+        } else {
+            console.error(res.statusCode);
+            process.exit(1);
+        }
+    });
+}
diff --git a/package.json b/package.json
index 134271c0..24558dc5 100644
--- a/package.json
+++ b/package.json
@@ -83,6 +83,7 @@
         "jsonwebtoken": "~8.5.1",
         "jwt-decode": "^3.1.2",
         "limiter": "^2.1.0",
+        "node-cloudflared-tunnel": "~1.0.0",
         "nodemailer": "~6.6.5",
         "notp": "~2.0.3",
         "password-hash": "~1.2.2",

From 44fb2a88f290acec2ba750d678a6a45cee81d394 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Tue, 29 Mar 2022 14:48:02 +0800
Subject: [PATCH 2/6] Add cloudflared socket handler

---
 server/server.js                              |  2 ++
 .../cloudflared-socket-handler.js             | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+)
 create mode 100644 server/socket-handlers/cloudflared-socket-handler.js

diff --git a/server/server.js b/server/server.js
index 9a5e1028..602b5a86 100644
--- a/server/server.js
+++ b/server/server.js
@@ -133,6 +133,7 @@ const { statusPageSocketHandler } = require("./socket-handlers/status-page-socke
 const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
 const TwoFA = require("./2fa");
 const StatusPage = require("./model/status_page");
+const { cloudflaredSocketHandler } = require("./socket-handlers/cloudflared-socket-handler");
 
 app.use(express.json());
 
@@ -1319,6 +1320,7 @@ exports.entryPage = "dashboard";
 
         // Status Page Socket Handler for admin only
         statusPageSocketHandler(socket);
+        cloudflaredSocketHandler(socket);
         databaseSocketHandler(socket);
 
         debug("added all socket handlers");
diff --git a/server/socket-handlers/cloudflared-socket-handler.js b/server/socket-handlers/cloudflared-socket-handler.js
new file mode 100644
index 00000000..95dd4d80
--- /dev/null
+++ b/server/socket-handlers/cloudflared-socket-handler.js
@@ -0,0 +1,19 @@
+const { checkLogin } = require("../util-server");
+
+const prefix = "cloudflared_";
+
+module.exports.cloudflaredSocketHandler = (socket) => {
+
+    socket.on(prefix + "start", async (callback) => {
+        try {
+            checkLogin(socket);
+
+        } catch (error) {
+            callback({
+                ok: false,
+                msg: error.message,
+            });
+        }
+    });
+
+};

From f1f4b3b377d6c00a720fee93bf4be8ed3ac675e6 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 30 Mar 2022 01:49:45 +0800
Subject: [PATCH 3/6] Add reverse proxy setting page for controlling
 cloudflared

---
 package.json                                  |   2 +-
 .../cloudflared-socket-handler.js             |  69 +++++++++--
 src/components/settings/ReverseProxy.vue      | 111 ++++++++++++++++++
 src/mixins/socket.js                          |  13 ++
 src/pages/Settings.vue                        |   3 +
 src/router.js                                 |   5 +
 6 files changed, 194 insertions(+), 9 deletions(-)
 create mode 100644 src/components/settings/ReverseProxy.vue

diff --git a/package.json b/package.json
index 24558dc5..bb7b1e57 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,7 @@
         "jsonwebtoken": "~8.5.1",
         "jwt-decode": "^3.1.2",
         "limiter": "^2.1.0",
-        "node-cloudflared-tunnel": "~1.0.0",
+        "node-cloudflared-tunnel": "~1.0.6",
         "nodemailer": "~6.6.5",
         "notp": "~2.0.3",
         "password-hash": "~1.2.2",
diff --git a/server/socket-handlers/cloudflared-socket-handler.js b/server/socket-handlers/cloudflared-socket-handler.js
index 95dd4d80..f7c69ed2 100644
--- a/server/socket-handlers/cloudflared-socket-handler.js
+++ b/server/socket-handlers/cloudflared-socket-handler.js
@@ -1,19 +1,72 @@
-const { checkLogin } = require("../util-server");
+const { checkLogin, setSetting, setting } = require("../util-server");
+const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
+const { io } = require("../server");
 
 const prefix = "cloudflared_";
+const cloudflared = new CloudflaredTunnel();
+
+let isRunning;
+
+cloudflared.change = (running, message) => {
+    io.to("cloudflared").emit(prefix + "running", running);
+    io.to("cloudflared").emit(prefix + "message", message);
+    isRunning = running;
+
+};
+
+cloudflared.error = (errorMessage) => {
+    io.to("cloudflared").emit(prefix + "errorMessage", errorMessage);
+};
 
 module.exports.cloudflaredSocketHandler = (socket) => {
 
-    socket.on(prefix + "start", async (callback) => {
+    socket.on(prefix + "join", async () => {
         try {
             checkLogin(socket);
+            socket.join("cloudflared");
+            io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
+            io.to(socket.userID).emit(prefix + "running", isRunning);
+            io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
+        } catch (error) { }
+    });
 
-        } catch (error) {
-            callback({
-                ok: false,
-                msg: error.message,
-            });
-        }
+    socket.on(prefix + "leave", async () => {
+        try {
+            checkLogin(socket);
+            socket.leave("cloudflared");
+        } catch (error) { }
+    });
+
+    socket.on(prefix + "start", async (token) => {
+        try {
+            checkLogin(socket);
+            if (token && typeof token === "string") {
+                token = token.trim();
+
+                // try to strip out "cloudflared.exe service install"
+                let array = token.split(" ");
+                if (array.length > 1) {
+                    for (let i = 0; i < array.length - 1; i++) {
+                        if (array[i] === "install") {
+                            token = array[i + 1];
+                        }
+                    }
+                }
+
+                await setSetting("cloudflaredTunnelToken", token);
+                cloudflared.token = token;
+            } else {
+                cloudflared.token = null;
+            }
+            cloudflared.start();
+        } catch (error) { }
+    });
+
+    socket.on(prefix + "stop", async () => {
+        try {
+            checkLogin(socket);
+            cloudflared.stop();
+        } catch (error) { }
     });
 
 };
diff --git a/src/components/settings/ReverseProxy.vue b/src/components/settings/ReverseProxy.vue
new file mode 100644
index 00000000..fe41644b
--- /dev/null
+++ b/src/components/settings/ReverseProxy.vue
@@ -0,0 +1,111 @@
+<template>
+    <div>
+        <h4 class="mt-4">Cloudflare Tunnel</h4>
+
+        <div class="my-3">
+            <div>
+                cloudflared:
+                <span v-if="installed === true" class="text-primary">{{ $t("Installed") }}</span>
+                <span v-else-if="installed === false" class="text-danger">{{ $t("Not installed") }}</span>
+            </div>
+
+            <div>
+                {{ $t("Status") }}:
+                <span v-if="running" class="text-primary">{{ $t("Running") }}</span>
+                <span v-else-if="!running" class="text-danger">{{ $t("Not running") }}</span>
+            </div>
+
+            <div v-if="false">
+                {{ message }}
+            </div>
+
+            <div class="mt-3">
+                Message:
+                <textarea v-model="errorMessage" class="form-control" readonly></textarea>
+            </div>
+
+            <p v-if="installed === false">(Download cloudflared from <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/">Cloudflare Website</a>)</p>
+        </div>
+
+        <!-- If installed show token input -->
+        <div v-if="installed" class="mb-2">
+            <div class="mb-4">
+                <label class="form-label" for="cloudflareTunnelToken">
+                    Cloudflare Tunnel {{ $t("Token") }}
+                </label>
+                <HiddenInput
+                    id="cloudflareTunnelToken"
+                    v-model="cloudflareTunnelToken"
+                    autocomplete="one-time-code"
+                />
+                <div class="form-text">
+                    Don't know how to get the token? Please read the guide:<br />
+                    <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel" target="_blank">
+                        https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel
+                    </a>
+                </div>
+            </div>
+
+            <!-- Save Button -->
+            <div>
+                <button v-if="!running" class="btn btn-primary" type="submit" @click="start">
+                    {{ $t("Start") }} cloudflared
+                </button>
+
+                <button v-if="running" class="btn btn-danger" type="submit" @click="stop">
+                    {{ $t("Stop") }} cloudflared
+                </button>
+            </div>
+        </div>
+
+        <h4 class="mt-4">Other Software</h4>
+        <div>
+            For example: nginx, Apache and Traefik. <br />
+            Please read <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy</a>.
+        </div>
+    </div>
+</template>
+
+<script>
+import HiddenInput from "../../components/HiddenInput.vue";
+
+const prefix = "cloudflared_";
+
+export default {
+    components: {
+        HiddenInput,
+    },
+    data() {
+        return this.$root.cloudflared;
+    },
+    computed: {
+
+    },
+    watch: {
+
+    },
+    mounted() {
+        this.$root.getSocket().emit(prefix + "join");
+    },
+    unmounted() {
+        this.$root.getSocket().emit(prefix + "leave");
+    },
+    methods: {
+        start() {
+            this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
+        },
+        stop() {
+            this.$root.getSocket().emit(prefix + "stop");
+        },
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.logo {
+    margin: 4em 1em;
+}
+.update-link {
+    font-size: 0.9em;
+}
+</style>
diff --git a/src/mixins/socket.js b/src/mixins/socket.js
index 7d1bbea5..f2c4ff6d 100644
--- a/src/mixins/socket.js
+++ b/src/mixins/socket.js
@@ -42,6 +42,13 @@ export default {
             statusPageList: [],
             connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
             showReverseProxyGuide: true,
+            cloudflared: {
+                cloudflareTunnelToken: "",
+                installed: null,
+                running: false,
+                message: "",
+                errorMessage: "",
+            }
         };
     },
 
@@ -231,6 +238,12 @@ export default {
                 this.socket.firstConnect = false;
             });
 
+            // cloudflared
+            socket.on("cloudflared_installed", (res) => this.cloudflared.installed = res);
+            socket.on("cloudflared_running", (res) => this.cloudflared.running = res);
+            socket.on("cloudflared_message", (res) => this.cloudflared.message = res);
+            socket.on("cloudflared_errorMessage", (res) => this.cloudflared.errorMessage = res);
+            socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res);
         },
 
         storage() {
diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue
index 5b54e424..bd8ade5a 100644
--- a/src/pages/Settings.vue
+++ b/src/pages/Settings.vue
@@ -75,6 +75,9 @@ export default {
                 notifications: {
                     title: this.$t("Notifications"),
                 },
+                "reverse-proxy": {
+                    title: this.$t("Reverse Proxy"),
+                },
                 "monitor-history": {
                     title: this.$t("Monitor History"),
                 },
diff --git a/src/router.js b/src/router.js
index f59192d3..7ab75939 100644
--- a/src/router.js
+++ b/src/router.js
@@ -14,6 +14,7 @@ import Entry from "./pages/Entry.vue";
 import Appearance from "./components/settings/Appearance.vue";
 import General from "./components/settings/General.vue";
 import Notifications from "./components/settings/Notifications.vue";
+import ReverseProxy from "./components/settings/ReverseProxy.vue";
 import MonitorHistory from "./components/settings/MonitorHistory.vue";
 import Security from "./components/settings/Security.vue";
 import Backup from "./components/settings/Backup.vue";
@@ -83,6 +84,10 @@ const routes = [
                                 path: "notifications",
                                 component: Notifications,
                             },
+                            {
+                                path: "reverse-proxy",
+                                component: ReverseProxy,
+                            },
                             {
                                 path: "monitor-history",
                                 component: MonitorHistory,

From 82ea896bbc340eec09adc80872762607bc38d276 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 30 Mar 2022 11:59:49 +0800
Subject: [PATCH 4/6] Improve the workflow of cloudflared

---
 package.json                                  |  2 +-
 server/server.js                              |  5 +-
 .../cloudflared-socket-handler.js             | 33 ++++++++++---
 src/components/settings/ReverseProxy.vue      | 48 +++++++++++++++----
 src/mixins/socket.js                          |  1 +
 5 files changed, 70 insertions(+), 19 deletions(-)

diff --git a/package.json b/package.json
index bb7b1e57..641bcace 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,7 @@
         "jsonwebtoken": "~8.5.1",
         "jwt-decode": "^3.1.2",
         "limiter": "^2.1.0",
-        "node-cloudflared-tunnel": "~1.0.6",
+        "node-cloudflared-tunnel": "~1.0.7",
         "nodemailer": "~6.6.5",
         "notp": "~2.0.3",
         "password-hash": "~1.2.2",
diff --git a/server/server.js b/server/server.js
index f3203545..0734f527 100644
--- a/server/server.js
+++ b/server/server.js
@@ -133,7 +133,7 @@ const { statusPageSocketHandler } = require("./socket-handlers/status-page-socke
 const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
 const TwoFA = require("./2fa");
 const StatusPage = require("./model/status_page");
-const { cloudflaredSocketHandler } = require("./socket-handlers/cloudflared-socket-handler");
+const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart } = require("./socket-handlers/cloudflared-socket-handler");
 
 app.use(express.json());
 
@@ -1406,6 +1406,9 @@ exports.entryPage = "dashboard";
 
     initBackgroundJobs(args);
 
+    // Start cloudflared at the end if configured
+    await cloudflaredAutoStart();
+
 })();
 
 async function updateMonitorNotification(monitorID, notificationIDList) {
diff --git a/server/socket-handlers/cloudflared-socket-handler.js b/server/socket-handlers/cloudflared-socket-handler.js
index f7c69ed2..128c4788 100644
--- a/server/socket-handlers/cloudflared-socket-handler.js
+++ b/server/socket-handlers/cloudflared-socket-handler.js
@@ -1,17 +1,13 @@
-const { checkLogin, setSetting, setting } = require("../util-server");
+const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
 const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
 const { io } = require("../server");
 
 const prefix = "cloudflared_";
 const cloudflared = new CloudflaredTunnel();
 
-let isRunning;
-
 cloudflared.change = (running, message) => {
     io.to("cloudflared").emit(prefix + "running", running);
     io.to("cloudflared").emit(prefix + "message", message);
-    isRunning = running;
-
 };
 
 cloudflared.error = (errorMessage) => {
@@ -25,7 +21,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
             checkLogin(socket);
             socket.join("cloudflared");
             io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
-            io.to(socket.userID).emit(prefix + "running", isRunning);
+            io.to(socket.userID).emit(prefix + "running", cloudflared.running);
             io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
         } catch (error) { }
     });
@@ -62,11 +58,34 @@ module.exports.cloudflaredSocketHandler = (socket) => {
         } catch (error) { }
     });
 
-    socket.on(prefix + "stop", async () => {
+    socket.on(prefix + "stop", async (currentPassword, callback) => {
         try {
             checkLogin(socket);
+            await doubleCheckPassword(socket, currentPassword);
             cloudflared.stop();
+        } catch (error) {
+            callback({
+                ok: false,
+                msg: error.message,
+            });
+        }
+    });
+
+    socket.on(prefix + "removeToken", async () => {
+        try {
+            checkLogin(socket);
+            await setSetting("cloudflaredTunnelToken", "");
         } catch (error) { }
     });
 
 };
+
+module.exports.autoStart = async () => {
+    let token = await setting("cloudflaredTunnelToken");
+
+    if (token) {
+        console.log("Start cloudflared");
+        cloudflared.token = token;
+        cloudflared.start();
+    }
+};
diff --git a/src/components/settings/ReverseProxy.vue b/src/components/settings/ReverseProxy.vue
index fe41644b..2b5f65d8 100644
--- a/src/components/settings/ReverseProxy.vue
+++ b/src/components/settings/ReverseProxy.vue
@@ -19,7 +19,7 @@
                 {{ message }}
             </div>
 
-            <div class="mt-3">
+            <div v-if="errorMessage" class="mt-3">
                 Message:
                 <textarea v-model="errorMessage" class="form-control" readonly></textarea>
             </div>
@@ -37,8 +37,13 @@
                     id="cloudflareTunnelToken"
                     v-model="cloudflareTunnelToken"
                     autocomplete="one-time-code"
+                    :readonly="running"
                 />
                 <div class="form-text">
+                    <div v-if="cloudflareTunnelToken" class="mb-3">
+                        <span v-if="!running" class="remove-token" @click="removeToken">{{ $t("Remove Token") }}</span>
+                    </div>
+
                     Don't know how to get the token? Please read the guide:<br />
                     <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel" target="_blank">
                         https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel
@@ -46,15 +51,31 @@
                 </div>
             </div>
 
-            <!-- Save Button -->
             <div>
                 <button v-if="!running" class="btn btn-primary" type="submit" @click="start">
                     {{ $t("Start") }} cloudflared
                 </button>
 
-                <button v-if="running" class="btn btn-danger" type="submit" @click="stop">
+                <button v-if="running" class="btn btn-danger" type="submit" @click="$refs.confirmStop.show();">
                     {{ $t("Stop") }} cloudflared
                 </button>
+
+                <Confirm ref="confirmStop" btn-style="btn-danger" :yes-text="$t('Stop') + ' cloudflared'" :no-text="$t('Cancel')" @yes="stop">
+                    The current connection may be lost if you are connecting Cloudflare Tunnel. Are you sure want to stop it? Type your password to confirm it.
+
+                    <div class="mt-3">
+                        <label for="current-password2" class="form-label">
+                            {{ $t("Current Password") }}
+                        </label>
+                        <input
+                            id="current-password2"
+                            v-model="currentPassword"
+                            type="password"
+                            class="form-control"
+                            required
+                        />
+                    </div>
+                </Confirm>
             </div>
         </div>
 
@@ -68,14 +89,17 @@
 
 <script>
 import HiddenInput from "../../components/HiddenInput.vue";
+import Confirm from "../Confirm.vue";
 
 const prefix = "cloudflared_";
 
 export default {
     components: {
         HiddenInput,
+        Confirm
     },
     data() {
+        // See /src/mixins/socket.js
         return this.$root.cloudflared;
     },
     computed: {
@@ -84,7 +108,7 @@ export default {
     watch: {
 
     },
-    mounted() {
+    created() {
         this.$root.getSocket().emit(prefix + "join");
     },
     unmounted() {
@@ -95,17 +119,21 @@ export default {
             this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
         },
         stop() {
-            this.$root.getSocket().emit(prefix + "stop");
+            this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
+                this.$root.toastRes(res);
+            });
         },
+        removeToken() {
+            this.$root.getSocket().emit(prefix + "removeToken");
+            this.cloudflareTunnelToken = "";
+        }
     }
 };
 </script>
 
 <style lang="scss" scoped>
-.logo {
-    margin: 4em 1em;
-}
-.update-link {
-    font-size: 0.9em;
+.remove-token {
+    text-decoration: underline;
+    cursor: pointer;
 }
 </style>
diff --git a/src/mixins/socket.js b/src/mixins/socket.js
index f2c4ff6d..d8b1ad22 100644
--- a/src/mixins/socket.js
+++ b/src/mixins/socket.js
@@ -48,6 +48,7 @@ export default {
                 running: false,
                 message: "",
                 errorMessage: "",
+                currentPassword: "",
             }
         };
     },

From 71be030733bcbc582a1305ec8700cdc31571fb64 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 30 Mar 2022 18:52:10 +0800
Subject: [PATCH 5/6] Add package-lock.json and minor words

---
 package-lock.json                        | 21 +++++++++++++++++++--
 src/components/settings/ReverseProxy.vue |  2 +-
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 1d30ce07..da15c8c3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "uptime-kuma",
-    "version": "1.12.1",
+    "version": "1.13.1",
     "lockfileVersion": 2,
     "requires": true,
     "packages": {
         "": {
             "name": "uptime-kuma",
-            "version": "1.12.1",
+            "version": "1.13.1",
             "license": "MIT",
             "dependencies": {
                 "@fortawesome/fontawesome-svg-core": "~1.2.36",
@@ -36,6 +36,7 @@
                 "jsonwebtoken": "~8.5.1",
                 "jwt-decode": "^3.1.2",
                 "limiter": "^2.1.0",
+                "node-cloudflared-tunnel": "~1.0.7",
                 "nodemailer": "~6.6.5",
                 "notp": "~2.0.3",
                 "password-hash": "~1.2.2",
@@ -11160,6 +11161,14 @@
             "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
             "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
         },
+        "node_modules/node-cloudflared-tunnel": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.7.tgz",
+            "integrity": "sha512-2xKygxFNZZPktF73dvJTNPjFkK4ThOPMpsZf885Iqq5Eie/vxk5mFH8a8dLlDWZYYpGDc699qToJTOlrTqd3Eg==",
+            "dependencies": {
+                "command-exists": "^1.2.9"
+            }
+        },
         "node_modules/node-fetch": {
             "version": "2.6.7",
             "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -24071,6 +24080,14 @@
             "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
             "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
         },
+        "node-cloudflared-tunnel": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.7.tgz",
+            "integrity": "sha512-2xKygxFNZZPktF73dvJTNPjFkK4ThOPMpsZf885Iqq5Eie/vxk5mFH8a8dLlDWZYYpGDc699qToJTOlrTqd3Eg==",
+            "requires": {
+                "command-exists": "^1.2.9"
+            }
+        },
         "node-fetch": {
             "version": "2.6.7",
             "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
diff --git a/src/components/settings/ReverseProxy.vue b/src/components/settings/ReverseProxy.vue
index 2b5f65d8..d35d5353 100644
--- a/src/components/settings/ReverseProxy.vue
+++ b/src/components/settings/ReverseProxy.vue
@@ -61,7 +61,7 @@
                 </button>
 
                 <Confirm ref="confirmStop" btn-style="btn-danger" :yes-text="$t('Stop') + ' cloudflared'" :no-text="$t('Cancel')" @yes="stop">
-                    The current connection may be lost if you are connecting Cloudflare Tunnel. Are you sure want to stop it? Type your password to confirm it.
+                    The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.
 
                     <div class="mt-3">
                         <label for="current-password2" class="form-label">

From b72a2d350fa82b279182e08af33974c6414f58cb Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 30 Mar 2022 20:08:26 +0800
Subject: [PATCH 6/6] Set cloudflared token from env var or arg

---
 package-lock.json                             | 14 +++++------
 package.json                                  |  2 +-
 server/server.js                              |  3 ++-
 .../cloudflared-socket-handler.js             | 23 +++++++------------
 4 files changed, 18 insertions(+), 24 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index da15c8c3..e4fa75ce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -36,7 +36,7 @@
                 "jsonwebtoken": "~8.5.1",
                 "jwt-decode": "^3.1.2",
                 "limiter": "^2.1.0",
-                "node-cloudflared-tunnel": "~1.0.7",
+                "node-cloudflared-tunnel": "~1.0.9",
                 "nodemailer": "~6.6.5",
                 "notp": "~2.0.3",
                 "password-hash": "~1.2.2",
@@ -11162,9 +11162,9 @@
             "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
         },
         "node_modules/node-cloudflared-tunnel": {
-            "version": "1.0.7",
-            "resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.7.tgz",
-            "integrity": "sha512-2xKygxFNZZPktF73dvJTNPjFkK4ThOPMpsZf885Iqq5Eie/vxk5mFH8a8dLlDWZYYpGDc699qToJTOlrTqd3Eg==",
+            "version": "1.0.9",
+            "resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.9.tgz",
+            "integrity": "sha512-d0mhIM5P2ldE2yHChehC6EvnpFCkifWRzWrW81gVWdcCWqNcyISXuDdOYzRW5mwmjWuT6WNtLJoGQ84uqS4EmA==",
             "dependencies": {
                 "command-exists": "^1.2.9"
             }
@@ -24081,9 +24081,9 @@
             "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
         },
         "node-cloudflared-tunnel": {
-            "version": "1.0.7",
-            "resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.7.tgz",
-            "integrity": "sha512-2xKygxFNZZPktF73dvJTNPjFkK4ThOPMpsZf885Iqq5Eie/vxk5mFH8a8dLlDWZYYpGDc699qToJTOlrTqd3Eg==",
+            "version": "1.0.9",
+            "resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.9.tgz",
+            "integrity": "sha512-d0mhIM5P2ldE2yHChehC6EvnpFCkifWRzWrW81gVWdcCWqNcyISXuDdOYzRW5mwmjWuT6WNtLJoGQ84uqS4EmA==",
             "requires": {
                 "command-exists": "^1.2.9"
             }
diff --git a/package.json b/package.json
index 641bcace..3857aaac 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,7 @@
         "jsonwebtoken": "~8.5.1",
         "jwt-decode": "^3.1.2",
         "limiter": "^2.1.0",
-        "node-cloudflared-tunnel": "~1.0.7",
+        "node-cloudflared-tunnel": "~1.0.9",
         "nodemailer": "~6.6.5",
         "notp": "~2.0.3",
         "password-hash": "~1.2.2",
diff --git a/server/server.js b/server/server.js
index 0734f527..a3777c8c 100644
--- a/server/server.js
+++ b/server/server.js
@@ -91,6 +91,7 @@ const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.p
 const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined;
 const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined;
 const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false;
+const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
 
 // 2FA / notp verification defaults
 const twofa_verification_opts = {
@@ -1407,7 +1408,7 @@ exports.entryPage = "dashboard";
     initBackgroundJobs(args);
 
     // Start cloudflared at the end if configured
-    await cloudflaredAutoStart();
+    await cloudflaredAutoStart(cloudflaredToken);
 
 })();
 
diff --git a/server/socket-handlers/cloudflared-socket-handler.js b/server/socket-handlers/cloudflared-socket-handler.js
index 128c4788..3f4a26e5 100644
--- a/server/socket-handlers/cloudflared-socket-handler.js
+++ b/server/socket-handlers/cloudflared-socket-handler.js
@@ -37,19 +37,6 @@ module.exports.cloudflaredSocketHandler = (socket) => {
         try {
             checkLogin(socket);
             if (token && typeof token === "string") {
-                token = token.trim();
-
-                // try to strip out "cloudflared.exe service install"
-                let array = token.split(" ");
-                if (array.length > 1) {
-                    for (let i = 0; i < array.length - 1; i++) {
-                        if (array[i] === "install") {
-                            token = array[i + 1];
-                        }
-                    }
-                }
-
-                await setSetting("cloudflaredTunnelToken", token);
                 cloudflared.token = token;
             } else {
                 cloudflared.token = null;
@@ -80,8 +67,14 @@ module.exports.cloudflaredSocketHandler = (socket) => {
 
 };
 
-module.exports.autoStart = async () => {
-    let token = await setting("cloudflaredTunnelToken");
+module.exports.autoStart = async (token) => {
+    if (!token) {
+        token = await setting("cloudflaredTunnelToken");
+    } else {
+        // Override the current token via args or env var
+        await setSetting("cloudflaredTunnelToken", token);
+        console.log("Use cloudflared token from args or env var");
+    }
 
     if (token) {
         console.log("Start cloudflared");