Backup of ${dbType} database ${dbName} on ${currentDateString} completed successfully.
`;
51 |
52 | if (removedDirs.length > 0) {
53 | removedDirs = removedDirs.map((d) => {
54 | d = d.replace(constants.DB_BACKUP_DIR_PREFIX, '');
55 | d = d.replace(jobName, '');
56 | d = utils.replaceAll(d, '-', '/');
57 | d = new Date(d);
58 | return d.toDateString();
59 | });
60 | retString += `
Removed backup of ${dbType} database ${dbName} on ${removedDirs}.
`;
61 | }
62 |
63 | retString += `
64 | Remote-Sync:`;
65 |
66 | if (remoteStatus) {
67 | if (remoteSuccess) {
68 | retString += `
Remote sync of local backups to ${jobConfObj.remoteType} completed successfully.
`;
69 | } else {
70 | retString += `
Remote sync of local backups to ${jobConfObj.remoteType} failed.
71 |
${remoteError.name}: ${remoteError.message}
72 |
Stacktrace:
73 |
${remoteError}
`;
74 | }
75 | } else {
76 | retString += `
Disabled
`;
77 | }
78 | } else {
79 | retString += `
Backup of ${dbType} database ${dbName} on ${currentDateString} failed
80 |
${dbError.name}: ${dbError.message}
81 |
Stacktrace:
82 |
${dbError}
`;
83 | }
84 |
85 | retString += `
86 |
SMTP:
87 |
Status notifications are sent to the following e-mails:
88 |
${jobConfObj.smtpRecipientMail}
`;
89 |
90 | return retString;
91 | };
92 |
93 | const statusReportLog = (jobName, key, dbSuccess, removedDirs, dbError, remoteSuccess, remoteError) => {
94 | const jobConfStore = new configstore({configName: jobName, encryptionKey: key});
95 | const jobConfObj = jobConfStore.store;
96 | const currentDateString = new Date().toDateString();
97 | const remoteSyncEnabled = jobConfObj.remoteSyncEnabled;
98 | const smtpEnabled = jobConfObj.smtpEnabled;
99 | const dbType = jobConfObj.dbType;
100 | const dbName = jobConfObj.dbName;
101 | let retString = `Job '${jobName}' - Daily Status Report:\n\n`;
102 |
103 | if (dbSuccess) {
104 | retString += `Backup of ${dbType} database ${dbName} on ${currentDateString} completed successfully.\n`;
105 |
106 | if (removedDirs.length > 0) {
107 | removedDirs = removedDirs.map((d) => {
108 | d = d.replace(constants.DB_BACKUP_DIR_PREFIX, '');
109 | d = d.replace(jobName, '');
110 | d = utils.replaceAll(d, '-', '/');
111 | d = new Date(d);
112 | return d.toDateString();
113 | });
114 | retString += `Removed backup of ${dbType} database ${dbName} on ${removedDirs}.\n`;
115 | }
116 |
117 | retString += `\nRemote-Sync:\n`;
118 |
119 | if (remoteSyncEnabled) {
120 | if (remoteSuccess) {
121 | retString += `Remote sync of local backups to ${jobConfObj.remoteType} completed successfully.\n`;
122 | } else {
123 | retString += `Remote sync of local backups to ${jobConfObj.remoteType} failed.\n${remoteError.name}: ${remoteError.message}\n`;
124 | retString += `Stacktrace:\n${remoteError}\n`;
125 | }
126 | } else {
127 | retString += `Disabled\n`;
128 | }
129 | } else {
130 | retString += `Backup of ${dbType} database ${dbName} on ${currentDateString} failed.\n${dbError.name}: ${dbError.message}\n`;
131 | retString += `Stacktrace:\n${dbError}\n`;
132 | }
133 |
134 | retString += `\nSMTP:\n`;
135 | if (smtpEnabled) {
136 | retString += `Status notifications are sent to the following e-mails:\n${jobConfObj.smtpRecipientMail}\n`;
137 | } else {
138 | retString += `Disabled\n`;
139 | }
140 |
141 | return retString;
142 | };
143 |
144 | const jobConfigsLog = (jobName, jobConfObj) => {
145 | const backupTime = new Date(jobConfObj.dbBackupTime).toTimeString();
146 | const smtpNotifyTime = new Date(jobConfObj.smtpNotifyTime).toTimeString();
147 | let retString = `\nStarting Job '${jobName}' with following configuration:\n`;
148 | retString += `Backup of ${jobConfObj.dbType} database ${jobConfObj.dbName} scheduled on ${backupTime}\n`;
149 |
150 | retString += `Remote-Sync:\n`;
151 |
152 | if (jobConfObj.remoteSyncEnabled) {
153 | retString += `Local backups will be synched to the remote - ${jobConfObj.remoteType}.\n`;
154 | } else {
155 | retString += `Disabled\n`;
156 | }
157 |
158 | retString += `SMTP:\n`;
159 | if (jobConfObj.smtpEnabled) {
160 | retString += `Status notifications scheduled to be sent to the following e-mails on ${smtpNotifyTime}:\n`;
161 | retString += `${jobConfObj.smtpRecipientMail}\n`;
162 | } else {
163 | retString += `Disabled\n`;
164 | }
165 |
166 | return retString;
167 | };
168 |
169 | module.exports = {
170 | debugModeDesc: 'Re run with --stacktrace or --debug to get more details about the error',
171 | validNoWarning: 'Please enter a valid number',
172 | usageInfo,
173 | helpDesc,
174 | synchlyStartedDesc: 'Spawning synchly jobs...',
175 | statusReportTemplate,
176 | statusReportLog,
177 | fileWoConfigArg: 'Use --file=filePath along with --config=module for initializing the module config using the file',
178 | jobConfigsLog,
179 | enableJobsWarning: 'Only enabled jobs are started, enable a job using synchly --enablejob --job=NAME',
180 | moduleStatusEnabled: 'enabled',
181 | moduleStatusDisabled: 'disabled',
182 | serviceName: 'synchly-nodejs',
183 | accountName: 'synchlyAdmin',
184 | encryptionTag: 'bETr',
185 | };
186 |
--------------------------------------------------------------------------------
/src/database/inquirer.js:
--------------------------------------------------------------------------------
1 | const inquirer = require('inquirer');
2 | const files = require('./../utils/files');
3 | const configstore = require('conf');
4 | const constants = require('../utils/constants');
5 | const strings = require('./../utils/strings');
6 |
7 | let askConfig = async (jobName, key) => {
8 | const jobConfStore = new configstore({configName: jobName, encryptionKey: key});
9 | const jobConfigObj = jobConfStore.store;
10 |
11 | inquirer.registerPrompt('datetime', require('inquirer-datepicker-prompt'));
12 |
13 | let questions = [];
14 |
15 | questions.push({
16 | type: 'list',
17 | name: 'dbType',
18 | message: 'Choose the type of database to backup',
19 | choices: ['MongoDB', 'MySQL', 'PostgreSQL'],
20 | default: jobConfigObj.dbType || 'MongoDB',
21 | });
22 |
23 | questions.push({
24 | name: 'dbAuthUser',
25 | type: 'input',
26 | message: 'Enter your database username:',
27 | default: jobConfigObj.dbAuthUser,
28 | validate: function (value) {
29 | if (value.length) {
30 | return true;
31 | } else {
32 | return 'Please enter your database username.';
33 | }
34 | },
35 | });
36 |
37 | questions.push({
38 | name: 'dbAuthPwd',
39 | type: 'password',
40 | message: 'Enter your database password:',
41 | default: jobConfigObj.dbAuthPwd,
42 | mask: true,
43 | validate: function (value) {
44 | if (value.length) {
45 | return true;
46 | } else {
47 | return 'Please enter your database password.';
48 | }
49 | },
50 | });
51 |
52 | questions.push({
53 | name: 'dbHost',
54 | type: 'input',
55 | message: 'Enter the database hostname:',
56 | default: jobConfigObj.dbHost || 'localhost',
57 | validate: function (value) {
58 | if (value.length) {
59 | return true;
60 | } else {
61 | return 'Please enter the database hostname.';
62 | }
63 | },
64 | });
65 |
66 | questions.push({
67 | name: 'dbPort',
68 | type: 'input',
69 | message: 'Enter the database server port:',
70 | default: function (ans) {
71 | let defaultPort;
72 | if (ans.dbType == 'MongoDB') defaultPort = '27017';
73 | else if (ans.dbType == 'MySQL') defaultPort = '3306';
74 | else if(ans.dbType == 'PostgreSQL') defaultPort = '5432';
75 | return jobConfigObj.dbPort || defaultPort;
76 | },
77 | validate: function (value) {
78 | if (value.length) {
79 | if (isNaN(value) || Number(value) == 0) {
80 | return strings.validNoWarning;
81 | }
82 | return true;
83 | } else {
84 | return 'Please enter the database server port.';
85 | }
86 | },
87 | });
88 |
89 | questions.push({
90 | name: 'dbName',
91 | type: 'input',
92 | message: 'Enter the database name to backup:',
93 | default: jobConfigObj.dbName,
94 | validate: function (value) {
95 | if (value.length) {
96 | return true;
97 | } else {
98 | return 'Please enter your database name to be backed up.';
99 | }
100 | },
101 | });
102 |
103 | questions.push({
104 | name: 'dbAuthSource',
105 | type: 'input',
106 | message: 'Enter the database name associated with the user credentials (i.e. authSource):',
107 | default: 'admin',
108 | validate: function (value) {
109 | if (value.length) {
110 | return true;
111 | } else {
112 | return 'Please enter the database name associated with the user credentials.';
113 | }
114 | },
115 | when: function (answers) {
116 | return answers.dbType == 'MongoDB';
117 | },
118 | });
119 |
120 | questions.push({
121 | name: 'dbBackupPath',
122 | type: 'input',
123 | message: 'Enter the absolute path of the directory for storing local backups:',
124 | default: jobConfigObj.dbBackupPath,
125 | validate: function (value) {
126 | if (value.length) {
127 | if (!files.directoryExists(value)) {
128 | return `No such directory, '${value}'`;
129 | }
130 | let isFile = files.isFile(value);
131 | if (isFile) {
132 | return `'${value}' is a file.`;
133 | }
134 | return true;
135 | } else {
136 | return 'Please enter the absolute path of the directory for storing local backups.';
137 | }
138 | },
139 | });
140 | questions.push({
141 | type: 'confirm',
142 | name: 'backupEncryptionEnabled',
143 | message: 'Do you want the backup files to be encrypted?',
144 | when: function () {
145 | if (key) {
146 | return true;
147 | }
148 | },
149 | });
150 |
151 | questions.push({
152 | type: 'confirm',
153 | name: 'dbIsCompressionEnabled',
154 | message: 'Do you want to enable backup compression?',
155 | });
156 |
157 | const dbBackupTimeString = jobConfigObj.dbBackupTime || '1970-01-01 00:00';
158 |
159 | questions.push({
160 | type: 'datetime',
161 | name: 'dbBackupTime',
162 | message: 'Enter the time to run the backups every day:',
163 | format: ['H', ':', 'MM', ' ', 'Z'],
164 | initial: new Date(dbBackupTimeString),
165 | });
166 |
167 | questions.push({
168 | name: 'dbNoOfDays',
169 | type: 'input',
170 | message: 'Enter the No. of days to persist backups for (1 backup per day):',
171 | default: jobConfigObj.dbNoOfDays || '7',
172 | validate: function (value) {
173 | if (value.length) {
174 | if (isNaN(value) || Number(value) == 0) {
175 | return strings.validNoWarning;
176 | }
177 | return true;
178 | } else {
179 | return 'Please enter No. of days to persist backups for.';
180 | }
181 | },
182 | });
183 |
184 | questions.push({
185 | name: 'dbNoOfWeeks',
186 | type: 'input',
187 | message: 'Enter the No. of weeks to persist backups for (1 backup per week):',
188 | default: jobConfigObj.dbNoOfWeeks || '8',
189 | validate: function (value) {
190 | if (value.length) {
191 | if (isNaN(value) || Number(value) == 0) {
192 | return strings.validNoWarning;
193 | }
194 | return true;
195 | } else {
196 | return 'Please enter No. of weeks to persist backups for.';
197 | }
198 | },
199 | });
200 |
201 | questions.push({
202 | name: 'dbNoOfMonths',
203 | type: 'input',
204 | message: 'Enter the No. of months to persist backups for (1 backup per month):',
205 | default: jobConfigObj.dbNoOfMonths || '6',
206 | validate: function (value) {
207 | if (value.length) {
208 | if (isNaN(value) || Number(value) == 0) {
209 | return strings.validNoWarning;
210 | }
211 | return true;
212 | } else {
213 | return 'Please enter No. of months to persist backups for.';
214 | }
215 | },
216 | });
217 |
218 | let dbConfig;
219 | try {
220 | dbConfig = await inquirer.prompt(questions);
221 | } catch (e) {
222 | throw e;
223 | }
224 |
225 | return dbConfig;
226 | };
227 |
228 | let askRestoreConfig = async (jobName, key) => {
229 | const jobConfStore = new configstore({configName: jobName, encryptionKey: key});
230 | const jobConfigObj = jobConfStore.store;
231 | let questions = [];
232 | let fileList = [];
233 | let choices = [];
234 |
235 | fileList = await files.listFileNames(jobConfigObj.dbBackupPath);
236 | choices = getJobSpecificBackups(jobName, fileList);
237 | if (choices.length == 0) {
238 | throw {
239 | name: 'Empty directory',
240 | message: 'No backups have been found',
241 | };
242 | }
243 | questions.push({
244 | type: 'list',
245 | name: 'backupFileName',
246 | message: 'Choose the backup to restore :',
247 | choices: choices,
248 | default: choices[0],
249 | });
250 | questions.push({
251 | type: 'confirm',
252 | name: 'restoreConfirmation',
253 | message: 'Restoring database from the backup will flush the existing database, are you sure want to conitnue ?',
254 | });
255 | let restoreConfig;
256 | restoreConfig = await inquirer.prompt(questions);
257 | return restoreConfig;
258 | };
259 |
260 | var getJobSpecificBackups = (jobName, fileList) => {
261 | let choices = [];
262 | for (let index in fileList) {
263 | let fileName = fileList[index];
264 | let jobNameAutomatic = fileName.split(constants.DB_BACKUP_DIR_PREFIX);
265 | let jobNameManual = fileName.split(constants.DB_MANUAL_BACKUP_DIR_PREFIX);
266 | if (jobNameAutomatic[0] == jobName || jobNameManual[0] == jobName) {
267 | choices.push(fileName);
268 | }
269 | }
270 | return choices;
271 | };
272 |
273 | module.exports = {
274 | askConfig,
275 | askRestoreConfig,
276 | };
277 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | ## Database configuration
2 |
3 | #### MongoDB
4 |
5 | ```
6 | $ synchly --config db
7 | ? Choose the type of database to backup MongoDB
8 | ? Enter your database username: foo
9 | ? Enter your database password: ***
10 | ? Enter the database hostname: localhost
11 | ? Enter the database server port: 27017
12 | ? Enter the database name to backup: foobar
13 | ? Enter the database name associated with the user credentials (i.e. authSource): admin
14 | ? Enter the absolute path of the directory for storing local backups: /home/foobar/backups/
15 | ? Do you want the backup files to be encrypted? Yes
16 | ? Do you want to enable backup compression? Yes
17 | ? Enter the time to run the backups every day: 2:30 GMT+0530
18 | ? Enter the No. of days to persist backups for (1 backup per day): 7
19 | ? Enter the No. of weeks to persist backups for (1 backup per week): 8
20 | ? Enter the No. of months to persist backups for (1 backup per month): 6
21 | ✔ Authentication success
22 | Database configuration updated successfully.
23 | ```
24 |
25 | #### MySQL
26 |
27 | ```
28 | $ synchly --config db
29 | ? Choose the type of database to backup MySQL
30 | ? Enter your database username: foo
31 | ? Enter your database password: ***
32 | ? Enter the database hostname: localhost
33 | ? Enter the database server port: 3306
34 | ? Enter the database name to backup: foobar
35 | ? Enter the absolute path of the directory for storing local backups: /home/foobar/backups/
36 | ? Do you want the backup files to be encrypted? Yes
37 | ? Do you want to enable backup compression? Yes
38 | ? Enter the time to run the backups every day: 2:30 GMT+0530
39 | ? Enter the No. of days to persist backups for (1 backup per day): 7
40 | ? Enter the No. of weeks to persist backups for (1 backup per week): 8
41 | ? Enter the No. of months to persist backups for (1 backup per month): 6
42 | ✔ Authentication success
43 | Database configuration updated successfully.
44 | ```
45 |
46 | #### PostgreSQL
47 |
48 | ```
49 | $ synchly --config db
50 | ? Choose the type of database to backup PostgreSQL
51 | ? Enter your database username: foo
52 | ? Enter your database password: ***
53 | ? Enter the database hostname: localhost
54 | ? Enter the database server port: 5432
55 | ? Enter the database name to backup: foobar
56 | ? Enter the absolute path of the directory for storing local backups: /home/foobar/backups/
57 | ? Do you want the backup files to be encrypted? Yes
58 | ? Do you want to enable backup compression? Yes
59 | ? Enter the time to run the backups every day: 2:30 GMT+0530
60 | ? Enter the No. of days to persist backups for (1 backup per day): 7
61 | ? Enter the No. of weeks to persist backups for (1 backup per week): 8
62 | ? Enter the No. of months to persist backups for (1 backup per month): 6
63 | ✔ Authentication success
64 | Database configuration updated successfully.
65 | ```
66 |
67 | ## Cloud Storage (remote-sync) configuration
68 |
69 | #### Google Drive
70 |
71 | Synchly uses a service account for authentication to use Google Drive API.
72 | Steps to create a service account:
73 | * Visit [Google Cloud Console](https://console.developers.google.com) and select a project or create a new one. You can access credentials page using the tab on the left. Go to the [Credentials](https://console.developers.google.com/apis/credentials) page, click “create credentials” and then click “service account key”.
74 | * When you’re done, you’ll get a JSON file with the following structure:
75 | ```
76 | {
77 | "type": "service_account",
78 | "project_id": "",
79 | "private_key_id": "",
80 | "private_key": "",
81 | "client_email": "",
82 | "client_id": "",
83 | "auth_uri": "",
84 | "token_uri": "",
85 | "auth_provider_x509_cert_url": "",
86 | "client_x509_cert_url": ""
87 | }
88 | ```
89 | * The absolute path of the downloaded service account key file is used in the remote-sync configuration.
90 | * Enable API - You should enable the APIs you want to use in [Google’s API Library](https://console.developers.google.com/apis/library). By default, nothing is enabled, to prevent you from changing anything by accident. You’ll need to enable [Google Drive](https://console.developers.google.com/apis/api/drive.googleapis.com).
91 | * Share Files - To use the service account method of authentication, you’ll need to provide access to the service account. Find the "client_email" key in your service account key file downloaded and copy the value. This is the email address that you’ll need to share, and it will look something like:
92 | ```
93 | service-account@projectname-123456.iam.gserviceaccount.com
94 | ```
95 | Create a new folder in your account’s Google Drive, and then share it with the "client-email" in your service account key file. Sharing access to the service account is required, and it’s one of the reasons a service account is more secure than a simple API key.
96 |
97 | ```
98 | $ synchly --config remote-sync
99 | ? Choose the remote service: Google Drive
100 | ? Enter the absolute path of the service account key file: /home/foobar/googleCreds.json
101 | ✔ Authentication success
102 | ? Choose the remote folder in which backups will be stored: Backups
103 | Remote Sync configuration updated successfully.
104 | ```
105 |
106 | #### SFTP
107 |
108 | ```
109 | $ synchly --config remote-sync
110 | ? Choose the remote service: SFTP
111 | ? Enter the hostname or IP of the remote server: exampleHostName.com
112 | ? Enter the port number of the remote server: 22
113 | ? Enter the username for authentication: foo
114 | ? Enter the password for authentication: ***
115 | ? Enter the absolute path of the directory for storing backups on the remote server: /home/foobar/backups
116 | ✔ Authentication success
117 | Remote Sync configuration updated successfully.
118 | ```
119 |
120 | #### s3
121 |
122 | Synchly needs `aws_access_key_id` and `aws_secret_access_key` for authentication to use AWS s3 API.
123 | Steps to create a local sdk credentials file:
124 | * Create a file named `credentials.json` in your local system.
125 | * Visit [this blog](https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/) to know how to get `aws_access_key_id` and `aws_secret_access_key` of your aws account.
126 | * `credentials.json` should contain two keys shown in following structure:
127 | ```
128 | {
129 | "aws_access_key_id" : "",
130 | "aws_secret_access_key" : ""
131 | }
132 | ```
133 | * When you’re done, copy the values from aws account page to `credentials.json` file.
134 | * The absolute path of the `credentials.json` file is used in the remote-sync configuration.
135 |
136 | ```
137 | $ synchly --config remote-sync
138 | ? Choose the remote service: S3
139 | ? Enter the absolute path of the service account key file: /home/foobar/credentials.json
140 | ✔ Authentication success
141 | ? Choose the remote bucket in which backups will be stored: BucketBackup
142 | ? Choose the remote folder in "BucketBackup" in which backups will be stored: Backups
143 | Remote Sync configuration updated successfully.
144 | ```
145 |
146 | ## Status notifications (smtp) configuration
147 |
148 | You can use any email delivery provider which supports SMTP for configuring status reports.
149 |
150 | ```
151 | $ synchly --config smtp
152 | ? Enter your SMTP hostname: smtp.sendgrid.net
153 | ? Enter your SMTP port: 465
154 | ? Enter the SMTP username: apikey
155 | ? Enter the SMTP password: *******************
156 | ? Enter the SMTP sender e-mail: exampleSender@gmail.com
157 | ? Enter the SMTP recipient e-mail: exampleRecipient@gmail.com
158 | ? Enter the time to send the status updates every day: 06:00 GMT+0530
159 | ✔ Authentication success
160 | SMTP configuration updated successfully.
161 | ```
162 |
163 | #### Using Gmail
164 |
165 | Gmail has come up with the concept of [“Less Secure”](https://support.google.com/accounts/answer/6010255?hl=en) apps which is basically anyone who uses a plain password to login to Gmail. You can configure your Gmail account to allow less secure apps [here](https://www.google.com/settings/security/lesssecureapps). When using this method make sure to also enable the required functionality by completing the [“Captcha Enable”](https://accounts.google.com/b/0/displayunlockcaptcha) challenge. Without this, less secure connections probably would not work.
166 |
167 | If you are using 2FA you would have to create an [“Application Specific”](https://security.google.com/settings/security/apppasswords) password.
168 |
169 | Gmail also always sets an authenticated username as the From: email address. So if you authenticate as foo@example.com and set bar@example.com as the from: address, then Gmail reverts this and replaces the sender with the authenticated user.
170 |
171 | ```
172 | $ synchly --config smtp
173 | ? Enter your SMTP hostname: smtp.gmail.com
174 | ? Enter your SMTP port: 465
175 | ? Enter the SMTP username: exampleSender@gmail.com
176 | ? Enter the SMTP password: ****
177 | ? Enter the SMTP sender e-mail: exampleSender@gmail.com
178 | ? Enter the SMTP recipient e-mail: exampleRecipient@gmail.com
179 | ? Enter the time to send the status updates every day: 06:00 GMT+0530
180 | ✔ Authentication success
181 | SMTP configuration updated successfully.
182 | ```
183 |
184 | ## Enabling modules
185 |
186 | remote-sync and smtp modules are disabled by default. Even after adding or updating their config via `synchly --config=module`, you have to enable them in order to be able to use the module.
187 |
188 | Enable a module like:
189 |
190 | ```
191 | $ synchly --enable remote-sync
192 | ```
193 |
194 | ```
195 | $ synchly --enable smtp
196 | ```
197 |
198 | ## Disabling modules
199 |
200 | Disable a module anytime to stop using it.
201 |
202 | ```
203 | $ synchly --disable remote-sync
204 | ```
205 |
206 | ```
207 | $ synchly --disable smtp
208 | ```
209 |
210 | ## Stacktrace of errors
211 |
212 | Flag `-S, --stacktrace` can be used with the other options to get the full stacktrace of the error.
213 |
214 | ```
215 | $ synchly --config db --stacktrace
216 | $ synchly --enable remote-sync --stacktrace
217 | $ synchly --start --stacktrace
218 | ```
219 |
220 | ## Running multiple jobs
221 |
222 | Create a new job with `-j, --job`. A job named 'master' is created by default if the option `--job` is not specified.
223 |
224 | ```
225 | $ synchly --job exampleJob --config db
226 | ```
227 |
228 | All the options can be used along with the option `-j, --job` to make them job-specific.
229 |
230 | All jobs are enabled by default. Enable stopped jobs before they can be run.
231 |
232 | ```
233 | $ synchly --enablejob --job exampleJob
234 | ```
235 |
236 | Disable a job to stop it from running.
237 |
238 | ```
239 | $ synchly --disablejob --job exampleJob
240 | ```
241 |
242 | Reset all the saved configurations for a job using the option `--reset`.
243 |
244 | ```
245 | $ synchly --job exampleJob --reset
246 | ```
247 |
248 | Start all the enabled jobs using the option `--start`
249 |
250 | ```
251 | $ synchly --start
252 | ```
253 |
254 | Show all the created jobs using the option `--jobs`
255 |
256 | ```
257 | $ synchly --jobs
258 | ```
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Synchly backups
2 |
3 |
4 | 
5 | 
6 | [](https://github.com/hariprasanths/synchly/blob/master/docs)
7 | [](https://github.com/hariprasanths/synchly/blob/master/LICENSE)
8 |
9 |
10 | * [Description](#description)
11 | * [Features](#features)
12 | * [Prerequisites](#prerequisites)
13 | * [Installation](#installation)
14 | * [Tab completion](#tab-completion)
15 | * [Usage](#usage)
16 | * [Quick setup](#quick-setup)
17 | * [List of options](#list-of-options)
18 | * [Running as a daemon](#running-as-a-daemon)
19 | * [Examples](#examples)
20 | * [Contributing](#contributing)
21 | * [Show your support](#show-your-support)
22 | * [License](#license)
23 |
24 |
25 | ## Description
26 | Automate database backups with customizable recurring schedules.
27 |
28 | ## Features
29 |
30 | * **Backup scheme** - Synchly uses a [Grandfather-father-son backup rotation scheme](https://en.wikipedia.org/wiki/Backup_rotation_scheme#Grandfather-father-son) (daily, weekly, monthly) that is fully customizable. Default schedule: 7 dailies + 8 weeklies + 6 monthlies (at max there will be 21 backups at a given instant).
31 | * **Flexible scheduling** - Schedule the daily backups to fit your maintenance and development schedule, so that you get a clear picture of your database backups over time.
32 | * **Supported Databases**
33 | * MySQL
34 | * MongoDB
35 | * PostgreSQL
36 | * **Compression** - Compress the database backups to save up space.
37 | * **Cloud Storage Integration** - Sync the local backups to remote storage of your choice.
38 | * **Restoration** - Restore the database from the backups.
39 | * **Supported remote storages**
40 | * Google Drive
41 | * SFTP
42 | * S3
43 | * **Status notifications** - Get daily status reports for successful and failed backups, delivered when you want them via SMTP to the specified email(s). Check [Usage](#usage) and the [List of Options](#list-of-options) below.
44 | * **Multiple Backup Jobs** - Run multiple backup jobs in parallel.
45 | * **Encryption** - Encrypt the job configuration and backup files.
46 |
47 | ## Prerequisites
48 |
49 | * node >=8
50 |
51 | ## Installation
52 |
53 | The Synchly CLI is distributed as an NPM package. To use it, install it globally using:
54 |
55 | ```
56 | npm install --global synchly
57 | ```
58 |
59 | or using yarn:
60 |
61 | ```
62 | yarn global add synchly
63 | ```
64 |
65 | ## Tab completion
66 |
67 | The synchly package includes a useful tab-completion feature. This feature is installed automatically after the installation of the package. However, you might need to restart the console after installing the package for the autocomplete feature to work.
68 |
69 | If you use **Bash**, it will create a file at ~/.synchly/completion.sh and append a loader code to ~/.bash_profile file.
70 |
71 | If you use **Zsh**, it appends a loader code to ~/.zshrc file.
72 |
73 | If you use **Fish**, it appends a loader code to ~/.config/fish/config.fish file.
74 |
75 | ## Usage
76 |
77 | ```
78 | $ synchly [--config module]
79 | $ synchly [--config module] [--file filepath]
80 | $ synchly [--disablejob] [--job exampleJob]
81 | $ synchly [--disable module] [--debug]
82 | $ synchly [--enablejob] [--job exampleJob]
83 | $ synchly [--enable module] [--stacktrace]
84 | $ synchly [--help]
85 | $ synchly [--job exampleJob] [--config module]
86 | $ synchly [--jobs]
87 | $ synchly [--reset]
88 | $ synchly [--restore]
89 | $ synchly [--run]
90 | $ synchly [--start]
91 | $ synchly [--version]
92 | ```
93 |
94 | ## Quick setup
95 |
96 | Synchly can be run with `--help` flag to get a full list of flags.
97 |
98 | The quickest way to get started is to run the `synchly --config=db` command.
99 |
100 | To start synchly use the command `synchly --start`.
101 | Synchly instance have to be restarted everytime you make a change to the configuration using the [cli options](#list-of-options).
102 |
103 | To restore database from the backup files use the command `synchly --restore`.
104 |
105 | Configuration of modules (remote-sync and smtp) can be added or updated using `synchly --config=module` command.
106 | Initializing configurations can also be done using a file, `synchly --config=module --file=filepath`, refer [Configuration using file](https://github.com/hariprasanths/synchly/blob/master/docs/configuration-using-file.md#configuration-using-file).
107 |
108 | By default, remote-sync and smtp modules are disabled, to enable them, use `synchly --enable=module` command.
109 |
110 | To encyrpt the job configuration files use the command `synchly --enable=cipher` and to disable it globally use the command `synchly --disable=cipher`.
111 | Encryption of backup files can be done only when cipher module is enabled.
112 |
113 | For the complete list of options and their behavior, refer [List of options](#list-of-options).
114 |
115 | For running synchly as a daemon, refer [Running as a deamon](#running-as-a-daemon).
116 |
117 | For creating multiple backup jobs, refer [Running multiple jobs](https://github.com/hariprasanths/synchly/blob/master/docs/examples.md#running-multiple-jobs)
118 |
119 | ## List of options
120 |
121 |
122 |
123 |
124 |
Option
125 |
Description
126 |
127 |
128 |
-c, --config=module
129 |
130 |
Create or update module configuration. Allowed modules: db | remote-sync | smtp
131 |
132 |
133 |
134 |
-D, --debug
135 |
136 |
Prints even more information from CLI operations, used for debugging purposes
137 |
138 |
139 |
140 |
--disablejob
141 |
142 |
Disable a job. Use with option --job=NAME to disable the specific job NAME
143 |
144 |
145 |
146 |
-d, --disable=module
147 |
148 |
Disable a module. Allowed modules: cipher | remote-sync | smtp
149 |
150 |
151 |
152 |
--enablejob
153 |
154 |
Enable a job. Use with option --job=NAME to enable the specific job NAME
155 |
156 |
157 |
158 |
-e, --enable=module
159 |
160 |
Enable a module. Allowed modules: cipher | remote-sync | smtp
161 |
162 |
163 |
164 |
-f, --file=filepath
165 |
166 |
Create or update module configuration using the specified file. To be used with --config flag
167 |
168 |
169 |
170 |
-h, --help
171 |
172 |
Prints CLI reference information about options and their arguments
173 |
174 |
175 |
176 |
-j, --job
177 |
178 |
Create a new synchly job with the NAME (creates a job named 'master' by default if the option --job is not specified). This is useful for running multiple backup jobs in parallel
179 |
180 |
181 |
182 |
--jobs
183 |
184 |
Displays information about all the synchly jobs
185 |
186 |
187 |
188 |
--reset
189 |
190 |
Reset all the configurations saved
191 |
192 |
193 |
194 |
-R, --restore
195 |
196 |
Restore database from the backup
197 |
198 |
199 |
200 |
--run
201 |
202 |
Take a database backup forthwith
203 |
204 |
205 |
206 |
-S, --stacktrace
207 |
208 |
Prints even more information about errors from CLI operation, used for debugging purposes. If you find a bug, provide output generated with the --stacktrace flag on when submitting a bug report
209 |
210 |
211 |
212 |
--start
213 |
214 |
Start all the enabled synchly jobs which logs to stdout and stderr