#!/usr/bin/env bash # mattermost-restore-database.sh [backup-file-name] # # Replaces the Mattermost database with one of the dumps the backups service wrote. # # ./mattermost-restore-database.sh list and ask # ./mattermost-restore-database.sh restore that one # # EVERY PATH, NAME AND CREDENTIAL COMES FROM THE RUNNING BACKUPS CONTAINER. # The previous version carried the database name, user and backup directory # as literals, wrong for any .env that sets them differently, and found its # containers with a name filter that misses them under any -p but the default. # The backup loop reads its own environment, so this reads the same one, and # the two cannot disagree. # # CI runs this exact file against a marker written after the backup it # restores, and requires the marker to be gone. # # Set COMPOSE_PROJECT_NAME if the stack was started with a -p other than mattermost. set -Eeuo pipefail PROJECT="${COMPOSE_PROJECT_NAME:-mattermost}" APP_SERVICE="mattermost" cid() { # the container of one compose service in this project docker ps -aq --filter "label=com.docker.compose.project=$PROJECT" \ --filter "label=com.docker.compose.service=$1" | head -n 1 } APP="$(cid "$APP_SERVICE")"; BKP="$(cid backups)" [ -n "$BKP" ] || { echo "error: no backups container in compose project '$PROJECT' (set COMPOSE_PROJECT_NAME)" >&2; exit 1; } [ -n "$APP" ] || { echo "error: no $APP_SERVICE container in compose project '$PROJECT'" >&2; exit 1; } [ "$(docker inspect -f '{{.State.Running}}' "$BKP")" = true ] || { echo "error: the backups container is not running" >&2; exit 1; } env_of() { docker exec "$BKP" printenv "$1"; } DIR="$(env_of POSTGRES_BACKUPS_PATH)"; NAME="$(env_of POSTGRES_BACKUP_NAME)" DB_NAME="$(env_of MATTERMOST_DB_NAME)"; DB_USER="$(env_of MATTERMOST_DB_USER)" SELECTED="${1:-}" if [ -z "$SELECTED" ]; then echo "Database backups in $DIR:" docker exec "$BKP" sh -c "ls -1 '$DIR' | grep -E '^$NAME-.*\\.gz\$'" || { echo " none found" >&2; exit 1; } read -r -p "File name to restore: " SELECTED fi case "$SELECTED" in ""|*/*) echo "error: give a file name from the list, not a path" >&2; exit 1 ;; esac docker exec "$BKP" gunzip -t "$DIR/$SELECTED" >/dev/null \ || { echo "error: $DIR/$SELECTED is missing or does not open; nothing was changed" >&2; exit 1; } echo "Stopping $APP_SERVICE so nothing writes while the database is replaced" docker stop "$APP" >/dev/null restart() { docker start "$APP" >/dev/null && echo "Started $APP_SERVICE"; } trap 'restart' EXIT echo "Restoring $SELECTED" if ! docker exec "$BKP" sh -c "(set -o pipefail) 2>/dev/null && set -o pipefail; set -eu dropdb --force -h postgres -U '$DB_USER' --if-exists '$DB_NAME' createdb -h postgres -U '$DB_USER' '$DB_NAME' gunzip -c '$DIR/$SELECTED' | psql -q -v ON_ERROR_STOP=1 -h postgres -U '$DB_USER' -d '$DB_NAME' >/dev/null"; then echo "error: the restore failed part-way. The database may now be empty: restore another backup before using Mattermost." >&2 exit 1 fi echo "Restored $SELECTED into $DB_NAME"