Skip to content

Commit d9a1a76

Browse files
authored
Merge pull request #649 from PAWECOGmbH/staging
Staging to Production
2 parents 9e2ba86 + fe6d5d6 commit d9a1a76

5 files changed

Lines changed: 166 additions & 70 deletions

File tree

config/backup/backup.sh

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,91 @@
11
#!/bin/bash
22

3-
# Enable automatic export of variables
43
set -a
54

6-
# Load the .env file from the project root
5+
# --------------------------------------
6+
# CONFIGURATION
7+
# --------------------------------------
8+
9+
LOGFILE="/var/log/backup-cron.log"
10+
echo "[BACKUP START] $(date)" | tee -a $LOGFILE
11+
12+
# Load environment variables from .env
713
source "$(dirname "$0")/../../.env"
814

9-
# Dynamically generate volume names based on the project
15+
# Setup volume names and timestamp
1016
DB_VOLUME="${COMPOSE_PROJECT_NAME}_db_volume"
1117
USERDATA_VOLUME="${COMPOSE_PROJECT_NAME}_userdata_volume"
12-
13-
# Date format for versioning (e.g., 20241022_2300)
1418
TIMESTAMP=$(date +"%Y%m%d_%H%M")
1519

16-
# Check if the /backup folder exists and create it if necessary
17-
if [ ! -d "/backup" ]; then
18-
mkdir -p /backup
19-
fi
20+
# Ensure local backup directory exists
21+
mkdir -p /backup
2022

2123
# Create remote directories if they don't exist
22-
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "mkdir -p ${REMOTE_BACKUP_PATH}/db ${REMOTE_BACKUP_PATH}/userdata ${REMOTE_BACKUP_PATH}/lucee"
24+
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} \
25+
"mkdir -p ${REMOTE_BACKUP_PATH}/{db,userdata,lucee}" >> $LOGFILE 2>&1
26+
27+
# --------------------------------------
28+
# DATABASE BACKUP
29+
# --------------------------------------
30+
31+
echo "[DB] Creating archive..." | tee -a $LOGFILE
32+
docker run --rm -v ${DB_VOLUME}:/volume -v /backup:/backup alpine sh -c \
33+
"tar -czf /backup/database_${TIMESTAMP}.tar.gz -C /volume ." >> $LOGFILE 2>&1
34+
35+
echo "[DB] Uploading to remote..." >> $LOGFILE
36+
scp -i ${SSH_KEY_PATH} /backup/database_${TIMESTAMP}.tar.gz \
37+
${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/db/ >> $LOGFILE 2>&1
38+
39+
# Verify and delete local backup if transfer succeeded
40+
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} \
41+
"[ -f ${REMOTE_BACKUP_PATH}/db/database_${TIMESTAMP}.tar.gz ]" \
42+
&& { echo "[DB] Remote verified, deleting local file" | tee -a $LOGFILE; rm /backup/database_${TIMESTAMP}.tar.gz; } \
43+
|| echo "[DB] Remote file missing – NOT deleted" | tee -a $LOGFILE
44+
45+
# --------------------------------------
46+
# USERDATA BACKUP
47+
# --------------------------------------
48+
49+
echo "[USERDATA] Creating archive..." | tee -a $LOGFILE
50+
docker run --rm -v ${USERDATA_VOLUME}:/volume -v /backup:/backup alpine sh -c \
51+
"tar -czf /backup/userdata_${TIMESTAMP}.tar.gz -C /volume ." >> $LOGFILE 2>&1
52+
53+
echo "[USERDATA] Uploading to remote..." | tee -a $LOGFILE
54+
scp -i ${SSH_KEY_PATH} /backup/userdata_${TIMESTAMP}.tar.gz \
55+
${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/userdata/ >> $LOGFILE 2>&1
56+
57+
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} \
58+
"[ -f ${REMOTE_BACKUP_PATH}/userdata/userdata_${TIMESTAMP}.tar.gz ]" \
59+
&& { echo "[USERDATA] Remote verified, deleting local file" | tee -a $LOGFILE; rm /backup/userdata_${TIMESTAMP}.tar.gz; } \
60+
|| echo "[USERDATA] Remote file missing – NOT deleted" | tee -a $LOGFILE
2361

24-
# Backup database volume and store in the remote db directory
25-
docker run --rm -v ${DB_VOLUME}:/volume -v /backup:/backup alpine sh -c "tar -czf /backup/database_${TIMESTAMP}.tar.gz -C /volume ."
26-
scp -i ${SSH_KEY_PATH} /backup/database_${TIMESTAMP}.tar.gz ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/db/
62+
# --------------------------------------
63+
# LUCEE IMAGE BACKUP
64+
# --------------------------------------
2765

28-
# Backup userdata volume and store in the remote userdata directory
29-
docker run --rm -v ${USERDATA_VOLUME}:/volume -v /backup:/backup alpine sh -c "tar -czf /backup/userdata_${TIMESTAMP}.tar.gz -C /volume ."
30-
scp -i ${SSH_KEY_PATH} /backup/userdata_${TIMESTAMP}.tar.gz ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/userdata/
66+
echo "[LUCEE] Saving Docker image..." | tee -a $LOGFILE
67+
docker save -o /backup/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_${TIMESTAMP}.tar \
68+
${LUCEE_IMAGE}:${LUCEE_IMAGE_VERSION} >> $LOGFILE 2>&1
3169

32-
# Backup Lucee image and store in the remote lucee directory
33-
docker save -o /backup/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_${TIMESTAMP}.tar ${LUCEE_IMAGE}:${LUCEE_IMAGE_VERSION}
34-
scp -i ${SSH_KEY_PATH} /backup/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_${TIMESTAMP}.tar ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/lucee/
70+
echo "[LUCEE] Uploading to remote..." | tee -a $LOGFILE
71+
scp -i ${SSH_KEY_PATH} /backup/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_${TIMESTAMP}.tar \
72+
${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/lucee/ >> $LOGFILE 2>&1
3573

36-
# Rotate backups: Keep only the latest 30 backups per type
74+
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} \
75+
"[ -f ${REMOTE_BACKUP_PATH}/lucee/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_${TIMESTAMP}.tar ]" \
76+
&& { echo "[LUCEE] Remote verified, deleting local file" | tee -a $LOGFILE; rm /backup/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_${TIMESTAMP}.tar; } \
77+
|| echo "[LUCEE] Remote file missing – NOT deleted" | tee -a $LOGFILE
3778

38-
# For database backups
39-
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "cd ${REMOTE_BACKUP_PATH}/db && ls -tp | grep -v '/$' | tail -n +31 | xargs -I {} rm -- {}"
79+
# --------------------------------------
80+
# REMOTE ROTATION
81+
# --------------------------------------
4082

41-
# For userdata backups
42-
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "cd ${REMOTE_BACKUP_PATH}/userdata && ls -tp | grep -v '/$' | tail -n +31 | xargs -I {} rm -- {}"
83+
echo "[ROTATE] Cleaning up old remote backups..." | tee -a $LOGFILE
84+
for folder in db userdata lucee; do
85+
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} \
86+
"cd ${REMOTE_BACKUP_PATH}/$folder && ls -tp | grep -v '/$' | tail -n +31 | xargs -I {} rm -- {}" >> $LOGFILE 2>&1
87+
done
4388

44-
# For Lucee image backups
45-
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "cd ${REMOTE_BACKUP_PATH}/lucee && ls -tp | grep -v '/$' | tail -n +31 | xargs -I {} rm -- {}"
89+
echo "[BACKUP DONE] $(date)" | tee -a $LOGFILE
4690

47-
# Disable automatic export of variables
4891
set +a

config/backup/readme.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ This will:
3636
- Backup the **user data volume**.
3737
- Backup the **Lucee image**.
3838
- Securely transfer all backups to the remote backup server.
39+
- Backups are only deleted locally if the remote transfer was successful.
3940

4041
Each backup will be **timestamped** in the format `YYYYMMDD_HHMM`, ensuring you can differentiate between multiple backup versions.
4142

@@ -60,15 +61,22 @@ To perform a restore, you need to specify which backup you want to restore by us
6061
- To list all available backups on the remote server:
6162
`bash restore.sh --list`
6263

64+
- After each restore, the downloaded backup file is automatically deleted from the `/restore/` folder to keep the system clean.
65+
6366

6467
## **Automating Backups**
6568

6669
To automate the backup process, you can set up a **cron job** to run the backup script at regular intervals (e.g., daily). For example, to run the backup every night at midnight, add the following entry to your crontab:
6770
`0 0 * * * /path/to/your/project/config/backup/backup.sh`
71+
Backup logs are written to /var/log/backup-cron.log
72+
You can review this log to verify execution and spot issues.
6873

6974

7075
## **Notes**
7176

7277
- These backups are intended for the **production** and **staging** environments. Ensure that the environment variables in the `.env` file are correctly configured before running any backups or restores.
7378
- The backup script automatically **rotates** backups, keeping only the **latest 30 backups** per backup type (database, user data, Lucee image) by removing older backups on the remote server.
7479
- Always ensure that your **SSH keys** and **server information** are secure, as they are used for transferring backups between the production or staging environment and the remote server.
80+
- Locally created backup files are deleted only if the remote copy exists to prevent data loss.
81+
- Backups on the remote server are rotated automatically: only the latest 30 backups per type are kept.
82+
- The restore script stops immediately if any step fails, ensuring that incomplete restores do not go unnoticed.

config/backup/restore.sh

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,141 @@
11
#!/bin/bash
22

3+
# Exit immediately if a command exits with a non-zero status
4+
set -e
5+
6+
# Print error line if something goes wrong
7+
trap 'echo "ERROR: Restore failed at line $LINENO"; exit 1' ERR
8+
39
# Enable automatic export of variables
410
set -a
511

6-
# Load the .env file from the project root
12+
# Load environment variables from the project root
713
source "$(dirname "$0")/../../.env"
814

9-
# Dynamically generate volume names based on the project
15+
# Generate volume names
1016
DB_VOLUME="${COMPOSE_PROJECT_NAME}_db_volume"
1117
USERDATA_VOLUME="${COMPOSE_PROJECT_NAME}_userdata_volume"
1218

13-
# Check if the /restore folder exists and create it if necessary
14-
if [ ! -d "/restore" ]; then
15-
mkdir -p /restore
16-
fi
19+
# Ensure /restore directory exists
20+
mkdir -p /restore
1721

18-
# Functions for the various restore processes
22+
# -------------------------
23+
# Restore functions
24+
# -------------------------
1925

2026
restore_db() {
27+
local BACKUP_NAME
2128
if [ -z "$1" ]; then
22-
echo "Restoring the latest database backup..."
23-
# Get the latest database backup
24-
LATEST_DB_BACKUP=$(ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls -t ${REMOTE_BACKUP_PATH}/db | head -n 1")
29+
echo "[DB] Restoring latest backup..."
30+
BACKUP_NAME=$(ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls -t ${REMOTE_BACKUP_PATH}/db | head -n 1")
2531
else
26-
echo "Restoring database backup from $1..."
27-
LATEST_DB_BACKUP="database_$1.tar.gz"
32+
echo "[DB] Restoring backup from timestamp: $1"
33+
BACKUP_NAME="database_$1.tar.gz"
2834
fi
29-
scp -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/db/${LATEST_DB_BACKUP} /restore/database.tar.gz
35+
36+
scp -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/db/${BACKUP_NAME} /restore/database.tar.gz
3037
docker run --rm -v ${DB_VOLUME}:/volume -v /restore:/restore alpine sh -c "tar -xzf /restore/database.tar.gz -C /volume"
38+
rm /restore/database.tar.gz
3139
docker restart ${MYSQL_CONTAINER_NAME}
40+
echo "[DB] Restore complete"
3241
}
3342

3443
restore_userdata() {
44+
local BACKUP_NAME
3545
if [ -z "$1" ]; then
36-
echo "Restoring the latest userdata backup..."
37-
# Get the latest userdata backup
38-
LATEST_USERDATA_BACKUP=$(ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls -t ${REMOTE_BACKUP_PATH}/userdata | head -n 1")
46+
echo "[USERDATA] Restoring latest backup..."
47+
BACKUP_NAME=$(ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls -t ${REMOTE_BACKUP_PATH}/userdata | head -n 1")
3948
else
40-
echo "Restoring userdata backup from $1..."
41-
LATEST_USERDATA_BACKUP="userdata_$1.tar.gz"
49+
echo "[USERDATA] Restoring backup from timestamp: $1"
50+
BACKUP_NAME="userdata_$1.tar.gz"
4251
fi
43-
scp -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/userdata/${LATEST_USERDATA_BACKUP} /restore/userdata.tar.gz
52+
53+
scp -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/userdata/${BACKUP_NAME} /restore/userdata.tar.gz
4454
docker run --rm -v ${USERDATA_VOLUME}:/volume -v /restore:/restore alpine sh -c "tar -xzf /restore/userdata.tar.gz -C /volume"
55+
rm /restore/userdata.tar.gz
4556
docker restart ${LUCEE_CONTAINER_NAME}
57+
echo "[USERDATA] Restore complete"
4658
}
4759

4860
restore_lucee_image() {
61+
local BACKUP_NAME
4962
if [ -z "$1" ]; then
50-
echo "Restoring the latest Lucee image backup..."
51-
# Get the latest Lucee image backup
52-
LATEST_LUCEE_BACKUP=$(ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls -t ${REMOTE_BACKUP_PATH}/lucee | head -n 1")
63+
echo "[LUCEE] Restoring latest image..."
64+
BACKUP_NAME=$(ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls -t ${REMOTE_BACKUP_PATH}/lucee | head -n 1")
5365
else
54-
echo "Restoring Lucee image backup from $1..."
55-
LATEST_LUCEE_BACKUP="image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_$1.tar"
66+
echo "[LUCEE] Restoring image from timestamp: $1"
67+
BACKUP_NAME="image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}_$1.tar"
5668
fi
57-
scp -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/lucee/${LATEST_LUCEE_BACKUP} /restore/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}.tar
58-
docker load -i /restore/image_${LUCEE_IMAGE}_${LUCEE_IMAGE_VERSION}.tar
69+
70+
scp -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP}:${REMOTE_BACKUP_PATH}/lucee/${BACKUP_NAME} /restore/image.tar
71+
docker load -i /restore/image.tar
72+
rm /restore/image.tar
73+
echo "[LUCEE] Image restore complete"
5974
}
6075

6176
list_backups() {
6277
echo "Available backups on remote server:"
63-
echo "Database backups:"
78+
echo "-----------------------------------"
79+
echo "Database:"
6480
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls ${REMOTE_BACKUP_PATH}/db"
6581
echo ""
66-
echo "Userdata backups:"
82+
echo "Userdata:"
6783
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls ${REMOTE_BACKUP_PATH}/userdata"
6884
echo ""
69-
echo "Lucee image backups:"
85+
echo "Lucee images:"
7086
ssh -i ${SSH_KEY_PATH} ${SERVER_USER}@${SERVER_IP} "ls ${REMOTE_BACKUP_PATH}/lucee"
7187
echo ""
7288
}
7389

74-
# Show help if no options have been specified
90+
# -------------------------
91+
# CLI Argument handling
92+
# -------------------------
93+
7594
if [ $# -eq 0 ]; then
76-
echo "Usage: $0 [--db [TIMESTAMP]] [--userdata [TIMESTAMP]] [--lucee-image [TIMESTAMP]] [--list]"
95+
echo "Usage:"
96+
echo " $0 --db [TIMESTAMP] Restore database (latest if none given)"
97+
echo " $0 --userdata [TIMESTAMP] Restore userdata volume"
98+
echo " $0 --lucee-image [TIMESTAMP] Restore Lucee image"
99+
echo " $0 --list List available backups"
77100
exit 1
78101
fi
79102

80-
# Process the specified options
81103
while [[ "$#" -gt 0 ]]; do
82104
case $1 in
83-
--db) restore_db "$2"; shift ;;
84-
--userdata) restore_userdata "$2"; shift ;;
85-
--lucee-image) restore_lucee_image "$2"; shift ;;
86-
--list) list_backups; exit 0 ;;
87-
*) echo "Unknown option: $1"; exit 1 ;;
105+
--db)
106+
if [[ -n "$2" && "$2" != --* ]]; then
107+
restore_db "$2"
108+
shift
109+
else
110+
restore_db
111+
fi
112+
;;
113+
--userdata)
114+
if [[ -n "$2" && "$2" != --* ]]; then
115+
restore_userdata "$2"
116+
shift
117+
else
118+
restore_userdata
119+
fi
120+
;;
121+
--lucee-image)
122+
if [[ -n "$2" && "$2" != --* ]]; then
123+
restore_lucee_image "$2"
124+
shift
125+
else
126+
restore_lucee_image
127+
fi
128+
;;
129+
--list)
130+
list_backups
131+
exit 0
132+
;;
133+
*)
134+
echo "ERROR: Unknown option: $1"
135+
exit 1
136+
;;
88137
esac
89138
shift
90139
done
91140

92-
# Disable automatic export of variables
93141
set +a

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Update the ```.env``` file with your configuration. You can also adopt the sugge
4444

4545
<b>5. Copy the NGINX base settings</b><br>
4646
```
47-
mv config/example.base.conf config/nginx/base.conf
47+
mv config/example.base.conf config/nginx/conf.d/base.conf
4848
```
4949

5050
<b>6. Set up the application configuration</b><br>

www/frontend/core/scheduletasks/tasks.cfm

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,8 @@ if (url.pass eq variables.schedulePassword) {
122122
// Stop schedulecontrol
123123
application.objSysadmin.stopScheduleControl(url.task);
124124
125-
// Deactivate the schedule task
126-
application.objSysadmin.deactivateTask(qGetTasks.intScheduletaskID);
127-
128125
// Make log
129-
objLogs.logWrite("scheduletask", "error", "Something went wrong in schedule task, the task has been deactivated [File: #qGetTasks.strPath#, Error: #e.message#]", false);
126+
objLogs.logWrite("scheduletask", "error", "Something went wrong in schedule task [File: #qGetTasks.strPath#, Error: #e.message#]", false);
130127
131128
// Send email to the developer with the error dump
132129
cfmail(subject="Error in included scheduletask file", to="#application.errorMail#", from="#application.fromEmail#" type="html" ) {

0 commit comments

Comments
 (0)