mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
refactor: implement Invoke-NativeSafe function in installation and migration scripts to streamline error handling for Docker commands
This commit is contained in:
parent
59d8afca4f
commit
b88122fc17
2 changed files with 64 additions and 68 deletions
|
|
@ -42,6 +42,17 @@ function Write-Warn { param([string]$Msg) Write-Host "[SurfSense] " -Foregrou
|
|||
function Write-Step { param([string]$Msg) Write-Host "`n-- $Msg" -ForegroundColor Cyan }
|
||||
function Write-Err { param([string]$Msg) Write-Host "[SurfSense] ERROR: $Msg" -ForegroundColor Red; exit 1 }
|
||||
|
||||
function Invoke-NativeSafe {
|
||||
param([scriptblock]$Command)
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $Command
|
||||
} finally {
|
||||
$ErrorActionPreference = $previousErrorActionPreference
|
||||
}
|
||||
}
|
||||
|
||||
# ── Pre-flight checks ──────────────────────────────────────────────────────
|
||||
|
||||
Write-Step "Checking prerequisites"
|
||||
|
|
@ -51,23 +62,13 @@ if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
|
|||
}
|
||||
Write-Ok "Docker found."
|
||||
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
docker info *>$null
|
||||
} finally {
|
||||
$ErrorActionPreference = 'Stop'
|
||||
}
|
||||
Invoke-NativeSafe { docker info *>$null } | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "Docker daemon is not running. Please start Docker Desktop and try again."
|
||||
}
|
||||
Write-Ok "Docker daemon is running."
|
||||
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
docker compose version *>$null
|
||||
} finally {
|
||||
$ErrorActionPreference = 'Stop'
|
||||
}
|
||||
Invoke-NativeSafe { docker compose version *>$null } | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "Docker Compose is not available. It should be bundled with Docker Desktop."
|
||||
}
|
||||
|
|
@ -88,10 +89,8 @@ function Wait-ForPostgres {
|
|||
}
|
||||
Start-Sleep -Seconds 2
|
||||
Push-Location $InstallDir
|
||||
$ErrorActionPreference = 'Continue'
|
||||
docker compose exec -T db pg_isready -U $DbUser -q *>$null
|
||||
Invoke-NativeSafe { docker compose exec -T db pg_isready -U $DbUser -q *>$null } | Out-Null
|
||||
$ready = $LASTEXITCODE -eq 0
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Pop-Location
|
||||
} while (-not $ready)
|
||||
|
||||
|
|
@ -127,9 +126,7 @@ Write-Ok "All files downloaded to $InstallDir/"
|
|||
|
||||
# ── Legacy all-in-one detection ─────────────────────────────────────────────
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$volumeList = docker volume ls --format '{{.Name}}' 2>$null
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$volumeList = Invoke-NativeSafe { docker volume ls --format '{{.Name}}' 2>$null }
|
||||
if (($volumeList -split "`n") -contains $OldVolume -and -not (Test-Path $MigrationDoneFile)) {
|
||||
$MigrationMode = $true
|
||||
|
||||
|
|
@ -203,7 +200,7 @@ if ($MigrationMode) {
|
|||
|
||||
Write-Step "Starting PostgreSQL 17"
|
||||
Push-Location $InstallDir
|
||||
docker compose up -d db
|
||||
Invoke-NativeSafe { docker compose up -d db } | Out-Null
|
||||
Pop-Location
|
||||
Wait-ForPostgres -DbUser $DbUser
|
||||
|
||||
|
|
@ -215,7 +212,7 @@ if ($MigrationMode) {
|
|||
|
||||
$restoreErrFile = Join-Path $env:TEMP "surfsense_restore_err.log"
|
||||
Push-Location $InstallDir
|
||||
Get-Content $DumpFile | docker compose exec -T -e "PGPASSWORD=$DbPass" db psql -U $DbUser -d $DbName 2>$restoreErrFile | Out-Null
|
||||
Invoke-NativeSafe { Get-Content $DumpFile | docker compose exec -T -e "PGPASSWORD=$DbPass" db psql -U $DbUser -d $DbName 2>$restoreErrFile | Out-Null } | Out-Null
|
||||
Pop-Location
|
||||
|
||||
$fatalErrors = @()
|
||||
|
|
@ -236,9 +233,7 @@ if ($MigrationMode) {
|
|||
|
||||
# Smoke test
|
||||
Push-Location $InstallDir
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$tableCount = (docker compose exec -T -e "PGPASSWORD=$DbPass" db psql -U $DbUser -d $DbName -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" 2>$null).Trim()
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$tableCount = (Invoke-NativeSafe { docker compose exec -T -e "PGPASSWORD=$DbPass" db psql -U $DbUser -d $DbName -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" 2>$null }).Trim()
|
||||
Pop-Location
|
||||
|
||||
if (-not $tableCount -or $tableCount -eq "0") {
|
||||
|
|
@ -251,7 +246,7 @@ if ($MigrationMode) {
|
|||
|
||||
Write-Step "Starting all SurfSense services"
|
||||
Push-Location $InstallDir
|
||||
docker compose up -d
|
||||
Invoke-NativeSafe { docker compose up -d } | Out-Null
|
||||
Pop-Location
|
||||
Write-Ok "All services started."
|
||||
|
||||
|
|
@ -260,7 +255,7 @@ if ($MigrationMode) {
|
|||
} else {
|
||||
Write-Step "Starting SurfSense"
|
||||
Push-Location $InstallDir
|
||||
docker compose up -d
|
||||
Invoke-NativeSafe { docker compose up -d } | Out-Null
|
||||
Pop-Location
|
||||
Write-Ok "All services started."
|
||||
}
|
||||
|
|
@ -271,30 +266,25 @@ if ($SetupWatchtower) {
|
|||
$wtHours = [math]::Floor($WatchtowerInterval / 3600)
|
||||
Write-Step "Setting up Watchtower (auto-updates every ${wtHours}h)"
|
||||
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$wtState = docker inspect -f '{{.State.Running}}' $WatchtowerContainer 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { $wtState = "missing" }
|
||||
} finally {
|
||||
$ErrorActionPreference = 'Stop'
|
||||
}
|
||||
$wtState = Invoke-NativeSafe { docker inspect -f '{{.State.Running}}' $WatchtowerContainer 2>$null }
|
||||
if ($LASTEXITCODE -ne 0) { $wtState = "missing" }
|
||||
|
||||
if ($wtState -eq "true") {
|
||||
Write-Ok "Watchtower is already running - skipping."
|
||||
} else {
|
||||
if ($wtState -ne "missing") {
|
||||
Write-Info "Removing stopped Watchtower container..."
|
||||
docker rm -f $WatchtowerContainer *>$null
|
||||
Invoke-NativeSafe { docker rm -f $WatchtowerContainer *>$null } | Out-Null
|
||||
}
|
||||
$ErrorActionPreference = 'Continue'
|
||||
docker run -d `
|
||||
--name $WatchtowerContainer `
|
||||
--restart unless-stopped `
|
||||
-v /var/run/docker.sock:/var/run/docker.sock `
|
||||
nickfedor/watchtower `
|
||||
--label-enable `
|
||||
--interval $WatchtowerInterval *>$null
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Invoke-NativeSafe {
|
||||
docker run -d `
|
||||
--name $WatchtowerContainer `
|
||||
--restart unless-stopped `
|
||||
-v /var/run/docker.sock:/var/run/docker.sock `
|
||||
nickfedor/watchtower `
|
||||
--label-enable `
|
||||
--interval $WatchtowerInterval *>$null
|
||||
} | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "Watchtower started - labeled SurfSense containers will auto-update."
|
||||
|
|
|
|||
|
|
@ -64,6 +64,17 @@ function Write-Err { param([string]$Msg) Write-Host "[SurfSense] ERROR: $Msg
|
|||
|
||||
function Log { param([string]$Msg) Add-Content -Path $LogFile -Value $Msg }
|
||||
|
||||
function Invoke-NativeSafe {
|
||||
param([scriptblock]$Command)
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $Command
|
||||
} finally {
|
||||
$ErrorActionPreference = $previousErrorActionPreference
|
||||
}
|
||||
}
|
||||
|
||||
function Confirm-Action {
|
||||
param([string]$Prompt)
|
||||
if ($Yes) { return }
|
||||
|
|
@ -77,20 +88,20 @@ function Confirm-Action {
|
|||
# ── Cleanup helper ───────────────────────────────────────────────────────────
|
||||
|
||||
function Remove-TempContainer {
|
||||
$containers = docker ps -a --format '{{.Names}}' 2>$null
|
||||
$containers = Invoke-NativeSafe { docker ps -a --format '{{.Names}}' 2>$null }
|
||||
if ($containers -and ($containers -split "`n") -contains $TempContainer) {
|
||||
Write-Info "Cleaning up temporary container '$TempContainer'..."
|
||||
docker stop $TempContainer *>$null
|
||||
docker rm $TempContainer *>$null
|
||||
Invoke-NativeSafe { docker stop $TempContainer *>$null } | Out-Null
|
||||
Invoke-NativeSafe { docker rm $TempContainer *>$null } | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Register cleanup on script exit
|
||||
Register-EngineEvent PowerShell.Exiting -Action {
|
||||
$containers = docker ps -a --format '{{.Names}}' 2>$null
|
||||
$containers = Invoke-NativeSafe { docker ps -a --format '{{.Names}}' 2>$null }
|
||||
if ($containers -and ($containers -split "`n") -contains "surfsense-pg14-migration") {
|
||||
docker stop "surfsense-pg14-migration" *>$null
|
||||
docker rm "surfsense-pg14-migration" *>$null
|
||||
Invoke-NativeSafe { docker stop "surfsense-pg14-migration" *>$null } | Out-Null
|
||||
Invoke-NativeSafe { docker rm "surfsense-pg14-migration" *>$null } | Out-Null
|
||||
}
|
||||
} | Out-Null
|
||||
|
||||
|
|
@ -112,7 +123,7 @@ function Wait-ForPostgres {
|
|||
Write-Err "$Label did not become ready after $($maxAttempts * 2) seconds. Check: docker logs $Container"
|
||||
}
|
||||
Start-Sleep -Seconds 2
|
||||
docker exec $Container pg_isready -U $User -q 2>$null
|
||||
Invoke-NativeSafe { docker exec $Container pg_isready -U $User -q 2>$null } | Out-Null
|
||||
} while ($LASTEXITCODE -ne 0)
|
||||
|
||||
Write-Ok "$Label is ready."
|
||||
|
|
@ -129,28 +140,23 @@ if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
|
|||
Write-Err "Docker is not installed. Install Docker Desktop: https://docs.docker.com/desktop/install/windows-install/"
|
||||
}
|
||||
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
docker info *>$null
|
||||
} finally {
|
||||
$ErrorActionPreference = 'Stop'
|
||||
}
|
||||
Invoke-NativeSafe { docker info *>$null } | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "Docker daemon is not running. Please start Docker Desktop and try again."
|
||||
}
|
||||
|
||||
$volumeList = docker volume ls --format '{{.Name}}' 2>$null
|
||||
$volumeList = Invoke-NativeSafe { docker volume ls --format '{{.Name}}' 2>$null }
|
||||
if (-not (($volumeList -split "`n") -contains $OldVolume)) {
|
||||
Write-Err "Legacy volume '$OldVolume' not found. Are you sure you ran the old all-in-one SurfSense container?"
|
||||
}
|
||||
Write-Ok "Found legacy volume: $OldVolume"
|
||||
|
||||
$oldContainer = (docker ps --filter "volume=$OldVolume" --format '{{.Names}}' 2>$null | Select-Object -First 1)
|
||||
$oldContainer = (Invoke-NativeSafe { docker ps --filter "volume=$OldVolume" --format '{{.Names}}' 2>$null } | Select-Object -First 1)
|
||||
if ($oldContainer) {
|
||||
Write-Warn "Container '$oldContainer' is running and using the '$OldVolume' volume."
|
||||
Write-Warn "It must be stopped before migration to prevent data file corruption."
|
||||
Confirm-Action "Stop '$oldContainer' now and proceed with data extraction?"
|
||||
docker stop $oldContainer *>$null
|
||||
Invoke-NativeSafe { docker stop $oldContainer *>$null } | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "Failed to stop '$oldContainer'. Try: docker stop $oldContainer"
|
||||
}
|
||||
|
|
@ -164,11 +170,11 @@ if (Test-Path $DumpFile) {
|
|||
Write-Err "Aborting to avoid overwriting an existing dump."
|
||||
}
|
||||
|
||||
$staleContainers = docker ps -a --format '{{.Names}}' 2>$null
|
||||
$staleContainers = Invoke-NativeSafe { docker ps -a --format '{{.Names}}' 2>$null }
|
||||
if ($staleContainers -and ($staleContainers -split "`n") -contains $TempContainer) {
|
||||
Write-Warn "Stale migration container '$TempContainer' found - removing it."
|
||||
docker stop $TempContainer *>$null
|
||||
docker rm $TempContainer *>$null
|
||||
Invoke-NativeSafe { docker stop $TempContainer *>$null } | Out-Null
|
||||
Invoke-NativeSafe { docker rm $TempContainer *>$null } | Out-Null
|
||||
}
|
||||
|
||||
$drive = (Get-Item .).PSDrive
|
||||
|
|
@ -199,12 +205,12 @@ Confirm-Action "Start data extraction? (Your original data will not be deleted o
|
|||
Write-Step "1" "Starting temporary PostgreSQL 14 container"
|
||||
|
||||
Write-Info "Pulling $PG14Image..."
|
||||
docker pull $PG14Image *>$null
|
||||
Invoke-NativeSafe { docker pull $PG14Image *>$null } | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warn "Could not pull $PG14Image - using cached image if available."
|
||||
}
|
||||
|
||||
$dataUid = docker run --rm -v "${OldVolume}:/data" alpine stat -c '%u' /data/postgres 2>$null
|
||||
$dataUid = Invoke-NativeSafe { docker run --rm -v "${OldVolume}:/data" alpine stat -c '%u' /data/postgres 2>$null }
|
||||
if (-not $dataUid -or $dataUid -eq "0") {
|
||||
Write-Warn "Could not detect data directory UID - falling back to default (may chown files)."
|
||||
$userFlag = @()
|
||||
|
|
@ -223,7 +229,7 @@ $dockerRunArgs = @(
|
|||
"-e", "POSTGRES_DB=$DbName"
|
||||
) + $userFlag + @($PG14Image)
|
||||
|
||||
docker @dockerRunArgs *>$null
|
||||
Invoke-NativeSafe { docker @dockerRunArgs *>$null } | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "Failed to start temporary PostgreSQL 14 container."
|
||||
}
|
||||
|
|
@ -238,7 +244,7 @@ Write-Step "2" "Dumping PostgreSQL 14 database"
|
|||
Write-Info "Running pg_dump - this may take a while for large databases..."
|
||||
|
||||
$pgDumpErrFile = Join-Path $env:TEMP "surfsense_pgdump_err.log"
|
||||
docker exec -e "PGPASSWORD=$DbPassword" $TempContainer pg_dump -U $DbUser --no-password $DbName > $DumpFile 2>$pgDumpErrFile
|
||||
Invoke-NativeSafe { docker exec -e "PGPASSWORD=$DbPassword" $TempContainer pg_dump -U $DbUser --no-password $DbName > $DumpFile 2>$pgDumpErrFile } | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
if (Test-Path $pgDumpErrFile) { Get-Content $pgDumpErrFile | Write-Host -ForegroundColor Red }
|
||||
Remove-TempContainer
|
||||
|
|
@ -266,8 +272,8 @@ $dumpSize = "{0:N1} MB" -f ((Get-Item $DumpFile).Length / 1MB)
|
|||
Write-Ok "Dump complete: $dumpSize ($dumpLines lines) -> $DumpFile"
|
||||
|
||||
Write-Info "Stopping temporary PostgreSQL 14 container..."
|
||||
docker stop $TempContainer *>$null
|
||||
docker rm $TempContainer *>$null
|
||||
Invoke-NativeSafe { docker stop $TempContainer *>$null } | Out-Null
|
||||
Invoke-NativeSafe { docker rm $TempContainer *>$null } | Out-Null
|
||||
Write-Ok "Temporary container removed."
|
||||
|
||||
# ── Step 3: Recover SECRET_KEY ───────────────────────────────────────────────
|
||||
|
|
@ -276,7 +282,7 @@ Write-Step "3" "Recovering SECRET_KEY"
|
|||
|
||||
$recoveredKey = ""
|
||||
|
||||
$keyCheck = docker run --rm -v "${OldVolume}:/data" alpine sh -c 'test -f /data/.secret_key && cat /data/.secret_key' 2>$null
|
||||
$keyCheck = Invoke-NativeSafe { docker run --rm -v "${OldVolume}:/data" alpine sh -c 'test -f /data/.secret_key && cat /data/.secret_key' 2>$null }
|
||||
if ($LASTEXITCODE -eq 0 -and $keyCheck) {
|
||||
$recoveredKey = $keyCheck.Trim()
|
||||
Write-Ok "Recovered SECRET_KEY from '$OldVolume'."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue