Compare commits

11 Commits

8 changed files with 686 additions and 529 deletions

15
.eslintrc.json Normal file
View File

@@ -0,0 +1,15 @@
{
"env": {
"commonjs": true,
"es2021": true,
"node": true
},
"extends": [
"google"
],
"parserOptions": {
"ecmaVersion": "latest"
},
"rules": {
}
}

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": ["javascript"]
}

View File

@@ -7,7 +7,7 @@ This branch is a rewrite of the [previous in Java Developed branch](https://git.
### Requirements ### Requirements
- NodeJS >= 14 - NodeJS >= 14
You can either: [Build a executeable](#build-a-executeable) or [Runing from Source](#running-from-source) You can either: [Build a executeable](#build-a-executeable) or [Running from Source](#running-from-source)
### Build a executeable ### Build a executeable
@@ -43,7 +43,7 @@ pnpm run pkg
yarn run pkg yarn run pkg
``` ```
Your builded binary executeable should be in bin/ Your builded binary executeable should be located in `bin/`
### Running from Source ### Running from Source

View File

@@ -1,14 +1,16 @@
/* eslint-disable require-jsdoc */
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { EventEmitter } = require('events'); const {EventEmitter} = require('events');
class Archiver { class Archiver {
constructor(backupPath, backupPaths) { constructor(backupPath, backupPaths, gzip, gzipLevel) {
this.backupPath = backupPath; this.backupPath = backupPath;
this.backupPaths = backupPaths; this.backupPaths = backupPaths;
this.eventEmitter = new EventEmitter(); this.eventEmitter = new EventEmitter();
this.archive = require('archiver')('tar', { this.archive = require('archiver')('tar', {
zlib: { level: 9 } gzip: gzip,
gzipOptions: {level: gzipLevel},
}); });
this.totalFiles = 0; this.totalFiles = 0;
} }
@@ -18,24 +20,24 @@ class Archiver {
const ARCHIVE_LOCATION = path.join(this.backupPath); const ARCHIVE_LOCATION = path.join(this.backupPath);
const OUTPUT = fs.createWriteStream(ARCHIVE_LOCATION); const OUTPUT = fs.createWriteStream(ARCHIVE_LOCATION);
OUTPUT.on('close', function () { OUTPUT.on('close', function() {
EVENT_EMITTER.emit('finish'); EVENT_EMITTER.emit('finish');
}); });
OUTPUT.on('end', function () { OUTPUT.on('end', function() {
console.log('Data has been drained'); console.log('Data has been drained');
}); });
this.archive.on('warning', function (err) { this.archive.on('warning', function(err) {
if (err.code === 'ENOENT') console.log(err); if (err.code === 'ENOENT') console.log(err);
else throw err; else throw err;
}); });
this.archive.on('error', function (err) { this.archive.on('error', function(err) {
throw err; throw err;
}); });
this.archive.on('progress', function (progressInfo) { this.archive.on('progress', function(progressInfo) {
EVENT_EMITTER.emit('progress', progressInfo); EVENT_EMITTER.emit('progress', progressInfo);
}); });
@@ -50,7 +52,7 @@ class Archiver {
const COUNTED = require('count-files-dirs').countSync(ITEM); const COUNTED = require('count-files-dirs').countSync(ITEM);
this.totalFiles += COUNTED.fileCount + COUNTED.dirCount; this.totalFiles += COUNTED.fileCount + COUNTED.dirCount;
} else { } else {
this.archive.file(ITEM, { name: path.basename(ITEM) }); this.archive.file(ITEM, {name: path.basename(ITEM)});
this.totalFiles++; this.totalFiles++;
} }
} }
@@ -64,7 +66,7 @@ class Archiver {
const COUNTED = require('count-files-dirs').countSync(item); const COUNTED = require('count-files-dirs').countSync(item);
this.totalFiles += COUNTED.fileCount + COUNTED.dirCount; this.totalFiles += COUNTED.fileCount + COUNTED.dirCount;
} else { } else {
this.archive.file(item, { name: path.basename(item) }); this.archive.file(item, {name: path.basename(item)});
this.totalFiles++; this.totalFiles++;
} }
} }

549
index.js
View File

@@ -1,102 +1,166 @@
const PROMPTS = require('prompts'); /* eslint-disable max-len */
const PATH = require('path'); const prompts = require('prompts');
const FS = require('fs'); const path = require('path');
const GLOB = require('tiny-glob'); const fs = require('fs');
const LOGGER = require("./logger"); const glob = require('tiny-glob');
const ORA = require('ora'); const logger = require('./logger');
const BACKUP_ARCHIVE = require("./archiver"); const ora = require('ora');
const MYSQLDUMP = require("mysqldump"); const BackupArchive = require('./archiver');
const mysqldump = require('mysqldump');
const CURRENT_FOLDER = __dirname.startsWith("/snapshot") ? PATH.dirname(process.env._) : __dirname; const CURRENT_FOLDER = __dirname.startsWith('/snapshot') ?
const TASKS_DIRECTORY = PATH.join(CURRENT_FOLDER, 'tasks'); path.dirname(process.env._) :
const BACKUPS_DIRECTORY = PATH.join(CURRENT_FOLDER, 'backups'); __dirname;
const TEMP_DIR = PATH.join(CURRENT_FOLDER, '.temp'); const TASKS_DIRECTORY = path.join(CURRENT_FOLDER, 'tasks');
const BACKUPS_DIRECTORY = path.join(CURRENT_FOLDER, 'backups');
const TEMP_DIR = path.join(CURRENT_FOLDER, '.temp');
const run = async (programArgs) => { const run = async (programArgs) => {
switch (programArgs.action) { if (programArgs === undefined || programArgs.action === undefined) {
case "exit": return;
LOGGER.info("exiting..."); }
const ACTION = programArgs.action.toLowerCase();
switch (ACTION) {
case 'exit':
logger.info('exiting...');
break; break;
case "backup": { case 'createcrontab':
let taskName = programArgs.filename ? programArgs.filename + ".json" : ""; {
const AUTO_COMPLETE_ARRAY = new Array(); logger.info('not added yet.');
const FILES = await GLOB(PATH.join('*.json'), { }
break;
case 'backup': {
let taskName = programArgs.filename ? programArgs.filename + '.json' : '';
const AUTO_COMPLETE_ARRAY = [];
const FILES = await glob(path.join('*.json'), {
filesOnly: true, filesOnly: true,
cwd: TASKS_DIRECTORY cwd: TASKS_DIRECTORY,
}); });
for (const FILE of FILES) { for (const FILE of FILES) {
AUTO_COMPLETE_ARRAY.push({ AUTO_COMPLETE_ARRAY.push({
title: FILE, title: FILE,
value: FILE, value: FILE,
}) });
} }
if (taskName === undefined || taskName === "") { if (taskName === undefined || taskName === '') {
const PROMPT = await PROMPTS({ const PROMPT = await prompts({
type: "autocomplete", type: 'autocomplete',
name: "filename", name: 'filename',
message: "Select a Task file to use", message: 'Select a Task file to use',
choices: AUTO_COMPLETE_ARRAY, choices: AUTO_COMPLETE_ARRAY,
validate: value => FILES.includes(value + ".json") ? true : "Task file does not exist, please specify a valid task file." validate: (value) =>
FILES.includes(value + '.json') ?
true :
'Task file does not exist, please specify a valid task file.',
}); });
taskName = PROMPT.filename; taskName = PROMPT.filename;
} }
if (taskName === undefined) if (taskName === undefined) {
return; return;
}
if (!FS.existsSync(PATH.join(TASKS_DIRECTORY, taskName))) { if (!fs.existsSync(path.join(TASKS_DIRECTORY, taskName))) {
LOGGER.error("task file does not exist"); logger.error('task file does not exist');
process.exit(1); process.exit(1);
} }
const TASK_FILE_CONTENTS = JSON.parse(FS.readFileSync(PATH.join(TASKS_DIRECTORY, taskName), 'utf8')); const TASK_FILE_CONTENTS = JSON.parse(
fs.readFileSync(path.join(TASKS_DIRECTORY, taskName), 'utf8'),
);
const VALID_TASK_FILE = validate(TASK_FILE_CONTENTS); const VALID_TASK_FILE = validate(TASK_FILE_CONTENTS);
if (VALID_TASK_FILE.length > 0) { if (VALID_TASK_FILE.length > 0) {
LOGGER.error("task file is not valid, " + VALID_TASK_FILE.length + " errors found"); logger.error(
LOGGER.info("check with '" + process.argv0 + " checkTaskConf " + PATH.parse(taskName).name + "'"); 'task file is not valid, ' + VALID_TASK_FILE.length + ' errors found',
);
logger.info(
'check with \'' +
process.argv0 +
' checkTaskConf ' +
path.parse(taskName).name +
'\'',
);
process.exit(1); process.exit(1);
} }
LOGGER.success("task file is valid"); logger.success('task file is valid');
const TIMER = new (require('./timer'))().startTimer(); const TIMER = new (require('./timer'))().startTimer();
const SPINNER = ORA('initialization...').start(); const SPINNER = ora('preparing backup... please wait.').start();
if (!FS.existsSync(PATH.join(BACKUPS_DIRECTORY, PATH.parse(taskName).name)))
await FS.promises.mkdir(PATH.join(BACKUPS_DIRECTORY, PATH.parse(taskName).name)); const FILE_PATHS = [];
else { const fsExtra = require('fs-extra');
await Promise.all(TASK_FILE_CONTENTS.filesystem.targets.map(async (file) => {
if (fs.existsSync(file)) {
const newPath = path.join(TEMP_DIR, path.parse(file).name);
FILE_PATHS.push(newPath);
await fsExtra.copySync(file, newPath);
}
}));
if (
!fs.existsSync(path.join(BACKUPS_DIRECTORY, path.parse(taskName).name))
) {
await fs.promises.mkdir(
path.join(BACKUPS_DIRECTORY, path.parse(taskName).name),
);
} else {
if (TASK_FILE_CONTENTS.vacuum.enabled) { if (TASK_FILE_CONTENTS.vacuum.enabled) {
SPINNER.text = "cleaning up old backups..."; SPINNER.text = 'cleaning up old backups...';
const ALL_FILES = FS.readdirSync(PATH.join(BACKUPS_DIRECTORY, PATH.parse(taskName).name)); const ALL_FILES = fs.readdirSync(
path.join(BACKUPS_DIRECTORY, path.parse(taskName).name),
);
const CURRENT_DATE = Date.now(); const CURRENT_DATE = Date.now();
for (const FILE of ALL_FILES) { for (const FILE of ALL_FILES) {
const FILE_DATE = new Date(FS.statSync(PATH.join(BACKUPS_DIRECTORY, PATH.parse(taskName).name, FILE)).birthtime).getTime(); const FILE_DATE = new Date(
fs.statSync(
path.join(BACKUPS_DIRECTORY, path.parse(taskName).name, FILE),
).birthtime,
).getTime();
let timeAdd = 0; let timeAdd = 0;
switch (TASK_FILE_CONTENTS.vacuum.unit) { switch (TASK_FILE_CONTENTS.vacuum.unit) {
case "DAYS": case 'DAYS':
timeAdd = TASK_FILE_CONTENTS.vacuum.time * 24 * 60 * 60 * 1000; timeAdd = TASK_FILE_CONTENTS.vacuum.time * 24 * 60 * 60 * 1000;
break; break;
case "HOURS": case 'HOURS':
timeAdd = TASK_FILE_CONTENTS.vacuum.time * 60 * 60 * 1000; timeAdd = TASK_FILE_CONTENTS.vacuum.time * 60 * 60 * 1000;
break; break;
case "MINUTES": case 'MINUTES':
timeAdd = TASK_FILE_CONTENTS.vacuum.time * 60 * 1000; timeAdd = TASK_FILE_CONTENTS.vacuum.time * 60 * 1000;
break; break;
} }
const DELETE_DATE = new Date(FILE_DATE + timeAdd); const DELETE_DATE = new Date(FILE_DATE + timeAdd);
if (DELETE_DATE.getTime() < CURRENT_DATE) { if (DELETE_DATE.getTime() < CURRENT_DATE) {
await FS.unlinkSync(PATH.join(BACKUPS_DIRECTORY, PATH.parse(taskName).name, FILE)); await fs.unlinkSync(
path.join(BACKUPS_DIRECTORY, path.parse(taskName).name, FILE),
);
} }
} }
} }
} }
const UNREPLACED_FILENAME = TASK_FILE_CONTENTS.general.outputFile; const UNREPLACED_FILENAME = TASK_FILE_CONTENTS.general.outputFile;
const REPLACED_FILENAME = UNREPLACED_FILENAME.replace("{date}", require('moment')().format(TASK_FILE_CONTENTS.general.dateFormat)) const REPLACED_FILENAME = UNREPLACED_FILENAME.replace(
.replace("{taskName}", PATH.parse(taskName).name); '{date}',
require('moment')().format(TASK_FILE_CONTENTS.general.dateFormat),
).replace('{taskName}', path.parse(taskName).name);
const DB_FILE = PATH.join(TEMP_DIR, require('crypto').randomBytes(16).toString('hex') + ".sql"); const DB_FILE = path.join(
const ARCHIVE = new BACKUP_ARCHIVE(PATH.join(BACKUPS_DIRECTORY, PATH.parse(taskName).name, REPLACED_FILENAME + ".tar.gz"), TASK_FILE_CONTENTS.filesystem.targets); TEMP_DIR,
require('crypto').randomBytes(16).toString('hex') + '.sql',
);
const ARCHIVE = new BackupArchive(
path.join(
BACKUPS_DIRECTORY,
path.parse(taskName).name,
REPLACED_FILENAME + '.tar.gz',
),
FILE_PATHS,
TASK_FILE_CONTENTS.general.gzip,
TASK_FILE_CONTENTS.general.gzipLevel,
);
ARCHIVE.eventEmitter.on('progress', (progressInfo) => { ARCHIVE.eventEmitter.on('progress', (progressInfo) => {
const TOTAL = ARCHIVE.totalFiles; const TOTAL = ARCHIVE.totalFiles;
@@ -106,38 +170,41 @@ const run = async (programArgs) => {
}); });
ARCHIVE.eventEmitter.on('finish', async () => { ARCHIVE.eventEmitter.on('finish', async () => {
if (TASK_FILE_CONTENTS.mysql.enabled) { SPINNER.text = 'Cleaning up temporary files...';
if (DB_FILE !== undefined && FS.existsSync(DB_FILE)) if (fs.existsSync(DB_FILE)) {
await FS.unlinkSync(DB_FILE); await fs.unlinkSync(DB_FILE);
} }
SPINNER.succeed("backup complete, took " + TIMER.endTimer()); for (const FILE of FILE_PATHS) {
await fsExtra.removeSync(FILE);
}
SPINNER.succeed('backup complete, took ' + TIMER.endTimer());
}); });
if (TASK_FILE_CONTENTS.mysql.enabled) { if (TASK_FILE_CONTENTS.mysql.enabled) {
SPINNER.text = `Dumping Database "${TASK_FILE_CONTENTS.mysql.database}"...`; SPINNER.text = `Dumping Database "${TASK_FILE_CONTENTS.mysql.database}"...`;
try { try {
await MYSQLDUMP({ await mysqldump({
connection: { connection: {
host: TASK_FILE_CONTENTS.mysql.host, host: TASK_FILE_CONTENTS.mysql.host,
port: TASK_FILE_CONTENTS.mysql.port, port: TASK_FILE_CONTENTS.mysql.port,
user: TASK_FILE_CONTENTS.mysql.user, user: TASK_FILE_CONTENTS.mysql.user,
password: TASK_FILE_CONTENTS.mysql.password, password: TASK_FILE_CONTENTS.mysql.password,
database: TASK_FILE_CONTENTS.mysql.database database: TASK_FILE_CONTENTS.mysql.database,
}, },
dumpToFile: DB_FILE dumpToFile: DB_FILE,
}); });
} catch (err) { } catch (err) {
LOGGER.error(err); logger.error(err);
await FS.unlinkSync(DB_FILE); await fs.unlinkSync(DB_FILE);
} }
} }
SPINNER.text = "Adding Files..."; SPINNER.text = 'Adding Files...';
await ARCHIVE.start(); await ARCHIVE.start();
if (TASK_FILE_CONTENTS.mysql.enabled) { if (TASK_FILE_CONTENTS.mysql.enabled) {
if (FS.existsSync(DB_FILE)) { if (fs.existsSync(DB_FILE)) {
SPINNER.text = "Adding Database..."; SPINNER.text = 'Adding Database...';
await ARCHIVE.add(DB_FILE); await ARCHIVE.add(DB_FILE);
} }
} }
@@ -145,296 +212,349 @@ const run = async (programArgs) => {
ARCHIVE.pack(); ARCHIVE.pack();
break; break;
} }
case "checkTaskConf": { case 'checktaskconf': {
let taskName = programArgs.filename ? programArgs.filename + ".json" : ""; let taskName = programArgs.filename ? programArgs.filename + '.json' : '';
const AUTO_COMPLETE_ARRAY = new Array(); const AUTO_COMPLETE_ARRAY = [];
const FILES = await GLOB(PATH.join('*.json'), { const FILES = await glob(path.join('*.json'), {
filesOnly: true, filesOnly: true,
cwd: TASKS_DIRECTORY cwd: TASKS_DIRECTORY,
}); });
for (const FILE of FILES) { for (const FILE of FILES) {
AUTO_COMPLETE_ARRAY.push({ AUTO_COMPLETE_ARRAY.push({
title: FILE, title: FILE,
value: FILE, value: FILE,
}) });
} }
if (taskName === undefined || taskName === "") { if (taskName === undefined || taskName === '') {
const PROMPT = await PROMPTS({ const PROMPT = await prompts({
type: "autocomplete", type: 'autocomplete',
name: "filename", name: 'filename',
message: "Select a Task file to check", message: 'Select a Task file to check',
choices: AUTO_COMPLETE_ARRAY, choices: AUTO_COMPLETE_ARRAY,
validate: value => FILES.includes(value + ".json") ? true : "Task file does not exist, please specify a valid task file." validate: (value) =>
FILES.includes(value + '.json') ?
true :
'Task file does not exist, please specify a valid task file.',
}); });
taskName = PROMPT.filename; taskName = PROMPT.filename;
} }
if (taskName === undefined) if (taskName === undefined) {
return;
const TASK_FILE = PATH.join(TASKS_DIRECTORY, taskName);
if (!FS.existsSync(TASK_FILE)) {
LOGGER.error("Task file does not exist");
return; return;
} }
const TASK_FILE_CONTENTS = await FS.readFileSync(TASK_FILE, 'utf8');
const TASK_FILE = path.join(TASKS_DIRECTORY, taskName);
if (!fs.existsSync(TASK_FILE)) {
logger.error('Task file does not exist');
return;
}
const TASK_FILE_CONTENTS = await fs.readFileSync(TASK_FILE, 'utf8');
const TASK_FILE_TO_CHECK = JSON.parse(TASK_FILE_CONTENTS); const TASK_FILE_TO_CHECK = JSON.parse(TASK_FILE_CONTENTS);
const VALIDATION_ERRORS = validate(TASK_FILE_TO_CHECK); const VALIDATION_ERRORS = validate(TASK_FILE_TO_CHECK);
if (VALIDATION_ERRORS.length > 0) { if (VALIDATION_ERRORS.length > 0) {
LOGGER.error("task file is not valid, " + VALIDATION_ERRORS.length + " errors found"); logger.error(
for (const ERROR of VALIDATION_ERRORS) LOGGER.error(ERROR); 'task file is not valid, ' +
VALIDATION_ERRORS.length +
' errors found',
);
for (const ERROR of VALIDATION_ERRORS) logger.error(ERROR);
} else { } else {
LOGGER.success("Task file is valid!"); logger.success('Task file is valid!');
} }
break; break;
} }
case "generateTaskConf": { case 'generatetaskconf': {
let taskName = programArgs.filename ? programArgs.filename : ""; let taskName = programArgs.filename ? programArgs.filename : '';
if (taskName === undefined || taskName === "") { if (taskName === undefined || taskName === '') {
const PROMPT = await PROMPTS({ const PROMPT = await prompts({
type: "text", type: 'text',
name: "filename", name: 'filename',
message: "Define a name for the new task file", message: 'Define a name for the new task file',
validate: value => value.length <= 0 || value == "" ? "Please specify a valid file name" : true validate: (value) =>
value.length <= 0 || value == '' ?
'Please specify a valid file name' :
true,
}); });
taskName = PROMPT.filename; taskName = PROMPT.filename;
const TASK_FILE_PATH = PATH.join(TASKS_DIRECTORY, taskName + ".json"); const TASK_FILE_PATH = path.join(TASKS_DIRECTORY, taskName + '.json');
if (await FS.existsSync(TASK_FILE_PATH)) { if (await fs.existsSync(TASK_FILE_PATH)) {
const PROMPT_2 = await PROMPTS({ const PROMPT_2 = await prompts({
type: "toggle", type: 'toggle',
name: "overwrite", name: 'overwrite',
message: "A task file with the same name already exists. Do you want to overwrite it?", message:
'A task file with the same name already exists. Do you want to overwrite it?',
active: 'yes', active: 'yes',
inactive: 'no', inactive: 'no',
initial: false initial: false,
}); });
if (!PROMPT_2.overwrite) { if (!PROMPT_2.overwrite) {
LOGGER.info("exiting..."); logger.info('exiting...');
return; return;
} }
} }
} }
if (taskName === undefined) if (taskName === undefined) {
return; return;
}
const TASK_FILE_PATH = PATH.join(TASKS_DIRECTORY, taskName + ".json"); const TASK_FILE_PATH = path.join(TASKS_DIRECTORY, taskName + '.json');
const TASK_CONFIG = { const TASK_CONFIG = {
"general": { general: {
"dateFormat": "yyyy-MM-DD HH-mm-ss", dateFormat: 'yyyy-MM-DD HH-mm-ss',
"outputFile": "{date} - {taskName}" outputFile: '{date} - {taskName}',
gzip: true,
gzipLevel: 6,
}, },
"vacuum": { vacuum: {
"enabled": true, enabled: true,
"unit": "DAYS", unit: 'DAYS',
"time": 7 time: 7,
}, },
"mysql": { mysql: {
"enabled": true, enabled: true,
"host": "localhost", host: 'localhost',
"port": 3306, port: 3306,
"user": "", user: '',
"password": "", password: '',
"database": "", database: '',
},
filesystem: {
enabled: true,
targets: ['/home/magento/', '/home/test/testfile.txt'],
}, },
"filesystem": {
"enabled": true,
"targets": [
"/home/magento/",
"/home/test/testfile.txt"
]
}
}; };
try { try {
const SAVE_FILE = FS.createWriteStream(TASK_FILE_PATH); const SAVE_FILE = fs.createWriteStream(TASK_FILE_PATH);
SAVE_FILE.write(JSON.stringify(TASK_CONFIG, null, 4)); SAVE_FILE.write(JSON.stringify(TASK_CONFIG, null, 4));
SAVE_FILE.end(); SAVE_FILE.end();
LOGGER.success("Task file \"" + PATH.basename(TASK_FILE_PATH) + "\" saved successfully"); logger.success(
'Task file "' + path.basename(TASK_FILE_PATH) + '" saved successfully',
);
} catch (err) { } catch (err) {
LOGGER.error(err); logger.error(err);
} }
break; break;
} }
default: default:
LOGGER.warn("Unknown action."); logger.warn('Unknown action.');
cli(true); cli(true);
return; return;
} }
} };
const validate = (taskConfig) => { const validate = (taskConfig) => {
const ERRORS = new Array(); const ERRORS = [];
if (taskConfig.general && typeof taskConfig.general === "object") { if (taskConfig.general && typeof taskConfig.general === 'object') {
if (taskConfig.general.dateFormat && typeof taskConfig.general.dateFormat === "string") { if (
taskConfig.general.dateFormat &&
typeof taskConfig.general.dateFormat === 'string'
) {
const DATE_FORMAT = taskConfig.general.dateFormat; const DATE_FORMAT = taskConfig.general.dateFormat;
const DATE_FORMAT_REGEX = /d{1,4}|D{3,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|W{1,2}|[LlopSZN]|"[^"]*"|'[^']*'/g; const DATE_FORMAT_REGEX =
/d{1,4}|D{3,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|W{1,2}|[LlopSZN]|"[^"]*"|'[^']*'/g;
if (!DATE_FORMAT_REGEX.test(DATE_FORMAT)) { if (!DATE_FORMAT_REGEX.test(DATE_FORMAT)) {
ERRORS.push("general.dateFormat is not a valid date format"); ERRORS.push('general.dateFormat is not a valid date format');
} }
} else { } else {
ERRORS.push("general.dateFormat is not defined or is not a string"); ERRORS.push('general.dateFormat is not defined or is not a string');
} }
if (taskConfig.general.outputFile && typeof taskConfig.general.outputFile === "string") { if (
taskConfig.general.outputFile &&
typeof taskConfig.general.outputFile === 'string'
) {
const OUTPUT_FILE = taskConfig.general.outputFile; const OUTPUT_FILE = taskConfig.general.outputFile;
const OUTPUT_FILE_REGEX = /^([^.]+)$/g; const OUTPUT_FILE_REGEX = /^([^.]+)$/g;
if (!OUTPUT_FILE_REGEX.test(OUTPUT_FILE)) { if (!OUTPUT_FILE_REGEX.test(OUTPUT_FILE)) {
ERRORS.push("general.outputFile is not a valid output file name, maybe you added a file extension?"); ERRORS.push(
'general.outputFile is not a valid output file name, maybe you added a file extension?',
);
} }
} else { } else {
ERRORS.push("general.outputFile is not defined or is not a string"); ERRORS.push('general.outputFile is not defined or is not a string');
}
if (typeof taskConfig.general.gzip === 'boolean') {
if (taskConfig.general.gzip) {
if (
!taskConfig.general.gzipLevel &&
typeof taskConfig.general.gzipLevel !== 'number'
) {
ERRORS.push('general.gzipLevel is not defined or is not a number');
}
} }
} else { } else {
ERRORS.push("general section is missing or not an object"); ERRORS.push('general.gzip is not defined or is not a boolean');
ERRORS.push("general.dateFormat is not defined or is not a string"); ERRORS.push('general.gzipLevel is not defined or is not a number');
ERRORS.push("general.outputFile is not defined or is not a string"); }
} else {
ERRORS.push('general section is missing or not an object');
ERRORS.push('general.dateFormat is not defined or is not a string');
ERRORS.push('general.outputFile is not defined or is not a string');
} }
if (taskConfig.vacuum && typeof taskConfig.vacuum === "object") { if (taskConfig.vacuum && typeof taskConfig.vacuum === 'object') {
if (typeof taskConfig.vacuum.enabled !== "boolean") { if (typeof taskConfig.vacuum.enabled !== 'boolean') {
ERRORS.push("vacuum.enabled is not defined or is not a boolean"); ERRORS.push('vacuum.enabled is not defined or is not a boolean');
} else if (taskConfig.vacuum.enabled) { } else if (taskConfig.vacuum.enabled) {
if (
if (taskConfig.vacuum.unit && typeof taskConfig.vacuum.unit === "string") { taskConfig.vacuum.unit &&
typeof taskConfig.vacuum.unit === 'string'
) {
const UNIT = taskConfig.vacuum.unit; const UNIT = taskConfig.vacuum.unit;
const UNIT_REGEX = /^(DAYS|HOURS|MINUTES)$/g; const UNIT_REGEX = /^(DAYS|HOURS|MINUTES)$/g;
if (!UNIT_REGEX.test(UNIT)) { if (!UNIT_REGEX.test(UNIT)) {
ERRORS.push("vacuum.unit is not a valid unit, please use DAYS, HOURS or MINUTES"); ERRORS.push(
'vacuum.unit is not a valid unit, please use DAYS, HOURS or MINUTES',
);
} }
} else { } else {
ERRORS.push("vacuum.unit is not defined or is not a string"); ERRORS.push('vacuum.unit is not defined or is not a string');
} }
if (taskConfig.vacuum.time && typeof taskConfig.vacuum.time === "number") { if (
taskConfig.vacuum.time &&
typeof taskConfig.vacuum.time === 'number'
) {
const TIME = taskConfig.vacuum.time; const TIME = taskConfig.vacuum.time;
if (TIME < 1) { if (TIME < 1) {
ERRORS.push("vacuum.time is not a valid time, please use a number greater than 0"); ERRORS.push(
'vacuum.time is not a valid time, please use a number greater than 0',
);
} }
} else { } else {
ERRORS.push("vacuum.time is not defined or is not a number"); ERRORS.push('vacuum.time is not defined or is not a number');
} }
} }
} else { } else {
ERRORS.push("vacuum section is missing or not an object"); ERRORS.push('vacuum section is missing or not an object');
ERRORS.push("vacuum.enabled is not defined or is not a boolean"); ERRORS.push('vacuum.enabled is not defined or is not a boolean');
ERRORS.push("vacuum.unit is not defined or is not a string"); ERRORS.push('vacuum.unit is not defined or is not a string');
ERRORS.push("vacuum.time is not defined or is not a number"); ERRORS.push('vacuum.time is not defined or is not a number');
} }
if (taskConfig.mysql && typeof taskConfig.mysql === "object") { if (taskConfig.mysql && typeof taskConfig.mysql === 'object') {
if (typeof taskConfig.mysql.enabled !== "boolean") { if (typeof taskConfig.mysql.enabled !== 'boolean') {
ERRORS.push("mysql.enabled is not defined or is not a boolean"); ERRORS.push('mysql.enabled is not defined or is not a boolean');
} else if (taskConfig.mysql.enabled) { } else if (taskConfig.mysql.enabled) {
if (!taskConfig.mysql.host || typeof taskConfig.mysql.host !== "string") { if (!taskConfig.mysql.host || typeof taskConfig.mysql.host !== 'string') {
ERRORS.push("mysql.host is not defined or is not a string"); ERRORS.push('mysql.host is not defined or is not a string');
} }
if (!taskConfig.mysql.port || typeof taskConfig.mysql.port !== "number") { if (!taskConfig.mysql.port || typeof taskConfig.mysql.port !== 'number') {
ERRORS.push("mysql.port is not defined or is not a number"); ERRORS.push('mysql.port is not defined or is not a number');
} }
if (!taskConfig.mysql.user || typeof taskConfig.mysql.user !== "string") { if (!taskConfig.mysql.user || typeof taskConfig.mysql.user !== 'string') {
ERRORS.push("mysql.user is not defined or is not a string"); ERRORS.push('mysql.user is not defined or is not a string');
} }
if (typeof taskConfig.mysql.password !== "string") { if (typeof taskConfig.mysql.password !== 'string') {
ERRORS.push("mysql.password is not defined or is not a string"); ERRORS.push('mysql.password is not defined or is not a string');
} }
if (!taskConfig.mysql.database || typeof taskConfig.mysql.database !== "string") { if (
ERRORS.push("mysql.database is not defined or is not a string"); !taskConfig.mysql.database ||
typeof taskConfig.mysql.database !== 'string'
) {
ERRORS.push('mysql.database is not defined or is not a string');
} }
} }
} else { } else {
ERRORS.push("mysql section is missing or not an object"); ERRORS.push('mysql section is missing or not an object');
ERRORS.push("mysql.host is not defined or is not a string"); ERRORS.push('mysql.host is not defined or is not a string');
ERRORS.push("mysql.port is not defined or is not a number"); ERRORS.push('mysql.port is not defined or is not a number');
ERRORS.push("mysql.user is not defined or is not a string"); ERRORS.push('mysql.user is not defined or is not a string');
ERRORS.push("mysql.password is not defined or is not a string"); ERRORS.push('mysql.password is not defined or is not a string');
ERRORS.push("mysql.database is not defined or is not a string"); ERRORS.push('mysql.database is not defined or is not a string');
} }
if (taskConfig.filesystem && typeof taskConfig.filesystem === "object") { if (taskConfig.filesystem && typeof taskConfig.filesystem === 'object') {
if (typeof taskConfig.filesystem.enabled !== "boolean") { if (typeof taskConfig.filesystem.enabled !== 'boolean') {
ERRORS.push("filesystem.enabled is not defined or is not a boolean"); ERRORS.push('filesystem.enabled is not defined or is not a boolean');
} else if (taskConfig.filesystem.enabled) { } else if (taskConfig.filesystem.enabled) {
if (taskConfig.filesystem.targets && typeof taskConfig.filesystem.targets === "object") { if (
taskConfig.filesystem.targets &&
typeof taskConfig.filesystem.targets === 'object'
) {
const TARGETS = taskConfig.filesystem.targets; const TARGETS = taskConfig.filesystem.targets;
if (TARGETS.length < 1) { if (TARGETS.length < 1) {
ERRORS.push("filesystem.targets is not defined or is not an array"); ERRORS.push('filesystem.targets is not defined or is not an array');
} else { } else {
for (const target of TARGETS) { for (const target of TARGETS) {
if (!target || typeof target !== "string") { if (!target || typeof target !== 'string') {
ERRORS.push("filesystem.targets[] is not defined or is not a valid path"); ERRORS.push(
'filesystem.targets[] is not defined or is not a valid path',
);
} }
} }
} }
} else { } else {
ERRORS.push("filesystem.targets is not defined or is not an array"); ERRORS.push('filesystem.targets is not defined or is not an array');
} }
} }
} else { } else {
ERRORS.push("filesystem section is missing or not an object"); ERRORS.push('filesystem section is missing or not an object');
ERRORS.push("filesystem.enabled is not defined or is not a boolean"); ERRORS.push('filesystem.enabled is not defined or is not a boolean');
ERRORS.push("filesystem.targets is not defined or is not an array"); ERRORS.push('filesystem.targets is not defined or is not an array');
} }
return ERRORS; return ERRORS;
} };
const cli = async (forcePrompt) => { const cli = async (forcePrompt) => {
const FIRST_RUN = !FS.existsSync(TASKS_DIRECTORY); const FIRST_RUN = !fs.existsSync(TASKS_DIRECTORY);
try { try {
if (!FS.existsSync(TASKS_DIRECTORY)) { if (!fs.existsSync(TASKS_DIRECTORY)) {
LOGGER.info("tasks directory does not exist, creating it..."); logger.info('tasks directory does not exist, creating it...');
await FS.mkdirSync(TASKS_DIRECTORY); await fs.mkdirSync(TASKS_DIRECTORY);
} }
if (!FS.existsSync(BACKUPS_DIRECTORY)) { if (!fs.existsSync(BACKUPS_DIRECTORY)) {
LOGGER.info("backups directory does not exist, creating it..."); logger.info('backups directory does not exist, creating it...');
await FS.mkdirSync(BACKUPS_DIRECTORY); await fs.mkdirSync(BACKUPS_DIRECTORY);
} }
if (!FS.existsSync(TEMP_DIR)) { if (!fs.existsSync(TEMP_DIR)) {
await FS.mkdirSync(TEMP_DIR); await fs.mkdirSync(TEMP_DIR);
} }
} catch (err) { } catch (err) {
LOGGER.error(err); logger.error(err);
} }
if (FIRST_RUN) { if (FIRST_RUN) {
const PROMPT = await PROMPTS({ const PROMPT = await prompts({
type: "toggle", type: 'toggle',
name: "createTask", name: 'createTask',
message: "This is the first time you run this script, do you want to create a new task?", message:
'This is the first time you run this script, do you want to create a new task?',
active: 'yes', active: 'yes',
inactive: 'no', inactive: 'no',
initial: false initial: false,
}); });
if (PROMPT.createTask) { if (PROMPT.createTask) {
run({ action: "generateTaskConf" }); run({action: 'generateTaskConf'});
} else cli(true); } else cli(true);
} else { } else {
const ARGS = process.argv.slice(2); const ARGS = process.argv.slice(2);
let action = ARGS[0]; let action = ARGS[0];
let fileName = ARGS[1]; const fileName = ARGS[1];
if (action === undefined || forcePrompt) { if (action === undefined || forcePrompt) {
const PROMPT = await PROMPTS({ const PROMPT = await prompts({
type: "select", type: 'select',
name: "action", name: 'action',
message: "What would you like to do?", message: 'What would you like to do?',
choices: [ choices: [
{ title: "Create a new task file", value: "generateTaskConf" }, {title: 'Create a new task file', value: 'generateTaskConf'},
{ title: "Check a task file", value: "checkTaskConf" }, {title: 'Check a task file', value: 'checkTaskConf'},
{ title: "Perform a Backup", value: "backup" }, {title: 'Perform a Backup', value: 'backup'},
{ title: "Exit", value: "exit" } {title: 'Create a Crontab for a task file', value: 'createCrontab'},
{title: 'Exit', value: 'exit'},
], ],
}); });
action = PROMPT.action; action = PROMPT.action;
@@ -442,12 +562,11 @@ const cli = async (forcePrompt) => {
const programArgs = { const programArgs = {
action, action,
filename: fileName filename: fileName,
} };
run(programArgs); run(programArgs);
} }
} };
cli(false); cli(false);

View File

@@ -2,16 +2,16 @@ const ansiColors = require('ansi-colors');
module.exports = { module.exports = {
info(message) { info(message) {
console.log(ansiColors.bold.blue("🛈") + " " + ansiColors.bold(message)); console.log(ansiColors.bold.blue('🛈') + ' ' + ansiColors.bold(message));
}, },
warn(message) { warn(message) {
console.log(ansiColors.bold.yellow("⚠") + " " + ansiColors.bold(message)); console.log(ansiColors.bold.yellow('⚠') + ' ' + ansiColors.bold(message));
}, },
error(message) { error(message) {
console.log(ansiColors.red.red("✖") + " " + ansiColors.bold(message)); console.log(ansiColors.red.red('✖') + ' ' + ansiColors.bold(message));
}, },
success(message) { success(message) {
console.log(ansiColors.green("✔") + " " + ansiColors.bold(message)); console.log(ansiColors.green('✔') + ' ' + ansiColors.bold(message));
} },
} };

View File

@@ -14,6 +14,8 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"eslint": ">=5.16.0",
"eslint-config-google": "^0.14.0",
"pkg": "^5.8.0" "pkg": "^5.8.0"
}, },
"dependencies": { "dependencies": {
@@ -21,12 +23,25 @@
"ansi-colors": "^4.1.3", "ansi-colors": "^4.1.3",
"archiver": "^5.3.1", "archiver": "^5.3.1",
"count-files-dirs": "^0.2.3", "count-files-dirs": "^0.2.3",
"crontab": "^1.4.2",
"fs-extra": "^10.1.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"mysqldump": "^3.2.0", "mysqldump": "^3.2.0",
"ora": "5.4.1", "ora": "5.4.1",
"prettytime": "^1.0.0", "prettytime": "^1.0.0",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"recursive-readdir": "^2.2.2",
"tiny-glob": "^0.2.9" "tiny-glob": "^0.2.9"
},
"pnpm": {
"overrides": {
"minimist@<1.2.6": ">=1.2.6",
"braces@<2.3.1": ">=2.3.1",
"diff@<3.5.0": ">=3.5.0",
"minimatch@<3.0.2": ">=3.0.2",
"debug@<2.6.9": ">=2.6.9",
"minimist@<0.2.1": ">=0.2.1",
"glob-parent@<5.1.2": ">=5.1.2",
"growl@<1.10.0": ">=1.10.0"
}
} }
} }

View File

@@ -1,4 +1,5 @@
const prettytime = require('prettytime') /* eslint-disable require-jsdoc */
const prettytime = require('prettytime');
class Timer { class Timer {
startTimer() { startTimer() {
this.start = Date.now(); this.start = Date.now();
@@ -6,8 +7,7 @@ class Timer {
} }
endTimer() { endTimer() {
const ELAPSED = Date.now() - this.start; return prettytime(Date.now() - this.start, {decimals: 2});
return prettytime(ELAPSED, { decimals: 2 });
} }
} }