Daily Backup Script

Create a backup script to run every night and backup:

Prerequisites

Prepare directory structure

Before running any script, make sure all the directories exist, otherwise the script will fail (it doesn't check nor creates them).

This is the structure of the backup directory:

/var/
├── backup
│   └── bookstack
│       ├── db
│       ├── files
│       └── nginx

This is the structure of the log directory:

/var/
├── log
│   ├── bookstack
│   │   └── backup_script

To make these paths quickly, just add -p to mkdir, so it creates all folders along the way.

$ sudo mkdir -p /var/backup/bookstack/{db,files,nginx}
$ sudo mkdir -p /var/log/bookstack/backup_script

Adjust permissions

Both directories, especially the backup one will contain sensitive files like the .env configuration file etc. For this reason, put 0600 umask to all directories and files. -R will make sure all directories under the one we specified will have their permissions changed. Use -v to see what dirs and files were changed exactly.

$ sudo chmod -R 600 /var/log/bookstack
$ sudo chmod -R 600 /var/backup/bookstack/

Note that you can create your own directory structure, but make sure to adjust the script accordingly.

Create MySQL configuration file

To backup a MySQL database, you'll utilize mysqldump tool. You would normally use it with the -p option to specify a password for a user of a database. This however, passes the password in plain text as an option to a command, therefore will be visible to any user who runs ps aux. You want to avoid this, because it's a serious security issue.

Here's where my.cnf comes into play. my.cnf is a MariaDB configuration file. MariaDB actually has multiple configuration files in a few directories, my.cnf is usually used for user-specifies settings. By utilizing my.cnf, we can put the password in it, adjust permissions, and then point mysqldump to it. This way, the password is hidden in a well-protected file.

The script will run as root and since my.cnf will store a password, it should only be readable by root. Pick a directory for the file. I will go for /root/.my.cnf. Create it and edit permissions accordingly:

$ sudo touch /root/.my.cnf
$ sudo chmod 600 /root/.my.cnf

Open it with some editor and add the following to it. Replace {password} with a password (without {}) for the user that will be used to access (backup) the database.

$ sudo vi /root/.my.cnf
[mysqldump]
password={password}

Create the scripts

Main script

Due to the way I decided to implement logging for this script, I have to divide it in two. It is completely possible to have just one script, so adjust it to your liking. The script will be run as root, because it's writing to privileged directories. You are free to adjust permissions, directories, users etc.

RUN_bookstack_backup.sh
#!/bin/bash
LOG_DIR=/var/log/bookstack/backup_script
CURRENT_DATE=$(date +"%Y-%m-%d")

# Run bookstack_backup_worker.sh with root privileges, pipe it to gawk which puts a timestamp before every line and writes to file
source ./bookstack_backup_worker.sh | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S %Z]"), $0 }' > $LOG_DIR/bookstack-backup_$CURRENT_DATE.log

Slave script

bookstack_backup_worker.sh
#!/bin/bash
BOOKSTACK_DIR=/var/www/BookStack
NGINX_DIR=/etc/nginx
BACKUP_DIR=/var/backup/bookstack
DB_BACKUP_DIR=$BACKUP_DIR/db
WEBROOT_BACKUP_DIR=$BACKUP_DIR/files
NGINX_BACKUP_DIR=$BACKUP_DIR/nginx

exec 2>&1

# MYSQL DATABASE BACKUP
echo "Starting BACKUP SCRIPT..."
echo "Starting MySQL backup..."
echo "Backing up to $DB_BACKUP_DIR..."
mysqldump --defaults-extra-file=/root/.my.cnf -v -u user database | gzip -vc > $DB_BACKUP_DIR/bookstackdb-backup_$CURRENT_DATE.sql.gz
echo "Done..."

# WEBSERVER BACKUP
# Archive and compress BookStack webroot folder and save it to backup location with current date
echo "Backing up BookStack webroot directory to $WEBROOT_BACKUP_DIR..."
tar -czvf $WEBROOT_BACKUP_DIR/bookstack-backup_$CURRENT_DATE.tar.gz $BOOKSTACK_DIR
echo "Done..."

# NGINX CONFIG BACKUP
# Archive and compress Nginx config folder and save it to backup location with current date
echo "Backing up Nginx to $NGINX_BACKUP_DIR..."
tar -czvf $NGINX_BACKUP_DIR/nginx-backup_$CURRENT_DATE.tar.gz $NGINX_DIR
echo "Done..."
echo "Finished..."
VARIABLE PREPARATION

exec 2>&1 – Redirect stderr (2) to stdout (1). This means all errors and normal messages will be redirected to standart output (stdout), which is what we would normally see in a terminal. All messages generated by this script will then go from stdout to the master script, which then redirects is to gawk using pipe | (described in the master script).

MYSQL DATABASE BACKUP
WEBSERVER BACKUP
NGINX CONFIG BACKUP

You can now run manual backups. Logs will be located in /var/log/bookstack/backup_script. Use cron to automate it.

Add to cron

In order to run the script daily, setup cron to do it for you.

Save scripts to a location

These scripts will run as root, save them to a safe location and give them appropriate privileges (700).

Create the directory:

$ sudo su
(root)$ mkdir -p /root/scripts/bookstack_backup

Copy the scripts from an old directory:

(root)$ cd /root/scripts/bookstack_backup
(root)$ cp /home/user/bookstack_backup_worker.sh /home/user/RUN_bookstack_backup.sh .

Adjust permissions:

(root)$ chmod 700 bookstack_backup_worker.sh RUN_bookstack_backup.sh
Modify the script

Take a look at this script that we are about to add to crontab. Do you see anything wrong?

#!/bin/bash
LOG_DIR=/var/log/bookstack/backup_script
CURRENT_DATE=$(date +"%Y-%m-%d")

# Run bookstack_backup_worker.sh with root privileges, pipe it to gawk which puts timestamp before every line and writes to file
source ./bookstack_backup_worker.sh | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S %Z]"), $0 }' > $LOG_DIR/bookstack-backup_$CURRENT_DATE.log

Don't worry if you don't, I also hadn't seen anything when I looked at it before.

Remember how we ran this script previously? We had them both in the same directory and typed ./RUN_bookstack_backup.sh. Inside of this script we called source with ./ again. This works standalone, but try adding it to cron and you will sooner or later realize the script is not working and the reason for it is ./. There might be other reasons why some scripts work standalone, but don't in cron, but this is what helped in my case. Generally, cron doesn't like relative paths and user specific things, especially if you run the script as another user. Replacing ./bookstack_backup_worker.sh with a proper full path to the other script solved my problems:

#!/bin/bash
LOG_DIR=/var/log/bookstack/backup_script
CURRENT_DATE=$(date +"%Y-%m-%d")

# Run bookstack_backup_worker.sh with root privileges, pipe it to gawk which puts timestamp before every line and writes to file
source /root/scripts/bookstack_backup/bookstack_backup_worker.sh | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S %Z]"), $0 }' > $LOG_DIR/bookstack-backup_$CURRENT_DATE.log

Tip: Use * * * * * in /etc/crontab to run the script every minute to troubleshoot quicker.

Edit crontab

If you aren't too afraid of messing up and you want to run the scripts as root, you can open /etc/crontab directly

(root)$ vi /etc/crontab

Explaining what options you have when setting a cron job is out of the scope of this guide. To run the script every dat at 4AM, put the following line at the end of the file:

0  4  * * *   root    /root/scripts/bookstack_backup/RUN_bookstack_backup.sh

Use https://crontab.guru to convert your desired time to cronjob command.


Revision #15
Created 22 September 2021 13:41:07 by Marek
Updated 27 September 2021 00:26:50 by Marek