From 33ce0ef02c8ad1e660d253020cb8d01d9e829188 Mon Sep 17 00:00:00 2001 From: Nelson Chan <3271800+chakflying@users.noreply.github.com> Date: Tue, 21 Nov 2023 23:56:17 +0800 Subject: [PATCH 01/11] Fix: Improve error message on timeout (#4054) * Fix: Improve error message on timeout * Chore: Format --- server/model/monitor.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index f7cbff30..8958e3d8 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -938,7 +938,11 @@ class Monitor extends BeanModel { } catch (error) { - bean.msg = error.message; + if (error?.name === "CanceledError") { + bean.msg = `timeout by AbortSignal (${this.timeout}s)`; + } else { + bean.msg = error.message; + } // If UP come in here, it must be upside down mode // Just reset the retries From bf58838b891a9bec92f611e50ab6a14f04de68b6 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 22 Nov 2023 16:03:03 +0800 Subject: [PATCH 02/11] +10 seconds for Abort signal (#4053) * Debug only * Remove debug --- server/model/monitor.js | 2 +- server/util-server.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 8958e3d8..ec40115c 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -483,7 +483,7 @@ class Monitor extends BeanModel { validateStatus: (status) => { return checkStatusCode(status, this.getAcceptedStatuscodes()); }, - signal: axiosAbortSignal(this.timeout * 1000), + signal: axiosAbortSignal((this.timeout + 10) * 1000), }; if (bodyValue) { diff --git a/server/util-server.js b/server/util-server.js index 329810be..b9832af3 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -1141,7 +1141,6 @@ module.exports.axiosAbortSignal = (timeoutMs) => { // v16-: AbortSignal.timeout is not supported try { const abortController = new AbortController(); - setTimeout(() => abortController.abort(), timeoutMs); return abortController.signal; From 8e611587587cb74483cd5a9ca768ca682533b41d Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 22 Nov 2023 19:50:03 +0800 Subject: [PATCH 03/11] Close the client postgresql connection after rejection. (#4084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel Vรกzquez Acosta --- server/util-server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/util-server.js b/server/util-server.js index b9832af3..ec0cb768 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -458,6 +458,7 @@ exports.postgresQuery = function (connectionString, query) { }); } catch (e) { reject(e); + client.end(); } } }); From 121d1a11af7ff73bba12fbb5c73dc419a4fff7d8 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 02:23:38 +0800 Subject: [PATCH 04/11] Revert "Restart running monitors if no heartbeat (#3952)" (#4088) This reverts commit c43223a16de9f1c65e0aa14fd6a71837655385f8. --- server/model/monitor.js | 18 +------- server/uptime-kuma-server.js | 89 ------------------------------------ 2 files changed, 1 insertion(+), 106 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index ec40115c..e407e5a2 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -3,7 +3,7 @@ const dayjs = require("dayjs"); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, - SQL_DATETIME_FORMAT, isDev, sleep, getRandomInt + SQL_DATETIME_FORMAT } = require("../../src/util"); const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery, redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal @@ -329,16 +329,6 @@ class Monitor extends BeanModel { } } - // Evil - if (isDev) { - if (process.env.EVIL_RANDOM_MONITOR_SLEEP === "SURE") { - if (getRandomInt(0, 100) === 0) { - log.debug("evil", `[${this.name}] Evil mode: Random sleep: ` + beatInterval * 10000); - await sleep(beatInterval * 10000); - } - } - } - // Expose here for prometheus update // undefined if not https let tlsInfo = undefined; @@ -1024,7 +1014,6 @@ class Monitor extends BeanModel { if (! this.isStop) { log.debug("monitor", `[${this.name}] SetTimeout for next check.`); this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000); - this.lastScheduleBeatTime = dayjs(); } else { log.info("monitor", `[${this.name}] isStop = true, no next check.`); } @@ -1034,9 +1023,7 @@ class Monitor extends BeanModel { /** Get a heartbeat and handle errors */ const safeBeat = async () => { try { - this.lastStartBeatTime = dayjs(); await beat(); - this.lastEndBeatTime = dayjs(); } catch (e) { console.trace(e); UptimeKumaServer.errorLog(e, false); @@ -1045,9 +1032,6 @@ class Monitor extends BeanModel { if (! this.isStop) { log.info("monitor", "Try to restart the monitor"); this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000); - this.lastScheduleBeatTime = dayjs(); - } else { - log.info("monitor", "isStop = true, no next check."); } } }; diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 6b1d3d01..6acc8d4d 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -12,7 +12,6 @@ const { Settings } = require("./settings"); const dayjs = require("dayjs"); const childProcess = require("child_process"); const path = require("path"); -const axios = require("axios"); // DO NOT IMPORT HERE IF THE MODULES USED `UptimeKumaServer.getInstance()`, put at the bottom of this file instead. /** @@ -63,8 +62,6 @@ class UptimeKumaServer { */ jwtSecret = null; - checkMonitorsInterval = null; - static getInstance(args) { if (UptimeKumaServer.instance == null) { UptimeKumaServer.instance = new UptimeKumaServer(args); @@ -78,9 +75,6 @@ class UptimeKumaServer { const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined; const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined; - // Set default axios timeout to 5 minutes instead of infinity - axios.defaults.timeout = 300 * 1000; - log.info("server", "Creating express and socket.io instance"); this.app = express(); if (sslKey && sslCert) { @@ -352,10 +346,6 @@ class UptimeKumaServer { if (enable || enable === null) { this.startNSCDServices(); } - - this.checkMonitorsInterval = setInterval(() => { - this.checkMonitors(); - }, 60 * 1000); } /** @@ -368,8 +358,6 @@ class UptimeKumaServer { if (enable || enable === null) { this.stopNSCDServices(); } - - clearInterval(this.checkMonitorsInterval); } /** @@ -400,83 +388,6 @@ class UptimeKumaServer { } } } - - /** - * Start the specified monitor - * @param {number} monitorID ID of monitor to start - * @returns {Promise} - */ - async startMonitor(monitorID) { - log.info("manage", `Resume Monitor: ${monitorID} by server`); - - await R.exec("UPDATE monitor SET active = 1 WHERE id = ?", [ - monitorID, - ]); - - let monitor = await R.findOne("monitor", " id = ? ", [ - monitorID, - ]); - - if (monitor.id in this.monitorList) { - this.monitorList[monitor.id].stop(); - } - - this.monitorList[monitor.id] = monitor; - monitor.start(this.io); - } - - /** - * Restart a given monitor - * @param {number} monitorID ID of monitor to start - * @returns {Promise} - */ - async restartMonitor(monitorID) { - return await this.startMonitor(monitorID); - } - - /** - * Check if monitors are running properly - */ - async checkMonitors() { - log.debug("monitor_checker", "Checking monitors"); - - for (let monitorID in this.monitorList) { - let monitor = this.monitorList[monitorID]; - - // Not for push monitor - if (monitor.type === "push") { - continue; - } - - if (!monitor.active) { - continue; - } - - // Check the lastStartBeatTime, if it is too long, then restart - if (monitor.lastScheduleBeatTime ) { - let diff = dayjs().diff(monitor.lastStartBeatTime, "second"); - - if (diff > monitor.interval * 1.5) { - log.error("monitor_checker", `Monitor Interval: ${monitor.interval} Monitor ` + monitorID + " lastStartBeatTime diff: " + diff); - log.error("monitor_checker", "Unexpected error: Monitor " + monitorID + " is struck for unknown reason"); - log.error("monitor_checker", "Last start beat time: " + R.isoDateTime(monitor.lastStartBeatTime)); - log.error("monitor_checker", "Last end beat time: " + R.isoDateTime(monitor.lastEndBeatTime)); - log.error("monitor_checker", "Last ScheduleBeatTime: " + R.isoDateTime(monitor.lastScheduleBeatTime)); - - // Restart - log.error("monitor_checker", `Restarting monitor ${monitorID} automatically now`); - this.restartMonitor(monitorID); - } else { - //log.debug("monitor_checker", "Monitor " + monitorID + " is running normally"); - } - } else { - //log.debug("monitor_checker", "Monitor " + monitorID + " is not started yet, skipp"); - } - - } - - log.debug("monitor_checker", "Checking monitors end"); - } } module.exports = { From afaa7bb2f0bc955816e99bfcafd3e1699793d607 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 16:03:35 +0800 Subject: [PATCH 05/11] Do not process debug log for production --- src/util.js | 3 +++ src/util.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/util.js b/src/util.js index 6b8f8f37..8936ffbf 100644 --- a/src/util.js +++ b/src/util.js @@ -101,6 +101,9 @@ class Logger { * @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized. */ log(module, msg, level) { + if (level === "DEBUG" && !exports.isDev) { + return; + } if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) { return; } diff --git a/src/util.ts b/src/util.ts index e8a2706e..4f62fe76 100644 --- a/src/util.ts +++ b/src/util.ts @@ -115,6 +115,10 @@ class Logger { * @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized. */ log(module: string, msg: any, level: string) { + if (level === "DEBUG" && !isDev) { + return; + } + if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) { return; } From b689733d59139f158de95b0017865c867d0c7159 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 16:37:52 +0800 Subject: [PATCH 06/11] Fix getGameList, testChrome without checkLogin --- .../socket-handlers/general-socket-handler.js | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/server/socket-handlers/general-socket-handler.js b/server/socket-handlers/general-socket-handler.js index 2f0c63b4..c7f77b65 100644 --- a/server/socket-handlers/general-socket-handler.js +++ b/server/socket-handlers/general-socket-handler.js @@ -42,24 +42,40 @@ module.exports.generalSocketHandler = (socket, server) => { }); socket.on("getGameList", async (callback) => { - callback({ - ok: true, - gameList: getGameList(), - }); - }); - - socket.on("testChrome", (executable, callback) => { - // Just noticed that await call could block the whole socket.io server!!! Use pure promise instead. - testChrome(executable).then((version) => { + try { + checkLogin(socket); callback({ ok: true, - msg: "Found Chromium/Chrome. Version: " + version, + gameList: getGameList(), }); - }).catch((e) => { + } catch (e) { callback({ ok: false, msg: e.message, + }) + } + }); + + socket.on("testChrome", (executable, callback) => { + try { + checkLogin(socket); + // Just noticed that await call could block the whole socket.io server!!! Use pure promise instead. + testChrome(executable).then((version) => { + callback({ + ok: true, + msg: "Found Chromium/Chrome. Version: " + version, + }); + }).catch((e) => { + callback({ + ok: false, + msg: e.message, + }); }); - }); + } catch (e) { + callback({ + ok: false, + msg: e.message, + }) + } }); }; From f28dccf4e11f041564293e4f407e69ab9ee2277f Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 17:18:01 +0800 Subject: [PATCH 07/11] Merge pull request from GHSA-v4v2-8h88-65qj --- package-lock.json | 14 ++++++++++---- package.json | 1 + server/google-analytics.js | 14 +++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81bfcf76..8cf653a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "express-static-gzip": "~2.1.7", "form-data": "~4.0.0", "gamedig": "~4.1.0", + "html-escaper": "^3.0.3", "http-graceful-shutdown": "~3.1.7", "http-proxy-agent": "~5.0.0", "https-proxy-agent": "~5.0.1", @@ -10747,10 +10748,9 @@ "dev": true }, "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==" }, "node_modules/html-tags": { "version": "3.3.1", @@ -11558,6 +11558,12 @@ "node": ">=8" } }, + "node_modules/istanbul-reports/node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", diff --git a/package.json b/package.json index b6c7c22d..cb108bd3 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "express-static-gzip": "~2.1.7", "form-data": "~4.0.0", "gamedig": "~4.1.0", + "html-escaper": "^3.0.3", "http-graceful-shutdown": "~3.1.7", "http-proxy-agent": "~5.0.0", "https-proxy-agent": "~5.0.1", diff --git a/server/google-analytics.js b/server/google-analytics.js index fc9fbec8..3e8e645f 100644 --- a/server/google-analytics.js +++ b/server/google-analytics.js @@ -1,4 +1,5 @@ const jsesc = require("jsesc"); +const { escape } = require("html-escaper"); /** * Returns a string that represents the javascript that is required to insert the Google Analytics scripts @@ -7,15 +8,18 @@ const jsesc = require("jsesc"); * @returns {string} */ function getGoogleAnalyticsScript(tagId) { - let escapedTagId = jsesc(tagId, { isScriptContext: true }); + let escapedTagIdJS = jsesc(tagId, { isScriptContext: true }); - if (escapedTagId) { - escapedTagId = escapedTagId.trim(); + if (escapedTagIdJS) { + escapedTagIdJS = escapedTagIdJS.trim(); } + // Escape the tag ID for use in an HTML attribute. + let escapedTagIdHTMLAttribute = escape(tagId); + return ` - - + + `; } From 4255496b113abf4ee989333510c62f5e3f2ddc37 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 17:29:42 +0800 Subject: [PATCH 08/11] Rewrite Tailscale ping using spawnSync --- server/monitor-types/tailscale-ping.js | 41 +++++++++++--------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/server/monitor-types/tailscale-ping.js b/server/monitor-types/tailscale-ping.js index eeec9e3f..3b2ac487 100644 --- a/server/monitor-types/tailscale-ping.js +++ b/server/monitor-types/tailscale-ping.js @@ -1,6 +1,6 @@ const { MonitorType } = require("./monitor-type"); -const { UP, log } = require("../../src/util"); -const exec = require("child_process").exec; +const { UP } = require("../../src/util"); +const childProcess = require("child_process"); /** * A TailscalePing class extends the MonitorType. @@ -23,7 +23,6 @@ class TailscalePing extends MonitorType { let tailscaleOutput = await this.runTailscalePing(monitor.hostname, monitor.interval); this.parseTailscaleOutput(tailscaleOutput, heartbeat); } catch (err) { - log.debug("Tailscale", err); // trigger log function somewhere to display a notification or alert to the user (but how?) throw new Error(`Error checking Tailscale ping: ${err}`); } @@ -33,30 +32,26 @@ class TailscalePing extends MonitorType { * Runs the Tailscale ping command to the given URL. * * @param {string} hostname - The hostname to ping. + * @param {number} interval * @returns {Promise} - A Promise that resolves to the output of the Tailscale ping command * @throws Will throw an error if the command execution encounters any error. */ async runTailscalePing(hostname, interval) { - let cmd = `tailscale ping ${hostname}`; - - log.debug("Tailscale", cmd); - - return new Promise((resolve, reject) => { - let timeout = interval * 1000 * 0.8; - exec(cmd, { timeout: timeout }, (error, stdout, stderr) => { - // we may need to handle more cases if tailscale reports an error that isn't necessarily an error (such as not-logged in or DERP health-related issues) - if (error) { - reject(`Execution error: ${error.message}`); - return; - } - if (stderr) { - reject(`Error in output: ${stderr}`); - return; - } - - resolve(stdout); - }); + let timeout = interval * 1000 * 0.8; + let res = childProcess.spawnSync("tailscale", [ "ping", hostname ], { + timeout: timeout }); + if (res.error) { + throw new Error(`Execution error: ${res.error.message}`); + } + if (res.stderr && res.stderr.toString()) { + throw new Error(`Error in output: ${res.stderr.toString()}`); + } + if (res.stdout && res.stdout.toString()) { + return res.stdout.toString(); + } else { + throw new Error("No output from Tailscale ping"); + } } /** @@ -74,7 +69,7 @@ class TailscalePing extends MonitorType { heartbeat.status = UP; let time = line.split(" in ")[1].split(" ")[0]; heartbeat.ping = parseInt(time); - heartbeat.msg = line; + heartbeat.msg = "OK"; break; } else if (line.includes("timed out")) { throw new Error(`Ping timed out: "${line}"`); From 9536c6aa6a5fc12749fa54192d0aeb86465a6877 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 17:33:13 +0800 Subject: [PATCH 09/11] Minor --- server/socket-handlers/general-socket-handler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/socket-handlers/general-socket-handler.js b/server/socket-handlers/general-socket-handler.js index c7f77b65..3fc6f1d5 100644 --- a/server/socket-handlers/general-socket-handler.js +++ b/server/socket-handlers/general-socket-handler.js @@ -52,7 +52,7 @@ module.exports.generalSocketHandler = (socket, server) => { callback({ ok: false, msg: e.message, - }) + }); } }); @@ -75,7 +75,7 @@ module.exports.generalSocketHandler = (socket, server) => { callback({ ok: false, msg: e.message, - }) + }); } }); }; From 4ceeb304f12be913288a15d4e7bdd5784c68e297 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 18:44:54 +0800 Subject: [PATCH 10/11] Add a script to prepare a changelog --- extra/reformat-changelog.js | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 extra/reformat-changelog.js diff --git a/extra/reformat-changelog.js b/extra/reformat-changelog.js new file mode 100644 index 00000000..80a1b725 --- /dev/null +++ b/extra/reformat-changelog.js @@ -0,0 +1,44 @@ +// Generate on GitHub +const input = ` +* Add Korean translation by @Alanimdeo in https://github.com/louislam/dockge/pull/86 +`; + +const template = ` +### ๐Ÿ†• New Features + +### ๐Ÿ’‡โ€โ™€๏ธ Improvements + +### ๐Ÿž Bug Fixes + +### โฌ†๏ธ Security Fixes + +### ๐ŸฆŽ Translation Contributions + +### Others +- Other small changes, code refactoring and comment/doc updates in this repo: +`; + +const lines = input.split("\n").filter((line) => line.trim() !== ""); + +for (const line of lines) { + // Split the last " by " + const usernamePullRequesURL = line.split(" by ").pop(); + + if (!usernamePullRequesURL) { + console.log("Unable to parse", line); + continue; + } + + const [ username, pullRequestURL ] = usernamePullRequesURL.split(" in "); + const pullRequestID = "#" + pullRequestURL.split("/").pop(); + let message = line.split(" by ").shift(); + + if (!message) { + console.log("Unable to parse", line); + continue; + } + + message = message.split("* ").pop(); + console.log("-", pullRequestID, message, `(Thanks ${username})`); +} +console.log(template); From 73239d441d830db3e6ab8c9558434c1295bafae0 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 24 Nov 2023 18:49:27 +0800 Subject: [PATCH 11/11] Update to 1.23.7 --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8cf653a1..248e64a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.23.6", + "version": "1.23.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.23.6", + "version": "1.23.7", "license": "MIT", "dependencies": { "@grpc/grpc-js": "~1.7.3", diff --git a/package.json b/package.json index cb108bd3..b24f3ec2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.23.6", + "version": "1.23.7", "license": "MIT", "repository": { "type": "git", @@ -40,7 +40,7 @@ "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push", "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain", - "setup": "git checkout 1.23.6 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.23.7 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js",