Skip to content

Commit

Permalink
V1.1.0 (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
aSouchereau authored Aug 9, 2024
1 parent 53c3376 commit bd54127
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 41 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
docker-compose.yaml
.env
.env
.idea
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG ALPINE_VERSION=3.18.4
ARG ALPINE_VERSION=3.20.2
FROM alpine:${ALPINE_VERSION}
LABEL Maintainer="Alex Souchereau"
LABEL Description="Easy way to manage backups of your vaultwarden data"
Expand All @@ -18,6 +18,7 @@ RUN chmod -R 755 /app/scripts/
RUN apk update && apk upgrade
RUN apk add --no-cache \
sqlite \
mariadb-client \
rsync

# forward script logs to docker log collector
Expand Down
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Simple automatic Vaultwarden backups using the [Grandfather-Father-Son](https://www.backblaze.com/blog/better-backup-practices-what-is-the-grandfather-father-son-approach/) approach.

** This image currently only supports sqlite installations of Vaultwarden.
** This image currently only supports sqlite and mysql/mariadb installations of Vaultwarden.

** This is a 3rd party project created independently by a user of Vaultwarden and is not associated with [Vaultwarden](https://github.com/dani-garcia/vaultwarden), [Bitwarden](https://github.com/bitwarden), or Bitwarden Inc.

Expand All @@ -12,7 +12,7 @@ Simple automatic Vaultwarden backups using the [Grandfather-Father-Son](https://
vaultwarden-backup-gfs has three requirements:

- Your Vaultwarden data folder or volume is mounted to `/vaultwarden/data`
- An output folder mounted to `/app/output/`
- An output folder mounted to `/vw-backups/output/`
- `DAILY_RETENTION`, `WEEKLY_RETENTION`, and `MONTHLY_RETENTION` env variables are set

Once retention values are set and the correct volumes mounted, you can run the container with the following commands
Expand All @@ -22,7 +22,7 @@ docker pull asouchereau/vaultwarden-backup-gfs
docker run -d --name vaultwarden-backup-gfs \
-v /vw-data/:/vaultwarden/data/ \
-v /vw-backups/:/app/output/ \
-v /vw-backups/:/vw-backups/output/ \
-e DAILY_RETENTION=7 \
-e WEEKLY_RETENTION=8 \
-e MONTHLY_RETENTION=6 \
Expand Down Expand Up @@ -50,7 +50,7 @@ services:
restart: always
volumes:
- vw-data:/vaultwarden/data/
- vw-backups:/app/output
- vw-backups:/vw-backups/output
environment:
- DAILY_RETENTION=7
- WEEKLY_RETENTION=8
Expand All @@ -66,6 +66,39 @@ volumes:
~~~

### Database
#### sqlite
By default, this image looks for a db.sqlite3 file in the top level of your mounted data directory. Set environment variable `DB_TYPE` to `sqlite` or leave it unset.
-#### MariaDB/MySQL
For MySQL or MariaDB setups, this image requires your vaultwarden database credentials to connect and create a dump file. Set the following environment variables
- `DB_TYPE` ("mysql")
- `DB_HOST` (server address or name of docker container running db server)
- `DB_PORT`
- `DB_USER`
- `DB_PASSWORD`
- `DB_DATABASE`

For example:

~~~
backup:
image: asouchereau/vaultwarden-backup-gfs:latest
restart: always
volumes:
- vw-data:/vaultwarden/data/
- vw-backups:/vw-backups/output
environment:
- DAILY_RETENTION: 7
- WEEKLY_RETENTION: 8
- MONTHLY_RETENTION: 6
- DB_TYPE: "mysql"
- DB_HOST: "mariadb"
- DB_PORT: "3306"
- DB_USER: "vwuser"
- DB_PASSWORD: "changeme"
- DB_DATABASE: "vaultwarden"
~~~


## Usage

Expand Down
2 changes: 1 addition & 1 deletion docker-compose-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ services:
restart: always
volumes:
- vw-data:/vaultwarden/data/
- vw-backups:/app/output
- vw-backups:/vw-backups/output
environment:
- DAILY_RETENTION=7
- WEEKLY_RETENTION=8
Expand Down
55 changes: 35 additions & 20 deletions scripts/backup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ function init() {
export BACKUP_TYPE=$1

# useful directories
BACKUP_DIR=/vaultwarden/data
INPUT_DIR=/app/input
export OUTPUT_DIR=/app/output/$BACKUP_TYPE
DATA_DIR=/vaultwarden/data
STAGING_DIR=/vw-backups/staging
export OUTPUT_DIR=/vw-backups/output/$BACKUP_TYPE

export NOW="$(date "+%Y-%m-%d")"
TMP_SQLITE_DB_FILENAME="${INPUT_DIR}/db.${NOW}.sqlite3"

if [ ! -d "$OUTPUT_DIR" ] ; then
mkdir -p "${OUTPUT_DIR}"
Expand All @@ -24,41 +23,46 @@ function init() {
}

function clear_dir() {
# reset input directory for new backup
if [ -d "$INPUT_DIR" ] ; then
echo "removing previous input directory"
rm -rf "${INPUT_DIR}"
if [ -d "$STAGING_DIR" ] ; then
echo "clearing staging directory"
rm -rf "${STAGING_DIR}"
fi
if [ ! -d "$INPUT_DIR" ] ; then
mkdir "${INPUT_DIR}"
echo "recreating input directory"
if [ ! -d "$STAGING_DIR" ] ; then
mkdir "${STAGING_DIR}"
fi

}

function backup_sqlite() {
echo "creating sqlite online backup"
sqlite3 "${BACKUP_DIR}/db.sqlite3" ".backup '${TMP_SQLITE_DB_FILENAME}'"
echo "creating sqlite backup"
sqlite3 "${DATA_DIR}/db.sqlite3" ".backup '${STAGING_DIR}/db.sqlite3'"
echo "checking backup integrity"
if sqlite3 "${TMP_SQLITE_DB_FILENAME}" "pragma integrity_check;"; then
echo "sqlite online backup finished successfully"
if sqlite3 "${STAGING_DIR}/db.sqlite3" "pragma integrity_check;"; then
echo "sqlite backup finished successfully"
else
echo "Backup canceled: database file failed integrity check"

exit 1
fi
}

function backup_mysql() {
mysqldump -h $DB_HOST -P $DB_PORT -u $DB_USER -p$DB_PASSWORD --lock-tables $DB_DATABASE > $STAGING_DIR/db.sql
if [ $? -eq 0 ]; then
echo "Database backup successful"
else
exit 1
fi
}

function backup_files() {
echo "creating copy of files"
rsync --recursive --partial --exclude="icon_cache" --exclude="tmp" --exclude="db.sqlite3" --exclude="db.sqlite3-shm" --exclude="db.sqlite3-wal" "${BACKUP_DIR}/" "${INPUT_DIR}/"
mv "${INPUT_DIR}/db.${NOW}.sqlite3" "${INPUT_DIR}/db.sqlite3"
rsync --recursive --partial --exclude="icon_cache" --exclude="tmp" --exclude="db.sqlite3" --exclude="db.sqlite3-shm" --exclude="db.sqlite3-wal" "${DATA_DIR}/" "${STAGING_DIR}/"
}

function package() {
echo "packaging backup"
tar -cf "${OUTPUT_DIR}/${NOW}_vw-data.tar" -C "${INPUT_DIR}" .
echo "backup process complete"
tar -cf "${OUTPUT_DIR}/${NOW}_vw-data.tar" -C "${STAGING_DIR}" .
}

function cleanup() {
Expand All @@ -69,7 +73,18 @@ function cleanup() {
init "$1"
clear_dir

backup_sqlite
case "$DB_TYPE" in
sqlite)
backup_sqlite
;;
mysql|mariadb)
backup_mysql
;;
*)
echo "[Error] Invalid database type. Skipping database backup..."
;;
esac

backup_files

package
Expand Down
11 changes: 5 additions & 6 deletions scripts/cleanup.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
echo "cleaning up expired backups"

# Set default backup minimums
# Optional env var... min backups should be same as retention period by default
MIN_DAILY_BACKUPS="${MIN_DAILY_BACKUPS:-$DAILY_RETENTION}"
MIN_WEEKLY_BACKUPS="${MIN_WEEKLY_BACKUPS:-$WEEKLY_RETENTION}"
MIN_MONTHLY_BACKUPS="${MIN_MONTHLY_BACKUPS:-$MONTHLY_RETENTION}"
Expand All @@ -20,8 +20,7 @@ else
fi


# Remove backups older than the retention period
function cleanup() {
function clean_expired_backups() {
cd "${OUTPUT_DIR}"
retentionCutoff=$(date -u -d "@$((${NOW} - ${1} ))" +%s) # get epoch time for cutoff date of provided retention period
excessFiles=$(find "$OUTPUT_DIR" -maxdepth 1 -type f -name '*-*-*_vw-data.tar' -exec basename {} \; | sort -r | tail -n +$((${2} + 1)))
Expand All @@ -40,13 +39,13 @@ function cleanup() {

case "$BACKUP_TYPE" in
daily)
cleanup "$DAILY_RETENTION" "$MIN_DAILY_BACKUPS"
clean_expired_backups "$DAILY_RETENTION" "$MIN_DAILY_BACKUPS"
;;
weekly)
cleanup "$WEEKLY_RETENTION" "$MIN_WEEKLY_BACKUPS"
clean_expired_backups "$WEEKLY_RETENTION" "$MIN_WEEKLY_BACKUPS"
;;
monthly)
cleanup "$MONTHLY_RETENTION" "$MIN_MONTHLY_BACKUPS"
clean_expired_backups "$MONTHLY_RETENTION" "$MIN_MONTHLY_BACKUPS"
;;
*)
echo "Invalid backup type. Skipping cleanup"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

# WARNING: This script will affect data in the output folder. Do not use it in production

# As a safety precaution, this script will only run when the APP_ENV is set to "dev", and the first argument passed set to "DELETEMYDATA".
# As a safety precaution, this script will only run when the APP_ENV is set to "dev", and the first argument passed set to "NUKEMYBACKUPS".

OUTPUT_DIR=/app/output
OUTPUT_DIR=/vw-backups/output

function clear_output() {
rm -rf "${OUTPUT_DIR}"
Expand All @@ -16,7 +16,7 @@ function clear_output() {
mkdir "${OUTPUT_DIR}/monthly"
}

if [ "$APP_ENV" = "dev" ] && [ "$1" = "DELETEMYDATA" ]; then
if [ "$APP_ENV" = "dev" ] && [ "$1" = "NUKEMYBACKUPS" ]; then
echo "Creating mock backup files"
clear_output

Expand All @@ -32,6 +32,6 @@ if [ "$APP_ENV" = "dev" ] && [ "$1" = "DELETEMYDATA" ]; then
touch "${OUTPUT_DIR}/monthly/${filename}"
done < "monthly-filenames.txt"
else
echo "Conditions not met, test script refuses to run. Check line 7 of /app/scripts/dev/populate_output.sh for details"
echo "Conditions not met, script refuses to run. Check line 7 of /app/scripts/dev/create_dummy_files.sh for details"
fi

31 changes: 28 additions & 3 deletions scripts/entry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ trap 'cleanup' SIGINT SIGTERM
echo "╦ ╦╦ ╦ ╔╗ ╔═╗╔═╗╦╔═╦ ╦╔═╗ ╔═╗╔═╗╔═╗";
echo "╚╗╔╝║║║ ─── ╠╩╗╠═╣║ ╠╩╗║ ║╠═╝ ─── ║ ╦╠╣ ╚═╗";
echo " ╚╝ ╚╩╝ ╚═╝╩ ╩╚═╝╩ ╩╚═╝╩ ╚═╝╚ ╚═╝";
echo "v1.1.0";


# populate output for dev testing
if [ "$APP_ENV" = "dev" ] && [ "$POPULATE_OUTPUT_ON_START" = true ]; then
if [ "$APP_ENV" = "dev" ] && [ "$CREATE_DUMMY_FILES" = true ]; then
cd /app/scripts/dev
sh populate_output.sh DELETEMYDATA
sh create_dummy_files.sh NUKEMYBACKUPS
fi

# load correct cron schedules depending on environment
Expand All @@ -30,7 +31,31 @@ else
/usr/bin/crontab /crontab.txt
fi

# start cron
# check if database is reachable
DB_TYPE="${DB_TYPE:-"sqlite"}"
case "$DB_TYPE" in
sqlite)
echo "Checking sqlite database"
sqlite3 "${STAGING_DIR}/db.sqlite3" "pragma integrity_check;"
;;
mysql|mariadb)
result=$(mysql --host $DB_HOST --port $DB_PORT \
--user $DB_USER -p$DB_PASSWORD $DB_DATABASE -e "SELECT VERSION();")
if [ $? -eq 0 ]; then
echo "Database connection successful"
else
echo $result
exit 1
fi
;;
*)
echo "Invalid or unsupported database type \"$DB_TYPE\""
exit 1
;;

esac


echo "Starting cron"
exec /usr/sbin/crond -f -l 8 &
CRON_PID=$!
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.1.0

0 comments on commit bd54127

Please sign in to comment.