function BackupManager(config) { /** * Implements backup management of the environment data * @param {{ * session : {String} * baseUrl : {String} * uid : {Number} * cronTime : {String} * scriptName : {String} * envName : {String} * envAppid : {String} * [backupCount] : {String} * }} config * @constructor */ var Response = com.hivext.api.Response, EnvironmentResponse = com.hivext.api.environment.response.EnvironmentResponse, ScriptEvalResponse = com.hivext.api.development.response.ScriptEvalResponse, Transport = com.hivext.api.core.utils.Transport, Random = com.hivext.api.utils.Random, SimpleDateFormat = java.text.SimpleDateFormat, StrSubstitutor = org.apache.commons.lang3.text.StrSubstitutor, Scripting = com.hivext.api.development.Scripting, LoggerFactory = org.slf4j.LoggerFactory, LoggerName = "scripting.logger.backup-addon:" + config.envName, Logger = LoggerFactory.getLogger(LoggerName), me = this, nodeManager, session; config = config || {}; session = config.session; nodeManager = new NodeManager(config.envName); me.invoke = function (action) { var actions = { "install": me.install, "uninstall": me.uninstall, "backup": me.backup, "restore": me.restore, "backup_wp_core": me.backup_wp_core, "backup_uploads": me.backup_uploads, "backup_database": me.backup_database }; if (!actions[action]) { return { result: Response.ERROR_UNKNOWN, error: "unknown action [" + action + "]" } } return actions[action].call(me); }; me.install = function () { var resp; return me.exec([ [me.cmd, ['echo $(date) %(envName) "Creating the backup task for %(envName) with the backup count %(backupCount), backup schedule %(cronTime)" | tee -a %(backupLogFile)'], { envName: config.envName, cronTime: config.cronTime, backupCount: config.backupCount, backupLogFile: "/var/log/backup_addon.log" }], [me.createScript], [me.clearScheduledBackups], [me.scheduleBackup] ]); }; me.uninstall = function () { return me.exec(me.clearScheduledBackups); }; me.checkCurrentlyRunningBackup = function () { var resp = me.exec([ [me.cmd, ['pgrep -f "%(envName)"_backup-logic.sh 1>/dev/null && echo "Running"; true'], { envName: config.envName }] ]); if (resp.responses[0].out == "Running") { return { result: Response.ERROR_UNKNOWN, error: "Another backup process is already running" } } else { return { "result": 0 }; } } me.backup = function () { var backupType = !getParam("task") ? "manual" : "auto"; var backupCallParams = { envName: config.envName, appPath: "/var/www/webroot/ROOT", backupCount: config.backupCount, backupLogFile: "/var/log/backup_addon.log", baseUrl: config.baseUrl, backupType: backupType, session: session, email: user.email, backupPath: "/mnt/backup" // Direct path to mounted backup storage }; return me.exec([ [me.checkEnvStatus], [me.checkCurrentlyRunningBackup], [me.cmd, [ '[ -f /root/%(envName)_backup-logic.sh ] && rm -f /root/%(envName)_backup-logic.sh || true', 'wget -O /root/%(envName)_backup-logic.sh %(baseUrl)/scripts/backup-logic.sh' ], { nodeGroup: "cp", envName: config.envName, baseUrl: config.baseUrl }], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh update_restic' ], { nodeGroup: "cp", envName: config.envName } ], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(backupCount) %(appPath) %(session) %(email)' ], backupCallParams ], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh backup %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(backupCount) %(appPath)' ], backupCallParams ], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh create_snapshot %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(backupCount) %(appPath)' ], backupCallParams ], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(backupCount) %(appPath) %(session) %(email)' ], backupCallParams ], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(backupCount) %(appPath) %(session) %(email)' ], backupCallParams ] ]); }; me.restore = function () { return me.exec([ [me.checkEnvStatus], [me.checkCurrentlyRunningBackup], [me.addMountForRestore], [me.cmd, ['echo $(date) %(envName) Restoring the snapshot $(cat /root/.backupid)', 'restic self-update 2>&1', 'if [ -e /root/.backupedenv ]; then REPO_DIR=$(cat /root/.backupedenv); else REPO_DIR="%(envName)"; fi', 'jem service stop', 'SNAPSHOT_ID=$(RESTIC_PASSWORD=$REPO_DIR restic -r /opt/backup/$REPO_DIR snapshots|grep $(cat /root/.backupid)|awk \'{print $1}\')', '[ -n "${SNAPSHOT_ID}" ] || false', 'RESTIC_PASSWORD=$REPO_DIR GOGC=20 restic -r /opt/backup/$REPO_DIR restore ${SNAPSHOT_ID} --target /'], { nodeGroup: "cp", envName: config.envName }], [me.cmd, [ 'echo $(date) %(envName) Restoring the database from snapshot $(cat /root/.backupid)', '! which mysqld || service mysql start 2>&1', 'for i in DB_HOST DB_USER DB_PASSWORD DB_NAME; do declare "${i}"=$(cat %(appPath)/wp-config.php | grep ${i} |grep -v \'^[[:space:]]*#\' | tr -d \'[[:blank:]]\' | awk -F \',\' \'{print $2}\' | tr -d "\\"\');"|tr -d \'\\r\'|tail -n 1); done', 'source /etc/jelastic/metainf.conf ; if [ "${COMPUTE_TYPE}" == "lemp" -o "${COMPUTE_TYPE}" == "llsmp" ]; then wget -O /root/addAppDbUser.sh %(baseUrl)/scripts/addAppDbUser.sh; chmod +x /root/addAppDbUser.sh; bash /root/addAppDbUser.sh ${DB_USER} ${DB_PASSWORD} ${DB_HOST}; fi', 'mysql -u${DB_USER} -p${DB_PASSWORD} -h ${DB_HOST} --execute="CREATE DATABASE IF NOT EXISTS ${DB_NAME};"', 'mysql -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} --force < /root/wp_db_backup.sql' ], { envName: config.envName, baseUrl: config.baseUrl, appPath: "/var/www/webroot/ROOT" }], [me.cmd, ['rm -f /root/.backupid /root/wp_db_backup.sql', 'jem service start'], { nodeGroup: "cp", envName: config.envName }] ]); } me.checkEnvStatus = function checkEnvStatus() { if (!nodeManager.isEnvRunning()) { return { result: EnvironmentResponse.ENVIRONMENT_NOT_RUNNING, error: _("env [%(name)] not running", { name: config.envName }) }; } return { result: 0 }; }; me.createScript = function createScript() { var url = me.getScriptUrl("backup-main.js"), scriptName = config.scriptName, scriptBody, resp; try { scriptBody = new Transport().get(url); scriptBody = me.replaceText(scriptBody, config); //delete the script if it already exists api.dev.scripting.DeleteScript(scriptName); //create a new script resp = api.dev.scripting.CreateScript(scriptName, "js", scriptBody); java.lang.Thread.sleep(1000); //build script to avoid caching api.dev.scripting.Build(scriptName); } catch (ex) { resp = { result: Response.ERROR_UNKNOWN, error: toJSON(ex) }; } return resp; }; me.scheduleBackup = function scheduleBackup() { var quartz = CronToQuartzConverter.convert(config.cronTime); for (var i = quartz.length; i--;) { var resp = api.utils.scheduler.CreateEnvTask({ appid: appid, envName: config.envName, session: session, script: config.scriptName, trigger: "cron:" + quartz[i], params: { task: 1, action: "backup" } }); if (resp.result !== 0) return resp; } return { result: 0 }; }; me.clearScheduledBackups = function clearScheduledBackups() { var envAppid = config.envAppid, resp = api.utils.scheduler.GetTasks(envAppid, session); if (resp.result != 0) return resp; var tasks = resp.objects; for (var i = tasks.length; i--;) { if (tasks[i].script == config.scriptName) { resp = api.utils.scheduler.RemoveTask(envAppid, session, tasks[i].id); if (resp.result != 0) return resp; } } return resp; }; me.getFileUrl = function (filePath) { return config.baseUrl + "/" + filePath + "?_r=" + Math.random(); }; me.getScriptUrl = function (scriptName) { return me.getFileUrl("scripts/" + scriptName); }; me.cmd = function cmd(commands, values, sep) { return nodeManager.cmd(commands, values, sep, true); }; me.replaceText = function (text, values) { return new StrSubstitutor(values, "${", "}").replace(text); }; me.exec = function (methods, oScope, bBreakOnError) { var scope, resp, fn; if (!methods.push) { methods = [Array.prototype.slice.call(arguments)]; onFail = null; bBreakOnError = true; } for (var i = 0, n = methods.length; i < n; i++) { if (!methods[i].push) { methods[i] = [methods[i]]; } fn = methods[i][0]; methods[i].shift(); log(fn.name + (methods[i].length > 0 ? ": " + methods[i] : "")); scope = oScope || (methods[methods.length - 1] || {}).scope || this; resp = fn.apply(scope, methods[i]); log(fn.name + ".response: " + resp); if (resp.result != 0) { resp.method = fn.name; resp.type = "error"; if (resp.error) { resp.message = resp.error; } if (bBreakOnError !== false) break; } } return resp; }; var CronToQuartzConverter = use("https://raw.githubusercontent.com/jelastic-jps/common/main/CronToQuartzConverter"); function use(script) { var Transport = com.hivext.api.core.utils.Transport, body = new Transport().get(script + "?_r=" + Math.random()); return new(new Function("return " + body)())(session); } function NodeManager(envName, baseDir, logPath) { var ENV_STATUS_TYPE_RUNNING = 1, me = this, envInfo; me.isEnvRunning = function () { var resp = me.getEnvInfo(); if (resp.result != 0) { throw new Error("can't get environment info: " + toJSON(resp)); } return resp.env.status == ENV_STATUS_TYPE_RUNNING; }; me.getEnvInfo = function () { var resp; if (!envInfo) { resp = api.env.control.GetEnvInfo(envName, session); if (resp.result != 0) return resp; envInfo = resp; } return envInfo; }; me.cmd = function (cmd, values, sep, disableLogging) { var resp, command; values = values || {}; values.log = values.log || logPath; cmd = cmd.join ? cmd.join(sep || " && ") : cmd; command = _(cmd, values); if (!disableLogging) { log("cmd: " + command); } // Since we're removing specific node handling, we'll execute commands at the environment level resp = api.env.control.ExecCmdByGroup(envName, session, "cp", toJSON([{ command: command }]), true, false, "root"); if (resp.result != 0) { var title = "Backup failed for " + envName, text = "Backup failed for the environment " + envName + " of " + user.email + " with error message " + resp.responses[0].errOut; try { api.message.email.Send(appid, signature, null, user.email, user.email, title, text); } catch (ex) { emailResp = error(Response.ERROR_UNKNOWN, toJSON(ex)); } } return resp; }; } function log(message) { Logger.debug(message); return api.marketplace.console.WriteLog(appid, session, message); } function _(str, values) { return new StrSubstitutor(values || {}, "%(", ")").replace(str); } me.backup_wp_core = function () { var backupCallParams = { envName: config.envName, backupPath: "/mnt/backup", // Direct path to mounted backup storage baseUrl: config.baseUrl, backupType: "wp_core", backupLogFile: "/var/log/backup_addon.log", session: session, email: user.email }; return me.exec([ [me.checkEnvStatus], [me.checkCurrentlyRunningBackup], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh backup_wp_core %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(session) %(email)' ], backupCallParams] ]); }; me.backup_uploads = function () { var backupCallParams = { envName: config.envName, backupPath: "/mnt/backup", // Direct path to mounted backup storage baseUrl: config.baseUrl, backupType: "wp_core", backupLogFile: "/var/log/backup_addon.log", session: session, email: user.email }; return me.exec([ [me.checkEnvStatus], [me.checkCurrentlyRunningBackup], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh backup_uploads %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(session) %(email)' ], backupCallParams] ]); }; me.backup_database = function () { var backupCallParams = { envName: config.envName, backupPath: "/mnt/backup", // Direct path to mounted backup storage baseUrl: config.baseUrl, backupType: "wp_core", backupLogFile: "/var/log/backup_addon.log", session: session, email: user.email }; return me.exec([ [me.checkEnvStatus], [me.checkCurrentlyRunningBackup], [me.cmd, [ 'bash /root/%(envName)_backup-logic.sh backup_database %(baseUrl) %(backupType) %(backupPath) %(backupLogFile) %(envName) %(session) %(email)' ], backupCallParams] ]); }; }