2021-08-08 06:03:10 -07:00
console . log ( "Welcome to Uptime Kuma" ) ;
console . log ( "Node Env: " + process . env . NODE _ENV ) ;
2021-07-31 06:57:58 -07:00
2021-08-19 03:33:52 -07:00
const { sleep , debug , TimeLogger , getRandomInt } = require ( "../src/util" ) ;
2021-07-31 06:57:58 -07:00
console . log ( "Importing Node libraries" )
const fs = require ( "fs" ) ;
2021-07-27 10:47:13 -07:00
const http = require ( "http" ) ;
2021-09-02 05:18:27 -07:00
const https = require ( "https" ) ;
2021-07-31 06:57:58 -07:00
console . log ( "Importing 3rd-party libraries" )
debug ( "Importing express" ) ;
const express = require ( "express" ) ;
debug ( "Importing socket.io" ) ;
2021-06-25 06:55:49 -07:00
const { Server } = require ( "socket.io" ) ;
2021-07-31 06:57:58 -07:00
debug ( "Importing redbean-node" ) ;
2021-07-27 10:47:13 -07:00
const { R } = require ( "redbean-node" ) ;
2021-07-31 06:57:58 -07:00
debug ( "Importing jsonwebtoken" ) ;
2021-07-27 10:47:13 -07:00
const jwt = require ( "jsonwebtoken" ) ;
2021-07-31 06:57:58 -07:00
debug ( "Importing http-graceful-shutdown" ) ;
2021-07-27 10:47:13 -07:00
const gracefulShutdown = require ( "http-graceful-shutdown" ) ;
2021-07-31 06:57:58 -07:00
debug ( "Importing prometheus-api-metrics" ) ;
2021-07-27 10:47:13 -07:00
const prometheusAPIMetrics = require ( "prometheus-api-metrics" ) ;
2021-07-31 06:57:58 -07:00
console . log ( "Importing this project modules" ) ;
debug ( "Importing Monitor" ) ;
const Monitor = require ( "./model/monitor" ) ;
debug ( "Importing Settings" ) ;
2021-08-08 22:34:44 -07:00
const { getSettings , setSettings , setting , initJWTSecret } = require ( "./util-server" ) ;
2021-09-08 01:27:37 -07:00
const { userMonitorList } = require ( "./user-monitor-list" ) ;
2021-09-07 07:42:46 -07:00
2021-07-31 06:57:58 -07:00
debug ( "Importing Notification" ) ;
const { Notification } = require ( "./notification" ) ;
2021-09-07 07:42:46 -07:00
Notification . init ( ) ;
2021-07-31 06:57:58 -07:00
debug ( "Importing Database" ) ;
const Database = require ( "./database" ) ;
2021-07-27 09:52:31 -07:00
const { basicAuth } = require ( "./auth" ) ;
2021-07-27 10:47:13 -07:00
const { login } = require ( "./auth" ) ;
2021-07-28 05:35:55 -07:00
const passwordHash = require ( "./password-hash" ) ;
2021-07-31 06:57:58 -07:00
const args = require ( "args-parser" ) ( process . argv ) ;
2021-08-21 04:50:22 -07:00
const checkVersion = require ( "./check-version" ) ;
console . info ( "Version: " + checkVersion . version ) ;
2021-08-10 01:36:21 -07:00
2021-08-10 01:45:37 -07:00
// 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 (::)
2021-08-10 01:36:21 -07:00
const hostname = process . env . HOST || args . host ;
2021-07-29 20:33:44 -07:00
const port = parseInt ( process . env . PORT || args . port || 3001 ) ;
2021-06-25 06:55:49 -07:00
2021-09-02 05:18:27 -07:00
// SSL
2021-09-02 05:36:52 -07:00
const sslKey = process . env . SSL _KEY || args [ "ssl-key" ] || undefined ;
const sslCert = process . env . SSL _CERT || args [ "ssl-cert" ] || undefined ;
2021-09-02 05:18:27 -07:00
2021-09-02 07:37:51 -07:00
// Demo Mode?
const demoMode = args [ "demo" ] || false ;
if ( demoMode ) {
console . log ( "==== Demo Mode ====" ) ;
}
2021-09-02 05:27:18 -07:00
// Data Directory (must be end with "/")
2021-09-02 05:36:52 -07:00
Database . dataDir = process . env . DATA _DIR || args [ "data-dir" ] || "./data/" ;
2021-09-02 06:08:00 -07:00
Database . path = Database . dataDir + "kuma.db" ;
2021-09-02 06:11:20 -07:00
if ( ! fs . existsSync ( Database . dataDir ) ) {
fs . mkdirSync ( Database . dataDir , { recursive : true } ) ;
2021-09-02 06:10:18 -07:00
}
2021-09-02 05:27:18 -07:00
console . log ( ` Data Dir: ${ Database . dataDir } ` ) ;
2021-07-15 10:44:51 -07:00
console . log ( "Creating express and socket.io instance" )
const app = express ( ) ;
2021-09-02 05:18:27 -07:00
let server ;
if ( sslKey && sslCert ) {
2021-09-02 05:27:18 -07:00
console . log ( "Server Type: HTTPS" ) ;
2021-09-02 06:13:59 -07:00
server = https . createServer ( {
2021-09-02 06:16:04 -07:00
key : fs . readFileSync ( sslKey ) ,
cert : fs . readFileSync ( sslCert )
2021-09-02 06:13:59 -07:00
} , app ) ;
2021-09-02 05:18:27 -07:00
} else {
2021-09-02 05:27:18 -07:00
console . log ( "Server Type: HTTP" ) ;
server = http . createServer ( app ) ;
2021-09-02 05:18:27 -07:00
}
2021-07-15 10:44:51 -07:00
const io = new Server ( server ) ;
2021-09-04 11:03:40 -07:00
module . exports . io = io ;
// Must be after io instantiation
const { sendNotificationList , sendHeartbeatList , sendImportantHeartbeatList } = require ( "./client" ) ;
app . use ( express . json ( ) ) ;
2021-07-09 04:33:22 -07:00
2021-07-21 11:02:35 -07:00
/ * *
* Total WebSocket client connected to server currently , no actual use
* @ type { number }
* /
2021-06-25 06:55:49 -07:00
let totalClient = 0 ;
2021-07-21 11:02:35 -07:00
/ * *
* Use for decode the auth object
* @ type { null }
* /
2021-06-25 06:55:49 -07:00
let jwtSecret = null ;
2021-07-21 11:02:35 -07:00
/ * *
* Show Setup Page
* @ type { boolean }
* /
2021-07-10 22:47:57 -07:00
let needSetup = false ;
2021-06-25 06:55:49 -07:00
2021-07-28 08:40:50 -07:00
/ * *
* Cache Index HTML
* @ type { string }
* /
let indexHTML = fs . readFileSync ( "./dist/index.html" ) . toString ( ) ;
2021-06-25 06:55:49 -07:00
( async ( ) => {
await initDatabase ( ) ;
2021-07-18 03:51:58 -07:00
console . log ( "Adding route" )
2021-07-27 09:52:31 -07:00
// Normal Router here
2021-08-09 03:16:27 -07:00
// Robots.txt
app . get ( "/robots.txt" , async ( _request , response ) => {
let txt = "User-agent: *\nDisallow:" ;
if ( ! await setting ( "searchEngineIndex" ) ) {
txt += " /" ;
}
response . setHeader ( "Content-Type" , "text/plain" ) ;
response . send ( txt ) ;
} ) ;
2021-06-25 06:55:49 -07:00
2021-07-27 09:52:31 -07:00
// Basic Auth Router here
// Prometheus API metrics /metrics
// With Basic Auth using the first user's username/password
2021-08-09 03:16:27 -07:00
app . get ( "/metrics" , basicAuth , prometheusAPIMetrics ( ) ) ;
app . use ( "/" , express . static ( "dist" ) ) ;
2021-07-22 00:22:15 -07:00
2021-07-27 09:52:31 -07:00
// Universal Route Handler, must be at the end
2021-08-09 03:16:27 -07:00
app . get ( "*" , async ( _request , response ) => {
2021-08-03 20:56:10 -07:00
response . send ( indexHTML ) ;
2021-07-08 23:14:03 -07:00
} ) ;
2021-07-18 03:51:58 -07:00
console . log ( "Adding socket handler" )
2021-07-27 10:47:13 -07:00
io . on ( "connection" , async ( socket ) => {
2021-07-13 03:08:12 -07:00
socket . emit ( "info" , {
2021-08-21 04:50:22 -07:00
version : checkVersion . version ,
latestVersion : checkVersion . latestVersion ,
2021-07-13 03:08:12 -07:00
} )
2021-06-25 06:55:49 -07:00
totalClient ++ ;
2021-07-10 22:47:57 -07:00
if ( needSetup ) {
console . log ( "Redirect to setup page" )
socket . emit ( "setup" )
}
2021-07-27 10:47:13 -07:00
socket . on ( "disconnect" , ( ) => {
2021-06-25 06:55:49 -07:00
totalClient -- ;
} ) ;
2021-07-29 20:33:44 -07:00
// ***************************
2021-06-25 06:55:49 -07:00
// Public API
2021-07-29 20:33:44 -07:00
// ***************************
2021-06-25 06:55:49 -07:00
socket . on ( "loginByToken" , async ( token , callback ) => {
try {
let decoded = jwt . verify ( token , jwtSecret ) ;
console . log ( "Username from JWT: " + decoded . username )
let user = await R . findOne ( "user" , " username = ? AND active = 1 " , [
2021-07-27 10:47:13 -07:00
decoded . username ,
2021-06-25 06:55:49 -07:00
] )
if ( user ) {
2021-08-03 10:03:40 -07:00
debug ( "afterLogin" )
2021-08-23 03:52:55 -07:00
afterLogin ( socket , user )
2021-06-25 06:55:49 -07:00
2021-08-03 10:03:40 -07:00
debug ( "afterLogin ok" )
2021-06-25 06:55:49 -07:00
callback ( {
ok : true ,
} )
} else {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : "The user is inactive or deleted." ,
2021-06-25 06:55:49 -07:00
} )
}
} catch ( error ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : "Invalid token." ,
2021-06-25 06:55:49 -07:00
} )
}
} ) ;
socket . on ( "login" , async ( data , callback ) => {
console . log ( "Login" )
2021-07-27 09:52:31 -07:00
let user = await login ( data . username , data . password )
2021-07-13 07:22:46 -07:00
2021-07-27 09:52:31 -07:00
if ( user ) {
2021-08-23 03:52:55 -07:00
afterLogin ( socket , user )
2021-06-25 06:55:49 -07:00
callback ( {
ok : true ,
token : jwt . sign ( {
2021-07-27 10:47:13 -07:00
username : data . username ,
} , jwtSecret ) ,
2021-06-25 06:55:49 -07:00
} )
} else {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : "Incorrect username or password." ,
2021-06-25 06:55:49 -07:00
} )
}
} ) ;
socket . on ( "logout" , async ( callback ) => {
socket . leave ( socket . userID )
socket . userID = null ;
callback ( ) ;
2021-07-10 22:47:57 -07:00
} ) ;
socket . on ( "needSetup" , async ( callback ) => {
callback ( needSetup ) ;
} ) ;
socket . on ( "setup" , async ( username , password , callback ) => {
try {
if ( ( await R . count ( "user" ) ) !== 0 ) {
throw new Error ( "Uptime Kuma has been setup. If you want to setup again, please delete the database." )
}
let user = R . dispense ( "user" )
user . username = username ;
user . password = passwordHash . generate ( password )
await R . store ( user )
needSetup = false ;
callback ( {
ok : true ,
2021-07-27 10:47:13 -07:00
msg : "Added Successfully." ,
2021-07-10 22:47:57 -07:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-07-10 22:47:57 -07:00
} ) ;
}
2021-06-25 06:55:49 -07:00
} ) ;
2021-07-29 20:33:44 -07:00
// ***************************
2021-06-25 06:55:49 -07:00
// Auth Only API
2021-07-29 20:33:44 -07:00
// ***************************
2021-06-25 06:55:49 -07:00
2021-07-30 04:18:26 -07:00
// Add a new monitor
2021-06-25 06:55:49 -07:00
socket . on ( "add" , async ( monitor , callback ) => {
try {
checkLogin ( socket )
let bean = R . dispense ( "monitor" )
2021-07-09 02:55:48 -07:00
let notificationIDList = monitor . notificationIDList ;
delete monitor . notificationIDList ;
2021-08-06 11:10:38 -07:00
monitor . accepted _statuscodes _json = JSON . stringify ( monitor . accepted _statuscodes ) ;
delete monitor . accepted _statuscodes ;
2021-06-25 06:55:49 -07:00
bean . import ( monitor )
bean . user _id = socket . userID
2021-09-08 03:58:02 -07:00
await R . store ( bean ) ;
2021-07-09 02:55:48 -07:00
await updateMonitorNotification ( bean . id , notificationIDList )
2021-06-27 01:10:55 -07:00
await startMonitor ( socket . userID , bean . id ) ;
await sendMonitorList ( socket ) ;
2021-06-25 06:55:49 -07:00
callback ( {
ok : true ,
msg : "Added Successfully." ,
2021-07-27 10:47:13 -07:00
monitorID : bean . id ,
2021-06-25 06:55:49 -07:00
} ) ;
2021-06-27 01:10:55 -07:00
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-06-27 01:10:55 -07:00
} ) ;
}
} ) ;
2021-07-30 04:18:26 -07:00
// Edit a monitor
2021-06-27 01:10:55 -07:00
socket . on ( "editMonitor" , async ( monitor , callback ) => {
try {
checkLogin ( socket )
let bean = await R . findOne ( "monitor" , " id = ? " , [ monitor . id ] )
if ( bean . user _id !== socket . userID ) {
throw new Error ( "Permission denied." )
}
bean . name = monitor . name
bean . type = monitor . type
bean . url = monitor . url
bean . interval = monitor . interval
2021-06-30 23:03:06 -07:00
bean . hostname = monitor . hostname ;
2021-07-19 09:23:06 -07:00
bean . maxretries = monitor . maxretries ;
2021-06-30 23:03:06 -07:00
bean . port = monitor . port ;
2021-07-01 02:19:28 -07:00
bean . keyword = monitor . keyword ;
2021-07-30 04:18:26 -07:00
bean . ignoreTls = monitor . ignoreTls ;
bean . upsideDown = monitor . upsideDown ;
2021-08-08 09:23:51 -07:00
bean . maxredirects = monitor . maxredirects ;
2021-08-05 04:04:38 -07:00
bean . accepted _statuscodes _json = JSON . stringify ( monitor . accepted _statuscodes ) ;
2021-08-22 15:05:48 -07:00
bean . dns _resolve _type = monitor . dns _resolve _type ;
bean . dns _resolve _server = monitor . dns _resolve _server ;
2021-06-27 01:10:55 -07:00
await R . store ( bean )
2021-07-09 02:55:48 -07:00
await updateMonitorNotification ( bean . id , monitor . notificationIDList )
2021-06-27 01:10:55 -07:00
if ( bean . active ) {
await restartMonitor ( socket . userID , bean . id )
}
2021-06-25 06:55:49 -07:00
await sendMonitorList ( socket ) ;
2021-06-27 01:10:55 -07:00
callback ( {
ok : true ,
msg : "Saved." ,
2021-07-27 10:47:13 -07:00
monitorID : bean . id ,
2021-06-27 01:10:55 -07:00
} ) ;
2021-06-25 06:55:49 -07:00
} catch ( e ) {
2021-07-17 14:13:54 -07:00
console . error ( e )
2021-06-25 06:55:49 -07:00
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-06-25 06:55:49 -07:00
} ) ;
}
} ) ;
socket . on ( "getMonitor" , async ( monitorID , callback ) => {
try {
checkLogin ( socket )
console . log ( ` Get Monitor: ${ monitorID } User ID: ${ socket . userID } ` )
let bean = await R . findOne ( "monitor" , " id = ? AND user_id = ? " , [
monitorID ,
socket . userID ,
] )
callback ( {
ok : true ,
2021-07-09 02:55:48 -07:00
monitor : await bean . toJSON ( ) ,
2021-06-25 06:55:49 -07:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-06-25 06:55:49 -07:00
} ) ;
}
} ) ;
// Start or Resume the monitor
socket . on ( "resumeMonitor" , async ( monitorID , callback ) => {
try {
checkLogin ( socket )
await startMonitor ( socket . userID , monitorID ) ;
await sendMonitorList ( socket ) ;
callback ( {
ok : true ,
2021-07-27 10:47:13 -07:00
msg : "Resumed Successfully." ,
2021-06-25 06:55:49 -07:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-06-25 06:55:49 -07:00
} ) ;
}
} ) ;
socket . on ( "pauseMonitor" , async ( monitorID , callback ) => {
try {
checkLogin ( socket )
await pauseMonitor ( socket . userID , monitorID )
await sendMonitorList ( socket ) ;
callback ( {
ok : true ,
2021-07-27 10:47:13 -07:00
msg : "Paused Successfully." ,
2021-06-25 06:55:49 -07:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-06-25 06:55:49 -07:00
} ) ;
}
} ) ;
socket . on ( "deleteMonitor" , async ( monitorID , callback ) => {
try {
checkLogin ( socket )
2021-09-07 23:16:30 -07:00
console . log ( ` Delete Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-06-25 06:55:49 -07:00
2021-09-07 23:16:30 -07:00
let monitor = userMonitorList . getMonitor ( socket . userID , monitorID ) ;
if ( monitor ) {
userMonitorList . delete ( socket . userID , monitorID ) ;
2021-06-25 06:55:49 -07:00
}
await R . exec ( "DELETE FROM monitor WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 10:47:13 -07:00
socket . userID ,
2021-06-25 06:55:49 -07:00
] ) ;
callback ( {
ok : true ,
2021-07-27 10:47:13 -07:00
msg : "Deleted Successfully." ,
2021-06-25 06:55:49 -07:00
} ) ;
await sendMonitorList ( socket ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-06-25 06:55:49 -07:00
} ) ;
}
} ) ;
socket . on ( "changePassword" , async ( password , callback ) => {
try {
checkLogin ( socket )
if ( ! password . currentPassword ) {
throw new Error ( "Invalid new password" )
}
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
2021-07-27 10:47:13 -07:00
socket . userID ,
2021-06-25 06:55:49 -07:00
] )
if ( user && passwordHash . verify ( password . currentPassword , user . password ) ) {
2021-08-08 22:34:44 -07:00
user . resetPassword ( password . newPassword ) ;
2021-06-25 06:55:49 -07:00
callback ( {
ok : true ,
2021-07-27 10:47:13 -07:00
msg : "Password has been updated successfully." ,
2021-06-25 06:55:49 -07:00
} )
} else {
throw new Error ( "Incorrect current password" )
}
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-06-25 06:55:49 -07:00
} ) ;
}
} ) ;
2021-07-05 23:30:10 -07:00
2021-07-31 06:57:58 -07:00
socket . on ( "getSettings" , async ( callback ) => {
2021-07-05 23:30:10 -07:00
try {
checkLogin ( socket )
callback ( {
ok : true ,
2021-07-31 06:57:58 -07:00
data : await getSettings ( "general" ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "setSettings" , async ( data , callback ) => {
try {
checkLogin ( socket )
await setSettings ( "general" , data )
callback ( {
ok : true ,
msg : "Saved"
2021-07-05 23:30:10 -07:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-07-05 23:30:10 -07:00
} ) ;
}
} ) ;
2021-07-08 23:14:03 -07:00
// Add or Edit
socket . on ( "addNotification" , async ( notification , notificationID , callback ) => {
try {
checkLogin ( socket )
await Notification . save ( notification , notificationID , socket . userID )
await sendNotificationList ( socket )
callback ( {
ok : true ,
msg : "Saved" ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-07-08 23:14:03 -07:00
} ) ;
}
} ) ;
socket . on ( "deleteNotification" , async ( notificationID , callback ) => {
try {
checkLogin ( socket )
await Notification . delete ( notificationID , socket . userID )
await sendNotificationList ( socket )
callback ( {
ok : true ,
msg : "Deleted" ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-07-08 23:14:03 -07:00
} ) ;
}
} ) ;
socket . on ( "testNotification" , async ( notification , callback ) => {
try {
checkLogin ( socket )
2021-07-18 05:49:46 -07:00
let msg = await Notification . send ( notification , notification . name + " Testing" )
2021-07-08 23:14:03 -07:00
callback ( {
ok : true ,
2021-07-27 10:47:13 -07:00
msg ,
2021-07-08 23:14:03 -07:00
} ) ;
} catch ( e ) {
2021-07-18 05:49:46 -07:00
console . error ( e )
2021-07-08 23:14:03 -07:00
callback ( {
ok : false ,
2021-07-27 10:47:13 -07:00
msg : e . message ,
2021-07-08 23:14:03 -07:00
} ) ;
}
} ) ;
2021-07-18 03:51:58 -07:00
socket . on ( "checkApprise" , async ( callback ) => {
try {
checkLogin ( socket )
callback ( Notification . checkApprise ( ) ) ;
} catch ( e ) {
callback ( false ) ;
}
} ) ;
2021-08-03 10:03:40 -07:00
2021-08-29 09:47:01 -07:00
socket . on ( "clearEvents" , async ( monitorID , callback ) => {
try {
checkLogin ( socket )
console . log ( ` Clear Events Monitor: ${ monitorID } User ID: ${ socket . userID } ` )
await R . exec ( "UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? " , [
"" ,
"0" ,
monitorID ,
] ) ;
2021-09-04 11:03:40 -07:00
await sendImportantHeartbeatList ( socket , monitorID , true , true ) ;
2021-08-29 09:47:01 -07:00
callback ( {
ok : true ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "clearHeartbeats" , async ( monitorID , callback ) => {
try {
checkLogin ( socket )
console . log ( ` Clear Heartbeats Monitor: ${ monitorID } User ID: ${ socket . userID } ` )
await R . exec ( "DELETE FROM heartbeat WHERE monitor_id = ?" , [
monitorID
] ) ;
2021-09-04 11:03:40 -07:00
await sendHeartbeatList ( socket , monitorID , true , true ) ;
2021-08-29 09:47:01 -07:00
callback ( {
ok : true ,
2021-08-31 15:36:24 -07:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "clearStatistics" , async ( callback ) => {
try {
checkLogin ( socket )
console . log ( ` Clear Statistics User ID: ${ socket . userID } ` )
await R . exec ( "DELETE FROM heartbeat" ) ;
callback ( {
ok : true ,
2021-08-29 09:47:01 -07:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-08-03 10:03:40 -07:00
debug ( "added all socket handlers" )
2021-08-03 22:31:17 -07:00
// ***************************
// Better do anything after added all socket handlers here
// ***************************
2021-08-03 10:03:40 -07:00
debug ( "check auto login" )
if ( await setting ( "disableAuth" ) ) {
console . log ( "Disabled Auth: auto login to admin" )
2021-08-23 03:52:55 -07:00
afterLogin ( socket , await R . findOne ( "user" ) )
2021-08-03 10:03:40 -07:00
socket . emit ( "autoLogin" )
} else {
debug ( "need auth" )
}
2021-06-25 06:55:49 -07:00
} ) ;
2021-09-07 23:16:30 -07:00
console . log ( "Starting All Monitors" ) ;
2021-09-08 03:58:02 -07:00
await initMonitors ( ) ;
2021-09-07 23:16:30 -07:00
console . log ( "Init the server" ) ;
2021-08-10 06:28:54 -07:00
server . once ( "error" , async ( err ) => {
console . error ( "Cannot listen: " + err . message ) ;
await Database . close ( ) ;
} ) ;
2021-08-10 01:36:21 -07:00
2021-08-10 06:28:54 -07:00
server . listen ( port , hostname , ( ) => {
2021-08-10 01:36:21 -07:00
if ( hostname ) {
console . log ( ` Listening on ${ hostname } : ${ port } ` ) ;
} else {
2021-08-10 07:00:29 -07:00
console . log ( ` Listening on ${ port } ` ) ;
2021-08-10 01:36:21 -07:00
}
2021-09-07 23:16:30 -07:00
2021-08-21 04:50:22 -07:00
checkVersion . startInterval ( ) ;
2021-06-25 06:55:49 -07:00
} ) ;
} ) ( ) ;
2021-07-09 02:55:48 -07:00
async function updateMonitorNotification ( monitorID , notificationIDList ) {
2021-08-10 06:37:51 -07:00
await R . exec ( "DELETE FROM monitor_notification WHERE monitor_id = ? " , [
2021-07-27 10:47:13 -07:00
monitorID ,
2021-07-09 02:55:48 -07:00
] )
for ( let notificationID in notificationIDList ) {
if ( notificationIDList [ notificationID ] ) {
let relation = R . dispense ( "monitor_notification" ) ;
relation . monitor _id = monitorID ;
relation . notification _id = notificationID ;
await R . store ( relation )
}
}
}
2021-06-25 06:55:49 -07:00
async function checkOwner ( userID , monitorID ) {
let row = await R . getRow ( "SELECT id FROM monitor WHERE id = ? AND user_id = ? " , [
monitorID ,
userID ,
] )
if ( ! row ) {
throw new Error ( "You do not own this monitor." ) ;
}
}
async function sendMonitorList ( socket ) {
2021-06-29 01:06:20 -07:00
let list = await getMonitorJSONList ( socket . userID ) ;
io . to ( socket . userID ) . emit ( "monitorList" , list )
return list ;
2021-06-25 06:55:49 -07:00
}
async function afterLogin ( socket , user ) {
socket . userID = user . id ;
socket . join ( user . id )
2021-06-29 01:06:20 -07:00
let monitorList = await sendMonitorList ( socket )
2021-07-26 08:26:47 -07:00
sendNotificationList ( socket )
2021-08-08 10:58:56 -07:00
2021-08-23 03:52:55 -07:00
await sleep ( 500 ) ;
for ( let monitorID in monitorList ) {
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 )
}
2021-06-25 06:55:49 -07:00
}
async function getMonitorJSONList ( userID ) {
2021-06-27 01:10:55 -07:00
let result = { } ;
2021-06-25 06:55:49 -07:00
2021-09-07 23:16:30 -07:00
let monitorList = userMonitorList . getMonitorList ( userID ) ;
2021-06-25 06:55:49 -07:00
2021-09-07 23:16:30 -07:00
for ( let monitorID in monitorList ) {
let monitor = monitorList [ monitorID ] ;
2021-07-09 02:55:48 -07:00
result [ monitor . id ] = await monitor . toJSON ( ) ;
2021-06-25 06:55:49 -07:00
}
return result ;
}
function checkLogin ( socket ) {
if ( ! socket . userID ) {
throw new Error ( "You are not logged in." ) ;
}
}
async function initDatabase ( ) {
2021-07-21 11:02:35 -07:00
if ( ! fs . existsSync ( Database . path ) ) {
2021-07-15 10:44:51 -07:00
console . log ( "Copying Database" )
2021-07-21 11:02:35 -07:00
fs . copyFileSync ( Database . templatePath , Database . path ) ;
2021-07-10 22:47:57 -07:00
}
2021-07-15 10:44:51 -07:00
console . log ( "Connecting to Database" )
2021-08-08 22:34:44 -07:00
await Database . connect ( ) ;
2021-07-18 03:51:58 -07:00
console . log ( "Connected" )
2021-07-21 11:02:35 -07:00
// Patch the database
await Database . patch ( )
2021-06-25 06:55:49 -07:00
let jwtSecretBean = await R . findOne ( "setting" , " `key` = ? " , [
2021-07-27 10:47:13 -07:00
"jwtSecret" ,
2021-06-25 06:55:49 -07:00
] ) ;
if ( ! jwtSecretBean ) {
2021-08-08 22:34:44 -07:00
console . log ( "JWT secret is not found, generate one." ) ;
2021-08-09 05:09:01 -07:00
jwtSecretBean = await initJWTSecret ( ) ;
2021-08-08 22:34:44 -07:00
console . log ( "Stored JWT secret into database" ) ;
2021-06-25 06:55:49 -07:00
} else {
2021-08-08 22:34:44 -07:00
console . log ( "Load JWT secret from database." ) ;
2021-06-25 06:55:49 -07:00
}
2021-07-21 11:02:35 -07:00
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
2021-07-10 22:47:57 -07:00
if ( ( await R . count ( "user" ) ) === 0 ) {
console . log ( "No user, need setup" )
needSetup = true ;
}
2021-06-25 06:55:49 -07:00
jwtSecret = jwtSecretBean . value ;
}
async function startMonitor ( userID , monitorID ) {
await checkOwner ( userID , monitorID )
console . log ( ` Resume Monitor: ${ monitorID } User ID: ${ userID } ` )
await R . exec ( "UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 10:47:13 -07:00
userID ,
2021-06-25 06:55:49 -07:00
] ) ;
let monitor = await R . findOne ( "monitor" , " id = ? " , [
2021-07-27 10:47:13 -07:00
monitorID ,
2021-09-08 03:58:02 -07:00
] ) ;
2021-06-27 01:10:55 -07:00
2021-09-07 23:16:30 -07:00
userMonitorList . add ( userID , monitor ) ;
2021-06-25 06:55:49 -07:00
monitor . start ( io )
}
2021-06-27 01:10:55 -07:00
async function restartMonitor ( userID , monitorID ) {
2021-09-08 03:58:02 -07:00
return await startMonitor ( userID , monitorID ) ;
2021-06-27 01:10:55 -07:00
}
2021-06-25 06:55:49 -07:00
async function pauseMonitor ( userID , monitorID ) {
2021-09-08 03:58:02 -07:00
await checkOwner ( userID , monitorID ) ;
2021-06-25 06:55:49 -07:00
console . log ( ` Pause Monitor: ${ monitorID } User ID: ${ userID } ` )
await R . exec ( "UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 10:47:13 -07:00
userID ,
2021-06-25 06:55:49 -07:00
] ) ;
2021-09-07 23:16:30 -07:00
let monitor = userMonitorList . getMonitor ( userID , monitorID ) ;
if ( monitor ) {
2021-09-08 03:58:02 -07:00
monitor . active = 0 ;
2021-09-07 23:16:30 -07:00
monitor . stop ( ) ;
2021-06-25 06:55:49 -07:00
}
}
/ * *
* Resume active monitors
* /
2021-09-08 03:58:02 -07:00
async function initMonitors ( ) {
// Init all monitors
let list = await R . find ( "monitor" ) ;
let activeList = [ ] ;
2021-06-25 06:55:49 -07:00
for ( let monitor of list ) {
2021-09-07 23:16:30 -07:00
userMonitorList . add ( monitor . user _id , monitor ) ;
2021-09-08 03:58:02 -07:00
if ( monitor . active ) {
activeList . push ( monitor ) ;
}
2021-08-19 03:41:31 -07:00
}
2021-08-19 03:33:52 -07:00
2021-09-08 03:58:02 -07:00
// Start active monitors only
delayStartMonitors ( activeList ) ;
2021-09-07 23:16:30 -07:00
}
/ * *
* Only used by startMonitors ( )
* /
async function delayStartMonitors ( list ) {
2021-08-19 03:41:31 -07:00
for ( let monitor of list ) {
monitor . start ( io ) ;
2021-08-19 03:33:52 -07:00
// Give some delays, so all monitors won't make request at the same moment when just start the server.
2021-09-07 23:16:30 -07:00
await sleep ( getRandomInt ( 500 , 1200 ) ) ;
2021-06-25 06:55:49 -07:00
}
}
2021-07-15 10:44:51 -07:00
async function shutdownFunction ( signal ) {
2021-07-28 05:35:55 -07:00
console . log ( "Shutdown requested" ) ;
2021-07-27 10:47:13 -07:00
console . log ( "Called signal: " + signal ) ;
2021-07-15 10:44:51 -07:00
console . log ( "Stopping all monitors" )
2021-09-07 23:16:30 -07:00
let allMonitorList = userMonitorList . getAllMonitorList ( ) ;
for ( let id in allMonitorList ) {
let monitor = allMonitorList [ id ] ;
monitor . stop ( ) ;
2021-07-15 10:44:51 -07:00
}
2021-07-21 11:02:35 -07:00
await sleep ( 2000 ) ;
await Database . close ( ) ;
2021-07-15 10:44:51 -07:00
}
function finalFunction ( ) {
2021-08-17 00:59:23 -07:00
console . log ( "Graceful shutdown successfully!" ) ;
2021-07-15 10:44:51 -07:00
}
gracefulShutdown ( server , {
2021-07-27 10:47:13 -07:00
signals : "SIGINT SIGTERM" ,
2021-07-15 10:44:51 -07:00
timeout : 30000 , // timeout: 30 secs
development : false , // not in dev mode
forceExit : true , // triggers process.exit() at the end of shutdown process
onShutdown : shutdownFunction , // shutdown function (async) - e.g. for cleanup DB, ...
2021-07-27 10:47:13 -07:00
finally : finalFunction , // finally function (sync) - e.g. for logging
2021-07-15 10:44:51 -07:00
} ) ;
2021-08-17 00:32:34 -07:00
// Catch unexpected errors here
process . addListener ( "unhandledRejection" , ( error , promise ) => {
console . trace ( error ) ;
console . error ( "If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues" ) ;
} ) ;