Revised backup logic
parent
d123a32517
commit
71b172aeac
44
manifest.jps
44
manifest.jps
|
@ -19,7 +19,15 @@ onInstall:
|
||||||
- checkApplication
|
- checkApplication
|
||||||
- checkAddons
|
- checkAddons
|
||||||
- installRestic
|
- installRestic
|
||||||
- setSchedule
|
|
||||||
|
menu:
|
||||||
|
confirmText: Backup ALL Now?
|
||||||
|
loadingText: Backing up ALL data on your site
|
||||||
|
action: backupnow
|
||||||
|
caption: Backup All
|
||||||
|
successText: Backup All Completed Successfully
|
||||||
|
title: Backup All Now
|
||||||
|
submitButtonText: Backup Now
|
||||||
|
|
||||||
onUninstall:
|
onUninstall:
|
||||||
- callScript: uninstall
|
- callScript: uninstall
|
||||||
|
@ -40,8 +48,6 @@ onAfterClone:
|
||||||
cronTime: "0 * * * *"
|
cronTime: "0 * * * *"
|
||||||
backupCount: "5"
|
backupCount: "5"
|
||||||
|
|
||||||
onAfterConfirmTransfer: setSchedule
|
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
checkApplication:
|
checkApplication:
|
||||||
- cmd[${nodes.cp.master.id}]: |-
|
- cmd[${nodes.cp.master.id}]: |-
|
||||||
|
@ -53,6 +59,8 @@ actions:
|
||||||
- stopEvent:
|
- stopEvent:
|
||||||
type: warning
|
type: warning
|
||||||
message: Deployed application is not supported by Backup add-on.
|
message: Deployed application is not supported by Backup add-on.
|
||||||
|
backupnow:
|
||||||
|
message: BACKUP ALL DONE
|
||||||
|
|
||||||
checkAddons:
|
checkAddons:
|
||||||
- script: |-
|
- script: |-
|
||||||
|
@ -89,7 +97,14 @@ actions:
|
||||||
yum-config-manager --enable copr:copr.fedorainfracloud.org:copart:restic
|
yum-config-manager --enable copr:copr.fedorainfracloud.org:copart:restic
|
||||||
yum -y install restic
|
yum -y install restic
|
||||||
yum-config-manager --disable copr:copr.fedorainfracloud.org:copart:restic
|
yum-config-manager --disable copr:copr.fedorainfracloud.org:copart:restic
|
||||||
wget -O /etc/logrotate.d/backup-addon ${baseUrl}/scripts/backup-addon;
|
echo "/var/log/backup_addon.log {
|
||||||
|
weekly
|
||||||
|
rotate 52
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
compress
|
||||||
|
copytruncate
|
||||||
|
}" > /etc/logrotate.d/backup-addon
|
||||||
fi
|
fi
|
||||||
user: root
|
user: root
|
||||||
|
|
||||||
|
@ -100,12 +115,9 @@ actions:
|
||||||
params:
|
params:
|
||||||
scriptName: ${env.envName}-${globals.scriptSufix}
|
scriptName: ${env.envName}-${globals.scriptSufix}
|
||||||
baseUrl: ${baseUrl}
|
baseUrl: ${baseUrl}
|
||||||
cronTime: '${this.cronTime}'
|
cronTime: '0 * * * *'
|
||||||
backupCount: ${this.backupCount}
|
backupCount: '5'
|
||||||
userId: ${env.uid}
|
userId: ${env.uid}
|
||||||
storageNodeId: ${response.storageCtid}
|
|
||||||
backupExecNode: ${nodes.cp.master.id}
|
|
||||||
storageEnv: ${response.storageEnvShortName}
|
|
||||||
|
|
||||||
callScript:
|
callScript:
|
||||||
script: |-
|
script: |-
|
||||||
|
@ -134,17 +146,3 @@ actions:
|
||||||
echo "${settings.backupDir}" > /root/.backupid
|
echo "${settings.backupDir}" > /root/.backupid
|
||||||
user: root
|
user: root
|
||||||
- callScript: restore
|
- callScript: restore
|
||||||
|
|
||||||
configure:
|
|
||||||
- setSchedule
|
|
||||||
|
|
||||||
getStorageCtid:
|
|
||||||
- script: scripts/getStorageCtid.js
|
|
||||||
|
|
||||||
setSchedule:
|
|
||||||
- setGlobals:
|
|
||||||
cron: ${settings.cronTime}
|
|
||||||
- installScript:
|
|
||||||
cronTime: ${globals.cron}
|
|
||||||
backupCount: 5
|
|
||||||
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BASE_URL=$2
|
||||||
|
BACKUP_TYPE=$3
|
||||||
|
BACKUP_LOG_FILE=$4
|
||||||
|
ENV_NAME=$5
|
||||||
|
BACKUP_COUNT=$6
|
||||||
|
APP_PATH=$7
|
||||||
|
USER_SESSION=$8
|
||||||
|
USER_EMAIL=$9
|
||||||
|
BACKUP_PATH=${10}
|
||||||
|
|
||||||
|
# Ensure restic is installed
|
||||||
|
if ! which restic &>/dev/null; then
|
||||||
|
if which dnf &>/dev/null; then
|
||||||
|
dnf install -y epel-release
|
||||||
|
dnf install -y restic
|
||||||
|
elif which yum &>/dev/null; then
|
||||||
|
yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/copart/restic/repo/epel-7/copart-restic-epel-7.repo
|
||||||
|
yum-config-manager --enable copart-restic
|
||||||
|
yum -y install restic
|
||||||
|
yum-config-manager --disable copart-restic
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function definitions remain the same, but adjustments are made to use $BACKUP_PATH
|
||||||
|
function update_restic(){
|
||||||
|
restic self-update 2>&1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_backup_repo(){
|
||||||
|
local backup_repo_path="/mnt/backups/${ENV_NAME}"
|
||||||
|
[ -d "${backup_repo_path}" ] || mkdir -p "${backup_repo_path}"
|
||||||
|
export FILES_COUNT=$(find "${backup_repo_path}" -mindepth 1 | wc -l)
|
||||||
|
|
||||||
|
if [ "${FILES_COUNT}" -gt 0 ]; then
|
||||||
|
echo "$(date) ${ENV_NAME} Checking the backup repository integrity and consistency" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
|
||||||
|
if [[ $(ls -A "${backup_repo_path}/locks") ]]; then
|
||||||
|
echo "$(date) ${ENV_NAME} Backup repository has a stale lock, removing" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
GOGC=20 RESTIC_PASSWORD="${ENV_NAME}" restic -r "${backup_repo_path}" unlock
|
||||||
|
sendEmailNotification
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! GOGC=20 RESTIC_PASSWORD="${ENV_NAME}" restic -q -r "${backup_repo_path}" check --read-data-subset=5%; then
|
||||||
|
echo "Backup repository integrity check failed." | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$(date) ${ENV_NAME} Initializing new backup repository" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
GOGC=20 RESTIC_PASSWORD="${ENV_NAME}" restic init -r "${backup_repo_path}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendEmailNotification() {
|
||||||
|
if [ -e "/usr/lib/jelastic/modules/api.module" ]; then
|
||||||
|
[ -e "/var/run/jem.pid" ] && return 0;
|
||||||
|
CURRENT_PLATFORM_MAJOR_VERSION=$(jem api apicall -s --connect-timeout 3 --max-time 15 [API_DOMAIN]/1.0/statistic/system/rest/getversion 2>/dev/null | jq .version | grep -o [0-9.]* | awk -F . '{print $1}')
|
||||||
|
if [ "${CURRENT_PLATFORM_MAJOR_VERSION}" -ge "7" ]; then
|
||||||
|
echo "$(date) ${ENV_NAME} Sending e-mail notification about removing the stale lock" | tee -a "$BACKUP_LOG_FILE"
|
||||||
|
SUBJECT="Stale lock is removed on ${BACKUP_PATH}/${ENV_NAME} backup repo"
|
||||||
|
BODY="Please pay attention to ${BACKUP_PATH}/${ENV_NAME} backup repo because the stale lock left from previous operation is removed during the integrity check and backup rotation. Manual check of backup repo integrity and consistency is highly desired."
|
||||||
|
jem api apicall -s --connect-timeout 3 --max-time 15 [API_DOMAIN]/1.0/message/email/rest/send --data-urlencode "session=$USER_SESSION" --data-urlencode "to=$USER_EMAIL" --data-urlencode "subject=$SUBJECT" --data-urlencode "body=$BODY"
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
echo "$(date) ${ENV_NAME} Sending of e-mail notification failed" | tee -a "$BACKUP_LOG_FILE"
|
||||||
|
else
|
||||||
|
echo "$(date) ${ENV_NAME} E-mail notification is sent successfully" | tee -a "$BACKUP_LOG_FILE"
|
||||||
|
fi
|
||||||
|
elif [ -z "${CURRENT_PLATFORM_MAJOR_VERSION}" ]; then
|
||||||
|
echo "$(date) ${ENV_NAME} Error when checking the platform version" | tee -a "$BACKUP_LOG_FILE"
|
||||||
|
else
|
||||||
|
echo "$(date) ${ENV_NAME} Email notification is not sent because this functionality is unavailable for current platform version." | tee -a "$BACKUP_LOG_FILE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$(date) ${ENV_NAME} Email notification is not sent because this functionality is unavailable for current platform version." | tee -a "$BACKUP_LOG_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotate_snapshots(){
|
||||||
|
local backup_repo_path="/mnt/backups/${ENV_NAME}"
|
||||||
|
echo "$(date) ${ENV_NAME} Rotating snapshots by keeping the last ${BACKUP_COUNT}" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
|
||||||
|
if [[ $(ls -A "${backup_repo_path}/locks") ]]; then
|
||||||
|
echo "$(date) ${ENV_NAME} Backup repository has a stale lock, removing" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
GOGC=20 RESTIC_PASSWORD="${ENV_NAME}" restic -r "${backup_repo_path}" unlock
|
||||||
|
sendEmailNotification
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD="${ENV_NAME}" restic forget -q -r "${backup_repo_path}" --keep-last "${BACKUP_COUNT}" --prune | tee -a "${BACKUP_LOG_FILE}"; then
|
||||||
|
echo "Backup rotation failed." | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_snapshot(){
|
||||||
|
local backup_repo_path="/mnt/backups/${ENV_NAME}"
|
||||||
|
DUMP_NAME=$(date "+%F_%H%M%S_%Z")
|
||||||
|
echo "$(date) ${ENV_NAME} Begin uploading the ${DUMP_NAME} snapshot to backup storage" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
|
||||||
|
if ! GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_READ_CONCURRENCY=8 RESTIC_PASSWORD="${ENV_NAME}" restic backup -q -r "${backup_repo_path}" --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" "${APP_PATH}" ~/wp_db_backup.sql | tee -a "${BACKUP_LOG_FILE}"; then
|
||||||
|
echo "Backup snapshot creation failed." | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(date) ${ENV_NAME} End uploading the ${DUMP_NAME} snapshot to backup storage" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function backup(){
|
||||||
|
local backup_repo_path="/mnt/backups/${ENV_NAME}"
|
||||||
|
echo $$ > "/var/run/${ENV_NAME}_backup.pid"
|
||||||
|
BACKUP_ADDON_REPO=$(echo "${BASE_URL}" | sed 's|https://raw.githubusercontent.com/||' | awk -F '/' '{print $1"/"$2}')
|
||||||
|
BACKUP_ADDON_BRANCH=$(echo "${BASE_URL}" | sed 's|https://raw.githubusercontent.com/||' | awk -F '/' '{print $3}')
|
||||||
|
BACKUP_ADDON_COMMIT_ID=$(git ls-remote "https://github.com/${BACKUP_ADDON_REPO}.git" | grep "/${BACKUP_ADDON_BRANCH}$" | awk '{print $1}')
|
||||||
|
echo "$(date) ${ENV_NAME} Creating the ${BACKUP_TYPE} backup (using the backup addon with commit id ${BACKUP_ADDON_COMMIT_ID})" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
|
||||||
|
# Database credentials and dump
|
||||||
|
source /var/www/webroot/ROOT/wp-config.php
|
||||||
|
DB_HOST=$(echo "${DB_HOST}" | awk -F ':' '{print $1}')
|
||||||
|
DB_PORT=$(echo "${DB_HOST}" | awk -F ':' '{print $2:-3306}')
|
||||||
|
|
||||||
|
echo "$(date) ${ENV_NAME} Creating the DB dump" | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
if ! mysqldump -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASSWORD}" "${DB_NAME}" --force --single-transaction --quote-names --opt --databases > "${backup_repo_path}/wp_db_backup.sql"; then
|
||||||
|
echo "$(date) ${ENV_NAME} DB backup process failed." | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup process
|
||||||
|
if ! restic -r "${backup_repo_path}" backup "${backup_repo_path}/wp_db_backup.sql" "${APP_PATH}" --tag "${BACKUP_TYPE}" --host "${ENV_NAME}"; then
|
||||||
|
echo "$(date) ${ENV_NAME} Backup process failed." | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(date) ${ENV_NAME} Backup process completed successfully." | tee -a "${BACKUP_LOG_FILE}"
|
||||||
|
rm -f "/var/run/${ENV_NAME}_backup.pid"
|
||||||
|
}
|
||||||
|
|
||||||
|
function backup_wp_core() {
|
||||||
|
echo "Starting backup of WordPress core files excluding uploads directory..."
|
||||||
|
# Define the path to the WordPress installation and the backup tag
|
||||||
|
local wp_path="$APP_PATH"
|
||||||
|
local backup_tag="wp-core-$(date "+%F_%H%M%S_%Z")"
|
||||||
|
|
||||||
|
# Exclude the uploads directory using the --exclude option
|
||||||
|
GOGC=20 RESTIC_PASSWORD="$RESTIC_PASSWORD" restic -r "$backupPath" backup \
|
||||||
|
--tag "$backup_tag" \
|
||||||
|
--exclude="$wp_path/wp-content/uploads" \
|
||||||
|
"$wp_path"
|
||||||
|
|
||||||
|
echo "WordPress core files backup completed."
|
||||||
|
}
|
||||||
|
|
||||||
|
function backup_uploads() {
|
||||||
|
echo "Starting backup of WordPress uploads directory..."
|
||||||
|
# Define the path to the uploads directory and the backup tag
|
||||||
|
local uploads_path="$APP_PATH/wp-content/uploads"
|
||||||
|
local backup_tag="wp-uploads-$(date "+%F_%H%M%S_%Z")"
|
||||||
|
|
||||||
|
# Perform the backup
|
||||||
|
GOGC=20 RESTIC_PASSWORD="$RESTIC_PASSWORD" restic -r "$backupPath" backup \
|
||||||
|
--tag "$backup_tag" \
|
||||||
|
"$uploads_path"
|
||||||
|
|
||||||
|
echo "WordPress uploads directory backup completed."
|
||||||
|
}
|
||||||
|
|
||||||
|
function backup_database() {
|
||||||
|
echo "Starting backup of WordPress database..."
|
||||||
|
# Define the backup tag and the path for the temporary SQL dump
|
||||||
|
local backup_tag="wp-db-$(date "+%F_%H%M%S_%Z")"
|
||||||
|
local sql_dump_path="/tmp/wp_db_backup.sql"
|
||||||
|
|
||||||
|
# Create a database dump
|
||||||
|
mysqldump -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > "$sql_dump_path"
|
||||||
|
|
||||||
|
# Perform the backup
|
||||||
|
GOGC=20 RESTIC_PASSWORD="$RESTIC_PASSWORD" restic -r "$backupPath" backup \
|
||||||
|
--tag "$backup_tag" \
|
||||||
|
"$sql_dump_path"
|
||||||
|
|
||||||
|
# Remove the temporary SQL dump file
|
||||||
|
rm -f "$sql_dump_path"
|
||||||
|
echo "WordPress database backup completed."
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
backup)
|
||||||
|
backup_wp_core
|
||||||
|
backup_uploads
|
||||||
|
backup_database
|
||||||
|
;;
|
||||||
|
backup_wp_core)
|
||||||
|
backup_wp_core
|
||||||
|
;;
|
||||||
|
backup_uploads)
|
||||||
|
backup_uploads
|
||||||
|
;;
|
||||||
|
backup_database)
|
||||||
|
backup_database
|
||||||
|
;;
|
||||||
|
check_backup_repo)
|
||||||
|
check_backup_repo
|
||||||
|
;;
|
||||||
|
rotate_snapshots)
|
||||||
|
rotate_snapshots
|
||||||
|
;;
|
||||||
|
create_snapshot)
|
||||||
|
create_snapshot
|
||||||
|
;;
|
||||||
|
update_restic)
|
||||||
|
update_restic
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {backup|backup_wp_core|backup_uploads|backup_database|check_backup_repo|rotate_snapshots|create_snapshot|update_restic}"
|
||||||
|
exit 2
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
exit $?
|
|
@ -12,10 +12,8 @@ function run() {
|
||||||
scriptName : "${scriptName}",
|
scriptName : "${scriptName}",
|
||||||
envName : "${envName}",
|
envName : "${envName}",
|
||||||
envAppid : "${envAppid}",
|
envAppid : "${envAppid}",
|
||||||
storageNodeId : "${storageNodeId}",
|
|
||||||
backupExecNode : "${backupExecNode}",
|
|
||||||
backupCount : "${backupCount}",
|
backupCount : "${backupCount}",
|
||||||
storageEnv : "${storageEnv}"
|
backupPath : "/mnt/backup"
|
||||||
});
|
});
|
||||||
|
|
||||||
api.local.ReturnResult(
|
api.local.ReturnResult(
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ -e /var/www/webroot/ROOT/wp-config.php ]; then
|
|
||||||
echo "$(date) trying to install the backup add-on" >> /var/log/backup_addon.log
|
|
||||||
if [ -e /home/jelastic/bin/wp ]; then
|
|
||||||
/home/jelastic/bin/wp --info >> /var/log/backup_addon.log
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "$(date) The application deployed to WEBROOT cannot be backuped by Jelastic backup add-on" >> /var/log/backup_addon.log
|
|
||||||
echo "Non-supported"
|
|
||||||
fi
|
|
|
@ -1,89 +0,0 @@
|
||||||
function BackupManager(config) {
|
|
||||||
var Response = com.hivext.api.Response,
|
|
||||||
Transport = com.hivext.api.core.utils.Transport,
|
|
||||||
Logger = org.slf4j.LoggerFactory.getLogger("backup-addon:" + config.envName);
|
|
||||||
|
|
||||||
var me = this;
|
|
||||||
var BACKUP_TYPES = {
|
|
||||||
CORE: "core",
|
|
||||||
MEDIA: "media",
|
|
||||||
DATABASE: "db"
|
|
||||||
};
|
|
||||||
|
|
||||||
me.invoke = function(action) {
|
|
||||||
var actions = {
|
|
||||||
"backup": me.backup,
|
|
||||||
"restore": me.restore
|
|
||||||
};
|
|
||||||
|
|
||||||
return actions[action] ? actions[action].call(me) : {
|
|
||||||
result: Response.ERROR_UNKNOWN,
|
|
||||||
error: "Unknown action: " + action
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
me.backup = function() {
|
|
||||||
// Sequentially perform all backups
|
|
||||||
me.backupCoreFiles();
|
|
||||||
me.backupMediaFiles();
|
|
||||||
me.backupDatabase();
|
|
||||||
Logger.info("All backups completed successfully.");
|
|
||||||
};
|
|
||||||
|
|
||||||
me.backupCoreFiles = function() {
|
|
||||||
var backupName = me.getBackupName(BACKUP_TYPES.CORE);
|
|
||||||
// Placeholder for core files backup logic
|
|
||||||
Logger.info("Core files backup completed: " + backupName);
|
|
||||||
};
|
|
||||||
|
|
||||||
me.backupMediaFiles = function() {
|
|
||||||
var backupName = me.getBackupName(BACKUP_TYPES.ME DIA);
|
|
||||||
// Placeholder for media files backup logic
|
|
||||||
Logger.info("Media files backup completed: " + backupName);
|
|
||||||
};
|
|
||||||
|
|
||||||
me.backupDatabase = function() {
|
|
||||||
var backupName = me.getBackupName(BACKUP_TYPES.DATABASE);
|
|
||||||
// Placeholder for database backup logic
|
|
||||||
Logger.info("Database backup completed: " + backupName);
|
|
||||||
};
|
|
||||||
|
|
||||||
me.restore = function(backupName) {
|
|
||||||
// Determine type from backupName and call the respective restore function
|
|
||||||
var type = backupName.split("_")[1]; // Assumes naming convention is used
|
|
||||||
switch(type) {
|
|
||||||
case BACKUP_TYPES.CORE:
|
|
||||||
me.restoreCoreFiles(backupName);
|
|
||||||
break;
|
|
||||||
case BACKUP_TYPES.MEDIA:
|
|
||||||
me.restoreMediaFiles(backupName);
|
|
||||||
break;
|
|
||||||
case BACKUP_TYPES.DATABASE:
|
|
||||||
me.restoreDatabase(backupName);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Logger.error("Unknown backup type for restoration: " + backupName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
me.restoreCoreFiles = function(backupName) {
|
|
||||||
// Placeholder for core files restore logic
|
|
||||||
Logger.info("Core files restoration completed: " + backupName);
|
|
||||||
};
|
|
||||||
|
|
||||||
me.restoreMediaFiles = function(backupName) {
|
|
||||||
// Placeholder for media files restore logic
|
|
||||||
Logger.info("Media files restoration completed: " + backupName);
|
|
||||||
};
|
|
||||||
|
|
||||||
me.restoreDatabase = function(backupName) {
|
|
||||||
// Placeholder for database restore logic
|
|
||||||
Logger.info("Database restoration completed: " + backupName);
|
|
||||||
};
|
|
||||||
|
|
||||||
me.getBackupName = function(type) {
|
|
||||||
var dateFormat = new java.text.SimpleDateFormat("yyyyMMddHHmmss");
|
|
||||||
var dateStr = dateFormat.format(new java.util.Date());
|
|
||||||
return config.envName + "_" + type + "_" + dateStr;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -0,0 +1,454 @@
|
||||||
|
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]
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue