#Requires -Version 5.1 param( [ValidateSet('inventory','dry-run','apply','verify','finalize')][string]$Mode = 'inventory', [Parameter(Mandatory=$true)][string]$ExpectedTarget ) $ErrorActionPreference = 'Stop' $root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) Set-Location $root . (Join-Path $root 'scripts/Initialize-SecurityEnv.ps1') Initialize-SecurityEnv -Path (Join-Path $root '.env') -Mode upgrade $content = Get-Content (Join-Path $root '.env') -Raw if (-not (Get-EnvValue $content 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING')) { throw 'Configure independent encryption keys and prepare database/queue backup before migrating' } $checkpointDir = Join-Path $root 'data/credential-migration' [System.IO.Directory]::CreateDirectory($checkpointDir) | Out-Null if ($env:OS -eq 'Windows_NT') { $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().User $acl = New-Object System.Security.AccessControl.DirectorySecurity $acl.SetAccessRuleProtection($true, $false) $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow') $acl.AddAccessRule($rule) Set-Acl -LiteralPath $checkpointDir -AclObject $acl } $toolArgs = @("--mode=$Mode", '--production=true', "--expected-target=$ExpectedTarget", '--batch-size=100', '--checkpoint=/credential-migration/checkpoint.json') if ($Mode -eq 'finalize') { $toolArgs = @('--mode=verify', '--finalize=true', '--production=true', "--expected-target=$ExpectedTarget", '--batch-size=100') } docker compose version *> $null if ($LASTEXITCODE -eq 0) { docker compose run --rm --no-deps -v "${checkpointDir}:/credential-migration" easyai-server node /app/dist/credential-migrate.js @toolArgs } else { docker-compose run --rm --no-deps -v "${checkpointDir}:/credential-migration" easyai-server node /app/dist/credential-migrate.js @toolArgs } if ($LASTEXITCODE -ne 0) { throw 'Credential migration failed; preserve checkpoint and resolve configuration or concurrency conflicts' }