Created
April 17, 2026 11:33
-
-
Save huyz/d981888b28810aa3d68f1ceed36514d9 to your computer and use it in GitHub Desktop.
UptimeKuma back-up scripts. (Tip: use backup-warden to automatically delete old backups)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # vim: set nomodifiable : Ansible-managed: Do NOT edit this file manually | |
| # Given that I have a bind mount from `/srv/uptimekuma/data` on host to `/app/data` within the container | |
| # 2024-12-21 per https://github.com/louislam/uptime-kuma/issues/1260#issuecomment-1427974555 | |
| #### Preamble (v2024-04-22) | |
| set -euo pipefail | |
| shopt -s failglob | |
| # shellcheck disable=SC2317 | |
| function trap_err { echo "ERR signal on line $(caller)" >&2; } | |
| trap trap_err ERR | |
| trap exit INT # So that ^C will stop the entire script, not just the current subprocess | |
| export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' | |
| # shellcheck disable=SC2034 | |
| SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" | |
| script="${BASH_SOURCE[0]}" | |
| while [[ -L "$script" ]]; do | |
| script="$(readlink "$script")" | |
| done | |
| # shellcheck disable=SC2034 | |
| SCRIPT_DIR="$(dirname "$script")" | |
| ############################################################################## | |
| #### Config | |
| HOST=${HOST:-$(hostname -s)} | |
| SERVICE=uptimekuma | |
| BACKUPS_ROOT_DIR='/srv/var/backups' | |
| DATA_DIR='data' | |
| BACKUP_FILE_SUFFIX='backup.db' | |
| CONTAINER_DATA_DIR='/app/data' | |
| SERVICE_DIR="/srv/$SERVICE" | |
| BACKUPS_DIR="$BACKUPS_ROOT_DIR/$SERVICE" | |
| #### Init | |
| zip_extra_post_args=() | |
| zip_extra_args=() | |
| if [[ ! -t 1 ]]; then | |
| # If stdout is not a terminal, we want quieter logs | |
| zip_extra_args+=(-q) | |
| fi | |
| now=$(date -Iseconds -u | sed 's/+00:00/Z/;s/://g') | |
| mkdir -p "$BACKUPS_DIR" | |
| # Create a symlink for CLI convenience | |
| if [[ ! -L "$SERVICE_DIR/backups" ]]; then | |
| ln -sfn "$BACKUPS_DIR" "$SERVICE_DIR/backups" | |
| fi | |
| cd "$SERVICE_DIR" | |
| if [[ -z "$(docker compose ps -q --services --status=running)" ]]; then | |
| DOCKER_RUN=(docker compose run -T --rm "$SERVICE") | |
| else | |
| DOCKER_RUN=(docker compose exec -T "$SERVICE") | |
| fi | |
| #### Main | |
| echo "𐄫 Backing up ${SERVICE}…" | |
| echo | |
| target="$BACKUPS_DIR/${now}_${SERVICE}_${BACKUP_FILE_SUFFIX}" | |
| echo "𐄬 Dumping $SERVICE SQLite DB…" | |
| "${DOCKER_RUN[@]}" sqlite3 data/kuma.db ".backup '$CONTAINER_DATA_DIR/$BACKUP_FILE_SUFFIX'" | |
| # NOTE: cannot `mv` because the directory ownership is different | |
| cp "$SERVICE_DIR/$DATA_DIR/$BACKUP_FILE_SUFFIX" "$target" | |
| "${DOCKER_RUN[@]}" rm -f "$CONTAINER_DATA_DIR/$BACKUP_FILE_SUFFIX" | |
| echo | |
| echo "𐄬 Archiving in ${target}.zip…" | |
| zip --move --junk-paths "${zip_extra_args[@]}" "$target.zip" "$target" "${zip_extra_post_args[@]}" | |
| echo | |
| # For use by automated scripts | |
| echo "$target.zip" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # vim: set nomodifiable : Ansible-managed: Do NOT edit this file manually | |
| # 2024-12-21 per https://github.com/louislam/uptime-kuma/issues/1260#issuecomment-1427974555 | |
| #### Preamble (v2024-04-22) | |
| set -euo pipefail | |
| shopt -s failglob extglob | |
| # shellcheck disable=SC2317 | |
| function trap_err { echo "ERR signal on line $(caller)" >&2; } | |
| trap trap_err ERR | |
| trap exit INT # So that ^C will stop the entire script, not just the current subprocess | |
| export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' | |
| # shellcheck disable=SC2034 | |
| SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" | |
| script="${BASH_SOURCE[0]}" | |
| while [[ -L "$script" ]]; do | |
| script="$(readlink "$script")" | |
| done | |
| # shellcheck disable=SC2034 | |
| SCRIPT_DIR="$(dirname "$script")" | |
| ############################################################################## | |
| #### Config | |
| HOST=${HOST:-$(hostname -s)} | |
| SERVICE=uptimekuma | |
| BACKUPS_ROOT_DIR='/srv/var/backups' | |
| DATA_DIR='data' | |
| BACKUP_FILE_SUFFIX='backup.db' | |
| CONTAINER_DATA_DIR='/app/data' | |
| SERVICE_DIR="/srv/$SERVICE" | |
| BACKUPS_DIR="$BACKUPS_ROOT_DIR/$SERVICE" | |
| BACKUP_SCRIPT="$(realpath "$SCRIPT_DIR/backup.sh")" | |
| #### Init | |
| unzip_extra_post_args=() | |
| now=$(date -Iseconds -u | sed 's/+00:00/Z/;s/://g') | |
| target="$BACKUPS_DIR/${now}_${SERVICE}_${BACKUP_FILE_SUFFIX}" | |
| if [[ $# -ne 1 || ("$1" != latest && ! -e "$1") ]]; then | |
| echo "Usage: $0 [<backup_file>|latest]" >&2 | |
| echo " ex: $0 $target.zip" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$1" == latest ]]; then | |
| backup_file="$(ls -1 "$BACKUPS_DIR"/20*.@(zip|tbz2) | grep -v 'KEEP-FROM-BEFORE-RESTORE' | tail -n 1)" | |
| echo "ℹ️ Using latest backup file: $backup_file" | |
| echo | |
| else | |
| backup_file="$(realpath "$1")" | |
| fi | |
| DOCKER_RUN=(docker compose run -T --rm "$SERVICE") | |
| #### Prep | |
| echo "𐄫 Shutting down ${SERVICE}…" | |
| cd "$SERVICE_DIR" | |
| docker compose stop 2>&1 | |
| echo | |
| echo "𐄫 Backing up current state…" | |
| last_backup=$("$BACKUP_SCRIPT" | tail -1) | |
| echo | |
| if [[ -n "$last_backup" ]]; then | |
| ext=${last_backup##*.} | |
| last_backup_new_name="${last_backup%.*}.KEEP-FROM-BEFORE-RESTORE.$ext" | |
| echo "𐄬 Renaming this backup to ${last_backup_new_name}…" | |
| mv -v "$last_backup" "$last_backup_new_name" | |
| echo | |
| fi | |
| # Gotta make sure to be able to nuke dotfiles, including .srv-bind-mount, | |
| # even if behind protected directories (e.g., pgdata/) | |
| if sudo bash -c "shopt -s extglob; compgen -G '$SERVICE_DIR/$DATA_DIR/@(|.)*'" > /dev/null; then | |
| echo "𐄫 Deleting current state…" | |
| sudo bash -c "shopt -s extglob; eval rm -rf '$SERVICE_DIR/$DATA_DIR/@(|.)*'" | |
| echo | |
| fi | |
| #### Restore | |
| echo "𐄫 Unarchiving ${backup_file}…" | |
| cd "$SERVICE_DIR/$DATA_DIR" | |
| sudo unzip -Xo "$backup_file" "${unzip_extra_post_args[@]}" | |
| echo | |
| for i in *_$BACKUP_FILE_SUFFIX; do | |
| echo "𐄫 Loading $SERVICE SQLite DB from ${i}…" | |
| "${DOCKER_RUN[@]}" mkdir -p data | |
| "${DOCKER_RUN[@]}" sqlite3 data/kuma.db ".restore '$CONTAINER_DATA_DIR/$i'" | |
| break | |
| done | |
| echo | |
| echo "Done. Service $SERVICE is ready to restart. Re-create DVB container too." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment