Hi Bob,
Thank you for clarifying! That makes total sense regarding why creating a custom script copy (e.g.,dhis2-moz-backup) is safer—to prevent an Ansible playbook run or upstream package update from overwriting local changes.
Just to confirm, your recommended workflow is:
- Copy and rename script:
Bash
sudo cp -a /usr/local/bin/dhis2-backup /usr/local/bin/dhis2-moz-backup
sudo chmod 700 /usr/local/bin/dhis2-moz-backup
- Update Root Crontab (sudo crontab -e):
0 3 * * * /usr/local/bin/dhis2-moz-backup
If dhis2-env is managed by Ansible/upstream, would you recommend creating a local/custom environment file in the same way as the custom backup script, e.g. dhis2-moz-env, and having dhis2-moz-backup source that file instead?
***grep -Rni --exclude-dir=.git “dhis2-backup” ~/dhis2-server-tools/ ***
roles/backups/tasks/lxd.yml:
*** src: dhis2-env.j2***
*** dest: /usr/local/etc/dhis/dhis2-env***
*** job: /usr/local/bin/dhis2-backup***
As requested, here is the current/usr/local/bin/dhis2-backupscript written by Tito Kipkurgat currently running on our server, along with our/usr/local/etc/dhis/dhis2-envconfiguration.
1. Script (/usr/local/bin/dhis2-backup)
#!/usr/bin/env bash
#########################################################
# postgres backup script v3.1
# author: Tito Kipkurgat
# licence: public domain
# using some ideas from
# Automated Backup on Linux - PostgreSQL wiki
########################################################
# load variables from a file
source /usr/local/etc/dhis/dhis2-env
function perform_backups()
{
SUFFIX=$1
DB_BUCKUP_DIR=$BACKUP_DIR/db-backup
AUDIT_BACKUP_DIR=$BACKUP_DIR/audit-backup
FINAL_BACKUP_DIR=$DB_BUCKUP_DIR/“$(date +%Y%m%d-%H%M%S)$SUFFIX”
FINAL_AUDIT_DIR=$AUDIT_BACKUP_DIR/“$(date +%Y%m%d-%H%M%S)$SUFFIX”
echo “Backup Directory = $FINAL_BACKUP_DIR”
echo “Audit Directory = $FINAL_AUDIT_DIR”
if ! mkdir -p $FINAL_BACKUP_DIR; then
echo “`date` Cannot create backup directory in $FINAL_BACKUP_DIR. Go and fix it!”
exit 1;
fi;
if ! mkdir -p $FINAL_AUDIT_DIR; then
echo “`date` Cannot create backup directory in $FINAL_AUDIT_DIR. Go and fix it!”
exit 1;
fi;
for DBNAME in $PLAIN_BACKUPS
do
set -o pipefail
# First backup and truncate audit table
if ! lxc exec postgres – pg_dump -a -t audit $DBNAME | gzip - > “$FINAL_AUDIT_DIR/“audit_”$DBNAME”.sql.gz.in_progress; then
echo “`date` [!!ERROR!!] Failed to backup audit table of database $DBNAME”
else
mv “$FINAL_AUDIT_DIR”/audit_“$DBNAME”.sql.gz.in_progress “$FINAL_AUDIT_DIR”/audit_“$DBNAME”.sql.gz
echo “TRUNCATE audit” | lxc exec postgres psql $DBNAME
fi
# Perform backup of the main database
if ! lxc exec postgres – pg_dump -O -Fp $DBNAME $EXCLUDED | gzip > $FINAL_BACKUP_DIR/“$DBNAME”.sql.gz.in_progress; then
echo “`date` [!!ERROR!!] Failed to produce plain backup of database $DBNAME”
else
mv $FINAL_BACKUP_DIR/“$DBNAME”.sql.gz.in_progress $FINAL_BACKUP_DIR/“$DBNAME”.sql.gz
fi
done
for DBNAME in $ENCRYPTED_BACKUPS
do
set -o pipefail
if ! lxc exec postgres – pg_dump -O -Fp $DBNAME $EXCLUDED | gzip | openssl $CIPHER -e -pass file:$PASSWORD_FILE -salt > $FINAL_BACKUP_DIR"$DBNAME".sql.gz.enc.in_progress; then
echo “`date` [!!ERROR!!] Failed to produce plain backup of database $DBNAME”
else
mv $FINAL_BACKUP_DIR"$DBNAME".sql.gz.enc.in_progress $FINAL_BACKUP_DIR"$DBNAME".sql.gz.enc
fi
done
# if a remote backup machine is defined, rsync to it
if [ -n “$REMOTE” ]; then
rsync -avq $BACKUP_DIR/* $REMOTE
fi
}
# MONTHLY BACKUPS
DAY_OF_MONTH=`date +%d`
if [ $DAY_OF_MONTH = “01” ]; then
# Delete all expired monthly directories, keeping only two
find $BACKUP_DIR -maxdepth 1 -name “*-monthly” -mtime +60 -exec rm -rf ‘{}’ ‘;’
find $DB_BUCKUP_DIR -maxdepth 1 -name “*-monthly” -mtime +60 -exec rm -rf ‘{}’ ‘;’
perform_backups “-monthly”
fi
# WEEKLY BACKUPS
DAY_OF_WEEK=$(date +%u) #1-7 (Monday-Sunday)
EXPIRED_DAYS=$(expr $((($WEEKS_TO_KEEP * 7) + 1)))
if [ $DAY_OF_WEEK = $DAY_OF_WEEK_TO_KEEP ]; then
# Delete all expired weekly directories
find $DB_BUCKUP_DIR -maxdepth 1 -mtime +$EXPIRED_DAYS -name “*-weekly” -exec rm -rf ‘{}’ ‘;’
find $BUCKUP_DIR -maxdepth 1 -mtime +$EXPIRED_DAYS -name “*-weekly” -exec rm -rf ‘{}’ ‘;’
perform_backups “-weekly”
fi
# Delete daily backups 7 days old or more
find $DB_BUCKUP_DIR -maxdepth 1 -mtime +$DAYS_TO_KEEP -name “*-daily” -exec rm -rf ‘{}’ ‘;’
find $BUCKUP_DIR -maxdepth 1 -mtime +$DAYS_TO_KEEP -name “*-daily” -exec rm -rf ‘{}’ ‘;’
perform_backups “-daily”
2. Environment File (/usr/local/etc/dhis/dhis2-env)
BACKUP_DIR=/var/pgbackups
PLAIN_BACKUPS=“dhis viva app treino”
EXCLUDED="-T aggregated_* -T analytics_* -T completeness_* "
CIPHER=“aes-256-cbc”
DAY_OF_WEEK_TO_KEEP=7
WEEKS_TO_KEEP=4
DAYS_TO_KEEP=7
Noted Issues & Areas Where We Seek Guidance:
- Typo Bugs in Variables:
- $DB_BUCKUP_DIRvs$BUCKUP_DIR(e.g.,find $BUCKUP_DIR …). Because$BUCKUP_DIRis undefined,findattempts to search from root or fails silently.
- Path missing a trailing slash in encrypted backup target:$FINAL_BACKUP_DIR"$DBNAME"…
- Hard-coded Retention Logic (Monthly vs Daily):
- The monthly cleanup uses a fixed-mtime +60rather than leveraging dynamic retention values.
- On the 1st day of the month, the script runs monthly cleanup/backup, then continues sequentially to perform daily cleanup/backup on the same run, creating duplicate directory creations or overlaps.
We would love your suggestions on the cleanest way to refactor this script to fix these typos and else.
Thanks again for your support!
Best regards
Fernando