454 lines
16 KiB
JavaScript
454 lines
16 KiB
JavaScript
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]
|
|
]);
|
|
};
|
|
|
|
|
|
} |