owpengram-server/deploy/migrate-db-naming.ps1
2026-07-28 00:04:47 +03:00

126 lines
5.5 KiB
PowerShell

<#
One-time, opt-in migration of the Postgres role/database name inside an
existing data volume from "telesrv" to "owpengram".
docker-compose.yml's POSTGRES_USER/POSTGRES_DB env vars only take effect when
Postgres bootstraps a brand-new, empty data volume -- they're silently
ignored against a pre-existing volume, so changing the compose file alone
does nothing for an install that already has data on disk. This script talks
to the already-running Postgres container directly (run it after "docker
compose up" plus a readiness wait, not before) and renames the role/database
in place via ALTER ROLE / ALTER DATABASE.
The existing password is left untouched -- renaming doesn't change it. Only
the TELESRV_POSTGRES_DSN line in .env is patched (never any other line, and
never if that line was already customized away from the plain telesrv
defaults) so the app can still connect afterwards.
Prints nothing when there's nothing to do. Prompts interactively at most
once; the decision is cached in a state file next to .secrets/.public_ip, so
this is never asked again. Declining is permanent: the install keeps the
"telesrv" role/database name forever.
#>
[CmdletBinding()]
param(
[string]$ContainerName = "owpengram-postgres",
[string]$EnvFile = ".env",
[string]$StateFile = ".db_naming"
)
function Write-Info([string]$Message) {
[Console]::Error.WriteLine($Message)
}
function Test-PgRoleExists([string]$Role) {
$out = docker exec $ContainerName psql -U telesrv -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='$Role'" 2>$null
return ($LASTEXITCODE -eq 0) -and ($out -match '1')
}
$state = $null
if (Test-Path $StateFile) {
$state = (Get-Content $StateFile -Raw -ErrorAction SilentlyContinue)
if ($state) { $state = $state.Trim() }
}
if ($state -eq "owpengram" -or $state -eq "telesrv") {
exit 0
}
$oldExists = Test-PgRoleExists "telesrv"
$newExists = Test-PgRoleExists "owpengram"
if (-not $oldExists -or $newExists) {
# Nothing to migrate: either already "owpengram"-named (fresh install or
# already renamed), or connecting as "telesrv" didn't work at all (custom
# credentials already in place) -- leave it alone either way.
Set-Content -Path $StateFile -Value "owpengram" -NoNewline
exit 0
}
if ([Console]::IsInputRedirected) {
Write-Info "[cfg] Postgres role/database still named 'telesrv' but running non-interactively; keeping old naming for now"
exit 0
}
Write-Info ""
Write-Info "== Postgres role/database naming =="
Write-Info "The Postgres role and database inside this install's data volume are still"
Write-Info "named 'telesrv' (renaming docker-compose.yml alone doesn't affect existing"
Write-Info "data). Renaming them to 'owpengram' keeps everything consistent. Recommended."
$reply = Read-Host "Rename Postgres role/database from 'telesrv' to 'owpengram' now? [Y/n]"
if ($reply -match '^[Nn]') {
Set-Content -Path $StateFile -Value "telesrv" -NoNewline
Write-Info "[cfg] keeping 'telesrv' Postgres role/database naming (won't ask again)"
exit 0
}
Write-Info "[cfg] renaming Postgres role/database: telesrv -> owpengram"
# Postgres refuses "ALTER ROLE <session user> RENAME" (session user cannot be
# renamed), so telesrv can't rename itself. Do the database rename directly
# as telesrv, then use a short-lived second superuser to rename the telesrv
# role, then drop that temporary role from the now-renamed "owpengram" role's
# own session. Each step is a separate connection/transaction on purpose:
# combining the ALTER DATABASE and ALTER ROLE in one multi-statement call
# rolls the database rename back too when the role rename fails (Postgres
# treats a semicolon-separated -c string as one implicit transaction).
docker exec $ContainerName psql -U telesrv -d postgres -c "ALTER DATABASE telesrv RENAME TO owpengram; CREATE ROLE _telesrv_migrate SUPERUSER LOGIN PASSWORD 'telesrv_migrate';" *> $null
if ($LASTEXITCODE -ne 0) {
Write-Info "[ERROR] failed to rename Postgres database / create temporary migration role"
exit 1
}
docker exec $ContainerName psql -U _telesrv_migrate -d owpengram -c "ALTER ROLE telesrv RENAME TO owpengram;" *> $null
if ($LASTEXITCODE -ne 0) {
Write-Info "[ERROR] failed to rename Postgres role"
exit 1
}
docker exec $ContainerName psql -U owpengram -d owpengram -c "DROP ROLE _telesrv_migrate;" *> $null
if ($LASTEXITCODE -ne 0) {
Write-Info "[WARN] renamed role/database but failed to drop the temporary _telesrv_migrate role - remove it by hand"
}
Set-Content -Path $StateFile -Value "owpengram" -NoNewline
Write-Info "[ok] Postgres role/database renamed to 'owpengram'"
if (Test-Path $EnvFile) {
$lines = Get-Content $EnvFile
$pattern = '^TELESRV_POSTGRES_DSN=postgres://telesrv:([^@]*)@([^/]+)/telesrv(\?.*)?$'
$patched = $false
$newLines = foreach ($line in $lines) {
if (-not $patched -and $line -match $pattern) {
$patched = $true
"TELESRV_POSTGRES_DSN=postgres://owpengram:$($Matches[1])@$($Matches[2])/owpengram$($Matches[3])"
} else {
$line
}
}
if ($patched) {
Set-Content -Path $EnvFile -Value $newLines
Write-Info "[ok] updated the TELESRV_POSTGRES_DSN line in $EnvFile (only that one line)"
} else {
Write-Info "[WARN] TELESRV_POSTGRES_DSN in $EnvFile doesn't match the plain telesrv defaults - update it by hand:"
Write-Info " TELESRV_POSTGRES_DSN=postgres://owpengram:<your-password>@<host>:<port>/owpengram?sslmode=disable"
}
} else {
Write-Info "[WARN] $EnvFile not found - if you create one, point TELESRV_POSTGRES_DSN at the 'owpengram' role/database"
}