src/public/Docker/Repair-AitherContainer.ps1
|
#Requires -Version 7.0 <# .SYNOPSIS Detect and repair orphaned Docker containers. .DESCRIPTION Finds containers with hash-prefixed names (e.g., 04f7a44d9008_aitheros-moltbook) that occur when Docker Compose loses track of containers. This typically happens when compose commands are run without --profile on a profiled compose file. In detection mode, reports orphaned containers without changing anything. In repair mode, stops orphans, removes them, and recreates with clean names. .PARAMETER Repair Actually fix the orphaned containers. Without this, only reports findings. .PARAMETER Profile The compose profile to use when recreating. Default: 'all'. .PARAMETER Force Skip confirmation prompts during repair. .EXAMPLE Repair-AitherContainer # Detect orphaned containers (dry run) .EXAMPLE Repair-AitherContainer -Repair # Fix all orphaned containers .EXAMPLE Repair-AitherContainer -Repair -Force # Fix without confirmation prompts .NOTES This function exists because ALL services in the compose file use profiles. Running 'docker compose up' without --profile sees ZERO services, causing Docker to create new containers with hash-prefixed names instead of reusing existing ones. PREVENTION: Always use --profile (or this module's functions which do it automatically). Copyright © 2025 Aitherium Corporation #> function Repair-AitherContainer { [CmdletBinding(SupportsShouldProcess)] param( [Parameter()] [switch]$Repair, [Parameter()] [ValidateSet('core', 'intelligence', 'perception', 'memory', 'training', 'autonomic', 'security', 'agents', 'social', 'creative', 'gpu', 'gateway', 'mcp', 'external', 'desktop', 'all')] [string]$Profile = 'all', [Parameter()] [switch]$Force ) $cfg = Get-AitherComposeConfig -Profile $Profile if (-not $cfg) { return } Write-Host 'Scanning for orphaned containers...' -ForegroundColor Cyan # Find all containers (running or stopped) with hash-prefixed names $allNames = docker ps -a --filter 'name=aitheros' --format '{{.Names}}\t{{.State}}' 2>&1 $orphans = @() $stoppedDuplicates = @() foreach ($line in $allNames) { if ([string]::IsNullOrWhiteSpace($line)) { continue } $parts = $line -split '\t' $name = $parts[0] $state = if ($parts.Count -gt 1) { $parts[1] } else { 'unknown' } if ($name -match '^[0-9a-f]{12}_aitheros-(.+)$') { $serviceName = $Matches[1] $orphans += [PSCustomObject]@{ ContainerName = $name ServiceName = $serviceName CleanName = "aitheros-$serviceName" ComposeSvc = "aither-$serviceName" State = $state } } } # Also find stopped duplicates that block clean names foreach ($orphan in $orphans) { $cleanState = docker inspect --format '{{.State.Status}}' $orphan.CleanName 2>$null if ($cleanState -and $cleanState -ne 'running') { $stoppedDuplicates += $orphan.CleanName } } # Report findings if ($orphans.Count -eq 0) { Write-Host 'No orphaned containers found. Everything looks clean!' -ForegroundColor Green return } Write-Host '' Write-Host "Found $($orphans.Count) orphaned container(s):" -ForegroundColor Yellow foreach ($o in $orphans) { $stateColor = if ($o.State -eq 'running') { 'Green' } else { 'DarkGray' } Write-Host " $($o.ContainerName)" -ForegroundColor Red -NoNewline Write-Host " ($($o.State))" -ForegroundColor $stateColor -NoNewline Write-Host " -> should be: $($o.CleanName)" -ForegroundColor DarkGray } if ($stoppedDuplicates.Count -gt 0) { Write-Host '' Write-Host "$($stoppedDuplicates.Count) stopped duplicate(s) blocking clean names:" -ForegroundColor Yellow foreach ($d in $stoppedDuplicates) { Write-Host " $d (stopped)" -ForegroundColor DarkGray } } if (-not $Repair) { Write-Host '' Write-Host 'Run with -Repair to fix these containers.' -ForegroundColor Cyan Write-Host 'This will: stop orphans -> remove orphans -> remove stopped duplicates -> recreate with clean names' -ForegroundColor DarkGray return } # Confirmation if (-not $Force) { Write-Host '' $confirm = Read-Host "Fix $($orphans.Count) orphaned containers? (y/n)" if ($confirm -notin @('y', 'yes')) { Write-Host 'Aborted.' -ForegroundColor Yellow return } } # Step 1: Remove stopped duplicates that block clean names if ($stoppedDuplicates.Count -gt 0) { Write-Host '' Write-Host 'Removing stopped duplicates...' -ForegroundColor Yellow foreach ($dup in $stoppedDuplicates) { if ($PSCmdlet.ShouldProcess($dup, 'Remove stopped duplicate')) { docker rm $dup 2>$null | Out-Null Write-Host " Removed: $dup" -ForegroundColor DarkGray } } } # Step 2: Stop and remove orphaned containers Write-Host 'Stopping orphaned containers...' -ForegroundColor Yellow $orphanNames = $orphans | ForEach-Object { $_.ContainerName } if ($PSCmdlet.ShouldProcess(($orphanNames -join ', '), 'Stop and remove')) { docker stop @orphanNames 2>$null | Out-Null docker rm @orphanNames 2>$null | Out-Null Write-Host " Removed $($orphanNames.Count) orphaned containers" -ForegroundColor Green } # Step 3: Recreate with clean names via compose (with profile!) $composeSvcs = $orphans | ForEach-Object { $_.ComposeSvc } | Sort-Object -Unique Write-Host 'Recreating with clean names...' -ForegroundColor Cyan if ($PSCmdlet.ShouldProcess(($composeSvcs -join ', '), 'Recreate via compose')) { $upArgs = @('compose') + $cfg.BaseArgs + @('up', '-d') + $composeSvcs & docker @upArgs if ($LASTEXITCODE -eq 0) { Write-Host '' Write-Host "Successfully repaired $($orphans.Count) containers!" -ForegroundColor Green # Verify $stillOrphaned = docker ps -a --format '{{.Names}}' 2>$null | Where-Object { $_ -match '^\w{12}_aitheros-' } if ($stillOrphaned) { Write-Warning "Some orphans remain: $($stillOrphaned -join ', ')" } else { Write-Host 'All containers have clean names.' -ForegroundColor Green } } else { Write-Error 'Compose recreate failed. Check docker compose logs for details.' } } } |