316 lines
6.8 KiB
Bash
316 lines
6.8 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
########################################
|
|
# Konfiguration
|
|
########################################
|
|
|
|
SFTP_HOST="${SFTP_HOST:-sftp.example.com}"
|
|
SFTP_PORT="${SFTP_PORT:-22}"
|
|
SFTP_USER="${SFTP_USER:-user}"
|
|
SFTP_PASS="${SFTP_PASS:-Tpassw0rd}"
|
|
SFTP_REMOTE_DIR="${SFTP_REMOTE_DIR:-/uploads}"
|
|
|
|
MAIL_TO="${MAIL_TO:-}"
|
|
MAIL_FROM="${MAIL_FROM:-}"
|
|
|
|
# Nicht /tmp verwenden: viele Systeme mounten /tmp klein oder als tmpfs.
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DIR_NAME="$(basename "$SCRIPT_DIR")"
|
|
BACKUP_DIR="${BACKUP_DIR:-$(dirname "$SCRIPT_DIR")/${DIR_NAME}_backups}"
|
|
LOG_DIR="${LOG_DIR:-${BACKUP_DIR}/logs}"
|
|
KEEP_LOCAL_BACKUP="${KEEP_LOCAL_BACKUP:-false}"
|
|
MIN_FREE_MB="${MIN_FREE_MB:-1024}"
|
|
|
|
########################################
|
|
# Variablen
|
|
########################################
|
|
|
|
DATE="$(date +"%Y-%m-%d_%H-%M-%S")"
|
|
BACKUP_FILE="${DIR_NAME}_${DATE}.tar.gz"
|
|
|
|
mkdir -p "$BACKUP_DIR" "$LOG_DIR"
|
|
BACKUP_DIR="$(cd "$BACKUP_DIR" && pwd)"
|
|
LOG_DIR="$(cd "$LOG_DIR" && pwd)"
|
|
|
|
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILE}"
|
|
LOGFILE="${LOG_DIR}/${DIR_NAME}_backup_${DATE}.log"
|
|
|
|
DOCKER_STOPPED=false
|
|
SUCCESS=false
|
|
|
|
########################################
|
|
# Logging
|
|
########################################
|
|
|
|
exec > >(tee -a "$LOGFILE")
|
|
exec 2>&1
|
|
|
|
log() {
|
|
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $*"
|
|
}
|
|
|
|
log "========================================="
|
|
log "Backup gestartet"
|
|
log "Projekt: $DIR_NAME"
|
|
log "Backup-Datei: $BACKUP_PATH"
|
|
log "Log-Datei: $LOGFILE"
|
|
log "========================================="
|
|
|
|
########################################
|
|
# Hilfsfunktionen
|
|
########################################
|
|
|
|
require_command() {
|
|
local command_name="$1"
|
|
|
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
|
log "FEHLER: Benötigtes Kommando fehlt: $command_name"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
send_mail() {
|
|
local subject="$1"
|
|
local body="$2"
|
|
|
|
if [[ -z "$MAIL_TO" ]]; then
|
|
log "Hinweis: MAIL_TO ist leer, Mailversand wird übersprungen."
|
|
return 0
|
|
fi
|
|
|
|
if ! command -v mail >/dev/null 2>&1; then
|
|
log "WARNUNG: Kommando 'mail' wurde nicht gefunden, Mailversand nicht möglich."
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "$MAIL_FROM" ]]; then
|
|
if printf '%s\n' "$body" | mail -r "$MAIL_FROM" -s "$subject" "$MAIL_TO"; then
|
|
return 0
|
|
fi
|
|
|
|
log "WARNUNG: Mailversand mit MAIL_FROM fehlgeschlagen, versuche ohne Absenderoption."
|
|
fi
|
|
|
|
if ! printf '%s\n' "$body" | mail -s "$subject" "$MAIL_TO"; then
|
|
log "WARNUNG: Mailversand fehlgeschlagen."
|
|
fi
|
|
}
|
|
|
|
send_error_mail() {
|
|
local exit_code="$1"
|
|
local body
|
|
|
|
body="$(cat <<EOF
|
|
Backup fehlgeschlagen.
|
|
|
|
Projekt:
|
|
${DIR_NAME}
|
|
|
|
Datum:
|
|
$(date)
|
|
|
|
Exit-Code:
|
|
${exit_code}
|
|
|
|
Log-Datei:
|
|
${LOGFILE}
|
|
|
|
Log:
|
|
|
|
$(cat "$LOGFILE" 2>/dev/null || true)
|
|
EOF
|
|
)"
|
|
|
|
send_mail "Backup FEHLGESCHLAGEN - ${DIR_NAME}" "$body"
|
|
}
|
|
|
|
send_success_mail() {
|
|
local size
|
|
local body
|
|
|
|
size="$(du -h "$BACKUP_PATH" | awk '{print $1}')"
|
|
|
|
body="$(cat <<EOF
|
|
Backup erfolgreich abgeschlossen.
|
|
|
|
Projekt:
|
|
${DIR_NAME}
|
|
|
|
Datum:
|
|
$(date)
|
|
|
|
Backup:
|
|
${BACKUP_FILE}
|
|
|
|
Groesse:
|
|
${size}
|
|
|
|
Remote-Ziel:
|
|
${SFTP_HOST}:${SFTP_REMOTE_DIR}/${BACKUP_FILE}
|
|
|
|
Log:
|
|
|
|
$(cat "$LOGFILE")
|
|
EOF
|
|
)"
|
|
|
|
send_mail "Backup erfolgreich - ${DIR_NAME} (${size})" "$body"
|
|
}
|
|
|
|
cleanup_local_backup() {
|
|
if [[ "$KEEP_LOCAL_BACKUP" == "true" ]]; then
|
|
log "Lokales Backup bleibt erhalten: $BACKUP_PATH"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -f "$BACKUP_PATH" ]]; then
|
|
log "Entferne lokales Backup: $BACKUP_PATH"
|
|
rm -f "$BACKUP_PATH"
|
|
fi
|
|
}
|
|
|
|
start_docker_if_needed() {
|
|
if [[ "$DOCKER_STOPPED" == "true" ]]; then
|
|
log "Starte Docker..."
|
|
docker compose up -d
|
|
DOCKER_STOPPED=false
|
|
fi
|
|
}
|
|
|
|
on_error() {
|
|
local exit_code="$?"
|
|
|
|
log "FEHLER: Backup fehlgeschlagen (Exit-Code: ${exit_code})."
|
|
|
|
if [[ "$DOCKER_STOPPED" == "true" ]]; then
|
|
log "Versuche Docker nach Fehler wieder zu starten..."
|
|
if docker compose up -d; then
|
|
DOCKER_STOPPED=false
|
|
log "Docker wurde nach Fehler wieder gestartet."
|
|
else
|
|
log "WARNUNG: Docker konnte nach Fehler nicht gestartet werden."
|
|
fi
|
|
fi
|
|
|
|
send_error_mail "$exit_code"
|
|
cleanup_local_backup
|
|
exit "$exit_code"
|
|
}
|
|
|
|
on_exit() {
|
|
local exit_code="$?"
|
|
|
|
if [[ "$SUCCESS" == "true" ]]; then
|
|
cleanup_local_backup
|
|
fi
|
|
|
|
exit "$exit_code"
|
|
}
|
|
|
|
check_free_space() {
|
|
local source_kb
|
|
local required_kb
|
|
local free_kb
|
|
local min_free_kb
|
|
|
|
source_kb="$(du -sk "$SCRIPT_DIR" | awk '{print $1}')"
|
|
min_free_kb=$((MIN_FREE_MB * 1024))
|
|
required_kb=$((source_kb + (source_kb / 10) + min_free_kb))
|
|
free_kb="$(df -Pk "$BACKUP_DIR" | awk 'NR == 2 {print $4}')"
|
|
|
|
log "Quellgroesse: $((source_kb / 1024)) MB"
|
|
log "Freier Speicher am Backup-Ziel: $((free_kb / 1024)) MB"
|
|
log "Geschaetzter Mindestbedarf: $((required_kb / 1024)) MB"
|
|
|
|
if (( free_kb < required_kb )); then
|
|
log "FEHLER: Zu wenig freier Speicher in $BACKUP_DIR."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
check_backup_dir_location() {
|
|
case "$BACKUP_DIR" in
|
|
"$SCRIPT_DIR"|"$SCRIPT_DIR"/*)
|
|
log "FEHLER: BACKUP_DIR darf nicht innerhalb des Projektverzeichnisses liegen."
|
|
log "Projektverzeichnis: $SCRIPT_DIR"
|
|
log "Backup-Verzeichnis: $BACKUP_DIR"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
trap on_error ERR
|
|
trap on_exit EXIT
|
|
|
|
########################################
|
|
# Vorbedingungen
|
|
########################################
|
|
|
|
cd "$SCRIPT_DIR"
|
|
|
|
require_command docker
|
|
require_command tar
|
|
require_command gzip
|
|
require_command sshpass
|
|
require_command sftp
|
|
check_backup_dir_location
|
|
check_free_space
|
|
|
|
########################################
|
|
# Docker stoppen
|
|
########################################
|
|
|
|
log "Stoppe Docker..."
|
|
docker compose down
|
|
DOCKER_STOPPED=true
|
|
|
|
########################################
|
|
# Archiv erstellen
|
|
########################################
|
|
|
|
log "Erstelle Backup..."
|
|
tar \
|
|
--same-owner \
|
|
--preserve-permissions \
|
|
-czpf "$BACKUP_PATH" \
|
|
-C "$(dirname "$SCRIPT_DIR")" \
|
|
"$DIR_NAME"
|
|
|
|
########################################
|
|
# Docker starten
|
|
########################################
|
|
|
|
start_docker_if_needed
|
|
|
|
########################################
|
|
# Upload
|
|
########################################
|
|
|
|
log "Upload nach ${SFTP_HOST}:${SFTP_REMOTE_DIR}..."
|
|
|
|
sshpass -p "$SFTP_PASS" sftp \
|
|
-P "$SFTP_PORT" \
|
|
-oBatchMode=no \
|
|
-oStrictHostKeyChecking=no \
|
|
-oServerAliveInterval=30 \
|
|
-oServerAliveCountMax=10 \
|
|
"${SFTP_USER}@${SFTP_HOST}" << EOF
|
|
cd "${SFTP_REMOTE_DIR}"
|
|
put "${BACKUP_PATH}" "${BACKUP_FILE}.part"
|
|
rename "${BACKUP_FILE}.part" "${BACKUP_FILE}"
|
|
bye
|
|
EOF
|
|
|
|
log "Upload erfolgreich."
|
|
|
|
########################################
|
|
# Erfolgsmail
|
|
########################################
|
|
|
|
send_success_mail
|
|
|
|
SUCCESS=true
|
|
log "Backup beendet: $(date)"
|