Initial commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Kopia zapasowa instancji: baza PostgreSQL i pliki MinIO.
|
||||
#
|
||||
# Uruchomienie z katalogu projektu:
|
||||
# ./scripts/backup.sh
|
||||
# ./scripts/backup.sh /mnt/backup/webinar
|
||||
#
|
||||
# Na serwerze deweloperskim z Windows i Git Bash trzeba wylaczyc konwersje
|
||||
# sciezek, inaczej tar dostanie sciezke windowsowa:
|
||||
# MSYS_NO_PATHCONV=1 COMPOSE_FILE=docker-compose.yml ENV_FILE=.env ./scripts/backup.sh
|
||||
#
|
||||
# Domyslnie zapisuje do ./backups. Kazda kopia trafia do osobnego katalogu
|
||||
# nazwanego znacznikiem czasu, zeby przypadkiem nie nadpisac poprzedniej.
|
||||
#
|
||||
# Odtworzenie: patrz scripts/restore.sh oraz sekcja w README.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.prod.yml}"
|
||||
ENV_FILE="${ENV_FILE:-.env.production}"
|
||||
BACKUP_ROOT="${1:-./backups}"
|
||||
|
||||
# ile kopii zachowac, starsze sa usuwane
|
||||
KEEP_LAST="${KEEP_LAST:-14}"
|
||||
|
||||
# Kopia bez plikow MinIO: SKIP_MINIO=1
|
||||
#
|
||||
# Nagrania stanowia prawie caly rozmiar kopii (400 MB do 1 GB na godzine),
|
||||
# a pliki w MinIO sa NIEZMIENNE PO WGRANIU: kopia sprzed tygodnia zawiera
|
||||
# dokladnie te same nagrania co dzisiejsza. Przy ograniczonym miejscu warto
|
||||
# robic codziennie sama baze, a calosc raz w tygodniu.
|
||||
#
|
||||
# Zrzut samej bazy wystarcza do odtworzenia konfiguracji, zapisow, kursow
|
||||
# i wynikow. Zginie wylacznie TRESC plikow: wpisy w bazie zostana i beda
|
||||
# wskazywac na nieistniejace obiekty.
|
||||
SKIP_MINIO="${SKIP_MINIO:-0}"
|
||||
|
||||
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
||||
TARGET="${BACKUP_ROOT}/${TIMESTAMP}"
|
||||
|
||||
compose() {
|
||||
docker compose -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" "$@"
|
||||
}
|
||||
|
||||
log() {
|
||||
echo "[$(date +%H:%M:%S)] $*"
|
||||
}
|
||||
|
||||
if [ ! -f "${ENV_FILE}" ]; then
|
||||
echo "BLAD: brak pliku ${ENV_FILE}. Uruchom skrypt z katalogu projektu." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# nazwa bazy i uzytkownika z konfiguracji instancji
|
||||
DB_NAME="$(grep -E '^DB_NAME=' "${ENV_FILE}" | cut -d= -f2-)"
|
||||
DB_USER="$(grep -E '^DB_USER=' "${ENV_FILE}" | cut -d= -f2-)"
|
||||
|
||||
if [ -z "${DB_NAME}" ] || [ -z "${DB_USER}" ]; then
|
||||
echo "BLAD: nie udalo sie odczytac DB_NAME lub DB_USER z ${ENV_FILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${TARGET}"
|
||||
log "Kopia zapasowa do katalogu ${TARGET}"
|
||||
|
||||
# --- baza danych ---
|
||||
# pg_dump w formacie custom, mniejszy i pozwala na wybiorcze odtwarzanie
|
||||
log "Zrzut bazy ${DB_NAME}..."
|
||||
compose exec -T postgres pg_dump \
|
||||
-U "${DB_USER}" \
|
||||
-d "${DB_NAME}" \
|
||||
--format=custom \
|
||||
--no-owner \
|
||||
> "${TARGET}/baza.dump"
|
||||
|
||||
DB_SIZE="$(du -h "${TARGET}/baza.dump" | cut -f1)"
|
||||
log "Baza zapisana (${DB_SIZE})"
|
||||
|
||||
# --- pliki MinIO ---
|
||||
# Obraz MinIO nie zawiera tar, wiec archiwizujemy wolumen z pomocniczego
|
||||
# kontenera alpine podpietego do tego samego wolumenu w trybie tylko do odczytu.
|
||||
if [ "${SKIP_MINIO}" = "1" ]; then
|
||||
log "Pomijam pliki MinIO (SKIP_MINIO=1)"
|
||||
MINIO_SIZE="pominieto"
|
||||
else
|
||||
log "Kopia plikow MinIO..."
|
||||
|
||||
# Nazwe wolumenu bierzemy prosto z dzialajacego kontenera, zeby nie zgadywac
|
||||
# przedrostka projektu ani nie parsowac konfiguracji compose.
|
||||
MINIO_CONTAINER="$(compose ps -q minio)"
|
||||
if [ -z "${MINIO_CONTAINER}" ]; then
|
||||
echo "BLAD: kontener minio nie dziala, nie mozna wykonac kopii plikow" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MINIO_VOLUME="$(docker inspect "${MINIO_CONTAINER}" \
|
||||
--format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')"
|
||||
|
||||
if [ -z "${MINIO_VOLUME}" ]; then
|
||||
echo "BLAD: nie udalo sie ustalic wolumenu MinIO" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log " wolumen: ${MINIO_VOLUME}"
|
||||
|
||||
docker run --rm \
|
||||
-v "${MINIO_VOLUME}:/data:ro" \
|
||||
alpine:3.20 tar czf - -C /data . > "${TARGET}/minio.tar.gz"
|
||||
|
||||
# pusty albo podejrzanie maly plik oznacza, ze archiwizacja sie nie udala
|
||||
if [ ! -s "${TARGET}/minio.tar.gz" ] || [ "$(stat -c%s "${TARGET}/minio.tar.gz")" -lt 100 ]; then
|
||||
echo "BLAD: archiwum MinIO jest puste, kopia jest niekompletna" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MINIO_SIZE="$(du -h "${TARGET}/minio.tar.gz" | cut -f1)"
|
||||
log "Pliki MinIO zapisane (${MINIO_SIZE})"
|
||||
fi
|
||||
|
||||
# --- konfiguracja instancji ---
|
||||
# .env.production zawiera sekrety, wiec kopia musi byc chroniona uprawnieniami
|
||||
cp "${ENV_FILE}" "${TARGET}/env.production.kopia"
|
||||
chmod 600 "${TARGET}/env.production.kopia"
|
||||
log "Konfiguracja zapisana"
|
||||
|
||||
# --- metryczka, zeby przy odtwarzaniu bylo wiadomo co to jest ---
|
||||
cat > "${TARGET}/INFO.txt" <<EOF
|
||||
Kopia zapasowa platformy webinarowej
|
||||
Data: $(date '+%Y-%m-%d %H:%M:%S %Z')
|
||||
Serwer: $(hostname)
|
||||
Baza: ${DB_NAME}
|
||||
Wersja obrazu: $(compose images backend --format json 2>/dev/null | head -c 200 || echo 'nieznana')
|
||||
|
||||
Rodzaj kopii: $([ "${SKIP_MINIO}" = "1" ] && echo 'TYLKO BAZA (bez plikow MinIO)' || echo 'pelna')
|
||||
Rozmiar MinIO: ${MINIO_SIZE}
|
||||
|
||||
Zawartosc:
|
||||
baza.dump zrzut PostgreSQL w formacie custom (pg_restore)
|
||||
minio.tar.gz zawartosc wolumenu MinIO (brak przy SKIP_MINIO=1)
|
||||
env.production.kopia konfiguracja instancji, ZAWIERA SEKRETY
|
||||
|
||||
Odtworzenie: ./scripts/restore.sh ${TARGET}
|
||||
EOF
|
||||
|
||||
chmod 700 "${TARGET}"
|
||||
|
||||
# --- rotacja starych kopii ---
|
||||
if [ "${KEEP_LAST}" -gt 0 ]; then
|
||||
COUNT="$(find "${BACKUP_ROOT}" -maxdepth 1 -mindepth 1 -type d | wc -l)"
|
||||
if [ "${COUNT}" -gt "${KEEP_LAST}" ]; then
|
||||
REMOVE=$((COUNT - KEEP_LAST))
|
||||
log "Usuwam ${REMOVE} najstarszych kopii (zachowuje ${KEEP_LAST})"
|
||||
find "${BACKUP_ROOT}" -maxdepth 1 -mindepth 1 -type d \
|
||||
| sort \
|
||||
| head -n "${REMOVE}" \
|
||||
| while read -r old; do
|
||||
rm -rf "${old}"
|
||||
log " usunieto $(basename "${old}")"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Gotowe: ${TARGET}"
|
||||
Reference in New Issue
Block a user