Skip to content

Instantly share code, notes, and snippets.

@ssddanbrown
Last active March 18, 2024 04:28
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssddanbrown/3d5dbebc51ac6ca45837d8a030b07b65 to your computer and use it in GitHub Desktop.
Save ssddanbrown/3d5dbebc51ac6ca45837d8a030b07b65 to your computer and use it in GitHub Desktop.
bookstack-backup

BookStack Backup Script

This is a simple BookStack backup script, to dump the database, copy uploaded files, and zip it all up into a timestamped archive. This is designed for an on-system install, not a docker setup. Database credentails are automatically read from your BookStack config.

This script will copy uploads before zipping, so you'll need more free space on your system than your BookStack directory already consumes.

Usage

  1. Copy the script down to a file (bookstack-backup.sh).
  2. Tweak the configu variables at the top of the script.
  3. Make the script executable (chmod +x bookstack-backup.sh).
  4. Run the script (./bookstack-backup.sh).
#!/bin/bash
# Directory to store backups within
# Should not end with a slash and not be stored within
# the BookStack directory
BACKUP_ROOT_DIR="$HOME"
# Directory of the BookStack install
# Should not end with a slash.
BOOKSTACK_DIR="/var/www/bookstack"
# Get database options from BookStack .env file
export $(cat "$BOOKSTACK_DIR/.env" | grep ^DB_ | xargs)
# Create an export name and location
DATE=$(date "+%Y-%m-%d_%H-%M-%S")
BACKUP_NAME="bookstack_backup_$DATE"
BACKUP_DIR="$BACKUP_ROOT_DIR/$BACKUP_NAME"
mkdir -p "$BACKUP_DIR"
# Dump database to backup dir using the values
# we got from the BookStack .env file.
mysqldump --single-transaction \
--no-tablespaces \
-u "$DB_USERNAME" \
-p"$DB_PASSWORD" \
"$DB_DATABASE" > "$BACKUP_DIR/database.sql"
# Copy BookStack files into backup dir
cp "$BOOKSTACK_DIR/.env" "$BACKUP_DIR/.env"
cp -a "$BOOKSTACK_DIR/storage/uploads" "$BACKUP_DIR/storage-uploads"
cp -a "$BOOKSTACK_DIR/public/uploads" "$BACKUP_DIR/public-uploads"
# Create backup archive
tar -zcf "$BACKUP_DIR.tar.gz" \
-C "$BACKUP_ROOT_DIR" \
"$BACKUP_NAME"
# Cleanup non-archive directory
rm -rf "$BACKUP_DIR"
echo "Backup complete, archive stored at:"
echo "$BACKUP_DIR.tar.gz"
@dwigth-scott
Copy link

Hi Dan,
Thanks so much for this.

Do you have a script to restore the backup to a different server?
I'm looking to backup the data in one server , and then restore the back up in another server. Both Ubuntu server 22.04.1

@ssddanbrown
Copy link
Author

@dwigth-scott I don't I'm afraid, since restore can be quite order/system dependant and is intended in much less frequency.
Our restore steps in our official docs outlines the process.

@Man-in-Black
Copy link

Man-in-Black commented Aug 17, 2023

I've made small adjustements to reduce the space needed by eliminating the part where you need to copy the files and create the archive after the copy job.
I also included the themes directory, because there are the most adjustment in it.

Unfortunately I can't paste a code here in the comments.
So I put the script here: https://github.com/Man-in-Black/Bookstack-Scripts/blob/main/bookstack-backup.sh

@fliptoback
Copy link

Hi Dan, thanks for the script - but I cant get this to work on my Synology with the docker setup. Could you provide a script that will work with the docker install version of Bookstack? Thanks.

@Man-in-Black
Copy link

Maybe you could just edit the script and extend the commands with "docker exec".
You then just need to mount a new volume where to save the backups from the container(s).
Or you could just export the docker container, but therefore you need to stop the container each time.

@fliptoback
Copy link

Maybe you could just edit the script and extend the commands with "docker exec". You then just need to mount a new volume where to save the backups from the container(s). Or you could just export the docker container, but therefore you need to stop the container each time.

Thanks for the reply. Not a programmer really. I am not sure how to co-relate the docker exec mysqldump command with this script here :-(

It would be really helpful if the backup script can be provided for a docker install of Bookstack.

@Man-in-Black
Copy link

I made a quick Docker version, but this can be modified.
Linked here: https://github.com/Man-in-Black/Bookstack-Scripts/blob/main/bookstack-docker-backup.sh
Or the complete script here:

#!/bin/bash

# Directories to use in this Docker backup script
# Should not end with a slash and not be stored within
# the BookStack directory
BACKUP_ROOT_DIR="/sicherung/bookstack" # Change to your backup path
DOCKER_DIR="/docker/bookstack" # Change to your bookstack Docker folder
BOOKSTACK_DIR="$DOCKER_DIR/bookstack_app_data/www"
CONTAINER_NAME="bookstack" # Change to your container name
CONTAINER_DB_NAME="bookstack_db" # Change to you database container name

# Directory of the BookStack within docker
# Should not end with a slash.
BOOKSTACK_DOCKER="/app/www"

# Get database options from BookStack .env file
export $(cat "$BOOKSTACK_DIR/.env" | grep ^DB_ | xargs)

# Create an export name and location
DATE=$(date "+%Y-%m-%d_%H-%M-%S")
BACKUP_NAME="bookstack_backup_$DATE"
BACKUP_NAME_DOCKER="bookstack_docker_backup_$DATE"
BACKUP_DIR="$BACKUP_ROOT_DIR/$BACKUP_NAME"
BACKUP_DIR_DOCKER="$BACKUP_ROOT_DIR/$BACKUP_NAME_DOCKER"
mkdir -p "$BACKUP_DIR"
mkdir -p "$BACKUP_DIR_DOCKER"

# Dump database to backup dir using the values
# we got from the BookStack .env file.
docker exec $CONTAINER_DB_NAME /usr/bin/mysqldump --single-transaction \
 --no-tablespaces \
 -u "$DB_USERNAME" \
 -p"$DB_PASSWORD" \
 $DB_DATABASE > "$BACKUP_DIR/database.sql"

# Create backup archive
tar -zcf "$BACKUP_DIR.tar.gz" \
 "$DOCKER_DIR/bookstack_app_data" \
 "$BACKUP_DIR/database.sql"
docker cp "$CONTAINER_NAME":"$BOOKSTACK_DOCKER" \
 "$BACKUP_DIR_DOCKER"
tar -czf "$BACKUP_DIR_DOCKER.tar.gz" \
 "$BACKUP_DIR_DOCKER"

# Cleanup non-archive directory
rm -rf "$BACKUP_DIR/"
rm -rf "BACKUP_DIR_DOCKER"

# delete old backups
BACKUPDAYS=14
/usr/bin/find $BACKUP_ROOT_DIR/bookstack_backup_*tar.gz -mtime +$BACKUPDAYS -exec rm -r {} \;
/usr/bin/find $BACKUP_ROOT_DIR/bookstack_docker_backup_*tar.gz -mtime +$BACKUPDAYS -exec rm -r {} \;

echo "Backup complete, archive stored at:"
echo "$BACKUP_DIR.tar.gz"
echo "$BACKUP_DIR_DOCKER.tar.gz"

@ssddanbrown
Copy link
Author

Thanks for providing and sharing that @Man-in-Black.

If it helps other looking for a backup solution, I recently added the System CLI to BookStack which can also help backup/restore tasks. It's in beta stage, but I did work on supporting this in the linuxserver BookStack container so it should be functional there. Remember though, no backup is a backup until it's tested via a restore.

@Man-in-Black
Copy link

That CLI ist also a nice extenstion to the installation

@fliptoback
Copy link

I made a quick Docker version, but this can be modified. Linked here: https://github.com/Man-in-Black/Bookstack-Scripts/blob/main/bookstack-docker-backup.sh Or the complete script here:

#!/bin/bash

# Directories to use in this Docker backup script
# Should not end with a slash and not be stored within
# the BookStack directory
BACKUP_ROOT_DIR="/sicherung/bookstack" # Change to your backup path
DOCKER_DIR="/docker/bookstack" # Change to your bookstack Docker folder
BOOKSTACK_DIR="$DOCKER_DIR/bookstack_app_data/www"
CONTAINER_NAME="bookstack" # Change to your container name
CONTAINER_DB_NAME="bookstack_db" # Change to you database container name

# Directory of the BookStack within docker
# Should not end with a slash.
BOOKSTACK_DOCKER="/app/www"

# Get database options from BookStack .env file
export $(cat "$BOOKSTACK_DIR/.env" | grep ^DB_ | xargs)

# Create an export name and location
DATE=$(date "+%Y-%m-%d_%H-%M-%S")
BACKUP_NAME="bookstack_backup_$DATE"
BACKUP_NAME_DOCKER="bookstack_docker_backup_$DATE"
BACKUP_DIR="$BACKUP_ROOT_DIR/$BACKUP_NAME"
BACKUP_DIR_DOCKER="$BACKUP_ROOT_DIR/$BACKUP_NAME_DOCKER"
mkdir -p "$BACKUP_DIR"
mkdir -p "$BACKUP_DIR_DOCKER"

# Dump database to backup dir using the values
# we got from the BookStack .env file.
docker exec $CONTAINER_DB_NAME /usr/bin/mysqldump --single-transaction \
 --no-tablespaces \
 -u "$DB_USERNAME" \
 -p"$DB_PASSWORD" \
 $DB_DATABASE > "$BACKUP_DIR/database.sql"

# Create backup archive
tar -zcf "$BACKUP_DIR.tar.gz" \
 "$DOCKER_DIR/bookstack_app_data" \
 "$BACKUP_DIR/database.sql"
docker cp "$CONTAINER_NAME":"$BOOKSTACK_DOCKER" \
 "$BACKUP_DIR_DOCKER"
tar -czf "$BACKUP_DIR_DOCKER.tar.gz" \
 "$BACKUP_DIR_DOCKER"

# Cleanup non-archive directory
rm -rf "$BACKUP_DIR/"
rm -rf "BACKUP_DIR_DOCKER"

# delete old backups
BACKUPDAYS=14
/usr/bin/find $BACKUP_ROOT_DIR/bookstack_backup_*tar.gz -mtime +$BACKUPDAYS -exec rm -r {} \;
/usr/bin/find $BACKUP_ROOT_DIR/bookstack_docker_backup_*tar.gz -mtime +$BACKUPDAYS -exec rm -r {} \;

echo "Backup complete, archive stored at:"
echo "$BACKUP_DIR.tar.gz"
echo "$BACKUP_DIR_DOCKER.tar.gz"

Thanks Man-in-Black for the script. I am trying to get my head around the paths.

The BACKUP_DIR - where exactly should this folder be set to?
My synology has the docker-compose set to /volume1/docker/bookstack/config = /config in bookstack.

Thanks. Looking forward to get this script working.

@Man-in-Black
Copy link

The BACKUP_DIR is created out of the to variables "$BACKUP_ROOT_DIR" & "$BACKUP_NAME"

So you simply need to have a look after the top 5 variables to mach your installation.

@fliptoback
Copy link

kstack Docker folder
BOOKSTACK_DIR="$DOCKER_DIR/bookstack_app_data

Sorry I really couldnt get this to work. My docker-compose file is

version: "2"
services:
  bookstack:
    image: lscr.io/linuxserver/bookstack
    container_name: bookstack
    environment:
      - PUID=1026
      - PGID=100
      - APP_URL=http://192.168.1.203:6875
      - DB_HOST=bookstack_db
      - DB_PORT=3306
      - DB_USER=bookstack
      - DB_PASS=MyPassword
      - DB_DATABASE=bookstackapp
    volumes:
      - /volume1/docker/bookstack/config:/config
    ports:
      - 6875:80
    restart: unless-stopped
    depends_on:
      - bookstack_db
  bookstack_db:
    image: lscr.io/linuxserver/mariadb
    container_name: bookstack_db
    environment:
      - PUID=1026
      - PGID=100
      - MYSQL_ROOT_PASSWORD=MyPassword
      - MYSQL_DATABASE=bookstackapp
      - MYSQL_USER=bookstack
      - MYSQL_PASSWORD=MyPassword
    volumes:
      - /volume1/docker/bookstack/config:/config
    restart: unless-stopped

My top 5 parameters in the script is this:

BACKUP_ROOT_DIR="/volume1/data/bookstack_backup" # Change to your backup path
DOCKER_DIR="/volume1/docker/bookstack" # Change to your bookstack Docker folder
BOOKSTACK_DIR="$DOCKER_DIR/config/www"
CONTAINER_NAME="bookstack" # Change to your container name
CONTAINER_DB_NAME="bookstack_db" # Change to you database container name

I have got errors like this:

tar: Removing leading `/' from member names
tar: /volume1/docker/bookstack/bookstack_app_data: Cannot stat: No such file or directory
tar: Removing leading `/' from hard link targets
tar: Exiting with failure status due to previous errors
tar: Removing leading `/' from member names
Backup complete, archive stored at:
/volume1/data/bookstack_backup/bookstack_backup_2023-09-06_17-32-20.tar.gz
/volume1/data/bookstack_backup/bookstack_docker_backup_2023-09-06_17-32-20.tar.gz

Please help?

@Man-in-Black
Copy link

Ok, I have a slightly different docker-compose file:

version: "2"
services:
  bookstack:
    image: lscr.io/linuxserver/bookstack
    container_name: bookstack
    environment:
      - PUID=1000
      - PGID=1000
      - APP_URL=https://xxx
      - DB_HOST=bookstack_db
      - DB_PORT=3306
      - DB_USER=bookstack
      - DB_PASS=xxx
      - DB_DATABASE=bookstackapp
    volumes:
      - ./bookstack_app_data:/config
      - /sicherung/bookstack:/sicherung
    ports:
      - 6875:80
    restart: unless-stopped
    depends_on:
      - bookstack_db
  bookstack_db:
    image: lscr.io/linuxserver/mariadb
    container_name: bookstack_db
    environment:
      - PUID=1000
      - PGID=1000
      - MYSQL_ROOT_PASSWORD=xxx
      - TZ=Europe/Berlin
      - MYSQL_DATABASE=bookstackapp
      - MYSQL_USER=bookstack
      - MYSQL_PASSWORD=xxx
    volumes:
      - ./bookstack_db_data:/config
    restart: unless-stopped

In this case maybe the CLI is a better way.
Just mount another volume (like "/sicherung" in my example) and then the following command:
docker exec -it bookstack /app/www/bookstack-system-cli backup /sicherung/bookstack.zip
That should do the trick.

@fliptoback
Copy link

Hi Man-in-Black, just chiming in to say that adding the new volume like what you have shown here - works well. Thanks for your help. This is greatly appreciated.

@Man-in-Black
Copy link

You're welcome ;)

@fliptoback
Copy link

After running for 2 days i come to realize that it doesnt backup anymore - it has this error message

Target ZIP output location at [/backup/bookstack.zip] already exists.

Is there a way i can force the backup to overwrite the previous one?

@Man-in-Black
Copy link

Man-in-Black commented Sep 9, 2023

did you use the bookstack-system-cli? In this case you need to put something like the date in it:
docker exec -it bookstack /app/www/bookstack-system-cli backup /sicherung/bookstack_$(date "+%Y-%m-%d_%H-%M-%S").zip
and of course a job to rotate the backups since it will fill you disk if you never clean them up ;)

@fliptoback
Copy link

Thanks. Once I add the time stamp to the bookstack.zip file it works. I just need to figure out how to rotate the backups to avoid the database filling up the disk.

Thanks again.

@Man-in-Black
Copy link

a simple solution to that: find /sicherung/bookstack/ -mtime +31 -name bookstack_*.zip -exec rm -f {} \;
this will delete all files older than 31 days.

@fliptoback
Copy link

Thanks Man-in-Black for that. At the moment I just use a bash script to automatically delete the zip file first, then run the docker backup script to create a new bookstack.zip, so that the hyperbackup program on my synology can pick up the bookstack backup files.

Your script is a much better and more elegant way of doing this file rotation. I will try to adapt to my system.
Thanks again for that. This is much appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment