-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a219516
Showing
5 changed files
with
1,574 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
.env | ||
databases.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Database S3 backups | ||
This script provides a simple and automated way to back up your essential databases to AWS S3, ensuring that your data is always safe and retrievable. | ||
|
||
The script provides the capability to initiate backups either upon its startup or on a scheduled basis using Cron expressions. Backups are compressed to reduce file size. | ||
|
||
## How it works | ||
1. Define database connection URI strings for each database you want backed up and the backup schedule | ||
2. The script will connect to the database(s) and do a dump | ||
3. The dump is compressed and uploaded to your defined AWS S3 Bucket | ||
4. Finally, the dumps are cleaned up on the local file system | ||
|
||
Supported databases: | ||
- `postgres` | ||
- `mysql` | ||
- `mongodb`. | ||
|
||
## Configuration | ||
Create a `.env` file in the root directory with the following variables: | ||
|
||
``` | ||
RUN_ON_STARTUP=true | ||
CRON=0 0 * * * | ||
AWS_ACCESS_KEY_ID=<ACCESS_KEY_ID> | ||
AWS_SECRET_ACCESS_KEY=<SECRET_ACCESS_KEY> | ||
AWS_S3_BUCKET=<S3_BUCKET> | ||
AWS_S3_REGION=<S3_REGION> | ||
AWS_S3_ENDPOINT=<S3_ENDPOINT> | ||
DATABASES="mysql://user:password@host:port/database,postgresql://user:password@host:port/database,mongodb://user:password@host:port" | ||
``` | ||
|
||
### Environment variables | ||
|
||
| Key | Description | Optional | Default Value | | ||
|-------------------------|--------------------------|----------|---------------| | ||
| `DATABASES` | Comma-separated string list of database URIs that should be backed up. | No | `[]`| | ||
| `RUN_ON_STARTUP` | Boolean value that indicates if the script should run immediately on startup. | Yes | `false` | | ||
| `CRON` | Cron expression for scheduling when the backup job will run for all databases. See [Crontab.guru](https://crontab.guru/) for help setting up schedules. | Yes | | | ||
| `AWS_ACCESS_KEY_ID` | [AWS access key ID](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). | No | | | ||
| `AWS_SECRET_ACCESS_KEY` | [AWS secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). | No | | | ||
| `AWS_S3_BUCKET` | [Name of the S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html). | No | | | ||
| `AWS_S3_REGION` | [Region of the S3 bucket](https://docs.aws.amazon.com/general/latest/gr/rande.html). | No | | | ||
| `AWS_S3_ENDPOINT` | [Endpoint for the S3 service](https://docs.aws.amazon.com/general/latest/gr/s3.html). | No | | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
require('dotenv').config(); | ||
const util = require('util'); | ||
const exec = util.promisify(require('child_process').exec); | ||
const s3 = require("@aws-sdk/client-s3"); | ||
const fs = require('fs'); | ||
const cron = require("cron"); | ||
|
||
function loadConfig() { | ||
const requiredEnvars = [ | ||
'AWS_ACCESS_KEY_ID', | ||
'AWS_SECRET_ACCESS_KEY', | ||
'AWS_S3_REGION', | ||
'AWS_S3_ENDPOINT', | ||
'AWS_S3_BUCKET' | ||
]; | ||
|
||
for (const key of requiredEnvars) { | ||
if (!process.env[key]) { | ||
throw new Error(`Environment variable ${key} is required`); | ||
} | ||
} | ||
|
||
return { | ||
aws: { | ||
accessKeyId: process.env.AWS_ACCESS_KEY_ID, | ||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, | ||
region: process.env.AWS_S3_REGION, | ||
endpoint: process.env.AWS_S3_ENDPOINT, | ||
s3_bucket: process.env.AWS_S3_BUCKET | ||
}, | ||
databases: process.env.DATABASES ? process.env.DATABASES.split(",") : [], | ||
run_on_startup: process.env.RUN_ON_STARTUP === 'true' ? true : false, | ||
cron: process.env.CRON, | ||
}; | ||
} | ||
|
||
const config = loadConfig(); | ||
|
||
const s3Client = new s3.S3Client(config.aws); | ||
|
||
async function processBackup() { | ||
if (config.databases.length === 0) { | ||
console.log("No databases defined."); | ||
return; | ||
} | ||
|
||
for (const [index, databaseURI] of config.databases.entries()) { | ||
const databaseIteration = index + 1; | ||
const totalDatabases = config.databases.length; | ||
|
||
const url = new URL(databaseURI); | ||
const dbType = url.protocol.slice(0, -1); // remove trailing colon | ||
const dbName = url.pathname.substring(1); // extract db name from URL | ||
const dbHostname = url.hostname; | ||
const dbUser = url.username; | ||
const dbPassword = url.password; | ||
const dbPort = url.port; | ||
|
||
const date = new Date(); | ||
const yyyy = date.getFullYear(); | ||
const mm = String(date.getMonth() + 1).padStart(2, '0'); | ||
const dd = String(date.getDate()).padStart(2, '0'); | ||
const hh = String(date.getHours()).padStart(2, '0'); | ||
const min = String(date.getMinutes()).padStart(2, '0'); | ||
const ss = String(date.getSeconds()).padStart(2, '0'); | ||
const timestamp = `${yyyy}-${mm}-${dd}_${hh}:${min}:${ss}`; | ||
const filename = `backup-${dbType}-${timestamp}-${dbName}-${dbHostname}.tar.gz`; | ||
const filepath = `/tmp/${filename}`; | ||
|
||
console.log(`\n[${databaseIteration}/${totalDatabases}] ${dbType}/${dbName} Backup in progress...`); | ||
|
||
let dumpCommand; | ||
switch (dbType) { | ||
case 'postgresql': | ||
dumpCommand = `pg_dump "${databaseURI}" -F c > "${filepath}.dump"`; | ||
break; | ||
case 'mongodb': | ||
dumpCommand = `mongodump --uri="${databaseURI}" --archive="${filepath}.dump"`; | ||
break; | ||
case 'mysql': | ||
dumpCommand = `mysqldump -u ${dbUser} -p${dbPassword} -h ${dbHostname} -P ${dbPort} ${dbName} > "${filepath}.dump"`; | ||
break; | ||
default: | ||
console.log(`Unknown database type: ${dbType}`); | ||
return; | ||
} | ||
|
||
try { | ||
// 1. Execute the dump command | ||
await exec(dumpCommand); | ||
|
||
// 2. Compress the dump file | ||
await exec(`tar -czvf ${filepath} ${filepath}.dump`); | ||
|
||
// 3. Read the compressed file | ||
const data = fs.readFileSync(filepath); | ||
|
||
// 4. Upload to S3 | ||
const params = { | ||
Bucket: config.aws.s3_bucket, | ||
Key: filename, | ||
Body: data | ||
}; | ||
|
||
const putCommand = new s3.PutObjectCommand(params); | ||
await s3Client.send(putCommand); | ||
|
||
console.log(`✓ Successfully uploaded db backup for database ${dbType} ${dbName} ${dbHostname}.`); | ||
} catch (error) { | ||
console.error(`An error occurred while processing the database ${dbType} ${dbName}, host: ${dbHostname}): ${error}`); | ||
} | ||
} | ||
} | ||
|
||
if (config.cron) { | ||
const CronJob = cron.CronJob; | ||
const job = new CronJob(config.cron, processBackup); | ||
job.start(); | ||
|
||
console.log(`Backups configured on Cron job schedule: ${config.cron}`); | ||
} | ||
|
||
if (config.run_on_startup) { | ||
console.log("run_on_startup enabled, backing up now...") | ||
processBackup(); | ||
} |
Oops, something went wrong.