<# .SYNOPSIS Create or update a Coolify application from a Git repository and deploy it. .DESCRIPTION Orchestration wrapper around Invoke-CoolifyApi.ps1 that performs the full Coolify onboarding flow for an existing Git repository: 1. Resolve or create the target Project (by name). 2. Resolve or create the target Environment (by name within the project). 3. Resolve the local server UUID (first reachable server). 4. Create the application (public repo by default, or via deploy key). 5. Set the FQDN and ports_exposes on the new app. 6. Push env vars (key=value pairs) onto the application. 7. Trigger an initial deploy and return the deployment UUIDs. Each mutating step asks for explicit confirmation unless -Force is supplied. The script NEVER writes secrets to stdout: env values are sent to the API but not echoed. .PARAMETER RepoUrl HTTPS clone URL of the Git repository (e.g. https://github.com/user/repo). .PARAMETER Branch Branch to deploy. Defaults to 'main'. .PARAMETER Fqdn Public URL Coolify will expose, e.g. https://myapp.urieljareth.org. .PARAMETER ProjectName Coolify project name. If it does not exist, it will be created. Default: 'default'. .PARAMETER EnvironmentName Coolify environment name within the project. Default: 'production'. .PARAMETER PortsExposes Internal port the app listens on (Traefik targets this). Default: '3000'. .PARAMETER BuildPack Optional Coolify build pack override (nixpacks, dockerfile, dockercompose). .PARAMETER IsStatic Switch. Marks the app as a static site (Coolify will use a static_image). .PARAMETER EnvFile Optional path to a .env-formatted file. Each KEY=VALUE line is pushed to Coolify as an environment variable. Lines starting with '#' are ignored. .PARAMETER ApplicationUuid Optional. If the app already exists in Coolify, pass its UUID to update it in place (FQDN, ports, env) and redeploy, instead of creating a new one. .PARAMETER Force Skip per-step confirmation prompts. Use only after validating the plan. .EXAMPLE $envs = "./.env.coolify" .\New-CoolifyApplication.ps1 -RepoUrl https://github.com/urieljarethbusiness-cpu/my-app ` -Fqdn https://myapp.urieljareth.org -PortsExposes 8080 -EnvFile $envs -Force #> param( [Parameter(Mandatory = $true, ParameterSetName = "Create")] [string]$RepoUrl, [string]$Branch = "main", [Parameter(Mandatory = $true)] [string]$Fqdn, [string]$ProjectName = "default", [string]$EnvironmentName = "production", [string]$PortsExposes = "3000", [string]$BuildPack, [switch]$IsStatic, [string]$EnvFile, [Parameter(ParameterSetName = "Update")] [string]$ApplicationUuid, [switch]$Force ) $ErrorActionPreference = "Stop" $here = Split-Path -Parent $MyInvocation.MyCommand.Path $coolifyApi = Join-Path $here "..\..\coolify_skill\scripts\Invoke-CoolifyApi.ps1" if (-not (Test-Path -LiteralPath $coolifyApi)) { throw "Invoke-CoolifyApi.ps1 not found at: $coolifyApi" } function Confirm-Step { param([string]$Action) if ($Force) { return $true } $reply = Read-Host "Confirm action: $Action`nProceed? (y/N)" return ($reply -match '^(y|yes|s|si|sí)$') } function Get-CoolifyResource { param([string]$Path) & $coolifyApi -Path $Path -ErrorAction Stop } function Resolve-Project { param([string]$Name) $projects = Get-CoolifyResource -Path "/projects" $match = $projects | Where-Object { $_.name -eq $Name } | Select-Object -First 1 if (-not $match) { if (-not (Confirm-Step "Create project '$Name' in Coolify")) { throw "Aborted by user before creating project." } $body = @{ name = $Name } | ConvertTo-Json -Compress $created = & $coolifyApi -Method POST -Path "/projects" -BodyJson $body -ErrorAction Stop Write-Host "Project created: $($created.uuid)" -ForegroundColor Green return $created } return $match } function Resolve-Environment { param([string]$ProjectUuid, [string]$Name) $envs = Get-CoolifyResource -Path "/projects/$ProjectUuid/environments" $match = $envs | Where-Object { $_.name -eq $Name } | Select-Object -First 1 if (-not $match) { if (-not (Confirm-Step "Create environment '$Name' in project $ProjectUuid")) { throw "Aborted by user before creating environment." } $body = @{ name = $Name } | ConvertTo-Json -Compress $created = & $coolifyApi -Method POST -Path "/projects/$ProjectUuid/environments" -BodyJson $body -ErrorAction Stop Write-Host "Environment created: $($created.uuid)" -ForegroundColor Green return $created } return $match } function Resolve-ServerUuid { $servers = Get-CoolifyResource -Path "/servers" if (-not $servers) { throw "No Coolify servers configured." } $srv = $servers | Where-Object { $_.settings.is_usable -eq $true } | Select-Object -First 1 if (-not $srv) { $srv = $servers | Select-Object -First 1 } return $srv.uuid } function Resolve-AppUuid { param([string]$Uuid) if ($Uuid) { $app = Get-CoolifyResource -Path "/applications/$Uuid" return @{ uuid = $app.uuid; created = $false } } return $null } Write-Host "Resolving Coolify project '$ProjectName'..." -ForegroundColor Cyan $proj = Resolve-Project -Name $ProjectName Write-Host "Resolving environment '$EnvironmentName'..." -ForegroundColor Cyan $env = Resolve-Environment -ProjectUuid $proj.uuid -Name $EnvironmentName Write-Host "Resolving server..." -ForegroundColor Cyan $serverUuid = Resolve-ServerUuid Write-Host "Using server: $serverUuid" -ForegroundColor Cyan $existing = Resolve-AppUuid -Uuid $ApplicationUuid if ($existing) { Write-Host "Updating existing application: $($existing.uuid)" -ForegroundColor Cyan $appUuid = $existing.uuid $patch = [ordered]@{ domains = $Fqdn ports_exposes = $PortsExposes } if ($BuildPack) { $patch.build_pack = $BuildPack } if (-not (Confirm-Step "PATCH /applications/$appUuid (domain=$Fqdn, ports=$PortsExposes)")) { throw "Aborted by user." } & $coolifyApi -Method PATCH -Path "/applications/$appUuid" -BodyJson ($patch | ConvertTo-Json -Compress) -ErrorAction Stop | Out-Null } else { $createBody = [ordered]@{ project_uuid = $proj.uuid environment_name = $env.name server_uuid = $serverUuid git_repository = $RepoUrl git_branch = $Branch ports_exposes = $PortsExposes domains = $Fqdn name = Split-Path -Leaf ($RepoUrl -replace '\.git$', '') is_static = [bool]$IsStatic instant_deploy = $false } if ($BuildPack) { $createBody.build_pack = $BuildPack } if (-not (Confirm-Step "POST /applications/public with repo=$RepoUrl, branch=$Branch, fqdn=$Fqdn, ports=$PortsExposes")) { throw "Aborted by user." } $created = & $coolifyApi -Method POST -Path "/applications/public" -BodyJson ($createBody | ConvertTo-Json -Compress) -ErrorAction Stop $appUuid = $created.uuid Write-Host "Application created: $appUuid" -ForegroundColor Green } if ($EnvFile -and (Test-Path -LiteralPath $EnvFile)) { if (-not (Confirm-Step "Push env vars from $EnvFile to application $appUuid")) { Write-Host "Skipping env push." -ForegroundColor Yellow } else { Get-Content -LiteralPath $EnvFile | ForEach-Object { $line = $_.Trim() if (-not $line -or $line.StartsWith("#")) { return } $eq = $line.IndexOf("=") if ($eq -le 0) { return } $k = $line.Substring(0, $eq).Trim() $v = $line.Substring($eq + 1).Trim().Trim('"').Trim("'") $body = [ordered]@{ key = $k value = $v is_literal = $true is_shown_once = $false } | ConvertTo-Json -Compress & $coolifyApi -Method POST -Path "/applications/$appUuid/envs" -BodyJson $body -ErrorAction Stop | Out-Null Write-Host " env: $k set" -ForegroundColor DarkGray } Write-Host "Env vars pushed." -ForegroundColor Green } } if (-not (Confirm-Step "Trigger DEPLOY of $appUuid")) { Write-Host "Skipping deploy trigger. Application is created but not deployed." -ForegroundColor Yellow return } $deployResult = & $coolifyApi -Method POST -Path "/deploy" -BodyJson (@{ uuid = $appUuid } | ConvertTo-Json -Compress) -ErrorAction Stop Write-Host "Deploy triggered." -ForegroundColor Green $deployResult [PSCustomObject]@{ project_uuid = $proj.uuid environment_name = $env.name server_uuid = $serverUuid application_uuid = $appUuid fqdn = $Fqdn repo = $RepoUrl branch = $Branch } | Format-List