diff --git a/README.md b/README.md index 9c32fd5..57b3190 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Disk-Space-Monitor +# disk-space-monitor [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-yellow)](https://raw.githubusercontent.com/blu3mania/disk-space-monitor/main/LICENSE) [![node.js 10+](https://img.shields.io/badge/node.js-10.16.3-blue?logo=node.js)](https://nodejs.org/en/) [![Latest Release](https://img.shields.io/github/v/release/blu3mania/disk-space-monitor)](https://github.com/blu3mania/disk-space-monitor/releases/latest) @@ -7,9 +7,8 @@ Monitor disk space usage and notify user by email or system notification. It can be run as a standalone application or as a system service. -**Note**, the instructions below uses yarn as package manager so it can install the latest minimist package -that doesn't have [Prototype Pollution vulnerability](https://www.npmjs.com/advisories/1179). Though, if you -prefer you can use npm as well, just replace "yarn" with "npm" in any command. +**Note**, this package is written as ES Module starting with 2.0. For CommonJS version, use version 1.x from +CommonJS branch. ## Run these steps first: @@ -18,6 +17,25 @@ prefer you can use npm as well, just replace "yarn" with "npm" in any command. instructions](https://github.com/nodejs/node-gyp#installation). 2. Edit src/settings.json. + * service defines service parameters when installed as a system service: + * name is the service name to be used. + * account info is optional. If provided, the service will be running as the specified account. These properties + can be provided: + * name is account's name, when running on Windows + * password is account's password, when running on Windows + * domain is optional, and should be provided if the account is a domain account when running on Windows + * user is the user name, when running on Linux + * group is the group name, when running on Linux + ``` + "service": { + "name": "Disk Space Monitor", + "account": { + "name": "{account name}", + "password": "{account password}", + "domain": "{account domain}" + } + }, + ``` * disks lists all the disks this script needs to montior. * path is the path to the disk. On Windows it's usaully the drive letter followed by a colon, e.g. "C:". On Linux it is usually the mounted file system path, e.g. "/dev". @@ -54,20 +72,20 @@ prefer you can use npm as well, just replace "yarn" with "npm" in any command. * to, cc, bcc are the email recipient. Multiple email addresses can be used with comma as delimiter. * smtp defines the SMTP server options used by nodemailer. Please refer to [nodemail's SMTP Transport page](https://nodemailer.com/smtp/) for details. -3. Run "yarn install". If running on Windows, accept UAC prompts if any (there could be up to 4). +3. Run "npm install". If running on Windows, accept UAC prompts if any (there could be up to 4). - **Note**, this step installs the script as a system service. If it's not desired, run "yarn run uninstall" afterwards. + **Note**, this step installs the script as a system service. If it's not desired, run "npm run uninstall" afterwards. ## To run the script manually: -Run "yarn start" or "node src/app.js". +Run "npm start" or "node src/app.js". ## To install and run the script as a system service: -Run "yarn run install" or "node src/install-service.js". If running on Windows, accept UAC prompts if any (there could be up to 4). +Run "npm run install" or "node src/install-service.js". If running on Windows, accept UAC prompts if any (there could be up to 4). **Note**, if settings.json is updated when service is running, restart the server (on Windows, this can be done from Services control panel). ## To uninstall the system service: -Run "yarn run uninstall" or "node src/uninstall-service.js". If running on Windows, accept UAC prompts if any (there could be up to 4). +Run "npm run uninstall" or "node src/uninstall-service.js". If running on Windows, accept UAC prompts if any (there could be up to 4). diff --git a/package.json b/package.json index 7038c4b..987cac9 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "disk-space-monitor", - "version": "1.1.0", + "type": "module", + "version": "2.0.0", "description": "Monitor disk space usage and notify user by email or system notification", - "main": "src/app.js", + "exports": "src/app.js", "scripts": { "start": "node src/app.js", "preinstall": "npm install npm-platform-dependencies && npmpd", @@ -11,8 +12,7 @@ }, "keywords": [ "disk space", - "disk monitor", - "disk space monitor" + "monitor" ], "author": "blu3mania ", "license": "Apache-2.0", @@ -25,7 +25,7 @@ "url": "https://github.com/blu3mania/disk-space-monitor.git" }, "dependencies": { - "chalk": "^4.1.2", + "chalk": "^5.0.1", "diskusage": "^1.1.3", "node-notifier": "^10.0.1", "nodemailer": "^6.7.8" diff --git a/src/app.js b/src/app.js index 4a17ea4..5677f61 100644 --- a/src/app.js +++ b/src/app.js @@ -1,16 +1,17 @@ -'use strict'; - -const os = require('os'); -const path = require('path'); -const notifier = require('node-notifier'); -const diskusage = require('diskusage'); -const nodemailer = require('nodemailer'); -const { +import os from 'os'; +import path from 'path'; +import url from 'url'; + +import diskusage from 'diskusage'; +import mailer from 'nodemailer'; +import notifier from 'node-notifier'; + +import { error, warning, info, - verbose } = require('./print.js'); -const settings = require('./settings.json'); + verbose } from './print.js'; +import settings from './settings.json' assert {type: 'json'}; const NotificationType = { Email: 'email', @@ -22,14 +23,20 @@ const EmailFormat = { Text: 'text', }; -const prefixes = [ +// Supported unit prefixes +const UnitPrefix = [ 'k', 'm', 'g', 't', 'p', + 'e', + 'z', + 'y', ]; +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); + let emailTransporter = null; main(); @@ -38,7 +45,7 @@ function main() { verbose('Starting...'); if (settings.notificationTypes.find(type => type.toLowerCase() === NotificationType.Email)) { - emailTransporter = nodemailer.createTransport(settings.email.smtp); + emailTransporter = mailer.createTransport(settings.email.smtp); } checkDiskUsage(); @@ -58,7 +65,7 @@ function checkDiskUsage() { return Promise.reject(`Invalid configuration "${disk.threshold}" for disk "${disk.path}".`); } disk.notificationTriggered = false; - verbose(`Disk "${disk.path}" threshold "${disk.threshold}", which is ${disk.thresholdInBytes} bytes.`); + verbose(`Disk "${disk.path}" threshold "${disk.threshold}", which is ${disk.thresholdInBytes.toLocaleString()} bytes.`); } checkDiskFreeSpace(disk, diskInfo); }) @@ -106,7 +113,7 @@ function calculateThresholdInBytes(disk, diskInfo) { base = 1024; prefixToCheck = disk.threshold.slice(-3, -2).toLowerCase(); } else { - // SI prefix + // Decimal (SI) prefix base = 1000; } } else { @@ -115,7 +122,7 @@ function calculateThresholdInBytes(disk, diskInfo) { } if (prefixToCheck !== null) { - const exponent = prefixes.findIndex(prefix => prefix === prefixToCheck) + 1; + const exponent = UnitPrefix.findIndex(prefix => prefix === prefixToCheck) + 1; if (exponent === 0) { invalidConfig = true; } else { diff --git a/src/install-service.js b/src/install-service.js index c511dc5..8f96024 100644 --- a/src/install-service.js +++ b/src/install-service.js @@ -1,39 +1,82 @@ -'use strict'; +import path from 'path'; +import url from 'url'; -const path = require('path'); -const Service = require(process.platform === 'win32' ? 'node-windows' : process.platform === 'darwin' ? 'node-mac' : 'node-linux').Service; -const { +import { warning, info, - verbose } = require('./print.js'); - -// Create a new service object. -const svc = new Service({ - name: 'Disk Space Monitor', - description: 'Monotr disk space usage and notify user by email or Windows Toast.', - script: `${path.join(__dirname, 'app.js')}`, - nodeOptions: [ - '--harmony', - '--max_old_space_size=4096' - ] -}); - -// Listen for the "install" event, which indicates the process is available as a service. -svc.on('install', () => { - verbose('Service installed.'); - info('Starting service, please accept UAC prompts if any...'); - svc.start(); -}); - -svc.on('start', () => { - verbose('Service started.'); -}); - -svc.on('alreadyinstalled', () => { - warning('Service is already installed!'); - info('Starting the service in case it is not running, please accept UAC prompts if any...'); - svc.start(); -}); - -info('Installing service, please accept UAC prompts if any...'); -svc.install(); + verbose } from './print.js'; +import settings from './settings.json' assert {type: 'json'}; + +main(); + +function main() { + // Dynamically import the module we need depending on current OS + switch (process.platform) { + case 'win32': + import('node-windows') + .then(module => installService(module.Service)); + break; + + case 'darwin': + import('node-mac') + .then(module => installService(module.Service)); + break; + + default: + import('node-linux') + .then(module => installService(module.Service)); + break; + } +} + +function installService(Service) { + // Create a new service object. + const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); + const svc = new Service({ + name: settings.service?.name ?? 'Disk Space Monitor', + description: 'Monotr disk space usage and notify user by email or Windows Toast.', + script: `${path.join(__dirname, 'app.js')}`, + nodeOptions: [ + '--harmony', + '--max_old_space_size=4096' + ] + }); + + if (process.platform === 'win32') { + if (settings.service?.account?.name && settings.service?.account?.password) { + svc.logOnAs.account = settings.service.account.name; + svc.logOnAs.password = settings.service.account.password; + if (settings.service?.account?.domain) { + svc.logOnAs.domain = settings.service.account.domain; + } + } + } else if (process.platform !== 'darwin') { + if (settings.service?.account?.user) { + svc.user = settings.service.account.user; + } + + if (settings.service?.account?.group) { + svc.group = settings.service.account.group; + } + } + + // Listen for the "install" event, which indicates the process is available as a service. + svc.on('install', () => { + verbose('Service installed.'); + info('Starting service, please accept UAC prompts if any...'); + svc.start(); + }); + + svc.on('start', () => { + verbose('Service started.'); + }); + + svc.on('alreadyinstalled', () => { + warning('Service is already installed!'); + info('Starting the service in case it is not running, please accept UAC prompts if any...'); + svc.start(); + }); + + info('Installing service, please accept UAC prompts if any...'); + svc.install(); +} diff --git a/src/print.js b/src/print.js index 0764775..a8e226e 100644 --- a/src/print.js +++ b/src/print.js @@ -1,6 +1,4 @@ -'use strict'; - -const chalk = require('chalk'); +import chalk from 'chalk'; const dateTimeFormatOprions = { year: 'numeric', @@ -12,31 +10,54 @@ const dateTimeFormatOprions = { hour12: false }; +/** Internal method: format a message so it shows with timestamp. */ function formatMessage(msg) { return `[${new Intl.DateTimeFormat('en-US', dateTimeFormatOprions).format(new Date())}] ${typeof msg === 'string' ? msg : JSON.stringify(msg, null, 2)}`; } +/** + * Prints a message in console. + * @param {string|Object} msg - The message to be printed. It can be a non-string type, in which case it will be serialized before printing. + * @param {Chalk} color - (Optional) The color of the message to be printed. + * If not provided, default color white is used. + */ function print(msg, color = chalk.white) { console.log(color(formatMessage(msg))); } +/** + * Prints an error message in console. + * @param {string|Object} msg - The error message to be printed. It can be a non-string type, in which case it will be serialized before printing. + */ function error(msg) { console.log(chalk.red(formatMessage(msg))); } +/** + * Prints a warning message in console. + * @param {string|Object} msg - The warning message to be printed. It can be a non-string type, in which case it will be serialized before printing. + */ function warning(msg) { console.log(chalk.yellow(formatMessage(msg))); } +/** + * Prints an infomation message in console. + * @param {string|Object} msg - The information message to be printed. It can be a non-string type, in which case it will be serialized before printing. + */ function info(msg) { console.log(chalk.cyan(formatMessage(msg))); } +/** + * Prints a verbose message in console. + * @param {string|Object} msg - The verbose message to be printed. It can be a non-string type, in which case it will be serialized before printing. + */ function verbose(msg) { console.log(chalk.green(formatMessage(msg))); } -module.exports = { +export { print, error, warning, diff --git a/src/settings.json b/src/settings.json index acc2930..f1229ab 100644 --- a/src/settings.json +++ b/src/settings.json @@ -1,4 +1,7 @@ { + "service": { + "name": "Disk Space Monitor" + }, "disks": [ { "path": "C:", diff --git a/src/uninstall-service.js b/src/uninstall-service.js index f1cc304..93ba3e1 100644 --- a/src/uninstall-service.js +++ b/src/uninstall-service.js @@ -1,21 +1,46 @@ -'use strict'; +import path from 'path'; +import url from 'url'; -const path = require('path'); -const Service = require(process.platform === 'win32' ? 'node-windows' : process.platform === 'darwin' ? 'node-mac' : 'node-linux').Service; -const { +import { info, - verbose } = require('./print.js'); + verbose } from './print.js'; +import settings from './settings.json' assert {type: 'json'}; -// Create a new service object. -const svc = new Service({ - name: 'Disk Space Monitor', - script: `${path.join(__dirname, 'app.js')}`, -}); +main(); -// Listen for the "uninstall" event so we know when it's done. -svc.on('uninstall', () => { - verbose('Service uninstalled.'); -}); +function main() { + // Dynamically import the module we need depending on current OS + switch (process.platform) { + case 'win32': + import('node-windows') + .then(module => uninstallService(module.Service)); + break; -info('Uninstalling service, please accept UAC prompts if any...'); -svc.uninstall(); + case 'darwin': + import('node-mac') + .then(module => uninstallService(module.Service)); + break; + + default: + import('node-linux') + .then(module => uninstallService(module.Service)); + break; + } +} + +function uninstallService(Service) { + // Create a new service object. + const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); + const svc = new Service({ + name: settings.service?.name ?? 'Disk Space Monitor', + script: `${path.join(__dirname, 'app.js')}`, + }); + + // Listen for the "uninstall" event so we know when it's done. + svc.on('uninstall', () => { + verbose('Service uninstalled.'); + }); + + info('Uninstalling service, please accept UAC prompts if any...'); + svc.uninstall(); +}