From e115197f6ee9507f8e004f5e39d0a3f68d086f35 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 17 Sep 2024 09:35:31 +0200 Subject: [PATCH 01/78] JE-71293 --- backup.jps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backup.jps b/backup.jps index f1e15d8..5e5b36f 100644 --- a/backup.jps +++ b/backup.jps @@ -5,7 +5,7 @@ id: db-backup targetEditions: any logo: /images/backup-logo.png description: Backup Add-On for the database. It can be used to create scheduled backups according to any required timezone and restore corrupted databases, even if the content has been completely deleted. -baseUrl: https://raw.githubusercontent.com/jelastic-jps/database-backup-addon/master +baseUrl: https://raw.githubusercontent.com/sych74/database-backup-addon/pitr targetNodes: nodeType: From 579259e2f72219d8eb3e92fc5b66d221c14108d4 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 17 Sep 2024 11:11:25 +0200 Subject: [PATCH 02/78] JE-71293 --- scripts/mariadb-restore.sh | 53 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/scripts/mariadb-restore.sh b/scripts/mariadb-restore.sh index 2bc18b1..6da10cb 100644 --- a/scripts/mariadb-restore.sh +++ b/scripts/mariadb-restore.sh @@ -1,11 +1,60 @@ #!/bin/bash -SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) +DBUSER=$1 +DBPASSWD=$2 +RESTORE_TYPE=$3 # 'full' or 'pitr' +FULL_BACKUP_FILE=$4 +STOP_TIME=$5 # For PITR, specify the datetime until which to apply the binary logs (format: YYYY-MM-DD HH:MM:SS) + +SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}' | sed 's/\/[0-9]*//g' | tail -n 1) [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" + if which mariadb 2>/dev/null; then CLIENT_APP="mariadb" else CLIENT_APP="mysql" fi -${CLIENT_APP} --silent -h ${SERVER_IP_ADDR} -u ${1} -p${2} --force < /root/db_backup.sql; +# Restore the full backup +function restore_full() { + echo "Restoring the full backup from ${FULL_BACKUP_FILE}..." + ${CLIENT_APP} --silent -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force < ${FULL_BACKUP_FILE} + if [ $? -eq 0 ]; then + echo "Full backup restoration completed successfully." + else + echo "Error occurred while restoring the full backup." + exit 1 + fi +} + +# Restore using PITR (full backup + binary logs up to a point in time) +function restore_pitr() { + echo "Restoring full backup for PITR..." + restore_full # Restore the full backup first + + if [ -z "${STOP_TIME}" ]; then + echo "Error: Please specify the stop time for PITR." + exit 1 + fi + + echo "Applying binary logs until ${STOP_TIME} for PITR..." + + # Apply binary logs up to the specified stop time + mysqlbinlog --stop-datetime="${STOP_TIME}" /opt/backup/mysql_binlogs/mysql-bin.* | ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} + if [ $? -eq 0 ]; then + echo "Binary logs applied successfully up to ${STOP_TIME}." + else + echo "Error occurred while applying binary logs." + exit 1 + fi +} + +# Main restore logic +if [ "${RESTORE_TYPE}" == "full" ]; then + restore_full +elif [ "${RESTORE_TYPE}" == "pitr" ]; then + restore_pitr +else + echo "Invalid restore type. Use 'full' for full restore or 'pitr' for point-in-time recovery." + exit 1 +fi From 6141b8848dd6f02f8cb9b2871ff6209c53c60f06 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 17 Sep 2024 11:23:04 +0200 Subject: [PATCH 03/78] JE-71293 --- scripts/mariadb-restore.sh | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/scripts/mariadb-restore.sh b/scripts/mariadb-restore.sh index 6da10cb..7be9075 100644 --- a/scripts/mariadb-restore.sh +++ b/scripts/mariadb-restore.sh @@ -15,23 +15,20 @@ else CLIENT_APP="mysql" fi -# Restore the full backup function restore_full() { - echo "Restoring the full backup from ${FULL_BACKUP_FILE}..." + echo $(date) ${ENV_NAME} "Restoring the full backup from ${FULL_BACKUP_FILE}" | tee -a $BACKUP_LOG_FILE; ${CLIENT_APP} --silent -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force < ${FULL_BACKUP_FILE} if [ $? -eq 0 ]; then - echo "Full backup restoration completed successfully." + echo $(date) ${ENV_NAME} "Full backup restoration completed successfully." | tee -a $BACKUP_LOG_FILE; else - echo "Error occurred while restoring the full backup." + echo $(date) ${ENV_NAME} "Error occurred while restoring the full backup." | tee -a $BACKUP_LOG_FILE; exit 1 fi } -# Restore using PITR (full backup + binary logs up to a point in time) function restore_pitr() { - echo "Restoring full backup for PITR..." - restore_full # Restore the full backup first - + echo $(date) ${ENV_NAME} "Restoring full backup for PITR..." | tee -a $BACKUP_LOG_FILE; + restore_full if [ -z "${STOP_TIME}" ]; then echo "Error: Please specify the stop time for PITR." exit 1 @@ -39,7 +36,6 @@ function restore_pitr() { echo "Applying binary logs until ${STOP_TIME} for PITR..." - # Apply binary logs up to the specified stop time mysqlbinlog --stop-datetime="${STOP_TIME}" /opt/backup/mysql_binlogs/mysql-bin.* | ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} if [ $? -eq 0 ]; then echo "Binary logs applied successfully up to ${STOP_TIME}." @@ -49,7 +45,6 @@ function restore_pitr() { fi } -# Main restore logic if [ "${RESTORE_TYPE}" == "full" ]; then restore_full elif [ "${RESTORE_TYPE}" == "pitr" ]; then From 198a4c8912219984f6ab745becfbde015edfeada Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 17 Sep 2024 11:30:08 +0200 Subject: [PATCH 04/78] JE-71293 --- scripts/backup-logic.sh | 172 ++++++++-------------------------------- 1 file changed, 32 insertions(+), 140 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 9cdc33b..4b722b2 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -15,102 +15,23 @@ BACKUP_ADDON_REPO=$(echo ${BASE_URL}|sed 's|https:\/\/raw.githubusercontent.com\ 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}') -if [ "$COMPUTE_TYPE" == "mongodb" ]; then - if grep -q '^replication' /etc/mongod.conf; then - MONGO_TYPE="-replica-set" - else - MONGO_TYPE="-standalone" - fi -fi +MYSQL_BACKUP_DIR="/opt/backup/mysql" +MYSQL_BINLOG_DIR="/opt/backup/mysql_binlogs" source /etc/jelastic/metainf.conf; -if [ "$COMPUTE_TYPE" == "redis" ]; then - REDIS_CONF_PATH=$(realpath /etc/redis.conf) - if grep -q '^cluster-enabled yes' ${REDIS_CONF_PATH}; then - REDIS_TYPE="-cluster" - else - REDIS_TYPE="-standalone" - fi -fi - -function forceInstallUpdateRestic(){ - wget --tries=10 -O /tmp/installUpdateRestic ${BASE_URL}/scripts/installUpdateRestic && \ - mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ - chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic -} - -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 /opt/backup/${ENV_NAME} backup repo" - BODY="Please pay attention to /opt/backup/${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 #this elif covers the case if the version is not received - 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 update_restic(){ - if which restic; then - restic self-update || forceInstallUpdateRestic - else - forceInstallUpdateRestic - fi -} - -function check_backup_repo(){ - [ -d /opt/backup/${ENV_NAME} ] || mkdir -p /opt/backup/${ENV_NAME} - export FILES_COUNT=$(ls -n /opt/backup/${ENV_NAME}|awk '{print $2}'); - if [ "${FILES_COUNT}" != "0" ]; then - echo $(date) ${ENV_NAME} "Checking the backup repository integrity and consistency" | tee -a $BACKUP_LOG_FILE; - if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then - echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; - GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} unlock - sendEmailNotification - fi - GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -q -r /opt/backup/${ENV_NAME} check --read-data-subset=5% || { echo "Backup repository integrity check failed."; exit 1; } - else - GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic init -r /opt/backup/${ENV_NAME} - fi +function backup_binlogs() { + echo $(date) ${ENV_NAME} "Backing up MySQL binary logs..." | tee -a $BACKUP_LOG_FILE + mkdir -p ${MYSQL_BINLOG_DIR} + cp /var/lib/mysql/mysql-bin.* ${MYSQL_BINLOG_DIR}/ + echo "Binary logs backup completed." | tee -a $BACKUP_LOG_FILE } -function rotate_snapshots(){ - echo $(date) ${ENV_NAME} "Rotating snapshots by keeping the last ${BACKUP_COUNT}" | tee -a ${BACKUP_LOG_FILE} - if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then - echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; - GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} unlock - sendEmailNotification - fi - { GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic forget -q -r /opt/backup/${ENV_NAME} --keep-last ${BACKUP_COUNT} --prune | tee -a $BACKUP_LOG_FILE; } || { echo "Backup rotation failed."; exit 1; } -} - -function create_snapshot(){ - source /etc/jelastic/metainf.conf - echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} - DUMP_NAME=$(date "+%F_%H%M%S_%Z"-${BACKUP_TYPE}\($COMPUTE_TYPE-$COMPUTE_TYPE_FULL_VERSION$REDIS_TYPE$MONGO_TYPE\)) - if [ "$COMPUTE_TYPE" == "redis" ]; then - RDB_TO_BACKUP=$(ls -d /tmp/* |grep redis-dump.*); - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${RDB_TO_BACKUP} | tee -a ${BACKUP_LOG_FILE}; - elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/dump | tee -a ${BACKUP_LOG_FILE} - else - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/db_backup.sql | tee -a ${BACKUP_LOG_FILE} - fi +function pitr_backup() { + echo $(date) ${ENV_NAME} "Starting Point-In-Time Recovery (PITR) backup..." | tee -a $BACKUP_LOG_FILE + backup + backup_binlogs + echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE } function backup(){ @@ -118,52 +39,20 @@ function backup(){ echo $(date) ${ENV_NAME} "Creating the ${BACKUP_TYPE} backup (using the backup addon with commit id ${BACKUP_ADDON_COMMIT_ID}) on storage node ${NODE_ID}" | tee -a ${BACKUP_LOG_FILE} source /etc/jelastic/metainf.conf; echo $(date) ${ENV_NAME} "Creating the DB dump" | tee -a ${BACKUP_LOG_FILE} - if [ "$COMPUTE_TYPE" == "redis" ]; then - RDB_TO_REMOVE=$(ls -d /tmp/* |grep redis-dump.*) - rm -f ${RDB_TO_REMOVE} - export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} |grep '^requirepass'|awk '{print $2}'); - if [ "$REDIS_TYPE" == "-standalone" ]; then - redis-cli --rdb /tmp/redis-dump-standalone.rdb - else - export MASTERS_LIST=$(redis-cli cluster nodes|grep master|grep -v fail|awk '{print $2}'|awk -F : '{print $1}'); - for i in $MASTERS_LIST - do - redis-cli -h $i --rdb /tmp/redis-dump-cluster-$i.rdb || { echo "DB backup process failed."; exit 1; } - done - fi - elif [ "$COMPUTE_TYPE" == "postgres" ]; then - PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > db_backup.sql || { echo "DB backup process failed."; exit 1; } - elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then - RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf|awk '{print $2}'); - RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest"; - else - RS_SUFFIX=""; - fi - TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) - if [ "$TLS_MODE" == "requireTLS" ]; then - SSL_TLS_OPTIONS="--ssl --sslPEMKeyFile=/var/lib/jelastic/keys/SSL-TLS/client/client.pem --sslCAFile=/var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsInsecure" - else - SSL_TLS_OPTIONS="" - fi - mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" + SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) + [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" + if which mariadb 2>/dev/null; then + CLIENT_APP="mariadb" + else + CLIENT_APP="mysql" + fi + if which mariadb-dump 2>/dev/null; then + DUMP_APP="mariadb-dump" else - SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) - [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" - if which mariadb 2>/dev/null; then - CLIENT_APP="mariadb" - else - CLIENT_APP="mysql" - fi - if which mariadb-dump 2>/dev/null; then - DUMP_APP="mariadb-dump" - else - DUMP_APP="mysqldump" - fi - ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + DUMP_APP="mysqldump" fi + ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } rm -f /var/run/${ENV_NAME}_backup.pid } @@ -178,13 +67,16 @@ case "$1" in $1 ;; create_snapshot) - $1 - ;; - update_restic) - $1 + $1 + ;; + update_restic) + $1 + ;; + pitr_backup) + pitr_backup ;; *) - echo "Usage: $0 {backup|check_backup_repo|rotate_snapshots|create_snapshot|update_restic}" + echo "Usage: $0 {backup|check_backup_repo|rotate_snapshots|create_snapshot|update_restic|enable_binlog|pitr_backup|restore_pitr }" exit 2 esac From f2aa75d855ca4ab5116879eb1c39dfd29449c32e Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 17 Sep 2024 13:25:50 +0200 Subject: [PATCH 05/78] JE-71293 --- scripts/backup-logic.sh | 172 ++++++++++++++++++++++++++++++++-------- 1 file changed, 140 insertions(+), 32 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 4b722b2..9cdc33b 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -15,23 +15,102 @@ BACKUP_ADDON_REPO=$(echo ${BASE_URL}|sed 's|https:\/\/raw.githubusercontent.com\ 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}') -MYSQL_BACKUP_DIR="/opt/backup/mysql" -MYSQL_BINLOG_DIR="/opt/backup/mysql_binlogs" +if [ "$COMPUTE_TYPE" == "mongodb" ]; then + if grep -q '^replication' /etc/mongod.conf; then + MONGO_TYPE="-replica-set" + else + MONGO_TYPE="-standalone" + fi +fi source /etc/jelastic/metainf.conf; -function backup_binlogs() { - echo $(date) ${ENV_NAME} "Backing up MySQL binary logs..." | tee -a $BACKUP_LOG_FILE - mkdir -p ${MYSQL_BINLOG_DIR} - cp /var/lib/mysql/mysql-bin.* ${MYSQL_BINLOG_DIR}/ - echo "Binary logs backup completed." | tee -a $BACKUP_LOG_FILE +if [ "$COMPUTE_TYPE" == "redis" ]; then + REDIS_CONF_PATH=$(realpath /etc/redis.conf) + if grep -q '^cluster-enabled yes' ${REDIS_CONF_PATH}; then + REDIS_TYPE="-cluster" + else + REDIS_TYPE="-standalone" + fi +fi + +function forceInstallUpdateRestic(){ + wget --tries=10 -O /tmp/installUpdateRestic ${BASE_URL}/scripts/installUpdateRestic && \ + mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ + chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic +} + +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 /opt/backup/${ENV_NAME} backup repo" + BODY="Please pay attention to /opt/backup/${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 #this elif covers the case if the version is not received + 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 update_restic(){ + if which restic; then + restic self-update || forceInstallUpdateRestic + else + forceInstallUpdateRestic + fi +} + +function check_backup_repo(){ + [ -d /opt/backup/${ENV_NAME} ] || mkdir -p /opt/backup/${ENV_NAME} + export FILES_COUNT=$(ls -n /opt/backup/${ENV_NAME}|awk '{print $2}'); + if [ "${FILES_COUNT}" != "0" ]; then + echo $(date) ${ENV_NAME} "Checking the backup repository integrity and consistency" | tee -a $BACKUP_LOG_FILE; + if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then + echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; + GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} unlock + sendEmailNotification + fi + GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -q -r /opt/backup/${ENV_NAME} check --read-data-subset=5% || { echo "Backup repository integrity check failed."; exit 1; } + else + GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic init -r /opt/backup/${ENV_NAME} + fi } -function pitr_backup() { - echo $(date) ${ENV_NAME} "Starting Point-In-Time Recovery (PITR) backup..." | tee -a $BACKUP_LOG_FILE - backup - backup_binlogs - echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE +function rotate_snapshots(){ + echo $(date) ${ENV_NAME} "Rotating snapshots by keeping the last ${BACKUP_COUNT}" | tee -a ${BACKUP_LOG_FILE} + if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then + echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; + GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} unlock + sendEmailNotification + fi + { GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic forget -q -r /opt/backup/${ENV_NAME} --keep-last ${BACKUP_COUNT} --prune | tee -a $BACKUP_LOG_FILE; } || { echo "Backup rotation failed."; exit 1; } +} + +function create_snapshot(){ + source /etc/jelastic/metainf.conf + echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} + DUMP_NAME=$(date "+%F_%H%M%S_%Z"-${BACKUP_TYPE}\($COMPUTE_TYPE-$COMPUTE_TYPE_FULL_VERSION$REDIS_TYPE$MONGO_TYPE\)) + if [ "$COMPUTE_TYPE" == "redis" ]; then + RDB_TO_BACKUP=$(ls -d /tmp/* |grep redis-dump.*); + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${RDB_TO_BACKUP} | tee -a ${BACKUP_LOG_FILE}; + elif [ "$COMPUTE_TYPE" == "mongodb" ]; then + echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/dump | tee -a ${BACKUP_LOG_FILE} + else + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/db_backup.sql | tee -a ${BACKUP_LOG_FILE} + fi } function backup(){ @@ -39,20 +118,52 @@ function backup(){ echo $(date) ${ENV_NAME} "Creating the ${BACKUP_TYPE} backup (using the backup addon with commit id ${BACKUP_ADDON_COMMIT_ID}) on storage node ${NODE_ID}" | tee -a ${BACKUP_LOG_FILE} source /etc/jelastic/metainf.conf; echo $(date) ${ENV_NAME} "Creating the DB dump" | tee -a ${BACKUP_LOG_FILE} - SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) - [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" - if which mariadb 2>/dev/null; then - CLIENT_APP="mariadb" - else - CLIENT_APP="mysql" - fi - if which mariadb-dump 2>/dev/null; then - DUMP_APP="mariadb-dump" + if [ "$COMPUTE_TYPE" == "redis" ]; then + RDB_TO_REMOVE=$(ls -d /tmp/* |grep redis-dump.*) + rm -f ${RDB_TO_REMOVE} + export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} |grep '^requirepass'|awk '{print $2}'); + if [ "$REDIS_TYPE" == "-standalone" ]; then + redis-cli --rdb /tmp/redis-dump-standalone.rdb + else + export MASTERS_LIST=$(redis-cli cluster nodes|grep master|grep -v fail|awk '{print $2}'|awk -F : '{print $1}'); + for i in $MASTERS_LIST + do + redis-cli -h $i --rdb /tmp/redis-dump-cluster-$i.rdb || { echo "DB backup process failed."; exit 1; } + done + fi + elif [ "$COMPUTE_TYPE" == "postgres" ]; then + PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } + PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > db_backup.sql || { echo "DB backup process failed."; exit 1; } + elif [ "$COMPUTE_TYPE" == "mongodb" ]; then + if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then + RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf|awk '{print $2}'); + RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest"; + else + RS_SUFFIX=""; + fi + TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) + if [ "$TLS_MODE" == "requireTLS" ]; then + SSL_TLS_OPTIONS="--ssl --sslPEMKeyFile=/var/lib/jelastic/keys/SSL-TLS/client/client.pem --sslCAFile=/var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsInsecure" + else + SSL_TLS_OPTIONS="" + fi + mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" else - DUMP_APP="mysqldump" + SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) + [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" + if which mariadb 2>/dev/null; then + CLIENT_APP="mariadb" + else + CLIENT_APP="mysql" + fi + if which mariadb-dump 2>/dev/null; then + DUMP_APP="mariadb-dump" + else + DUMP_APP="mysqldump" + fi + ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } fi - ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } rm -f /var/run/${ENV_NAME}_backup.pid } @@ -67,16 +178,13 @@ case "$1" in $1 ;; create_snapshot) - $1 - ;; - update_restic) - $1 - ;; - pitr_backup) - pitr_backup + $1 + ;; + update_restic) + $1 ;; *) - echo "Usage: $0 {backup|check_backup_repo|rotate_snapshots|create_snapshot|update_restic|enable_binlog|pitr_backup|restore_pitr }" + echo "Usage: $0 {backup|check_backup_repo|rotate_snapshots|create_snapshot|update_restic}" exit 2 esac From 5c7adc45f6f25fe33d1052c61233fc8021819160 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 17 Sep 2024 15:37:16 +0200 Subject: [PATCH 06/78] JE-71293 --- scripts/backup-logic.sh | 127 ++++++++++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 9cdc33b..e37c8ee 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -11,6 +11,7 @@ DBPASSWD=$9 USER_SESSION=${10} USER_EMAIL=${11} + 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}') @@ -113,60 +114,98 @@ function create_snapshot(){ fi } +function backup_redis(){ + source /etc/jelastic/metainf.conf; + RDB_TO_REMOVE=$(ls -d /tmp/* |grep redis-dump.*) + rm -f ${RDB_TO_REMOVE} + export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} |grep '^requirepass'|awk '{print $2}'); + if [ "$REDIS_TYPE" == "-standalone" ]; then + redis-cli --rdb /tmp/redis-dump-standalone.rdb + else + export MASTERS_LIST=$(redis-cli cluster nodes|grep master|grep -v fail|awk '{print $2}'|awk -F : '{print $1}'); + for i in $MASTERS_LIST + do + redis-cli -h $i --rdb /tmp/redis-dump-cluster-$i.rdb || { echo "DB backup process failed."; exit 1; } + done + fi +} + +function backup_postgres(){ + PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } + PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > db_backup.sql || { echo "DB backup process failed."; exit 1; } +} + +function backup_mongodb(){ + if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then + RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf|awk '{print $2}'); + RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest"; + else + RS_SUFFIX=""; + fi + TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) + if [ "$TLS_MODE" == "requireTLS" ]; then + SSL_TLS_OPTIONS="--ssl --sslPEMKeyFile=/var/lib/jelastic/keys/SSL-TLS/client/client.pem --sslCAFile=/var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsInsecure" + else + SSL_TLS_OPTIONS="" + fi + mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" +} + +function backup_mysql(){ + SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) + [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" + if which mariadb 2>/dev/null; then + CLIENT_APP="mariadb" + else + CLIENT_APP="mysql" + fi + if which mariadb-dump 2>/dev/null; then + DUMP_APP="mariadb-dump" + else + DUMP_APP="mysqldump" + fi + ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } +} + +function backup_mysql_binlogs() { + echo $(date) ${ENV_NAME} "Backing up MySQL binary logs..." | tee -a $BACKUP_LOG_FILE + mkdir -p ${MYSQL_BINLOG_DIR} + cp /var/lib/mysql/mysql-bin.* ${MYSQL_BINLOG_DIR}/ + echo "MySQL binary logs backup completed." | tee -a $BACKUP_LOG_FILE +} + + +function pitr_backup_mysql() { + echo $(date) ${ENV_NAME} "Starting Point-In-Time Recovery (PITR) backup..." | tee -a $BACKUP_LOG_FILE + backup_mysql; + backup_mysql_binlogs; + echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE +} + + function backup(){ echo $$ > /var/run/${ENV_NAME}_backup.pid echo $(date) ${ENV_NAME} "Creating the ${BACKUP_TYPE} backup (using the backup addon with commit id ${BACKUP_ADDON_COMMIT_ID}) on storage node ${NODE_ID}" | tee -a ${BACKUP_LOG_FILE} source /etc/jelastic/metainf.conf; echo $(date) ${ENV_NAME} "Creating the DB dump" | tee -a ${BACKUP_LOG_FILE} if [ "$COMPUTE_TYPE" == "redis" ]; then - RDB_TO_REMOVE=$(ls -d /tmp/* |grep redis-dump.*) - rm -f ${RDB_TO_REMOVE} - export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} |grep '^requirepass'|awk '{print $2}'); - if [ "$REDIS_TYPE" == "-standalone" ]; then - redis-cli --rdb /tmp/redis-dump-standalone.rdb - else - export MASTERS_LIST=$(redis-cli cluster nodes|grep master|grep -v fail|awk '{print $2}'|awk -F : '{print $1}'); - for i in $MASTERS_LIST - do - redis-cli -h $i --rdb /tmp/redis-dump-cluster-$i.rdb || { echo "DB backup process failed."; exit 1; } - done - fi + backup_redis; + elif [ "$COMPUTE_TYPE" == "postgres" ]; then - PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > db_backup.sql || { echo "DB backup process failed."; exit 1; } + backup_postgres; + elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then - RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf|awk '{print $2}'); - RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest"; - else - RS_SUFFIX=""; - fi - TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) - if [ "$TLS_MODE" == "requireTLS" ]; then - SSL_TLS_OPTIONS="--ssl --sslPEMKeyFile=/var/lib/jelastic/keys/SSL-TLS/client/client.pem --sslCAFile=/var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsInsecure" - else - SSL_TLS_OPTIONS="" - fi - mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" + backup_mongodb; + else - SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) - [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" - if which mariadb 2>/dev/null; then - CLIENT_APP="mariadb" - else - CLIENT_APP="mysql" - fi - if which mariadb-dump 2>/dev/null; then - DUMP_APP="mariadb-dump" - else - DUMP_APP="mysqldump" - fi - ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + backup_mysql; + fi rm -f /var/run/${ENV_NAME}_backup.pid } + case "$1" in backup) $1 @@ -178,10 +217,10 @@ case "$1" in $1 ;; create_snapshot) - $1 - ;; + $1 + ;; update_restic) - $1 + $1 ;; *) echo "Usage: $0 {backup|check_backup_repo|rotate_snapshots|create_snapshot|update_restic}" From f66c3b8cc54e3c3abebda06ef43c927b3faf3e04 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 11:09:22 +0200 Subject: [PATCH 07/78] JE-71298 --- scripts/backup-logic.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index e37c8ee..206aa78 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -165,7 +165,7 @@ function backup_mysql(){ DUMP_APP="mysqldump" fi ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } } function backup_mysql_binlogs() { From 5b83b69b0c124e7753bfbe7ece0303f0e1ac5307 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 14:14:39 +0200 Subject: [PATCH 08/78] JE-71297 --- backup.jps | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backup.jps b/backup.jps index 5e5b36f..93f45cb 100644 --- a/backup.jps +++ b/backup.jps @@ -144,6 +144,12 @@ settings: tooltip: "Always unmount backup storage when backup/restore is finished." value: false hidden: false + - type: toggle + name: isPitr + caption: PITR + tooltip: "Point-in-Time Recovery." + value: false + hidden: false - type: displayfield name: displayfield markup: Please specify the database user that has enough privileges to access and modify all the databases stored on server. Username and password are required for all the DB servers except Redis. @@ -281,6 +287,7 @@ actions: backupExecNode: ${targetNodes.master.id} storageEnv: ${response.storageEnvShortName} isAlwaysUmount: ${this.isAlwaysUmount} + isPitr: ${this.isPitr} nodeGroup: ${this.nodeGroup} dbuser: ${this.dbuser} dbpass: ${this.dbpass} @@ -358,6 +365,7 @@ actions: - setGlobals: storageEnv: ${settings.storageName} isAlwaysUmount: ${settings.isAlwaysUmount} + isPitr: ${settings.isPitr} - if ("${settings.scheduleType}" == 2): - convert - else: @@ -372,6 +380,7 @@ actions: cronTime: ${globals.cron} backupCount: ${settings.backupCount} isAlwaysUmount: ${globals.isAlwaysUmount} + isPitr: ${globals.isPitr} nodeGroup: ${targetNodes.nodeGroup} dbuser: ${settings.dbuser} dbpass: ${settings.dbpass} From 2a10a68e393c89523011b5978992aad4e7b1c664 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 14:15:40 +0200 Subject: [PATCH 09/78] JE-71297 --- scripts/backup-main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/backup-main.js b/scripts/backup-main.js index a9c17f2..16b4e57 100644 --- a/scripts/backup-main.js +++ b/scripts/backup-main.js @@ -17,6 +17,7 @@ function run() { backupCount : "${backupCount}", storageEnv : "${storageEnv}", isAlwaysUmount : "${isAlwaysUmount}", + isPitr : "${isPitr}", nodeGroup : "${nodeGroup}", dbuser : "${dbuser}", dbpass : "${dbpass}" From 5ed79e6ab1432ecca5fe63c588af39bb199ee338 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 14:18:17 +0200 Subject: [PATCH 10/78] JE-71297 --- scripts/backup-manager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index 61c42af..98a9394 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -11,6 +11,7 @@ function BackupManager(config) { * envAppid : {String} * storageNodeId : {String} * isAlwaysUmount : {Boolean} + * isPitr : {Boolean} * backupExecNode : {String} * [nodeGroup] : {String} * [storageEnv] : {String} @@ -124,6 +125,7 @@ function BackupManager(config) { baseUrl : config.baseUrl, backupType : backupType, isAlwaysUmount : config.isAlwaysUmount, + isPitr : config.isPitr, dbuser: config.dbuser, dbpass: config.dbpass, session : session, @@ -210,6 +212,7 @@ function BackupManager(config) { nodeId : config.backupExecNode, envName : config.envName, isAlwaysUmount : config.isAlwaysUmount, + isPirt : config.isPirt, baseUrl : config.baseUrl, dbuser: config.dbuser, dbpass: config.dbpass, From c397bbcddf1f58be811a61bb5abab94fd1bb8793 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 14:20:10 +0200 Subject: [PATCH 11/78] JE-71297 --- scripts/configOnBeforeInit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/configOnBeforeInit.js b/scripts/configOnBeforeInit.js index a049037..a7e26a6 100644 --- a/scripts/configOnBeforeInit.js +++ b/scripts/configOnBeforeInit.js @@ -127,6 +127,7 @@ if (scheduleType == '1') { jps.settings.main.fields[2].default = '${settings.backupCount}'; jps.settings.main.fields[3].value = ${settings.isAlwaysUmount}; +jps.settings.main.fields[4].value = ${settings.isPitr}; jps.settings.main.fields[jps.settings.main.fields.length - 2].default = '${settings.dbuser}'; jps.settings.main.fields[jps.settings.main.fields.length - 1].default = '${settings.dbpass}'; From 247b0a93933abfa1373434939214a5a9fe6133ac Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 17:16:27 +0200 Subject: [PATCH 12/78] JE-71293 --- scripts/pitr.sh | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 scripts/pitr.sh diff --git a/scripts/pitr.sh b/scripts/pitr.sh new file mode 100644 index 0000000..59e02cf --- /dev/null +++ b/scripts/pitr.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +DBUSER=$1 +DBPASSWD=$2 +ACTION=$3 + +check_pitr() { + MYSQL_VERSION=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SELECT VERSION();") + MYSQL_MAJOR_VERSION=$(echo "$MYSQL_VERSION" | cut -d. -f1) + + if [[ "$MYSQL_MAJOR_VERSION" -ge 8 ]]; then + BINLOG_EXPIRE_SETTING="binlog_expire_logs_seconds" + EXPIRY_SETTING="604800" # 7 дней в секундах + else + BINLOG_EXPIRE_SETTING="expire_logs_days" + EXPIRY_SETTING="7" + fi + + LOG_BIN=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE 'log_bin';" | grep "ON") + EXPIRE_LOGS=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE '$BINLOG_EXPIRE_SETTING';" | awk '{ print $2 }') + + if [[ -n "$LOG_BIN" && "$EXPIRE_LOGS" == "$EXPIRY_SETTING" ]]; then + echo '{"result":0}' + else + echo '{"result":702}' + fi +} + +setup_pitr() { + + MYSQL_VERSION=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SELECT VERSION();") + MYSQL_MAJOR_VERSION=$(echo "$MYSQL_VERSION" | cut -d. -f1) + + if [[ "$MYSQL_MAJOR_VERSION" -ge 8 ]]; then + CONFIG=" +[mysqld] +log-bin=mysql-bin +binlog_expire_logs_seconds=604800 +" + else + CONFIG=" +[mysqld] +log-bin=mysql-bin +expire_logs_days=7 +" + fi + + CONFIG_FILE="/etc/conf.d/mysql/pitr.cnf" + echo "$CONFIG" > "$CONFIG_FILE" + +} + +case $ACTION in + checkPitr) + check_pitr + ;; + setupPitr) + setup_pitr + ;; +esac From 868247708eb3cc498fd506de2de73d9f070749df Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 17:36:38 +0200 Subject: [PATCH 13/78] JE-71293 --- scripts/pitr.sh | 54 +++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index 59e02cf..4a3d241 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -4,50 +4,42 @@ DBUSER=$1 DBPASSWD=$2 ACTION=$3 +PITR_CONF='/etc/mysql/conf.d/pitr.cnf' +SUCCESS_CODE=0 +ERROR_CODE=99 +PITR_ERROR_CODE=701 + +source /etc/jelastic/metainf.conf +COMPUTE_TYPE_FULL_VERSION_FORMATTED=$(echo "$COMPUTE_TYPE_FULL_VERSION" | sed 's/\.//') +if [[ ("$COMPUTE_TYPE" == "mysql" || "$COMPUTE_TYPE" == "percona") && "$COMPUTE_TYPE_FULL_VERSION_FORMATTED" -ge "81" ]]; then + BINLOG_EXPIRE_SETTING="binlog_expire_logs_seconds" + EXPIRY_SETTING="604800" +elif [[ "$COMPUTE_TYPE" == "mariadb" ]]; then + BINLOG_EXPIRE_SETTING="expire_logs_days" + EXPIRY_SETTING="7" +else + echo "{result:$ERROR_CODE, out:'Fail detect DB server'}" + exit 0 +fi + check_pitr() { - MYSQL_VERSION=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SELECT VERSION();") - MYSQL_MAJOR_VERSION=$(echo "$MYSQL_VERSION" | cut -d. -f1) - - if [[ "$MYSQL_MAJOR_VERSION" -ge 8 ]]; then - BINLOG_EXPIRE_SETTING="binlog_expire_logs_seconds" - EXPIRY_SETTING="604800" # 7 дней в секундах - else - BINLOG_EXPIRE_SETTING="expire_logs_days" - EXPIRY_SETTING="7" - fi - LOG_BIN=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE 'log_bin';" | grep "ON") EXPIRE_LOGS=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE '$BINLOG_EXPIRE_SETTING';" | awk '{ print $2 }') if [[ -n "$LOG_BIN" && "$EXPIRE_LOGS" == "$EXPIRY_SETTING" ]]; then - echo '{"result":0}' + echo "{result:$SUCCESS_CODE}" else - echo '{"result":702}' + echo "{result:$PITR_ERROR_CODE}" fi } setup_pitr() { - - MYSQL_VERSION=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SELECT VERSION();") - MYSQL_MAJOR_VERSION=$(echo "$MYSQL_VERSION" | cut -d. -f1) - - if [[ "$MYSQL_MAJOR_VERSION" -ge 8 ]]; then - CONFIG=" + CONFIG=" [mysqld] log-bin=mysql-bin -binlog_expire_logs_seconds=604800 +$BINLOG_EXPIRE_SETTING=$EXPIRY_SETTING " - else - CONFIG=" -[mysqld] -log-bin=mysql-bin -expire_logs_days=7 -" - fi - - CONFIG_FILE="/etc/conf.d/mysql/pitr.cnf" - echo "$CONFIG" > "$CONFIG_FILE" - + echo "$CONFIG" > "$PITR_CONF" } case $ACTION in From 4436d75e8b2ca7b3742f9096246d49ed6449ae7e Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 17:49:36 +0200 Subject: [PATCH 14/78] JE-71293 --- scripts/pitr.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index 4a3d241..9448412 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -1,8 +1,8 @@ #!/bin/bash -DBUSER=$1 -DBPASSWD=$2 -ACTION=$3 +ACTION=$1 +DBUSER=$2 +DBPASSWD=$3 PITR_CONF='/etc/mysql/conf.d/pitr.cnf' SUCCESS_CODE=0 From f0c669cd6881cb5fdaf0cdc0a18ed5b4c4fc7764 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 18:38:05 +0200 Subject: [PATCH 15/78] JE-71293 --- scripts/pitr.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index 9448412..51e4ad3 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -5,9 +5,6 @@ DBUSER=$2 DBPASSWD=$3 PITR_CONF='/etc/mysql/conf.d/pitr.cnf' -SUCCESS_CODE=0 -ERROR_CODE=99 -PITR_ERROR_CODE=701 source /etc/jelastic/metainf.conf COMPUTE_TYPE_FULL_VERSION_FORMATTED=$(echo "$COMPUTE_TYPE_FULL_VERSION" | sed 's/\.//') @@ -18,18 +15,17 @@ elif [[ "$COMPUTE_TYPE" == "mariadb" ]]; then BINLOG_EXPIRE_SETTING="expire_logs_days" EXPIRY_SETTING="7" else - echo "{result:$ERROR_CODE, out:'Fail detect DB server'}" + echo '{"result":99}'; exit 0 fi check_pitr() { LOG_BIN=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE 'log_bin';" | grep "ON") EXPIRE_LOGS=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE '$BINLOG_EXPIRE_SETTING';" | awk '{ print $2 }') - if [[ -n "$LOG_BIN" && "$EXPIRE_LOGS" == "$EXPIRY_SETTING" ]]; then - echo "{result:$SUCCESS_CODE}" + echo '{"result":0}'; else - echo "{result:$PITR_ERROR_CODE}" + echo '{"result":702}'; fi } From 403f7a5f267a63027c0eb7053e5a190d43427a3c Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 18 Sep 2024 18:53:59 +0200 Subject: [PATCH 16/78] JE-71293 --- scripts/pitr.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index 51e4ad3..08d82da 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -24,8 +24,10 @@ check_pitr() { EXPIRE_LOGS=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE '$BINLOG_EXPIRE_SETTING';" | awk '{ print $2 }') if [[ -n "$LOG_BIN" && "$EXPIRE_LOGS" == "$EXPIRY_SETTING" ]]; then echo '{"result":0}'; + return 0; else echo '{"result":702}'; + return 702; fi } From ae63d0d6069f52f0bc6a06fafe9733b741c7ddaf Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 19 Sep 2024 15:43:02 +0200 Subject: [PATCH 17/78] JE-71293 --- scripts/pitr.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index 08d82da..be13fa8 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -16,7 +16,6 @@ elif [[ "$COMPUTE_TYPE" == "mariadb" ]]; then EXPIRY_SETTING="7" else echo '{"result":99}'; - exit 0 fi check_pitr() { @@ -27,7 +26,6 @@ check_pitr() { return 0; else echo '{"result":702}'; - return 702; fi } From c62eff910b1a149a2560f5eb7e7dad0235899dcd Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 19 Sep 2024 17:06:51 +0200 Subject: [PATCH 18/78] Create test.jps --- test.jps | 466 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 test.jps diff --git a/test.jps b/test.jps new file mode 100644 index 0000000..4f19d4f --- /dev/null +++ b/test.jps @@ -0,0 +1,466 @@ +type: update +jpsVersion: 6.1.1 +name: Database Backup/Restore for the filesystem and the databases +id: db-backup +targetEditions: any +logo: /images/backup-logo.png +description: Backup Add-On for the database. It can be used to create scheduled backups according to any required timezone and restore corrupted databases, even if the content has been completely deleted. +baseUrl: https://raw.githubusercontent.com/sych74/database-backup-addon/pitr + +targetNodes: + nodeType: + - redis + - redis7 + - redis6 + - postgresql + - postgres15 + - postgres14 + - postgres13 + - postgres12 + - postgres11 + - mysql + - mysql5 + - mysql8 + - mariadb-dockerized + - mariadb10 + - mariadb11 + - mongodb-dockerized + - mongodb + - mongo + - perconadb + - percona8 + - percona5 + +settings: + main: + fields: + - type: radio-fieldset + name: scheduleType + hidden: false + default: '1' + values: + - value: 1 + caption: Pre-defined + - value: 2 + caption: Custom + - value: 3 + caption: Manual (crontab) + tooltip: "A simple cron-based + scheduler to automatically start the backup process based on prescribed timing + instructions.
Note that the required timestamps should be specified + respectively to the UTC time zone.
" + showIf: + 1: + - name: cronTime + caption: Backup schedule + type: list + editable: false + values: + - value: 0 * * * * + caption: "Hourly (at minute 0)" + - value: 0 0 * * * + caption: "Daily (at 00:00)" + - value: 0 0 * * 0 + caption: "Weekly (at 00:00 on Sunday)" + - value: 0 0 1 * * + caption: "Monthly (at 00:00 on day 1)" + default: 0 0 * * * + 2: + - type: string + name: backupTime + caption: Time + inputType: time + default: "09:00" + cls: x-form-text + width: 120 + required: true + - caption: Days + type: compositefield + name: days + defaultMargins: 0 12 0 0 + items: + - name: sun + value: true + type: checkbox + caption: Su + - name: mon + value: true + type: checkbox + caption: Mo + - name: tue + value: true + type: checkbox + caption: Tu + - name: wed + value: true + type: checkbox + caption: We + - name: thu + value: true + type: checkbox + caption: Th + - name: fri + value: true + type: checkbox + caption: Fr + - name: sat + value: true + type: checkbox + caption: Sa + - name: "tz" + caption: "Time Zone" + type: "list" + required: true + editable: true + forceSelection: true + values: values + 3: + - name: cronTime + caption: Crontab + type: string + default: 0 0 * * * + regexText: Cron syntax is incorrect! + regex: "^(((([\\\\*]{1}){1,})|((\\\\*\\\\\\/){0,1}(([0-9\\/\\*\\-\\,]{1}){1,}|(([1-5]{1}){1}([0-9\\/\\*\\-\\,]{1}){1,}){1}))) + ((([\\\\*]{1}){1,})|((\\\\*\\\\\\/){0,1}(([0-9\\/\\*\\-\\,]{1}){1,}|(([1]{1}){1}([0-9\\/\\*\\-\\,-]{1}){1,}){1}|([2]{1}){1}([0-3]{1}){1}))) + ((([\\\\*]{1}){1})|((\\\\*\\\\\\/){0,1}(([1-9]{1}){1}|(([1-2]{1}){1}([0-9\\/\\*\\-\\,]{1}){1,5}){1}|([3]{1}){1}([0-1]{1}){1}))) + ((([\\\\*]{1}){1})|((\\\\*\\\\\\/){0,1}(([1-9]{1}){1}|(([1-2]{1}){1}([0-9\\/\\*\\-\\,]{1}){1,}){1}|([3]{1}){1}([0-1]{1}){1}))|(jan|JAN|feb|FEB|mar|MAR|apr|APR|may|MAY|jun|JUN|jul|JUL|aug|AUG|sep|SEP|okt|OKT|nov|NOV|dec|DEC)(-?\\w+?)?) + ((([\\\\*]{1}){1})|((\\\\*\\\\\\/){0,1}(([0-7]{1,}(-?[0-7]?(,[0-7]){0,6})){1}))|((sun|SUN|mon|MON|tue|TUE|wed|WED|thu|THU|fri|FRI|sat|SAT)?(,(sun|SUN|mon|MON|tue|TUE|wed|WED|thu|THU|fri|FRI|sat|SAT)){0,6})(-?\\w+?)?))$|^(@(reboot|yearly|annualy|monthly|weekly|daily|hourly))$" + - caption: Backup storage + type: list + tooltip: "The environment with backup storage to be used for backups creation. Presence of this environment is obligatory." + name: storageName + dependsOn: region + required: true + - type: spinner + name: backupCount + caption: Number of backups + tooltip: "The number of newest backups to be kept during rotation." + min: 1 + max: 30 + default: 5 + - type: toggle + name: isAlwaysUmount + caption: Always umount + tooltip: "Always unmount backup storage when backup/restore is finished." + value: false + hidden: false + - type: toggle + name: isPitr + caption: PITR + tooltip: "Point in time recovery" + value: false + hidden: false + - type: displayfield + name: displayPitr + markup: "" + hidden: true + hideLabel: true + cls: warning + - type: displayfield + name: displayfield + markup: Please specify the database user that has enough privileges to access and modify all the databases stored on server. Username and password are required for all the DB servers except Redis. + hidden: false + hideLabel: true + cls: warning + - hideLabel: false + hidden: false + type: string + caption: Database User + name: dbuser + tooltip: In case you restore non-native database backup do not forget to provide its credentials instead of initial ones with help of add-on Configure action. It is relevant to sqldb databases and MongoDB only. + - hideLabel: false + hidden: false + type: string + inputType: password + caption: Database Password + name: dbpass + onBeforeInit: scripts/configOnBeforeInit.js + + restore: + fields: [] + onBeforeInit: scripts/restoreOnBeforeInit.js + + pitr: + fields: [] + onBeforeInit: | + var respOut; + var pitr_conf_error_markup = "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon"; + var pitr_conf_success_markup = "Database configured for PITR support"; + var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; + + var checkPitrCmd = "wget " + '${baseUrl}' + "/scripts/pitr.sh -O /root/pitr.sh &>> /var/log/run.log; bash /root/pitr.sh checkPitr " + '${settings.dbuser}' + " " + '${settings.dbpass}'; + resp = jelastic.env.control.ExecCmdById('${env.envName}', session, '${nodes.sqldb.master.id}', toJSON([{ command: checkPitrCmd }]), true, "root"); + if (resp.result != 0) return resp; + respOut = resp.responses[0].out; + respOut = JSON.parse(respOut); + if (respOut.result == 702) { + settings.fields.push({ + caption: "PITR", + type: "toggle", + name: "isPitr", + tooltip: "Point in time recovery", + values: false, + hidden: false, + disabled: true + }, { + type: "displayfield", + cls: "warning", + height: 30, + hideLabel: true, + markup: pitr_conf_error_markup + }); + } else { + settings.fields.push({ + caption: "PITR", + type: "toggle", + name: "isPitr", + tooltip: "Point in time recovery", + values: false, + hidden: false, + disabled: false + }, { + type: "displayfield", + cls: "success", + height: 30, + hideLabel: true, + markup: pitr_conf_success_markup + }); + } + return settings; + +onBeforeInit: scripts/backupOnBeforeInit.js + +buttons: +- caption: Backup Now + action: backup + loadingText: Backing up... + confirmText: Do you want to initiate the backup process? + successText: The backup process has been finished successfully. + +- caption: Configure + action: configure + settings: main + loadingText: Configuring... + successText: The backup configs have been updated successfully. + +- caption: Restore + action: restore + loadingText: Restoring... + settings: restore + successText: The backup have been successfully restored. + title: Restore Backup + submitButtonText: Restore + confirmText: You are going to restore from a backup, which will override all your existing data. This action cannot be canceled or reverted. Do you want to proceed? + +globals: + scriptSufix: db-backup + +onInstall: + - return: + type: success + - checkAddons + - installRestic + - setSchedule + +onUninstall: + - callScript: uninstall + - removeScript + +onBeforeDelete: + - callScript: uninstall + - removeScript + +onAfterRedeployContainer[${targetNodes.nodeGroup}]: + - installRestic + +onAfterClone: + - script: return {result:0, jps:MANIFEST}; + - install: ${response.jps} + nodeGroup: ${targetNodes.nodeGroup} + envName: ${event.response.env.envName} + settings: + scheduleType: ${settings.scheduleType} + storageName: ${settings.storageName} + cronTime: ${settings.cronTime} + backupTime: ${settings.backupTime} + sun: ${settings.sun} + mon: ${settings.mon} + tue: ${settings.tue} + wed: ${settings.wed} + thu: ${settings.thu} + fri: ${settings.fri} + sat: ${settings.sat} + tz: ${settings.tz} + backupCount: ${settings.backupCount} + isAlwaysUmount: ${settings.isAlwaysUmount} + +onAfterConfirmTransfer: setSchedule + +actions: + checkAddons: + - script: |- + var onAfterReturn = { setGlobals: {} }, + glbs = onAfterReturn.setGlobals, + resp = api.marketplace.app.GetAddonList({ + search: {}, + envName: "${env.name}", + session: session + }); + if (resp.result != 0) return resp; + glbs["alreadyInstalled"] = false; + for (let i = 0, n = resp.apps.length; i < n; i++) { + if (resp.apps[i].isInstalled) { + if (resp.apps[i].app_id == 'wp-backup') { + glbs["alreadyInstalled"] = true; + break; + } + } + } + return { result: 0, onAfterReturn: onAfterReturn }; + - if ('${globals.alreadyInstalled}' == 'true' ): + - stopEvent: + type: warning + message: Database backup add-on is already installed on ${env.name}. Database backup addon installation is not possible. + + installRestic: + cmd [${targetNodes.nodeGroup}]: |- + wget --tries=10 -O /tmp/installUpdateRestic ${baseUrl}/scripts/installUpdateRestic && \ + mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ + chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic + user: root + + installScript: + - removeScript + - getStorageCtid + - script: ${baseUrl}/scripts/create-backup-main-script.js?_r=${fn.random} + params: + scriptName: ${env.envName}-${globals.scriptSufix} + baseUrl: ${baseUrl} + cronTime: ${this.cronTime} + backupCount: ${this.backupCount} + userId: ${env.uid} + storageNodeId: ${response.storageCtid} + backupExecNode: ${targetNodes.master.id} + storageEnv: ${response.storageEnvShortName} + isAlwaysUmount: ${this.isAlwaysUmount} + isPitr: ${this.isPitr:false} + nodeGroup: ${this.nodeGroup} + dbuser: ${this.dbuser} + dbpass: ${this.dbpass} + + callScript: + script: |- + var resp = jelastic.dev.scripting.Eval(appid, session, '${env.envName}-${globals.scriptSufix}', {action:"${this}"}); + if (resp.result === 1702 && "${this}" == "uninstall") { + return { result: 0, out: "script not found" }; + } else { + return resp.response || resp; + } + + removeScript: + script: |- + var resp = jelastic.dev.scripting.GetScript(appid, session, '${env.envName}-${globals.scriptSufix}'); + if (resp.result === 0) { + var resp = jelastic.dev.scripting.DeleteScript(appid, session, '${env.envName}-${globals.scriptSufix}'); + return resp.response || resp; + } + return { result: 0 }; + + backup: + - callScript: backup + - deleteDBdump + + restore: + - cmd[${targetNodes.nodeGroup}]: |- + echo "${settings.backupedEnvName}" > /root/.backupedenv + echo "${settings.backupDir}" > /root/.backupid + user: root + - callScript: restore + - deleteDBdump + + deleteDBdump: + - cmd[${targetNodes.nodeGroup}]: |- + [ -f /root/db_backup.sql ] && rm -f /root/db_backup.sql || exit 0; + user: root + + configure: + - setSchedule + + getStorageCtid: + - script: scripts/getStorageCtid.js + + convert: + - script: | + var resp = {result:0, onAfterReturn: {setGlobals:{cron: ""}}}, offset = java.util.TimeZone.getTimeZone("${settings.tz}").getRawOffset(), + setGlobals = resp.onAfterReturn.setGlobals; + + var time = "${settings.backupTime}".split(":"), + d1 = new Date(2020, 1, 10, parseInt(time[0],10), parseInt(time[1],10)), + d2 = new Date(d1.getTime() - offset), + dd = d2.getDate() - d1.getDate(), + days = getDays([${settings.sun:0}, ${settings.mon:0}, ${settings.tue:0}, ${settings.wed:0}, ${settings.thu:0}, ${settings.fri:0}, ${settings.sat:0}], dd); + + setGlobals.cron = d2.getMinutes() + " " + d2.getHours() + " * * " + days.join(","); + + + function getDays(settings, dd) { + var days = []; + for (var i = 0, n = settings.length; i < n; i++) { + if (settings[i]) { + var day = i + dd; + if (day < 0) day +=7; else if (day > 6) day -=7; + days.push(day); + } + } + days.sort(); + return days; + } + return resp; + + setSchedule: + - setGlobals: + storageEnv: ${settings.storageName} + isAlwaysUmount: ${settings.isAlwaysUmount} + isPitr: ${settings.isPitr:false} + - if ("${settings.scheduleType}" == 2): + - convert + - else: + - setGlobals: + cron: ${settings.cronTime} + - if ("${settings.isAlwaysUmount}" == "true"): + - removePermanentMount + - else: + - removePermanentMount + - addPermanentMount + - installScript: + cronTime: ${globals.cron} + backupCount: ${settings.backupCount} + isAlwaysUmount: ${globals.isAlwaysUmount} + isPitr: ${globals.isPitr} + nodeGroup: ${targetNodes.nodeGroup} + dbuser: ${settings.dbuser} + dbpass: ${settings.dbpass} + + addPermanentMount: + - getStorageCtid + - script: | + return jelastic.env.file.AddMountPointById("${env.envName}", session, "${targetNodes.master.id}", "/opt/backup", "nfs4", null, "/data/", "${response.storageCtid}", "DBBackupRestore", false); + + removePermanentMount: + - getStorageCtid + - script: | + var allMounts = jelastic.env.file.GetMountPoints("${env.envName}", session, "${targetNodes.master.id}").array; + for (var i = 0, n = allMounts.length; i < n; i++) { + if (allMounts[i].path == "/opt/backup" && allMounts[i].type == "INTERNAL") { + resp = jelastic.env.file.RemoveMountPointById("${env.envName}", session, "${targetNodes.master.id}", "/opt/backup"); + if (resp.result != 0) { return resp; } + } + } + allMounts = jelastic.env.file.GetMountPoints("${env.envName}", session).array; + for (var i = 0, n = allMounts.length; i < n; i++) { + if (allMounts[i].path == "/opt/backup" && allMounts[i].type == "INTERNAL") { + resp = jelastic.env.file.RemoveMountPointByGroup("${env.envName}", session, "sqldb", "/opt/backup"); + if (resp.result != 0) { return resp; } + } + } + return { "result": 0 }; From 6ca7f794afb235d8691687b1dce8df128cd1bfc7 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 20 Sep 2024 17:22:13 +0200 Subject: [PATCH 19/78] JE-71293 --- scripts/pitr.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index be13fa8..bb80cdb 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -30,6 +30,11 @@ check_pitr() { } setup_pitr() { + check_pitr | grep -q '"result":0' + if [[ $? -eq 0 ]]; then + exit 0; + fi + CONFIG=" [mysqld] log-bin=mysql-bin From ee720b7ac7ee6e18e7791be2d1b48996de2de6b8 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Mon, 23 Sep 2024 13:21:25 +0200 Subject: [PATCH 20/78] JE-71293 --- scripts/pitr.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index bb80cdb..b48b215 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -41,6 +41,7 @@ log-bin=mysql-bin $BINLOG_EXPIRE_SETTING=$EXPIRY_SETTING " echo "$CONFIG" > "$PITR_CONF" + jem service restart; } case $ACTION in From edd645e1eaed2c2ae965c1bc9df575d530f19cfb Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 24 Sep 2024 15:49:59 +0200 Subject: [PATCH 21/78] Update test.jps --- test.jps | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/test.jps b/test.jps index 4f19d4f..afc2610 100644 --- a/test.jps +++ b/test.jps @@ -150,6 +150,14 @@ settings: tooltip: "Point in time recovery" value: false hidden: false + showIf: + true: + - type: displayfield + cls: warning + height: 30, + hideLabel: true, + markup: "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon" + - type: displayfield name: displayPitr markup: "" @@ -256,8 +264,7 @@ globals: scriptSufix: db-backup onInstall: - - return: - type: success + - checkAddons - installRestic - setSchedule @@ -421,7 +428,7 @@ actions: - setGlobals: storageEnv: ${settings.storageName} isAlwaysUmount: ${settings.isAlwaysUmount} - isPitr: ${settings.isPitr:false} + isPitr: ${settings.isPitr} - if ("${settings.scheduleType}" == 2): - convert - else: @@ -432,6 +439,7 @@ actions: - else: - removePermanentMount - addPermanentMount + - if ("${settings.isPitr}" == "true"): setupPitr - installScript: cronTime: ${globals.cron} backupCount: ${settings.backupCount} @@ -440,7 +448,12 @@ actions: nodeGroup: ${targetNodes.nodeGroup} dbuser: ${settings.dbuser} dbpass: ${settings.dbpass} - + + setupPitr: + cmd[${nodes.sqldb.master.id}]: |- + wget --tries=10 -O /tmp/pitr.sh ${baseUrl}/scripts/pitr.sh && \ + chmod +x /tmp/pitr.sh && /tmp/pitr.sh setupPitr ${settings.dbuser} ${settings.dbpass}; + addPermanentMount: - getStorageCtid - script: | From b10fd54d2f87e2c99c0aecb49950dd1a4dbe8230 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 24 Sep 2024 16:23:51 +0200 Subject: [PATCH 22/78] JE-71293 --- scripts/create-backup-main-script.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/create-backup-main-script.js b/scripts/create-backup-main-script.js index 58766ba..426861c 100644 --- a/scripts/create-backup-main-script.js +++ b/scripts/create-backup-main-script.js @@ -10,6 +10,7 @@ var scriptName = getParam("scriptName", "${env.envName}-wp-backup"), backupExecNode = getParam("backupExecNode"), storageEnv = getParam("storageEnv"), isAlwaysUmount = getParam("isAlwaysUmount"), + isPitr = getParam("isPitr"), nodeGroup = getParam("nodeGroup"), dbuser = getParam("dbuser"), dbpass = getParam("dbpass"); @@ -28,6 +29,7 @@ function run() { backupExecNode : backupExecNode, storageEnv : storageEnv, isAlwaysUmount : isAlwaysUmount, + isPitr : isPitr, nodeGroup : nodeGroup, dbuser : dbuser, dbpass : dbpass From e3ca10a74235644ff8d01996de4c5fe44d82223a Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 25 Sep 2024 10:14:07 +0200 Subject: [PATCH 23/78] JE-71293 --- scripts/backup-logic.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 206aa78..2992966 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -10,12 +10,16 @@ DBUSER=$8 DBPASSWD=$9 USER_SESSION=${10} USER_EMAIL=${11} - +PITR=${12} 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}') +if [ -z "$PITR" ]; then + PITR="false" +fi + if [ "$COMPUTE_TYPE" == "mongodb" ]; then if grep -q '^replication' /etc/mongod.conf; then MONGO_TYPE="-replica-set" @@ -165,7 +169,11 @@ function backup_mysql(){ DUMP_APP="mysqldump" fi ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + if [ "$PITR" == "true" ]; then + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + else + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + fi } function backup_mysql_binlogs() { @@ -210,6 +218,9 @@ case "$1" in backup) $1 ;; + pitr_backup_mysql) + $1 + ;; check_backup_repo) $1 ;; @@ -219,7 +230,7 @@ case "$1" in create_snapshot) $1 ;; - update_restic) + update_restic) $1 ;; *) From 6f1c3a9cd2e243e588d3f591965a967fe7e268e2 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 25 Sep 2024 10:21:24 +0200 Subject: [PATCH 24/78] JE-71293 --- scripts/backup-manager.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index 98a9394..4422fbc 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -151,22 +151,22 @@ function BackupManager(config) { 'bash /root/%(envName)_backup-logic.sh update_restic %(baseUrl)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email)' + 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email)' + 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh backup %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass)' + 'bash /root/%(envName)_backup-logic.sh backup %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr) %(isPitr)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh create_snapshot %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email)' + 'bash /root/%(envName)_backup-logic.sh create_snapshot %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email)' + 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email)' + 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.removeMounts, config.isAlwaysUmount ] ]); From 9c5de3d91897683fed6512e62e5d4ffce57fc5af Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 25 Sep 2024 10:21:30 +0200 Subject: [PATCH 25/78] JE-71293 From 61b4b8ae346c60ce7e578a7f92a6b73e5990ff3e Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 25 Sep 2024 10:31:35 +0200 Subject: [PATCH 26/78] Update test.jps --- test.jps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.jps b/test.jps index afc2610..299f3a8 100644 --- a/test.jps +++ b/test.jps @@ -350,7 +350,7 @@ actions: backupExecNode: ${targetNodes.master.id} storageEnv: ${response.storageEnvShortName} isAlwaysUmount: ${this.isAlwaysUmount} - isPitr: ${this.isPitr:false} + isPitr: ${this.isPitr} nodeGroup: ${this.nodeGroup} dbuser: ${this.dbuser} dbpass: ${this.dbpass} From 79d48ad9204dcd3d8500fe8870d37eb26d0cd5ec Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 25 Sep 2024 13:36:25 +0200 Subject: [PATCH 27/78] JE-71293 --- scripts/backup-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index 4422fbc..2f9e12b 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -157,7 +157,7 @@ function BackupManager(config) { 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh backup %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr) %(isPitr)' + 'bash /root/%(envName)_backup-logic.sh backup %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.cmd, [ 'bash /root/%(envName)_backup-logic.sh create_snapshot %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' From a145e503bd888dfd556aa38cd7461ae4a28e8aa7 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 26 Sep 2024 11:26:43 +0200 Subject: [PATCH 28/78] JE-71293 --- scripts/backup-logic.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 2992966..b5163e0 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -155,7 +155,7 @@ function backup_mongodb(){ mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" } -function backup_mysql(){ +function backup_mysql_dump(){ SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" if which mariadb 2>/dev/null; then @@ -184,9 +184,9 @@ function backup_mysql_binlogs() { } -function pitr_backup_mysql() { +function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "Starting Point-In-Time Recovery (PITR) backup..." | tee -a $BACKUP_LOG_FILE - backup_mysql; + backup_mysql_dump; backup_mysql_binlogs; echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE } @@ -207,7 +207,7 @@ function backup(){ backup_mongodb; else - backup_mysql; + backup_mysql_dump; fi rm -f /var/run/${ENV_NAME}_backup.pid From 433e158e7f4c5e72ae1cacf7fafad60dcb514a9d Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Mon, 30 Sep 2024 13:59:09 +0200 Subject: [PATCH 29/78] JE-71293 --- scripts/backup-logic.sh | 74 +++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index b5163e0..3276535 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -16,6 +16,12 @@ BACKUP_ADDON_REPO=$(echo ${BASE_URL}|sed 's|https:\/\/raw.githubusercontent.com\ 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}') +DUMP_BACKUP_DIR=/root/backup/dump +BINLOGS_BACKUP_DIR=/root/backup/binlogs +SQL_DUMP_NAME=db_backup.sql + +rm -rf $DUMP_BACKUP_DIR && mkdir -p $DUMP_BACKUP_DIR + if [ -z "$PITR" ]; then PITR="false" fi @@ -39,6 +45,20 @@ if [ "$COMPUTE_TYPE" == "redis" ]; then fi fi +SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) +[ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" +if which mariadb 2>/dev/null; then + CLIENT_APP="mariadb" +else + CLIENT_APP="mysql" +fi + +if which mariadb-dump 2>/dev/null; then + DUMP_APP="mariadb-dump" +else + DUMP_APP="mysqldump" +fi + function forceInstallUpdateRestic(){ wget --tries=10 -O /tmp/installUpdateRestic ${BASE_URL}/scripts/installUpdateRestic && \ mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ @@ -80,7 +100,7 @@ function update_restic(){ function check_backup_repo(){ [ -d /opt/backup/${ENV_NAME} ] || mkdir -p /opt/backup/${ENV_NAME} export FILES_COUNT=$(ls -n /opt/backup/${ENV_NAME}|awk '{print $2}'); - if [ "${FILES_COUNT}" != "0" ]; then + if [ "${FILES_COUNT}" != "0" ]; then echo $(date) ${ENV_NAME} "Checking the backup repository integrity and consistency" | tee -a $BACKUP_LOG_FILE; if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; @@ -98,23 +118,39 @@ function rotate_snapshots(){ if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} unlock - sendEmailNotification + sendEmailNotification fi { GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic forget -q -r /opt/backup/${ENV_NAME} --keep-last ${BACKUP_COUNT} --prune | tee -a $BACKUP_LOG_FILE; } || { echo "Backup rotation failed."; exit 1; } } + +function get_binlog_file(){ + local binlog_file=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $1}') + echo $(date) ${ENV_NAME} "Getting the binlog_file: ${binlog_file}" >> ${BACKUP_LOG_FILE} + echo $binlog_file +} + +function get_binlog_position(){ + local binlog_pos=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $2}') + echo $(date) ${ENV_NAME} "Getting the binlog_position: ${binlog_pos}" >> ${BACKUP_LOG_FILE} + echo $binlog_pos +} + function create_snapshot(){ - source /etc/jelastic/metainf.conf - echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} + source /etc/jelastic/metainf.conf DUMP_NAME=$(date "+%F_%H%M%S_%Z"-${BACKUP_TYPE}\($COMPUTE_TYPE-$COMPUTE_TYPE_FULL_VERSION$REDIS_TYPE$MONGO_TYPE\)) + echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} if [ "$COMPUTE_TYPE" == "redis" ]; then RDB_TO_BACKUP=$(ls -d /tmp/* |grep redis-dump.*); GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${RDB_TO_BACKUP} | tee -a ${BACKUP_LOG_FILE}; elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/dump | tee -a ${BACKUP_LOG_FILE} + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/dump | tee -a ${BACKUP_LOG_FILE} else - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/db_backup.sql | tee -a ${BACKUP_LOG_FILE} + if [ "$PITR" == "true" ]; then + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" --tag "$(get_binlog_file)" --tag "$(get_binlog_position)" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} + else + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} + fi fi } @@ -136,7 +172,7 @@ function backup_redis(){ function backup_postgres(){ PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > db_backup.sql || { echo "DB backup process failed."; exit 1; } + PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > db_backup.sql || { echo "DB backup process failed."; exit 1; } } function backup_mongodb(){ @@ -156,23 +192,11 @@ function backup_mongodb(){ } function backup_mysql_dump(){ - SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) - [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" - if which mariadb 2>/dev/null; then - CLIENT_APP="mariadb" - else - CLIENT_APP="mysql" - fi - if which mariadb-dump 2>/dev/null; then - DUMP_APP="mariadb-dump" - else - DUMP_APP="mysqldump" - fi ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } if [ "$PITR" == "true" ]; then - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > ${TMP_BACKUP_DIR}/${SQL_DUMP_NAME} || { echo "DB backup process failed."; exit 1; } else - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > db_backup.sql || { echo "DB backup process failed."; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > ${TMP_BACKUP_DIR}/${SQL_DUMP_NAME} || { echo "DB backup process failed."; exit 1; } fi } @@ -200,11 +224,11 @@ function backup(){ if [ "$COMPUTE_TYPE" == "redis" ]; then backup_redis; - elif [ "$COMPUTE_TYPE" == "postgres" ]; then - backup_postgres; - elif [ "$COMPUTE_TYPE" == "mongodb" ]; then backup_mongodb; + + elif [ "$COMPUTE_TYPE" == "postgres" ]; then + backup_postgres; else backup_mysql_dump; From b35952033801e5a75b903db7d7914bc4f798e04a Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 1 Oct 2024 10:25:55 +0200 Subject: [PATCH 30/78] JE-71293 --- scripts/backup-logic.sh | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 3276535..435da0e 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -20,7 +20,8 @@ DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql -rm -rf $DUMP_BACKUP_DIR && mkdir -p $DUMP_BACKUP_DIR +##rm -rf $DUMP_BACKUP_DIR && mkdir -p $DUMP_BACKUP_DIR + if [ -z "$PITR" ]; then PITR="false" @@ -147,13 +148,33 @@ function create_snapshot(){ GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/dump | tee -a ${BACKUP_LOG_FILE} else if [ "$PITR" == "true" ]; then - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" --tag "$(get_binlog_file)" --tag "$(get_binlog_position)" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" --tag "PITR" --tag "$(get_binlog_file)" --tag "$(get_binlog_position)" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} else GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} fi fi } +function get_latest_pitr_snapshot_id(){ + local latest_pitr_snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PITR" --latest 1 --json | jq -r '.[0].short_id') + echo $(date) ${ENV_NAME} "Getting the latest PITR snapshot: ${latest_pitr_snapshot_id}" >> ${BACKUP_LOG_FILE} + echo ${latest_pitr_snapshot_id} +} + +function get_dump_name_by_snapshot_id(){ + local snapshot_id="$1" + local dump_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') + echo $(date) ${ENV_NAME} "Getting the dump name: ${dump_name}" >> ${BACKUP_LOG_FILE} + echo ${dump_name} +} + + +function create_binlog_snapshot(){ + echo + +} + + function backup_redis(){ source /etc/jelastic/metainf.conf; RDB_TO_REMOVE=$(ls -d /tmp/* |grep redis-dump.*) From 894e2856397d823183f7d79b0aa1e2fd1b429810 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 1 Oct 2024 14:18:49 +0200 Subject: [PATCH 31/78] JE-71293 --- scripts/backup-logic.sh | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 435da0e..d62c0b4 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -16,6 +16,8 @@ BACKUP_ADDON_REPO=$(echo ${BASE_URL}|sed 's|https:\/\/raw.githubusercontent.com\ 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}') + + DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql @@ -169,6 +171,13 @@ function get_dump_name_by_snapshot_id(){ } +function get_binlog_file_by_snapshot_id(){ + local snapshot_id="$1" + local binlog_file=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[2]') + echo $(date) ${ENV_NAME} "Getting the start binlog file name: ${binlog_file}" >> ${BACKUP_LOG_FILE} + echo ${binlog_file} +} + function create_binlog_snapshot(){ echo @@ -215,16 +224,17 @@ function backup_mongodb(){ function backup_mysql_dump(){ ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } if [ "$PITR" == "true" ]; then - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > ${TMP_BACKUP_DIR}/${SQL_DUMP_NAME} || { echo "DB backup process failed."; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > ${DUMP_BACKUP_DIR}/${SQL_DUMP_NAME} || { echo "DB backup process failed."; exit 1; } else - ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > ${TMP_BACKUP_DIR}/${SQL_DUMP_NAME} || { echo "DB backup process failed."; exit 1; } + ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force --single-transaction --quote-names --opt --all-databases > ${DUMP_BACKUP_DIR}/${SQL_DUMP_NAME} || { echo "DB backup process failed."; exit 1; } fi } function backup_mysql_binlogs() { - echo $(date) ${ENV_NAME} "Backing up MySQL binary logs..." | tee -a $BACKUP_LOG_FILE - mkdir -p ${MYSQL_BINLOG_DIR} - cp /var/lib/mysql/mysql-bin.* ${MYSQL_BINLOG_DIR}/ + local start_binlog_file="$1" + echo $(date) ${ENV_NAME} "Backing up MySQL binary logs from $start_binlog_file..." | tee -a $BACKUP_LOG_FILE + rm -rf ${BINLOGS_BACKUP_DIR} && mkdir -p ${BINLOGS_BACKUP_DIR} + find /var/lib/mysql -type f -name "mysql-bin.*" -newer /var/lib/mysql/${start_binlog_file} -o -name "${start_binlog_file}" -exec cp {} ${BINLOGS_BACKUP_DIR} \; echo "MySQL binary logs backup completed." | tee -a $BACKUP_LOG_FILE } @@ -236,6 +246,21 @@ function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE } +function backup_mysql(){ + backup_mysql_dump; + if [ "$PITR" == "true" ]; then + latest_pitr_snapshot_id=$(get_latest_pitr_snapshot_id) + if [ "x$latest_pitr_snapshot_id" != "xnull" ]; then + dump_name=$(get_dump_name_by_snapshot_id $latest_pitr_snapshot_id) + start_binlog_file=$(get_binlog_file_by_snapshot_id $latest_pitr_snapshot_id) + backup_mysql_binlogs $start_binlog_file + + echo ------$start_binlog_file + fi + fi + create_snapshot; +} + function backup(){ echo $$ > /var/run/${ENV_NAME}_backup.pid @@ -252,7 +277,7 @@ function backup(){ backup_postgres; else - backup_mysql_dump; + backup_mysql; fi rm -f /var/run/${ENV_NAME}_backup.pid From 17dd978f798c51e169146884e331a92e410d12cc Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 1 Oct 2024 14:31:11 +0200 Subject: [PATCH 32/78] JE-71293 --- scripts/backup-logic.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index d62c0b4..be5344f 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -246,6 +246,14 @@ function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE } + +function create_binlog_snapshot(){ + local snapshot_name="$1" + echo $(date) ${ENV_NAME} "Saving the BINLOGS to ${snapshot_name} snapshot" | tee -a ${BACKUP_LOG_FILE} + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${snapshot_name}" --tag "BINLOGS" ${BINLOGS_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} + +} + function backup_mysql(){ backup_mysql_dump; if [ "$PITR" == "true" ]; then @@ -254,8 +262,7 @@ function backup_mysql(){ dump_name=$(get_dump_name_by_snapshot_id $latest_pitr_snapshot_id) start_binlog_file=$(get_binlog_file_by_snapshot_id $latest_pitr_snapshot_id) backup_mysql_binlogs $start_binlog_file - - echo ------$start_binlog_file + create_binlog_snapshot "${dump_name}" fi fi create_snapshot; From 232de06bb2124a7b4d8d9d985c3f9cbfbcce50cb Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 3 Oct 2024 14:17:28 +0200 Subject: [PATCH 33/78] Create restote-test.sh --- scripts/restote-test.sh | 84 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 scripts/restote-test.sh diff --git a/scripts/restote-test.sh b/scripts/restote-test.sh new file mode 100644 index 0000000..1b45387 --- /dev/null +++ b/scripts/restote-test.sh @@ -0,0 +1,84 @@ +#!/bin/bash + + +DBUSER=$1 +DBPASSWD=$2 +RESTORE_LOG_FILE=$3 +PITR=$4 + +DUMP_BACKUP_DIR=/root/backup/dump +BINLOGS_BACKUP_DIR=/root/backup/binlogs +SQL_DUMP_NAME=db_backup.sql + + +if [ -f /root/.backupedenv ]; then + ENV_NAME=$(cat /root/.backupedenv) +else + echo "The /root/.backupedenv file with ENV_NAME doesnt exist." + exit 1; +fi + +if [ -z "$PITR" ]; then + PITR="false" +fi + +if [ "$PITR" == "true" ]; then + if [ -f /root/.backuptime ]; then + BACKUP_TIME=$(cat /root/.backuptime) + else + echo "The /root/.backuptime file with BACKUP_TIME doesnt exist." + exit 1; + fi +else + if [ -f /root/.backupid ]; then + BACKUP_NAME=$(cat /root/.backupid) + else + echo "The /root/.backupid file with BACKUP_NAME doesnt exist." + exit 1; + fi +fi + +function find_closest_snapshot_id_before_time() { + local target_datetime="$1" + RESTIC_PASSWORD=env-9009578 restic -r /opt/backup/env-9009578 snapshots --tag "PITR" --json | jq -r '.[] | "\(.time) \(.short_id) \(.tags[0])"' | sort -r | while read snapshot_time snapshot_id snapshot_tag; do + snapshot_tag_date=$(echo "$snapshot_tag" | grep -oP '\d{4}-\d{2}-\d{2}_\d{6}') + snapshot_datetime=$(echo "$snapshot_tag_date" | sed 's/_/ /' | sed 's/\(....\)-\(..\)-\(..\) \(..\)\(..\)\(..\)/\1-\2-\3 \4:\5:\6/') + snapshot_datetime_epoch=$(date -d "$snapshot_datetime" +%s) + target_epoch=$(date -d "$target_datetime" +%s) + if [ "$snapshot_datetime_epoch" -le "$target_epoch" ]; then + result_snapshot_id=$snapshot_id + return 0 + fi + done + if [[ -z "$result_snapshot_id" ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID before time $target_datetime" | tee -a ${RESTORE_LOG_FILE} + exit 1 + else + echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID before time $target_datetime: $result_snapshot_id" >> ${RESTORE_LOG_FILE} + echo $result_snapshot_id; + fi +} + +function get_dump_snapshot_id_by_name(){ + local backup_name="$1" + local snapshot_id=$(RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains('$backup_name')) | select((.tags[1] | contains("BINLOGS") | not) // true) | .short_id') + if [[ $? -ne 0 || -z "$snapshot_id" ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} + echo $snapshot_id +} + +function get_dump_snapshot_id_by_time(){ + local backup_time="$1" + + +} + +function restore_snapshot_by_id(){ + local snapshot_id="$1" + RESTIC_PASSWORD=${ENV_NAME} GOGC=20 restic -r /opt/backup/${ENV_NAME} restore ${snapshot_id} --target / || { echo $(date) ${ENV_NAME} "Error: Failed to restore snapshot ID $snapshot_id" | tee -a ${RESTORE_LOG_FILE}; exit 1; } + echo $(date) ${ENV_NAME} "Snapshot ID: $snapshot_id restored successfully" >> ${RESTORE_LOG_FILE} +} + From a43fa02952420359c6487ffa28d850b3bca2e979 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 3 Oct 2024 14:36:57 +0200 Subject: [PATCH 34/78] Update restote-test.sh --- scripts/restote-test.sh | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/scripts/restote-test.sh b/scripts/restote-test.sh index 1b45387..33074a3 100644 --- a/scripts/restote-test.sh +++ b/scripts/restote-test.sh @@ -1,6 +1,5 @@ #!/bin/bash - DBUSER=$1 DBPASSWD=$2 RESTORE_LOG_FILE=$3 @@ -10,7 +9,6 @@ DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql - if [ -f /root/.backupedenv ]; then ENV_NAME=$(cat /root/.backupedenv) else @@ -61,7 +59,7 @@ function find_closest_snapshot_id_before_time() { function get_dump_snapshot_id_by_name(){ local backup_name="$1" - local snapshot_id=$(RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains('$backup_name')) | select((.tags[1] | contains("BINLOGS") | not) // true) | .short_id') + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains('$backup_name')) | select((.tags[1] | contains("BINLOGS") | not) // true) | .short_id') if [[ $? -ne 0 || -z "$snapshot_id" ]]; then echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID" | tee -a ${RESTORE_LOG_FILE} exit 1 @@ -70,15 +68,36 @@ function get_dump_snapshot_id_by_name(){ echo $snapshot_id } -function get_dump_snapshot_id_by_time(){ - local backup_time="$1" - +function get_binlog_snapshot_id_by_name(){ + local backup_name="$1" + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r '.[] | select(.tags[0] | contains('$backup_name')) | .short_id') + if [[ $? -ne 0 || -z "$snapshot_id" ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to get DB binlogs snapshot ID" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo $(date) ${ENV_NAME} "Getting DB binlogs snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} + echo $snapshot_id +} + +function get_snapshot_name_by_id(){ + local snapshot_id="$1" + local snapshot_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') + if [[ $? -ne 0 || -z "$dump_name" ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to get snapshot name for $snapshot_id" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo $(date) ${ENV_NAME} "Getting the snapshot name: ${snapshot_name}" >> ${BACKUP_LOG_FILE} + echo ${snapshot_name} } function restore_snapshot_by_id(){ local snapshot_id="$1" - RESTIC_PASSWORD=${ENV_NAME} GOGC=20 restic -r /opt/backup/${ENV_NAME} restore ${snapshot_id} --target / || { echo $(date) ${ENV_NAME} "Error: Failed to restore snapshot ID $snapshot_id" | tee -a ${RESTORE_LOG_FILE}; exit 1; } + RESTIC_PASSWORD=${ENV_NAME} GOGC=20 restic -r /opt/backup/${ENV_NAME} restore ${snapshot_id} --target / + if [[ $? -ne 0 ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to restore snapshot ID $snapshot_id" | tee -a ${RESTORE_LOG_FILE}; + exit 1 + fi echo $(date) ${ENV_NAME} "Snapshot ID: $snapshot_id restored successfully" >> ${RESTORE_LOG_FILE} } From 87fbf7cd02cab780ba10fe99ef88501ec016d6fb Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 3 Oct 2024 15:09:54 +0200 Subject: [PATCH 35/78] Update restote-test.sh --- scripts/restote-test.sh | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/scripts/restote-test.sh b/scripts/restote-test.sh index 33074a3..65822f6 100644 --- a/scripts/restote-test.sh +++ b/scripts/restote-test.sh @@ -22,7 +22,7 @@ fi if [ "$PITR" == "true" ]; then if [ -f /root/.backuptime ]; then - BACKUP_TIME=$(cat /root/.backuptime) + PITR_TIME=$(cat /root/.pitrtime) else echo "The /root/.backuptime file with BACKUP_TIME doesnt exist." exit 1; @@ -36,7 +36,7 @@ else fi fi -function find_closest_snapshot_id_before_time() { +function get_snapshot_id_before_time() { local target_datetime="$1" RESTIC_PASSWORD=env-9009578 restic -r /opt/backup/env-9009578 snapshots --tag "PITR" --json | jq -r '.[] | "\(.time) \(.short_id) \(.tags[0])"' | sort -r | while read snapshot_time snapshot_id snapshot_tag; do snapshot_tag_date=$(echo "$snapshot_tag" | grep -oP '\d{4}-\d{2}-\d{2}_\d{6}') @@ -51,10 +51,9 @@ function find_closest_snapshot_id_before_time() { if [[ -z "$result_snapshot_id" ]]; then echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID before time $target_datetime" | tee -a ${RESTORE_LOG_FILE} exit 1 - else - echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID before time $target_datetime: $result_snapshot_id" >> ${RESTORE_LOG_FILE} - echo $result_snapshot_id; fi + echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID before time $target_datetime: $result_snapshot_id" >> ${RESTORE_LOG_FILE} + echo $result_snapshot_id; } function get_dump_snapshot_id_by_name(){ @@ -71,7 +70,7 @@ function get_dump_snapshot_id_by_name(){ function get_binlog_snapshot_id_by_name(){ local backup_name="$1" local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r '.[] | select(.tags[0] | contains('$backup_name')) | .short_id') - if [[ $? -ne 0 || -z "$snapshot_id" ]]; then + if [[ $? -ne 0 ]]; then echo $(date) ${ENV_NAME} "Error: Failed to get DB binlogs snapshot ID" | tee -a ${RESTORE_LOG_FILE} exit 1 fi @@ -101,3 +100,17 @@ function restore_snapshot_by_id(){ echo $(date) ${ENV_NAME} "Snapshot ID: $snapshot_id restored successfully" >> ${RESTORE_LOG_FILE} } +function restore_mysql(){ + if [ "$PITR" == "true" ]; then + dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") + dump_snapshot_name=$(get_snapshot_name_by_id "${dump_snapshot_id}") + binlog_snapshot_id=$(get_binlog_snapshot_id_by_name "${dump_snapshot_name}") + restore_snapshot_by_id "${dump_snapshot_id}" + restore_snapshot_by_id "${binlog_snapshot_id}" + else + dump_snapshot_id=$(get_dump_snapshot_id_by_name "${BACKUP_NAME}") + restore_snapshot_by_id "${dump_snapshot_id}" + fi +} + +restore_mysql; From 9effaa544359898346d9eff34f066b14d369bb6c Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 3 Oct 2024 16:15:12 +0200 Subject: [PATCH 36/78] Update restote-test.sh --- scripts/restote-test.sh | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/scripts/restote-test.sh b/scripts/restote-test.sh index 65822f6..095dc62 100644 --- a/scripts/restote-test.sh +++ b/scripts/restote-test.sh @@ -22,7 +22,7 @@ fi if [ "$PITR" == "true" ]; then if [ -f /root/.backuptime ]; then - PITR_TIME=$(cat /root/.pitrtime) + PITR_TIME=$(cat /root/.backuptime) else echo "The /root/.backuptime file with BACKUP_TIME doesnt exist." exit 1; @@ -38,27 +38,31 @@ fi function get_snapshot_id_before_time() { local target_datetime="$1" - RESTIC_PASSWORD=env-9009578 restic -r /opt/backup/env-9009578 snapshots --tag "PITR" --json | jq -r '.[] | "\(.time) \(.short_id) \(.tags[0])"' | sort -r | while read snapshot_time snapshot_id snapshot_tag; do + + while read snapshot_time snapshot_id snapshot_tag; do snapshot_tag_date=$(echo "$snapshot_tag" | grep -oP '\d{4}-\d{2}-\d{2}_\d{6}') snapshot_datetime=$(echo "$snapshot_tag_date" | sed 's/_/ /' | sed 's/\(....\)-\(..\)-\(..\) \(..\)\(..\)\(..\)/\1-\2-\3 \4:\5:\6/') + snapshot_datetime_epoch=$(date -d "$snapshot_datetime" +%s) target_epoch=$(date -d "$target_datetime" +%s) + if [ "$snapshot_datetime_epoch" -le "$target_epoch" ]; then - result_snapshot_id=$snapshot_id - return 0 + result_snapshot_id="$snapshot_id" + break fi - done + done < <(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PITR" --json | jq -r '.[] | "\(.time) \(.short_id) \(.tags[0])"' | sort -r) + if [[ -z "$result_snapshot_id" ]]; then - echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID before time $target_datetime" | tee -a ${RESTORE_LOG_FILE} + echo "$(date) ${ENV_NAME} Error: Failed to get DB dump snapshot ID before time $target_datetime" | tee -a ${RESTORE_LOG_FILE} exit 1 fi - echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID before time $target_datetime: $result_snapshot_id" >> ${RESTORE_LOG_FILE} - echo $result_snapshot_id; + echo "$(date) ${ENV_NAME} Getting DB dump snapshot ID before time $target_datetime: $result_snapshot_id" >> ${RESTORE_LOG_FILE} + echo "$result_snapshot_id"; } function get_dump_snapshot_id_by_name(){ local backup_name="$1" - local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains('$backup_name')) | select((.tags[1] | contains("BINLOGS") | not) // true) | .short_id') + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains("'$backup_name'")) | select((.tags[1] != null and (.tags[1] | contains("BINLOGS")) | not) // true) | .short_id') if [[ $? -ne 0 || -z "$snapshot_id" ]]; then echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID" | tee -a ${RESTORE_LOG_FILE} exit 1 @@ -69,20 +73,21 @@ function get_dump_snapshot_id_by_name(){ function get_binlog_snapshot_id_by_name(){ local backup_name="$1" - local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r '.[] | select(.tags[0] | contains('$backup_name')) | .short_id') - if [[ $? -ne 0 ]]; then - echo $(date) ${ENV_NAME} "Error: Failed to get DB binlogs snapshot ID" | tee -a ${RESTORE_LOG_FILE} + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r --arg backup_name "$backup_name" '.[] | select(.tags[0] | contains($backup_name)) | .short_id') + + if [[ $? -ne 0 || -z "$snapshot_id" ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to get DB binlogs snapshot ID" | tee -a ${RESTORE_LOG_FILE} exit 1 fi - echo $(date) ${ENV_NAME} "Getting DB binlogs snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} - echo $snapshot_id -} + echo "$(date) ${ENV_NAME} Getting DB binlogs snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} + echo "$snapshot_id" +} function get_snapshot_name_by_id(){ local snapshot_id="$1" local snapshot_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') - if [[ $? -ne 0 || -z "$dump_name" ]]; then + if [[ $? -ne 0 || -z "${snapshot_name}" ]]; then echo $(date) ${ENV_NAME} "Error: Failed to get snapshot name for $snapshot_id" | tee -a ${RESTORE_LOG_FILE} exit 1 fi @@ -92,7 +97,7 @@ function get_snapshot_name_by_id(){ function restore_snapshot_by_id(){ local snapshot_id="$1" - RESTIC_PASSWORD=${ENV_NAME} GOGC=20 restic -r /opt/backup/${ENV_NAME} restore ${snapshot_id} --target / + RESTIC_PASSWORD=${ENV_NAME} GOGC=20 restic -r /opt/backup/${ENV_NAME} restore ${snapshot_id} --target / if [[ $? -ne 0 ]]; then echo $(date) ${ENV_NAME} "Error: Failed to restore snapshot ID $snapshot_id" | tee -a ${RESTORE_LOG_FILE}; exit 1 @@ -104,7 +109,9 @@ function restore_mysql(){ if [ "$PITR" == "true" ]; then dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") dump_snapshot_name=$(get_snapshot_name_by_id "${dump_snapshot_id}") + binlog_snapshot_id=$(get_binlog_snapshot_id_by_name "${dump_snapshot_name}") + restore_snapshot_by_id "${dump_snapshot_id}" restore_snapshot_by_id "${binlog_snapshot_id}" else From ff1fd1945ad5c51aacdc788329c101672df49b5e Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 4 Oct 2024 16:30:39 +0200 Subject: [PATCH 37/78] Update restote-test.sh --- scripts/restote-test.sh | 52 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/scripts/restote-test.sh b/scripts/restote-test.sh index 095dc62..b236784 100644 --- a/scripts/restote-test.sh +++ b/scripts/restote-test.sh @@ -9,6 +9,8 @@ DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql +#rm -rf $DUMP_BACKUP_DIR $BINLOGS_BACKUP_DIR + if [ -f /root/.backupedenv ]; then ENV_NAME=$(cat /root/.backupedenv) else @@ -91,7 +93,7 @@ function get_snapshot_name_by_id(){ echo $(date) ${ENV_NAME} "Error: Failed to get snapshot name for $snapshot_id" | tee -a ${RESTORE_LOG_FILE} exit 1 fi - echo $(date) ${ENV_NAME} "Getting the snapshot name: ${snapshot_name}" >> ${BACKUP_LOG_FILE} + echo $(date) ${ENV_NAME} "Getting the snapshot name: ${snapshot_name}" >> ${RESTORE_LOG_FILE} echo ${snapshot_name} } @@ -105,18 +107,56 @@ function restore_snapshot_by_id(){ echo $(date) ${ENV_NAME} "Snapshot ID: $snapshot_id restored successfully" >> ${RESTORE_LOG_FILE} } +function restore_mysql_dump(){ + if which mariadb 2>/dev/null; then + CLIENT_APP="mariadb" + else + CLIENT_APP="mysql" + fi + ${CLIENT_APP} -u "${DBUSER}" -p"${DBPASSWD}" < "${DUMP_BACKUP_DIR}/${SQL_DUMP_NAME}" + if [[ $? -ne 0 ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to restore MySQL dump" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo "$(date) ${ENV_NAME} MySQL dump restored successfully" | tee -a ${RESTORE_LOG_FILE} +} + +function apply_binlogs_until_time(){ + local stop_time="$1" + local binlog_files=($BINLOGS_BACKUP_DIR/mysql-bin.*) + + if which mariadb 2>/dev/null; then + BINLOG_APP="mariadb-binlog" + else + BINLOG_APP="mysqlbinlog" + fi + + for binlog_file in "${binlog_files[@]}"; do + ${BINLOG_APP} --stop-datetime="${stop_time}" "$binlog_file" | mysql -u "${DBUSER}" -p"${DBPASSWD}" + if [[ $? -ne 0 ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to apply binlogs from $binlog_file" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo "$(date) ${ENV_NAME} Applied binlogs from $binlog_file until $stop_time" >> ${RESTORE_LOG_FILE} + done +} + function restore_mysql(){ if [ "$PITR" == "true" ]; then - dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") - dump_snapshot_name=$(get_snapshot_name_by_id "${dump_snapshot_id}") +# dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") +# dump_snapshot_name=$(get_snapshot_name_by_id "${dump_snapshot_id}") - binlog_snapshot_id=$(get_binlog_snapshot_id_by_name "${dump_snapshot_name}") +# binlog_snapshot_id=$(get_binlog_snapshot_id_by_name "${dump_snapshot_name}") - restore_snapshot_by_id "${dump_snapshot_id}" - restore_snapshot_by_id "${binlog_snapshot_id}" +# restore_snapshot_by_id "${dump_snapshot_id}" + restore_mysql_dump + +# restore_snapshot_by_id "${binlog_snapshot_id}" + apply_binlogs_until_time "${PITR_TIME}" else dump_snapshot_id=$(get_dump_snapshot_id_by_name "${BACKUP_NAME}") restore_snapshot_by_id "${dump_snapshot_id}" + restore_mysql_dump fi } From a20fcaa0d0c29ea43737d71b4b7ab98aeded56a3 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 4 Oct 2024 16:39:58 +0200 Subject: [PATCH 38/78] JE-71293 --- scripts/pitr.sh | 81 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index b48b215..51385d3 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -4,9 +4,13 @@ ACTION=$1 DBUSER=$2 DBPASSWD=$3 -PITR_CONF='/etc/mysql/conf.d/pitr.cnf' +PITR_CONF_MYSQL='/etc/mysql/conf.d/pitr.cnf' +PITR_CONF_PG='/etc/postgresql/12/main/postgresql.conf' +ARCHIVE_DIR_PG='/var/lib/postgresql/wal_archive' +BACKUP_DIR_PG='/var/lib/postgresql/backups' source /etc/jelastic/metainf.conf + COMPUTE_TYPE_FULL_VERSION_FORMATTED=$(echo "$COMPUTE_TYPE_FULL_VERSION" | sed 's/\.//') if [[ ("$COMPUTE_TYPE" == "mysql" || "$COMPUTE_TYPE" == "percona") && "$COMPUTE_TYPE_FULL_VERSION_FORMATTED" -ge "81" ]]; then BINLOG_EXPIRE_SETTING="binlog_expire_logs_seconds" @@ -15,10 +19,17 @@ elif [[ "$COMPUTE_TYPE" == "mariadb" ]]; then BINLOG_EXPIRE_SETTING="expire_logs_days" EXPIRY_SETTING="7" else - echo '{"result":99}'; + BINLOG_EXPIRE_SETTING="" + EXPIRY_SETTING="" fi - -check_pitr() { + +WAL_ARCHIVE_SETTING="archive_mode" +WAL_ARCHIVE_COMMAND="archive_command" +WAL_TIMEOUT_SETTING="archive_timeout" +WAL_TIMEOUT_VALUE="60" +WAL_ARCHIVE_ON="on" + +check_pitr_mysql() { LOG_BIN=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE 'log_bin';" | grep "ON") EXPIRE_LOGS=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE '$BINLOG_EXPIRE_SETTING';" | awk '{ print $2 }') if [[ -n "$LOG_BIN" && "$EXPIRE_LOGS" == "$EXPIRY_SETTING" ]]; then @@ -29,8 +40,8 @@ check_pitr() { fi } -setup_pitr() { - check_pitr | grep -q '"result":0' +setup_pitr_mysql() { + check_pitr_mysql | grep -q '"result":0' if [[ $? -eq 0 ]]; then exit 0; fi @@ -40,15 +51,67 @@ setup_pitr() { log-bin=mysql-bin $BINLOG_EXPIRE_SETTING=$EXPIRY_SETTING " - echo "$CONFIG" > "$PITR_CONF" + echo "$CONFIG" > "$PITR_CONF_MYSQL" + jem service restart; +} + +check_pitr_pg() { + ARCHIVE_MODE=$(sudo -u postgres psql -U "$DBUSER" -c "SHOW $WAL_ARCHIVE_SETTING;" | grep "on") + ARCHIVE_COMMAND=$(sudo -u postgres psql -U "$DBUSER" -c "SHOW $WAL_ARCHIVE_COMMAND;" | grep "$ARCHIVE_DIR_PG") + + if [[ -n "$ARCHIVE_MODE" && -n "$ARCHIVE_COMMAND" ]]; then + echo '{"result":0}'; + return 0; + else + echo '{"result":702}'; + fi +} + +setup_pitr_pg() { + check_pitr_pg | grep -q '"result":0' + if [[ $? -eq 0 ]]; then + exit 0; + fi + + CONFIG=" +# PITR Configuration +archive_mode = on +archive_command = 'test ! -f $ARCHIVE_DIR_PG/%f && cp %p $ARCHIVE_DIR_PG/%f' +archive_timeout = $WAL_TIMEOUT_VALUE +" + + echo "$CONFIG" >> "$PITR_CONF_PG" + + if [ ! -d "$ARCHIVE_DIR_PG" ]; then + sudo mkdir -p "$ARCHIVE_DIR_PG" + sudo chown -R postgres:postgres "$ARCHIVE_DIR_PG" + fi + jem service restart; + + echo '{"result":0}'; } case $ACTION in checkPitr) - check_pitr + if [[ "$COMPUTE_TYPE" == "mysql" || "$COMPUTE_TYPE" == "percona" || "$COMPUTE_TYPE" == "mariadb" ]]; then + check_pitr_mysql + elif [[ "$COMPUTE_TYPE" == "postgresql" ]]; then + check_pitr_pg + else + echo '{"result":99}'; + fi ;; setupPitr) - setup_pitr + if [[ "$COMPUTE_TYPE" == "mysql" || "$COMPUTE_TYPE" == "percona" || "$COMPUTE_TYPE" == "mariadb" ]]; then + setup_pitr_mysql + elif [[ "$COMPUTE_TYPE" == "postgresql" ]]; then + setup_pitr_pg + else + echo '{"result":99}'; + fi + ;; + *) + echo "checkPitr or setupPitr." ;; esac From bf7a489d1e203310001d6e6527403f481770c7c0 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 10 Oct 2024 14:03:08 +0200 Subject: [PATCH 39/78] JE-71293 --- scripts/backup-logic.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index be5344f..76f3e30 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -22,7 +22,7 @@ DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql -##rm -rf $DUMP_BACKUP_DIR && mkdir -p $DUMP_BACKUP_DIR +rm -rf $DUMP_BACKUP_DIR && mkdir -p $DUMP_BACKUP_DIR if [ -z "$PITR" ]; then @@ -178,11 +178,6 @@ function get_binlog_file_by_snapshot_id(){ echo ${binlog_file} } -function create_binlog_snapshot(){ - echo - -} - function backup_redis(){ source /etc/jelastic/metainf.conf; From 2c22f14eddf5f07ffbefc3f5daabd011513588d8 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Thu, 10 Oct 2024 15:07:06 +0200 Subject: [PATCH 40/78] JE-71293 --- scripts/restore-logic.sh | 192 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 scripts/restore-logic.sh diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh new file mode 100644 index 0000000..075d3d6 --- /dev/null +++ b/scripts/restore-logic.sh @@ -0,0 +1,192 @@ +#!/bin/bash + +DBUSER=$1 +DBPASSWD=$2 +RESTORE_LOG_FILE=$3 +PITR=$4 + +DUMP_BACKUP_DIR=/root/backup/dump +BINLOGS_BACKUP_DIR=/root/backup/binlogs +SQL_DUMP_NAME=db_backup.sql + +#rm -rf $DUMP_BACKUP_DIR $BINLOGS_BACKUP_DIR + +if [ -f /root/.backupedenv ]; then + ENV_NAME=$(cat /root/.backupedenv) +else + echo "The /root/.backupedenv file with ENV_NAME doesnt exist." + exit 1; +fi + +if [ -z "$PITR" ]; then + PITR="false" +fi + +if [ "$PITR" == "true" ]; then + if [ -f /root/.backuptime ]; then + PITR_TIME=$(cat /root/.backuptime) + else + echo "The /root/.backuptime file with BACKUP_TIME doesnt exist." + exit 1; + fi +else + if [ -f /root/.backupid ]; then + BACKUP_NAME=$(cat /root/.backupid) + else + echo "The /root/.backupid file with BACKUP_NAME doesnt exist." + exit 1; + fi +fi + +function get_snapshot_id_before_time() { + local target_datetime="$1" + + while read snapshot_time snapshot_id snapshot_tag; do + snapshot_tag_date=$(echo "$snapshot_tag" | grep -oP '\d{4}-\d{2}-\d{2}_\d{6}') + snapshot_datetime=$(echo "$snapshot_tag_date" | sed 's/_/ /' | sed 's/\(....\)-\(..\)-\(..\) \(..\)\(..\)\(..\)/\1-\2-\3 \4:\5:\6/') + + snapshot_datetime_epoch=$(date -d "$snapshot_datetime" +%s) + target_epoch=$(date -d "$target_datetime" +%s) + + if [ "$snapshot_datetime_epoch" -le "$target_epoch" ]; then + result_snapshot_id="$snapshot_id" + break + fi + done < <(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PITR" --json | jq -r '.[] | "\(.time) \(.short_id) \(.tags[0])"' | sort -r) + + if [[ -z "$result_snapshot_id" ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to get DB dump snapshot ID before time $target_datetime" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo "$(date) ${ENV_NAME} Getting DB dump snapshot ID before time $target_datetime: $result_snapshot_id" >> ${RESTORE_LOG_FILE} + echo "$result_snapshot_id"; +} + +function get_dump_snapshot_id_by_name(){ + local backup_name="$1" + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains("'$backup_name'")) | select((.tags[1] != null and (.tags[1] | contains("BINLOGS")) | not) // true) | .short_id') + if [[ $? -ne 0 || -z "$snapshot_id" ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} + echo $snapshot_id +} + +function get_binlog_snapshot_id_by_name(){ + local backup_name="$1" + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r --arg backup_name "$backup_name" '.[] | select(.tags[0] | contains($backup_name)) | .short_id') + + if [[ $? -ne 0 || -z "$snapshot_id" ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to get DB binlogs snapshot ID" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + + echo "$(date) ${ENV_NAME} Getting DB binlogs snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} + echo "$snapshot_id" +} + +function get_snapshot_name_by_id(){ + local snapshot_id="$1" + local snapshot_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') + if [[ $? -ne 0 || -z "${snapshot_name}" ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to get snapshot name for $snapshot_id" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo $(date) ${ENV_NAME} "Getting the snapshot name: ${snapshot_name}" >> ${RESTORE_LOG_FILE} + echo ${snapshot_name} +} + +function restore_snapshot_by_id(){ + local snapshot_id="$1" + RESTIC_PASSWORD=${ENV_NAME} GOGC=20 restic -r /opt/backup/${ENV_NAME} restore ${snapshot_id} --target / + if [[ $? -ne 0 ]]; then + echo $(date) ${ENV_NAME} "Error: Failed to restore snapshot ID $snapshot_id" | tee -a ${RESTORE_LOG_FILE}; + exit 1 + fi + echo $(date) ${ENV_NAME} "Snapshot ID: $snapshot_id restored successfully" >> ${RESTORE_LOG_FILE} +} + +function restore_mysql_dump(){ + if which mariadb 2>/dev/null; then + CLIENT_APP="mariadb" + else + CLIENT_APP="mysql" + fi + ${CLIENT_APP} -u "${DBUSER}" -p"${DBPASSWD}" < "${DUMP_BACKUP_DIR}/${SQL_DUMP_NAME}" + if [[ $? -ne 0 ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to restore MySQL dump" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo "$(date) ${ENV_NAME} MySQL dump restored successfully" | tee -a ${RESTORE_LOG_FILE} +} + +function apply_binlogs_until_time(){ + local stop_time="$1" + local binlog_files=($BINLOGS_BACKUP_DIR/mysql-bin.*) + + if which mariadb 2>/dev/null; then + BINLOG_APP="mariadb-binlog" + else + BINLOG_APP="mysqlbinlog" + fi + + for binlog_file in "${binlog_files[@]}"; do + ${BINLOG_APP} --stop-datetime="${stop_time}" "$binlog_file" | mysql -u "${DBUSER}" -p"${DBPASSWD}" + if [[ $? -ne 0 ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to apply binlogs from $binlog_file" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + echo "$(date) ${ENV_NAME} Applied binlogs from $binlog_file until $stop_time" >> ${RESTORE_LOG_FILE} + done +} + +function restore_mysql(){ + if [ "$PITR" == "true" ]; then + dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") + dump_snapshot_name=$(get_snapshot_name_by_id "${dump_snapshot_id}") + + binlog_snapshot_id=$(get_binlog_snapshot_id_by_name "${dump_snapshot_name}") + + restore_snapshot_by_id "${dump_snapshot_id}" + restore_mysql_dump + + restore_snapshot_by_id "${binlog_snapshot_id}" + apply_binlogs_until_time "${PITR_TIME}" + else + dump_snapshot_id=$(get_dump_snapshot_id_by_name "${BACKUP_NAME}") + restore_snapshot_by_id "${dump_snapshot_id}" + restore_mysql_dump + fi +} + +function restore(){ + echo $$ > /var/run/${ENV_NAME}_restore.pid + source /etc/jelastic/metainf.conf; + echo $(date) ${ENV_NAME} "Restoring the DB dump" | tee -a ${RESTORE_LOG_FILE} + if [ "$COMPUTE_TYPE" == "redis" ]; then + restore_redis; + + elif [ "$COMPUTE_TYPE" == "mongodb" ]; then + restore_mongodb; + + elif [ "$COMPUTE_TYPE" == "postgres" ]; then + restore_postgres; + + else + restore_mysql; + + fi + rm -f /var/run/${ENV_NAME}_restore.pid +} + +case "$1" in + restore) + $1 + ;; + *) + echo "Usage: $0 restore}" + exit 2 +esac + +exit $? From 0c84bd5d6a974211847454a2c47617776d353b06 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 10:24:08 +0200 Subject: [PATCH 41/78] JE-71293 --- scripts/restore-logic.sh | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index 075d3d6..f252c6f 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -141,6 +141,51 @@ function apply_binlogs_until_time(){ done } +function restore_mongodb(){ + dump_snapshot_id=$(get_dump_snapshot_id_by_name "${BACKUP_NAME}") + restore_snapshot_by_id "${dump_snapshot_id}" + if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then + export RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf|awk '{print $2}'); + export RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest"; + else + export RS_SUFFIX=""; + fi + TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) + if [ "$TLS_MODE" == "requireTLS" ]; then + SSL_TLS_OPTIONS="--ssl --sslPEMKeyFile=/var/lib/jelastic/keys/SSL-TLS/client/client.pem --sslCAFile=/var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsInsecure" + else + SSL_TLS_OPTIONS="" + fi + mongorestore ${SSL_TLS_OPTIONS} --uri="mongodb://${1}:${2}@localhost${RS_SUFFIX}" ${DUMP_BACKUP_DIR} 1>/dev/null + +} + +function restore_redis(){ + REDIS_CONF_PATH=$(realpath /etc/redis.conf) + RDB_TO_RESTORE=$(ls -d /tmp/* |grep redis-dump.*); + + dump_snapshot_id=$(get_dump_snapshot_id_by_name "${BACKUP_NAME}") + restore_snapshot_by_id "${dump_snapshot_id}" + + cd tmp; wget https://github.com/tair-opensource/RedisShake/releases/download/v3.1.11/redis-shake-linux-amd64.tar.gz; + tar -xf redis-shake-linux-amd64.tar.gz; + grep -q '^cluster-enabled yes' ${REDIS_CONF_PATH} && REDIS_TYPE="cluster" || REDIS_TYPE="standalone"; + sed -ci -e "s/^type =.*/type = '${REDIS_TYPE}'/" restore.toml; + sed -ci -e "1s/^type =.*/type = 'restore'/" restore.toml; + export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} |grep '^requirepass'|awk '{print $2}'); + sed -ci -e "s/^password =.*/password = '${REDISCLI_AUTH}'/" restore.toml; + RESTORE_MASTER_ID=$(redis-cli cluster nodes|grep master|grep -v fail|head -n 1|awk '{print $2}'|awk -F : '{print $1}') + sed -ci -e "s/^address =.*/address = '${RESTORE_MASTER_ID}:6379'/" restore.toml; + for i in ${RDB_TO_RESTORE} + do + sed -ci -e "s|^rdb_file_path =.*|rdb_file_path = '${i}'|" restore.toml; + ./redis-shake restore.toml 1>/dev/null + done + rm -f ${RDB_TO_RESTORE} + rm -f redis-shake* sync.toml restore.toml +} + + function restore_mysql(){ if [ "$PITR" == "true" ]; then dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") From d5bf93a4b442c436038e2cecb82dd51cedc84862 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 10:36:31 +0200 Subject: [PATCH 42/78] JE-71293 --- scripts/backup-logic.sh | 60 ++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 76f3e30..b5ec194 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -1,16 +1,16 @@ #!/bin/bash -BASE_URL=$2 -BACKUP_TYPE=$3 -NODE_ID=$4 -BACKUP_LOG_FILE=$5 -ENV_NAME=$6 -BACKUP_COUNT=$7 -DBUSER=$8 -DBPASSWD=$9 -USER_SESSION=${10} -USER_EMAIL=${11} -PITR=${12} +BASE_URL=$1 +BACKUP_TYPE=$2 +NODE_ID=$3 +BACKUP_LOG_FILE=$4 +ENV_NAME=$5 +BACKUP_COUNT=$6 +DBUSER=$7 +DBPASSWD=$8 +USER_SESSION=$9 +USER_EMAIL=${10} +PITR=${11} 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}') @@ -23,7 +23,7 @@ BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql rm -rf $DUMP_BACKUP_DIR && mkdir -p $DUMP_BACKUP_DIR - +rm -rf $BINLOGS_BACKUP_DIR && mkdir -p $BINLOGS_BACKUP_DIR if [ -z "$PITR" ]; then PITR="false" @@ -147,7 +147,7 @@ function create_snapshot(){ RDB_TO_BACKUP=$(ls -d /tmp/* |grep redis-dump.*); GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${RDB_TO_BACKUP} | tee -a ${BACKUP_LOG_FILE}; elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ~/dump | tee -a ${BACKUP_LOG_FILE} + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} else if [ "$PITR" == "true" ]; then GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" --tag "PITR" --tag "$(get_binlog_file)" --tag "$(get_binlog_position)" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} @@ -213,7 +213,7 @@ function backup_mongodb(){ else SSL_TLS_OPTIONS="" fi - mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" + mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" --out="${DUMP_BACKUP_DIR}" } function backup_mysql_dump(){ @@ -285,29 +285,9 @@ function backup(){ rm -f /var/run/${ENV_NAME}_backup.pid } - -case "$1" in - backup) - $1 - ;; - pitr_backup_mysql) - $1 - ;; - check_backup_repo) - $1 - ;; - rotate_snapshots) - $1 - ;; - create_snapshot) - $1 - ;; - update_restic) - $1 - ;; - *) - echo "Usage: $0 {backup|check_backup_repo|rotate_snapshots|create_snapshot|update_restic}" - exit 2 -esac - -exit $? +check_backup_repo +rotate_snapshots +backup +create_snapshot +rotate_snapshots +check_backup_repo From 619988f999c8d295ca4f9b6630b3bf02516978bc Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 10:37:57 +0200 Subject: [PATCH 43/78] JE-71293 --- scripts/backup-manager.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index 2f9e12b..46e2bd9 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -151,22 +151,7 @@ function BackupManager(config) { 'bash /root/%(envName)_backup-logic.sh update_restic %(baseUrl)' ], backupCallParams ], [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' - ], backupCallParams ], - [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' - ], backupCallParams ], - [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh backup %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' - ], backupCallParams ], - [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh create_snapshot %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' - ], backupCallParams ], - [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh rotate_snapshots %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' - ], backupCallParams ], - [ me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh check_backup_repo %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' + 'bash /root/%(envName)_backup-logic.sh %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' ], backupCallParams ], [ me.removeMounts, config.isAlwaysUmount ] ]); From 3c20a2e441714583a9e0bff6a2902e5eb93b1f52 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 10:45:15 +0200 Subject: [PATCH 44/78] JE-71293 --- scripts/backup-logic.sh | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index b5ec194..b7ee4d1 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -16,8 +16,6 @@ BACKUP_ADDON_REPO=$(echo ${BASE_URL}|sed 's|https:\/\/raw.githubusercontent.com\ 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}') - - DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql @@ -263,31 +261,27 @@ function backup_mysql(){ create_snapshot; } +### Main section +echo $$ > /var/run/${ENV_NAME}_backup.pid +echo $(date) ${ENV_NAME} "Creating the ${BACKUP_TYPE} backup (using the backup addon with commit id ${BACKUP_ADDON_COMMIT_ID}) on storage node ${NODE_ID}" | tee -a ${BACKUP_LOG_FILE} +check_backup_repo +rotate_snapshots +source /etc/jelastic/metainf.conf; +echo $(date) ${ENV_NAME} "Creating the DB dump" | tee -a ${BACKUP_LOG_FILE} +if [ "$COMPUTE_TYPE" == "redis" ]; then + backup_redis; -function backup(){ - echo $$ > /var/run/${ENV_NAME}_backup.pid - echo $(date) ${ENV_NAME} "Creating the ${BACKUP_TYPE} backup (using the backup addon with commit id ${BACKUP_ADDON_COMMIT_ID}) on storage node ${NODE_ID}" | tee -a ${BACKUP_LOG_FILE} - source /etc/jelastic/metainf.conf; - echo $(date) ${ENV_NAME} "Creating the DB dump" | tee -a ${BACKUP_LOG_FILE} - if [ "$COMPUTE_TYPE" == "redis" ]; then - backup_redis; - - elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - backup_mongodb; +elif [ "$COMPUTE_TYPE" == "mongodb" ]; then + backup_mongodb; - elif [ "$COMPUTE_TYPE" == "postgres" ]; then - backup_postgres; - - else - backup_mysql; +elif [ "$COMPUTE_TYPE" == "postgres" ]; then + backup_postgres; - fi - rm -f /var/run/${ENV_NAME}_backup.pid -} +else + backup_mysql; -check_backup_repo -rotate_snapshots -backup +fi create_snapshot rotate_snapshots check_backup_repo +rm -f /var/run/${ENV_NAME}_backup.pid From f8dcaa2e52c3bcdb48250c847c0d76e487708537 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 11:47:04 +0200 Subject: [PATCH 45/78] JE-71293 --- scripts/backup-manager.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index 46e2bd9..4763ba5 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -141,14 +141,16 @@ function BackupManager(config) { [ me.addMountForBackup, config.isAlwaysUmount ], [ 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' + 'wget -O /root/%(envName)_backup-logic.sh %(baseUrl)/scripts/backup-logic.sh', + '[ -f /root/installUpdateRestic ] && rm -f /root/installUpdateRestic || true', + 'wget -O /root/installUpdateRestic %(baseUrl)/scripts/installUpdateRestic' ], { nodeId : config.backupExecNode, envName : config.envName, baseUrl : config.baseUrl }], [me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh update_restic %(baseUrl)' + 'bash /root/installUpdateRestic' ], backupCallParams ], [ me.cmd, [ 'bash /root/%(envName)_backup-logic.sh %(baseUrl) %(backupType) %(nodeId) %(backupLogFile) %(envName) %(backupCount) %(dbuser) %(dbpass) %(session) %(email) %(isPitr)' @@ -167,14 +169,16 @@ function BackupManager(config) { [ me.addMountForRestore, config.isAlwaysUmount ], [ 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' + 'wget -O /root/%(envName)_backup-logic.sh %(baseUrl)/scripts/backup-logic.sh', + '[ -f /root/installUpdateRestic ] && rm -f /root/installUpdateRestic || true', + 'wget -O /root/installUpdateRestic %(baseUrl)/scripts/installUpdateRestic' ], { nodeId : config.backupExecNode, envName : config.envName, baseUrl : config.baseUrl }], [me.cmd, [ - 'bash /root/%(envName)_backup-logic.sh update_restic %(baseUrl)' + 'bash /root/installUpdateRestic' ], { nodeId : config.backupExecNode, envName : config.envName, From ad18fc4d07e02e8f438e6c253c4ccdf00f37f0a9 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 11:53:32 +0200 Subject: [PATCH 46/78] JE-71293 --- scripts/backup-manager.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index 4763ba5..a01a0bb 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -168,8 +168,8 @@ function BackupManager(config) { [ me.removeMounts, config.isAlwaysUmount], [ me.addMountForRestore, config.isAlwaysUmount ], [ 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', + '[ -f /root/%(envName)_restore-logic.sh ] && rm -f /root/%(envName)_restore-logic.sh || true', + 'wget -O /root/%(envName)_restore-logic.sh %(baseUrl)/scripts/restore-logic.sh', '[ -f /root/installUpdateRestic ] && rm -f /root/installUpdateRestic || true', 'wget -O /root/installUpdateRestic %(baseUrl)/scripts/installUpdateRestic' ], { @@ -185,15 +185,7 @@ function BackupManager(config) { baseUrl : config.baseUrl }], [ me.cmd, [ - 'SNAPSHOT_ID=$(RESTIC_PASSWORD=$(cat /root/.backupedenv) restic -r /opt/backup/$(cat /root/.backupedenv) snapshots|grep $(cat /root/.backupid)|awk \'{print $1}\')', - '[ -n "${SNAPSHOT_ID}" ] || false', - 'source /etc/jelastic/metainf.conf', - 'RESTIC_PASSWORD=$(cat /root/.backupedenv) GOGC=20 restic -r /opt/backup/$(cat /root/.backupedenv) restore ${SNAPSHOT_ID} --target /', - 'if [ "$COMPUTE_TYPE" == "redis" ]; then rm -f /root/redis-restore.sh; wget -O /root/redis-restore.sh %(baseUrl)/scripts/redis-restore.sh; chmod +x /root/redis-restore.sh; bash /root/redis-restore.sh 2> >(tee -a %(restoreLogFile) >&2); else true; fi', - 'if [ "$COMPUTE_TYPE" == "postgres" ]; then rm -f /root/postgres-restore.sh; wget -O /root/postgres-restore.sh %(baseUrl)/scripts/postgres-restore.sh; chmod +x /root/postgres-restore.sh; bash /root/postgres-restore.sh %(dbuser) %(dbpass) 2> >(tee -a %(restoreLogFile) >&2); else true; fi', - 'if [ "$COMPUTE_TYPE" == "mariadb" ]; then rm -f /root/mariadb-restore.sh; wget -O /root/mariadb-restore.sh %(baseUrl)/scripts/mariadb-restore.sh; chmod +x /root/mariadb-restore.sh; bash /root/mariadb-restore.sh %(dbuser) %(dbpass) 2> >(tee -a %(restoreLogFile) >&2); else true; fi', - 'if [ "$COMPUTE_TYPE" == "mysql" ] || [ "$COMPUTE_TYPE" == "percona" ]; then mysql --silent -h localhost -u %(dbuser) -p%(dbpass) --force < /root/db_backup.sql 2> >(tee -a %(restoreLogFile) >&2); else true; fi', - 'if [ "$COMPUTE_TYPE" == "mongodb" ]; then rm -f /root/mongo-restore.sh; wget -O /root/mongo-restore.sh %(baseUrl)/scripts/mongo-restore.sh; chmod +x /root/mongo-restore.sh; bash /root/mongo-restore.sh %(dbuser) %(dbpass) 2> >(tee -a %(restoreLogFile) >&2); else true; fi', + 'bash /root/%(envName)_restore-logic.sh %(dbuser) %(dbpass) %(restoreLogFile) %(isPitr)' 'jem service restart', 'if [ -n "$REPLICA_PSWD" ] && [ -n "$REPLICA_USER" ] ; then wget %(baseUrl)/scripts/setupUser.sh -O /root/setupUser.sh &>> /var/log/run.log; bash /root/setupUser.sh ${REPLICA_USER} ${REPLICA_PSWD} %(userEmail) %(envName) %(userSession); fi', 'echo $(date) %(envName) snapshot $(cat /root/.backupid) restored successfully| tee -a %(restoreLogFile)' From 36bc385d7eda7a6e90d829e801771a3cfff30b72 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 13:20:16 +0200 Subject: [PATCH 47/78] JE-71293 --- scripts/backup-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index a01a0bb..1fbaf8d 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -185,7 +185,7 @@ function BackupManager(config) { baseUrl : config.baseUrl }], [ me.cmd, [ - 'bash /root/%(envName)_restore-logic.sh %(dbuser) %(dbpass) %(restoreLogFile) %(isPitr)' + 'bash /root/%(envName)_restore-logic.sh %(dbuser) %(dbpass) %(restoreLogFile) %(isPitr)', 'jem service restart', 'if [ -n "$REPLICA_PSWD" ] && [ -n "$REPLICA_USER" ] ; then wget %(baseUrl)/scripts/setupUser.sh -O /root/setupUser.sh &>> /var/log/run.log; bash /root/setupUser.sh ${REPLICA_USER} ${REPLICA_PSWD} %(userEmail) %(envName) %(userSession); fi', 'echo $(date) %(envName) snapshot $(cat /root/.backupid) restored successfully| tee -a %(restoreLogFile)' From a0cc6277ee53e9d95a66a3c95e25473c1508b092 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Fri, 11 Oct 2024 14:13:46 +0200 Subject: [PATCH 48/78] JE-71293 --- scripts/backup-logic.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index b7ee4d1..f7997eb 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -258,7 +258,6 @@ function backup_mysql(){ create_binlog_snapshot "${dump_name}" fi fi - create_snapshot; } ### Main section @@ -281,7 +280,7 @@ else backup_mysql; fi -create_snapshot -rotate_snapshots -check_backup_repo +create_snapshot; +rotate_snapshots; +check_backup_repo; rm -f /var/run/${ENV_NAME}_backup.pid From ee9aa730317df8e0dff3a10906f35388e4698ec7 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 15 Oct 2024 14:32:21 +0200 Subject: [PATCH 49/78] JE-71293 --- test.jps | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-) diff --git a/test.jps b/test.jps index 299f3a8..6c28d8d 100644 --- a/test.jps +++ b/test.jps @@ -186,7 +186,167 @@ settings: restore: fields: [] - onBeforeInit: scripts/restoreOnBeforeInit.js + onBeforeInit: | + import org.json.JSONObject; + var storage_unavailable_markup = ""; + var storageInfo = getStorageNodeid(); + var storageEnvDomain = storageInfo.storageEnvShortName; + var storageEnvMasterId = storageInfo.storageNodeId; + var checkSchemaCommand = "if grep -q '^SCHEME=' /.jelenv; then echo true; else echo false; fi"; + var mysql_cluster_markup = "Be careful when restoring the dump from another DB environment (or environment with another replication schema) to the replicated MySQL/MariaDB/Percona solution."; + var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; + + resp = jelastic.env.control.GetEnvInfo(storageEnvDomain, session); + if (resp.result != 0 && resp.result != 11) return resp; + if (resp.result == 11) { + storage_unavailable_markup = "Storage environment " + "${settings.storageName}" + " is deleted."; + } else if (resp.env.status == 1) { + var baseUrl = jps.baseUrl; + var updateResticOnStorageCommand = "wget --tries=10 -O /tmp/installUpdateRestic " + baseUrl + "/scripts/installUpdateRestic && mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic"; + var respUpdate = api.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": updateResticOnStorageCommand, "params": ""}]), false, "root"); + if (respUpdate.result != 0) return respUpdate; + var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": "/root/getBackupsAllEnvs.sh", "params": ""}]), false, "root").responses[0].out; + var backupList = toNative(new JSONObject(String(backups))); + var envs = prepareEnvs(backupList.envs); + var backups = prepareBackups(backupList.backups); + } else { + storage_unavailable_markup = "Storage environment " + storageEnvDomain + " is unavailable (stopped/sleeping)."; + } + + var checkSchema = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": checkSchemaCommand, "params": ""}]), false, "root"); + if (checkSchema.result != 0) return checkSchema; + + function getStorageNodeid(){ + var storageEnv = '${settings.storageName}' + var storageEnvShortName = storageEnv.split(".")[0] + var resp = jelastic.environment.control.GetEnvInfo(storageEnvShortName, session) + if (resp.result != 0) return resp + for (var i = 0; resp.nodes; i++) { + var node = resp.nodes[i] + if (node.nodeGroup == 'storage' && node.ismaster) { + return { result: 0, storageNodeId: node.id, storageEnvShortName: storageEnvShortName }; + } + } + } + + function prepareEnvs(values) { + var aResultValues = []; + + values = values || []; + + for (var i = 0, n = values.length; i < n; i++) { + aResultValues.push({ caption: values[i], value: values[i] }); + } + + return aResultValues; + } + + function prepareBackups(backups) { + var oResultBackups = {}; + var aValues; + + for (var envName in backups) { + if (Object.prototype.hasOwnProperty.call(backups, envName)) { + aValues = []; + + for (var i = 0, n = backups[envName].length; i < n; i++) { + aValues.push({ caption: backups[envName][i], value: backups[envName][i] }); + } + + oResultBackups[envName] = aValues; + } + } + + return oResultBackups; + } + + if (storage_unavailable_markup === "") { + if ('${settings.isPitr}' == 'true') { + settings.fields.push({ + "type": "toggle", + "name": "isPitr", + "caption": "PITR", + "tooltip": "Point in time recovery", + "value": true, + "hidden": false, + "showIf": { + "true": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Time for restore", + "type": "string", + "name": "restoreTime", + "inputType": "datetime-local", + "cls": "x-form-text", + "required": true + } + ], + "false": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Backup", + "type": "list", + "name": "backupDir", + "required": true, + "tooltip": "Select the time stamp for which you want to restore the DB dump", + "dependsOn": { + "backupedEnvName" : backups + } + } + ] + } + }); + if (checkSchema.responses[0].out == "true") { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} + ); + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} + ); + } + } else { + settings.fields.push({ + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Backup", + "type": "list", + "name": "backupDir", + "required": true, + "tooltip": "Select the time stamp for which you want to restore the DB dump", + "dependsOn": { + "backupedEnvName" : backups + } + }); + if (checkSchema.responses[0].out == "true") { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} + ); + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} + ); + } + } + } else { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": storage_unavailable_markup} + ) + } + + return settings; pitr: fields: [] From ce0e40d913de2e1210ab63112521b7a83d42ff5d Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 15 Oct 2024 15:52:23 +0200 Subject: [PATCH 50/78] JE-71293 --- scripts/restore-logic.sh | 41 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index f252c6f..b6dd55c 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -205,33 +205,22 @@ function restore_mysql(){ fi } -function restore(){ - echo $$ > /var/run/${ENV_NAME}_restore.pid - source /etc/jelastic/metainf.conf; - echo $(date) ${ENV_NAME} "Restoring the DB dump" | tee -a ${RESTORE_LOG_FILE} - if [ "$COMPUTE_TYPE" == "redis" ]; then - restore_redis; - - elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - restore_mongodb; - - elif [ "$COMPUTE_TYPE" == "postgres" ]; then - restore_postgres; +### Main block - else - restore_mysql; +echo $$ > /var/run/${ENV_NAME}_restore.pid +source /etc/jelastic/metainf.conf; +echo $(date) ${ENV_NAME} "Restoring the DB dump" | tee -a ${RESTORE_LOG_FILE} +if [ "$COMPUTE_TYPE" == "redis" ]; then + restore_redis; - fi - rm -f /var/run/${ENV_NAME}_restore.pid -} +elif [ "$COMPUTE_TYPE" == "mongodb" ]; then + restore_mongodb; + +elif [ "$COMPUTE_TYPE" == "postgres" ]; then + restore_postgres; -case "$1" in - restore) - $1 - ;; - *) - echo "Usage: $0 restore}" - exit 2 -esac +else + restore_mysql; -exit $? +fi +rm -f /var/run/${ENV_NAME}_restore.pid From 912506118bb4101e5fc8ff7c680157bb46d2997f Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 15 Oct 2024 16:36:54 +0200 Subject: [PATCH 51/78] JE-71293 --- test.jps | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/test.jps b/test.jps index 6c28d8d..a26c36b 100644 --- a/test.jps +++ b/test.jps @@ -175,12 +175,14 @@ settings: type: string caption: Database User name: dbuser + default: root tooltip: In case you restore non-native database backup do not forget to provide its credentials instead of initial ones with help of add-on Configure action. It is relevant to sqldb databases and MongoDB only. - hideLabel: false hidden: false type: string inputType: password caption: Database Password + default: IBOkks15239 name: dbpass onBeforeInit: scripts/configOnBeforeInit.js @@ -538,10 +540,25 @@ actions: - deleteDBdump restore: - - cmd[${targetNodes.nodeGroup}]: |- - echo "${settings.backupedEnvName}" > /root/.backupedenv - echo "${settings.backupDir}" > /root/.backupid + - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupedEnvName}" > /root/.backupedenv user: root + - if ("${settings.isPitr}" == "true"): + - script: | + var dateTimeInput = '${settings.restoreTime}'; + var [date, time] = dateTimeInput.split('T'); + var formattedDateTime = date + " " + time.slice(0, 5) + ":00"; + return api.environment.file.Write({ + envName: '${env.envName}', + session: session, + path: "/root/.backuptime", + nodeGroup: "${targetNodes.nodeGroupp}", + nodeid: "-1", + userName: "root", + body: formattedDateTime + }); + - else: + - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupDir}" > /root/.backupid; + user: root - callScript: restore - deleteDBdump From cc8a67498113cdac5e046f03e692abc1498b3c72 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 15 Oct 2024 16:59:01 +0200 Subject: [PATCH 52/78] JE-71293 --- scripts/getBackupsAllEnvs.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 scripts/getBackupsAllEnvs.sh diff --git a/scripts/getBackupsAllEnvs.sh b/scripts/getBackupsAllEnvs.sh new file mode 100644 index 0000000..4cc6f94 --- /dev/null +++ b/scripts/getBackupsAllEnvs.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +restic self-update &>/dev/null || true + +ENV_LIST=$(ls /data) + +OUTPUT_JSON="{\"result\": 0, \"envs\": [" + +if [ -n "$ENV_LIST" ]; then + for i in $ENV_LIST; do + DIRECTORY_LIST=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots --json) + [ -z "$DIRECTORY_LIST" ] || OUTPUT_JSON="${OUTPUT_JSON}{\"${i}\": ${DIRECTORY_LIST}}," + done + OUTPUT_JSON="${OUTPUT_JSON%,}" +fi + +OUTPUT_JSON="${OUTPUT_JSON}]}" + +echo $OUTPUT_JSON From 80f44d44dc79e288dad45d75b55b3c7d4c2fb698 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 11 Feb 2025 08:21:18 +0100 Subject: [PATCH 53/78] JE-71293 --- backup.jps | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 252 insertions(+), 6 deletions(-) diff --git a/backup.jps b/backup.jps index 93f45cb..a26c36b 100644 --- a/backup.jps +++ b/backup.jps @@ -147,9 +147,23 @@ settings: - type: toggle name: isPitr caption: PITR - tooltip: "Point-in-Time Recovery." + tooltip: "Point in time recovery" value: false hidden: false + showIf: + true: + - type: displayfield + cls: warning + height: 30, + hideLabel: true, + markup: "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon" + + - type: displayfield + name: displayPitr + markup: "" + hidden: true + hideLabel: true + cls: warning - type: displayfield name: displayfield markup: Please specify the database user that has enough privileges to access and modify all the databases stored on server. Username and password are required for all the DB servers except Redis. @@ -161,18 +175,228 @@ settings: type: string caption: Database User name: dbuser + default: root tooltip: In case you restore non-native database backup do not forget to provide its credentials instead of initial ones with help of add-on Configure action. It is relevant to sqldb databases and MongoDB only. - hideLabel: false hidden: false type: string inputType: password caption: Database Password + default: IBOkks15239 name: dbpass onBeforeInit: scripts/configOnBeforeInit.js restore: fields: [] - onBeforeInit: scripts/restoreOnBeforeInit.js + onBeforeInit: | + import org.json.JSONObject; + var storage_unavailable_markup = ""; + var storageInfo = getStorageNodeid(); + var storageEnvDomain = storageInfo.storageEnvShortName; + var storageEnvMasterId = storageInfo.storageNodeId; + var checkSchemaCommand = "if grep -q '^SCHEME=' /.jelenv; then echo true; else echo false; fi"; + var mysql_cluster_markup = "Be careful when restoring the dump from another DB environment (or environment with another replication schema) to the replicated MySQL/MariaDB/Percona solution."; + var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; + + resp = jelastic.env.control.GetEnvInfo(storageEnvDomain, session); + if (resp.result != 0 && resp.result != 11) return resp; + if (resp.result == 11) { + storage_unavailable_markup = "Storage environment " + "${settings.storageName}" + " is deleted."; + } else if (resp.env.status == 1) { + var baseUrl = jps.baseUrl; + var updateResticOnStorageCommand = "wget --tries=10 -O /tmp/installUpdateRestic " + baseUrl + "/scripts/installUpdateRestic && mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic"; + var respUpdate = api.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": updateResticOnStorageCommand, "params": ""}]), false, "root"); + if (respUpdate.result != 0) return respUpdate; + var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": "/root/getBackupsAllEnvs.sh", "params": ""}]), false, "root").responses[0].out; + var backupList = toNative(new JSONObject(String(backups))); + var envs = prepareEnvs(backupList.envs); + var backups = prepareBackups(backupList.backups); + } else { + storage_unavailable_markup = "Storage environment " + storageEnvDomain + " is unavailable (stopped/sleeping)."; + } + + var checkSchema = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": checkSchemaCommand, "params": ""}]), false, "root"); + if (checkSchema.result != 0) return checkSchema; + + function getStorageNodeid(){ + var storageEnv = '${settings.storageName}' + var storageEnvShortName = storageEnv.split(".")[0] + var resp = jelastic.environment.control.GetEnvInfo(storageEnvShortName, session) + if (resp.result != 0) return resp + for (var i = 0; resp.nodes; i++) { + var node = resp.nodes[i] + if (node.nodeGroup == 'storage' && node.ismaster) { + return { result: 0, storageNodeId: node.id, storageEnvShortName: storageEnvShortName }; + } + } + } + + function prepareEnvs(values) { + var aResultValues = []; + + values = values || []; + + for (var i = 0, n = values.length; i < n; i++) { + aResultValues.push({ caption: values[i], value: values[i] }); + } + + return aResultValues; + } + + function prepareBackups(backups) { + var oResultBackups = {}; + var aValues; + + for (var envName in backups) { + if (Object.prototype.hasOwnProperty.call(backups, envName)) { + aValues = []; + + for (var i = 0, n = backups[envName].length; i < n; i++) { + aValues.push({ caption: backups[envName][i], value: backups[envName][i] }); + } + + oResultBackups[envName] = aValues; + } + } + + return oResultBackups; + } + + if (storage_unavailable_markup === "") { + if ('${settings.isPitr}' == 'true') { + settings.fields.push({ + "type": "toggle", + "name": "isPitr", + "caption": "PITR", + "tooltip": "Point in time recovery", + "value": true, + "hidden": false, + "showIf": { + "true": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Time for restore", + "type": "string", + "name": "restoreTime", + "inputType": "datetime-local", + "cls": "x-form-text", + "required": true + } + ], + "false": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Backup", + "type": "list", + "name": "backupDir", + "required": true, + "tooltip": "Select the time stamp for which you want to restore the DB dump", + "dependsOn": { + "backupedEnvName" : backups + } + } + ] + } + }); + if (checkSchema.responses[0].out == "true") { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} + ); + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} + ); + } + } else { + settings.fields.push({ + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Backup", + "type": "list", + "name": "backupDir", + "required": true, + "tooltip": "Select the time stamp for which you want to restore the DB dump", + "dependsOn": { + "backupedEnvName" : backups + } + }); + if (checkSchema.responses[0].out == "true") { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} + ); + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} + ); + } + } + } else { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": storage_unavailable_markup} + ) + } + + return settings; + + pitr: + fields: [] + onBeforeInit: | + var respOut; + var pitr_conf_error_markup = "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon"; + var pitr_conf_success_markup = "Database configured for PITR support"; + var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; + + var checkPitrCmd = "wget " + '${baseUrl}' + "/scripts/pitr.sh -O /root/pitr.sh &>> /var/log/run.log; bash /root/pitr.sh checkPitr " + '${settings.dbuser}' + " " + '${settings.dbpass}'; + resp = jelastic.env.control.ExecCmdById('${env.envName}', session, '${nodes.sqldb.master.id}', toJSON([{ command: checkPitrCmd }]), true, "root"); + if (resp.result != 0) return resp; + respOut = resp.responses[0].out; + respOut = JSON.parse(respOut); + if (respOut.result == 702) { + settings.fields.push({ + caption: "PITR", + type: "toggle", + name: "isPitr", + tooltip: "Point in time recovery", + values: false, + hidden: false, + disabled: true + }, { + type: "displayfield", + cls: "warning", + height: 30, + hideLabel: true, + markup: pitr_conf_error_markup + }); + } else { + settings.fields.push({ + caption: "PITR", + type: "toggle", + name: "isPitr", + tooltip: "Point in time recovery", + values: false, + hidden: false, + disabled: false + }, { + type: "displayfield", + cls: "success", + height: 30, + hideLabel: true, + markup: pitr_conf_success_markup + }); + } + return settings; onBeforeInit: scripts/backupOnBeforeInit.js @@ -202,6 +426,7 @@ globals: scriptSufix: db-backup onInstall: + - checkAddons - installRestic - setSchedule @@ -315,10 +540,25 @@ actions: - deleteDBdump restore: - - cmd[${targetNodes.nodeGroup}]: |- - echo "${settings.backupedEnvName}" > /root/.backupedenv - echo "${settings.backupDir}" > /root/.backupid + - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupedEnvName}" > /root/.backupedenv user: root + - if ("${settings.isPitr}" == "true"): + - script: | + var dateTimeInput = '${settings.restoreTime}'; + var [date, time] = dateTimeInput.split('T'); + var formattedDateTime = date + " " + time.slice(0, 5) + ":00"; + return api.environment.file.Write({ + envName: '${env.envName}', + session: session, + path: "/root/.backuptime", + nodeGroup: "${targetNodes.nodeGroupp}", + nodeid: "-1", + userName: "root", + body: formattedDateTime + }); + - else: + - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupDir}" > /root/.backupid; + user: root - callScript: restore - deleteDBdump @@ -376,6 +616,7 @@ actions: - else: - removePermanentMount - addPermanentMount + - if ("${settings.isPitr}" == "true"): setupPitr - installScript: cronTime: ${globals.cron} backupCount: ${settings.backupCount} @@ -384,7 +625,12 @@ actions: nodeGroup: ${targetNodes.nodeGroup} dbuser: ${settings.dbuser} dbpass: ${settings.dbpass} - + + setupPitr: + cmd[${nodes.sqldb.master.id}]: |- + wget --tries=10 -O /tmp/pitr.sh ${baseUrl}/scripts/pitr.sh && \ + chmod +x /tmp/pitr.sh && /tmp/pitr.sh setupPitr ${settings.dbuser} ${settings.dbpass}; + addPermanentMount: - getStorageCtid - script: | From ec5c082e8ea099427094707c5424f4ff818452a4 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 11 Feb 2025 08:21:40 +0100 Subject: [PATCH 54/78] Delete test.jps --- test.jps | 656 ------------------------------------------------------- 1 file changed, 656 deletions(-) delete mode 100644 test.jps diff --git a/test.jps b/test.jps deleted file mode 100644 index a26c36b..0000000 --- a/test.jps +++ /dev/null @@ -1,656 +0,0 @@ -type: update -jpsVersion: 6.1.1 -name: Database Backup/Restore for the filesystem and the databases -id: db-backup -targetEditions: any -logo: /images/backup-logo.png -description: Backup Add-On for the database. It can be used to create scheduled backups according to any required timezone and restore corrupted databases, even if the content has been completely deleted. -baseUrl: https://raw.githubusercontent.com/sych74/database-backup-addon/pitr - -targetNodes: - nodeType: - - redis - - redis7 - - redis6 - - postgresql - - postgres15 - - postgres14 - - postgres13 - - postgres12 - - postgres11 - - mysql - - mysql5 - - mysql8 - - mariadb-dockerized - - mariadb10 - - mariadb11 - - mongodb-dockerized - - mongodb - - mongo - - perconadb - - percona8 - - percona5 - -settings: - main: - fields: - - type: radio-fieldset - name: scheduleType - hidden: false - default: '1' - values: - - value: 1 - caption: Pre-defined - - value: 2 - caption: Custom - - value: 3 - caption: Manual (crontab) - tooltip: "A simple cron-based - scheduler to automatically start the backup process based on prescribed timing - instructions.
Note that the required timestamps should be specified - respectively to the UTC time zone.
" - showIf: - 1: - - name: cronTime - caption: Backup schedule - type: list - editable: false - values: - - value: 0 * * * * - caption: "Hourly (at minute 0)" - - value: 0 0 * * * - caption: "Daily (at 00:00)" - - value: 0 0 * * 0 - caption: "Weekly (at 00:00 on Sunday)" - - value: 0 0 1 * * - caption: "Monthly (at 00:00 on day 1)" - default: 0 0 * * * - 2: - - type: string - name: backupTime - caption: Time - inputType: time - default: "09:00" - cls: x-form-text - width: 120 - required: true - - caption: Days - type: compositefield - name: days - defaultMargins: 0 12 0 0 - items: - - name: sun - value: true - type: checkbox - caption: Su - - name: mon - value: true - type: checkbox - caption: Mo - - name: tue - value: true - type: checkbox - caption: Tu - - name: wed - value: true - type: checkbox - caption: We - - name: thu - value: true - type: checkbox - caption: Th - - name: fri - value: true - type: checkbox - caption: Fr - - name: sat - value: true - type: checkbox - caption: Sa - - name: "tz" - caption: "Time Zone" - type: "list" - required: true - editable: true - forceSelection: true - values: values - 3: - - name: cronTime - caption: Crontab - type: string - default: 0 0 * * * - regexText: Cron syntax is incorrect! - regex: "^(((([\\\\*]{1}){1,})|((\\\\*\\\\\\/){0,1}(([0-9\\/\\*\\-\\,]{1}){1,}|(([1-5]{1}){1}([0-9\\/\\*\\-\\,]{1}){1,}){1}))) - ((([\\\\*]{1}){1,})|((\\\\*\\\\\\/){0,1}(([0-9\\/\\*\\-\\,]{1}){1,}|(([1]{1}){1}([0-9\\/\\*\\-\\,-]{1}){1,}){1}|([2]{1}){1}([0-3]{1}){1}))) - ((([\\\\*]{1}){1})|((\\\\*\\\\\\/){0,1}(([1-9]{1}){1}|(([1-2]{1}){1}([0-9\\/\\*\\-\\,]{1}){1,5}){1}|([3]{1}){1}([0-1]{1}){1}))) - ((([\\\\*]{1}){1})|((\\\\*\\\\\\/){0,1}(([1-9]{1}){1}|(([1-2]{1}){1}([0-9\\/\\*\\-\\,]{1}){1,}){1}|([3]{1}){1}([0-1]{1}){1}))|(jan|JAN|feb|FEB|mar|MAR|apr|APR|may|MAY|jun|JUN|jul|JUL|aug|AUG|sep|SEP|okt|OKT|nov|NOV|dec|DEC)(-?\\w+?)?) - ((([\\\\*]{1}){1})|((\\\\*\\\\\\/){0,1}(([0-7]{1,}(-?[0-7]?(,[0-7]){0,6})){1}))|((sun|SUN|mon|MON|tue|TUE|wed|WED|thu|THU|fri|FRI|sat|SAT)?(,(sun|SUN|mon|MON|tue|TUE|wed|WED|thu|THU|fri|FRI|sat|SAT)){0,6})(-?\\w+?)?))$|^(@(reboot|yearly|annualy|monthly|weekly|daily|hourly))$" - - caption: Backup storage - type: list - tooltip: "The environment with backup storage to be used for backups creation. Presence of this environment is obligatory." - name: storageName - dependsOn: region - required: true - - type: spinner - name: backupCount - caption: Number of backups - tooltip: "The number of newest backups to be kept during rotation." - min: 1 - max: 30 - default: 5 - - type: toggle - name: isAlwaysUmount - caption: Always umount - tooltip: "Always unmount backup storage when backup/restore is finished." - value: false - hidden: false - - type: toggle - name: isPitr - caption: PITR - tooltip: "Point in time recovery" - value: false - hidden: false - showIf: - true: - - type: displayfield - cls: warning - height: 30, - hideLabel: true, - markup: "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon" - - - type: displayfield - name: displayPitr - markup: "" - hidden: true - hideLabel: true - cls: warning - - type: displayfield - name: displayfield - markup: Please specify the database user that has enough privileges to access and modify all the databases stored on server. Username and password are required for all the DB servers except Redis. - hidden: false - hideLabel: true - cls: warning - - hideLabel: false - hidden: false - type: string - caption: Database User - name: dbuser - default: root - tooltip: In case you restore non-native database backup do not forget to provide its credentials instead of initial ones with help of add-on Configure action. It is relevant to sqldb databases and MongoDB only. - - hideLabel: false - hidden: false - type: string - inputType: password - caption: Database Password - default: IBOkks15239 - name: dbpass - onBeforeInit: scripts/configOnBeforeInit.js - - restore: - fields: [] - onBeforeInit: | - import org.json.JSONObject; - var storage_unavailable_markup = ""; - var storageInfo = getStorageNodeid(); - var storageEnvDomain = storageInfo.storageEnvShortName; - var storageEnvMasterId = storageInfo.storageNodeId; - var checkSchemaCommand = "if grep -q '^SCHEME=' /.jelenv; then echo true; else echo false; fi"; - var mysql_cluster_markup = "Be careful when restoring the dump from another DB environment (or environment with another replication schema) to the replicated MySQL/MariaDB/Percona solution."; - var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; - - resp = jelastic.env.control.GetEnvInfo(storageEnvDomain, session); - if (resp.result != 0 && resp.result != 11) return resp; - if (resp.result == 11) { - storage_unavailable_markup = "Storage environment " + "${settings.storageName}" + " is deleted."; - } else if (resp.env.status == 1) { - var baseUrl = jps.baseUrl; - var updateResticOnStorageCommand = "wget --tries=10 -O /tmp/installUpdateRestic " + baseUrl + "/scripts/installUpdateRestic && mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic"; - var respUpdate = api.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": updateResticOnStorageCommand, "params": ""}]), false, "root"); - if (respUpdate.result != 0) return respUpdate; - var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": "/root/getBackupsAllEnvs.sh", "params": ""}]), false, "root").responses[0].out; - var backupList = toNative(new JSONObject(String(backups))); - var envs = prepareEnvs(backupList.envs); - var backups = prepareBackups(backupList.backups); - } else { - storage_unavailable_markup = "Storage environment " + storageEnvDomain + " is unavailable (stopped/sleeping)."; - } - - var checkSchema = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": checkSchemaCommand, "params": ""}]), false, "root"); - if (checkSchema.result != 0) return checkSchema; - - function getStorageNodeid(){ - var storageEnv = '${settings.storageName}' - var storageEnvShortName = storageEnv.split(".")[0] - var resp = jelastic.environment.control.GetEnvInfo(storageEnvShortName, session) - if (resp.result != 0) return resp - for (var i = 0; resp.nodes; i++) { - var node = resp.nodes[i] - if (node.nodeGroup == 'storage' && node.ismaster) { - return { result: 0, storageNodeId: node.id, storageEnvShortName: storageEnvShortName }; - } - } - } - - function prepareEnvs(values) { - var aResultValues = []; - - values = values || []; - - for (var i = 0, n = values.length; i < n; i++) { - aResultValues.push({ caption: values[i], value: values[i] }); - } - - return aResultValues; - } - - function prepareBackups(backups) { - var oResultBackups = {}; - var aValues; - - for (var envName in backups) { - if (Object.prototype.hasOwnProperty.call(backups, envName)) { - aValues = []; - - for (var i = 0, n = backups[envName].length; i < n; i++) { - aValues.push({ caption: backups[envName][i], value: backups[envName][i] }); - } - - oResultBackups[envName] = aValues; - } - } - - return oResultBackups; - } - - if (storage_unavailable_markup === "") { - if ('${settings.isPitr}' == 'true') { - settings.fields.push({ - "type": "toggle", - "name": "isPitr", - "caption": "PITR", - "tooltip": "Point in time recovery", - "value": true, - "hidden": false, - "showIf": { - "true": [ - { - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs - }, { - "caption": "Time for restore", - "type": "string", - "name": "restoreTime", - "inputType": "datetime-local", - "cls": "x-form-text", - "required": true - } - ], - "false": [ - { - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs - }, { - "caption": "Backup", - "type": "list", - "name": "backupDir", - "required": true, - "tooltip": "Select the time stamp for which you want to restore the DB dump", - "dependsOn": { - "backupedEnvName" : backups - } - } - ] - } - }); - if (checkSchema.responses[0].out == "true") { - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} - ); - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} - ); - } - } else { - settings.fields.push({ - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs - }, { - "caption": "Backup", - "type": "list", - "name": "backupDir", - "required": true, - "tooltip": "Select the time stamp for which you want to restore the DB dump", - "dependsOn": { - "backupedEnvName" : backups - } - }); - if (checkSchema.responses[0].out == "true") { - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} - ); - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} - ); - } - } - } else { - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": storage_unavailable_markup} - ) - } - - return settings; - - pitr: - fields: [] - onBeforeInit: | - var respOut; - var pitr_conf_error_markup = "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon"; - var pitr_conf_success_markup = "Database configured for PITR support"; - var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; - - var checkPitrCmd = "wget " + '${baseUrl}' + "/scripts/pitr.sh -O /root/pitr.sh &>> /var/log/run.log; bash /root/pitr.sh checkPitr " + '${settings.dbuser}' + " " + '${settings.dbpass}'; - resp = jelastic.env.control.ExecCmdById('${env.envName}', session, '${nodes.sqldb.master.id}', toJSON([{ command: checkPitrCmd }]), true, "root"); - if (resp.result != 0) return resp; - respOut = resp.responses[0].out; - respOut = JSON.parse(respOut); - if (respOut.result == 702) { - settings.fields.push({ - caption: "PITR", - type: "toggle", - name: "isPitr", - tooltip: "Point in time recovery", - values: false, - hidden: false, - disabled: true - }, { - type: "displayfield", - cls: "warning", - height: 30, - hideLabel: true, - markup: pitr_conf_error_markup - }); - } else { - settings.fields.push({ - caption: "PITR", - type: "toggle", - name: "isPitr", - tooltip: "Point in time recovery", - values: false, - hidden: false, - disabled: false - }, { - type: "displayfield", - cls: "success", - height: 30, - hideLabel: true, - markup: pitr_conf_success_markup - }); - } - return settings; - -onBeforeInit: scripts/backupOnBeforeInit.js - -buttons: -- caption: Backup Now - action: backup - loadingText: Backing up... - confirmText: Do you want to initiate the backup process? - successText: The backup process has been finished successfully. - -- caption: Configure - action: configure - settings: main - loadingText: Configuring... - successText: The backup configs have been updated successfully. - -- caption: Restore - action: restore - loadingText: Restoring... - settings: restore - successText: The backup have been successfully restored. - title: Restore Backup - submitButtonText: Restore - confirmText: You are going to restore from a backup, which will override all your existing data. This action cannot be canceled or reverted. Do you want to proceed? - -globals: - scriptSufix: db-backup - -onInstall: - - - checkAddons - - installRestic - - setSchedule - -onUninstall: - - callScript: uninstall - - removeScript - -onBeforeDelete: - - callScript: uninstall - - removeScript - -onAfterRedeployContainer[${targetNodes.nodeGroup}]: - - installRestic - -onAfterClone: - - script: return {result:0, jps:MANIFEST}; - - install: ${response.jps} - nodeGroup: ${targetNodes.nodeGroup} - envName: ${event.response.env.envName} - settings: - scheduleType: ${settings.scheduleType} - storageName: ${settings.storageName} - cronTime: ${settings.cronTime} - backupTime: ${settings.backupTime} - sun: ${settings.sun} - mon: ${settings.mon} - tue: ${settings.tue} - wed: ${settings.wed} - thu: ${settings.thu} - fri: ${settings.fri} - sat: ${settings.sat} - tz: ${settings.tz} - backupCount: ${settings.backupCount} - isAlwaysUmount: ${settings.isAlwaysUmount} - -onAfterConfirmTransfer: setSchedule - -actions: - checkAddons: - - script: |- - var onAfterReturn = { setGlobals: {} }, - glbs = onAfterReturn.setGlobals, - resp = api.marketplace.app.GetAddonList({ - search: {}, - envName: "${env.name}", - session: session - }); - if (resp.result != 0) return resp; - glbs["alreadyInstalled"] = false; - for (let i = 0, n = resp.apps.length; i < n; i++) { - if (resp.apps[i].isInstalled) { - if (resp.apps[i].app_id == 'wp-backup') { - glbs["alreadyInstalled"] = true; - break; - } - } - } - return { result: 0, onAfterReturn: onAfterReturn }; - - if ('${globals.alreadyInstalled}' == 'true' ): - - stopEvent: - type: warning - message: Database backup add-on is already installed on ${env.name}. Database backup addon installation is not possible. - - installRestic: - cmd [${targetNodes.nodeGroup}]: |- - wget --tries=10 -O /tmp/installUpdateRestic ${baseUrl}/scripts/installUpdateRestic && \ - mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ - chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic - user: root - - installScript: - - removeScript - - getStorageCtid - - script: ${baseUrl}/scripts/create-backup-main-script.js?_r=${fn.random} - params: - scriptName: ${env.envName}-${globals.scriptSufix} - baseUrl: ${baseUrl} - cronTime: ${this.cronTime} - backupCount: ${this.backupCount} - userId: ${env.uid} - storageNodeId: ${response.storageCtid} - backupExecNode: ${targetNodes.master.id} - storageEnv: ${response.storageEnvShortName} - isAlwaysUmount: ${this.isAlwaysUmount} - isPitr: ${this.isPitr} - nodeGroup: ${this.nodeGroup} - dbuser: ${this.dbuser} - dbpass: ${this.dbpass} - - callScript: - script: |- - var resp = jelastic.dev.scripting.Eval(appid, session, '${env.envName}-${globals.scriptSufix}', {action:"${this}"}); - if (resp.result === 1702 && "${this}" == "uninstall") { - return { result: 0, out: "script not found" }; - } else { - return resp.response || resp; - } - - removeScript: - script: |- - var resp = jelastic.dev.scripting.GetScript(appid, session, '${env.envName}-${globals.scriptSufix}'); - if (resp.result === 0) { - var resp = jelastic.dev.scripting.DeleteScript(appid, session, '${env.envName}-${globals.scriptSufix}'); - return resp.response || resp; - } - return { result: 0 }; - - backup: - - callScript: backup - - deleteDBdump - - restore: - - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupedEnvName}" > /root/.backupedenv - user: root - - if ("${settings.isPitr}" == "true"): - - script: | - var dateTimeInput = '${settings.restoreTime}'; - var [date, time] = dateTimeInput.split('T'); - var formattedDateTime = date + " " + time.slice(0, 5) + ":00"; - return api.environment.file.Write({ - envName: '${env.envName}', - session: session, - path: "/root/.backuptime", - nodeGroup: "${targetNodes.nodeGroupp}", - nodeid: "-1", - userName: "root", - body: formattedDateTime - }); - - else: - - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupDir}" > /root/.backupid; - user: root - - callScript: restore - - deleteDBdump - - deleteDBdump: - - cmd[${targetNodes.nodeGroup}]: |- - [ -f /root/db_backup.sql ] && rm -f /root/db_backup.sql || exit 0; - user: root - - configure: - - setSchedule - - getStorageCtid: - - script: scripts/getStorageCtid.js - - convert: - - script: | - var resp = {result:0, onAfterReturn: {setGlobals:{cron: ""}}}, offset = java.util.TimeZone.getTimeZone("${settings.tz}").getRawOffset(), - setGlobals = resp.onAfterReturn.setGlobals; - - var time = "${settings.backupTime}".split(":"), - d1 = new Date(2020, 1, 10, parseInt(time[0],10), parseInt(time[1],10)), - d2 = new Date(d1.getTime() - offset), - dd = d2.getDate() - d1.getDate(), - days = getDays([${settings.sun:0}, ${settings.mon:0}, ${settings.tue:0}, ${settings.wed:0}, ${settings.thu:0}, ${settings.fri:0}, ${settings.sat:0}], dd); - - setGlobals.cron = d2.getMinutes() + " " + d2.getHours() + " * * " + days.join(","); - - - function getDays(settings, dd) { - var days = []; - for (var i = 0, n = settings.length; i < n; i++) { - if (settings[i]) { - var day = i + dd; - if (day < 0) day +=7; else if (day > 6) day -=7; - days.push(day); - } - } - days.sort(); - return days; - } - return resp; - - setSchedule: - - setGlobals: - storageEnv: ${settings.storageName} - isAlwaysUmount: ${settings.isAlwaysUmount} - isPitr: ${settings.isPitr} - - if ("${settings.scheduleType}" == 2): - - convert - - else: - - setGlobals: - cron: ${settings.cronTime} - - if ("${settings.isAlwaysUmount}" == "true"): - - removePermanentMount - - else: - - removePermanentMount - - addPermanentMount - - if ("${settings.isPitr}" == "true"): setupPitr - - installScript: - cronTime: ${globals.cron} - backupCount: ${settings.backupCount} - isAlwaysUmount: ${globals.isAlwaysUmount} - isPitr: ${globals.isPitr} - nodeGroup: ${targetNodes.nodeGroup} - dbuser: ${settings.dbuser} - dbpass: ${settings.dbpass} - - setupPitr: - cmd[${nodes.sqldb.master.id}]: |- - wget --tries=10 -O /tmp/pitr.sh ${baseUrl}/scripts/pitr.sh && \ - chmod +x /tmp/pitr.sh && /tmp/pitr.sh setupPitr ${settings.dbuser} ${settings.dbpass}; - - addPermanentMount: - - getStorageCtid - - script: | - return jelastic.env.file.AddMountPointById("${env.envName}", session, "${targetNodes.master.id}", "/opt/backup", "nfs4", null, "/data/", "${response.storageCtid}", "DBBackupRestore", false); - - removePermanentMount: - - getStorageCtid - - script: | - var allMounts = jelastic.env.file.GetMountPoints("${env.envName}", session, "${targetNodes.master.id}").array; - for (var i = 0, n = allMounts.length; i < n; i++) { - if (allMounts[i].path == "/opt/backup" && allMounts[i].type == "INTERNAL") { - resp = jelastic.env.file.RemoveMountPointById("${env.envName}", session, "${targetNodes.master.id}", "/opt/backup"); - if (resp.result != 0) { return resp; } - } - } - allMounts = jelastic.env.file.GetMountPoints("${env.envName}", session).array; - for (var i = 0, n = allMounts.length; i < n; i++) { - if (allMounts[i].path == "/opt/backup" && allMounts[i].type == "INTERNAL") { - resp = jelastic.env.file.RemoveMountPointByGroup("${env.envName}", session, "sqldb", "/opt/backup"); - if (resp.result != 0) { return resp; } - } - } - return { "result": 0 }; From dc806e087babde500cbebfe73e84c023c17f053e Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Mon, 24 Feb 2025 09:10:36 +0100 Subject: [PATCH 55/78] JE-71293 --- backup.jps | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backup.jps b/backup.jps index a26c36b..81e8b0d 100644 --- a/backup.jps +++ b/backup.jps @@ -156,8 +156,7 @@ settings: cls: warning height: 30, hideLabel: true, - markup: "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon" - + markup: "Database does not configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon" - type: displayfield name: displayPitr markup: "" From fef9c59c5f14942ac54a25eda5fbed072dc160df Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Mon, 24 Feb 2025 10:41:28 +0100 Subject: [PATCH 56/78] JE-71293 --- backup.jps | 162 +-------------------------------- scripts/restoreOnBeforeInit.js | 77 +++++++++++++--- 2 files changed, 67 insertions(+), 172 deletions(-) diff --git a/backup.jps b/backup.jps index 81e8b0d..738b902 100644 --- a/backup.jps +++ b/backup.jps @@ -187,167 +187,7 @@ settings: restore: fields: [] - onBeforeInit: | - import org.json.JSONObject; - var storage_unavailable_markup = ""; - var storageInfo = getStorageNodeid(); - var storageEnvDomain = storageInfo.storageEnvShortName; - var storageEnvMasterId = storageInfo.storageNodeId; - var checkSchemaCommand = "if grep -q '^SCHEME=' /.jelenv; then echo true; else echo false; fi"; - var mysql_cluster_markup = "Be careful when restoring the dump from another DB environment (or environment with another replication schema) to the replicated MySQL/MariaDB/Percona solution."; - var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; - - resp = jelastic.env.control.GetEnvInfo(storageEnvDomain, session); - if (resp.result != 0 && resp.result != 11) return resp; - if (resp.result == 11) { - storage_unavailable_markup = "Storage environment " + "${settings.storageName}" + " is deleted."; - } else if (resp.env.status == 1) { - var baseUrl = jps.baseUrl; - var updateResticOnStorageCommand = "wget --tries=10 -O /tmp/installUpdateRestic " + baseUrl + "/scripts/installUpdateRestic && mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic"; - var respUpdate = api.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": updateResticOnStorageCommand, "params": ""}]), false, "root"); - if (respUpdate.result != 0) return respUpdate; - var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": "/root/getBackupsAllEnvs.sh", "params": ""}]), false, "root").responses[0].out; - var backupList = toNative(new JSONObject(String(backups))); - var envs = prepareEnvs(backupList.envs); - var backups = prepareBackups(backupList.backups); - } else { - storage_unavailable_markup = "Storage environment " + storageEnvDomain + " is unavailable (stopped/sleeping)."; - } - - var checkSchema = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": checkSchemaCommand, "params": ""}]), false, "root"); - if (checkSchema.result != 0) return checkSchema; - - function getStorageNodeid(){ - var storageEnv = '${settings.storageName}' - var storageEnvShortName = storageEnv.split(".")[0] - var resp = jelastic.environment.control.GetEnvInfo(storageEnvShortName, session) - if (resp.result != 0) return resp - for (var i = 0; resp.nodes; i++) { - var node = resp.nodes[i] - if (node.nodeGroup == 'storage' && node.ismaster) { - return { result: 0, storageNodeId: node.id, storageEnvShortName: storageEnvShortName }; - } - } - } - - function prepareEnvs(values) { - var aResultValues = []; - - values = values || []; - - for (var i = 0, n = values.length; i < n; i++) { - aResultValues.push({ caption: values[i], value: values[i] }); - } - - return aResultValues; - } - - function prepareBackups(backups) { - var oResultBackups = {}; - var aValues; - - for (var envName in backups) { - if (Object.prototype.hasOwnProperty.call(backups, envName)) { - aValues = []; - - for (var i = 0, n = backups[envName].length; i < n; i++) { - aValues.push({ caption: backups[envName][i], value: backups[envName][i] }); - } - - oResultBackups[envName] = aValues; - } - } - - return oResultBackups; - } - - if (storage_unavailable_markup === "") { - if ('${settings.isPitr}' == 'true') { - settings.fields.push({ - "type": "toggle", - "name": "isPitr", - "caption": "PITR", - "tooltip": "Point in time recovery", - "value": true, - "hidden": false, - "showIf": { - "true": [ - { - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs - }, { - "caption": "Time for restore", - "type": "string", - "name": "restoreTime", - "inputType": "datetime-local", - "cls": "x-form-text", - "required": true - } - ], - "false": [ - { - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs - }, { - "caption": "Backup", - "type": "list", - "name": "backupDir", - "required": true, - "tooltip": "Select the time stamp for which you want to restore the DB dump", - "dependsOn": { - "backupedEnvName" : backups - } - } - ] - } - }); - if (checkSchema.responses[0].out == "true") { - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} - ); - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} - ); - } - } else { - settings.fields.push({ - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs - }, { - "caption": "Backup", - "type": "list", - "name": "backupDir", - "required": true, - "tooltip": "Select the time stamp for which you want to restore the DB dump", - "dependsOn": { - "backupedEnvName" : backups - } - }); - if (checkSchema.responses[0].out == "true") { - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} - ); - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} - ); - } - } - } else { - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": storage_unavailable_markup} - ) - } - - return settings; + onBeforeInit: script/restoreOnBeforeInit.js pitr: fields: [] diff --git a/scripts/restoreOnBeforeInit.js b/scripts/restoreOnBeforeInit.js index b4156f4..28a88aa 100644 --- a/scripts/restoreOnBeforeInit.js +++ b/scripts/restoreOnBeforeInit.js @@ -26,7 +26,7 @@ if (resp.result == 11) { var checkSchema = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": checkSchemaCommand, "params": ""}]), false, "root"); if (checkSchema.result != 0) return checkSchema; - + function getStorageNodeid(){ var storageEnv = '${settings.storageName}' var storageEnvShortName = storageEnv.split(".")[0] @@ -72,7 +72,61 @@ function prepareBackups(backups) { } if (storage_unavailable_markup === "") { - settings.fields.push({ + if ('${settings.isPitr}' == 'true') { + settings.fields.push({ + "type": "toggle", + "name": "isPitr", + "caption": "PITR", + "tooltip": "Point in time recovery", + "value": true, + "hidden": false, + "showIf": { + "true": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Time for restore", + "type": "string", + "name": "restoreTime", + "inputType": "datetime-local", + "cls": "x-form-text", + "required": true + } + ], + "false": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs + }, { + "caption": "Backup", + "type": "list", + "name": "backupDir", + "required": true, + "tooltip": "Select the time stamp for which you want to restore the DB dump", + "dependsOn": { + "backupedEnvName" : backups + } + } + ] + } + }); + if (checkSchema.responses[0].out == "true") { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} + ); + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} + ); + } + } else { + settings.fields.push({ "caption": "Restore from", "type": "list", "name": "backupedEnvName", @@ -83,19 +137,20 @@ if (storage_unavailable_markup === "") { "type": "list", "name": "backupDir", "required": true, - "tooltip": "Select the time stamp for which you want to restore the DB dump", + "tooltip": "Select the time stamp for which you want to restore the DB dump", "dependsOn": { "backupedEnvName" : backups } }); - if (checkSchema.responses[0].out == "true") { - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} - ); - settings.fields.push( - {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} - ); - } + if (checkSchema.responses[0].out == "true") { + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": mysql_cluster_markup} + ); + settings.fields.push( + {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": recovery_addon_markup} + ); + } + } } else { settings.fields.push( {"type": "displayfield", "cls": "warning", "height": 30, "hideLabel": true, "markup": storage_unavailable_markup} From 31235c39a936d0931f6b0f1939fa1bd7cc94c0ab Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Mon, 24 Feb 2025 20:29:14 +0100 Subject: [PATCH 57/78] JE-71293 --- backup.jps | 3 - scripts/backup-logic.sh | 297 +++++++++++++++++++++++++----------- scripts/checkCredentials.sh | 18 ++- scripts/mariadb-restore.sh | 55 ------- scripts/mongo-restore.sh | 15 -- scripts/pitr.sh | 14 +- scripts/postgres-restore.sh | 19 --- scripts/redis-restore.sh | 21 --- scripts/restore-logic.sh | 119 +++++++++++++++ scripts/restote-test.sh | 163 -------------------- 10 files changed, 348 insertions(+), 376 deletions(-) delete mode 100644 scripts/mariadb-restore.sh delete mode 100644 scripts/mongo-restore.sh delete mode 100644 scripts/postgres-restore.sh delete mode 100644 scripts/redis-restore.sh delete mode 100644 scripts/restote-test.sh diff --git a/backup.jps b/backup.jps index 738b902..a9f7ff5 100644 --- a/backup.jps +++ b/backup.jps @@ -174,14 +174,12 @@ settings: type: string caption: Database User name: dbuser - default: root tooltip: In case you restore non-native database backup do not forget to provide its credentials instead of initial ones with help of add-on Configure action. It is relevant to sqldb databases and MongoDB only. - hideLabel: false hidden: false type: string inputType: password caption: Database Password - default: IBOkks15239 name: dbpass onBeforeInit: scripts/configOnBeforeInit.js @@ -265,7 +263,6 @@ globals: scriptSufix: db-backup onInstall: - - checkAddons - installRestic - setSchedule diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index f7997eb..085d1a8 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -12,21 +12,47 @@ USER_SESSION=$9 USER_EMAIL=${10} PITR=${11} -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}') +# Define PID file location after ENV_NAME is available +readonly LOCK_FILE="/var/run/${ENV_NAME}_backup.pid" + +# Add cleanup trap before any potential exit points +trap 'rm -f "${LOCK_FILE}"' EXIT + +# Check if another backup process is running +if [ -f "${LOCK_FILE}" ]; then + pid=$(cat "${LOCK_FILE}") + if kill -0 "${pid}" 2>/dev/null; then + echo "Another backup process (PID: ${pid}) is already running" | tee -a "${BACKUP_LOG_FILE}" + exit 1 + else + echo "Removing stale lock file" | tee -a "${BACKUP_LOG_FILE}" + rm -f "${LOCK_FILE}" + fi +fi + +# Create PID file +echo $$ > "${LOCK_FILE}" + +# Extract repository and branch information +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}') +# Define backup directories DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs SQL_DUMP_NAME=db_backup.sql +# Prepare backup directories rm -rf $DUMP_BACKUP_DIR && mkdir -p $DUMP_BACKUP_DIR rm -rf $BINLOGS_BACKUP_DIR && mkdir -p $BINLOGS_BACKUP_DIR +# Default PITR to false if not set if [ -z "$PITR" ]; then PITR="false" fi +# Determine MongoDB type if [ "$COMPUTE_TYPE" == "mongodb" ]; then if grep -q '^replication' /etc/mongod.conf; then MONGO_TYPE="-replica-set" @@ -35,8 +61,10 @@ if [ "$COMPUTE_TYPE" == "mongodb" ]; then fi fi -source /etc/jelastic/metainf.conf; +# Source external configuration +source /etc/jelastic/metainf.conf +# Determine Redis type if [ "$COMPUTE_TYPE" == "redis" ]; then REDIS_CONF_PATH=$(realpath /etc/redis.conf) if grep -q '^cluster-enabled yes' ${REDIS_CONF_PATH}; then @@ -46,8 +74,11 @@ if [ "$COMPUTE_TYPE" == "redis" ]; then fi fi -SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}'| sed 's/\/[0-9]*//g' | tail -n 1) +# Determine server IP address +SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}' | sed 's/\/[0-9]*//g' | tail -n 1) [ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" + +# Determine MySQL/MariaDB client applications if which mariadb 2>/dev/null; then CLIENT_APP="mariadb" else @@ -60,37 +91,40 @@ else DUMP_APP="mysqldump" fi -function forceInstallUpdateRestic(){ - wget --tries=10 -O /tmp/installUpdateRestic ${BASE_URL}/scripts/installUpdateRestic && \ - mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ - chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic +# Function to force install/update Restic +function forceInstallUpdateRestic() { + wget --tries=10 -O /tmp/installUpdateRestic ${BASE_URL}/scripts/installUpdateRestic && \ + mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ + chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic } -function sendEmailNotification(){ +# Function to send email notification +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}') + [ -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; + echo $(date) ${ENV_NAME} "Sending e-mail notification about removing the stale lock" | tee -a $BACKUP_LOG_FILE SUBJECT="Stale lock is removed on /opt/backup/${ENV_NAME} backup repo" BODY="Please pay attention to /opt/backup/${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; + 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; + echo $(date) ${ENV_NAME} "E-mail notification is sent successfully" | tee -a $BACKUP_LOG_FILE fi - elif [ -z "${CURRENT_PLATFORM_MAJOR_VERSION}" ]; then #this elif covers the case if the version is not received - echo $(date) ${ENV_NAME} "Error when checking the platform version" | tee -a $BACKUP_LOG_FILE; + 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; + 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; + 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 update_restic(){ +# Function to update Restic +function update_restic() { if which restic; then restic self-update || forceInstallUpdateRestic else @@ -98,15 +132,16 @@ function update_restic(){ fi } -function check_backup_repo(){ +# Function to check backup repository +function check_backup_repo() { [ -d /opt/backup/${ENV_NAME} ] || mkdir -p /opt/backup/${ENV_NAME} - export FILES_COUNT=$(ls -n /opt/backup/${ENV_NAME}|awk '{print $2}'); + export FILES_COUNT=$(ls -n /opt/backup/${ENV_NAME} | awk '{print $2}') if [ "${FILES_COUNT}" != "0" ]; then - echo $(date) ${ENV_NAME} "Checking the backup repository integrity and consistency" | tee -a $BACKUP_LOG_FILE; - if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then - echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; + echo $(date) ${ENV_NAME} "Checking the backup repository integrity and consistency" | tee -a $BACKUP_LOG_FILE + if [[ $(ls -A /opt/backup/${ENV_NAME}/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 /opt/backup/${ENV_NAME} unlock - sendEmailNotification + sendEmailNotification fi GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -q -r /opt/backup/${ENV_NAME} check --read-data-subset=5% || { echo "Backup repository integrity check failed."; exit 1; } else @@ -114,107 +149,177 @@ function check_backup_repo(){ fi } -function rotate_snapshots(){ +# Function to rotate snapshots +function rotate_snapshots() { echo $(date) ${ENV_NAME} "Rotating snapshots by keeping the last ${BACKUP_COUNT}" | tee -a ${BACKUP_LOG_FILE} - if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]] ; then - echo $(date) ${ENV_NAME} "Backup repository has a slate lock, removing" | tee -a $BACKUP_LOG_FILE; + if [[ $(ls -A /opt/backup/${ENV_NAME}/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 /opt/backup/${ENV_NAME} unlock sendEmailNotification fi { GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic forget -q -r /opt/backup/${ENV_NAME} --keep-last ${BACKUP_COUNT} --prune | tee -a $BACKUP_LOG_FILE; } || { echo "Backup rotation failed."; exit 1; } } - -function get_binlog_file(){ +# Function to get binlog file +function get_binlog_file() { local binlog_file=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $1}') echo $(date) ${ENV_NAME} "Getting the binlog_file: ${binlog_file}" >> ${BACKUP_LOG_FILE} echo $binlog_file } -function get_binlog_position(){ +# Function to get binlog position +function get_binlog_position() { local binlog_pos=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $2}') echo $(date) ${ENV_NAME} "Getting the binlog_position: ${binlog_pos}" >> ${BACKUP_LOG_FILE} echo $binlog_pos } -function create_snapshot(){ +# Function to create snapshot +function create_snapshot() { source /etc/jelastic/metainf.conf DUMP_NAME=$(date "+%F_%H%M%S_%Z"-${BACKUP_TYPE}\($COMPUTE_TYPE-$COMPUTE_TYPE_FULL_VERSION$REDIS_TYPE$MONGO_TYPE\)) echo $(date) ${ENV_NAME} "Saving the DB dump to ${DUMP_NAME} snapshot" | tee -a ${BACKUP_LOG_FILE} if [ "$COMPUTE_TYPE" == "redis" ]; then - RDB_TO_BACKUP=$(ls -d /tmp/* |grep redis-dump.*); - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${RDB_TO_BACKUP} | tee -a ${BACKUP_LOG_FILE}; + RDB_TO_BACKUP=$(ls -d /tmp/* | grep redis-dump.*) + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${RDB_TO_BACKUP} | tee -a ${BACKUP_LOG_FILE} elif [ "$COMPUTE_TYPE" == "mongodb" ]; then GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} + elif [ "$COMPUTE_TYPE" == "postgresql" ] && [ "$PITR" == "true" ]; then + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} \ + --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" \ + --tag "PITR" \ + --tag "$(cat ${DUMP_BACKUP_DIR}/wal_location)" \ + ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} else if [ "$PITR" == "true" ]; then GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" --tag "PITR" --tag "$(get_binlog_file)" --tag "$(get_binlog_position)" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} else - GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${DUMP_NAME} ${BACKUP_ADDON_COMMIT_ID} ${BACKUP_TYPE}" ${DUMP_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} fi fi } -function get_latest_pitr_snapshot_id(){ +# Function to get latest PITR snapshot ID +function get_latest_pitr_snapshot_id() { local latest_pitr_snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PITR" --latest 1 --json | jq -r '.[0].short_id') echo $(date) ${ENV_NAME} "Getting the latest PITR snapshot: ${latest_pitr_snapshot_id}" >> ${BACKUP_LOG_FILE} echo ${latest_pitr_snapshot_id} } -function get_dump_name_by_snapshot_id(){ +# Function to get dump name by snapshot ID +function get_dump_name_by_snapshot_id() { local snapshot_id="$1" local dump_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') echo $(date) ${ENV_NAME} "Getting the dump name: ${dump_name}" >> ${BACKUP_LOG_FILE} echo ${dump_name} } - -function get_binlog_file_by_snapshot_id(){ +# Function to get binlog file by snapshot ID +function get_binlog_file_by_snapshot_id() { local snapshot_id="$1" local binlog_file=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[2]') echo $(date) ${ENV_NAME} "Getting the start binlog file name: ${binlog_file}" >> ${BACKUP_LOG_FILE} echo ${binlog_file} } +# Function to get PostgreSQL WAL location +function get_pg_wal_location() { + local wal_location=$(PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -t -c "SELECT pg_current_wal_lsn();" | tr -d ' ') + echo $(date) ${ENV_NAME} "Getting the WAL location: ${wal_location}" >> ${BACKUP_LOG_FILE} + echo $wal_location +} -function backup_redis(){ - source /etc/jelastic/metainf.conf; - RDB_TO_REMOVE=$(ls -d /tmp/* |grep redis-dump.*) +# Function to backup PostgreSQL WAL files +function backup_postgres_wal() { + local wal_dir="/var/lib/postgresql/wal_archive" + echo $(date) ${ENV_NAME} "Backing up PostgreSQL WAL files..." | tee -a $BACKUP_LOG_FILE + rm -rf ${BINLOGS_BACKUP_DIR} && mkdir -p ${BINLOGS_BACKUP_DIR} + + # Copy WAL files from archive directory + if [ -d "$wal_dir" ]; then + cp -r $wal_dir/* ${BINLOGS_BACKUP_DIR}/ || { echo "WAL files backup failed."; exit 1; } + else + echo "Warning: WAL archive directory does not exist" | tee -a $BACKUP_LOG_FILE + fi + echo "PostgreSQL WAL files backup completed." | tee -a $BACKUP_LOG_FILE +} + +# Function to create WAL snapshot +function create_wal_snapshot() { + local snapshot_name="$1" + echo $(date) ${ENV_NAME} "Saving the WAL files to ${snapshot_name} snapshot" | tee -a ${BACKUP_LOG_FILE} + GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${snapshot_name}" --tag "PGWAL" ${BINLOGS_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} +} + +# Function to backup Redis +function backup_redis() { + source /etc/jelastic/metainf.conf + RDB_TO_REMOVE=$(ls -d /tmp/* | grep redis-dump.*) rm -f ${RDB_TO_REMOVE} - export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} |grep '^requirepass'|awk '{print $2}'); + export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} | grep '^requirepass' | awk '{print $2}') if [ "$REDIS_TYPE" == "-standalone" ]; then redis-cli --rdb /tmp/redis-dump-standalone.rdb else - export MASTERS_LIST=$(redis-cli cluster nodes|grep master|grep -v fail|awk '{print $2}'|awk -F : '{print $1}'); - for i in $MASTERS_LIST - do + export MASTERS_LIST=$(redis-cli cluster nodes | grep master | grep -v fail | awk '{print $2}' | awk -F : '{print $1}') + for i in $MASTERS_LIST; do redis-cli -h $i --rdb /tmp/redis-dump-cluster-$i.rdb || { echo "DB backup process failed."; exit 1; } done fi } -function backup_postgres(){ - PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } - PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > db_backup.sql || { echo "DB backup process failed."; exit 1; } +# Function to backup PostgreSQL +function backup_postgres() { + PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { + echo "DB credentials specified in add-on settings are incorrect!" | tee -a $BACKUP_LOG_FILE + exit 1 + } + + if [ "$PITR" == "true" ]; then + # Get current WAL location before backup + local wal_location=$(get_pg_wal_location) + + # Perform backup with WAL position + PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > ${DUMP_BACKUP_DIR}/db_backup.sql || { + echo "DB backup process failed." | tee -a $BACKUP_LOG_FILE + exit 1 + } + echo $wal_location > ${DUMP_BACKUP_DIR}/wal_location + + # Get latest PITR snapshot and backup WAL files if exists + local latest_pitr_snapshot_id=$(get_latest_pitr_snapshot_id) + if [ "x$latest_pitr_snapshot_id" != "xnull" ]; then + local dump_name=$(get_dump_name_by_snapshot_id "$latest_pitr_snapshot_id") + backup_postgres_wal + create_wal_snapshot "${dump_name}" + fi + else + # Regular backup without PITR + PGPASSWORD="${DBPASSWD}" pg_dumpall -U webadmin --clean --if-exist > ${DUMP_BACKUP_DIR}/db_backup.sql || { + echo "DB backup process failed." | tee -a $BACKUP_LOG_FILE + exit 1 + } + fi } -function backup_mongodb(){ +# Function to backup MongoDB +function backup_mongodb() { if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then - RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf|awk '{print $2}'); - RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest"; + RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf | awk '{print $2}') + RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest" else - RS_SUFFIX=""; + RS_SUFFIX="" fi - TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) + TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) if [ "$TLS_MODE" == "requireTLS" ]; then SSL_TLS_OPTIONS="--ssl --sslPEMKeyFile=/var/lib/jelastic/keys/SSL-TLS/client/client.pem --sslCAFile=/var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsInsecure" else SSL_TLS_OPTIONS="" fi - mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" --out="${DUMP_BACKUP_DIR}" + mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" --out="${DUMP_BACKUP_DIR}" } -function backup_mysql_dump(){ +# Function to backup MySQL dump +function backup_mysql_dump() { ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } if [ "$PITR" == "true" ]; then ${DUMP_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --master-data=2 --flush-logs --force --single-transaction --quote-names --opt --all-databases > ${DUMP_BACKUP_DIR}/${SQL_DUMP_NAME} || { echo "DB backup process failed."; exit 1; } @@ -223,6 +328,7 @@ function backup_mysql_dump(){ fi } +# Function to backup MySQL binary logs function backup_mysql_binlogs() { local start_binlog_file="$1" echo $(date) ${ENV_NAME} "Backing up MySQL binary logs from $start_binlog_file..." | tee -a $BACKUP_LOG_FILE @@ -231,56 +337,67 @@ function backup_mysql_binlogs() { echo "MySQL binary logs backup completed." | tee -a $BACKUP_LOG_FILE } - +# Function to perform PITR backup for MySQL function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "Starting Point-In-Time Recovery (PITR) backup..." | tee -a $BACKUP_LOG_FILE - backup_mysql_dump; - backup_mysql_binlogs; + backup_mysql_dump + backup_mysql_binlogs echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE } - -function create_binlog_snapshot(){ +# Function to create binlog snapshot +function create_binlog_snapshot() { local snapshot_name="$1" echo $(date) ${ENV_NAME} "Saving the BINLOGS to ${snapshot_name} snapshot" | tee -a ${BACKUP_LOG_FILE} GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${snapshot_name}" --tag "BINLOGS" ${BINLOGS_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} - } -function backup_mysql(){ - backup_mysql_dump; +# Function to backup MySQL +function backup_mysql() { + local exit_code=0 + backup_mysql_dump || exit_code=$? + + if [ $exit_code -ne 0 ]; then + echo "Error: MySQL dump failed" | tee -a "$BACKUP_LOG_FILE" + return $exit_code + } + if [ "$PITR" == "true" ]; then + local latest_pitr_snapshot_id latest_pitr_snapshot_id=$(get_latest_pitr_snapshot_id) + if [ "x$latest_pitr_snapshot_id" != "xnull" ]; then - dump_name=$(get_dump_name_by_snapshot_id $latest_pitr_snapshot_id) - start_binlog_file=$(get_binlog_file_by_snapshot_id $latest_pitr_snapshot_id) - backup_mysql_binlogs $start_binlog_file + local dump_name start_binlog_file + dump_name=$(get_dump_name_by_snapshot_id "$latest_pitr_snapshot_id") + start_binlog_file=$(get_binlog_file_by_snapshot_id "$latest_pitr_snapshot_id") + + backup_mysql_binlogs "$start_binlog_file" create_binlog_snapshot "${dump_name}" fi fi } -### Main section -echo $$ > /var/run/${ENV_NAME}_backup.pid -echo $(date) ${ENV_NAME} "Creating the ${BACKUP_TYPE} backup (using the backup addon with commit id ${BACKUP_ADDON_COMMIT_ID}) on storage node ${NODE_ID}" | tee -a ${BACKUP_LOG_FILE} -check_backup_repo -rotate_snapshots -source /etc/jelastic/metainf.conf; -echo $(date) ${ENV_NAME} "Creating the DB dump" | tee -a ${BACKUP_LOG_FILE} -if [ "$COMPUTE_TYPE" == "redis" ]; then - backup_redis; - -elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - backup_mongodb; - -elif [ "$COMPUTE_TYPE" == "postgres" ]; then - backup_postgres; - -else - backup_mysql; +# Main section improvement +main() { + echo "$(date) ${ENV_NAME} Starting backup process..." | tee -a "${BACKUP_LOG_FILE}" + + check_backup_repo + rotate_snapshots + source /etc/jelastic/metainf.conf + + echo "$(date) ${ENV_NAME} Creating DB dump..." | tee -a "${BACKUP_LOG_FILE}" + + case "$COMPUTE_TYPE" in + redis) backup_redis ;; + mongodb) backup_mongodb ;; + postgres) backup_postgres ;; + *) backup_mysql ;; + esac + + create_snapshot + rotate_snapshots + check_backup_repo +} -fi -create_snapshot; -rotate_snapshots; -check_backup_repo; -rm -f /var/run/${ENV_NAME}_backup.pid +# Execute main function +main "$@" diff --git a/scripts/checkCredentials.sh b/scripts/checkCredentials.sh index ab58258..8d0133f 100644 --- a/scripts/checkCredentials.sh +++ b/scripts/checkCredentials.sh @@ -3,27 +3,29 @@ DBUSER=$2 DBPASSWD=$3 -function checkCredentials(){ +# Function to check database credentials +function checkCredentials() { source /etc/jelastic/metainf.conf jem service start >> /dev/null if [ "$COMPUTE_TYPE" == "postgres" ]; then - PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || exit 1; + PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || exit 1 elif [ "$COMPUTE_TYPE" == "mariadb" ] || [ "$COMPUTE_TYPE" == "mysql" ] || [ "$COMPUTE_TYPE" == "percona" ]; then - mysql -h localhost -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" 1>/dev/null || exit 1; + mysql -h localhost -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" 1>/dev/null || exit 1 elif [ "$COMPUTE_TYPE" == "mongodb" ]; then - which mongo && CLIENT="mongo" || CLIENT="mongosh" - TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) + which mongo && CLIENT="mongo" || CLIENT="mongosh" + TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) if [ "$TLS_MODE" == "requireTLS" ]; then - SSL_TLS_OPTIONS="--tls --tlsCertificateKeyFile /var/lib/jelastic/keys/SSL-TLS/client/client.pem --tlsCAFile /var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsAllowInvalidHostnames" + SSL_TLS_OPTIONS="--tls --tlsCertificateKeyFile /var/lib/jelastic/keys/SSL-TLS/client/client.pem --tlsCAFile /var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsAllowInvalidHostnames" else - SSL_TLS_OPTIONS="" + SSL_TLS_OPTIONS="" fi echo "show dbs" | ${CLIENT} --shell ${SSL_TLS_OPTIONS} --username ${DBUSER} --password ${DBPASSWD} --authenticationDatabase admin "mongodb://localhost:27017" else - true; + true fi } +# Main script logic if [ "x$1" == "xcheckCredentials" ]; then checkCredentials fi diff --git a/scripts/mariadb-restore.sh b/scripts/mariadb-restore.sh deleted file mode 100644 index 7be9075..0000000 --- a/scripts/mariadb-restore.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -DBUSER=$1 -DBPASSWD=$2 -RESTORE_TYPE=$3 # 'full' or 'pitr' -FULL_BACKUP_FILE=$4 -STOP_TIME=$5 # For PITR, specify the datetime until which to apply the binary logs (format: YYYY-MM-DD HH:MM:SS) - -SERVER_IP_ADDR=$(ip a | grep -A1 venet0 | grep inet | awk '{print $2}' | sed 's/\/[0-9]*//g' | tail -n 1) -[ -n "${SERVER_IP_ADDR}" ] || SERVER_IP_ADDR="localhost" - -if which mariadb 2>/dev/null; then - CLIENT_APP="mariadb" -else - CLIENT_APP="mysql" -fi - -function restore_full() { - echo $(date) ${ENV_NAME} "Restoring the full backup from ${FULL_BACKUP_FILE}" | tee -a $BACKUP_LOG_FILE; - ${CLIENT_APP} --silent -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} --force < ${FULL_BACKUP_FILE} - if [ $? -eq 0 ]; then - echo $(date) ${ENV_NAME} "Full backup restoration completed successfully." | tee -a $BACKUP_LOG_FILE; - else - echo $(date) ${ENV_NAME} "Error occurred while restoring the full backup." | tee -a $BACKUP_LOG_FILE; - exit 1 - fi -} - -function restore_pitr() { - echo $(date) ${ENV_NAME} "Restoring full backup for PITR..." | tee -a $BACKUP_LOG_FILE; - restore_full - if [ -z "${STOP_TIME}" ]; then - echo "Error: Please specify the stop time for PITR." - exit 1 - fi - - echo "Applying binary logs until ${STOP_TIME} for PITR..." - - mysqlbinlog --stop-datetime="${STOP_TIME}" /opt/backup/mysql_binlogs/mysql-bin.* | ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} - if [ $? -eq 0 ]; then - echo "Binary logs applied successfully up to ${STOP_TIME}." - else - echo "Error occurred while applying binary logs." - exit 1 - fi -} - -if [ "${RESTORE_TYPE}" == "full" ]; then - restore_full -elif [ "${RESTORE_TYPE}" == "pitr" ]; then - restore_pitr -else - echo "Invalid restore type. Use 'full' for full restore or 'pitr' for point-in-time recovery." - exit 1 -fi diff --git a/scripts/mongo-restore.sh b/scripts/mongo-restore.sh deleted file mode 100644 index 7f2a69d..0000000 --- a/scripts/mongo-restore.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then - export RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf|awk '{print $2}'); - export RS_SUFFIX="/?replicaSet=${RS_NAME}&readPreference=nearest"; -else - export RS_SUFFIX=""; -fi -TLS_MODE=$(yq eval '.net.tls.mode' /etc/mongod.conf) -if [ "$TLS_MODE" == "requireTLS" ]; then - SSL_TLS_OPTIONS="--ssl --sslPEMKeyFile=/var/lib/jelastic/keys/SSL-TLS/client/client.pem --sslCAFile=/var/lib/jelastic/keys/SSL-TLS/client/root.pem --tlsInsecure" -else - SSL_TLS_OPTIONS="" -fi -mongorestore ${SSL_TLS_OPTIONS} --uri="mongodb://${1}:${2}@localhost${RS_SUFFIX}" ~/dump 1>/dev/null diff --git a/scripts/pitr.sh b/scripts/pitr.sh index 51385d3..c9f4d83 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -4,14 +4,19 @@ ACTION=$1 DBUSER=$2 DBPASSWD=$3 +# Configuration file paths PITR_CONF_MYSQL='/etc/mysql/conf.d/pitr.cnf' PITR_CONF_PG='/etc/postgresql/12/main/postgresql.conf' ARCHIVE_DIR_PG='/var/lib/postgresql/wal_archive' BACKUP_DIR_PG='/var/lib/postgresql/backups' +# Source external configuration source /etc/jelastic/metainf.conf +# Format compute type version COMPUTE_TYPE_FULL_VERSION_FORMATTED=$(echo "$COMPUTE_TYPE_FULL_VERSION" | sed 's/\.//') + +# Determine binlog expire settings based on compute type if [[ ("$COMPUTE_TYPE" == "mysql" || "$COMPUTE_TYPE" == "percona") && "$COMPUTE_TYPE_FULL_VERSION_FORMATTED" -ge "81" ]]; then BINLOG_EXPIRE_SETTING="binlog_expire_logs_seconds" EXPIRY_SETTING="604800" @@ -23,12 +28,14 @@ else EXPIRY_SETTING="" fi +# PostgreSQL WAL archive settings WAL_ARCHIVE_SETTING="archive_mode" WAL_ARCHIVE_COMMAND="archive_command" WAL_TIMEOUT_SETTING="archive_timeout" WAL_TIMEOUT_VALUE="60" WAL_ARCHIVE_ON="on" +# Function to check PITR configuration for MySQL check_pitr_mysql() { LOG_BIN=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE 'log_bin';" | grep "ON") EXPIRE_LOGS=$(mysql -u"$DBUSER" -p"$DBPASSWD" -se "SHOW VARIABLES LIKE '$BINLOG_EXPIRE_SETTING';" | awk '{ print $2 }') @@ -40,6 +47,7 @@ check_pitr_mysql() { fi } +# Function to set up PITR configuration for MySQL setup_pitr_mysql() { check_pitr_mysql | grep -q '"result":0' if [[ $? -eq 0 ]]; then @@ -55,6 +63,7 @@ $BINLOG_EXPIRE_SETTING=$EXPIRY_SETTING jem service restart; } +# Function to check PITR configuration for PostgreSQL check_pitr_pg() { ARCHIVE_MODE=$(sudo -u postgres psql -U "$DBUSER" -c "SHOW $WAL_ARCHIVE_SETTING;" | grep "on") ARCHIVE_COMMAND=$(sudo -u postgres psql -U "$DBUSER" -c "SHOW $WAL_ARCHIVE_COMMAND;" | grep "$ARCHIVE_DIR_PG") @@ -67,6 +76,7 @@ check_pitr_pg() { fi } +# Function to set up PITR configuration for PostgreSQL setup_pitr_pg() { check_pitr_pg | grep -q '"result":0' if [[ $? -eq 0 ]]; then @@ -88,10 +98,10 @@ archive_timeout = $WAL_TIMEOUT_VALUE fi jem service restart; - echo '{"result":0}'; } +# Main script logic case $ACTION in checkPitr) if [[ "$COMPUTE_TYPE" == "mysql" || "$COMPUTE_TYPE" == "percona" || "$COMPUTE_TYPE" == "mariadb" ]]; then @@ -112,6 +122,6 @@ case $ACTION in fi ;; *) - echo "checkPitr or setupPitr." + echo "Usage: $0 {checkPitr|setupPitr} DBUSER DBPASSWD" ;; esac diff --git a/scripts/postgres-restore.sh b/scripts/postgres-restore.sh deleted file mode 100644 index 42b9aea..0000000 --- a/scripts/postgres-restore.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -CLIENT_APP="psql" -ORIG_BACKUP="/root/db_backup.sql" -TEMP_BACKUP="/tmp/db_backup.sql" - -[ -f "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP" -cp "$ORIG_BACKUP" "$TEMP_BACKUP" - -sed -i -e '/^CREATE ROLE webadmin/d' \ - -e '/^CREATE ROLE postgres/d' \ - -e '/^DROP ROLE IF EXISTS postgres/d' \ - -e '/^DROP ROLE IF EXISTS webadmin/d' \ - -e '/^ALTER ROLE postgres WITH SUPERUSER/d' \ - -e '/^ALTER ROLE webadmin WITH SUPERUSER/d' "$TEMP_BACKUP" - -PGPASSWORD=${2} ${CLIENT_APP} --no-readline -q -U ${1} -d postgres < "$TEMP_BACKUP" > /dev/null; - -[ -f "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP" diff --git a/scripts/redis-restore.sh b/scripts/redis-restore.sh deleted file mode 100644 index dda7c22..0000000 --- a/scripts/redis-restore.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -REDIS_CONF_PATH=$(realpath /etc/redis.conf) -RDB_TO_RESTORE=$(ls -d /tmp/* |grep redis-dump.*); - -cd tmp; wget https://github.com/tair-opensource/RedisShake/releases/download/v3.1.11/redis-shake-linux-amd64.tar.gz; -tar -xf redis-shake-linux-amd64.tar.gz; -grep -q '^cluster-enabled yes' ${REDIS_CONF_PATH} && REDIS_TYPE="cluster" || REDIS_TYPE="standalone"; -sed -ci -e "s/^type =.*/type = '${REDIS_TYPE}'/" restore.toml; -sed -ci -e "1s/^type =.*/type = 'restore'/" restore.toml; -export REDISCLI_AUTH=$(cat ${REDIS_CONF_PATH} |grep '^requirepass'|awk '{print $2}'); -sed -ci -e "s/^password =.*/password = '${REDISCLI_AUTH}'/" restore.toml; -RESTORE_MASTER_ID=$(redis-cli cluster nodes|grep master|grep -v fail|head -n 1|awk '{print $2}'|awk -F : '{print $1}') -sed -ci -e "s/^address =.*/address = '${RESTORE_MASTER_ID}:6379'/" restore.toml; -for i in ${RDB_TO_RESTORE} -do - sed -ci -e "s|^rdb_file_path =.*|rdb_file_path = '${i}'|" restore.toml; - ./redis-shake restore.toml 1>/dev/null -done -rm -f ${RDB_TO_RESTORE} -rm -f redis-shake* sync.toml restore.toml diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index b6dd55c..e8362fa 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -185,6 +185,125 @@ function restore_redis(){ rm -f redis-shake* sync.toml restore.toml } +# Function to get WAL location by snapshot ID +function get_wal_location_by_snapshot_id() { + local snapshot_id="$1" + local wal_location=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[2]') + echo "$(date) ${ENV_NAME} Getting the WAL location: ${wal_location}" >> ${RESTORE_LOG_FILE} + echo "$wal_location" +} + +# Function to get WAL snapshot ID by name +function get_wal_snapshot_id_by_name() { + local backup_name="$1" + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PGWAL" --json | jq -r --arg backup_name "$backup_name" '.[] | select(.tags[0] | contains($backup_name)) | .short_id') + + if [[ $? -ne 0 || -z "$snapshot_id" ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to get WAL snapshot ID" | tee -a ${RESTORE_LOG_FILE} + exit 1 + fi + + echo "$(date) ${ENV_NAME} Getting WAL snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} + echo "$snapshot_id" +} + +# Function to restore PostgreSQL WAL files +function restore_postgres_wal() { + local target_time="$1" + local wal_dir="/var/lib/postgresql/wal_archive" + + echo "$(date) ${ENV_NAME} Restoring WAL files until $target_time..." | tee -a ${RESTORE_LOG_FILE} + + # Ensure WAL archive directory exists + if [ ! -d "$wal_dir" ]; then + sudo mkdir -p "$wal_dir" + sudo chown postgres:postgres "$wal_dir" + fi + + # Copy WAL files to archive directory + sudo cp -r ${BINLOGS_BACKUP_DIR}/* "$wal_dir/" + sudo chown -R postgres:postgres "$wal_dir" + + # Create recovery configuration + sudo -u postgres bash -c "cat > /var/lib/postgresql/data/recovery.signal << EOF +# Recovery configuration +restore_command = 'cp /var/lib/postgresql/wal_archive/%f %p' +recovery_target_time = '$target_time' +EOF" + + echo "$(date) ${ENV_NAME} WAL files restored successfully" >> ${RESTORE_LOG_FILE} +} + +# Enhanced PostgreSQL restore function with PITR support +function restore_postgres() { + if [ "$PITR" == "true" ]; then + # Get snapshot before specified time + dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") + dump_snapshot_name=$(get_snapshot_name_by_id "${dump_snapshot_id}") + + # Get associated WAL snapshot + wal_snapshot_id=$(get_wal_snapshot_id_by_name "${dump_snapshot_name}") + + # Restore main backup + restore_snapshot_by_id "${dump_snapshot_id}" + + # Process dump file + local ORIG_BACKUP="${DUMP_BACKUP_DIR}/db_backup.sql" + local TEMP_BACKUP="/tmp/db_backup.sql" + + [ -f "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP" + cp "$ORIG_BACKUP" "$TEMP_BACKUP" + + # Remove problematic role commands + sed -i -e '/^CREATE ROLE webadmin/d' \ + -e '/^CREATE ROLE postgres/d' \ + -e '/^DROP ROLE IF EXISTS postgres/d' \ + -e '/^DROP ROLE IF EXISTS webadmin/d' \ + -e '/^ALTER ROLE postgres WITH SUPERUSER/d' \ + -e '/^ALTER ROLE webadmin WITH SUPERUSER/d' "$TEMP_BACKUP" + + # Restore the database + PGPASSWORD="${DBPASSWD}" psql --no-readline -q -U "${DBUSER}" -d postgres < "$TEMP_BACKUP" + + # Restore WAL files + restore_snapshot_by_id "${wal_snapshot_id}" + restore_postgres_wal "${PITR_TIME}" + + # Restart PostgreSQL to apply recovery + jem service restart postgresql + + [ -f "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP" + + else + # Regular restore without PITR + local ORIG_BACKUP="${DUMP_BACKUP_DIR}/db_backup.sql" + local TEMP_BACKUP="/tmp/db_backup.sql" + + dump_snapshot_id=$(get_dump_snapshot_id_by_name "${BACKUP_NAME}") + restore_snapshot_by_id "${dump_snapshot_id}" + + [ -f "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP" + cp "$ORIG_BACKUP" "$TEMP_BACKUP" + + # Remove problematic role commands + sed -i -e '/^CREATE ROLE webadmin/d' \ + -e '/^CREATE ROLE postgres/d' \ + -e '/^DROP ROLE IF EXISTS postgres/d' \ + -e '/^DROP ROLE IF EXISTS webadmin/d' \ + -e '/^ALTER ROLE postgres WITH SUPERUSER/d' \ + -e '/^ALTER ROLE webadmin WITH SUPERUSER/d' "$TEMP_BACKUP" + + PGPASSWORD="${DBPASSWD}" psql --no-readline -q -U "${DBUSER}" -d postgres < "$TEMP_BACKUP" > /dev/null + if [[ $? -ne 0 ]]; then + echo "$(date) ${ENV_NAME} Error: Failed to restore PostgreSQL dump" | tee -a ${RESTORE_LOG_FILE} + [ -f "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP" + exit 1 + fi + + echo "$(date) ${ENV_NAME} PostgreSQL dump restored successfully" | tee -a ${RESTORE_LOG_FILE} + [ -f "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP" + fi +} function restore_mysql(){ if [ "$PITR" == "true" ]; then diff --git a/scripts/restote-test.sh b/scripts/restote-test.sh deleted file mode 100644 index b236784..0000000 --- a/scripts/restote-test.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/bin/bash - -DBUSER=$1 -DBPASSWD=$2 -RESTORE_LOG_FILE=$3 -PITR=$4 - -DUMP_BACKUP_DIR=/root/backup/dump -BINLOGS_BACKUP_DIR=/root/backup/binlogs -SQL_DUMP_NAME=db_backup.sql - -#rm -rf $DUMP_BACKUP_DIR $BINLOGS_BACKUP_DIR - -if [ -f /root/.backupedenv ]; then - ENV_NAME=$(cat /root/.backupedenv) -else - echo "The /root/.backupedenv file with ENV_NAME doesnt exist." - exit 1; -fi - -if [ -z "$PITR" ]; then - PITR="false" -fi - -if [ "$PITR" == "true" ]; then - if [ -f /root/.backuptime ]; then - PITR_TIME=$(cat /root/.backuptime) - else - echo "The /root/.backuptime file with BACKUP_TIME doesnt exist." - exit 1; - fi -else - if [ -f /root/.backupid ]; then - BACKUP_NAME=$(cat /root/.backupid) - else - echo "The /root/.backupid file with BACKUP_NAME doesnt exist." - exit 1; - fi -fi - -function get_snapshot_id_before_time() { - local target_datetime="$1" - - while read snapshot_time snapshot_id snapshot_tag; do - snapshot_tag_date=$(echo "$snapshot_tag" | grep -oP '\d{4}-\d{2}-\d{2}_\d{6}') - snapshot_datetime=$(echo "$snapshot_tag_date" | sed 's/_/ /' | sed 's/\(....\)-\(..\)-\(..\) \(..\)\(..\)\(..\)/\1-\2-\3 \4:\5:\6/') - - snapshot_datetime_epoch=$(date -d "$snapshot_datetime" +%s) - target_epoch=$(date -d "$target_datetime" +%s) - - if [ "$snapshot_datetime_epoch" -le "$target_epoch" ]; then - result_snapshot_id="$snapshot_id" - break - fi - done < <(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PITR" --json | jq -r '.[] | "\(.time) \(.short_id) \(.tags[0])"' | sort -r) - - if [[ -z "$result_snapshot_id" ]]; then - echo "$(date) ${ENV_NAME} Error: Failed to get DB dump snapshot ID before time $target_datetime" | tee -a ${RESTORE_LOG_FILE} - exit 1 - fi - echo "$(date) ${ENV_NAME} Getting DB dump snapshot ID before time $target_datetime: $result_snapshot_id" >> ${RESTORE_LOG_FILE} - echo "$result_snapshot_id"; -} - -function get_dump_snapshot_id_by_name(){ - local backup_name="$1" - local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains("'$backup_name'")) | select((.tags[1] != null and (.tags[1] | contains("BINLOGS")) | not) // true) | .short_id') - if [[ $? -ne 0 || -z "$snapshot_id" ]]; then - echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID" | tee -a ${RESTORE_LOG_FILE} - exit 1 - fi - echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} - echo $snapshot_id -} - -function get_binlog_snapshot_id_by_name(){ - local backup_name="$1" - local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r --arg backup_name "$backup_name" '.[] | select(.tags[0] | contains($backup_name)) | .short_id') - - if [[ $? -ne 0 || -z "$snapshot_id" ]]; then - echo "$(date) ${ENV_NAME} Error: Failed to get DB binlogs snapshot ID" | tee -a ${RESTORE_LOG_FILE} - exit 1 - fi - - echo "$(date) ${ENV_NAME} Getting DB binlogs snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} - echo "$snapshot_id" -} - -function get_snapshot_name_by_id(){ - local snapshot_id="$1" - local snapshot_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') - if [[ $? -ne 0 || -z "${snapshot_name}" ]]; then - echo $(date) ${ENV_NAME} "Error: Failed to get snapshot name for $snapshot_id" | tee -a ${RESTORE_LOG_FILE} - exit 1 - fi - echo $(date) ${ENV_NAME} "Getting the snapshot name: ${snapshot_name}" >> ${RESTORE_LOG_FILE} - echo ${snapshot_name} -} - -function restore_snapshot_by_id(){ - local snapshot_id="$1" - RESTIC_PASSWORD=${ENV_NAME} GOGC=20 restic -r /opt/backup/${ENV_NAME} restore ${snapshot_id} --target / - if [[ $? -ne 0 ]]; then - echo $(date) ${ENV_NAME} "Error: Failed to restore snapshot ID $snapshot_id" | tee -a ${RESTORE_LOG_FILE}; - exit 1 - fi - echo $(date) ${ENV_NAME} "Snapshot ID: $snapshot_id restored successfully" >> ${RESTORE_LOG_FILE} -} - -function restore_mysql_dump(){ - if which mariadb 2>/dev/null; then - CLIENT_APP="mariadb" - else - CLIENT_APP="mysql" - fi - ${CLIENT_APP} -u "${DBUSER}" -p"${DBPASSWD}" < "${DUMP_BACKUP_DIR}/${SQL_DUMP_NAME}" - if [[ $? -ne 0 ]]; then - echo "$(date) ${ENV_NAME} Error: Failed to restore MySQL dump" | tee -a ${RESTORE_LOG_FILE} - exit 1 - fi - echo "$(date) ${ENV_NAME} MySQL dump restored successfully" | tee -a ${RESTORE_LOG_FILE} -} - -function apply_binlogs_until_time(){ - local stop_time="$1" - local binlog_files=($BINLOGS_BACKUP_DIR/mysql-bin.*) - - if which mariadb 2>/dev/null; then - BINLOG_APP="mariadb-binlog" - else - BINLOG_APP="mysqlbinlog" - fi - - for binlog_file in "${binlog_files[@]}"; do - ${BINLOG_APP} --stop-datetime="${stop_time}" "$binlog_file" | mysql -u "${DBUSER}" -p"${DBPASSWD}" - if [[ $? -ne 0 ]]; then - echo "$(date) ${ENV_NAME} Error: Failed to apply binlogs from $binlog_file" | tee -a ${RESTORE_LOG_FILE} - exit 1 - fi - echo "$(date) ${ENV_NAME} Applied binlogs from $binlog_file until $stop_time" >> ${RESTORE_LOG_FILE} - done -} - -function restore_mysql(){ - if [ "$PITR" == "true" ]; then -# dump_snapshot_id=$(get_snapshot_id_before_time "${PITR_TIME}") -# dump_snapshot_name=$(get_snapshot_name_by_id "${dump_snapshot_id}") - -# binlog_snapshot_id=$(get_binlog_snapshot_id_by_name "${dump_snapshot_name}") - -# restore_snapshot_by_id "${dump_snapshot_id}" - restore_mysql_dump - -# restore_snapshot_by_id "${binlog_snapshot_id}" - apply_binlogs_until_time "${PITR_TIME}" - else - dump_snapshot_id=$(get_dump_snapshot_id_by_name "${BACKUP_NAME}") - restore_snapshot_by_id "${dump_snapshot_id}" - restore_mysql_dump - fi -} - -restore_mysql; From b87192f87cda35d1bc635d74817e8982ed33e85f Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Mon, 24 Feb 2025 20:45:47 +0100 Subject: [PATCH 58/78] JE-71293 --- backup.jps | 4 ++-- scripts/restoreOnBeforeInit.js | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backup.jps b/backup.jps index a9f7ff5..6c3a597 100644 --- a/backup.jps +++ b/backup.jps @@ -147,7 +147,7 @@ settings: - type: toggle name: isPitr caption: PITR - tooltip: "Point in time recovery" + tooltip: "Enable Point-In-Time Recovery." value: false hidden: false showIf: @@ -156,7 +156,7 @@ settings: cls: warning height: 30, hideLabel: true, - markup: "Database does not configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon" + markup: "Database is not configured for PITR. Click Apply to configure automatically, or close and configure manually following documentation." - type: displayfield name: displayPitr markup: "" diff --git a/scripts/restoreOnBeforeInit.js b/scripts/restoreOnBeforeInit.js index 28a88aa..599adad 100644 --- a/scripts/restoreOnBeforeInit.js +++ b/scripts/restoreOnBeforeInit.js @@ -87,14 +87,16 @@ if (storage_unavailable_markup === "") { "type": "list", "name": "backupedEnvName", "required": true, - "values": envs + "values": envs, + "tooltip": "Select the environment to restore from" }, { "caption": "Time for restore", "type": "string", "name": "restoreTime", "inputType": "datetime-local", "cls": "x-form-text", - "required": true + "required": true, + "tooltip": "Select specific date and time for point-in-time recovery" } ], "false": [ From a08ebff57d365110385c48404801ed1ff57f74dc Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Mon, 24 Feb 2025 21:13:34 +0100 Subject: [PATCH 59/78] Create pitrOnBeforeInit.js --- scripts/pitrOnBeforeInit.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 scripts/pitrOnBeforeInit.js diff --git a/scripts/pitrOnBeforeInit.js b/scripts/pitrOnBeforeInit.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/scripts/pitrOnBeforeInit.js @@ -0,0 +1 @@ + From 7e6eabc82d20b6cc27a22c7bdd5789ca218e7766 Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Mon, 24 Feb 2025 21:15:42 +0100 Subject: [PATCH 60/78] JE-71293 --- backup.jps | 48 ++----------------------------------- scripts/pitrOnBeforeInit.js | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/backup.jps b/backup.jps index 6c3a597..fe93d47 100644 --- a/backup.jps +++ b/backup.jps @@ -185,55 +185,11 @@ settings: restore: fields: [] - onBeforeInit: script/restoreOnBeforeInit.js + onBeforeInit: scripts/restoreOnBeforeInit.js pitr: fields: [] - onBeforeInit: | - var respOut; - var pitr_conf_error_markup = "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon"; - var pitr_conf_success_markup = "Database configured for PITR support"; - var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; - - var checkPitrCmd = "wget " + '${baseUrl}' + "/scripts/pitr.sh -O /root/pitr.sh &>> /var/log/run.log; bash /root/pitr.sh checkPitr " + '${settings.dbuser}' + " " + '${settings.dbpass}'; - resp = jelastic.env.control.ExecCmdById('${env.envName}', session, '${nodes.sqldb.master.id}', toJSON([{ command: checkPitrCmd }]), true, "root"); - if (resp.result != 0) return resp; - respOut = resp.responses[0].out; - respOut = JSON.parse(respOut); - if (respOut.result == 702) { - settings.fields.push({ - caption: "PITR", - type: "toggle", - name: "isPitr", - tooltip: "Point in time recovery", - values: false, - hidden: false, - disabled: true - }, { - type: "displayfield", - cls: "warning", - height: 30, - hideLabel: true, - markup: pitr_conf_error_markup - }); - } else { - settings.fields.push({ - caption: "PITR", - type: "toggle", - name: "isPitr", - tooltip: "Point in time recovery", - values: false, - hidden: false, - disabled: false - }, { - type: "displayfield", - cls: "success", - height: 30, - hideLabel: true, - markup: pitr_conf_success_markup - }); - } - return settings; + onBeforeInit: scripts/pitrOnBeforeInit.js onBeforeInit: scripts/backupOnBeforeInit.js diff --git a/scripts/pitrOnBeforeInit.js b/scripts/pitrOnBeforeInit.js index 8b13789..b3b2478 100644 --- a/scripts/pitrOnBeforeInit.js +++ b/scripts/pitrOnBeforeInit.js @@ -1 +1,45 @@ +var respOut; +var pitr_conf_error_markup = "Database doesnt configured for PITR support. Please push apply for automatic configuring or close and manually configure acording to instruction and reinstall addon"; +var pitr_conf_success_markup = "Database configured for PITR support"; +var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; + +var checkPitrCmd = "wget " + '${baseUrl}' + "/scripts/pitr.sh -O /root/pitr.sh &>> /var/log/run.log; bash /root/pitr.sh checkPitr " + '${settings.dbuser}' + " " + '${settings.dbpass}'; +resp = jelastic.env.control.ExecCmdById('${env.envName}', session, '${nodes.sqldb.master.id}', toJSON([{ command: checkPitrCmd }]), true, "root"); +if (resp.result != 0) return resp; +respOut = resp.responses[0].out; +respOut = JSON.parse(respOut); +if (respOut.result == 702) { + settings.fields.push({ + caption: "PITR", + type: "toggle", + name: "isPitr", + tooltip: "Point in time recovery", + values: false, + hidden: false, + disabled: true + }, { + type: "displayfield", + cls: "warning", + height: 30, + hideLabel: true, + markup: pitr_conf_error_markup + }); +} else { + settings.fields.push({ + caption: "PITR", + type: "toggle", + name: "isPitr", + tooltip: "Point in time recovery", + values: false, + hidden: false, + disabled: false + }, { + type: "displayfield", + cls: "success", + height: 30, + hideLabel: true, + markup: pitr_conf_success_markup + }); +} +return settings; From d407cf31fb3255074317525b20f4bb50be19205e Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Sun, 2 Mar 2025 21:20:58 +0100 Subject: [PATCH 61/78] PITR --- scripts/backup-logic.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 085d1a8..9dba96f 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -360,7 +360,7 @@ function backup_mysql() { if [ $exit_code -ne 0 ]; then echo "Error: MySQL dump failed" | tee -a "$BACKUP_LOG_FILE" return $exit_code - } + fi if [ "$PITR" == "true" ]; then local latest_pitr_snapshot_id From 2fce8863da5ce465da59822da05d52fb1d74d043 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Sun, 2 Mar 2025 22:05:19 +0100 Subject: [PATCH 62/78] JE-71293 --- scripts/restoreOnBeforeInit.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/restoreOnBeforeInit.js b/scripts/restoreOnBeforeInit.js index 599adad..375800d 100644 --- a/scripts/restoreOnBeforeInit.js +++ b/scripts/restoreOnBeforeInit.js @@ -16,7 +16,8 @@ if (resp.result == 11) { var updateResticOnStorageCommand = "wget --tries=10 -O /tmp/installUpdateRestic " + baseUrl + "/scripts/installUpdateRestic && mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic"; var respUpdate = api.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": updateResticOnStorageCommand, "params": ""}]), false, "root"); if (respUpdate.result != 0) return respUpdate; - var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": "/root/getBackupsAllEnvs.sh", "params": ""}]), false, "root").responses[0].out; + var getBackupsAllEnvs = "wget --tries=10 -O /root/getBackupsAllEnvs.sh " + baseUrl + "/scripts/getBackupsAllEnvs.sh && chmod +x /root/getBackupsAllEnvs.sh && /root/getBackupsAllEnvs.sh"; + var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": getBackupsAllEnvs, "params": ""}]), false, "root").responses[0].out; var backupList = toNative(new JSONObject(String(backups))); var envs = prepareEnvs(backupList.envs); var backups = prepareBackups(backupList.backups); From 766dc13b4bb64afa8a278f4997d437a0f4a90f3d Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Tue, 4 Mar 2025 17:04:18 +0100 Subject: [PATCH 63/78] JE-71293 --- scripts/getBackupsAllEnvs.sh | 20 +++++++++++--------- scripts/getBackupsAllEnvsJSON.sh | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 scripts/getBackupsAllEnvsJSON.sh diff --git a/scripts/getBackupsAllEnvs.sh b/scripts/getBackupsAllEnvs.sh index 4cc6f94..f26ccc1 100644 --- a/scripts/getBackupsAllEnvs.sh +++ b/scripts/getBackupsAllEnvs.sh @@ -2,18 +2,20 @@ restic self-update &>/dev/null || true -ENV_LIST=$(ls /data) +ENV_LIST=$(ls -Qm /data) -OUTPUT_JSON="{\"result\": 0, \"envs\": [" +OUTPUT_JSON="{\"result\": 0, \"envs\": [${ENV_LIST}], \"backups\": {" if [ -n "$ENV_LIST" ]; then - for i in $ENV_LIST; do - DIRECTORY_LIST=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots --json) - [ -z "$DIRECTORY_LIST" ] || OUTPUT_JSON="${OUTPUT_JSON}{\"${i}\": ${DIRECTORY_LIST}}," + + for i in $(ls /data) + do + DIRECTORY_LIST=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots|awk '{print $5}'|grep -v 'Paths'|grep '[0-9.*]'|awk '{print "\""$1"\""}'|tr '\n' ',') + [ -z "${DIRECTORY_LIST}" ] || DIRECTORY_LIST=${DIRECTORY_LIST::-1} + OUTPUT_JSON="${OUTPUT_JSON}\"${i}\":[${DIRECTORY_LIST}]," done - OUTPUT_JSON="${OUTPUT_JSON%,}" -fi -OUTPUT_JSON="${OUTPUT_JSON}]}" + OUTPUT_JSON=${OUTPUT_JSON::-1} +fi -echo $OUTPUT_JSON +echo $OUTPUT_JSON}} \ No newline at end of file diff --git a/scripts/getBackupsAllEnvsJSON.sh b/scripts/getBackupsAllEnvsJSON.sh new file mode 100644 index 0000000..4cc6f94 --- /dev/null +++ b/scripts/getBackupsAllEnvsJSON.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +restic self-update &>/dev/null || true + +ENV_LIST=$(ls /data) + +OUTPUT_JSON="{\"result\": 0, \"envs\": [" + +if [ -n "$ENV_LIST" ]; then + for i in $ENV_LIST; do + DIRECTORY_LIST=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots --json) + [ -z "$DIRECTORY_LIST" ] || OUTPUT_JSON="${OUTPUT_JSON}{\"${i}\": ${DIRECTORY_LIST}}," + done + OUTPUT_JSON="${OUTPUT_JSON%,}" +fi + +OUTPUT_JSON="${OUTPUT_JSON}]}" + +echo $OUTPUT_JSON From b3b06418ee1ac9cc588fe52d13b70bd24ebd72db Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Tue, 4 Mar 2025 17:37:13 +0100 Subject: [PATCH 64/78] JE-71293 --- scripts/backup-logic.sh | 138 ++++++++++++++++++++++++++++++++------- scripts/restore-logic.sh | 18 +++++ 2 files changed, 133 insertions(+), 23 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 9dba96f..14a7719 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -91,14 +91,23 @@ else DUMP_APP="mysqldump" fi -# Function to force install/update Restic +/** + * Forces installation or update of Restic backup tool + * Downloads and installs the latest version of Restic + * Ensures proper permissions and execution rights + */ function forceInstallUpdateRestic() { wget --tries=10 -O /tmp/installUpdateRestic ${BASE_URL}/scripts/installUpdateRestic && \ mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic } -# Function to send email notification +/** + * Sends email notifications for backup events + * Handles platform version checking and email delivery + * @param: none - uses global environment variables + * Sends notifications for stale locks and backup issues + */ function sendEmailNotification() { if [ -e "/usr/lib/jelastic/modules/api.module" ]; then [ -e "/var/run/jem.pid" ] && return 0 @@ -123,7 +132,11 @@ function sendEmailNotification() { fi } -# Function to update Restic +/** + * Updates Restic to latest version or installs if missing + * Attempts self-update first, falls back to force install + * @return: none, exits on critical failure + */ function update_restic() { if which restic; then restic self-update || forceInstallUpdateRestic @@ -132,7 +145,12 @@ function update_restic() { fi } -# Function to check backup repository +/** + * Checks and prepares backup repository + * Initializes new repo if needed + * Handles stale locks and integrity verification + * @return: exits with code 1 on failure + */ function check_backup_repo() { [ -d /opt/backup/${ENV_NAME} ] || mkdir -p /opt/backup/${ENV_NAME} export FILES_COUNT=$(ls -n /opt/backup/${ENV_NAME} | awk '{print $2}') @@ -149,7 +167,12 @@ function check_backup_repo() { fi } -# Function to rotate snapshots +/** + * Manages backup rotation according to retention policy + * Removes old backups keeping specified count + * Handles stale locks during rotation + * @return: exits with code 1 on failure + */ function rotate_snapshots() { echo $(date) ${ENV_NAME} "Rotating snapshots by keeping the last ${BACKUP_COUNT}" | tee -a ${BACKUP_LOG_FILE} if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]]; then @@ -160,21 +183,31 @@ function rotate_snapshots() { { GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic forget -q -r /opt/backup/${ENV_NAME} --keep-last ${BACKUP_COUNT} --prune | tee -a $BACKUP_LOG_FILE; } || { echo "Backup rotation failed."; exit 1; } } -# Function to get binlog file +/** + * Retrieves the current binlog file name from MySQL + * @return: binlog file name + */ function get_binlog_file() { local binlog_file=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $1}') echo $(date) ${ENV_NAME} "Getting the binlog_file: ${binlog_file}" >> ${BACKUP_LOG_FILE} echo $binlog_file } -# Function to get binlog position +/** + * Retrieves the current binlog position from MySQL + * @return: binlog position + */ function get_binlog_position() { local binlog_pos=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $2}') echo $(date) ${ENV_NAME} "Getting the binlog_position: ${binlog_pos}" >> ${BACKUP_LOG_FILE} echo $binlog_pos } -# Function to create snapshot +/** + * Creates a snapshot of the current database state + * Handles different database types and PITR + * @return: none, exits on critical failure + */ function create_snapshot() { source /etc/jelastic/metainf.conf DUMP_NAME=$(date "+%F_%H%M%S_%Z"-${BACKUP_TYPE}\($COMPUTE_TYPE-$COMPUTE_TYPE_FULL_VERSION$REDIS_TYPE$MONGO_TYPE\)) @@ -199,14 +232,21 @@ function create_snapshot() { fi } -# Function to get latest PITR snapshot ID +/** + * Retrieves the latest PITR snapshot ID + * @return: latest PITR snapshot ID + */ function get_latest_pitr_snapshot_id() { local latest_pitr_snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PITR" --latest 1 --json | jq -r '.[0].short_id') echo $(date) ${ENV_NAME} "Getting the latest PITR snapshot: ${latest_pitr_snapshot_id}" >> ${BACKUP_LOG_FILE} echo ${latest_pitr_snapshot_id} } -# Function to get dump name by snapshot ID +/** + * Retrieves the dump name by snapshot ID + * @param: snapshot_id - the snapshot ID to query + * @return: dump name + */ function get_dump_name_by_snapshot_id() { local snapshot_id="$1" local dump_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') @@ -214,7 +254,11 @@ function get_dump_name_by_snapshot_id() { echo ${dump_name} } -# Function to get binlog file by snapshot ID +/** + * Retrieves the binlog file by snapshot ID + * @param: snapshot_id - the snapshot ID to query + * @return: binlog file name + */ function get_binlog_file_by_snapshot_id() { local snapshot_id="$1" local binlog_file=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[2]') @@ -222,14 +266,21 @@ function get_binlog_file_by_snapshot_id() { echo ${binlog_file} } -# Function to get PostgreSQL WAL location +/** + * Retrieves the current PostgreSQL WAL location + * @return: WAL location + */ function get_pg_wal_location() { local wal_location=$(PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -t -c "SELECT pg_current_wal_lsn();" | tr -d ' ') echo $(date) ${ENV_NAME} "Getting the WAL location: ${wal_location}" >> ${BACKUP_LOG_FILE} echo $wal_location } -# Function to backup PostgreSQL WAL files +/** + * Backs up PostgreSQL WAL files + * Copies WAL files from archive directory to backup directory + * @return: none, exits on critical failure + */ function backup_postgres_wal() { local wal_dir="/var/lib/postgresql/wal_archive" echo $(date) ${ENV_NAME} "Backing up PostgreSQL WAL files..." | tee -a $BACKUP_LOG_FILE @@ -244,14 +295,22 @@ function backup_postgres_wal() { echo "PostgreSQL WAL files backup completed." | tee -a $BACKUP_LOG_FILE } -# Function to create WAL snapshot +/** + * Creates a snapshot of PostgreSQL WAL files + * @param: snapshot_name - the name of the snapshot + * @return: none, exits on critical failure + */ function create_wal_snapshot() { local snapshot_name="$1" echo $(date) ${ENV_NAME} "Saving the WAL files to ${snapshot_name} snapshot" | tee -a ${BACKUP_LOG_FILE} GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${snapshot_name}" --tag "PGWAL" ${BINLOGS_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} } -# Function to backup Redis +/** + * Backs up Redis database + * Handles both standalone and cluster modes + * @return: none, exits on critical failure + */ function backup_redis() { source /etc/jelastic/metainf.conf RDB_TO_REMOVE=$(ls -d /tmp/* | grep redis-dump.*) @@ -267,7 +326,11 @@ function backup_redis() { fi } -# Function to backup PostgreSQL +/** + * Backs up PostgreSQL database + * Handles both regular and PITR backups + * @return: none, exits on critical failure + */ function backup_postgres() { PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!" | tee -a $BACKUP_LOG_FILE @@ -301,7 +364,11 @@ function backup_postgres() { fi } -# Function to backup MongoDB +/** + * Backs up MongoDB database + * Handles both standalone and replica set modes + * @return: none, exits on critical failure + */ function backup_mongodb() { if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf | awk '{print $2}') @@ -318,7 +385,11 @@ function backup_mongodb() { mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" --out="${DUMP_BACKUP_DIR}" } -# Function to backup MySQL dump +/** + * Backs up MySQL database dump + * Handles both regular and PITR backups + * @return: none, exits on critical failure + */ function backup_mysql_dump() { ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } if [ "$PITR" == "true" ]; then @@ -328,7 +399,12 @@ function backup_mysql_dump() { fi } -# Function to backup MySQL binary logs +/** + * Backs up MySQL binary logs + * Copies binary logs from specified start file + * @param: start_binlog_file - the starting binlog file + * @return: none, exits on critical failure + */ function backup_mysql_binlogs() { local start_binlog_file="$1" echo $(date) ${ENV_NAME} "Backing up MySQL binary logs from $start_binlog_file..." | tee -a $BACKUP_LOG_FILE @@ -337,7 +413,11 @@ function backup_mysql_binlogs() { echo "MySQL binary logs backup completed." | tee -a $BACKUP_LOG_FILE } -# Function to perform PITR backup for MySQL +/** + * Performs Point-In-Time Recovery (PITR) backup for MySQL + * Handles both dump and binary logs backup + * @return: none, exits on critical failure + */ function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "Starting Point-In-Time Recovery (PITR) backup..." | tee -a $BACKUP_LOG_FILE backup_mysql_dump @@ -345,14 +425,22 @@ function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE } -# Function to create binlog snapshot +/** + * Creates a snapshot of MySQL binary logs + * @param: snapshot_name - the name of the snapshot + * @return: none, exits on critical failure + */ function create_binlog_snapshot() { local snapshot_name="$1" echo $(date) ${ENV_NAME} "Saving the BINLOGS to ${snapshot_name} snapshot" | tee -a ${BACKUP_LOG_FILE} GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${snapshot_name}" --tag "BINLOGS" ${BINLOGS_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} } -# Function to backup MySQL +/** + * Backs up MySQL database + * Handles both regular and PITR backups + * @return: none, exits on critical failure + */ function backup_mysql() { local exit_code=0 backup_mysql_dump || exit_code=$? @@ -377,7 +465,11 @@ function backup_mysql() { fi } -# Main section improvement +/** + * Main function to orchestrate the backup process + * Handles repository checks, snapshot creation, and rotation + * @return: none, exits on critical failure + */ main() { echo "$(date) ${ENV_NAME} Starting backup process..." | tee -a "${BACKUP_LOG_FILE}" diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index e8362fa..86a2d7f 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -38,6 +38,12 @@ else fi fi +/** + * Finds snapshot ID before specified timestamp + * @param {string} target_datetime - Target restoration time + * @return {string} Snapshot ID or exits with error + * Searches through PITR snapshots chronologically + */ function get_snapshot_id_before_time() { local target_datetime="$1" @@ -62,6 +68,12 @@ function get_snapshot_id_before_time() { echo "$result_snapshot_id"; } +/** + * Retrieves snapshot ID for given backup name + * @param {string} backup_name - Name of the backup to find + * @return {string} Snapshot ID or exits with error + * Filters out BINLOGS tagged snapshots + */ function get_dump_snapshot_id_by_name(){ local backup_name="$1" local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains("'$backup_name'")) | select((.tags[1] != null and (.tags[1] | contains("BINLOGS")) | not) // true) | .short_id') @@ -73,6 +85,12 @@ function get_dump_snapshot_id_by_name(){ echo $snapshot_id } +/** + * Retrieves binlog snapshot ID for backup name + * @param {string} backup_name - Name of the backup + * @return {string} Binlog snapshot ID or exits with error + * Specifically searches for BINLOGS tagged snapshots + */ function get_binlog_snapshot_id_by_name(){ local backup_name="$1" local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r --arg backup_name "$backup_name" '.[] | select(.tags[0] | contains($backup_name)) | .short_id') From 50a8b6ff88e75ecf4ff1f2f73e72e2a9d9cf5170 Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Tue, 4 Mar 2025 18:15:49 +0100 Subject: [PATCH 65/78] JE-71293 --- scripts/backup-logic.sh | 198 +++++++++++++++------------------------ scripts/restore-logic.sh | 30 +++--- 2 files changed, 88 insertions(+), 140 deletions(-) diff --git a/scripts/backup-logic.sh b/scripts/backup-logic.sh index 14a7719..b6fa286 100644 --- a/scripts/backup-logic.sh +++ b/scripts/backup-logic.sh @@ -91,23 +91,19 @@ else DUMP_APP="mysqldump" fi -/** - * Forces installation or update of Restic backup tool - * Downloads and installs the latest version of Restic - * Ensures proper permissions and execution rights - */ +# Forces installation or update of Restic backup tool +# Downloads and installs the latest version of Restic +# Ensures proper permissions and execution rights function forceInstallUpdateRestic() { wget --tries=10 -O /tmp/installUpdateRestic ${BASE_URL}/scripts/installUpdateRestic && \ mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && \ chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic } -/** - * Sends email notifications for backup events - * Handles platform version checking and email delivery - * @param: none - uses global environment variables - * Sends notifications for stale locks and backup issues - */ +# Sends email notifications for backup events +# Handles platform version checking and email delivery +# @param: none - uses global environment variables +# Sends notifications for stale locks and backup issues function sendEmailNotification() { if [ -e "/usr/lib/jelastic/modules/api.module" ]; then [ -e "/var/run/jem.pid" ] && return 0 @@ -132,11 +128,9 @@ function sendEmailNotification() { fi } -/** - * Updates Restic to latest version or installs if missing - * Attempts self-update first, falls back to force install - * @return: none, exits on critical failure - */ +# Updates Restic to latest version or installs if missing +# Attempts self-update first, falls back to force install +# @return: none, exits on critical failure function update_restic() { if which restic; then restic self-update || forceInstallUpdateRestic @@ -145,12 +139,10 @@ function update_restic() { fi } -/** - * Checks and prepares backup repository - * Initializes new repo if needed - * Handles stale locks and integrity verification - * @return: exits with code 1 on failure - */ +# Checks and prepares backup repository +# Initializes new repo if needed +# Handles stale locks and integrity verification +# @return: exits with code 1 on failure function check_backup_repo() { [ -d /opt/backup/${ENV_NAME} ] || mkdir -p /opt/backup/${ENV_NAME} export FILES_COUNT=$(ls -n /opt/backup/${ENV_NAME} | awk '{print $2}') @@ -167,12 +159,10 @@ function check_backup_repo() { fi } -/** - * Manages backup rotation according to retention policy - * Removes old backups keeping specified count - * Handles stale locks during rotation - * @return: exits with code 1 on failure - */ +# Manages backup rotation according to retention policy +# Removes old backups keeping specified count +# Handles stale locks during rotation +# @return: exits with code 1 on failure function rotate_snapshots() { echo $(date) ${ENV_NAME} "Rotating snapshots by keeping the last ${BACKUP_COUNT}" | tee -a ${BACKUP_LOG_FILE} if [[ $(ls -A /opt/backup/${ENV_NAME}/locks) ]]; then @@ -183,31 +173,25 @@ function rotate_snapshots() { { GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic forget -q -r /opt/backup/${ENV_NAME} --keep-last ${BACKUP_COUNT} --prune | tee -a $BACKUP_LOG_FILE; } || { echo "Backup rotation failed."; exit 1; } } -/** - * Retrieves the current binlog file name from MySQL - * @return: binlog file name - */ +# Retrieves the current binlog file name from MySQL +# @return: binlog file name function get_binlog_file() { local binlog_file=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $1}') echo $(date) ${ENV_NAME} "Getting the binlog_file: ${binlog_file}" >> ${BACKUP_LOG_FILE} echo $binlog_file } -/** - * Retrieves the current binlog position from MySQL - * @return: binlog position - */ +# Retrieves the current binlog position from MySQL +# @return: binlog position function get_binlog_position() { local binlog_pos=$(${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW MASTER STATUS" | awk 'NR==2 {print $2}') echo $(date) ${ENV_NAME} "Getting the binlog_position: ${binlog_pos}" >> ${BACKUP_LOG_FILE} echo $binlog_pos } -/** - * Creates a snapshot of the current database state - * Handles different database types and PITR - * @return: none, exits on critical failure - */ +# Creates a snapshot of the current database state +# Handles different database types and PITR +# @return: none, exits on critical failure function create_snapshot() { source /etc/jelastic/metainf.conf DUMP_NAME=$(date "+%F_%H%M%S_%Z"-${BACKUP_TYPE}\($COMPUTE_TYPE-$COMPUTE_TYPE_FULL_VERSION$REDIS_TYPE$MONGO_TYPE\)) @@ -232,21 +216,17 @@ function create_snapshot() { fi } -/** - * Retrieves the latest PITR snapshot ID - * @return: latest PITR snapshot ID - */ +# Retrieves the latest PITR snapshot ID +# @return: latest PITR snapshot ID function get_latest_pitr_snapshot_id() { local latest_pitr_snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "PITR" --latest 1 --json | jq -r '.[0].short_id') echo $(date) ${ENV_NAME} "Getting the latest PITR snapshot: ${latest_pitr_snapshot_id}" >> ${BACKUP_LOG_FILE} echo ${latest_pitr_snapshot_id} } -/** - * Retrieves the dump name by snapshot ID - * @param: snapshot_id - the snapshot ID to query - * @return: dump name - */ +# Retrieves the dump name by snapshot ID +# @param: snapshot_id - the snapshot ID to query +# @return: dump name function get_dump_name_by_snapshot_id() { local snapshot_id="$1" local dump_name=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[0]') @@ -254,11 +234,9 @@ function get_dump_name_by_snapshot_id() { echo ${dump_name} } -/** - * Retrieves the binlog file by snapshot ID - * @param: snapshot_id - the snapshot ID to query - * @return: binlog file name - */ +# Retrieves the binlog file by snapshot ID +# @param: snapshot_id - the snapshot ID to query +# @return: binlog file name function get_binlog_file_by_snapshot_id() { local snapshot_id="$1" local binlog_file=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r --arg id "$snapshot_id" '.[] | select(.short_id == $id) | .tags[2]') @@ -266,21 +244,17 @@ function get_binlog_file_by_snapshot_id() { echo ${binlog_file} } -/** - * Retrieves the current PostgreSQL WAL location - * @return: WAL location - */ +# Retrieves the current PostgreSQL WAL location +# @return: WAL location function get_pg_wal_location() { local wal_location=$(PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -t -c "SELECT pg_current_wal_lsn();" | tr -d ' ') echo $(date) ${ENV_NAME} "Getting the WAL location: ${wal_location}" >> ${BACKUP_LOG_FILE} echo $wal_location } -/** - * Backs up PostgreSQL WAL files - * Copies WAL files from archive directory to backup directory - * @return: none, exits on critical failure - */ +# Backs up PostgreSQL WAL files +# Copies WAL files from archive directory to backup directory +# @return: none, exits on critical failure function backup_postgres_wal() { local wal_dir="/var/lib/postgresql/wal_archive" echo $(date) ${ENV_NAME} "Backing up PostgreSQL WAL files..." | tee -a $BACKUP_LOG_FILE @@ -295,22 +269,18 @@ function backup_postgres_wal() { echo "PostgreSQL WAL files backup completed." | tee -a $BACKUP_LOG_FILE } -/** - * Creates a snapshot of PostgreSQL WAL files - * @param: snapshot_name - the name of the snapshot - * @return: none, exits on critical failure - */ +# Creates a snapshot of PostgreSQL WAL files +# @param: snapshot_name - the name of the snapshot +# @return: none, exits on critical failure function create_wal_snapshot() { local snapshot_name="$1" echo $(date) ${ENV_NAME} "Saving the WAL files to ${snapshot_name} snapshot" | tee -a ${BACKUP_LOG_FILE} GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${snapshot_name}" --tag "PGWAL" ${BINLOGS_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} } -/** - * Backs up Redis database - * Handles both standalone and cluster modes - * @return: none, exits on critical failure - */ +# Backs up Redis database +# Handles both standalone and cluster modes +# @return: none, exits on critical failure function backup_redis() { source /etc/jelastic/metainf.conf RDB_TO_REMOVE=$(ls -d /tmp/* | grep redis-dump.*) @@ -326,11 +296,9 @@ function backup_redis() { fi } -/** - * Backs up PostgreSQL database - * Handles both regular and PITR backups - * @return: none, exits on critical failure - */ +# Backs up PostgreSQL database +# Handles both regular and PITR backups +# @return: none, exits on critical failure function backup_postgres() { PGPASSWORD="${DBPASSWD}" psql -U ${DBUSER} -d postgres -c "SELECT current_user" || { echo "DB credentials specified in add-on settings are incorrect!" | tee -a $BACKUP_LOG_FILE @@ -364,11 +332,9 @@ function backup_postgres() { fi } -/** - * Backs up MongoDB database - * Handles both standalone and replica set modes - * @return: none, exits on critical failure - */ +# Backs up MongoDB database +# Handles both standalone and replica set modes +# @return: none, exits on critical failure function backup_mongodb() { if grep -q ^[[:space:]]*replSetName /etc/mongod.conf; then RS_NAME=$(grep ^[[:space:]]*replSetName /etc/mongod.conf | awk '{print $2}') @@ -385,11 +351,9 @@ function backup_mongodb() { mongodump ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" --out="${DUMP_BACKUP_DIR}" } -/** - * Backs up MySQL database dump - * Handles both regular and PITR backups - * @return: none, exits on critical failure - */ +# Backs up MySQL database dump +# Handles both regular and PITR backups +# @return: none, exits on critical failure function backup_mysql_dump() { ${CLIENT_APP} -h ${SERVER_IP_ADDR} -u ${DBUSER} -p${DBPASSWD} mysql --execute="SHOW COLUMNS FROM user" || { echo "DB credentials specified in add-on settings are incorrect!"; exit 1; } if [ "$PITR" == "true" ]; then @@ -399,12 +363,10 @@ function backup_mysql_dump() { fi } -/** - * Backs up MySQL binary logs - * Copies binary logs from specified start file - * @param: start_binlog_file - the starting binlog file - * @return: none, exits on critical failure - */ +# Backs up MySQL binary logs +# Copies binary logs from specified start file +# @param: start_binlog_file - the starting binlog file +# @return: none, exits on critical failure function backup_mysql_binlogs() { local start_binlog_file="$1" echo $(date) ${ENV_NAME} "Backing up MySQL binary logs from $start_binlog_file..." | tee -a $BACKUP_LOG_FILE @@ -413,11 +375,9 @@ function backup_mysql_binlogs() { echo "MySQL binary logs backup completed." | tee -a $BACKUP_LOG_FILE } -/** - * Performs Point-In-Time Recovery (PITR) backup for MySQL - * Handles both dump and binary logs backup - * @return: none, exits on critical failure - */ +# Performs Point-In-Time Recovery (PITR) backup for MySQL +# Handles both dump and binary logs backup +# @return: none, exits on critical failure function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "Starting Point-In-Time Recovery (PITR) backup..." | tee -a $BACKUP_LOG_FILE backup_mysql_dump @@ -425,26 +385,22 @@ function backup_mysql_pitr() { echo $(date) ${ENV_NAME} "PITR backup completed." | tee -a $BACKUP_LOG_FILE } -/** - * Creates a snapshot of MySQL binary logs - * @param: snapshot_name - the name of the snapshot - * @return: none, exits on critical failure - */ +# Creates a snapshot of MySQL binary logs +# @param: snapshot_name - the name of the snapshot +# @return: none, exits on critical failure function create_binlog_snapshot() { local snapshot_name="$1" echo $(date) ${ENV_NAME} "Saving the BINLOGS to ${snapshot_name} snapshot" | tee -a ${BACKUP_LOG_FILE} GOGC=20 RESTIC_COMPRESSION=off RESTIC_PACK_SIZE=8 RESTIC_PASSWORD=${ENV_NAME} restic backup -q -r /opt/backup/${ENV_NAME} --tag "${snapshot_name}" --tag "BINLOGS" ${BINLOGS_BACKUP_DIR} | tee -a ${BACKUP_LOG_FILE} } -/** - * Backs up MySQL database - * Handles both regular and PITR backups - * @return: none, exits on critical failure - */ +# Backs up MySQL database +# Handles both regular and PITR backups +# @return: none, exits on critical failure function backup_mysql() { local exit_code=0 backup_mysql_dump || exit_code=$? - + if [ $exit_code -ne 0 ]; then echo "Error: MySQL dump failed" | tee -a "$BACKUP_LOG_FILE" return $exit_code @@ -453,39 +409,37 @@ function backup_mysql() { if [ "$PITR" == "true" ]; then local latest_pitr_snapshot_id latest_pitr_snapshot_id=$(get_latest_pitr_snapshot_id) - + if [ "x$latest_pitr_snapshot_id" != "xnull" ]; then local dump_name start_binlog_file dump_name=$(get_dump_name_by_snapshot_id "$latest_pitr_snapshot_id") start_binlog_file=$(get_binlog_file_by_snapshot_id "$latest_pitr_snapshot_id") - + backup_mysql_binlogs "$start_binlog_file" create_binlog_snapshot "${dump_name}" fi fi } -/** - * Main function to orchestrate the backup process - * Handles repository checks, snapshot creation, and rotation - * @return: none, exits on critical failure - */ +# Main function to orchestrate the backup process +# Handles repository checks, snapshot creation, and rotation +# @return: none, exits on critical failure main() { echo "$(date) ${ENV_NAME} Starting backup process..." | tee -a "${BACKUP_LOG_FILE}" - + check_backup_repo rotate_snapshots source /etc/jelastic/metainf.conf - + echo "$(date) ${ENV_NAME} Creating DB dump..." | tee -a "${BACKUP_LOG_FILE}" - + case "$COMPUTE_TYPE" in redis) backup_redis ;; mongodb) backup_mongodb ;; postgres) backup_postgres ;; *) backup_mysql ;; esac - + create_snapshot rotate_snapshots check_backup_repo diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index 86a2d7f..28801ad 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -38,12 +38,10 @@ else fi fi -/** - * Finds snapshot ID before specified timestamp - * @param {string} target_datetime - Target restoration time - * @return {string} Snapshot ID or exits with error - * Searches through PITR snapshots chronologically - */ +# Finds snapshot ID before specified timestamp +# @param {string} target_datetime - Target restoration time +# @return {string} Snapshot ID or exits with error +# Searches through PITR snapshots chronologically function get_snapshot_id_before_time() { local target_datetime="$1" @@ -68,12 +66,10 @@ function get_snapshot_id_before_time() { echo "$result_snapshot_id"; } -/** - * Retrieves snapshot ID for given backup name - * @param {string} backup_name - Name of the backup to find - * @return {string} Snapshot ID or exits with error - * Filters out BINLOGS tagged snapshots - */ +# Retrieves snapshot ID for given backup name +# @param {string} backup_name - Name of the backup to find +# @return {string} Snapshot ID or exits with error +# Filters out BINLOGS tagged snapshots function get_dump_snapshot_id_by_name(){ local backup_name="$1" local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains("'$backup_name'")) | select((.tags[1] != null and (.tags[1] | contains("BINLOGS")) | not) // true) | .short_id') @@ -85,12 +81,10 @@ function get_dump_snapshot_id_by_name(){ echo $snapshot_id } -/** - * Retrieves binlog snapshot ID for backup name - * @param {string} backup_name - Name of the backup - * @return {string} Binlog snapshot ID or exits with error - * Specifically searches for BINLOGS tagged snapshots - */ +# Retrieves binlog snapshot ID for backup name +# @param {string} backup_name - Name of the backup +# @return {string} Binlog snapshot ID or exits with error +# Specifically searches for BINLOGS tagged snapshots function get_binlog_snapshot_id_by_name(){ local backup_name="$1" local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r --arg backup_name "$backup_name" '.[] | select(.tags[0] | contains($backup_name)) | .short_id') From acf296cf4acdf58a2a4f3a4163a593305f748483 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 4 Mar 2025 18:26:31 +0100 Subject: [PATCH 66/78] JE-71293 --- scripts/backup-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/backup-manager.js b/scripts/backup-manager.js index 1fbaf8d..6671e7a 100644 --- a/scripts/backup-manager.js +++ b/scripts/backup-manager.js @@ -193,7 +193,7 @@ function BackupManager(config) { nodeId : config.backupExecNode, envName : config.envName, isAlwaysUmount : config.isAlwaysUmount, - isPirt : config.isPirt, + isPitr : config.isPitr, baseUrl : config.baseUrl, dbuser: config.dbuser, dbpass: config.dbpass, From 3c565884c8cd66c3df7d721ed8eddc31d08d5691 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 18 Mar 2025 17:34:20 +0100 Subject: [PATCH 67/78] JE-73946 --- scripts/pitr.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pitr.sh b/scripts/pitr.sh index c9f4d83..f971fba 100644 --- a/scripts/pitr.sh +++ b/scripts/pitr.sh @@ -14,7 +14,7 @@ BACKUP_DIR_PG='/var/lib/postgresql/backups' source /etc/jelastic/metainf.conf # Format compute type version -COMPUTE_TYPE_FULL_VERSION_FORMATTED=$(echo "$COMPUTE_TYPE_FULL_VERSION" | sed 's/\.//') +COMPUTE_TYPE_FULL_VERSION_FORMATTED=$(echo "$COMPUTE_TYPE_FULL_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+)\..*$/\1.\2/' | sed 's/\.//') # Determine binlog expire settings based on compute type if [[ ("$COMPUTE_TYPE" == "mysql" || "$COMPUTE_TYPE" == "percona") && "$COMPUTE_TYPE_FULL_VERSION_FORMATTED" -ge "81" ]]; then From 9bcb84470ed87d71c8fc9583e175afd00c01a5a8 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 18 Mar 2025 18:56:17 +0100 Subject: [PATCH 68/78] Update backup.jps --- backup.jps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backup.jps b/backup.jps index fe93d47..caf727e 100644 --- a/backup.jps +++ b/backup.jps @@ -343,7 +343,7 @@ actions: envName: '${env.envName}', session: session, path: "/root/.backuptime", - nodeGroup: "${targetNodes.nodeGroupp}", + nodeGroup: "${targetNodes.nodeGroup}", nodeid: "-1", userName: "root", body: formattedDateTime From 8b5fa24e2e4084334751cf5316d729839112fed8 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 18 Mar 2025 19:15:52 +0100 Subject: [PATCH 69/78] JE-71293 --- backup.jps | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/backup.jps b/backup.jps index caf727e..0b99d77 100644 --- a/backup.jps +++ b/backup.jps @@ -339,15 +339,9 @@ actions: var dateTimeInput = '${settings.restoreTime}'; var [date, time] = dateTimeInput.split('T'); var formattedDateTime = date + " " + time.slice(0, 5) + ":00"; - return api.environment.file.Write({ - envName: '${env.envName}', - session: session, - path: "/root/.backuptime", - nodeGroup: "${targetNodes.nodeGroup}", - nodeid: "-1", - userName: "root", - body: formattedDateTime - }); + return {result: 0, formattedDateTime: formattedDateTime}; + - cmd[sqldb]: echo ${response.formattedDateTime} > /root/.backuptime; + user: root - else: - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupDir}" > /root/.backupid; user: root From 89bd1ad013d44e5ead27c7bb3ffadd4c36a1ed77 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Tue, 18 Mar 2025 19:19:34 +0100 Subject: [PATCH 70/78] JE-71293 --- backup.jps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backup.jps b/backup.jps index 0b99d77..1449932 100644 --- a/backup.jps +++ b/backup.jps @@ -340,7 +340,7 @@ actions: var [date, time] = dateTimeInput.split('T'); var formattedDateTime = date + " " + time.slice(0, 5) + ":00"; return {result: 0, formattedDateTime: formattedDateTime}; - - cmd[sqldb]: echo ${response.formattedDateTime} > /root/.backuptime; + - cmd[${targetNodes.nodeGroup}]: echo "${response.formattedDateTime}" > /root/.backuptime; user: root - else: - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupDir}" > /root/.backupid; From d123eedfbcc0dc687a40b57babf0e6f882a00c91 Mon Sep 17 00:00:00 2001 From: Henadii Sychevskyi Date: Wed, 19 Mar 2025 11:58:32 +0100 Subject: [PATCH 71/78] JE-71293 --- scripts/restoreOnBeforeInit.js | 58 ++++++++-------------------------- 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/scripts/restoreOnBeforeInit.js b/scripts/restoreOnBeforeInit.js index 375800d..90271d9 100644 --- a/scripts/restoreOnBeforeInit.js +++ b/scripts/restoreOnBeforeInit.js @@ -75,50 +75,20 @@ function prepareBackups(backups) { if (storage_unavailable_markup === "") { if ('${settings.isPitr}' == 'true') { settings.fields.push({ - "type": "toggle", - "name": "isPitr", - "caption": "PITR", - "tooltip": "Point in time recovery", - "value": true, - "hidden": false, - "showIf": { - "true": [ - { - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs, - "tooltip": "Select the environment to restore from" - }, { - "caption": "Time for restore", - "type": "string", - "name": "restoreTime", - "inputType": "datetime-local", - "cls": "x-form-text", - "required": true, - "tooltip": "Select specific date and time for point-in-time recovery" - } - ], - "false": [ - { - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": envs - }, { - "caption": "Backup", - "type": "list", - "name": "backupDir", - "required": true, - "tooltip": "Select the time stamp for which you want to restore the DB dump", - "dependsOn": { - "backupedEnvName" : backups - } - } - ] - } + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": envs, + "tooltip": "Select the environment to restore from" + }, { + "caption": "Time for restore", + "type": "string", + "name": "restoreTime", + "inputType": "datetime-local", + "cls": "x-form-text", + "required": true, + "tooltip": "Select specific date and time for point-in-time recovery" }); if (checkSchema.responses[0].out == "true") { settings.fields.push( From 94012b4b067624428c0835e6472b523c49507d89 Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Wed, 19 Mar 2025 19:16:58 +0100 Subject: [PATCH 72/78] JE-71293 --- scripts/getBackupsAllEnvs.sh | 29 ++++++++------- scripts/restoreOnBeforeInit.js | 66 ++++++++++++++-------------------- 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/scripts/getBackupsAllEnvs.sh b/scripts/getBackupsAllEnvs.sh index f26ccc1..1ca5499 100644 --- a/scripts/getBackupsAllEnvs.sh +++ b/scripts/getBackupsAllEnvs.sh @@ -2,20 +2,25 @@ restic self-update &>/dev/null || true -ENV_LIST=$(ls -Qm /data) +OUTPUT_JSON="{\"result\": 0, \"envs\": {" -OUTPUT_JSON="{\"result\": 0, \"envs\": [${ENV_LIST}], \"backups\": {" +for i in $(ls /data); do + SNAPSHOTS_JSON=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots --json) + DIRECTORY_LIST=$(echo "$SNAPSHOTS_JSON" | jq -r '[.[] | .tags[0] | split(" ")[0]] | map("\"" + . + "\"") | join(",")') -if [ -n "$ENV_LIST" ]; then + SERVER_VERSION=$(echo "$SNAPSHOTS_JSON" | jq -r '[.[] | .tags[0] | capture("\\((?[^)]+)").server_version] | unique | .[0]') - for i in $(ls /data) - do - DIRECTORY_LIST=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots|awk '{print $5}'|grep -v 'Paths'|grep '[0-9.*]'|awk '{print "\""$1"\""}'|tr '\n' ',') - [ -z "${DIRECTORY_LIST}" ] || DIRECTORY_LIST=${DIRECTORY_LIST::-1} - OUTPUT_JSON="${OUTPUT_JSON}\"${i}\":[${DIRECTORY_LIST}]," - done + SERVER=$(echo "$SERVER_VERSION" | cut -d'-' -f1) + VERSION=$(echo "$SERVER_VERSION" | cut -d'-' -f2-) - OUTPUT_JSON=${OUTPUT_JSON::-1} -fi + if [ -n "$DIRECTORY_LIST" ]; then + OUTPUT_JSON="${OUTPUT_JSON}\"${i}\": { \"server\": \"${SERVER}\", \"version\": \"${VERSION}\", \"backups\": [${DIRECTORY_LIST}] }," + else + OUTPUT_JSON="${OUTPUT_JSON}\"${i}\": { \"server\": \"${SERVER}\", \"version\": \"${VERSION}\", \"backups\": [] }," + fi +done -echo $OUTPUT_JSON}} \ No newline at end of file +OUTPUT_JSON=${OUTPUT_JSON::-1} +OUTPUT_JSON="${OUTPUT_JSON}}}" + +echo "$OUTPUT_JSON" \ No newline at end of file diff --git a/scripts/restoreOnBeforeInit.js b/scripts/restoreOnBeforeInit.js index 90271d9..bdd12ce 100644 --- a/scripts/restoreOnBeforeInit.js +++ b/scripts/restoreOnBeforeInit.js @@ -4,9 +4,16 @@ var storageInfo = getStorageNodeid(); var storageEnvDomain = storageInfo.storageEnvShortName; var storageEnvMasterId = storageInfo.storageNodeId; var checkSchemaCommand = "if grep -q '^SCHEME=' /.jelenv; then echo true; else echo false; fi"; +var computeTypeCommand = "grep 'COMPUTE_TYPE=' /etc/jelastic/metainf.conf | cut -d'=' -f2"; var mysql_cluster_markup = "Be careful when restoring the dump from another DB environment (or environment with another replication schema) to the replicated MySQL/MariaDB/Percona solution."; var recovery_addon_markup = "Please use Database Corruption Diagnostic add-on for check after restore, and Database Recovery Add-on for fix if it is needed."; +var checkSchema = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": checkSchemaCommand, "params": ""}]), false, "root"); +if (checkSchema.result != 0) return checkSchema; +var computeTypeResp = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": computeTypeCommand, "params": ""}]), false, "root"); +if (computeTypeResp.result != 0) return computeTypeResp; +var computeType = computeTypeResp.responses[0].out.trim(); + resp = jelastic.env.control.GetEnvInfo(storageEnvDomain, session); if (resp.result != 0 && resp.result != 11) return resp; if (resp.result == 11) { @@ -19,15 +26,27 @@ if (resp.result == 11) { var getBackupsAllEnvs = "wget --tries=10 -O /root/getBackupsAllEnvs.sh " + baseUrl + "/scripts/getBackupsAllEnvs.sh && chmod +x /root/getBackupsAllEnvs.sh && /root/getBackupsAllEnvs.sh"; var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": getBackupsAllEnvs, "params": ""}]), false, "root").responses[0].out; var backupList = toNative(new JSONObject(String(backups))); - var envs = prepareEnvs(backupList.envs); - var backups = prepareBackups(backupList.backups); + + var filteredEnvs = []; + var filteredBackups = {}; + + for (var env in backupList.envs) { + if (backupList.envs.hasOwnProperty(env)) { + var backupInfo = backupList.envs[env]; + + if (backupInfo.server == computeType) { + filteredEnvs.push({ caption: env, value: env }); + + filteredBackups[env] = backupInfo.backups.map(function(backup) { + return { caption: backup, value: backup }; + }); + } + } + } } else { storage_unavailable_markup = "Storage environment " + storageEnvDomain + " is unavailable (stopped/sleeping)."; } -var checkSchema = api.env.control.ExecCmdById("${env.name}", session, ${targetNodes.master.id}, toJSON([{"command": checkSchemaCommand, "params": ""}]), false, "root"); -if (checkSchema.result != 0) return checkSchema; - function getStorageNodeid(){ var storageEnv = '${settings.storageName}' var storageEnvShortName = storageEnv.split(".")[0] @@ -41,37 +60,6 @@ function getStorageNodeid(){ } } -function prepareEnvs(values) { - var aResultValues = []; - - values = values || []; - - for (var i = 0, n = values.length; i < n; i++) { - aResultValues.push({ caption: values[i], value: values[i] }); - } - - return aResultValues; -} - -function prepareBackups(backups) { - var oResultBackups = {}; - var aValues; - - for (var envName in backups) { - if (Object.prototype.hasOwnProperty.call(backups, envName)) { - aValues = []; - - for (var i = 0, n = backups[envName].length; i < n; i++) { - aValues.push({ caption: backups[envName][i], value: backups[envName][i] }); - } - - oResultBackups[envName] = aValues; - } - } - - return oResultBackups; -} - if (storage_unavailable_markup === "") { if ('${settings.isPitr}' == 'true') { settings.fields.push({ @@ -79,7 +67,7 @@ if (storage_unavailable_markup === "") { "type": "list", "name": "backupedEnvName", "required": true, - "values": envs, + "values": filteredEnvs, "tooltip": "Select the environment to restore from" }, { "caption": "Time for restore", @@ -104,7 +92,7 @@ if (storage_unavailable_markup === "") { "type": "list", "name": "backupedEnvName", "required": true, - "values": envs + "values": filteredEnvs }, { "caption": "Backup", "type": "list", @@ -112,7 +100,7 @@ if (storage_unavailable_markup === "") { "required": true, "tooltip": "Select the time stamp for which you want to restore the DB dump", "dependsOn": { - "backupedEnvName" : backups + "backupedEnvName" : filteredBackups } }); if (checkSchema.responses[0].out == "true") { From b9a04e7997f4c3c3661e532c27e8bc4eb9ea4728 Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Thu, 20 Mar 2025 17:40:51 +0100 Subject: [PATCH 73/78] JE-71293 --- scripts/getBackupsAllEnvsJSON.sh | 58 ++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/scripts/getBackupsAllEnvsJSON.sh b/scripts/getBackupsAllEnvsJSON.sh index 4cc6f94..032e2eb 100644 --- a/scripts/getBackupsAllEnvsJSON.sh +++ b/scripts/getBackupsAllEnvsJSON.sh @@ -2,18 +2,54 @@ restic self-update &>/dev/null || true -ENV_LIST=$(ls /data) +OUTPUT_JSON="{\"result\": 0, \"envs\": {" -OUTPUT_JSON="{\"result\": 0, \"envs\": [" +for i in $(ls /data); do -if [ -n "$ENV_LIST" ]; then - for i in $ENV_LIST; do - DIRECTORY_LIST=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots --json) - [ -z "$DIRECTORY_LIST" ] || OUTPUT_JSON="${OUTPUT_JSON}{\"${i}\": ${DIRECTORY_LIST}}," - done - OUTPUT_JSON="${OUTPUT_JSON%,}" -fi + PITR=false + PITR_START_TIME=null -OUTPUT_JSON="${OUTPUT_JSON}]}" + SNAPSHOTS_JSON=$(RESTIC_PASSWORD="$i" restic -r /data/$i snapshots --json | jq 'sort_by(.time) | reverse') -echo $OUTPUT_JSON + DIRECTORY_LIST=$(echo "$SNAPSHOTS_JSON" | jq -r '[.[] | select(.tags | index("BINLOGS") | not) | .tags[0] | split(" ")[0]] | map("\"" + . + "\"") | join(",")') + + SERVER_VERSION=$(echo "$SNAPSHOTS_JSON" | jq -r '[.[] | .tags[0] | capture("\\((?[^)]+)").server_version] | unique | .[0]') + + SERVER=$(echo "$SERVER_VERSION" | cut -d'-' -f1) + VERSION=$(echo "$SERVER_VERSION" | cut -d'-' -f2-) + + if [[ "$SERVER" == "mariadb" || "$SERVER" == "postgres" || "$SERVER" == "mysql" ]]; then + FIRST_SNAPSHOT=$(echo "$SNAPSHOTS_JSON" | jq '.[0]') + + if echo "$FIRST_SNAPSHOT" | jq -e '.tags | index("PITR")' &>/dev/null; then + PITR=true + FIRST_TAG=$(echo "$FIRST_SNAPSHOT" | jq -r '.tags[0]') + PITR_START_TIME=$(echo "$FIRST_TAG" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{6}_UTC') + + SNAPSHOT_COUNT=$(echo "$SNAPSHOTS_JSON" | jq 'length') + for (( j=1; j < SNAPSHOT_COUNT-2; j+=2 )); do + SECOND_TAG=$(echo "$SNAPSHOTS_JSON" | jq -r ".[$j].tags[0]") + THIRD_TAG=$(echo "$SNAPSHOTS_JSON" | jq -r ".[$((j+1))].tags[0]") + + if [[ "$SECOND_TAG" == "$THIRD_TAG" ]]; then + PITR_START_TIME=$(echo "$SECOND_TAG" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{6}_UTC') + else + break + fi + done + fi + fi + + PITR_START_TIME=$(echo "$PITR_START_TIME" | jq -R) + + if [ -n "$DIRECTORY_LIST" ]; then + OUTPUT_JSON="${OUTPUT_JSON}\"${i}\": { \"server\": \"${SERVER}\", \"version\": \"${VERSION}\", \"pitr\": ${PITR}, \"pitrStartTime\": ${PITR_START_TIME}, \"backups\": [${DIRECTORY_LIST}] }," + else + OUTPUT_JSON="${OUTPUT_JSON}\"${i}\": { \"server\": \"${SERVER}\", \"version\": \"${VERSION}\", \"pitr\": ${PITR}, \"pitrStartTime\": ${PITR_START_TIME}, \"backups\": [] }," + fi +done + +OUTPUT_JSON=${OUTPUT_JSON::-1} +OUTPUT_JSON="${OUTPUT_JSON}}}" + +echo "$OUTPUT_JSON" From 7e39dca70865b14608a6e0c5a9c22008206a3e4c Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Thu, 20 Mar 2025 17:45:38 +0100 Subject: [PATCH 74/78] JE-71293 --- scripts/restoreOnBeforeInit.js | 83 +++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/scripts/restoreOnBeforeInit.js b/scripts/restoreOnBeforeInit.js index bdd12ce..3b39826 100644 --- a/scripts/restoreOnBeforeInit.js +++ b/scripts/restoreOnBeforeInit.js @@ -23,13 +23,15 @@ if (resp.result == 11) { var updateResticOnStorageCommand = "wget --tries=10 -O /tmp/installUpdateRestic " + baseUrl + "/scripts/installUpdateRestic && mv -f /tmp/installUpdateRestic /usr/sbin/installUpdateRestic && chmod +x /usr/sbin/installUpdateRestic && /usr/sbin/installUpdateRestic"; var respUpdate = api.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": updateResticOnStorageCommand, "params": ""}]), false, "root"); if (respUpdate.result != 0) return respUpdate; - var getBackupsAllEnvs = "wget --tries=10 -O /root/getBackupsAllEnvs.sh " + baseUrl + "/scripts/getBackupsAllEnvs.sh && chmod +x /root/getBackupsAllEnvs.sh && /root/getBackupsAllEnvs.sh"; + var getBackupsAllEnvs = "wget --tries=10 -O /root/getBackupsAllEnvsJSON.sh " + baseUrl + "/scripts/getBackupsAllEnvsJSON.sh && chmod +x /root/getBackupsAllEnvsJSON.sh && /root/getBackupsAllEnvsJSON.sh"; var backups = jelastic.env.control.ExecCmdById(storageEnvDomain, session, storageEnvMasterId, toJSON([{"command": getBackupsAllEnvs, "params": ""}]), false, "root").responses[0].out; var backupList = toNative(new JSONObject(String(backups))); - + var filteredEnvs = []; var filteredBackups = {}; - + var filteredPitrEnvs = []; + var filteredPitrStartTime = {}; + for (var env in backupList.envs) { if (backupList.envs.hasOwnProperty(env)) { var backupInfo = backupList.envs[env]; @@ -40,6 +42,11 @@ if (resp.result == 11) { filteredBackups[env] = backupInfo.backups.map(function(backup) { return { caption: backup, value: backup }; }); + + if (backupInfo.pitr === true) { + filteredPitrEnvs.push({ caption: env, value: env }); + filteredPitrStartTime[env] = [{ caption: backupInfo.pitrStartTime, value: backupInfo.pitrStartTime }]; + } } } } @@ -63,20 +70,61 @@ function getStorageNodeid(){ if (storage_unavailable_markup === "") { if ('${settings.isPitr}' == 'true') { settings.fields.push({ - "caption": "Restore from", - "type": "list", - "name": "backupedEnvName", - "required": true, - "values": filteredEnvs, - "tooltip": "Select the environment to restore from" - }, { - "caption": "Time for restore", - "type": "string", - "name": "restoreTime", - "inputType": "datetime-local", - "cls": "x-form-text", - "required": true, - "tooltip": "Select specific date and time for point-in-time recovery" + "type": "toggle", + "name": "isPitr", + "caption": "PITR", + "tooltip": "Point in time recovery", + "value": true, + "hidden": false, + "showIf": { + "true": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "values": filteredPitrEnvs, + "default": filteredPitrEnvs[0], + "tooltip": "Select the environment to restore from" + }, { + "caption": "PITR Start Time", + "type": "list", + "name": "pitrStartTime", + "required": true, + "tooltip": "PITR Start Time", + "dependsOn": { + "backupedEnvName" : filteredPitrStartTime + } + }, { + "caption": "Time for restore", + "type": "string", + "name": "restoreTime", + "inputType": "datetime-local", + "cls": "x-form-text", + "required": true, + "tooltip": "Select specific date and time for point-in-time recovery" + } + ], + "false": [ + { + "caption": "Restore from", + "type": "list", + "name": "backupedEnvName", + "required": true, + "default": filteredEnvs[0], + "values": filteredEnvs + }, { + "caption": "Backup", + "type": "list", + "name": "backupDir", + "required": true, + "tooltip": "Select the time stamp for which you want to restore the DB dump", + "dependsOn": { + "backupedEnvName" : filteredBackups + } + } + ] + } }); if (checkSchema.responses[0].out == "true") { settings.fields.push( @@ -92,6 +140,7 @@ if (storage_unavailable_markup === "") { "type": "list", "name": "backupedEnvName", "required": true, + "default": filteredEnvs[0], "values": filteredEnvs }, { "caption": "Backup", From cfa83d6f069f46570b981dbf2caedd62ef8464f1 Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Thu, 20 Mar 2025 18:52:21 +0100 Subject: [PATCH 75/78] JE-71293 --- scripts/restore-logic.sh | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index 28801ad..27b9130 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -3,7 +3,6 @@ DBUSER=$1 DBPASSWD=$2 RESTORE_LOG_FILE=$3 -PITR=$4 DUMP_BACKUP_DIR=/root/backup/dump BINLOGS_BACKUP_DIR=/root/backup/binlogs @@ -14,27 +13,23 @@ SQL_DUMP_NAME=db_backup.sql if [ -f /root/.backupedenv ]; then ENV_NAME=$(cat /root/.backupedenv) else - echo "The /root/.backupedenv file with ENV_NAME doesnt exist." - exit 1; + echo "The /root/.backupedenv file with ENV_NAME doesn't exist." + exit 1 fi -if [ -z "$PITR" ]; then +if [ -f /root/.backuptime ]; then + PITR="true" + PITR_TIME=$(cat /root/.backuptime) +else PITR="false" fi -if [ "$PITR" == "true" ]; then - if [ -f /root/.backuptime ]; then - PITR_TIME=$(cat /root/.backuptime) - else - echo "The /root/.backuptime file with BACKUP_TIME doesnt exist." - exit 1; - fi -else +if [ "$PITR" == "false" ]; then if [ -f /root/.backupid ]; then BACKUP_NAME=$(cat /root/.backupid) else - echo "The /root/.backupid file with BACKUP_NAME doesnt exist." - exit 1; + echo "The /root/.backupid file with BACKUP_NAME doesn't exist." + exit 1 fi fi From 19e5f9a68ae8ae9ea35465022835bfcd87a531be Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Thu, 20 Mar 2025 19:22:35 +0100 Subject: [PATCH 76/78] JE-71293 --- backup.jps | 4 +++- scripts/restore-logic.sh | 13 +++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/backup.jps b/backup.jps index 1449932..1b5acb6 100644 --- a/backup.jps +++ b/backup.jps @@ -332,7 +332,9 @@ actions: - deleteDBdump restore: - - cmd[${targetNodes.nodeGroup}]: echo "${settings.backupedEnvName}" > /root/.backupedenv + - cmd[${targetNodes.nodeGroup}]: |- + rm -f /root/.backupedenv /root/.backuptime /root/.backupid || exit 0; + echo "${settings.backupedEnvName}" > /root/.backupedenv user: root - if ("${settings.isPitr}" == "true"): - script: | diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index 27b9130..59df3a2 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -67,13 +67,18 @@ function get_snapshot_id_before_time() { # Filters out BINLOGS tagged snapshots function get_dump_snapshot_id_by_name(){ local backup_name="$1" - local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | jq -r '.[] | select(.tags[0] | contains("'$backup_name'")) | select((.tags[1] != null and (.tags[1] | contains("BINLOGS")) | not) // true) | .short_id') + local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | \ + jq -r '.[] | select(.tags[0] | contains("'"$backup_name"'")) | + select((.tags | index("BINLOGS") | not)) | + .short_id' | head -n1) + if [[ $? -ne 0 || -z "$snapshot_id" ]]; then - echo $(date) ${ENV_NAME} "Error: Failed to get DB dump snapshot ID" | tee -a ${RESTORE_LOG_FILE} + echo "$(date) ${ENV_NAME} Error: Failed to get DB dump snapshot ID" | tee -a ${RESTORE_LOG_FILE} exit 1 fi - echo $(date) ${ENV_NAME} "Getting DB dump snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} - echo $snapshot_id + + echo "$(date) ${ENV_NAME} Getting DB dump snapshot ID: $snapshot_id" >> ${RESTORE_LOG_FILE} + echo "$snapshot_id" } # Retrieves binlog snapshot ID for backup name From 382c03d61dc7df1cc1b93b21fc7136d925d7e11e Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Wed, 26 Mar 2025 15:03:26 +0100 Subject: [PATCH 77/78] JE-73990 --- scripts/restore-logic.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index 59df3a2..40af2cf 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -168,7 +168,7 @@ function restore_mongodb(){ else SSL_TLS_OPTIONS="" fi - mongorestore ${SSL_TLS_OPTIONS} --uri="mongodb://${1}:${2}@localhost${RS_SUFFIX}" ${DUMP_BACKUP_DIR} 1>/dev/null + mongorestore ${SSL_TLS_OPTIONS} --uri="mongodb://${DBUSER}:${DBPASSWD}@localhost${RS_SUFFIX}" ${DUMP_BACKUP_DIR} 1>/dev/null } From 087af3253898b9ebdefcb1641ee02f924ae4494a Mon Sep 17 00:00:00 2001 From: Sychevskyi Henadii Date: Wed, 26 Mar 2025 16:47:24 +0100 Subject: [PATCH 78/78] JE-71293 --- scripts/restore-logic.sh | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/scripts/restore-logic.sh b/scripts/restore-logic.sh index 40af2cf..7dcf21c 100644 --- a/scripts/restore-logic.sh +++ b/scripts/restore-logic.sh @@ -34,9 +34,6 @@ if [ "$PITR" == "false" ]; then fi # Finds snapshot ID before specified timestamp -# @param {string} target_datetime - Target restoration time -# @return {string} Snapshot ID or exits with error -# Searches through PITR snapshots chronologically function get_snapshot_id_before_time() { local target_datetime="$1" @@ -62,9 +59,6 @@ function get_snapshot_id_before_time() { } # Retrieves snapshot ID for given backup name -# @param {string} backup_name - Name of the backup to find -# @return {string} Snapshot ID or exits with error -# Filters out BINLOGS tagged snapshots function get_dump_snapshot_id_by_name(){ local backup_name="$1" local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --json | \ @@ -82,9 +76,6 @@ function get_dump_snapshot_id_by_name(){ } # Retrieves binlog snapshot ID for backup name -# @param {string} backup_name - Name of the backup -# @return {string} Binlog snapshot ID or exits with error -# Specifically searches for BINLOGS tagged snapshots function get_binlog_snapshot_id_by_name(){ local backup_name="$1" local snapshot_id=$(GOGC=20 RESTIC_PASSWORD=${ENV_NAME} restic -r /opt/backup/${ENV_NAME} snapshots --tag "BINLOGS" --json | jq -r --arg backup_name "$backup_name" '.[] | select(.tags[0] | contains($backup_name)) | .short_id') @@ -269,10 +260,13 @@ function restore_postgres() { # Remove problematic role commands sed -i -e '/^CREATE ROLE webadmin/d' \ -e '/^CREATE ROLE postgres/d' \ + -e "/^CREATE ROLE ${DBUSER}/d" \ -e '/^DROP ROLE IF EXISTS postgres/d' \ -e '/^DROP ROLE IF EXISTS webadmin/d' \ + -e "/^DROP ROLE IF EXISTS ${DBUSER}/d" \ -e '/^ALTER ROLE postgres WITH SUPERUSER/d' \ - -e '/^ALTER ROLE webadmin WITH SUPERUSER/d' "$TEMP_BACKUP" + -e '/^ALTER ROLE webadmin WITH SUPERUSER/d' \ + -e "/^ALTER ROLE ${DBUSER} WITH SUPERUSER/d" "$TEMP_BACKUP" # Restore the database PGPASSWORD="${DBPASSWD}" psql --no-readline -q -U "${DBUSER}" -d postgres < "$TEMP_BACKUP" @@ -300,10 +294,13 @@ function restore_postgres() { # Remove problematic role commands sed -i -e '/^CREATE ROLE webadmin/d' \ -e '/^CREATE ROLE postgres/d' \ + -e "/^CREATE ROLE ${DBUSER}/d" \ -e '/^DROP ROLE IF EXISTS postgres/d' \ -e '/^DROP ROLE IF EXISTS webadmin/d' \ + -e "/^DROP ROLE IF EXISTS ${DBUSER}/d" \ -e '/^ALTER ROLE postgres WITH SUPERUSER/d' \ - -e '/^ALTER ROLE webadmin WITH SUPERUSER/d' "$TEMP_BACKUP" + -e '/^ALTER ROLE webadmin WITH SUPERUSER/d' \ + -e "/^ALTER ROLE ${DBUSER} WITH SUPERUSER/d" "$TEMP_BACKUP" PGPASSWORD="${DBPASSWD}" psql --no-readline -q -U "${DBUSER}" -d postgres < "$TEMP_BACKUP" > /dev/null if [[ $? -ne 0 ]]; then