2025-04-17 16:11:04 +02:00
const express = require ( "express" ) ;
const ping = require ( "ping" ) ;
const pm2 = require ( "pm2" ) ;
2025-04-29 22:02:28 +02:00
const fs = require ( "fs" ) ;
const path = require ( "path" ) ;
2025-05-01 09:32:25 +02:00
const axios = require ( "axios" ) ;
const admin = require ( "firebase-admin" ) ;
// Initialize Firebase Admin SDK
try {
const serviceAccount = require ( "../../firebase-service-account.json" ) ;
admin . initializeApp ( {
credential : admin . credential . cert ( serviceAccount )
} ) ;
console . log ( "Firebase Admin SDK initialized successfully" ) ;
} catch ( error ) {
console . error ( "Error initializing Firebase Admin SDK:" , error ) ;
}
2025-04-17 16:11:04 +02:00
const router = express . Router ( ) ;
const REMOTE _SERVERS = [
2025-05-05 18:33:40 +02:00
{ name : "google.com" , host : "google.com" } ,
// { name: "home server", host: "31.223.36.208" } removed cause router not behaving, dropped all pings today.
2025-04-17 16:11:04 +02:00
] ;
const CHECK _INTERVAL = 5 * 1000 ;
2025-04-29 22:02:28 +02:00
const LOGS _DIR = path . join ( _ _dirname , '../../logs' ) ;
const ONLINE _LOGS _DIR = path . join ( LOGS _DIR , 'online' ) ;
const OFFLINE _LOGS _DIR = path . join ( LOGS _DIR , 'offline' ) ;
2025-05-02 18:36:05 +02:00
// Number of offline cycles before sending a notification
const NOTIFICATION _THRESHOLD = 3 ;
2025-04-29 22:02:28 +02:00
// Create log directories if they don't exist
function ensureLogDirectories ( ) {
if ( ! fs . existsSync ( LOGS _DIR ) ) {
fs . mkdirSync ( LOGS _DIR , { recursive : true } ) ;
}
if ( ! fs . existsSync ( ONLINE _LOGS _DIR ) ) {
fs . mkdirSync ( ONLINE _LOGS _DIR , { recursive : true } ) ;
}
if ( ! fs . existsSync ( OFFLINE _LOGS _DIR ) ) {
fs . mkdirSync ( OFFLINE _LOGS _DIR , { recursive : true } ) ;
}
}
2025-04-17 16:11:04 +02:00
2025-05-01 09:32:25 +02:00
// Track previous states for notifications
let previousServersStatus = { } ;
let previousPM2Status = { } ;
2025-05-02 18:36:05 +02:00
// Track consecutive offline cycles
let serverFailureCounts = { } ;
let pm2FailureCounts = { } ;
2025-04-17 16:11:04 +02:00
let serversStatus = { } ;
REMOTE _SERVERS . forEach ( server => {
serversStatus [ server . name ] = {
online : false ,
lastChecked : null ,
responseTime : null ,
} ;
2025-05-01 09:32:25 +02:00
// Initialize previous status
previousServersStatus [ server . name ] = false ;
2025-05-02 18:36:05 +02:00
// Initialize failure counters
serverFailureCounts [ server . name ] = 0 ;
2025-04-17 16:11:04 +02:00
} ) ;
// Add PM2 services status object
let pm2ServicesStatus = { } ;
2025-05-01 09:32:25 +02:00
// Function to send FCM notification
async function sendFCMNotification ( message , topic ) {
try {
if ( ! admin . apps . length ) {
console . warn ( "Firebase Admin not initialized, skipping notification" ) ;
return ;
}
// Create the message object according to Firebase Admin SDK format
const fcmMessage = {
topic : topic ,
notification : {
title : 'Server Status Alert' ,
body : message
} ,
data : {
type : 'server_status' ,
timestamp : Date . now ( ) . toString ( )
}
} ;
await admin . messaging ( ) . send ( fcmMessage ) ;
console . log ( ` Notification sent: ${ message } ` ) ;
} catch ( error ) {
console . error ( 'Error sending notification:' , error ) ;
}
}
2025-04-17 16:11:04 +02:00
async function checkServers ( ) {
try {
2025-04-29 22:02:28 +02:00
ensureLogDirectories ( ) ;
2025-04-17 16:11:04 +02:00
for ( const server of REMOTE _SERVERS ) {
try {
const res = await ping . promise . probe ( server . host , {
timeout : 4 , // Set a timeout of 4 seconds
} ) ;
2025-05-01 09:32:25 +02:00
// Get previous status before updating
const wasOnline = previousServersStatus [ server . name ] ;
const isNowOnline = res . alive ;
// Update status
serversStatus [ server . name ] . online = isNowOnline ;
2025-04-17 16:11:04 +02:00
serversStatus [ server . name ] . responseTime = res . time ;
2025-05-01 09:32:25 +02:00
2025-05-02 18:36:05 +02:00
if ( isNowOnline ) {
// Service is online
// Reset failure counter
serverFailureCounts [ server . name ] = 0 ;
// If service was previously offline, send online notification
if ( wasOnline === false ) {
await sendFCMNotification ( ` Server ${ server . name } is back online ` , 'service_online' ) ;
}
} else {
// Service is offline
// Increment failure counter
serverFailureCounts [ server . name ] ++ ;
// Check if we've reached the notification threshold
if ( serverFailureCounts [ server . name ] === NOTIFICATION _THRESHOLD ) {
await sendFCMNotification ( ` Server ${ server . name } is offline (after ${ NOTIFICATION _THRESHOLD } checks) ` , 'service_offline' ) ;
}
2025-05-01 09:32:25 +02:00
}
// Update previous status
previousServersStatus [ server . name ] = isNowOnline ;
2025-04-17 16:11:04 +02:00
} catch ( error ) {
console . error ( ` Error pinging ${ server . host } : ` , error ) ;
serversStatus [ server . name ] . online = false ;
serversStatus [ server . name ] . responseTime = null ;
2025-05-01 09:32:25 +02:00
2025-05-02 18:36:05 +02:00
// Increment failure counter
serverFailureCounts [ server . name ] ++ ;
// Check if we've reached the notification threshold
if ( serverFailureCounts [ server . name ] === NOTIFICATION _THRESHOLD ) {
await sendFCMNotification ( ` Server ${ server . name } is unreachable (after ${ NOTIFICATION _THRESHOLD } checks) ` , 'service_offline' ) ;
2025-05-01 09:32:25 +02:00
}
2025-05-02 18:36:05 +02:00
// Update previous status
previousServersStatus [ server . name ] = false ;
2025-04-17 16:11:04 +02:00
}
2025-05-01 09:32:25 +02:00
2025-04-17 16:11:04 +02:00
serversStatus [ server . name ] . lastChecked = new Date ( ) . toISOString ( ) ;
2025-04-29 22:02:28 +02:00
// Log server status to the appropriate folder
const timestamp = new Date ( ) . toISOString ( ) ;
const serverStatus = serversStatus [ server . name ] ;
const logFolder = serverStatus . online ? ONLINE _LOGS _DIR : OFFLINE _LOGS _DIR ;
const logFilePath = path . join ( logFolder , ` ${ server . name . replace ( /\s+/g , '_' ) } .log ` ) ;
// Create a human-readable log entry
const logEntry = ` [ ${ timestamp } ] Server: ${ server . name } ( ${ server . host } ) \n ` +
` Status: ${ serverStatus . online ? 'ONLINE' : 'OFFLINE' } \n ` +
` Response Time: ${ serverStatus . responseTime ? serverStatus . responseTime + 'ms' : 'N/A' } \n ` +
2025-05-02 18:36:05 +02:00
` Failure Count: ${ serverFailureCounts [ server . name ] } \n ` +
2025-04-29 22:02:28 +02:00
` ----------------------------------- \n ` ;
// Append to log file
fs . appendFile ( logFilePath , logEntry , ( err ) => {
if ( err ) {
console . error ( ` Error writing log file for ${ server . name } : ` , err ) ;
}
} ) ;
2025-04-17 16:11:04 +02:00
}
} catch ( error ) {
console . error ( "Error in checkServers function:" , error ) ;
}
}
async function checkPM2Services ( ) {
2025-05-01 09:37:56 +02:00
return new Promise ( ( resolve , reject ) => {
pm2 . connect ( function ( err ) {
if ( err ) {
console . error ( 'Error connecting to PM2:' , err ) ;
pm2 . disconnect ( ) ;
resolve ( ) ;
return ;
}
pm2 . list ( async ( err , list ) => {
if ( err ) {
console . error ( 'Error getting PM2 process list:' , err ) ;
pm2 . disconnect ( ) ;
resolve ( ) ;
return ;
}
try {
// Process each PM2 service sequentially with proper async handling
for ( const process of list ) {
const uptimeMs = process . pm2 _env . pm _uptime ?
Date . now ( ) - process . pm2 _env . pm _uptime :
null ;
const processName = process . name ;
const isNowOnline = process . pm2 _env . status === 'online' ;
// Check if we've seen this process before
if ( previousPM2Status [ processName ] === undefined ) {
// First time seeing this process - initialize and don't send notification
previousPM2Status [ processName ] = isNowOnline ;
2025-05-02 18:36:05 +02:00
pm2FailureCounts [ processName ] = 0 ;
2025-05-01 09:37:56 +02:00
console . log ( ` Initializing PM2 service status for ${ processName } : ${ isNowOnline ? 'online' : 'offline' } ` ) ;
2025-05-02 18:36:05 +02:00
} else {
if ( isNowOnline ) {
// Service is online - reset failure counter
pm2FailureCounts [ processName ] = 0 ;
// If service was previously offline, send online notification
if ( previousPM2Status [ processName ] === false ) {
await sendFCMNotification ( ` PM2 service ${ processName } is back online ` , 'service_online' ) ;
console . log ( ` PM2 service ${ processName } changed from offline to online ` ) ;
}
} else {
// Service is offline - increment failure counter
pm2FailureCounts [ processName ] ++ ;
// Only send notification if threshold is reached
if ( pm2FailureCounts [ processName ] === NOTIFICATION _THRESHOLD ) {
await sendFCMNotification ( ` PM2 service ${ processName } is offline (status: ${ process . pm2 _env . status } , after ${ NOTIFICATION _THRESHOLD } checks) ` , 'service_offline' ) ;
console . log ( ` PM2 service ${ processName } is offline for ${ NOTIFICATION _THRESHOLD } consecutive checks ` ) ;
}
}
2025-05-01 09:37:56 +02:00
}
// Update previous status
previousPM2Status [ processName ] = isNowOnline ;
// Update status object
pm2ServicesStatus [ processName ] = {
name : processName ,
id : process . pm _id ,
status : process . pm2 _env . status ,
cpu : process . monit ? process . monit . cpu : null ,
memory : process . monit ? process . monit . memory : null ,
uptime : uptimeMs ,
restarts : process . pm2 _env . restart _time ,
2025-05-02 18:36:05 +02:00
failureCount : pm2FailureCounts [ processName ] ,
2025-05-01 09:37:56 +02:00
lastChecked : new Date ( ) . toISOString ( )
} ;
}
} catch ( error ) {
console . error ( 'Error processing PM2 services:' , error ) ;
}
pm2 . disconnect ( ) ;
resolve ( ) ;
} ) ;
} ) ;
} ) ;
2025-04-17 16:11:04 +02:00
}
async function checkAll ( ) {
try {
await checkServers ( ) ;
await checkPM2Services ( ) ;
} catch ( error ) {
console . error ( "Error in checkAll function:" , error ) ;
}
}
// Initial check with error handling
try {
2025-04-29 22:02:28 +02:00
// Ensure log directories exist at startup
ensureLogDirectories ( ) ;
2025-04-17 16:11:04 +02:00
checkAll ( ) ;
} catch ( error ) {
console . error ( "Error during initial check:" , error ) ;
}
// Set interval with error handling
setInterval ( ( ) => {
try {
checkAll ( ) ;
} catch ( error ) {
console . error ( "Error during scheduled check:" , error ) ;
}
} , CHECK _INTERVAL ) ;
// Route with error handling
router . get ( "/" , ( req , res ) => {
try {
res . json ( {
servers : serversStatus ,
2025-05-02 18:36:05 +02:00
pm2Services : pm2ServicesStatus ,
serverFailureCounts : serverFailureCounts ,
pm2FailureCounts : pm2FailureCounts ,
notificationThreshold : NOTIFICATION _THRESHOLD
2025-04-17 16:11:04 +02:00
} ) ;
} catch ( error ) {
console . error ( "Error sending status response:" , error ) ;
res . status ( 500 ) . json ( { error : "Internal server error" } ) ;
}
} ) ;
// Add a simple health check endpoint
router . get ( "/health" , ( req , res ) => {
res . status ( 200 ) . send ( "OK" ) ;
} ) ;
module . exports = router ;