diff --git a/docker/scripts/install.ps1 b/docker/scripts/install.ps1 index f97c8482e..d9719d4ab 100644 --- a/docker/scripts/install.ps1 +++ b/docker/scripts/install.ps1 @@ -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." diff --git a/docker/scripts/migrate-database.ps1 b/docker/scripts/migrate-database.ps1 index 447d7463b..c18633a53 100644 --- a/docker/scripts/migrate-database.ps1 +++ b/docker/scripts/migrate-database.ps1 @@ -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'."