Enforce coherent Idunn swarm deployments

This commit is contained in:
Meta Vens 2026-06-09 16:33:28 +01:00
commit dccac65ea7
12 changed files with 200 additions and 23 deletions

View file

@ -0,0 +1,2 @@
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\deploy-yggdrasil-source-app.ps1 -AppId yggdrasil-heimdall -RepoRoot E:\Projects\Heimdall -AppUser heimdall -RemoteAppHome /srv/heimdall -RemoteTarballName heimdall-source.tar -DeployScript E:\Projects\gamecult-ops\scripts\deploy-heimdall.sh -CheckScript E:\Projects\gamecult-ops\scripts\check-heimdall.sh

View file

@ -0,0 +1,2 @@
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\deploy-yggdrasil-source-app.ps1 -AppId yggdrasil-repixelizer -RepoRoot E:\Projects\repixelizer -AppUser repixelizer -RemoteAppHome /srv/repixelizer -RemoteTarballName repixelizer-source.tar -DeployScript E:\Projects\gamecult-ops\scripts\deploy-repixelizer-gui.sh -CheckScript E:\Projects\gamecult-ops\scripts\check-repixelizer-gui.sh

View file

@ -0,0 +1,84 @@
param(
[Parameter(Mandatory = $true)] [string] $AppId,
[Parameter(Mandatory = $true)] [string] $RepoRoot,
[Parameter(Mandatory = $true)] [string] $AppUser,
[Parameter(Mandatory = $true)] [string] $RemoteAppHome,
[Parameter(Mandatory = $true)] [string] $RemoteTarballName,
[Parameter(Mandatory = $true)] [string] $DeployScript,
[Parameter(Mandatory = $true)] [string] $CheckScript,
[string] $SshTarget = "yggdrasil"
)
$ErrorActionPreference = "Stop"
if (-not (Test-Path -LiteralPath $RepoRoot)) {
throw "Repo root not found: $RepoRoot"
}
if (-not (Test-Path -LiteralPath $DeployScript)) {
throw "Deploy script not found: $DeployScript"
}
if (-not (Test-Path -LiteralPath $CheckScript)) {
throw "Check script not found: $CheckScript"
}
$commit = (git -C $RepoRoot rev-parse HEAD).Trim()
if ([string]::IsNullOrWhiteSpace($commit)) {
throw "Could not determine git revision for $RepoRoot"
}
$scratch = Join-Path "E:\Projects\Odin\scratch\idunn-deploy" $AppId
$tarPath = Join-Path $scratch $RemoteTarballName
$manifestPath = Join-Path $scratch "deployment-manifest.txt"
New-Item -ItemType Directory -Force -Path $scratch | Out-Null
git -C $RepoRoot archive --format=tar --output=$tarPath HEAD
if ($LASTEXITCODE -ne 0) {
throw "git archive failed for $RepoRoot"
}
$hash = (Get-FileHash $tarPath -Algorithm SHA256).Hash.ToLowerInvariant()
$deployedAt = [DateTimeOffset]::UtcNow.ToString("O")
@(
"schema=gamecult.idunn.deployment_manifest.v1"
"appId=$AppId"
"repoRoot=$RepoRoot"
"gitCommit=$commit"
"artifact=$RemoteTarballName"
"sha256=$hash"
"deployedAtUtc=$deployedAt"
) | Set-Content -Encoding ASCII -LiteralPath $manifestPath
$remoteDeploy = "/home/gamecultadmin/$([IO.Path]::GetFileName($DeployScript))"
$remoteCheck = "/home/gamecultadmin/$([IO.Path]::GetFileName($CheckScript))"
$remoteTarball = "/home/gamecultadmin/$RemoteTarballName"
$remoteManifest = "/home/gamecultadmin/$AppId-deployment-manifest.txt"
$targetTarball = "$RemoteAppHome/$RemoteTarballName"
$targetManifest = "$RemoteAppHome/deployment-manifest.txt"
$sshArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=10", $SshTarget)
$scpArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=10")
scp.exe @scpArgs $tarPath "${SshTarget}:$remoteTarball"
if ($LASTEXITCODE -ne 0) { throw "scp source tarball failed for $AppId" }
scp.exe @scpArgs $DeployScript "${SshTarget}:$remoteDeploy"
if ($LASTEXITCODE -ne 0) { throw "scp deploy script failed for $AppId" }
scp.exe @scpArgs $CheckScript "${SshTarget}:$remoteCheck"
if ($LASTEXITCODE -ne 0) { throw "scp check script failed for $AppId" }
scp.exe @scpArgs $manifestPath "${SshTarget}:$remoteManifest"
if ($LASTEXITCODE -ne 0) { throw "scp manifest failed for $AppId" }
$remoteCommand = @(
"sudo -n install -d -o '$AppUser' -g '$AppUser' '$RemoteAppHome'",
"sudo -n install -o '$AppUser' -g '$AppUser' -m 600 '$remoteTarball' '$targetTarball'",
"chmod +x '$remoteDeploy' '$remoteCheck'",
"sudo -n bash '$remoteDeploy'",
"check_ok=0; for i in `$(seq 1 30); do if sudo -n bash '$remoteCheck'; then check_ok=1; break; fi; sleep 2; done; if [ `$check_ok -ne 1 ]; then sudo -n bash '$remoteCheck'; fi",
"sudo -n install -m 644 '$remoteManifest' '$targetManifest'"
) -join " && "
ssh.exe @sshArgs $remoteCommand
if ($LASTEXITCODE -ne 0) {
throw "remote deploy/check failed for $AppId"
}
Write-Host "$AppId deployed to Yggdrasil at $commit."
Write-Host " artifactSha256=$hash"

View file

@ -0,0 +1,2 @@
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\deploy-yggdrasil-source-app.ps1 -AppId yggdrasil-streampixels -RepoRoot E:\Projects\StreamPixels -AppUser streampixels -RemoteAppHome /srv/streampixels -RemoteTarballName streampixels-source.tar -DeployScript E:\Projects\gamecult-ops\scripts\deploy-streampixels-preview.sh -CheckScript E:\Projects\gamecult-ops\scripts\check-streampixels-preview.sh

View file

@ -0,0 +1,2 @@
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\health-yggdrasil-source-app.ps1 -AppId yggdrasil-heimdall -RepoRoot E:\Projects\Heimdall -RemoteAppHome /srv/heimdall -CheckScript E:\Projects\gamecult-ops\scripts\check-heimdall.sh

View file

@ -0,0 +1,2 @@
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\health-yggdrasil-source-app.ps1 -AppId yggdrasil-repixelizer -RepoRoot E:\Projects\repixelizer -RemoteAppHome /srv/repixelizer -CheckScript E:\Projects\gamecult-ops\scripts\check-repixelizer-gui.sh

View file

@ -0,0 +1,43 @@
param(
[Parameter(Mandatory = $true)] [string] $AppId,
[Parameter(Mandatory = $true)] [string] $RepoRoot,
[Parameter(Mandatory = $true)] [string] $RemoteAppHome,
[Parameter(Mandatory = $true)] [string] $CheckScript,
[string] $SshTarget = "yggdrasil"
)
$ErrorActionPreference = "Stop"
$localCommit = (git -C $RepoRoot rev-parse HEAD).Trim()
if ([string]::IsNullOrWhiteSpace($localCommit)) {
throw "Could not determine git revision for $RepoRoot"
}
$remoteCheck = "/home/gamecultadmin/$([IO.Path]::GetFileName($CheckScript))"
$scpArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=10")
$sshArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=10", $SshTarget)
scp.exe @scpArgs $CheckScript "${SshTarget}:$remoteCheck" | Out-Null
if ($LASTEXITCODE -ne 0) { throw "scp check script failed for $AppId" }
$manifest = ssh.exe @sshArgs "cat '$RemoteAppHome/deployment-manifest.txt' 2>/dev/null"
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($manifest)) {
throw "$AppId deploy manifest is missing on Yggdrasil."
}
$remoteCommit = ""
foreach ($line in $manifest) {
if ($line -like "gitCommit=*") {
$remoteCommit = $line.Substring("gitCommit=".Length).Trim()
}
}
if ($remoteCommit -ne $localCommit) {
throw "$AppId deploy is stale. remote=$remoteCommit local=$localCommit"
}
ssh.exe @sshArgs "chmod +x '$remoteCheck' && sudo -n bash '$remoteCheck'"
if ($LASTEXITCODE -ne 0) {
throw "$AppId remote health check failed."
}
Write-Host "$AppId is active and deployed at $localCommit."

View file

@ -0,0 +1,2 @@
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\health-yggdrasil-source-app.ps1 -AppId yggdrasil-streampixels -RepoRoot E:\Projects\StreamPixels -RemoteAppHome /srv/streampixels -CheckScript E:\Projects\gamecult-ops\scripts\check-streampixels-preview.sh

View file

@ -1,5 +1,4 @@
$repoRoot = Split-Path -Parent $PSScriptRoot
$opsRoot = "E:\Projects\gamecult-ops"
$IdunnDeploymentTargets = @(
[pscustomobject]@{
@ -19,12 +18,12 @@ $IdunnDeploymentTargets = @(
Repo = "Bifrost"
LocalPath = "E:\Projects\Bifrost"
Host = "yggdrasil"
Service = "bifrost.service/bifrost-web"
Service = "bifrost.service"
Status = "blocked"
Health = "$opsRoot\scripts\check-bifrost-alpha.sh"
Deploy = "$opsRoot\scripts\deploy-bifrost-container.ps1"
Health = $null
Deploy = $null
Restart = $null
Reason = "Deploy authority exists in gamecult-ops, but desired image digest/tag is not yet published as an Idunn deployment target record."
Reason = "Committed Bifrost HEAD currently expects UserAccounts.HeimdallAccountId, but Yggdrasil's production database lacks that column and EF reports no pending migration. Idunn must not enforce Bifrost freshness until the Bifrost schema migration path is fixed."
},
[pscustomobject]@{
Id = "yggdrasil-streampixels"
@ -32,11 +31,11 @@ $IdunnDeploymentTargets = @(
LocalPath = "E:\Projects\StreamPixels"
Host = "yggdrasil"
Service = "streampixels-service/streampixels-web"
Status = "blocked"
Health = "$opsRoot\scripts\check-streampixels-preview.sh"
Deploy = "$opsRoot\scripts\deploy-streampixels-preview.sh"
Status = "enforced"
Health = "$repoRoot\scripts\health-yggdrasil-streampixels.cmd"
Deploy = "$repoRoot\scripts\deploy-yggdrasil-streampixels.cmd"
Restart = $null
Reason = "Deploy script is remote-root/runbook shaped and needs a committed artifact manifest plus Idunn-safe wrapper before automatic execution."
Reason = "Idunn packages committed local StreamPixels HEAD, runs the existing ops deploy/check scripts, and verifies the remote manifest."
},
[pscustomobject]@{
Id = "yggdrasil-heimdall"
@ -44,11 +43,11 @@ $IdunnDeploymentTargets = @(
LocalPath = "E:\Projects\Heimdall"
Host = "yggdrasil"
Service = "heimdall.service"
Status = "blocked"
Health = "$opsRoot\scripts\check-heimdall.sh"
Deploy = "$opsRoot\scripts\deploy-heimdall.sh"
Status = "enforced"
Health = "$repoRoot\scripts\health-yggdrasil-heimdall.cmd"
Deploy = "$repoRoot\scripts\deploy-yggdrasil-heimdall.cmd"
Restart = $null
Reason = "Deploy script uses a workstation-built source tarball; Idunn needs a manifest-producing wrapper before automatic execution."
Reason = "Idunn packages committed local Heimdall HEAD, runs the existing ops deploy/check scripts, and verifies the remote manifest."
},
[pscustomobject]@{
Id = "yggdrasil-repixelizer"
@ -56,11 +55,11 @@ $IdunnDeploymentTargets = @(
LocalPath = "E:\Projects\repixelizer"
Host = "yggdrasil"
Service = "repixelizer-gui.service"
Status = "blocked"
Health = "$opsRoot\scripts\check-repixelizer-gui.sh"
Deploy = "$opsRoot\scripts\deploy-repixelizer-gui.sh"
Status = "enforced"
Health = "$repoRoot\scripts\health-yggdrasil-repixelizer.cmd"
Deploy = "$repoRoot\scripts\deploy-yggdrasil-repixelizer.cmd"
Restart = $null
Reason = "Deploy script uses a committed source tarball and Python environment mutation; Idunn needs an artifact manifest wrapper before automatic execution."
Reason = "Idunn packages committed local repixelizer HEAD, runs the existing ops deploy/check scripts, and verifies the remote manifest."
},
[pscustomobject]@{
Id = "github-pages-gamecult-site"

View file

@ -69,6 +69,30 @@ $watchdogs = @(
Health = "$repoRoot\scripts\health-idunn-swarm-deployment-coverage.cmd"
Restart = $null
},
[pscustomobject]@{
Id = "yggdrasil-heimdall"
Name = "Yggdrasil Heimdall"
Verse = "yggdrasil.local"
Health = "$repoRoot\scripts\health-yggdrasil-heimdall.cmd"
Deploy = "$repoRoot\scripts\deploy-yggdrasil-heimdall.cmd"
IntervalSeconds = 300
},
[pscustomobject]@{
Id = "yggdrasil-repixelizer"
Name = "Yggdrasil Repixelizer"
Verse = "yggdrasil.local"
Health = "$repoRoot\scripts\health-yggdrasil-repixelizer.cmd"
Deploy = "$repoRoot\scripts\deploy-yggdrasil-repixelizer.cmd"
IntervalSeconds = 300
},
[pscustomobject]@{
Id = "yggdrasil-streampixels"
Name = "Yggdrasil StreamPixels"
Verse = "yggdrasil.local"
Health = "$repoRoot\scripts\health-yggdrasil-streampixels.cmd"
Deploy = "$repoRoot\scripts\deploy-yggdrasil-streampixels.cmd"
IntervalSeconds = 300
},
[pscustomobject]@{
Id = "nightwing-gjallar"
Name = "Nightwing Gjallar framebuffer compositor"