Skip to content

Instantly share code, notes, and snippets.

@ssddanbrown
Last active October 30, 2024 15:51
Show Gist options
  • 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"
@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