param([switch]$CheckOnly) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest # This script never installs host software, restarts Windows or deletes volumes. $docker = Get-Command docker -ErrorAction SilentlyContinue if (-not $docker) { $candidate = Join-Path $env:ProgramFiles 'Docker\Docker\resources\bin\docker.exe' if (Test-Path -LiteralPath $candidate) { $docker = Get-Item -LiteralPath $candidate } } if (-not $docker) { throw 'Docker ausente. Instale e inicie Docker Desktop com contentores Linux. Numa VM Parallels, confirme primeiro suporte a virtualizacao aninhada, ou execute o Compose no macOS anfitriao.' } $dockerPath = if ($docker -is [System.Management.Automation.CommandInfo]) { $docker.Source } else { $docker.FullName } $engine = & $dockerPath info --format '{{.OSType}}' if ($LASTEXITCODE -ne 0 -or $engine -ne 'linux') { throw 'Motor Docker Linux indisponivel.' } & $dockerPath compose version if ($LASTEXITCODE -ne 0) { throw 'Docker Compose v2 indisponivel.' } if ($CheckOnly) { Write-Output 'Motor Linux e Compose disponiveis. Instalacao CRM ainda nao verificada.'; exit 0 } $secretDirectory = Join-Path $PSScriptRoot '.secrets' if (Test-Path -LiteralPath $secretDirectory) { if ((Get-Item -LiteralPath $secretDirectory).Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'A pasta de segredos nao pode ser um link.' } } else { New-Item -ItemType Directory -Path $secretDirectory | Out-Null } $acl = New-Object Security.AccessControl.DirectorySecurity $acl.SetAccessRuleProtection($true, $false) $identity = [Security.Principal.WindowsIdentity]::GetCurrent().User $acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($identity, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))) Set-Acl -LiteralPath $secretDirectory -AclObject $acl foreach ($name in @('db_password', 'db_root_password')) { $secretPath = Join-Path $secretDirectory $name if (Test-Path -LiteralPath $secretPath) { $item = Get-Item -LiteralPath $secretPath if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $item.Length -eq 0) { throw 'Ficheiro de segredo invalido.' } continue } $bytes = New-Object byte[] 32 $rng = [Security.Cryptography.RandomNumberGenerator]::Create() try { $rng.GetBytes($bytes) } finally { $rng.Dispose() } [IO.File]::WriteAllText($secretPath, [Convert]::ToBase64String($bytes), (New-Object Text.UTF8Encoding($false))) } $compose = Join-Path $PSScriptRoot 'compose.json' & $dockerPath compose -f $compose config --quiet if ($LASTEXITCODE -ne 0) { throw 'Configuracao Compose invalida.' } & $dockerPath compose -f $compose build web if ($LASTEXITCODE -ne 0) { throw 'Build falhou; volumes preservados.' } & $dockerPath compose -f $compose up -d --wait --wait-timeout 180 db web if ($LASTEXITCODE -ne 0) { throw 'Arranque falhou; consultar o estado dos contentores.' } Write-Output 'Contentores iniciados em http://127.0.0.1:8088. Conclua o instalador SuiteCRM conforme README; OAuth e sincronizacao ainda nao activos.'