diff --git a/.gitea/workflows/backup-volumes.yml b/.gitea/workflows/backup-volumes.yml index f105edc..73bd0cd 100644 --- a/.gitea/workflows/backup-volumes.yml +++ b/.gitea/workflows/backup-volumes.yml @@ -1,4 +1,4 @@ -name: Backup Docker Volumes (Remote) +name: Backup Docker Volumes (All Servers) on: schedule: @@ -8,6 +8,21 @@ on: jobs: backup: runs-on: ubuntu-latest + strategy: + matrix: + server: + - name: devops + host: 10.0.0.175 + user: lars + exclude_volumes: "" # Optional: Komma-separierte Liste zum Ausschließen + # Weitere Server können hier hinzugefügt werden: + # - name: production + # host: 10.0.0.180 + # user: ubuntu + # exclude_volumes: "temp-volume,cache-volume" + fail-fast: false # Andere Server weiterlaufen lassen wenn einer fehlschlägt + + name: Backup ${{ matrix.server.name }} steps: - name: Checkout repository @@ -18,120 +33,55 @@ jobs: mkdir -p ~/.ssh echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H ${{ secrets.BACKUP_HOST }} >> ~/.ssh/known_hosts + ssh-keyscan -H ${{ matrix.server.host }} >> ~/.ssh/known_hosts - - name: Create backup on remote server + - name: Upload backup script to server + run: | + scp scripts/backup-servers/backup-docker-volumes.sh \ + ${{ matrix.server.user }}@${{ matrix.server.host }}:/tmp/backup-docker-volumes.sh + + ssh ${{ matrix.server.user }}@${{ matrix.server.host }} \ + "chmod +x /tmp/backup-docker-volumes.sh" + + - name: Create backup on ${{ matrix.server.name }} run: | BACKUP_DATE=$(date +%Y%m%d_%H%M%S) echo "BACKUP_DATE=$BACKUP_DATE" >> $GITHUB_ENV - ssh ${{ secrets.BACKUP_USER }}@${{ secrets.BACKUP_HOST }} "BACKUP_DATE='$BACKUP_DATE' bash -s" << 'ENDSSH' - set -e + EXCLUDE_VOLUMES="${{ matrix.server.exclude_volumes }}" + ssh ${{ matrix.server.user }}@${{ matrix.server.host }} \ + "BACKUP_DATE='$BACKUP_DATE' SERVER_NAME='${{ matrix.server.name }}' EXCLUDE_VOLUMES='$EXCLUDE_VOLUMES' /tmp/backup-docker-volumes.sh" - BACKUP_BASE_DIR="$HOME/backups" - BACKUP_DIR="$BACKUP_BASE_DIR/$BACKUP_DATE" - - echo "Creating backup directory: $BACKUP_DIR" - mkdir -p "$BACKUP_DIR" - - echo "Listing available volumes:" - docker volume ls - - # Backup Gitea Data - echo "Backing up gitea-data..." - VOLUME_NAME=$(docker volume ls --format '{{.Name}}' | grep 'gitea-data$' | head -n1) - if [ -n "$VOLUME_NAME" ]; then - docker run --rm \ - -v "$VOLUME_NAME":/source:ro \ - -v "$BACKUP_DIR":/backup \ - alpine tar czf /backup/gitea-data.tar.gz -C /source . - echo "✓ gitea-data backed up" - else - echo "⚠ gitea-data volume not found" - fi - - # Backup Gitea Database - echo "Backing up gitea-database..." - VOLUME_NAME=$(docker volume ls --format '{{.Name}}' | grep 'gitea-database$' | head -n1) - if [ -n "$VOLUME_NAME" ]; then - docker run --rm \ - -v "$VOLUME_NAME":/source:ro \ - -v "$BACKUP_DIR":/backup \ - alpine tar czf /backup/gitea-database.tar.gz -C /source . - echo "✓ gitea-database backed up" - else - echo "⚠ gitea-database volume not found" - fi - - # Backup MinIO Data - echo "Backing up minio-data..." - VOLUME_NAME=$(docker volume ls --format '{{.Name}}' | grep 'minio-data$' | head -n1) - if [ -n "$VOLUME_NAME" ]; then - docker run --rm \ - -v "$VOLUME_NAME":/source:ro \ - -v "$BACKUP_DIR":/backup \ - alpine tar czf /backup/minio-data.tar.gz -C /source . - echo "✓ minio-data backed up" - else - echo "⚠ minio-data volume not found" - fi - - # Backup Gitea Runner Data - echo "Backing up gitea-runner-data..." - VOLUME_NAME=$(docker volume ls --format '{{.Name}}' | grep 'gitea-runner-data$' | head -n1) - if [ -n "$VOLUME_NAME" ]; then - docker run --rm \ - -v "$VOLUME_NAME":/source:ro \ - -v "$BACKUP_DIR":/backup \ - alpine tar czf /backup/gitea-runner-data.tar.gz -C /source . - echo "✓ gitea-runner-data backed up" - else - echo "⚠ gitea-runner-data volume not found" - fi - - # Create manifest - cd "$BACKUP_DIR" - cat > manifest.txt << EOF - Backup created: $(date) - Hostname: $(hostname) - - Files: - $(ls -lh *.tar.gz 2>/dev/null || echo "No backup files created") - - Checksums (SHA256): - $(sha256sum *.tar.gz 2>/dev/null || echo "No files to checksum") - EOF - - echo "Backup manifest:" - cat manifest.txt - - echo "Total backup size: $(du -sh $BACKUP_DIR)" - ENDSSH - - - name: Download backups from remote server + - name: Download backups from ${{ matrix.server.name }} run: | - mkdir -p backups + mkdir -p backups/${{ matrix.server.name }} - echo "Downloading backups from remote server..." - scp -r ${{ secrets.BACKUP_USER }}@${{ secrets.BACKUP_HOST }}:~/backups/${{ env.BACKUP_DATE }} backups/ + echo "Downloading backups from ${{ matrix.server.name }}..." + scp -r ${{ matrix.server.user }}@${{ matrix.server.host }}:~/backups/${{ env.BACKUP_DATE }} \ + backups/${{ matrix.server.name }}/ echo "Downloaded files:" - ls -lh backups/${{ env.BACKUP_DATE }}/ + ls -lh backups/${{ matrix.server.name }}/${{ env.BACKUP_DATE }}/ - name: Upload backup artifacts uses: actions/upload-artifact@v3 with: - name: docker-volumes-backup-${{ env.BACKUP_DATE }} - path: backups/ + name: backup-${{ matrix.server.name }}-${{ env.BACKUP_DATE }} + path: backups/${{ matrix.server.name }}/${{ env.BACKUP_DATE }} retention-days: 30 - - name: Cleanup old remote backups (keep last 7 days) + - name: Cleanup old backups on ${{ matrix.server.name }} (keep last 7 days) if: always() run: | - ssh ${{ secrets.BACKUP_USER }}@${{ secrets.BACKUP_HOST }} << 'CLEANUP_EOF' + ssh ${{ matrix.server.user }}@${{ matrix.server.host }} << 'CLEANUP_EOF' # Lösche Backups älter als 7 Tage find ~/backups -maxdepth 1 -type d -name "????????_??????" -mtime +7 -exec rm -rf {} \; 2>/dev/null || true echo "Old backups cleaned up (kept last 7 days)" echo "Current backups:" ls -lh ~/backups/ 2>/dev/null || echo "No backups found" CLEANUP_EOF + + - name: Cleanup temporary files + if: always() + run: | + ssh ${{ matrix.server.user }}@${{ matrix.server.host }} "rm -f /tmp/backup-docker-volumes.sh" || true diff --git a/scripts/backup-servers/backup-docker-volumes.sh b/scripts/backup-servers/backup-docker-volumes.sh new file mode 100644 index 0000000..057d1c2 --- /dev/null +++ b/scripts/backup-servers/backup-docker-volumes.sh @@ -0,0 +1,133 @@ +#!/bin/bash +# Docker Volume Backup Script +# Wird remote auf dem Server ausgeführt +# Sichert alle Docker Volumes automatisch + +set -e + +# Parameter +BACKUP_DATE="${BACKUP_DATE:-$(date +%Y%m%d_%H%M%S)}" +BACKUP_BASE_DIR="$HOME/backups" +BACKUP_DIR="$BACKUP_BASE_DIR/$BACKUP_DATE" +EXCLUDE_VOLUMES="${EXCLUDE_VOLUMES:-}" # Komma-separierte Liste von Volumes die übersprungen werden sollen + +echo "========================================" +echo "Docker Volume Backup" +echo "========================================" +echo "Backup directory: $BACKUP_DIR" +echo "Date: $(date)" +echo "Server: ${SERVER_NAME:-unknown}" +echo "" + +# Backup-Verzeichnis erstellen +echo "Creating backup directory: $BACKUP_DIR" +mkdir -p "$BACKUP_DIR" + +echo "Discovering Docker volumes..." +VOLUMES=$(docker volume ls --format '{{.Name}}') +VOLUME_COUNT=$(echo "$VOLUMES" | wc -l) + +if [ -z "$VOLUMES" ]; then + echo "⚠ No Docker volumes found!" + exit 0 +fi + +echo "Found $VOLUME_COUNT volumes to backup" +if [ -n "$EXCLUDE_VOLUMES" ]; then + echo "Excluding: $EXCLUDE_VOLUMES" +fi +echo "" + +# Funktion zum Prüfen ob Volume ausgeschlossen werden soll +is_excluded() { + local VOLUME_NAME=$1 + if [ -z "$EXCLUDE_VOLUMES" ]; then + return 1 # Nicht ausgeschlossen + fi + + IFS=',' read -ra EXCLUDED <<< "$EXCLUDE_VOLUMES" + for excluded in "${EXCLUDED[@]}"; do + if [[ "$VOLUME_NAME" == "$excluded" ]]; then + return 0 # Ausgeschlossen + fi + done + return 1 # Nicht ausgeschlossen +} + +# Funktion zum Backup eines Volumes +backup_volume() { + local VOLUME_NAME=$1 + local SAFE_NAME=$(echo "$VOLUME_NAME" | tr '/' '_' | tr ':' '_') + + # Prüfen ob ausgeschlossen + if is_excluded "$VOLUME_NAME"; then + echo "⊘ Skipping $VOLUME_NAME (excluded)" + return 0 + fi + + echo "Backing up: $VOLUME_NAME..." + + if docker run --rm \ + -v "$VOLUME_NAME":/source:ro \ + -v "$BACKUP_DIR":/backup \ + alpine tar czf "/backup/${SAFE_NAME}.tar.gz" -C /source . 2>/dev/null; then + + SIZE=$(du -h "$BACKUP_DIR/${SAFE_NAME}.tar.gz" | cut -f1) + echo "✓ $VOLUME_NAME backed up ($SIZE)" + else + echo "✗ Failed to backup $VOLUME_NAME" + return 1 + fi +} + +# Alle Volumes sichern +SUCCESS_COUNT=0 +FAILED_COUNT=0 + +while IFS= read -r volume; do + if backup_volume "$volume"; then + ((SUCCESS_COUNT++)) || true + else + ((FAILED_COUNT++)) || true + fi + echo "" +done <<< "$VOLUMES" + +# Create manifest +echo "========================================" +echo "Creating backup manifest..." +cd "$BACKUP_DIR" +cat > manifest.txt << EOF +Backup created: $(date) +Hostname: $(hostname) +Server: ${SERVER_NAME:-unknown} +Total volumes: $VOLUME_COUNT +Successful: $SUCCESS_COUNT +Failed: $FAILED_COUNT + +Files: +$(ls -lh *.tar.gz 2>/dev/null || echo "No backup files created") + +Checksums (SHA256): +$(sha256sum *.tar.gz 2>/dev/null || echo "No files to checksum") +EOF + +echo "Backup manifest:" +cat manifest.txt + +echo "" +echo "Total backup size: $(du -sh $BACKUP_DIR | cut -f1)" +echo "========================================" +echo "Summary:" +echo " Total volumes: $VOLUME_COUNT" +echo " Successful: $SUCCESS_COUNT" +echo " Failed: $FAILED_COUNT" +echo "========================================" + +if [ $FAILED_COUNT -gt 0 ]; then + echo "⚠ Backup completed with errors!" + exit 1 +else + echo "✓ Backup completed successfully!" + exit 0 +fi