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-07-07 05:49:25 +00:00
const timestamp = new Date ( ) . toISOString ( ) . replace ( /:/g , '-' )
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' ) ;
}
} ) ;
utils . stop ( [ 'backend' , 'rts' ] ) ;
2022-07-07 05:49:25 +00:00
console . log ( 'Available free space at /appsmith-stacks' ) ;
const availSpaceInBytes = parseInt ( shell . exec ( 'df --output=avail -B 1 /appsmith-stacks | tail -n 1' ) , 10 ) ;
console . log ( '\n' ) ;
if ( availSpaceInBytes < Constants . MIN _REQUIRED _DISK _SPACE _IN _BYTES ) {
throw new Error ( 'Not enough space avaliable at /appsmith-stacks. Please ensure availability of atleast 5GB to backup successfully.' ) ;
}
2022-06-09 03:44:18 +00:00
const backupRootPath = await fsPromises . mkdtemp ( path . join ( os . tmpdir ( ) , 'appsmithctl-backup-' ) ) ;
const backupContentsPath = backupRootPath + '/appsmith-backup-' + timestamp ;
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-07-28 12:15:28 +00:00
console . log ( 'Finished taking a backup at' , archivePath ) ;
await postBackupCleanup ( ) ;
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 ( ) ;
if ( ( lastMailTS + Constants . DURATION _BETWEEN _BACKUP _ERROR _MAILS _IN _MILLI _SEC ) < currentTS ) {
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 {
utils . start ( [ 'backend' , 'rts' ] ) ;
process . exit ( errorCode ) ;
}
}
async function exportDatabase ( destFolder ) {
console . log ( 'Exporting database' ) ;
await utils . execCommand ( [ 'mongodump' , ` --uri= ${ process . env . APPSMITH _MONGODB _URI } ` , ` --archive= ${ destFolder } /mongodb-data.gz ` , '--gzip' ] ) ;
console . log ( 'Exporting database done.' ) ;
}
async function createGitStorageArchive ( destFolder ) {
console . log ( 'Creating git-storage archive' ) ;
let gitRoot = process . env . APPSMITH _GIT _ROOT ;
if ( gitRoot == null || gitRoot === '' ) {
gitRoot = '/appsmith-stacks/git-storage' ;
}
await utils . execCommand ( [ 'ln' , '-s' , gitRoot , destFolder + '/git-storage' ] )
console . log ( 'Created git-storage archive' ) ;
}
async function createManifestFile ( path ) {
2022-07-07 05:49:25 +00:00
const content = await fsPromises . readFile ( '/opt/appsmith/rts/version.js' , { encoding : 'utf8' } ) ;
2022-06-09 03:44:18 +00:00
const version = content . match ( /\bexports\.VERSION\s*=\s*["']([^"]+)["']/ ) [ 1 ] ;
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' } ) ;
2022-06-09 03:44:18 +00:00
const output _lines = [ ]
2022-07-07 05:49:25 +00:00
content . split ( /\r?\n/ ) . forEach ( line => {
2022-06-09 03:44:18 +00:00
if ( ! line . startsWith ( "APPSMITH_ENCRYPTION" ) ) {
output _lines . push ( line )
}
} ) ;
await fsPromises . writeFile ( destFolder + '/docker.env' , output _lines . join ( '\n' ) ) ;
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 ( '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' ) ;
}
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-07-28 12:15:28 +00:00
async function postBackupCleanup ( ) {
console . log ( 'Starting the cleanup task after taking a backup.' ) ;
let backupArchivesLimit = process . env . APPSMITH _BACKUP _ARCHIVE _LIMIT ;
if ( ! backupArchivesLimit )
backupArchivesLimit = 4 ;
const backupFiles = await utils . listLocalBackupFiles ( ) ;
while ( backupFiles . length > backupArchivesLimit ) {
const fileName = backupFiles . shift ( ) ;
await fsPromises . rm ( Constants . BACKUP _PATH + '/' + fileName ) ;
}
console . log ( 'Cleanup task completed.' ) ;
}
2022-06-09 03:44:18 +00:00
module . exports = {
run ,
} ;