231 lines
4.4 KiB
Bash
231 lines
4.4 KiB
Bash
#!/bin/bash
|
||
|
||
set -euo pipefail
|
||
|
||
############################
|
||
# Konfiguration
|
||
############################
|
||
|
||
SFTP_SERVER="sftp.domain.tld"
|
||
SFTP_PORT="22"
|
||
SFTP_USER="sftp-user"
|
||
SFTP_PASS="passw0rd"
|
||
REMOTE_DIR="/uploads"
|
||
|
||
EMAIL=""
|
||
|
||
############################
|
||
# Basis
|
||
############################
|
||
|
||
HOSTNAME=$(hostname)
|
||
DATE=$(date "+%Y-%m-%d_%H-%M")
|
||
|
||
BACKUP_DIR="/backups/docker"
|
||
LOG_DIR="/var/log/script-logs"
|
||
|
||
mkdir -p "$BACKUP_DIR" "$LOG_DIR"
|
||
|
||
PROJECT_NAME=""
|
||
ARCHIVE_FILE=""
|
||
LOG_FILE=""
|
||
|
||
############################
|
||
# Mail Funktion
|
||
############################
|
||
send_mail() {
|
||
STATUS="$1"
|
||
SUBJECT="[$HOSTNAME] Docker Backup $STATUS ($PROJECT_NAME)"
|
||
|
||
if command -v mail >/dev/null 2>&1; then
|
||
mail -s "$SUBJECT" "$EMAIL" -A "$LOG_FILE" <<EOF
|
||
Docker Backup Status: $STATUS
|
||
|
||
Host: $HOSTNAME
|
||
Projekt: $PROJECT_NAME
|
||
Datum: $DATE
|
||
Archiv: $ARCHIVE_FILE
|
||
|
||
Logfile im Anhang.
|
||
EOF
|
||
else
|
||
echo "Mail nicht installiert – übersprungen."
|
||
fi
|
||
}
|
||
|
||
############################
|
||
# Fehlerbehandlung
|
||
############################
|
||
error_handler() {
|
||
echo "FEHLER aufgetreten!"
|
||
send_mail "FEHLER"
|
||
exit 1
|
||
}
|
||
|
||
trap error_handler ERR
|
||
|
||
############################
|
||
# Compose Projekte
|
||
############################
|
||
|
||
echo "Verfügbare Docker Compose Projekte:"
|
||
|
||
mapfile -t PROJECTS < <(
|
||
docker ps --format '{{.Label "com.docker.compose.project"}}' \
|
||
| sort -u \
|
||
| grep -v '^$'
|
||
)
|
||
|
||
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
|
||
echo "Keine Compose-Projekte gefunden."
|
||
exit 1
|
||
fi
|
||
|
||
for i in "${!PROJECTS[@]}"; do
|
||
echo "[$((i+1))] ${PROJECTS[$i]}"
|
||
done
|
||
|
||
echo
|
||
read -p "Projekt auswählen (Nummer): " SELECTION
|
||
|
||
if ! [[ "$SELECTION" =~ ^[0-9]+$ ]] || (( SELECTION < 1 || SELECTION > ${#PROJECTS[@]} )); then
|
||
echo "Ungültige Auswahl."
|
||
exit 1
|
||
fi
|
||
|
||
PROJECT_NAME="${PROJECTS[$((SELECTION-1))]}"
|
||
LOG_FILE="$LOG_DIR/docker-backup-${PROJECT_NAME}.log"
|
||
|
||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||
|
||
echo
|
||
echo "===== Backup gestartet ====="
|
||
echo "Projekt : $PROJECT_NAME"
|
||
echo "Host : $HOSTNAME"
|
||
echo "Datum : $DATE"
|
||
echo
|
||
|
||
############################
|
||
# Container sammeln
|
||
############################
|
||
|
||
mapfile -t CONTAINERS < <(
|
||
docker ps \
|
||
--filter "label=com.docker.compose.project=$PROJECT_NAME" \
|
||
--format '{{.Names}}'
|
||
)
|
||
|
||
echo "Container im Stack:"
|
||
printf ' - %s\n' "${CONTAINERS[@]}"
|
||
echo
|
||
|
||
############################
|
||
# Mounts sammeln
|
||
############################
|
||
|
||
MOUNTS=()
|
||
|
||
for c in "${CONTAINERS[@]}"; do
|
||
mapfile -t PATHS < <(
|
||
docker inspect "$c" \
|
||
| jq -r '.[0].Mounts[] | select(.Type=="volume" or .Type=="bind") | .Source'
|
||
)
|
||
|
||
for p in "${PATHS[@]}"; do
|
||
[[ -d "$p" ]] && MOUNTS+=("$p")
|
||
done
|
||
done
|
||
|
||
mapfile -t MOUNTS < <(printf "%s\n" "${MOUNTS[@]}" | sort -u)
|
||
|
||
if [[ ${#MOUNTS[@]} -eq 0 ]]; then
|
||
echo "Keine gültigen Mounts gefunden."
|
||
exit 1
|
||
fi
|
||
|
||
############################
|
||
# Stack stoppen
|
||
############################
|
||
|
||
echo "Stoppe Stack..."
|
||
docker stop "${CONTAINERS[@]}" >/dev/null
|
||
sleep 3
|
||
|
||
############################
|
||
# Daten kopieren
|
||
############################
|
||
|
||
TMP_DIR="/tmp/docker-backup-$PROJECT_NAME"
|
||
rm -rf "$TMP_DIR"
|
||
mkdir -p "$TMP_DIR"
|
||
|
||
echo "Kopiere Daten..."
|
||
|
||
for mount in "${MOUNTS[@]}"; do
|
||
BASENAME=$(basename "$mount")
|
||
rsync -a "$mount/" "$TMP_DIR/$BASENAME/"
|
||
done
|
||
|
||
############################
|
||
# ZIP Archiv erstellen
|
||
############################
|
||
|
||
ARCHIVE_FILE="$BACKUP_DIR/${HOSTNAME}-${PROJECT_NAME}-${DATE}.zip"
|
||
|
||
echo "Erstelle ZIP Archiv..."
|
||
|
||
TOTAL_SIZE=$(du -sb "$TMP_DIR" | awk '{print $1}')
|
||
|
||
(
|
||
cd "$TMP_DIR"
|
||
zip -r -q - .
|
||
) | pv -s "$TOTAL_SIZE" > "$ARCHIVE_FILE"
|
||
|
||
echo "Archiv erstellt: $ARCHIVE_FILE"
|
||
|
||
############################
|
||
# Stack starten
|
||
############################
|
||
|
||
echo "Starte Stack..."
|
||
docker start "${CONTAINERS[@]}" >/dev/null
|
||
echo "Stack läuft wieder."
|
||
|
||
############################
|
||
# Optionaler Upload
|
||
############################
|
||
|
||
echo
|
||
read -p "Backup zum SFTP hochladen? (j/n): " DO_UPLOAD
|
||
|
||
if [[ "$DO_UPLOAD" == "j" ]]; then
|
||
|
||
if [[ -z "$SFTP_PASS" ]]; then
|
||
read -s -p "SFTP Passwort eingeben: " SFTP_PASS
|
||
echo
|
||
fi
|
||
|
||
echo "Starte Upload..."
|
||
|
||
sshpass -p "$SFTP_PASS" \
|
||
scp -P "$SFTP_PORT" "$ARCHIVE_FILE" \
|
||
"$SFTP_USER@$SFTP_SERVER:$REMOTE_DIR/"
|
||
|
||
echo "Upload abgeschlossen."
|
||
fi
|
||
|
||
############################
|
||
# Cleanup
|
||
############################
|
||
|
||
rm -rf "$TMP_DIR"
|
||
|
||
echo "Backup erfolgreich abgeschlossen."
|
||
|
||
send_mail "ERFOLGREICH"
|
||
|
||
echo "===== Fertig ====="
|
||
exit 0
|
||
|
||
|