fix: stop baking Supabase anon key into the Docker image
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,8 +12,8 @@ COPY --from=deps /app/node_modules ./node_modules
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN mkdir -p public
|
RUN mkdir -p public
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV NEXT_PUBLIC_SUPABASE_URL=https://supabase.4ventures.es
|
# NEXT_PUBLIC_* must come from Dokploy "Create Environment File" (.env) or build-args.
|
||||||
ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NzIzMTgzMTAsImV4cCI6MTg5MzQ1NjAwMCwicm9sZSI6ImFub24iLCJpc3MiOiJzdXBhYmFzZSJ9.UWNn1leZNljna9Qwi9U5RtGqsh5_SnEoiPO8HkQIgi8
|
# Do not bake secrets into the image.
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
|
|||||||
@@ -1,20 +1,30 @@
|
|||||||
# Runbook — Centro de Mando 4Ventures
|
# Runbook — Centro de Mando 4Ventures
|
||||||
|
|
||||||
Operador: `marketing@4ventures.es`
|
Operador: `marketing@4ventures.es`
|
||||||
|
Credenciales locales (gitignored): `.ops/marketing-credentials.txt`
|
||||||
|
|
||||||
## Health
|
## Health
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sf https://app.4ventures.es/api/health
|
curl -sf https://app.4ventures.es/api/health
|
||||||
|
# expect: serviceRoleConfigured true
|
||||||
./scripts/smoke.sh https://app.4ventures.es
|
./scripts/smoke.sh https://app.4ventures.es
|
||||||
|
python3 scripts/smoke_operator_login.py
|
||||||
|
python3 scripts/smoke_chat_auth.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploy (Dokploy)
|
## Deploy (Dokploy)
|
||||||
|
|
||||||
1. Push a `main` en Gitea `Marketing/4ventures-client-portal`.
|
1. Push a `main` en Gitea `Marketing/4ventures-client-portal`.
|
||||||
2. Dokploy rebuild del servicio `app`.
|
2. Dokploy → **Web4Ventures / production / client-portal** → Deploy
|
||||||
3. Env: `NEXT_PUBLIC_SUPABASE_*`, `SUPABASE_SERVICE_ROLE_KEY`, `ANTHROPIC_API_KEY`.
|
(id interno `web4ventures-clientportal-jntjhs`).
|
||||||
4. Smoke: `/login` → KPIs → simulador → chat.
|
3. Environment (runtime + build via “Create Environment File”):
|
||||||
|
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||||
|
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||||
|
- `SUPABASE_SERVICE_ROLE_KEY`
|
||||||
|
- `ANTHROPIC_API_KEY` (opcional, LLM)
|
||||||
|
4. Traefik: `portal.yml` → `http://web4ventures-clientportal-jntjhs:3000`
|
||||||
|
5. Smoke: `/login` → KPIs → creatividades → chat.
|
||||||
|
|
||||||
## Alta cliente
|
## Alta cliente
|
||||||
|
|
||||||
@@ -28,6 +38,10 @@ UI `/dashboard/agencia` (roles admin/team) o RPC `provision_client`.
|
|||||||
|
|
||||||
`POST https://n8n-agencia.4ventures.es/webhook/lead-inbound`
|
`POST https://n8n-agencia.4ventures.es/webhook/lead-inbound`
|
||||||
|
|
||||||
|
## Endurecer git / n8n
|
||||||
|
|
||||||
|
Ver `.ops/HARDEN_SURFACES.md` (IP allowlist; no aplicado hasta tener IPs).
|
||||||
|
|
||||||
## Incidentes
|
## Incidentes
|
||||||
|
|
||||||
Ver `SECURITY.md`.
|
Ver `SECURITY.md`.
|
||||||
|
|||||||
67
scripts/smoke_agencia_simulate.py
Normal file
67
scripts/smoke_agencia_simulate.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Authenticated smoke: agencia + simulate."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
|
ROOT = Path("/Users/4ventures/4ventures-client-portal")
|
||||||
|
APP = "https://app.4ventures.es"
|
||||||
|
CREDS = ROOT / ".ops" / "marketing-credentials.txt"
|
||||||
|
SHOT = ROOT / "scripts/dokploy-shots"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
email, password = "marketing@4ventures.es", ""
|
||||||
|
for line in CREDS.read_text().splitlines():
|
||||||
|
if line.startswith("email="):
|
||||||
|
email = line.split("=", 1)[1].strip()
|
||||||
|
if line.startswith("password="):
|
||||||
|
password = line.split("=", 1)[1].strip()
|
||||||
|
|
||||||
|
with sync_playwright() as p:
|
||||||
|
browser = p.chromium.launch(headless=True)
|
||||||
|
page = browser.new_page(viewport={"width": 1400, "height": 1100})
|
||||||
|
page.goto(f"{APP}/login", wait_until="networkidle", timeout=90000)
|
||||||
|
page.locator('input[type="email"], input[name="email"]').first.fill(email)
|
||||||
|
page.locator('input[type="password"]').first.fill(password)
|
||||||
|
page.locator('button[type="submit"]').first.click()
|
||||||
|
page.wait_for_timeout(3500)
|
||||||
|
|
||||||
|
page.goto(f"{APP}/dashboard/agencia", wait_until="networkidle", timeout=60000)
|
||||||
|
page.wait_for_timeout(1500)
|
||||||
|
SHOT.mkdir(parents=True, exist_ok=True)
|
||||||
|
page.screenshot(path=str(SHOT / "smoke_agencia.png"), full_page=True)
|
||||||
|
body = page.inner_text("body")
|
||||||
|
missing_key = "Falta SUPABASE_SERVICE_ROLE_KEY" in body
|
||||||
|
print("agencia_missing_service_key", missing_key, flush=True)
|
||||||
|
print("agencia_snip", " | ".join([ln.strip() for ln in body.splitlines() if ln.strip()][10:22]), flush=True)
|
||||||
|
|
||||||
|
sim = page.evaluate(
|
||||||
|
"""async () => {
|
||||||
|
const r = await fetch('/api/simulate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'content-type':'application/json'},
|
||||||
|
body: JSON.stringify({ amount: 1000, channel: 'meta' })
|
||||||
|
});
|
||||||
|
const text = await r.text();
|
||||||
|
let json=null; try { json=JSON.parse(text); } catch {}
|
||||||
|
return { status: r.status, text: text.slice(0,250), json };
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
print("simulate", json.dumps(sim, ensure_ascii=False)[:400], flush=True)
|
||||||
|
browser.close()
|
||||||
|
|
||||||
|
if missing_key:
|
||||||
|
print("FAIL_AGENCIA", flush=True)
|
||||||
|
return 2
|
||||||
|
print("SUCCESS", flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user