How to Script Automatic Backups of Self-Hosted Invoice Ninja v5
How to Automate Backups of Invoice Ninja v5
Author: Josh Madrone
This guide is for self-hosted installations of the newest version of Invoice Ninja, I believe most are calling it "v5". Anyway, it was a complete re-write of the codebase from the ground up and required migration from a v4 install (could not overwrite existing installation).
I initially did the migration quite early when there were still quite a few bugs to work out and I wish I had waited a bit longer, but I'm very happy with the new version as it is today, and the continued development of new features is quite impressive, my hats off to the developers.
Also, the app lacks a built-in automated means of backup, so I decided to write a script to backup my server daily, transfer backups to a secure cloud storage, while keeping a specified number of local backups to ensure disk space does not fill-up with backup files.
On to the backup...
inbackup.sh - Bash script to backup Invoice Ninja v5
inbackup.sh
is a bash script that I use to backup my Invoice Ninja v5 self-
hosted installation running on a Vultr instance that is running Ubuntu 20.04.
Everything needed to perform a full restore to a new server is backed up into a single tarball using gunzip compression, and then uploaded to an Amazon S3 bukcet for storage.
There are a few configurable user options, such as:
number_of_days_to_keep
local backups on the serverbackup_path
- where to store local backupsinstall_dir
- root of installation (e.g.var/www/html/invoiceninja
)s3_bucket
- name of AWS S3 bucketdb_name
is the database namedb_user
is the name of the database user (I'm usingroot
)
Use at your own risk! Feel free to download, use, and modify this script for your own purposes.
Notes
-
I have not tested this on any system other than my production server, but you should be able to use with other linux servers with minor modifications to suit your environment
-
I am using AWS S3 for cloud storage, but you could easily modify script to work with other cloud providers, using their CLI or something like
s3cmd
for S3 compatible storage providers
What do we need to backup?
Let's start with what needs to be backed up to facilitate a full restore to a either the existing or new server. I'm going to break this up into two separate sections: (1) application, and (2) operating system.
application
.env
- application variables are stored in the environment file at${installation_dir}/.env
, where${installation_dir}
is the root of the installation (/var/www/html/invoiceninja
in my case)- database - we'll use
mysqldump
to backup the MariaDB database (also works for MySQL) - docs & attachments are stored in
public/storage
so we'll create a backup of that directory asninja-docs
also - Note: the company logo is stored at
public/images
, but I'm not backing that up as I have my logo saved in plenty of other places, but you could easily add it to the script if desired
operating system
This section references the other files used by the operating system, such as configuration files used by the Nginx web server, SSL certificate, etc.
.my.cnf
- database credentials are stored in a MySQL config file at/root/.my.cnf
.aws
- standard AWS credentials are stored in this directory. Read more about AWS CLI at https://aws.amazon.com/cli/- Nginx* config files - includes web server vhost config file & LE SSL** cert
* I'm using Nginx web server, but you could modify to work with Apache pretty easily
** I use acme.sh
(view on Github here) to manage my SSL certificate from LetsEncrypt (actually it might be from ZeroSSL but that's not important, both are free)
Getting Started
-
Clone the repo locally
git clone https://github.com/jmadrone/inbackup.sh
-
Make it executable if not already
cd inbackup.sh chmod +x inbackup.sh
-
Set user options in the
inbackup.sh
filevi inbackup.sh
Note: you could use any preferred text editor (e.g.
nano
) -
Run the backup script like this
./inbackup.sh
Note: Use
sudo
if not running asroot
user -
Add this line to your
crontab
to automatically run your backup every day at 3 AM (change to fit your environment)0 3 * * * /root/inbackup.sh/inbackup > /root/backups/inbackup-"$(date +%F)".log
Contents of the script (inbackup.sh
)
#!/usr/bin/env bash
#
# inbackup.sh
#
# A script to create full application backups of Invoice Ninja v5,
# and upload them to Amazon S3 cloud storage.
#
# For more information please see the README.
#
# Copyright (C) 2022 by Josh Madrone & Emerald Security LLC <https://emsec.ca>
#
#
################################ LICENSE ######################################
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
################################# user options ################################
backup_dir="$HOME/backups" # where to store local backups
# Email format
s3_bucket="s3:/bucket_name/prefix/" # name of the S3 bucket and prefix
# with trailing slash (/)
days_to_keep=7 # the number of days of local
# backups to keep
db_name="ninjadb" # database name (find in your .env file)
db_user="root" # database user (find in your .env file)
install_dir="/path/to/invoiceninja/installation" # web root of installation
################################ variables ####################################
# You shouldn't need to change these.
today="$(date +%Y-%m-%d)" # this is date only as YYYY-MM-DD
user="$(whoami)" # name of local user
now="$(date -R)" # this is full date and time in RFC
######################### script happens below this line ######################
# Check if root
if [ "$EUID" -ne 0 ]
then echo "Please run as root"
exit 1
fi
# Create the backup dir
if [ ! -d "$backup_dir" ]; then
mkdir -p "$backup_dir"
fi
echo Starting backup of Invoice Ninja v5 - "$now"...
echo ----------------------------
echo -- Step 1 of 5 --
echo Backing up application files...
# Backup env file
/usr/bin/cp "${install_dir}/.env" "${backup_dir}/env-${today}.txt"
echo Good to go.
echo Backing up ninjadb MySQL database with mysqldump to "${backup_dir}/ninjadb-${today}.sql"...
# Backup database uncompressed
/usr/bin/mysqldump --defaults-extra-file=~/.my.cnf --single-transaction --quick --skip-lock-tables -u "$db_user" "$db_name" > "${backup_dir}/ninjadb-${today}.sql"
echo Database dump successful.
echo Creating tarball with contents of 'public/storage' directory at "${backup_dir}/ninja-docs-${today}.tar"...
# Move to "$backup_dir"
cd "$backup_dir" || exit
# Create a tarball with contents of public/storage diretory
/usr/bin/tar cf "ninja-docs-${today}.tar" "${install_dir}/public/storage"
echo Good to go.
echo -- Step 2 of 5 --
echo Backing up other files...
# Backup Nginx files (including SSL cert)
/usr/bin/tar cf "nginx-${today}.tar" /etc/nginx
echo Nginx backup successful.
# Backup MySQL config file
/usr/bin/cp "/${user}/.my.cnf" "${backup_dir}/my.cnf-${today}.txt"
echo Database config file backed up.
# Backup AWS credentials
/usr/bin/tar cf "awscli-${today}.tar" "/${user}/.aws"
echo AWS credentials backed up.
echo -- Step 3 of 5 --
echo Creating archive of full backup using gunzip compression
# Create single archive using gunzip compression
/usr/bin/tar czf "invoice-ninja-full-backup-${today}.tgz" \
"ninjadb-${today}.sql" \
"ninja-docs-${today}.tar" \
"env-${today}.txt" \
"nginx-${today}.tar" \
"awscli-${today}.tar" \
"my.cnf-${today}.txt"
echo and removing individual backup files...
# Remove individual backup files
rm "ninjadb-${today}.sql" \
"ninja-docs-${today}.tar" \
"env-${today}.txt" \
"nginx-${today}.tar" \
"awscli-${today}.tar" \
"my.cnf-${today}.txt"
echo Good to go.
echo Backup successfully completed and saved at "${backup_dir}/invoice-ninja-full-backup-${today}.tgz" -----
echo -- Step 4 of 5 --
echo Uploading full backup to Amazon S3 bucket "$s3_bucket"...
# Copy full backup to S3 compatible storage
/usr/local/bin/aws s3 cp "invoice-ninja-full-backup-${today}.tgz" "$s3_bucket"
echo Successfully copied to cloud storage at "${s3_bucket}invoice-ninja-full-backup-${today}.tgz"
echo -- Step 5 of 5 --
echo Removing old 'local' backups to free disk space...
# Find backups older than 1 day and delete them
/usr/bin/find "$backup_dir" -type f -mtime +"$days_to_keep" -delete
echo -------- Backup of Invoice Ninja v5 Complete at "$now" --------
exit