param( [string]$ComposeFile = "compose.yaml", [string]$DataPath = "./data", [string]$OutputDirectory = "./backups" ) $ErrorActionPreference = "Stop" $timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMddTHHmmssZ") $resolvedData = (Resolve-Path -LiteralPath $DataPath).Path $output = [System.IO.Path]::GetFullPath($OutputDirectory) New-Item -ItemType Directory -Force -Path $output | Out-Null $archive = Join-Path $output "memrelay-$timestamp.tar.gz" $manifest = Join-Path $output "memrelay-$timestamp.json" $archiveName = [System.IO.Path]::GetFileName($archive) $images = @(& docker compose -f $ComposeFile config --images) if ($LASTEXITCODE -ne 0) { throw "Unable to read Compose image versions." } & docker compose -f $ComposeFile exec -T memrelay python -c ` "import os, sqlite3; p=os.path.join(os.environ.get('MEMRELAY_DATA_DIR', '/data/memrelay'), 'db', 'memrelay.sqlite3'); c=sqlite3.connect(p); print(c.execute('PRAGMA wal_checkpoint(TRUNCATE)').fetchone()); c.close()" | Out-Host if ($LASTEXITCODE -ne 0) { throw "Unable to checkpoint the MemRelay database." } docker compose -f $ComposeFile stop | Out-Host if ($LASTEXITCODE -ne 0) { throw "Unable to stop MemRelay services." } try { & docker run --rm --user "1000:1000" ` --mount "type=bind,source=$resolvedData,target=/source,readonly" ` --mount "type=bind,source=$output,target=/backup" ` alpine:3.22 tar -czf "/backup/$archiveName" -C /source . if ($LASTEXITCODE -ne 0) { throw "Unable to create the backup archive." } $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $archive).Hash.ToLowerInvariant() @{ product = "MemRelay" created_at = (Get-Date).ToUniversalTime().ToString("o") archive = $archiveName sha256 = $hash compose_file = $ComposeFile images = $images } | ConvertTo-Json | Set-Content -Encoding UTF8 -LiteralPath $manifest "$hash $archiveName" | Set-Content -Encoding ASCII -LiteralPath "$archive.sha256" Write-Host "Backup created: $archive" } catch { Remove-Item -Force -ErrorAction SilentlyContinue -LiteralPath $archive, $manifest, "$archive.sha256" throw } finally { docker compose -f $ComposeFile start | Out-Host if ($LASTEXITCODE -ne 0) { Write-Error "Backup finished, but MemRelay services could not be restarted." } }