2018-12-17 16:37:19 +01:00
'use strict' ;
exports = module . exports = {
NotificationsError : NotificationsError ,
get : get ,
ack : ack ,
2019-01-04 17:13:52 +01:00
getAllPaged : getAllPaged ,
2018-12-17 17:35:19 +01:00
2019-02-27 16:10:54 -08:00
onEvent : onEvent ,
2019-02-28 14:52:14 -08:00
// NOTE: if you add an alert, be sure to add title below
ALERT _BACKUP _CONFIG : 'backupConfig' ,
ALERT _DISK _SPACE : 'diskSpace' ,
ALERT _MAIL _STATUS : 'mailStatus' ,
ALERT _REBOOT : 'reboot' ,
alert : alert
2018-12-17 16:37:19 +01:00
} ;
2019-02-28 14:52:14 -08:00
let assert = require ( 'assert' ) ,
2018-12-17 17:35:19 +01:00
async = require ( 'async' ) ,
2019-01-10 12:00:04 +01:00
config = require ( './config.js' ) ,
2018-12-17 16:37:19 +01:00
DatabaseError = require ( './databaseerror.js' ) ,
debug = require ( 'debug' ) ( 'box:notifications' ) ,
2019-02-27 16:10:54 -08:00
eventlog = require ( './eventlog.js' ) ,
2018-12-17 17:35:19 +01:00
mailer = require ( './mailer.js' ) ,
2018-12-17 16:37:19 +01:00
notificationdb = require ( './notificationdb.js' ) ,
2019-03-01 14:40:28 -08:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
2019-01-19 13:22:29 +01:00
safe = require ( 'safetydance' ) ,
2018-12-17 17:35:19 +01:00
users = require ( './users.js' ) ,
2018-12-17 16:37:19 +01:00
util = require ( 'util' ) ;
2019-02-28 14:52:14 -08:00
// These titles are matched for upsert
const ALERT _TITLES = {
backupConfig : 'Backup configuration is unsafe' ,
diskSpace : 'Out of Disk Space' ,
mailStatus : 'Email is not configured properly' ,
reboot : 'Reboot Required'
} ;
2018-12-17 16:37:19 +01:00
function NotificationsError ( reason , errorOrMessage ) {
assert . strictEqual ( typeof reason , 'string' ) ;
assert ( errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined' ) ;
Error . call ( this ) ;
Error . captureStackTrace ( this , this . constructor ) ;
this . name = this . constructor . name ;
this . reason = reason ;
if ( typeof errorOrMessage === 'undefined' ) {
this . message = reason ;
} else if ( typeof errorOrMessage === 'string' ) {
this . message = errorOrMessage ;
} else {
this . message = 'Internal error' ;
this . nestedError = errorOrMessage ;
}
}
util . inherits ( NotificationsError , Error ) ;
NotificationsError . INTERNAL _ERROR = 'Internal Error' ;
NotificationsError . NOT _FOUND = 'Not Found' ;
2019-02-28 14:59:33 -08:00
function add ( userId , eventId , title , message , callback ) {
2018-12-17 16:37:19 +01:00
assert . strictEqual ( typeof userId , 'string' ) ;
2019-02-06 16:18:12 +01:00
assert ( typeof eventId === 'string' || eventId === null ) ;
2018-12-17 16:37:19 +01:00
assert . strictEqual ( typeof title , 'string' ) ;
assert . strictEqual ( typeof message , 'string' ) ;
2018-12-17 17:40:53 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-17 16:37:19 +01:00
2019-02-28 14:59:33 -08:00
debug ( 'add: ' , userId , title ) ;
2018-12-17 16:37:19 +01:00
notificationdb . add ( {
userId : userId ,
2019-01-19 13:22:29 +01:00
eventId : eventId ,
2018-12-17 16:37:19 +01:00
title : title ,
2019-02-28 14:59:33 -08:00
message : message
2018-12-17 16:37:19 +01:00
} , function ( error , result ) {
2019-01-21 08:51:04 +01:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND , error . message ) ) ;
2018-12-17 16:37:19 +01:00
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
callback ( null , { id : result } ) ;
} ) ;
}
function get ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
notificationdb . get ( id , function ( error , result ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND ) ) ;
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
callback ( null , result ) ;
} ) ;
}
function ack ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
notificationdb . update ( id , { acknowledged : true } , function ( error ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND ) ) ;
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
// if acknowledged === null we return all, otherwise yes or no based on acknowledged as a boolean
2019-01-04 17:13:52 +01:00
function getAllPaged ( userId , acknowledged , page , perPage , callback ) {
2018-12-17 16:37:19 +01:00
assert . strictEqual ( typeof userId , 'string' ) ;
assert ( acknowledged === null || typeof acknowledged === 'boolean' ) ;
assert . strictEqual ( typeof page , 'number' ) ;
assert . strictEqual ( typeof perPage , 'number' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
notificationdb . listByUserIdPaged ( userId , page , perPage , function ( error , result ) {
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
if ( acknowledged === null ) return callback ( null , result ) ;
callback ( null , result . filter ( function ( r ) { return r . acknowledged === acknowledged ; } ) ) ;
} ) ;
}
2018-12-17 17:35:19 +01:00
2019-01-07 14:56:43 +01:00
// Calls iterator with (admin, callback)
2019-01-17 13:36:54 +01:00
function actionForAllAdmins ( skippingUserIds , iterator , callback ) {
assert ( Array . isArray ( skippingUserIds ) ) ;
2019-01-07 14:56:43 +01:00
assert . strictEqual ( typeof iterator , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
users . getAllAdmins ( function ( error , result ) {
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
2019-01-17 13:36:54 +01:00
// filter out users we want to skip (like the user who did the action or the user the action was performed on)
result = result . filter ( function ( r ) { return skippingUserIds . indexOf ( r . id ) === - 1 ; } ) ;
2019-01-07 14:56:43 +01:00
async . each ( result , iterator , callback ) ;
} ) ;
}
2019-01-19 13:22:29 +01:00
function userAdded ( performedBy , eventId , user ) {
2019-01-17 13:36:54 +01:00
assert . strictEqual ( typeof performedBy , 'string' ) ;
2019-01-19 13:22:29 +01:00
assert . strictEqual ( typeof eventId , 'string' ) ;
2018-12-17 17:35:19 +01:00
assert . strictEqual ( typeof user , 'object' ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ performedBy , user . id ] , function ( admin , callback ) {
2019-01-07 14:56:43 +01:00
mailer . userAdded ( admin . email , user ) ;
2019-02-28 14:59:33 -08:00
add ( admin . id , eventId , 'User added' , ` User ${ user . fallbackEmail } was added ` , callback ) ;
2019-01-17 13:12:26 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2018-12-17 17:35:19 +01:00
}
2019-01-07 12:57:57 +01:00
2019-01-19 13:22:29 +01:00
function userRemoved ( performedBy , eventId , user ) {
2019-01-17 13:36:54 +01:00
assert . strictEqual ( typeof performedBy , 'string' ) ;
2019-01-19 13:22:29 +01:00
assert . strictEqual ( typeof eventId , 'string' ) ;
2019-01-10 12:00:04 +01:00
assert . strictEqual ( typeof user , 'object' ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ performedBy , user . id ] , function ( admin , callback ) {
2019-01-10 12:00:04 +01:00
mailer . userRemoved ( admin . email , user ) ;
2019-02-28 14:59:33 -08:00
add ( admin . id , eventId , 'User removed' , ` User ${ user . username || user . email || user . fallbackEmail } was removed ` , callback ) ;
2019-01-17 13:12:26 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-10 12:00:04 +01:00
}
2019-01-19 13:22:29 +01:00
function adminChanged ( performedBy , eventId , user ) {
2019-01-17 13:36:54 +01:00
assert . strictEqual ( typeof performedBy , 'string' ) ;
2019-01-10 12:00:04 +01:00
assert . strictEqual ( typeof user , 'object' ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ performedBy , user . id ] , function ( admin , callback ) {
2019-01-17 13:12:26 +01:00
mailer . adminChanged ( admin . email , user , user . admin ) ;
2019-02-28 14:59:33 -08:00
add ( admin . id , eventId , 'Admin status change' , ` User ${ user . username || user . email || user . fallbackEmail } ${ user . admin ? 'is now an admin' : 'is no more an admin' } ` , callback ) ;
2019-01-17 13:12:26 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-10 12:00:04 +01:00
}
2019-01-19 13:22:29 +01:00
function oomEvent ( eventId , program , context ) {
assert . strictEqual ( typeof eventId , 'string' ) ;
2019-01-07 12:57:57 +01:00
assert . strictEqual ( typeof program , 'string' ) ;
2019-01-08 14:20:08 +01:00
assert . strictEqual ( typeof context , 'object' ) ;
2019-01-07 12:57:57 +01:00
2019-01-10 12:00:04 +01:00
// also send us a notification mail
if ( config . provider ( ) === 'caas' ) mailer . oomEvent ( 'support@cloudron.io' , program , JSON . stringify ( context , null , 4 ) ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ ] , function ( admin , callback ) {
2019-01-10 12:00:04 +01:00
mailer . oomEvent ( admin . email , program , JSON . stringify ( context , null , 4 ) ) ;
2019-01-08 14:20:08 +01:00
var message ;
if ( context . app ) message = ` The application ${ context . app . manifest . title } with id ${ context . app . id } ran out of memory. ` ;
else message = ` The container with id ${ context . details . id } ran out of memory ` ;
2019-02-28 14:59:33 -08:00
add ( admin . id , eventId , 'Process died out-of-memory' , message , callback ) ;
2019-01-17 15:31:34 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-07 12:57:57 +01:00
}
2019-01-07 13:01:27 +01:00
2019-02-11 12:32:02 -08:00
function appUp ( eventId , app ) {
assert . strictEqual ( typeof eventId , 'string' ) ;
assert . strictEqual ( typeof app , 'object' ) ;
// also send us a notification mail
if ( config . provider ( ) === 'caas' ) mailer . appDied ( 'support@cloudron.io' , app ) ;
actionForAllAdmins ( [ ] , function ( admin , callback ) {
mailer . appUp ( admin . email , app ) ;
2019-02-28 14:59:33 -08:00
add ( admin . id , eventId , ` App ${ app . fqdn } is back online ` , ` The application ${ app . manifest . title } installed at ${ app . fqdn } is back online. ` , callback ) ;
2019-02-11 12:32:02 -08:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
}
2019-01-19 13:22:29 +01:00
function appDied ( eventId , app ) {
assert . strictEqual ( typeof eventId , 'string' ) ;
2019-01-07 13:01:27 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-01-10 12:00:04 +01:00
// also send us a notification mail
if ( config . provider ( ) === 'caas' ) mailer . appDied ( 'support@cloudron.io' , app ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ ] , function ( admin , callback ) {
2019-01-10 12:00:04 +01:00
mailer . appDied ( admin . email , app ) ;
2019-02-28 14:59:33 -08:00
add ( admin . id , eventId , ` App ${ app . fqdn } is down ` , ` The application ${ app . manifest . title } installed at ${ app . fqdn } is not responding. ` , callback ) ;
2019-01-17 15:49:04 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-07 13:01:27 +01:00
}
2019-01-10 13:57:47 +01:00
2019-01-19 13:31:31 +01:00
function processCrash ( eventId , processName , crashLogFile ) {
2019-01-19 13:22:29 +01:00
assert . strictEqual ( typeof eventId , 'string' ) ;
assert . strictEqual ( typeof processName , 'string' ) ;
assert . strictEqual ( typeof crashLogFile , 'string' ) ;
2019-01-10 13:57:47 +01:00
2019-01-19 13:22:29 +01:00
var subject = ` ${ processName } exited unexpectedly ` ;
2019-03-01 14:40:28 -08:00
var crashLogs = safe . fs . readFileSync ( path . join ( paths . CRASH _LOG _DIR , crashLogFile ) , 'utf8' ) || ` No logs found at ${ crashLogFile } ` ;
2019-01-10 13:57:47 +01:00
// also send us a notification mail
2019-01-19 13:22:29 +01:00
if ( config . provider ( ) === 'caas' ) mailer . unexpectedExit ( 'support@cloudron.io' , subject , crashLogs ) ;
2019-01-10 13:57:47 +01:00
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ ] , function ( admin , callback ) {
2019-01-19 13:22:29 +01:00
mailer . unexpectedExit ( admin . email , subject , crashLogs ) ;
2019-03-01 14:40:28 -08:00
add ( admin . id , eventId , subject , ` The service has been restarted automatically. Crash logs are available [here](/logs.html?crash= ${ crashLogFile } ) ` , callback ) ;
2019-01-19 13:22:29 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-10 13:57:47 +01:00
}
2019-01-19 13:30:24 +01:00
function apptaskCrash ( eventId , appId , crashLogFile ) {
assert . strictEqual ( typeof eventId , 'string' ) ;
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof crashLogFile , 'string' ) ;
var subject = ` Apptask for ${ appId } crashed ` ;
2019-01-19 15:23:54 +01:00
var crashLogs = safe . fs . readFileSync ( crashLogFile , 'utf8' ) || ` No logs found at ${ crashLogFile } ` ;
2019-01-19 13:30:24 +01:00
// also send us a notification mail
if ( config . provider ( ) === 'caas' ) mailer . unexpectedExit ( 'support@cloudron.io' , subject , crashLogs ) ;
actionForAllAdmins ( [ ] , function ( admin , callback ) {
mailer . unexpectedExit ( admin . email , subject , crashLogs ) ;
2019-02-28 14:59:33 -08:00
add ( admin . id , eventId , subject , 'Detailed logs have been sent to your email address.' , callback ) ;
2019-01-19 13:30:24 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
}
2019-02-06 15:47:31 +01:00
2019-02-28 14:52:14 -08:00
function upsert ( userId , eventId , title , message , callback ) {
assert . strictEqual ( typeof userId , 'string' ) ;
assert ( typeof eventId === 'string' || eventId === null ) ;
assert . strictEqual ( typeof title , 'string' ) ;
2019-02-06 15:47:31 +01:00
assert . strictEqual ( typeof message , 'string' ) ;
2019-02-28 14:52:14 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-02-06 15:47:31 +01:00
2019-02-28 14:52:14 -08:00
debug ( 'upsert: ' , userId , title , message ) ;
2019-02-06 15:47:31 +01:00
2019-02-28 14:52:14 -08:00
notificationdb . upsert ( {
userId : userId ,
eventId : eventId ,
title : title ,
message : message ,
acknowledged : ! message
} , function ( error , result ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND , error . message ) ) ;
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
2019-02-06 14:58:45 -08:00
2019-02-28 14:52:14 -08:00
callback ( null , { id : result } ) ;
2019-02-06 14:58:45 -08:00
} ) ;
}
2019-02-28 14:52:14 -08:00
function alert ( id , message , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
2019-02-19 09:19:56 -08:00
assert . strictEqual ( typeof message , 'string' ) ;
2019-02-28 14:52:14 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-02-19 09:19:56 -08:00
2019-02-28 14:52:14 -08:00
const title = ALERT _TITLES [ id ] ;
if ( ! title ) return callback ( ) ;
2019-02-06 15:47:31 +01:00
actionForAllAdmins ( [ ] , function ( admin , callback ) {
2019-02-28 14:52:14 -08:00
upsert ( admin . id , null , title , message , callback ) ;
2019-02-06 15:47:31 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-02-28 14:52:14 -08:00
callback ( ) ;
2019-02-06 15:47:31 +01:00
}
2019-02-27 16:10:54 -08:00
function onEvent ( id , action , source , data , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof action , 'string' ) ;
assert . strictEqual ( typeof source , 'object' ) ;
assert . strictEqual ( typeof data , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
// decide if we want to add notifications as well
if ( action === eventlog . ACTION _USER _ADD ) {
userAdded ( source . userId , id , data . user ) ;
} else if ( action === eventlog . ACTION _USER _REMOVE ) {
userRemoved ( source . userId , id , data . user ) ;
} else if ( action === eventlog . ACTION _USER _UPDATE && data . adminStatusChanged ) {
adminChanged ( source . userId , id , data . user ) ;
} else if ( action === eventlog . ACTION _APP _OOM ) {
oomEvent ( id , data . app ? data . app . id : data . containerId , { app : data . app , details : data } ) ;
} else if ( action === eventlog . ACTION _APP _DOWN ) {
appDied ( id , data . app ) ;
} else if ( action === eventlog . ACTION _APP _UP ) {
appUp ( id , data . app ) ;
} else if ( action === eventlog . ACTION _APP _TASK _CRASH ) {
apptaskCrash ( id , data . appId , data . crashLogFile ) ;
} else if ( action === eventlog . ACTION _PROCESS _CRASH ) {
processCrash ( id , data . processName , data . crashLogFile ) ;
} else {
// no notification
}
callback ( ) ;
}