Het script zette GIT_SSH_COMMAND hard op "ssh", waarmee het de al geconfigureerde core.sshCommand overschreef. Op Windows praat alleen de OpenSSH van het systeem met de ssh-agent; Git's eigen ssh niet. Afhankelijk van de PATH-volgorde faalde de controle daardoor met "Permission denied" terwijl git zelf prima kon pushen. Nu wordt core.sshCommand als basis gebruikt (met "ssh" als fallback) en worden alleen BatchMode en ConnectTimeout toegevoegd. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
408 lines
20 KiB
PowerShell
408 lines
20 KiB
PowerShell
# ── VibeFinance release script (PowerShell) ──────────────────────────────
|
|
# Productie release voor de 2-container opzet: draait de tests, bouwt het
|
|
# gecombineerde app-image multi-platform, pusht naar de registry, maakt een
|
|
# Git-tag en publiceert de release in Gitea.
|
|
#
|
|
# Gebruik:
|
|
# .\release.ps1 # test + bouw + push + Git-tag + Gitea-release
|
|
# .\release.ps1 -NoBuild # sla docker build over, alleen tag + release
|
|
# .\release.ps1 -SkipTests # sla de testsuite over (niet aanbevolen)
|
|
# .\release.ps1 -DryRun # toon alle commando's zonder iets uit te voeren
|
|
# .\release.ps1 -Force # sla conflictcontrole over, overschrijf tags
|
|
#
|
|
# Vereisten:
|
|
# - Docker Desktop actief
|
|
# - Ingelogd op de registry: docker login <registry>
|
|
# - docker buildx builder actief: docker buildx create --use
|
|
# - Git geinstalleerd en working directory is de repo root
|
|
# - root package.json bevat een schoon versienummer ZONDER -dev suffix
|
|
# - GITEA_TOKEN in de omgeving (token met release-rechten) voor stap 5,
|
|
# de Gitea-release waar de update-melding in de app op afgaat
|
|
|
|
param(
|
|
[switch]$NoBuild,
|
|
[switch]$SkipTests,
|
|
[switch]$DryRun,
|
|
[switch]$Force
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# ════════════════════════════════════════════════════════════════════════════
|
|
# ── PROJECTCONFIGURATIE — pas alleen dit blok aan voor een nieuw project ──
|
|
# ════════════════════════════════════════════════════════════════════════════
|
|
$appName = "VibeFinance"
|
|
$registry = "vibehoogie.duckdns.org/vibe"
|
|
$appImage = "vibefinance-app"
|
|
$apiUrl = "/api"
|
|
$platforms = "linux/amd64,linux/arm64" # arm64 = Raspberry Pi
|
|
# ════════════════════════════════════════════════════════════════════════════
|
|
|
|
# ── Versie uit root package.json ───────────────────────────────────────────
|
|
$rootPkg = Get-Content (Join-Path $PSScriptRoot "package.json") -Raw | ConvertFrom-Json
|
|
$appVersion = $rootPkg.version
|
|
|
|
if (-not $appVersion) {
|
|
Write-Error "Kon versienummer niet lezen uit package.json"
|
|
exit 1
|
|
}
|
|
|
|
if ($appVersion -match "-dev$") {
|
|
Write-Error "Versienummer '$appVersion' bevat '-dev' suffix. Verwijder de suffix voor een productie-release."
|
|
exit 1
|
|
}
|
|
|
|
# ── Versie synchroniseren naar frontend/package.json ──────────────────────
|
|
$frontendPkgPath = Join-Path $PSScriptRoot "frontend/package.json"
|
|
$frontendPkg = Get-Content $frontendPkgPath -Raw | ConvertFrom-Json
|
|
if ($frontendPkg.version -ne $appVersion) {
|
|
$frontendPkg.version = $appVersion
|
|
[System.IO.File]::WriteAllText($frontendPkgPath, ($frontendPkg | ConvertTo-Json -Depth 10))
|
|
Write-Host "Versie gesynchroniseerd naar frontend: $appVersion" -ForegroundColor Green
|
|
}
|
|
|
|
$gitTag = "$appVersion"
|
|
|
|
# ── Registry tags (één image voor frontend + backend) ─────────────────────
|
|
$appBase = "$registry/$appImage"
|
|
$appVersionTag = "$appBase`:$appVersion"
|
|
$appLatestTag = "$appBase`:latest"
|
|
|
|
# ── Helpers ────────────────────────────────────────────────────────────────
|
|
function Invoke-Cmd {
|
|
param(
|
|
[string[]]$Cmd,
|
|
# Wordt bij een fout uitgevoerd om een gerichte uitleg te geven
|
|
[scriptblock]$Uitleg
|
|
)
|
|
$display = $Cmd -join " "
|
|
if ($DryRun) {
|
|
Write-Host "[DRY-RUN] $display" -ForegroundColor Yellow
|
|
return ""
|
|
}
|
|
Write-Host "> $display" -ForegroundColor DarkGray
|
|
$output = & $Cmd[0] $Cmd[1..($Cmd.Length - 1)]
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
# Geen 'throw': dat levert een PowerShell-stacktrace met het hele
|
|
# commando erin, wat de eigenlijke oorzaak wegdrukt.
|
|
Write-Host ""
|
|
Write-Host "Mislukt: $($Cmd[0]) $($Cmd[1]) (exitcode $LASTEXITCODE)" -ForegroundColor Red
|
|
if ($Uitleg) { & $Uitleg }
|
|
Write-Host ""
|
|
Write-Host "Er is niets gepubliceerd. Los het bovenstaande op en draai release.ps1 opnieuw." -ForegroundColor DarkGray
|
|
exit 1
|
|
}
|
|
return $output
|
|
}
|
|
|
|
function Write-Step {
|
|
param([string]$Label)
|
|
Write-Host ""
|
|
Write-Host "── $Label" -ForegroundColor Cyan
|
|
}
|
|
|
|
# Haalt de sectie van deze versie uit Changelog.md als release-notes.
|
|
function Get-ChangelogSection {
|
|
param([string]$Version)
|
|
$path = Join-Path $PSScriptRoot "Changelog.md"
|
|
if (-not (Test-Path $path)) { return "" }
|
|
$lines = Get-Content $path
|
|
$start = -1
|
|
$needle = "^##\s*\[" + [regex]::Escape($Version) + "\]"
|
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
if ($lines[$i] -match $needle) { $start = $i + 1; break }
|
|
}
|
|
if ($start -lt 0) { return "" }
|
|
$out = @()
|
|
for ($i = $start; $i -lt $lines.Count; $i++) {
|
|
if ($lines[$i] -match "^##\s*\[") { break }
|
|
if ($lines[$i] -match "^---\s*$") { continue }
|
|
$out += $lines[$i]
|
|
}
|
|
return (($out -join "`n").Trim())
|
|
}
|
|
|
|
# ── Samenvatting ───────────────────────────────────────────────────────────
|
|
Write-Host ""
|
|
Write-Host "════════════════════════════════════════" -ForegroundColor Cyan
|
|
Write-Host " $appName Release" -ForegroundColor Cyan
|
|
Write-Host " Versie : $appVersion" -ForegroundColor Green
|
|
Write-Host " Git-tag : $gitTag" -ForegroundColor Green
|
|
Write-Host " Image : $appVersionTag" -ForegroundColor Green
|
|
Write-Host " Platform: $platforms" -ForegroundColor Green
|
|
if ($DryRun) { Write-Host " Mode : DRY-RUN (geen wijzigingen)" -ForegroundColor Yellow }
|
|
if ($NoBuild) { Write-Host " Mode : SKIP BUILD" -ForegroundColor Yellow }
|
|
if ($SkipTests) { Write-Host " Mode : SKIP TESTS" -ForegroundColor Yellow }
|
|
if ($Force) { Write-Host " Mode : FORCE (conflictcontrole overgeslagen)" -ForegroundColor Red }
|
|
Write-Host "════════════════════════════════════════" -ForegroundColor Cyan
|
|
|
|
# ── Stap 1: Conflictcontrole ──────────────────────────────────────────────
|
|
Write-Step "[1/5] Controles (tags + registry-login)"
|
|
|
|
# Geen prompts: anders blijft het script hangen wanneer SSH om een wachtwoord
|
|
# vraagt. Wel de al geconfigureerde ssh gebruiken (core.sshCommand) — op
|
|
# Windows praat alleen de OpenSSH van het systeem met de ssh-agent, Git's
|
|
# eigen ssh niet.
|
|
$sshBasis = (git config --get core.sshCommand)
|
|
if (-not $sshBasis) { $sshBasis = "ssh" }
|
|
$env:GIT_SSH_COMMAND = "$sshBasis -o BatchMode=yes -o ConnectTimeout=10"
|
|
|
|
function Test-GitPushToegang {
|
|
git ls-remote --heads origin 2>&1 | Out-Null
|
|
return ($LASTEXITCODE -eq 0)
|
|
}
|
|
|
|
if (-not $DryRun -and -not $Force) {
|
|
$localTag = git tag --list $gitTag
|
|
if ($localTag) {
|
|
Write-Error "Git-tag '$gitTag' bestaat al lokaal. Verhoog het versienummer in package.json of gebruik -Force."
|
|
exit 1
|
|
}
|
|
|
|
# Toegang tot origin nu controleren; stap 4 pusht de tag en zou anders pas
|
|
# na de build stranden.
|
|
if (-not (Test-GitPushToegang)) {
|
|
$remoteUrl = git remote get-url origin
|
|
Write-Host ""
|
|
Write-Host "Geen toegang tot origin ($remoteUrl)." -ForegroundColor Red
|
|
Write-Host ""
|
|
Write-Host "Stap 4 pusht de Git-tag, dus dit moet eerst werken. Controleer met:" -ForegroundColor Yellow
|
|
Write-Host " git ls-remote --heads origin" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host "Bij 'Permission denied (publickey)' doet de SSH-sleutel het niet." -ForegroundColor Yellow
|
|
Write-Host "Snelste alternatief is https, met hetzelfde Gitea-token als de registry:" -ForegroundColor Yellow
|
|
Write-Host " git remote set-url origin https://<host>/<org>/<repo>.git" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host "Er is niets gepubliceerd." -ForegroundColor DarkGray
|
|
exit 1
|
|
}
|
|
|
|
$remoteTags = git ls-remote --tags origin "refs/tags/$gitTag"
|
|
if ($remoteTags) {
|
|
Write-Error "Git-tag '$gitTag' bestaat al op origin. Verhoog het versienummer in package.json of gebruik -Force."
|
|
exit 1
|
|
}
|
|
|
|
Write-Host "Controleer registry: $appVersionTag" -ForegroundColor DarkGray
|
|
$ErrorActionPreference = "SilentlyContinue"
|
|
docker manifest inspect $appVersionTag 2>$null | Out-Null
|
|
$imageExists = $LASTEXITCODE -eq 0
|
|
$ErrorActionPreference = "Stop"
|
|
if ($imageExists) {
|
|
Write-Error "Registry-tag '$appVersionTag' bestaat al. Verhoog het versienummer in package.json of gebruik -Force."
|
|
exit 1
|
|
}
|
|
|
|
Write-Host "Geen conflicten gevonden." -ForegroundColor Green
|
|
} elseif ($Force) {
|
|
Write-Host "Conflictcontrole overgeslagen (-Force)" -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host "[DRY-RUN] Conflictcontrole overgeslagen" -ForegroundColor Yellow
|
|
}
|
|
|
|
# ── Registry-login ─────────────────────────────────────────────────────────
|
|
# Zonder geldige credentials klapt `buildx --push` pas ná de hele build met
|
|
# "401 Unauthorized". Daarom vooraf controleren, en zelf inloggen wanneer er
|
|
# een token in de omgeving staat.
|
|
$registryHost = $registry.Split("/")[0]
|
|
|
|
function Test-RegistryLogin {
|
|
param([string]$Repo)
|
|
# imagetools inspect praat rechtstreeks met de registry: 401 = niet
|
|
# ingelogd, "not found" = wél ingelogd maar tag bestaat (nog) niet.
|
|
$uitvoer = (docker buildx imagetools inspect "${Repo}:latest" 2>&1 | Out-String)
|
|
if ($uitvoer -match "401|[Uu]nauthorized|authentication required") { return $false }
|
|
return $true
|
|
}
|
|
|
|
function Connect-Registry {
|
|
$gebruiker = $env:REGISTRY_USER
|
|
$token = if ($env:REGISTRY_TOKEN) { $env:REGISTRY_TOKEN } else { $env:GITEA_TOKEN }
|
|
|
|
if (-not ($gebruiker -and $token)) { return $false }
|
|
|
|
Write-Host "Inloggen op $registryHost als $gebruiker..." -ForegroundColor White
|
|
# Via stdin, zodat het token niet in de commandoregel of logs belandt.
|
|
$token | docker login $registryHost -u $gebruiker --password-stdin
|
|
return ($LASTEXITCODE -eq 0)
|
|
}
|
|
|
|
if (-not $NoBuild) {
|
|
if (Test-RegistryLogin $appBase) {
|
|
Write-Host "Registry-login in orde ($registryHost)." -ForegroundColor Green
|
|
} elseif (Connect-Registry) {
|
|
Write-Host "Ingelogd op $registryHost." -ForegroundColor Green
|
|
} elseif ($DryRun) {
|
|
Write-Host "[DRY-RUN] Niet ingelogd op $registryHost — een echte push zou hier falen." -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host ""
|
|
Write-Host "Niet ingelogd op $registryHost — de push zou met 401 Unauthorized falen." -ForegroundColor Red
|
|
Write-Host ""
|
|
Write-Host "Kies een van deze twee:" -ForegroundColor Yellow
|
|
Write-Host ""
|
|
Write-Host " 1. Eenmalig handmatig inloggen (credential blijft bewaard):" -ForegroundColor Yellow
|
|
Write-Host " docker login $registryHost" -ForegroundColor White
|
|
Write-Host " Gebruikersnaam = je Gitea-account, wachtwoord = een Gitea access" -ForegroundColor DarkGray
|
|
Write-Host " token met scope write:package (Settings > Applications > Generate" -ForegroundColor DarkGray
|
|
Write-Host " New Token)." -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host " 2. Het script laten inloggen door deze variabelen te zetten:" -ForegroundColor Yellow
|
|
Write-Host ' $env:REGISTRY_USER = "<gitea-gebruiker>"' -ForegroundColor White
|
|
Write-Host ' $env:REGISTRY_TOKEN = "<gitea-token met write:package>"' -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host "Let op: zonder poortnummer inloggen, en in dezelfde shell en onder" -ForegroundColor DarkGray
|
|
Write-Host "dezelfde Windows-gebruiker als waarin je release.ps1 draait." -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host "Er is niets gepubliceerd." -ForegroundColor DarkGray
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# ── Stap 2: Tests ─────────────────────────────────────────────────────────
|
|
if (-not $SkipTests) {
|
|
Write-Step "[2/5] Testsuite"
|
|
if ($DryRun) {
|
|
Write-Host "[DRY-RUN] test.ps1 -NoSmoke" -ForegroundColor Yellow
|
|
} else {
|
|
& (Join-Path $PSScriptRoot "test.ps1") -NoSmoke
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Tests mislukt — release afgebroken."
|
|
exit 1
|
|
}
|
|
}
|
|
} else {
|
|
Write-Step "[2/5] Tests overgeslagen (-SkipTests)"
|
|
}
|
|
|
|
# ── Stap 3: Bouwen en pushen ──────────────────────────────────────────────
|
|
if (-not $NoBuild) {
|
|
Write-Step "[3/5] Multi-platform bouwen en pushen"
|
|
|
|
Invoke-Cmd @(
|
|
"docker", "buildx", "build",
|
|
"--platform", $platforms,
|
|
"--target", "production",
|
|
"--push",
|
|
"-t", $appVersionTag,
|
|
"-t", $appLatestTag,
|
|
"--build-arg", "APP_VERSION=$appVersion",
|
|
"--build-arg", "VITE_API_URL=$apiUrl",
|
|
"--build-arg", "VITE_APP_VERSION=$appVersion",
|
|
$PSScriptRoot
|
|
) -Uitleg {
|
|
# Meestal is de build zelf goed en weigert de registry de push.
|
|
if (-not (Test-RegistryLogin $appBase)) {
|
|
Write-Host "Oorzaak: geen geldige login op $registryHost (de push geeft dan 401 Unauthorized)." -ForegroundColor Yellow
|
|
Write-Host ""
|
|
Write-Host " docker login $registryHost" -ForegroundColor White
|
|
Write-Host " gebruikersnaam : je Gitea-account" -ForegroundColor DarkGray
|
|
Write-Host " wachtwoord : Gitea access token met scope write:package" -ForegroundColor DarkGray
|
|
Write-Host " (Gitea -> Settings -> Applications -> Generate New Token)" -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host "Of laat het script inloggen:" -ForegroundColor Yellow
|
|
Write-Host ' $env:REGISTRY_USER = "<gitea-gebruiker>"' -ForegroundColor White
|
|
Write-Host ' $env:REGISTRY_TOKEN = "<gitea-token>"' -ForegroundColor White
|
|
} else {
|
|
Write-Host "De registry-login is in orde, dus dit is een build- of netwerkfout." -ForegroundColor Yellow
|
|
Write-Host "Bekijk de regels boven deze melding; de eerste ERROR wijst de oorzaak aan." -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
} else {
|
|
Write-Step "[3/5] Bouwen overgeslagen (-NoBuild)"
|
|
}
|
|
|
|
# ── Stap 4: Git-tag aanmaken en pushen ────────────────────────────────────
|
|
Write-Step "[4/5] Git-tag aanmaken en pushen"
|
|
|
|
if ($Force) {
|
|
Write-Host "Bestaande lokale tag verwijderen (indien aanwezig)..." -ForegroundColor Yellow
|
|
$ErrorActionPreference = "SilentlyContinue"
|
|
git tag -d $gitTag 2>$null | Out-Null
|
|
$ErrorActionPreference = "Stop"
|
|
Write-Host "Bestaande remote tag verwijderen (indien aanwezig)..." -ForegroundColor Yellow
|
|
$ErrorActionPreference = "SilentlyContinue"
|
|
git push origin ":refs/tags/$gitTag" 2>$null | Out-Null
|
|
$ErrorActionPreference = "Stop"
|
|
}
|
|
|
|
Write-Host "Git-tag aanmaken: $gitTag" -ForegroundColor White
|
|
Invoke-Cmd @("git", "tag", "-a", $gitTag, "-m", "Release $gitTag")
|
|
|
|
Write-Host "Git-tag pushen naar origin" -ForegroundColor White
|
|
Invoke-Cmd @("git", "push", "origin", "refs/tags/$gitTag") -Uitleg {
|
|
Write-Host "Kon de tag niet naar origin pushen." -ForegroundColor Yellow
|
|
Write-Host "Het image staat al in de registry; alleen de tag en de Gitea-release ontbreken." -ForegroundColor DarkGray
|
|
Write-Host "Controleer de toegang met: git ls-remote --heads origin" -ForegroundColor White
|
|
Write-Host "Bij 'Permission denied (publickey)' is https het snelste alternatief:" -ForegroundColor DarkGray
|
|
Write-Host " git remote set-url origin https://<host>/<org>/<repo>.git" -ForegroundColor White
|
|
}
|
|
|
|
# ── Stap 5: Gitea-release aanmaken ────────────────────────────────────────
|
|
Write-Step "[5/5] Gitea-release aanmaken"
|
|
|
|
# De update-melding in de app leest /releases/latest. Een kale Git-tag komt
|
|
# daar niet in terug, dus er moet een echte release van gemaakt worden.
|
|
$repoUrl = ($rootPkg.repository -replace "/+$", "")
|
|
$releaseApi = $null
|
|
if ($repoUrl -match "^(https?://[^/]+)/([^/]+)/([^/]+)$") {
|
|
$releaseApi = "$($Matches[1])/api/v1/repos/$($Matches[2])/$($Matches[3])/releases"
|
|
}
|
|
$giteaToken = $env:GITEA_TOKEN
|
|
|
|
if (-not $releaseApi) {
|
|
Write-Host "Kon 'repository' niet parsen uit package.json — release overgeslagen." -ForegroundColor Yellow
|
|
} elseif (-not $giteaToken) {
|
|
Write-Host "Geen GITEA_TOKEN in de omgeving — release overgeslagen." -ForegroundColor Yellow
|
|
Write-Host " Zonder release ziet de app geen update-melding voor $appVersion." -ForegroundColor DarkGray
|
|
} else {
|
|
$notes = Get-ChangelogSection $appVersion
|
|
if (-not $notes) { $notes = "Release $appVersion" }
|
|
$payload = @{
|
|
tag_name = $gitTag
|
|
name = "v$appVersion"
|
|
body = $notes
|
|
draft = $false
|
|
prerelease = $false
|
|
} | ConvertTo-Json -Depth 3
|
|
|
|
if ($DryRun) {
|
|
Write-Host "[DRY-RUN] POST $releaseApi" -ForegroundColor Yellow
|
|
Write-Host $payload -ForegroundColor DarkGray
|
|
} else {
|
|
Write-Host "Release publiceren: $releaseApi" -ForegroundColor White
|
|
try {
|
|
$rel = Invoke-RestMethod -Method Post -Uri $releaseApi -Body $payload `
|
|
-ContentType "application/json" `
|
|
-Headers @{ Authorization = "token $giteaToken" }
|
|
Write-Host "Release aangemaakt: $($rel.html_url)" -ForegroundColor Green
|
|
} catch {
|
|
$code = $_.Exception.Response.StatusCode.value__
|
|
if ($code -eq 409) {
|
|
Write-Host "Release voor $gitTag bestaat al — overgeslagen." -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host "Release aanmaken mislukt ($code): $($_.Exception.Message)" -ForegroundColor Red
|
|
Write-Host "Tag en image staan al klaar; maak de release eventueel handmatig aan." -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ── Klaar ─────────────────────────────────────────────────────────────────
|
|
$kop = if ($DryRun) { "DRY-RUN klaar: $appVersion (niets gepubliceerd)" } else { "Gepubliceerd: $appVersion" }
|
|
Write-Host ""
|
|
Write-Host "════════════════════════════════════════" -ForegroundColor Cyan
|
|
Write-Host " $kop" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host " Registry image:" -ForegroundColor Green
|
|
Write-Host " $appVersionTag" -ForegroundColor White
|
|
Write-Host " $appLatestTag" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " Git-tag: $gitTag -> origin" -ForegroundColor Green
|
|
Write-Host "════════════════════════════════════════" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# Expliciete 0: anders erft de caller de exitcode van het laatste
|
|
# docker-commando (bijv. de registry-check).
|
|
exit 0
|