2022-06-09 03:44:18 +00:00
const fsPromises = require ( 'fs/promises' ) ;
const path = require ( 'path' ) ;
const os = require ( 'os' ) ;
const shell = require ( 'shelljs' ) ;
const utils = require ( './utils' ) ;
const Constants = require ( './constants' ) ;
2022-07-07 05:49:25 +00:00
const logger = require ( './logger' ) ;
const mailer = require ( './mailer' ) ;
const command _args = process . argv . slice ( 3 ) ;
2022-06-09 03:44:18 +00:00
async function run ( ) {
2022-11-01 07:27:41 +00:00
const timestamp = getTimeStampInISO ( ) ;
2022-06-09 03:44:18 +00:00
let errorCode = 0 ;
try {
const check _supervisord _status _cmd = '/usr/bin/supervisorctl >/dev/null 2>&1' ;
shell . exec ( check _supervisord _status _cmd , function ( code ) {
if ( code > 0 ) {
shell . echo ( 'application is not running, starting supervisord' ) ;
shell . exec ( '/usr/bin/supervisord' ) ;
}
} ) ;
2022-07-07 05:49:25 +00:00
console . log ( 'Available free space at /appsmith-stacks' ) ;
2022-11-01 07:27:41 +00:00
const availSpaceInBytes = getAvailableBackupSpaceInBytes ( ) ;
2022-07-07 05:49:25 +00:00
console . log ( '\n' ) ;
2022-11-01 07:27:41 +00:00
checkAvailableBackupSpace ( availSpaceInBytes ) ;
2022-07-07 05:49:25 +00:00
2022-11-01 07:27:41 +00:00
const backupRootPath = await generateBackupRootPath ( ) ;
const backupContentsPath = getBackupContentsPath ( backupRootPath , timestamp ) ;
2022-06-09 03:44:18 +00:00
await fsPromises . mkdir ( backupContentsPath ) ;
await exportDatabase ( backupContentsPath ) ;
await createGitStorageArchive ( backupContentsPath ) ;
await createManifestFile ( backupContentsPath ) ;
await exportDockerEnvFile ( backupContentsPath ) ;
const archivePath = await createFinalArchive ( backupRootPath , timestamp ) ;
2022-07-07 05:49:25 +00:00
await fsPromises . rm ( backupRootPath , { recursive : true , force : true } ) ;
2022-06-09 03:44:18 +00:00
2022-11-01 07:27:41 +00:00
logger . backup _info ( 'Finished taking a backup at' + archivePath ) ;
2022-06-09 03:44:18 +00:00
} catch ( err ) {
errorCode = 1 ;
2022-07-07 05:49:25 +00:00
await logger . backup _error ( err . stack ) ;
2022-06-09 03:44:18 +00:00
2022-07-07 05:49:25 +00:00
if ( command _args . includes ( '--error-mail' ) ) {
2022-07-28 12:15:28 +00:00
const currentTS = new Date ( ) . getTime ( ) ;
const lastMailTS = await utils . getLastBackupErrorMailSentInMilliSec ( ) ;
2022-11-01 07:27:41 +00:00
if ( ( lastMailTS + Constants . DURATION _BETWEEN _BACKUP _ERROR _MAILS _IN _MILLI _SEC ) < currentTS ) {
2022-07-28 12:15:28 +00:00
await mailer . sendBackupErrorToAdmins ( err , timestamp ) ;
await utils . updateLastBackupErrorMailSentInMilliSec ( currentTS ) ;
}
2022-07-07 05:49:25 +00:00
}
2022-06-09 03:44:18 +00:00
} finally {
2023-04-18 01:22:36 +00:00
await postBackupCleanup ( ) ;
2022-06-09 03:44:18 +00:00
process . exit ( errorCode ) ;
}
}
async function exportDatabase ( destFolder ) {
console . log ( 'Exporting database' ) ;
2022-11-01 07:27:41 +00:00
await executeMongoDumpCMD ( destFolder , process . env . APPSMITH _MONGODB _URI )
2022-06-09 03:44:18 +00:00
console . log ( 'Exporting database done.' ) ;
}
async function createGitStorageArchive ( destFolder ) {
console . log ( 'Creating git-storage archive' ) ;
2022-11-01 07:27:41 +00:00
const gitRoot = getGitRoot ( process . env . APPSMITH _GIT _ROOT ) ;
2022-06-09 03:44:18 +00:00
2022-11-01 07:27:41 +00:00
await executeCopyCMD ( gitRoot , destFolder )
2022-06-09 03:44:18 +00:00
console . log ( 'Created git-storage archive' ) ;
}
async function createManifestFile ( path ) {
2023-01-03 06:44:21 +00:00
const version = await utils . getCurrentAppsmithVersion ( )
2022-07-07 05:49:25 +00:00
const manifest _data = { "appsmithVersion" : version }
2022-06-09 03:44:18 +00:00
await fsPromises . writeFile ( path + '/manifest.json' , JSON . stringify ( manifest _data ) ) ;
}
async function exportDockerEnvFile ( destFolder ) {
console . log ( 'Exporting docker environment file' ) ;
2022-07-07 05:49:25 +00:00
const content = await fsPromises . readFile ( '/appsmith-stacks/configuration/docker.env' , { encoding : 'utf8' } ) ;
2023-04-18 01:22:36 +00:00
const cleaned _content = removeSensitiveEnvData ( content )
2022-11-01 07:27:41 +00:00
await fsPromises . writeFile ( destFolder + '/docker.env' , cleaned _content ) ;
2022-06-09 03:44:18 +00:00
console . log ( 'Exporting docker environment file done.' ) ;
console . log ( '!!!!!!!!!!!!!!!!!!!!!!!!!! Important !!!!!!!!!!!!!!!!!!!!!!!!!!' ) ;
console . log ( '!!! Please ensure you have saved the APPSMITH_ENCRYPTION_SALT and APPSMITH_ENCRYPTION_PASSWORD variables from the docker.env file because those values are not included in the backup export.' ) ;
console . log ( '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' ) ;
}
2022-11-01 07:27:41 +00:00
async function executeMongoDumpCMD ( destFolder , appsmithMongoURI ) {
return await utils . execCommand ( [ 'mongodump' , ` --uri= ${ appsmithMongoURI } ` , ` --archive= ${ destFolder } /mongodb-data.gz ` , '--gzip' ] ) ; // generate cmd
}
2022-06-09 03:44:18 +00:00
async function createFinalArchive ( destFolder , timestamp ) {
console . log ( 'Creating final archive' ) ;
const archive = ` ${ Constants . BACKUP _PATH } /appsmith-backup- ${ timestamp } .tar.gz ` ;
await utils . execCommand ( [ 'tar' , '-cah' , '-C' , destFolder , '-f' , archive , '.' ] ) ;
console . log ( 'Created final archive' ) ;
return archive ;
}
2022-11-01 07:27:41 +00:00
async function postBackupCleanup ( ) {
2022-07-28 12:15:28 +00:00
console . log ( 'Starting the cleanup task after taking a backup.' ) ;
2022-11-01 07:27:41 +00:00
let backupArchivesLimit = getBackupArchiveLimit ( process . env . APPSMITH _BACKUP _ARCHIVE _LIMIT ) ;
2022-07-28 12:15:28 +00:00
const backupFiles = await utils . listLocalBackupFiles ( ) ;
2022-11-01 07:27:41 +00:00
while ( backupFiles . length > backupArchivesLimit ) {
2022-07-28 12:15:28 +00:00
const fileName = backupFiles . shift ( ) ;
await fsPromises . rm ( Constants . BACKUP _PATH + '/' + fileName ) ;
}
console . log ( 'Cleanup task completed.' ) ;
}
2022-11-01 07:27:41 +00:00
async function executeCopyCMD ( srcFolder , destFolder ) {
return await utils . execCommand ( [ 'ln' , '-s' , srcFolder , destFolder + '/git-storage' ] )
}
function getGitRoot ( gitRoot ) {
if ( gitRoot == null || gitRoot === '' ) {
gitRoot = '/appsmith-stacks/git-storage' ;
}
return gitRoot
}
async function generateBackupRootPath ( ) {
const backupRootPath = await fsPromises . mkdtemp ( path . join ( os . tmpdir ( ) , 'appsmithctl-backup-' ) ) ;
return backupRootPath
}
function getBackupContentsPath ( backupRootPath , timestamp ) {
return backupRootPath + '/appsmith-backup-' + timestamp ;
}
2023-04-18 01:22:36 +00:00
function removeSensitiveEnvData ( content ) {
// Remove encryption and Mongodb data from docker.env
2022-11-01 07:27:41 +00:00
const output _lines = [ ]
content . split ( /\r?\n/ ) . forEach ( line => {
2023-04-18 01:22:36 +00:00
if ( ! line . startsWith ( "APPSMITH_ENCRYPTION" ) && ! line . startsWith ( "APPSMITH_MONGODB" ) ) {
output _lines . push ( line ) ;
2022-11-01 07:27:41 +00:00
}
} ) ;
return output _lines . join ( '\n' )
}
function getBackupArchiveLimit ( backupArchivesLimit ) {
if ( ! backupArchivesLimit )
2023-04-18 01:22:36 +00:00
backupArchivesLimit = Constants . APPSMITH _DEFAULT _BACKUP _ARCHIVE _LIMIT ;
2022-11-01 07:27:41 +00:00
return backupArchivesLimit
}
async function removeOldBackups ( backupFiles , backupArchivesLimit ) {
while ( backupFiles . length > backupArchivesLimit ) {
const fileName = backupFiles . shift ( ) ;
await fsPromises . rm ( Constants . BACKUP _PATH + '/' + fileName ) ;
}
return backupFiles
}
function getTimeStampInISO ( ) {
return new Date ( ) . toISOString ( ) . replace ( /:/g , '-' )
}
function getAvailableBackupSpaceInBytes ( ) {
return parseInt ( shell . exec ( 'df --output=avail -B 1 /appsmith-stacks | tail -n 1' ) , 10 )
}
function checkAvailableBackupSpace ( availSpaceInBytes ) {
if ( availSpaceInBytes < Constants . MIN _REQUIRED _DISK _SPACE _IN _BYTES ) {
2023-04-18 01:22:36 +00:00
throw new Error ( 'Not enough space avaliable at /appsmith-stacks. Please ensure availability of atleast 2GB to backup successfully.' ) ;
2022-11-01 07:27:41 +00:00
}
}
2023-01-03 06:44:21 +00:00
2022-07-28 12:15:28 +00:00
2022-06-09 03:44:18 +00:00
module . exports = {
run ,
2022-11-01 07:27:41 +00:00
getTimeStampInISO ,
getAvailableBackupSpaceInBytes ,
checkAvailableBackupSpace ,
generateBackupRootPath ,
getBackupContentsPath ,
executeMongoDumpCMD ,
getGitRoot ,
executeCopyCMD ,
2023-04-18 01:22:36 +00:00
removeSensitiveEnvData ,
2022-11-01 07:27:41 +00:00
getBackupArchiveLimit ,
removeOldBackups
2022-06-09 03:44:18 +00:00
} ;