cna-monitor
Version:
监控域名/IP地址是否可达,并记录可访问性
742 lines (697 loc) • 26.2 kB
JavaScript
// const comm = require('fa-comm');
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const configFile = path.join(__dirname, '../config/config');
const emailFile = path.join(__dirname, '../config/email');
const keysFile = path.join(__dirname, '../config/keys');
const timeFile = path.join(__dirname, '../config/time');
const outFile = path.join(__dirname, '../out/result');
const cwd = path.join(__dirname, '../lib/main/');const packageFile = path.join(__dirname, '../package.json');
const packageJson = JSON.parse(fs.readFileSync(packageFile));
const iconv = require('iconv-lite');
require('../lib/proto/string');
require('../lib/proto/array');
require('../lib/proto/date');
const readlineSync = require('readline-sync');
const defaultConfig = require('../default/config');
const log = require('./log');
const archiver = require('archiver');
const random = require('./random');
const slog = require('single-line-log').stdout;
const axios = require('axios');
const cheerio = require('cheerio');
/**
* 将HTML文本转换成Jquery对象
* @param {*} html
* @param {string} [encoding='utf-8']
* @returns
*/
const toJquery = (html, encoding = 'utf-8') => {
const buf = iconv.decode(html, encoding);
return cheerio.load(buf);
}
/**
* 将文件夹打包压缩成zip
* @param {Array<String>} files 被打包的文件全路径
* @param {String} filepath 被打包的文件夹
* @param {String} filename 打包压缩后的文件名全路径,如:/xxx.zip
* @returns
*/
function zip(filepath, filename) {
return new Promise(function (resolve, reject) {
let output = fs.createWriteStream(filename);
let archive = archiver('zip', {
zlib: { level: 9 }
});
output.on('close', function () {
resolve();
});
archive.on('warning', function (err) {
reject(err);
});
archive.on('error', function (err) {
reject(err);
});
archive.pipe(output);
archive.directory(filepath, false);
archive.finalize();
});
}
exports.zip = zip;
/**
* 获取本机IP(内网)地址
*/
function getLocalIp() {
var interfaces = require('os').networkInterfaces();
for (var devName in interfaces) {
var iface = interfaces[devName];
for (var i = 0; i < iface.length; i++) {
var alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
return alias.address;
}
}
}
}
exports.getLocalIp = getLocalIp;
/**
* 递归删除文件夹
*/
var deleteFolderRecursive = function (path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach(function (file) {
var curPath = path + "/" + file;
if (fs.statSync(curPath).isDirectory()) { // recurse
deleteFolderRecursive(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
};
exports.unlinkSync = exports.deleteFolderRecursive = deleteFolderRecursive;
/**
* 将字节大小转换成直观的单位显示
*/
const sizeFormat = (bytes, minKB = true) => {
if (isNaN(bytes)) {
return '';
}
var symbols = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var exp = Math.floor(Math.log(bytes) / Math.log(2));
if (exp < 1) {
exp = 0;
}
var i = Math.floor(exp / 10);
if (i < symbols.length) {
bytes = bytes / Math.pow(2, 10 * i);
if (bytes.toString().length > bytes.toFixed(2).toString().length) {
bytes = bytes.toFixed(2);
}
if (bytes >= 1000) {
bytes = parseFloat(bytes / 1024).toFixed(2);
i += 1;
}
if (i == 0 && minKB) {
i += 1;
bytes = parseFloat(bytes / 1024).toFixed(2);
bytes = bytes != '0.00' ? bytes : '0.01';
}
return bytes + symbols[i];
} else {
i = symbols.length - 1;
bytes = bytes / Math.pow(2, 10 * i);
if (bytes.toString().length > bytes.toFixed(2).toString().length) {
bytes = bytes.toFixed(2);
}
return bytes + symbols[i];
}
}
exports.sizeFormat = sizeFormat;
const promisify = (fn, receiver) => {
return (...args) => {
return new Promise((resolve, reject) => {
fn.apply(receiver, [...args, (err, res) => {
return err ? reject(err) : resolve(res);
}]);
});
};
};
exports.promisify = promisify;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
exports.sleep = sleep;
function getProcessPid(command) {
const cmd = process.platform === 'win32' ? 'tasklist' : 'ps aux'
let tasklist = child_process.execSync(cmd);
tasklist = iconv.decode(tasklist, "GBK").split('\n');
let pids = [];
for (let i = 0; i < tasklist.length; i++) {
const p = tasklist[i].trim().split(/\s+/);
const pname = p[process.platform == 'win32' ? 0 : p.length - 1];
const pid = p[1];
if (pname.toLowerCase().indexOf(command) >= 0 && parseInt(pid)) {
pids.push(pid);
}
}
return pids;
}
exports.getProcessPid = getProcessPid;
function convertArgs() {
function getArgs(argArray) {
const args = {};
let lastKey = null;
argArray.forEach(item => {
if (/^-\w/g.test(item)) {
lastKey = item.replace(/^-/g, '')
args[lastKey] = true;
} else if (lastKey) {
args[lastKey] = item;
}
});
return args;
}
const args = process.argv.splice(2);
const debug = args.find(item => item === "-debug") ? true : false;
if (args[0] == "help" || args[0] == "-h") {
return { help: true, debug };
} else if (args[0] == "version" || args[0] == "-v") {
return { version: true, debug };
} else if (args[0] == "logs") {
return { logs: true, debug, ...getArgs(args) };
} else if (args[0] == 'docker') {
return { docker: true, debug };
} else if (args[0] == "backup") {
return { backup: true, debug, path: args[1], ...getArgs(args) };
} else if (args[0] == "import") {
return { import: true, debug, path: args[1], ...getArgs(args) };
} else if (args[0] == 'config') {
return { config: true, debug, ...getArgs(args) };
} else if (args[0] == 'remove') {
return { remove: true, debug };
} else if (args[0] == 'email') {
return { email: true, debug, ...getArgs(args) };
} else if (args[0] == "init") {
return { init: true, debug, ...getArgs(args) };
} else if (args[0] == "keys") {
return { keys: true, debug, ...getArgs(args) };
} else if (args[0] == "upgrade") {
return { upgrade: true, debug };
} else if (args[0] == "restart") {
return { restart: true, debug };
} else if (args[0] == "start") {
return { start: true, debug };
} else if (args[0] == "stop") {
return { stop: true, debug };
} else if (args[0] == "status") {
return { status: true, debug };
} else if (args[0] == "add") {
return { add: true, ip: args[1], debug, ...getArgs(args) };
} else if (args[0] == "delete") {
return { delete: true, ip: args[1], debug, ...getArgs(args) };
} else if (args[0] == "list" || args[0] == "-l") {
return { list: true, debug, ...getArgs(args) };
} else {
return { debug, ...getArgs(args) };
}
} exports.convertArgs = convertArgs;
function init(args) {
fs.writeFileSync(configFile, JSON.stringify({ t: args.t > 0 ? args.t : 1, p: args.p || 10, m: args.m || 1, n: args.n || "" }).encrypt());
if (fs.existsSync(keysFile)) {
fs.unlinkSync(keysFile);
}
if (fs.existsSync(outFile)) {
fs.unlinkSync(outFile);
}
}
exports.init = init;
/**
* 检测是否有进程在运行
* @returns
*/
function checkProcess() {
let pids = [];
const cnam = getProcessPid('cnam.js');
const cna = getProcessPid('cna.js');
if (cnam && cnam.length) {
cnam && cnam.length && pids.push(...cnam);
}
if (cna && cna.length) {
cna && cna.length && pids.push(...cna);
}
return pids.length ? pids : null;
}
exports.checkProcess = checkProcess;
function help(args) {
console.group(`-------------- 网络可达性监控程序(V${packageJson.version}) -----------------`);
console.info(`help\t显示帮助`);
console.info(`-h\t显示帮助`);
console.info('');
console.info(`init\t初始化服务(停止并清空所有监控信息)`);
console.info(`\t支持的参数:`);
console.info(`\t\t-t 监控频率,单位分钟,默认1,如:cna-monitor init -t 1`);
console.info(`\t\t-p 监控列表每次显示的数量,单位行,默认10,如:cna-monitor init -p 10`);
console.info(`\t\t-m 监控结果保存的历史记录大小,不建议设置太大,单位MB,默认1,如:cna-monitor init -m 1`);
console.info(`\t\t-n 监听到目标变化后,被通知的邮件地址,如果不需要通知请不要配置或配置为空`);
console.info('');
console.info(`add\t添加一个监听地址(可以是一个域名也可以是一个IP地址),如:cna-monitor add 10.0.0.1`);
console.info(`\t支持的参数:`);
console.info(`\t\t-name\t监听别名,如:cna-monitor add 10.0.0.1 -name myName`);
console.info('');
console.info(`delete\t删除一个监听地址(可以是之前添加的域名或IP地址或别名),如:cna-monitor delete 10.0.0.1`);
console.info('');
console.info(`keys\t显示目前监控的所有地址`);
console.info(`\t支持的参数:`);
console.info(`\t\t-p 显示的数量,单位行,默认10,如:cna-monitor keys -p 10`);
console.info('');
console.info(`list\t从近到远显示监控情况`);
console.info(`\t支持的参数:`);
console.info(`\t\t-p 显示的数量,单位行,默认10,如:cna-monitor list -p 10`);
console.info(`\t\t-t 筛选某一个域名或IP地址或别名,如:cna-monitor list -t 10.0.0.1`);
console.info(`\t\t-f 监控监听目标的状态变化且实时响应输出,目前使用-f参数不支持其他参数,如:cna-monitor list -f`);
console.info('');
console.info(`restart\t重启监控程序`);
console.info('');
console.info(`start\t启动监控程序`);
console.info('');
console.info(`stop\t停止监控程序`);
console.info('');
console.info(`status\t显示监控程序目前的状态`);
console.info('');
console.info(`version\t显示当前版本号`);
console.info(`-v\t显示当前版本号`);
console.info('');
console.info(`backup\t备份当前配置,后面指定备份文件保存目录,如:cna-monitor backup /cna/backup/`);
console.info('');
console.info(`import\t导入配置,后面指定对应的备份文件地址,如:cna-monitor import /cna/backup/backup_20211025.cna`);
console.info('');
console.info(`config\t设置或更改全局配置信息`);
console.info(`\t支持的参数:`);
console.info(`\t\t-t 监控频率,单位分钟,默认1,如:cna-monitor config -t 1`);
console.info(`\t\t-p 监控列表每次显示的数量,单位行,默认10,如:cna-monitor config -p 10`);
console.info(`\t\t-m 监控结果保存的历史记录大小,不建议设置太大,单位MB,默认1,如:cna-monitor config -m 1`);
console.info(`\t\t-n 监听到目标变化后,被通知的邮件地址,如果不需要通知请不要配置或配置为空`);
console.info(`\t\t-l 显示目前的配置信息,如:cna-monitor config -l`);
console.info('');
console.info(`remove\t移除所有监听目标并停止服务,如:cna-monitor remove`);
console.info('');
console.info(`email\t设置或更改邮件服务器配置`);
console.info(`\t支持的参数:`);
console.info(`\t\t-l 显示当前配置信息`);
console.info(`\t\t-clear 清空邮件服务器配置`);
console.info(`\t\t-try 测试邮件发送`);
console.info(`\t\t-host 设定SMTP服务器`);
console.info(`\t\t-port 设定SMTP服务器端口`);
console.info(`\t\t-secure 设定是否SSL,true或者false`);
console.info(`\t\t-user 设定账号`);
console.info(`\t\t-pass 设定密码`);
console.info('');
console.info(`upgrade\t升级本程序`);
console.info('');
if (args.debug) {
console.info(`logs\t查看日志信息`);
console.info(`\t支持的参数:`);
console.info(`\t\t-l 显示当前所有的日志文件`);
console.info(`\t\t-clear 清空日志`);
console.info(`\t\t-f 实时显示日志输出`);
console.info(`\t\t-export 导出日志文件到指定的文件夹`);
console.info(`\t\t-s 显示某个日志文件的内容`);
console.info('');
}
console.groupEnd();
console.info(`-------------- 网络可达性监控程序(V${packageJson.version}) -----------------`);
}
exports.help = help;
function version() {
console.info(`当前版本:V${packageJson.version}`);
}
exports.version = version;
function start() {
try {
const pid = checkProcess();
if (pid) {
console.warn('监控程序已经在运行!');
return;
}
child_process.exec("nohup node cnam.js &", {
cwd
});
console.info('监控程序开始运行!');
} catch (e) {
log.error("启动失败", e);
}
}
exports.start = start;
function stop() {
try {
const pid = checkProcess();
if (!pid) {
console.warn('监控程序未在运行!');
return;
}
pid.forEach(item => child_process.exec(`kill -9 ${item}`));
if (fs.existsSync(timeFile)) {
fs.unlinkSync(timeFile);
}
console.info('监控程序已经停止!');
} catch (e) {
log.error("停止失败", e);
}
}
exports.stop = stop;
function status() {
try {
const pid = checkProcess();
if (pid) {
let msg = '监控程序正在运行!';
if (fs.existsSync(timeFile)) {
try {
const time = new Date(parseInt(fs.readFileSync(timeFile).toString().decrypt())).format();
msg += `(上次检查时间:${time})`;
} catch (e) { }
}
console.warn(msg);
} else {
console.warn('监控程序未运行!');
}
} catch (e) {
log.error("查看运行状态失败", e);
}
}
exports.status = status;
function keys() {
try {
let keys = ["目标\t别名\t状态\t时间"];
if (fs.existsSync(keysFile)) {
const _keys = JSON.parse(fs.readFileSync(keysFile).toString().decrypt());
_keys.forEach(item => keys.push(`${item.target}\t${item.alias}\t${item.status || "未知"}\t${item.time || ""}`));
}
return keys;
} catch (e) {
log.error("查看监听目标失败", e);
}
}
exports.keys = keys;
/**
* 添加一个监控
* @param {*} target 需要监控的域名或IP
* @param {string} [alias=""] 别名
* @returns
*/
function add(target, alias = "") {
try {
if (!target || target == true) {
throw new Error(`需要添加的地址不能未空`);
}
let keys = [];
if (fs.existsSync(keysFile)) {
keys = JSON.parse(fs.readFileSync(keysFile).toString().decrypt());
}
const k = keys.find(item => item.target == target);
if (k) {
throw new Error(`${target}已经存在${k.alias ? `(${k.alias})` : ""}`);
}
keys.push({
target,
alias
});
fs.writeFileSync(keysFile, JSON.stringify(keys).encrypt());
return true;
} catch (e) {
log.error("添加监听目标失败", e);
throw e;
}
}
exports.add = add;
/**
* 删除一个监控
* @param {*} targetOrAlias 需要监控的域名或IP或别名
* @returns
*/
function _delete(targetOrAlias = "") {
try {
// if (comm.typeof(targetOrAlias) == 'boolean' && targetOrAlias == true || !targetOrAlias) {
if (!targetOrAlias) {
throw new Error(`请输入需要移除的目标`);
}
if (fs.existsSync(keysFile)) {
const keys = JSON.parse(fs.readFileSync(keysFile).toString().decrypt());
if (keys && keys.length == 0) {
throw new Error(`当前目标列表为空`);
}
let exists = false;
for (let i = 0; i < keys.length; i++) {
if (keys[i].target == targetOrAlias || keys[i].alias == targetOrAlias) {
keys.remove(i);
exists = true;
break;
}
}
if (exists) {
fs.writeFileSync(keysFile, JSON.stringify(keys).encrypt());
return true;
}
throw new Error(`需要移除的目标不存在`);
} else {
throw new Error(`当前目标列表为空`);
}
} catch (e) {
log.error("删除监听目标失败", e);
throw e;
}
}
exports.delete = _delete;
function list() {
try {
let output = [];
if (fs.existsSync(outFile)) {
// output = JSON.parse(fs.readFileSync(outFile).toString().decrypt());
let test = fs.readFileSync(outFile).toString().split('\r\n');
test.forEach(item => {
output.push(JSON.parse(item.decrypt()));
});
}
return output;
} catch (e) {
log.error("查看目标状态列表失败", e);
}
}
exports.list = list
function backup(args) {
try {
if (args.path == void 0) {
console.info('请设定备份目录');
return;
}
if (!fs.existsSync(args.path)) {
mkdirSync(args.path);
args.debug && console.info('备份目录不存在,创建目录:', args.path);
}
const _path = path.join(args.path, `./backup_${packageJson.version}_${new Date().format('yyyyMMddhhmmssS')}.cna`);
args.debug && console.info('备份文件保存位置:', _path);
const bak = {
backupid: 'cna-monitor',
date: new Date().format('yyyy/MM/dd hh:mm:ss.S'),
version: packageJson.version,
random: random.getRandomStr(32)
};
args.debug && console.info('组装备份信息');
if (fs.existsSync(keysFile)) {
const keys = JSON.parse(fs.readFileSync(keysFile).toString().decrypt());
bak.keys = keys.map(item => { return { target: item.target, alias: item.alias } });
}
args.debug && console.info('组装备份信息:监控信息完成');
if (fs.existsSync(configFile)) {
bak.config = JSON.parse(fs.readFileSync(configFile).toString().decrypt());
}
args.debug && console.info('组装备份信息:配置信息完成');
if (fs.existsSync(emailFile)) {
bak.email = JSON.parse(fs.readFileSync(emailFile).toString().decrypt());
}
args.debug && console.info('组装备份信息:邮箱信息完成');
fs.writeFileSync(_path, JSON.stringify(bak).encrypt());
console.info('配置备份完成');
} catch (e) {
console.error(`配置备份失败`);
args.debug && console.error(e);
log.error("备份失败", e);
}
}
exports.backup = backup;
function _import(args) {
try {
if (!fs.existsSync(args.path)) {
console.error('备份文件不存在');
return;
}
let text = fs.readFileSync(args.path).toString();
text = text.decrypt();
text = JSON.parse(text);
if (!text.backupid) {
console.error('备份文件错误');
return;
}
// console.info(`该备份文件备份于:${text.date}`);
let answer = readlineSync.question(`该备份文件备份于:${text.date},继续将覆盖现有配置及监控目标等信息,请确认是否继续(y/n)?`);
if (answer.toLowerCase() != 'y') {
return;
}
if (packageJson.version != text.version) {
answer = readlineSync.question(`备份文件版本(V${text.version})和当前版本不匹配,导入可能导致功能异常,请确认是否继续(y/n)?`);
if (answer.toLowerCase() != 'y') {
return;
}
}
if (text.config) {
console.info('还原配置信息');
fs.writeFileSync(configFile, JSON.stringify(text.config).encrypt());
}
if (text.keys) {
console.info('还原监控目标信息');
fs.writeFileSync(keysFile, JSON.stringify(text.keys).encrypt());
}
if (text.email) {
console.info('还原邮件服务器信息');
fs.writeFileSync(emailFile, JSON.stringify(text.email).encrypt());
}
console.info('配置导入完成');
} catch (e) {
console.error(`配置导入失败`);
args.debug && console.error(e);
log.error('导入失败', e);
}
}
exports.import = _import;
/**
* 同步创建文件夹,可递归创建
* @param {String} dir 需要创建的文件夹路径
* @returns
*/
const mkdirSync = (dir) => {
if (fs.existsSync(dir)) {
return true;
} else {
if (mkdirSync(path.dirname(dir))) {
fs.mkdirSync(dir);
return true;
}
}
};
exports.mkdirSync = mkdirSync;
function config(args) {
try {
let _config;
if (fs.existsSync(configFile)) {
_config = JSON.parse(fs.readFileSync(configFile).toString().decrypt());
} else {
_config = defaultConfig;
}
if (args.l) {
console.info(`监控频率:${_config.t}分钟`);
console.info(`监控列表数量:${_config.p}行`);
console.info(`监控保存容量:${_config.m}兆字节`);
console.info(`监听变化后的通知邮箱:${_config.n}`);
} else {
if (args.t || args.p || args.m || args.n) {
if (args.t && args.t > 0) {
_config.t = args.t;
}
if (args.p) {
_config.p = args.p;
}
if (args.m) {
_config.m = args.m;
}
if (args.n) {
_config.n = args.n;
}
fs.writeFileSync(configFile, JSON.stringify(_config).encrypt());
console.info(`设置成功`);
}
}
} catch (e) {
log.error("配置失败", e);
}
}
exports.config = config;
function email(args) {
try {
let emailConfig = {};
if (fs.existsSync(emailFile)) {
emailConfig = JSON.parse(fs.readFileSync(emailFile).toString().decrypt());
}
emailConfig.host = args.host || emailConfig.host || "";
emailConfig.port = args.port || emailConfig.port || "25";
emailConfig.secure = args.secure || emailConfig.secure || "false";
emailConfig.user = args.user || emailConfig.user || "";
emailConfig.pass = args.pass || emailConfig.pass || "";
fs.writeFileSync(emailFile, JSON.stringify(emailConfig).encrypt());
} catch (e) {
log.error("设定邮箱SMTP失败", e);
}
}
exports.email = email;
async function upgrade(args) {
try {
try {
const html = await axios({
method: 'get',
url: 'https://www.npmjs.com/package/cna-monitor'
});
const $ = toJquery(html.data);
const newVersion = $($($('time').parent().parent()).find('span')[1]).html().split('<')[0];
// console.info("最新版本:V" + newVersion, "当前版本:V" + packageJson.version);
if (newVersion == packageJson.version) {
console.info("当前已是最新版本!");
process.exit();
}
let answer = readlineSync.question(`发现新版本V${newVersion}(当前版本V${packageJson.version}),请确认是否继续(y/n)?`);
if (answer.toLowerCase() != 'y') {
process.exit();
}
} catch (e) {
console.warn("警告:检查最新版本失败,直接尝试强制升级")
}
let msg = "正在升级中";
const { spawn } = require('child_process');
slog(msg);
const npm = spawn('npm', ["install", "--unsafe-perm=true", "--allow-rootcd", "cna-monitor", "-g"]);
npm.stdout.on('data', (data) => {
msg += ".";
// slog.clear();
slog(msg + "\r\n" + data.toString());
// console.info(data.toString());
});
npm.stderr.on('data', (data) => {
msg += ".";
// slog.clear();
slog(msg + "\r\n" + data.toString());
// console.error(`升级失败`);
// process.exit();
});
npm.on('close', (code) => {
slog.clear();
setTimeout(function () {
// console.info('');
console.info('升级完成!');
if (fs.existsSync(outFile)) {
fs.unlinkSync(outFile);
}
if (fs.existsSync(timeFile)) {
fs.unlinkSync(timeFile);
}
process.exit();
}, 1000);
});
} catch (e) {
slog.clear();
// console.info('');
console.error(`升级失败`);
args.debug && console.error(e);
log.error("升级失败", e);
}
}
exports.upgrade = upgrade;