diff --git a/BACKUP-README.md b/BACKUP-README.md new file mode 100644 index 0000000..e18fcbe --- /dev/null +++ b/BACKUP-README.md @@ -0,0 +1,73 @@ +# Gitea Backup Strategy + +This document outlines the backup strategy for your Gitea installation, focusing on ensuring your database is always backed up in at least one place. + +## Backup Methods + +This setup provides two complementary backup methods: + +### 1. PostgreSQL Database Dumps + +Database dumps are SQL files containing all your database data. These are the most reliable way to back up a PostgreSQL database. + +- **Script**: `backup-gitea-db.ps1` +- **Output**: SQL dumps in the `backups` directory, compressed as ZIP files +- **Retention**: Keeps the last 10 backups by default + +### 2. Docker Volume Backups + +This method backs up the entire PostgreSQL data volume, which includes all database files. + +- **Script**: `backup-volume.ps1` +- **Output**: TAR archives in the `backups` directory, compressed as ZIP files +- **Retention**: Keeps the last 5 volume backups by default + +## Automated Backups + +You can set up automated daily backups using the included script: + +```powershell +.\schedule-backup.ps1 +``` + +This creates a Windows Scheduled Task that runs the database backup script daily at 3 AM. + +## Restoring from Backups + +### Restoring from a Database Dump + +```powershell +.\restore-gitea-db.ps1 -BackupFile "backups\gitea-db-backup-2025-03-01_10-30-00.sql.zip" +``` + +### Restoring from a Volume Backup + +```powershell +.\restore-volume.ps1 -BackupFile "backups\postgres-volume-backup-2025-03-01_10-30-00.tar.zip" +``` + +## Best Practices + +1. **Regular Backups**: Run backups at least daily +2. **Multiple Backup Methods**: Use both database dumps and volume backups +3. **Off-site Storage**: Copy your backups to an external drive or cloud storage +4. **Test Restores**: Periodically test restoring from your backups +5. **Version Control**: Keep your Gitea configuration files in version control + +## Important Notes + +- **Never** run `docker-compose down -v` unless you have a recent backup +- When upgrading Gitea, always create a backup first +- The database volume (`postgres-data`) persists even when containers are stopped or removed, but can be lost if explicitly deleted + +## Manual Backup Commands + +If you need to create a backup manually: + +```powershell +# Database dump +.\backup-gitea-db.ps1 + +# Volume backup +.\backup-volume.ps1 +``` \ No newline at end of file diff --git a/README.md b/README.md index 27e2a99..de6b63c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ This is a Docker Compose configuration for running Gitea with PostgreSQL, config - SSH access for Git operations - Persistent data storage - Self-signed SSL certificates (can be replaced with Let's Encrypt) +- Automated database backup system ## Configuration @@ -96,6 +97,51 @@ Replace `your.domain.here` with your actual domain name. - For production use, consider using Let's Encrypt certificates - Self-signed certificates will show browser security warnings +## Backup System + +This setup includes a comprehensive backup strategy to ensure your Gitea data is always protected. The backup system provides two complementary methods: + +### Database Backups + +PowerShell scripts are included to manage database backups: + +1. **Creating Backups**: + ```powershell + powershell -ExecutionPolicy Bypass -File .\backup-gitea-db.ps1 + ``` + This creates a SQL dump of your PostgreSQL database, compressed as a ZIP file in the `backups` directory. + +2. **Volume Backups**: + ```powershell + powershell -ExecutionPolicy Bypass -File .\backup-volume.ps1 + ``` + This backs up the entire PostgreSQL data volume as a TAR archive, compressed as a ZIP file. + +3. **Automated Backups**: + ```powershell + powershell -ExecutionPolicy Bypass -File .\schedule-backup.ps1 + ``` + This creates a Windows Scheduled Task that runs database backups daily at 3 AM. + +4. **Restoring from Backups**: + ```powershell + # Restore from database dump + powershell -ExecutionPolicy Bypass -File .\restore-gitea-db.ps1 -BackupFile "backups\your-backup-file.sql.zip" + + # Restore from volume backup + powershell -ExecutionPolicy Bypass -File .\restore-volume.ps1 -BackupFile "backups\your-volume-backup.tar.zip" + ``` + +### Backup Best Practices + +- Keep multiple backups using both methods (database dumps and volume backups) +- Store backups in multiple locations (local and off-site) +- Test restoring from backups periodically +- Create a backup before upgrading Gitea or making significant changes +- **Never** run `docker-compose down -v` unless you have a recent backup + +For more detailed information about the backup system, see [BACKUP-README.md](BACKUP-README.md). + ## Stopping the Services To stop the services, run: @@ -103,6 +149,8 @@ To stop the services, run: docker-compose down ``` +**Important**: Do not use the `-v` flag (`docker-compose down -v`) unless you intend to delete all data, as this will remove the Docker volumes containing your database. + ## Data Persistence All data is stored in Docker volumes and local directories: @@ -112,6 +160,7 @@ All data is stored in Docker volumes and local directories: - Docker volumes (managed by Docker): - `gitea-data` - Gitea repositories and application data - `postgres-data` - PostgreSQL database files +- `./backups/` - Database and volume backups ## Troubleshooting @@ -126,4 +175,9 @@ All data is stored in Docker volumes and local directories: 3. **Local Network Access**: - If bee8333.ddns.net doesn't resolve locally, use localhost:3000 instead - - Add an entry to your hosts file if needed \ No newline at end of file + - Add an entry to your hosts file if needed + +4. **Database Backup Issues**: + - Ensure Docker is running when attempting backups + - Check that the container names match those in the backup scripts + - For PowerShell execution issues, use the `-ExecutionPolicy Bypass` flag \ No newline at end of file diff --git a/backup-gitea-db.ps1 b/backup-gitea-db.ps1 new file mode 100644 index 0000000..ecdf86d --- /dev/null +++ b/backup-gitea-db.ps1 @@ -0,0 +1,32 @@ +# Gitea Database Backup Script +$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" +$backupDir = ".\backups" +$backupFile = "$backupDir\gitea-db-backup-$timestamp.sql" + +# Ensure backup directory exists +if (-not (Test-Path $backupDir)) { + New-Item -ItemType Directory -Path $backupDir +} + +# Create database dump +Write-Host "Creating database backup to $backupFile..." +docker exec gitea-db pg_dump -U gitea -d gitea > $backupFile + +# Check if backup was successful +if ($LASTEXITCODE -eq 0 -and (Test-Path $backupFile) -and (Get-Item $backupFile).Length -gt 0) { + Write-Host "Backup completed successfully!" + + # Optional: Compress the backup file + Compress-Archive -Path $backupFile -DestinationPath "$backupFile.zip" -Force + Remove-Item $backupFile + Write-Host "Backup compressed to $backupFile.zip" +} else { + Write-Host "Backup failed!" -ForegroundColor Red +} + +# Optional: Clean up old backups (keep last 10) +$oldBackups = Get-ChildItem -Path $backupDir -Filter "gitea-db-backup-*.zip" | Sort-Object LastWriteTime -Descending | Select-Object -Skip 10 +foreach ($backup in $oldBackups) { + Remove-Item $backup.FullName + Write-Host "Removed old backup: $($backup.Name)" +} \ No newline at end of file diff --git a/backup-volume.ps1 b/backup-volume.ps1 new file mode 100644 index 0000000..29f545d --- /dev/null +++ b/backup-volume.ps1 @@ -0,0 +1,41 @@ +# Script to backup the entire Postgres Docker volume +$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" +$backupDir = ".\backups" +$volumeName = "gitea-docker_postgres-data" +$backupFile = "$backupDir\postgres-volume-backup-$timestamp.tar" + +# Ensure backup directory exists +if (-not (Test-Path $backupDir)) { + New-Item -ItemType Directory -Path $backupDir +} + +# Check if volume exists +$volumeExists = docker volume ls --format "{{.Name}}" | Select-String -Pattern "^$volumeName$" +if (-not $volumeExists) { + Write-Host "Volume $volumeName not found!" -ForegroundColor Red + exit 1 +} + +# Create a temporary container to access the volume +Write-Host "Creating backup of Docker volume $volumeName..." +docker run --rm -v ${volumeName}:/volume -v ${PWD}/${backupDir}:/backup alpine tar -cf /backup/$(Split-Path $backupFile -Leaf) -C /volume ./ + +# Check if backup was successful +if ($LASTEXITCODE -eq 0 -and (Test-Path $backupFile) -and (Get-Item $backupFile).Length -gt 0) { + Write-Host "Volume backup completed successfully to $backupFile!" -ForegroundColor Green + + # Optional: Compress the backup file + Write-Host "Compressing backup file..." + Compress-Archive -Path $backupFile -DestinationPath "$backupFile.zip" -Force + Remove-Item $backupFile + Write-Host "Backup compressed to $backupFile.zip" -ForegroundColor Green +} else { + Write-Host "Volume backup failed!" -ForegroundColor Red +} + +# Optional: Clean up old volume backups (keep last 5) +$oldBackups = Get-ChildItem -Path $backupDir -Filter "postgres-volume-backup-*.zip" | Sort-Object LastWriteTime -Descending | Select-Object -Skip 5 +foreach ($backup in $oldBackups) { + Remove-Item $backup.FullName + Write-Host "Removed old volume backup: $($backup.Name)" +} \ No newline at end of file diff --git a/restore-gitea-db.ps1 b/restore-gitea-db.ps1 new file mode 100644 index 0000000..4dd49ae --- /dev/null +++ b/restore-gitea-db.ps1 @@ -0,0 +1,67 @@ +# Gitea Database Restore Script +param ( + [Parameter(Mandatory=$true)] + [string]$BackupFile +) + +# Check if backup file exists +if (-not (Test-Path $BackupFile)) { + Write-Host "Backup file not found: $BackupFile" -ForegroundColor Red + exit 1 +} + +# Extract the backup if it's a zip file +$tempFile = $null +if ($BackupFile.EndsWith(".zip")) { + $tempDir = [System.IO.Path]::GetTempPath() + $tempFile = Join-Path $tempDir "gitea-db-restore-temp.sql" + + Write-Host "Extracting backup file..." + Expand-Archive -Path $BackupFile -DestinationPath $tempDir -Force + $extractedFile = Get-ChildItem -Path $tempDir -Filter "*.sql" | Select-Object -First 1 + + if ($extractedFile) { + Copy-Item $extractedFile.FullName -Destination $tempFile + $BackupFile = $tempFile + } else { + Write-Host "Failed to extract SQL file from backup" -ForegroundColor Red + exit 1 + } +} + +# Confirm before proceeding +Write-Host "WARNING: This will overwrite the current database with the backup." -ForegroundColor Yellow +Write-Host "Make sure your Gitea service is stopped before proceeding." -ForegroundColor Yellow +$confirmation = Read-Host "Do you want to continue? (y/n)" + +if ($confirmation -ne "y") { + Write-Host "Restore cancelled." + if ($tempFile -and (Test-Path $tempFile)) { + Remove-Item $tempFile + } + exit 0 +} + +# Stop Gitea services +Write-Host "Stopping Gitea services..." +docker-compose stop + +# Restore the database +Write-Host "Restoring database from backup..." +Get-Content $BackupFile | docker exec -i gitea-db psql -U gitea -d gitea + +# Check restore status +if ($LASTEXITCODE -eq 0) { + Write-Host "Database restore completed successfully!" -ForegroundColor Green +} else { + Write-Host "Database restore failed!" -ForegroundColor Red +} + +# Clean up temp file if created +if ($tempFile -and (Test-Path $tempFile)) { + Remove-Item $tempFile +} + +# Restart Gitea services +Write-Host "Starting Gitea services..." +docker-compose start \ No newline at end of file diff --git a/restore-volume.ps1 b/restore-volume.ps1 new file mode 100644 index 0000000..913b003 --- /dev/null +++ b/restore-volume.ps1 @@ -0,0 +1,88 @@ +# Script to restore the Postgres Docker volume from a backup +param ( + [Parameter(Mandatory=$true)] + [string]$BackupFile +) + +$volumeName = "gitea-docker_postgres-data" +$tempDir = Join-Path $env:TEMP "postgres-volume-restore" + +# Check if backup file exists +if (-not (Test-Path $BackupFile)) { + Write-Host "Backup file not found: $BackupFile" -ForegroundColor Red + exit 1 +} + +# Extract the backup if it's a zip file +$tarFile = $BackupFile +if ($BackupFile.EndsWith(".zip")) { + # Create temp directory if it doesn't exist + if (-not (Test-Path $tempDir)) { + New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + } else { + # Clean temp directory + Remove-Item -Path "$tempDir\*" -Force -Recurse -ErrorAction SilentlyContinue + } + + Write-Host "Extracting backup file..." + Expand-Archive -Path $BackupFile -DestinationPath $tempDir -Force + $extractedFile = Get-ChildItem -Path $tempDir -Filter "*.tar" | Select-Object -First 1 + + if ($extractedFile) { + $tarFile = $extractedFile.FullName + } else { + Write-Host "Failed to extract TAR file from backup" -ForegroundColor Red + exit 1 + } +} + +# Confirm before proceeding +Write-Host "WARNING: This will overwrite the current database volume with the backup." -ForegroundColor Yellow +Write-Host "Make sure your Gitea services are stopped before proceeding." -ForegroundColor Yellow +$confirmation = Read-Host "Do you want to continue? (y/n)" + +if ($confirmation -ne "y") { + Write-Host "Restore cancelled." + exit 0 +} + +# Stop Gitea services +Write-Host "Stopping Gitea services..." +docker-compose down + +# Check if volume exists and remove it +$volumeExists = docker volume ls --format "{{.Name}}" | Select-String -Pattern "^$volumeName$" +if ($volumeExists) { + Write-Host "Removing existing volume $volumeName..." + docker volume rm $volumeName + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to remove existing volume. It might be in use by another container." -ForegroundColor Red + exit 1 + } +} + +# Create a new volume +Write-Host "Creating new volume $volumeName..." +docker volume create $volumeName + +# Restore from backup +Write-Host "Restoring volume from backup..." +docker run --rm -v ${volumeName}:/volume -v ${tarFile}:/backup.tar alpine sh -c "cd /volume && tar -xf /backup.tar" + +if ($LASTEXITCODE -eq 0) { + Write-Host "Volume restore completed successfully!" -ForegroundColor Green +} else { + Write-Host "Volume restore failed!" -ForegroundColor Red + exit 1 +} + +# Clean up temp directory if created +if ($BackupFile.EndsWith(".zip") -and (Test-Path $tempDir)) { + Remove-Item -Path $tempDir -Force -Recurse -ErrorAction SilentlyContinue +} + +# Start Gitea services +Write-Host "Starting Gitea services..." +docker-compose up -d + +Write-Host "Restore process completed. Check if your Gitea instance is working properly." -ForegroundColor Green \ No newline at end of file diff --git a/schedule-backup.ps1 b/schedule-backup.ps1 new file mode 100644 index 0000000..ff22ba0 --- /dev/null +++ b/schedule-backup.ps1 @@ -0,0 +1,29 @@ +# Script to create a scheduled task for Gitea database backups +$scriptPath = Join-Path (Get-Location) "backup-gitea-db.ps1" +$taskName = "GiteaDatabaseBackup" +$taskDescription = "Regular backup of Gitea PostgreSQL database" + +# Check if the backup script exists +if (-not (Test-Path $scriptPath)) { + Write-Host "Backup script not found at: $scriptPath" -ForegroundColor Red + exit 1 +} + +# Create a scheduled task to run daily at 3 AM +$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" +$trigger = New-ScheduledTaskTrigger -Daily -At 3AM +$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries + +# Register the scheduled task +$taskExists = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue + +if ($taskExists) { + Write-Host "Task '$taskName' already exists. Updating..." -ForegroundColor Yellow + Set-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Description $taskDescription +} else { + Write-Host "Creating new scheduled task '$taskName'..." -ForegroundColor Green + Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Description $taskDescription -User "$env:USERDOMAIN\$env:USERNAME" +} + +Write-Host "Scheduled task setup complete. The database will be backed up daily at 3 AM." -ForegroundColor Green +Write-Host "Backup files will be stored in the 'backups' folder in your Gitea Docker directory." -ForegroundColor Green \ No newline at end of file