deploy_skill: soporte para Coolify v4.1.2 (API /applications 404) + flujo UI Playwright
Aprendido desplegando cotizador (Next.js+FastAPI+Postgres, Docker Compose, repo privado) en coolify.urieljareth.org (v4.1.2): - Documenta que /applications/* devuelve 404 en esta instancia y que configurar apps git build-from-source solo es posible por la UI web. - Nuevos scripts scripts/coolify-ui/: coolify-login.mjs (guarda storageState) y Configure-CoolifyComposeApp.mjs (build pack -> Docker Compose, Reload Compose File, env vars por Developer view, dominios por servicio). - references/coolify-4.1.2-notes.md: mapa de API funcional/404, gotchas (BOM UTF-8 rompe el parser YAML de Coolify; Reload Compose File obligatorio; pin de dominios por servicio o 503; no encolar deploys concurrentes; PowerShell [IO.File]::ReadAllText para payloads de llaves). - env.local.template.ps1: COOLIFY_EMAIL/COOLIFY_PASSWORD para la UI. - SKILL.md/TOOLS.md: seccion "instance reality" y flujo UI paso a paso. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cd998ce6b0
commit
e7d1c33cd5
@@ -0,0 +1,177 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validate a project directory against the Coolify hard rules before pushing.
|
||||
|
||||
.DESCRIPTION
|
||||
Implements the pre-deploy checklist from docs/AGENTS-coolify-apps.md as an
|
||||
automated gate. Run this BEFORE pushing to GitHub and BEFORE creating the
|
||||
Coolify application. Reports each rule as PASS / WARN / FAIL with the
|
||||
concrete fix for any failure.
|
||||
|
||||
The script does NOT modify files. It only inspects.
|
||||
|
||||
.PARAMETER Path
|
||||
Project directory to validate. Defaults to the current directory.
|
||||
|
||||
.PARAMETER ExpectedPort
|
||||
Port the app is expected to listen on. Used to cross-check against
|
||||
ports_exposes if a compose file declares it, and against Dockerfile EXPOSE.
|
||||
|
||||
.PARAMETER Strict
|
||||
Treat WARN as failures (exit code 1). Default: only FAIL fails.
|
||||
|
||||
.EXAMPLE
|
||||
.\Test-PreDeployChecklist.ps1 -Path .\my-app -ExpectedPort 8080
|
||||
#>
|
||||
param(
|
||||
[string]$Path = ".",
|
||||
|
||||
[string]$ExpectedPort,
|
||||
|
||||
[switch]$Strict
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$root = (Resolve-Path -LiteralPath $Path).Path
|
||||
$results = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
function Add-Result {
|
||||
param([string]$Id, [string]$Status, [string]$Message, [string]$Fix = "")
|
||||
$results.Add([PSCustomObject]@{
|
||||
Id = $Id
|
||||
Status = $Status
|
||||
Message = $Message
|
||||
Fix = $Fix
|
||||
})
|
||||
}
|
||||
|
||||
$composeFiles = @("docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml") |
|
||||
ForEach-Object { Join-Path $root $_ } |
|
||||
Where-Object { Test-Path -LiteralPath $_ }
|
||||
|
||||
$dockerfile = @("Dockerfile", "Dockerfile.dev") |
|
||||
ForEach-Object { Join-Path $root $_ } |
|
||||
Where-Object { Test-Path -LiteralPath $_ } |
|
||||
Select-Object -First 1
|
||||
|
||||
# Rule 2.1 - DB host is service name, not localhost
|
||||
foreach ($cf in $composeFiles) {
|
||||
$txt = Get-Content -LiteralPath $cf -Raw
|
||||
if ($txt -match 'localhost|127\.0\.0\.1') {
|
||||
$svc = ($txt -split "`n" | Select-String -Pattern 'localhost|127\.0\.0\.1' | Select-Object -First 1)
|
||||
Add-Result "2.1-sibling-dns" "WARN" "$cf references localhost/127.0.0.1; if it is a DB/cache host for a sibling service, use the Docker service name." "Replace localhost with the sibling service name (see AGENTS-coolify-apps.md 2.1)."
|
||||
}
|
||||
}
|
||||
if ($results | Where-Object Id -eq '2.1-sibling-dns') {
|
||||
# already added
|
||||
} else {
|
||||
Add-Result "2.1-sibling-dns" "PASS" "No localhost/127.0.0.1 reference detected in compose files."
|
||||
}
|
||||
|
||||
# Rule 2.2 - coolify external network if app references shared services
|
||||
foreach ($cf in $composeFiles) {
|
||||
$txt = Get-Content -LiteralPath $cf -Raw
|
||||
if (($txt -match 'postgres|redis|mysql|mariadb|mongodb') -and ($txt -notmatch '(?ms)networks:.*?coolify:.*?external:\s*true')) {
|
||||
Add-Result "2.2-coolify-network" "WARN" "$cf references a DB/cache but does not declare the external 'coolify' network." "If the app must reach shared Coolify services, add the external coolify network (see AGENTS-coolify-apps.md 2.2)."
|
||||
}
|
||||
}
|
||||
if (-not ($results | Where-Object Id -eq '2.2-coolify-network')) {
|
||||
Add-Result "2.2-coolify-network" "PASS" "No external coolify network requirement detected, or it is correctly declared."
|
||||
}
|
||||
|
||||
# Rule 2.3 - no published 80/443
|
||||
$badPorts = $false
|
||||
foreach ($cf in $composeFiles) {
|
||||
$txt = Get-Content -LiteralPath $cf -Raw
|
||||
if ($txt -match '(?m)^\s*-\s*"?80:80"?|^\s*-\s*"?443:443"?|"80:80"|"443:443"|ports_exposes.*\b80\b.*\b443\b') {
|
||||
Add-Result "2.3-no-80-443-publish" "FAIL" "$cf publishes host ports 80 or 443 (owned by coolify-proxy)." "Remove the host port mapping; let Traefik route (AGENTS-coolify-apps.md 2.3)."
|
||||
$badPorts = $true
|
||||
}
|
||||
}
|
||||
if (-not $badPorts) {
|
||||
Add-Result "2.3-no-80-443-publish" "PASS" "No host 80/443 publishing found."
|
||||
}
|
||||
|
||||
# Rule 2.4 - FQDN hint (informational)
|
||||
Add-Result "2.4-fqdn-set" "INFO" "Remember to set FQDN to https://<name>.urieljareth.org BEFORE first deploy (not via Coolify's auto UUID subdomain)." "Set -Fqdn when calling New-CoolifyApplication.ps1."
|
||||
|
||||
# Rule 2.5 - no obvious secrets in tree
|
||||
$secretPatterns = @(
|
||||
'AKIA[0-9A-Z]{16}',
|
||||
'ghp_[A-Za-z0-9]{36}',
|
||||
'github_pat_[A-Za-z0-9_]{82}',
|
||||
'-----BEGIN (RSA |EC |OPENSSH |)PRIVATE KEY-----',
|
||||
'postgres(ql)?://[^:\s]+:[^@\s]+@'
|
||||
)
|
||||
$found = $false
|
||||
Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -notmatch '\\(\.git|node_modules|vendor|dist|build)\\' } |
|
||||
ForEach-Object {
|
||||
try {
|
||||
$content = Get-Content -LiteralPath $_.FullName -Raw -ErrorAction Stop
|
||||
foreach ($pat in $secretPatterns) {
|
||||
if ($content -match $pat) {
|
||||
Add-Result "2.5-no-secrets-in-repo" "FAIL" "Potential secret matching pattern '$pat' found in $($_.FullName)." "Remove the secret and inject it as env via Coolify (AGENTS-coolify-apps.md 2.5)."
|
||||
$found = $true
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
if (-not $found) {
|
||||
Add-Result "2.5-no-secrets-in-repo" "PASS" "No obvious hardcoded secrets detected."
|
||||
}
|
||||
|
||||
# Rule 4 - healthcheck present
|
||||
$hcFound = $false
|
||||
foreach ($cf in $composeFiles) {
|
||||
$txt = Get-Content -LiteralPath $cf -Raw
|
||||
if ($txt -match 'healthcheck:') { $hcFound = $true }
|
||||
}
|
||||
if ($dockerfile) {
|
||||
$txt = Get-Content -LiteralPath $dockerfile -Raw
|
||||
if ($txt -match 'HEALTHCHECK') { $hcFound = $true }
|
||||
}
|
||||
if ($hcFound) {
|
||||
Add-Result "4-healthcheck" "PASS" "Healthcheck detected."
|
||||
} else {
|
||||
Add-Result "4-healthcheck" "WARN" "No healthcheck found in compose or Dockerfile." "Add a healthcheck on a DB-independent endpoint (AGENTS-coolify-apps.md section 4)."
|
||||
}
|
||||
|
||||
# Rule 5 - named volumes for persistence
|
||||
foreach ($cf in $composeFiles) {
|
||||
$txt = Get-Content -LiteralPath $cf -Raw
|
||||
if ($txt -match 'volumes:' -and $txt -notmatch '(?ms)^volumes:\s*\n\s+\S+:') {
|
||||
Add-Result "5-named-volumes" "WARN" "$cf has volumes but no top-level named volumes section." "Declare named volumes to survive redeploys (AGENTS-coolify-apps.md section 5)."
|
||||
}
|
||||
}
|
||||
if (-not ($results | Where-Object Id -eq '5-named-volumes')) {
|
||||
Add-Result "5-named-volumes" "PASS" "Volume declarations look consistent."
|
||||
}
|
||||
|
||||
# Cross-check ExpectedPort vs Dockerfile EXPOSE
|
||||
if ($ExpectedPort -and $dockerfile) {
|
||||
$txt = Get-Content -LiteralPath $dockerfile -Raw
|
||||
if ($txt -match 'EXPOSE\s+(\d+)') {
|
||||
$exposed = $matches[1]
|
||||
if ($exposed -ne $ExpectedPort) {
|
||||
Add-Result "ports-match" "WARN" "Dockerfile EXPOSE=$exposed but ExpectedPort=$ExpectedPort." "Make ports_exposes match the real listening port."
|
||||
} else {
|
||||
Add-Result "ports-match" "PASS" "Dockerfile EXPOSE matches ExpectedPort ($ExpectedPort)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results | Format-Table -AutoSize
|
||||
|
||||
$failed = $results | Where-Object Status -eq 'FAIL'
|
||||
$warned = $results | Where-Object Status -eq 'WARN'
|
||||
if ($failed) {
|
||||
Write-Host "`nFAIL: $($failed.Count) hard rule(s) violated. Fix before pushing." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
if ($warned) {
|
||||
Write-Host "`nWARN: $($warned.Count) soft warning(s). Review before deploy." -ForegroundColor Yellow
|
||||
if ($Strict) { exit 1 }
|
||||
}
|
||||
Write-Host "`nChecklist complete." -ForegroundColor Green
|
||||
Reference in New Issue
Block a user