Merge branch 'master' into 1.23.X-merge-to-2.X.X

# Conflicts:
#	docker/debian-base.dockerfile
#	package-lock.json
#	server/database.js
#	server/model/monitor.js
#	server/uptime-kuma-server.js
#	server/util-server.js
This commit is contained in:
Louis Lam 2023-11-13 21:15:51 +08:00
commit ace1fe00c2
316 changed files with 9487 additions and 4997 deletions

View file

@ -38,9 +38,7 @@ if (!semver.satisfies(nodeVersion, requiredNodeVersions)) {
const args = require("args-parser")(process.argv);
const { sleep, log, getRandomInt, genSecret, isDev } = require("../src/util");
const config = require("./config");
log.info("server", "Welcome to Uptime Kuma");
log.debug("server", "Arguments");
log.debug("server", args);
@ -48,13 +46,13 @@ if (! process.env.NODE_ENV) {
process.env.NODE_ENV = "production";
}
log.info("server", "Node Env: " + process.env.NODE_ENV);
log.info("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1"));
log.info("server", "Env: " + process.env.NODE_ENV);
log.debug("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1"));
log.info("server", "Importing Node libraries");
const fs = require("fs");
const checkVersion = require("./check-version");
log.info("server", "Uptime Kuma Version: " + checkVersion.version);
log.info("server", "Importing 3rd-party libraries");
log.info("server", "Loading modules");
log.debug("server", "Importing express");
const express = require("express");
@ -67,8 +65,6 @@ log.debug("server", "Importing http-graceful-shutdown");
const gracefulShutdown = require("http-graceful-shutdown");
log.debug("server", "Importing prometheus-api-metrics");
const prometheusAPIMetrics = require("prometheus-api-metrics");
log.debug("server", "Importing compare-versions");
const compareVersions = require("compare-versions");
const { passwordStrength } = require("check-password-strength");
log.debug("server", "Importing 2FA Modules");
@ -76,26 +72,23 @@ const notp = require("notp");
const base32 = require("thirty-two");
const { UptimeKumaServer } = require("./uptime-kuma-server");
const server = UptimeKumaServer.getInstance(args);
const io = module.exports.io = server.io;
const app = server.app;
log.info("server", "Importing this project modules");
log.debug("server", "Importing Monitor");
const Monitor = require("./model/monitor");
const User = require("./model/user");
log.debug("server", "Importing Settings");
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, doubleCheckPassword, startE2eTests, shake256, SHAKE256_LENGTH
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, FBSD, doubleCheckPassword, startE2eTests, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
} = require("./util-server");
log.debug("server", "Importing Notification");
const { Notification } = require("./notification");
Notification.init();
log.debug("server", "Importing Proxy");
const { Proxy } = require("./proxy");
log.debug("server", "Importing Database");
const Database = require("./database");
@ -107,9 +100,6 @@ const { apiAuth } = require("./auth");
const { login } = require("./auth");
const passwordHash = require("./password-hash");
const checkVersion = require("./check-version");
log.info("server", "Version: " + checkVersion.version);
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
// Dual-stack support for (::)
// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
@ -140,12 +130,8 @@ const twoFAVerifyOptions = {
const testMode = !!args["test"] || false;
const e2eTestMode = !!args["e2e"] || false;
if (config.demoMode) {
log.info("server", "==== Demo Mode ====");
}
// Must be after io instantiation
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList } = require("./client");
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList } = require("./client");
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
const TwoFA = require("./2fa");
@ -160,6 +146,8 @@ const { Settings } = require("./settings");
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
const apicache = require("./modules/apicache");
const { resetChrome } = require("./monitor-types/real-browser-monitor-type");
const { EmbeddedMariaDB } = require("./embedded-mariadb");
const { SetupDatabase } = require("./setup-database");
app.use(express.json());
@ -179,13 +167,30 @@ app.use(function (req, res, next) {
let needSetup = false;
(async () => {
Database.init(args);
await initDatabase(testMode);
// Create a data directory
Database.initDataDir(args);
// Check if is chosen a database type
let setupDatabase = new SetupDatabase(args, server);
if (setupDatabase.isNeedSetup()) {
// Hold here and start a special setup page until user choose a database type
await setupDatabase.start(hostname, port);
}
// Connect to database
try {
await initDatabase(testMode);
} catch (e) {
log.error("server", "Failed to prepare your database: " + e.message);
process.exit(1);
}
// Database should be ready now
await server.initAfterDatabaseReady();
server.entryPage = await Settings.get("entryPage");
await StatusPage.loadDomainMappingList();
log.info("server", "Adding route");
log.debug("server", "Adding route");
// ***************************
// Normal Router here
@ -218,6 +223,14 @@ let needSetup = false;
}
});
app.get("/setup-database-info", (request, response) => {
allowDevAllOrigin(response);
response.json({
runningSetup: false,
needSetup: false,
});
});
if (isDev) {
app.use(express.urlencoded({ extended: true }));
app.post("/test-webhook", async (request, response) => {
@ -225,6 +238,12 @@ let needSetup = false;
log.debug("test", request.body);
response.send("OK");
});
app.post("/test-x-www-form-urlencoded", async (request, response) => {
log.debug("test", request.headers);
log.debug("test", request.body);
response.send("OK");
});
}
// Robots.txt
@ -271,7 +290,7 @@ let needSetup = false;
}
});
log.info("server", "Adding socket handler");
log.debug("server", "Adding socket handler");
io.on("connection", async (socket) => {
sendInfo(socket, true);
@ -320,7 +339,8 @@ let needSetup = false;
callback({
ok: false,
msg: "The user is inactive or deleted.",
msg: "authUserInactiveOrDeleted",
msgi18n: true,
});
}
} catch (error) {
@ -330,7 +350,8 @@ let needSetup = false;
}
callback({
ok: false,
msg: "Invalid token.",
msg: "authInvalidToken",
msgi18n: true,
});
}
@ -351,7 +372,7 @@ let needSetup = false;
}
// Login Rate Limit
if (! await loginRateLimiter.pass(callback)) {
if (!await loginRateLimiter.pass(callback)) {
log.info("auth", `Too many failed requests for user ${data.username}. IP=${clientIP}`);
return;
}
@ -402,7 +423,8 @@ let needSetup = false;
callback({
ok: false,
msg: "Invalid Token!",
msg: "authInvalidToken",
msgi18n: true,
});
}
}
@ -412,7 +434,8 @@ let needSetup = false;
callback({
ok: false,
msg: "Incorrect username or password.",
msg: "authIncorrectCreds",
msgi18n: true,
});
}
@ -420,7 +443,7 @@ let needSetup = false;
socket.on("logout", async (callback) => {
// Rate Limit
if (! await loginRateLimiter.pass(callback)) {
if (!await loginRateLimiter.pass(callback)) {
return;
}
@ -434,7 +457,7 @@ let needSetup = false;
socket.on("prepare2FA", async (currentPassword, callback) => {
try {
if (! await twoFaRateLimiter.pass(callback)) {
if (!await twoFaRateLimiter.pass(callback)) {
return;
}
@ -468,7 +491,8 @@ let needSetup = false;
} else {
callback({
ok: false,
msg: "2FA is already enabled.",
msg: "2faAlreadyEnabled",
msgi18n: true,
});
}
} catch (error) {
@ -483,7 +507,7 @@ let needSetup = false;
const clientIP = await server.getClientIP(socket);
try {
if (! await twoFaRateLimiter.pass(callback)) {
if (!await twoFaRateLimiter.pass(callback)) {
return;
}
@ -498,7 +522,8 @@ let needSetup = false;
callback({
ok: true,
msg: "2FA Enabled.",
msg: "2faEnabled",
msgi18n: true,
});
} catch (error) {
@ -515,7 +540,7 @@ let needSetup = false;
const clientIP = await server.getClientIP(socket);
try {
if (! await twoFaRateLimiter.pass(callback)) {
if (!await twoFaRateLimiter.pass(callback)) {
return;
}
@ -527,7 +552,8 @@ let needSetup = false;
callback({
ok: true,
msg: "2FA Disabled.",
msg: "2faDisabled",
msgi18n: true,
});
} catch (error) {
@ -559,7 +585,8 @@ let needSetup = false;
} else {
callback({
ok: false,
msg: "Invalid Token.",
msg: "authInvalidToken",
msgi18n: true,
valid: false,
});
}
@ -609,7 +636,7 @@ let needSetup = false;
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
}
if ((await R.count("user")) !== 0) {
if ((await R.knex("user").count("id as count").first()).count !== 0) {
throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database.");
}
@ -622,7 +649,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Added Successfully.",
msg: "successAdded",
msgi18n: true,
});
} catch (e) {
@ -675,7 +703,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Added Successfully.",
msg: "successAdded",
msgi18n: true,
monitorID: bean.id,
});
@ -746,6 +775,11 @@ let needSetup = false;
bean.game = monitor.game;
bean.maxretries = monitor.maxretries;
bean.port = parseInt(monitor.port);
if (isNaN(bean.port)) {
bean.port = null;
}
bean.keyword = monitor.keyword;
bean.invertKeyword = monitor.invertKeyword;
bean.ignoreTls = monitor.ignoreTls;
@ -813,6 +847,7 @@ let needSetup = false;
callback({
ok: true,
msg: "Saved.",
msgi18n: true,
monitorID: bean.id,
});
@ -875,14 +910,17 @@ let needSetup = false;
throw new Error("Invalid period.");
}
const sqlHourOffset = Database.sqlHourOffset();
let list = await R.getAll(`
SELECT * FROM heartbeat
WHERE monitor_id = ? AND
time > DATETIME('now', '-' || ? || ' hours')
SELECT *
FROM heartbeat
WHERE monitor_id = ?
AND time > ${sqlHourOffset}
ORDER BY time ASC
`, [
monitorID,
period,
-period,
]);
callback({
@ -906,7 +944,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Resumed Successfully.",
msg: "successResumed",
msgi18n: true,
});
} catch (e) {
@ -925,7 +964,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Paused Successfully.",
msg: "successPaused",
msgi18n: true,
});
} catch (e) {
@ -963,12 +1003,11 @@ let needSetup = false;
callback({
ok: true,
msg: "Deleted Successfully.",
msg: "successDeleted",
msgi18n: true,
});
await server.sendMonitorList(socket);
// Clear heartbeat list on client
await sendImportantHeartbeatList(socket, monitorID, true, true);
} catch (e) {
callback({
@ -1027,7 +1066,8 @@ let needSetup = false;
if (bean == null) {
callback({
ok: false,
msg: "Tag not found",
msg: "tagNotFound",
msgi18n: true,
});
return;
}
@ -1037,7 +1077,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Saved",
msg: "Saved.",
msgi18n: true,
tag: await bean.toJSON(),
});
@ -1057,7 +1098,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Deleted Successfully.",
msg: "successDeleted",
msgi18n: true,
});
} catch (e) {
@ -1080,7 +1122,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Added Successfully.",
msg: "successAdded",
msgi18n: true,
});
} catch (e) {
@ -1103,7 +1146,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Edited Successfully.",
msg: "successEdited",
msgi18n: true,
});
} catch (e) {
@ -1126,7 +1170,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Deleted Successfully.",
msg: "successDeleted",
msgi18n: true,
});
} catch (e) {
@ -1137,11 +1182,77 @@ let needSetup = false;
}
});
socket.on("monitorImportantHeartbeatListCount", async (monitorID, callback) => {
try {
checkLogin(socket);
let count;
if (monitorID == null) {
count = await R.count("heartbeat", "important = 1");
} else {
count = await R.count("heartbeat", "monitor_id = ? AND important = 1", [
monitorID,
]);
}
callback({
ok: true,
count: count,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("monitorImportantHeartbeatListPaged", async (monitorID, offset, count, callback) => {
try {
checkLogin(socket);
let list;
if (monitorID == null) {
list = await R.find("heartbeat", `
important = 1
ORDER BY time DESC
LIMIT ?
OFFSET ?
`, [
count,
offset,
]);
} else {
list = await R.find("heartbeat", `
monitor_id = ?
AND important = 1
ORDER BY time DESC
LIMIT ?
OFFSET ?
`, [
monitorID,
count,
offset,
]);
}
callback({
ok: true,
data: list,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("changePassword", async (password, callback) => {
try {
checkLogin(socket);
if (! password.newPassword) {
if (!password.newPassword) {
throw new Error("Invalid new password");
}
@ -1154,7 +1265,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Password has been updated successfully.",
msg: "successAuthChangePassword",
msgi18n: true,
});
} catch (e) {
@ -1231,7 +1343,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Saved"
msg: "Saved.",
msgi18n: true,
});
sendInfo(socket);
@ -1255,7 +1368,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Saved",
msg: "Saved.",
msgi18n: true,
id: notificationBean.id,
});
@ -1276,7 +1390,8 @@ let needSetup = false;
callback({
ok: true,
msg: "Deleted",
msg: "successDeleted",
msgi18n: true,
});
} catch (e) {
@ -1317,211 +1432,6 @@ let needSetup = false;
}
});
socket.on("uploadBackup", async (uploadedJSON, importHandle, callback) => {
try {
checkLogin(socket);
let backupData = JSON.parse(uploadedJSON);
log.info("manage", `Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`);
let notificationListData = backupData.notificationList;
let proxyListData = backupData.proxyList;
let monitorListData = backupData.monitorList;
let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
// If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
if (importHandle === "overwrite") {
// Stops every monitor first, so it doesn't execute any heartbeat while importing
for (let id in server.monitorList) {
let monitor = server.monitorList[id];
await monitor.stop();
}
await R.exec("DELETE FROM heartbeat");
await R.exec("DELETE FROM monitor_notification");
await R.exec("DELETE FROM monitor_tls_info");
await R.exec("DELETE FROM notification");
await R.exec("DELETE FROM monitor_tag");
await R.exec("DELETE FROM tag");
await R.exec("DELETE FROM monitor");
await R.exec("DELETE FROM proxy");
}
// Only starts importing if the backup file contains at least one notification
if (notificationListData.length >= 1) {
// Get every existing notification name and puts them in one simple string
let notificationNameList = await R.getAll("SELECT name FROM notification");
let notificationNameListString = JSON.stringify(notificationNameList);
for (let i = 0; i < notificationListData.length; i++) {
// Only starts importing the notification if the import option is "overwrite", "keep" or "skip" but the notification doesn't exists
if ((importHandle === "skip" && notificationNameListString.includes(notificationListData[i].name) === false) || importHandle === "keep" || importHandle === "overwrite") {
let notification = JSON.parse(notificationListData[i].config);
await Notification.save(notification, null, socket.userID);
}
}
}
// Only starts importing if the backup file contains at least one proxy
if (proxyListData && proxyListData.length >= 1) {
const proxies = await R.findAll("proxy");
// Loop over proxy list and save proxies
for (const proxy of proxyListData) {
const exists = proxies.find(item => item.id === proxy.id);
// Do not process when proxy already exists in import handle is skip and keep
if ([ "skip", "keep" ].includes(importHandle) && !exists) {
return;
}
// Save proxy as new entry if exists update exists one
await Proxy.save(proxy, exists ? proxy.id : undefined, proxy.userId);
}
}
// Only starts importing if the backup file contains at least one monitor
if (monitorListData.length >= 1) {
// Get every existing monitor name and puts them in one simple string
let monitorNameList = await R.getAll("SELECT name FROM monitor");
let monitorNameListString = JSON.stringify(monitorNameList);
for (let i = 0; i < monitorListData.length; i++) {
// Only starts importing the monitor if the import option is "overwrite", "keep" or "skip" but the notification doesn't exists
if ((importHandle === "skip" && monitorNameListString.includes(monitorListData[i].name) === false) || importHandle === "keep" || importHandle === "overwrite") {
// Define in here every new variable for monitors which where implemented after the first version of the Import/Export function (1.6.0)
// --- Start ---
// Define default values
let retryInterval = 0;
let timeout = monitorListData[i].timeout || (monitorListData[i].interval * 0.8); // fallback to old value
/*
Only replace the default value with the backup file data for the specific version, where it appears the first time
More information about that where "let version" will be defined
*/
if (version17x) {
retryInterval = monitorListData[i].retryInterval;
}
// --- End ---
let monitor = {
// Define the new variable from earlier here
name: monitorListData[i].name,
description: monitorListData[i].description,
type: monitorListData[i].type,
url: monitorListData[i].url,
method: monitorListData[i].method || "GET",
body: monitorListData[i].body,
headers: monitorListData[i].headers,
authMethod: monitorListData[i].authMethod,
basic_auth_user: monitorListData[i].basic_auth_user,
basic_auth_pass: monitorListData[i].basic_auth_pass,
authWorkstation: monitorListData[i].authWorkstation,
authDomain: monitorListData[i].authDomain,
timeout,
interval: monitorListData[i].interval,
retryInterval: retryInterval,
resendInterval: monitorListData[i].resendInterval || 0,
hostname: monitorListData[i].hostname,
maxretries: monitorListData[i].maxretries,
port: monitorListData[i].port,
keyword: monitorListData[i].keyword,
invertKeyword: monitorListData[i].invertKeyword,
ignoreTls: monitorListData[i].ignoreTls,
upsideDown: monitorListData[i].upsideDown,
maxredirects: monitorListData[i].maxredirects,
accepted_statuscodes: monitorListData[i].accepted_statuscodes,
dns_resolve_type: monitorListData[i].dns_resolve_type,
dns_resolve_server: monitorListData[i].dns_resolve_server,
notificationIDList: monitorListData[i].notificationIDList,
proxy_id: monitorListData[i].proxy_id || null,
};
if (monitorListData[i].pushToken) {
monitor.pushToken = monitorListData[i].pushToken;
}
let bean = R.dispense("monitor");
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
delete monitor.accepted_statuscodes;
bean.import(monitor);
bean.user_id = socket.userID;
await R.store(bean);
// Only for backup files with the version 1.7.0 or higher, since there was the tag feature implemented
if (version17x) {
// Only import if the specific monitor has tags assigned
for (const oldTag of monitorListData[i].tags) {
// Check if tag already exists and get data ->
let tag = await R.findOne("tag", " name = ?", [
oldTag.name,
]);
let tagId;
if (! tag) {
// -> If it doesn't exist, create new tag from backup file
let beanTag = R.dispense("tag");
beanTag.name = oldTag.name;
beanTag.color = oldTag.color;
await R.store(beanTag);
tagId = beanTag.id;
} else {
// -> If it already exist, set tagId to value from database
tagId = tag.id;
}
// Assign the new created tag to the monitor
await R.exec("INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)", [
tagId,
bean.id,
oldTag.value,
]);
}
}
await updateMonitorNotification(bean.id, notificationIDList);
// If monitor was active start it immediately, otherwise pause it
if (monitorListData[i].active === 1) {
await startMonitor(socket.userID, bean.id);
} else {
await pauseMonitor(socket.userID, bean.id);
}
}
}
await sendNotificationList(socket);
await server.sendMonitorList(socket);
}
callback({
ok: true,
msg: "Backup successfully restored.",
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearEvents", async (monitorID, callback) => {
try {
checkLogin(socket);
@ -1534,8 +1444,6 @@ let needSetup = false;
monitorID,
]);
await sendImportantHeartbeatList(socket, monitorID, true, true);
callback({
ok: true,
});
@ -1619,11 +1527,12 @@ let needSetup = false;
});
log.info("server", "Init the server");
log.debug("server", "Init the server");
server.httpServer.once("error", async (err) => {
console.error("Cannot listen: " + err.message);
log.error("server", "Cannot listen: " + err.message);
await shutdownFunction();
process.exit(1);
});
server.start();
@ -1637,10 +1546,6 @@ let needSetup = false;
startMonitors();
checkVersion.startInterval();
if (testMode) {
startUnitTest();
}
if (e2eTestMode) {
startE2eTests();
}
@ -1677,8 +1582,8 @@ async function updateMonitorNotification(monitorID, notificationIDList) {
/**
* Check if a given user owns a specific monitor
* @param {number} userID
* @param {number} monitorID
* @param {number} userID ID of user to check
* @param {number} monitorID ID of monitor to check
* @returns {Promise<void>}
* @throws {Error} The specified user does not own the monitor
*/
@ -1697,7 +1602,7 @@ async function checkOwner(userID, monitorID) {
* Function called after user login
* This function is used to send the heartbeat list of a monitor.
* @param {Socket} socket Socket.io instance
* @param {Object} user User object
* @param {object} user User object
* @returns {Promise<void>}
*/
async function afterLogin(socket, user) {
@ -1720,10 +1625,6 @@ async function afterLogin(socket, user) {
await sendHeartbeatList(socket, monitorID);
}
for (let monitorID in monitorList) {
await sendImportantHeartbeatList(socket, monitorID);
}
for (let monitorID in monitorList) {
await Monitor.sendStats(io, monitorID, user.id);
}
@ -1738,19 +1639,14 @@ async function afterLogin(socket, user) {
/**
* Initialize the database
* @param {boolean} [testMode=false] Should the connection be
* @param {boolean} testMode Should the connection be
* started in test mode?
* @returns {Promise<void>}
*/
async function initDatabase(testMode = false) {
if (! fs.existsSync(Database.path)) {
log.info("server", "Copying Database");
fs.copyFileSync(Database.templatePath, Database.path);
}
log.info("server", "Connecting to the Database");
log.debug("server", "Connecting to the database");
await Database.connect(testMode);
log.info("server", "Connected");
log.info("server", "Connected to the database");
// Patch the database
await Database.patch();
@ -1764,11 +1660,11 @@ async function initDatabase(testMode = false) {
jwtSecretBean = await initJWTSecret();
log.info("server", "Stored JWT secret into database");
} else {
log.info("server", "Load JWT secret from database.");
log.debug("server", "Load JWT secret from database.");
}
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
if ((await R.count("user")) === 0) {
if ((await R.knex("user").count("id as count").first()).count === 0) {
log.info("server", "No user, need setup");
needSetup = true;
}
@ -1835,7 +1731,10 @@ async function pauseMonitor(userID, monitorID) {
}
}
/** Resume active monitors */
/**
* Resume active monitors
* @returns {Promise<void>}
*/
async function startMonitors() {
let list = await R.find("monitor", " active = 1 ");
@ -1870,12 +1769,19 @@ async function shutdownFunction(signal) {
await sleep(2000);
await Database.close();
if (EmbeddedMariaDB.hasInstance()) {
EmbeddedMariaDB.getInstance().stop();
}
stopBackgroundJobs();
await cloudflaredStop();
Settings.stopCacheCleaner();
}
/** Final function called before application exits */
/**
* Final function called before application exits
* @returns {void}
*/
function finalFunction() {
log.info("server", "Graceful shutdown successful!");
}