@fazer-ai/agents 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/agents/claude.js +152 -0
- package/dist/agents/codex.js +155 -0
- package/dist/agents/detect.js +15 -0
- package/dist/agents/handoff.js +22 -0
- package/dist/agents/hermes-skills.js +177 -0
- package/dist/agents/hermes.js +474 -0
- package/dist/agents/index.js +57 -0
- package/dist/agents/manual.js +22 -0
- package/dist/agents/other.js +39 -0
- package/dist/agents/shell.js +15 -0
- package/dist/agents/types.js +2 -0
- package/dist/config.js +48 -0
- package/dist/exec.js +279 -0
- package/dist/hostinger.js +75 -0
- package/dist/hub-command.js +144 -0
- package/dist/index.js +726 -0
- package/dist/licenses.js +93 -0
- package/dist/mcp.js +100 -0
- package/dist/oauth.js +578 -0
- package/dist/onboarding-marker.js +48 -0
- package/dist/preferences.js +40 -0
- package/dist/skills/agents-dev/SKILL.md +37 -0
- package/dist/skills/agents-dev/gotchas.md +6 -0
- package/dist/skills/agents-dev/guardrails.md +6 -0
- package/dist/skills/agents-dev/references/00-get-the-code.md +28 -0
- package/dist/skills/agents-dev/references/01-layout-and-bun-check.md +29 -0
- package/dist/skills/agents-dev/references/02-free-full-and-invariants.md +7 -0
- package/dist/skills/agents-dev/references/03-implement.md +9 -0
- package/dist/skills/agents-dev/references/04-own-image-and-deploy.md +13 -0
- package/dist/skills/agents-onboarding/SKILL.md +80 -0
- package/dist/skills/agents-onboarding/gotchas.md +157 -0
- package/dist/skills/agents-onboarding/guardrails.md +65 -0
- package/dist/skills/agents-onboarding/references/00-prereqs-and-access.md +37 -0
- package/dist/skills/agents-onboarding/references/01-vps-dns-ssh.md +67 -0
- package/dist/skills/agents-onboarding/references/01b-brownfield.md +70 -0
- package/dist/skills/agents-onboarding/references/01c-pick-tier.md +61 -0
- package/dist/skills/agents-onboarding/references/02-coolify.md +109 -0
- package/dist/skills/agents-onboarding/references/03-chatwoot-pro.md +61 -0
- package/dist/skills/agents-onboarding/references/04-agents-image.md +46 -0
- package/dist/skills/agents-onboarding/references/05-langfuse.md +45 -0
- package/dist/skills/agents-onboarding/references/06-setup-and-mcp.md +47 -0
- package/dist/skills/agents-onboarding/references/08-agent-import.md +55 -0
- package/dist/skills/agents-onboarding/references/09-chatwoot-bind.md +41 -0
- package/dist/skills/agents-onboarding/references/10-validate-e2e.md +34 -0
- package/dist/skills/agents-onboarding/references/agent-features.md +61 -0
- package/dist/skills/agents-onboarding/references/chatwoot-hub-register.md +69 -0
- package/dist/skills/agents-onboarding/references/deploy-b-portainer.md +138 -0
- package/dist/skills/agents-onboarding/references/deploy-c-compose.md +64 -0
- package/dist/skills/agents-onboarding/samples/agents/README.md +23 -0
- package/dist/skills/agents-onboarding/samples/agents/maria-clinica-moreira.json +313 -0
- package/dist/skills/agents-onboarding/scripts/chatwoot-admin.py +248 -0
- package/dist/skills/agents-onboarding/scripts/coolify.py +552 -0
- package/dist/skills/agents-onboarding/scripts/docker-status.py +129 -0
- package/dist/skills/agents-onboarding/scripts/gen-onboarding-env.ts +187 -0
- package/dist/skills/agents-onboarding/scripts/harbor-login.py +118 -0
- package/dist/skills/agents-onboarding/scripts/langfuse-verify.py +118 -0
- package/dist/skills/agents-onboarding/scripts/portainer-brownfield.py +115 -0
- package/dist/skills/agents-onboarding/scripts/remote.py +198 -0
- package/dist/skills/agents-onboarding/scripts/sshkey.py +140 -0
- package/dist/skills/agents-onboarding/templates/chatwoot/.env.example +30 -0
- package/dist/skills/agents-onboarding/templates/chatwoot/README.md +65 -0
- package/dist/skills/agents-onboarding/templates/chatwoot/docker-compose.coolify.yml +136 -0
- package/dist/skills/agents-onboarding/templates/chatwoot/docker-compose.yml +139 -0
- package/dist/skills/agents-onboarding/templates/docker-compose.coolify.yml +73 -0
- package/dist/skills/agents-onboarding/templates/docker-compose.portainer.yml +132 -0
- package/dist/skills/agents-onboarding/templates/docker-compose.prod.yml +85 -0
- package/dist/skills/agents-onboarding/templates/langfuse/.env.example +59 -0
- package/dist/skills/agents-onboarding/templates/langfuse/README.md +132 -0
- package/dist/skills/agents-onboarding/templates/langfuse/docker-compose.coolify.yml +189 -0
- package/dist/skills/agents-onboarding/templates/langfuse/docker-compose.yml +185 -0
- package/dist/skills/agents-operation/SKILL.md +42 -0
- package/dist/skills/agents-operation/gotchas.md +61 -0
- package/dist/skills/agents-operation/guardrails.md +26 -0
- package/dist/skills/agents-operation/references/00-production-safety.md +24 -0
- package/dist/skills/agents-operation/references/01-diagnose.md +34 -0
- package/dist/skills/agents-operation/references/02-reproduce.md +22 -0
- package/dist/skills/agents-operation/references/03-adjust.md +36 -0
- package/dist/skills/agents-operation/references/04-validate-and-apply.md +31 -0
- package/dist/ui-select.js +279 -0
- package/dist/ui.js +167 -0
- package/package.json +53 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Chatwoot for fazer.ai: GENERIC flavor (Portainer / EasyPanel / Dokploy / plain Docker).
|
|
2
|
+
#
|
|
3
|
+
# ONE file, BOTH editions, selected by env (see ./README.md):
|
|
4
|
+
# - OSS (no hub subscription): defaults, our public `ghcr.io/fazer-ai/chatwoot` fork, no Baileys.
|
|
5
|
+
# - Pro (hub subscription): set CHATWOOT_IMAGE to the Harbor image AND COMPOSE_PROFILES=pro
|
|
6
|
+
# (adds the Baileys WhatsApp provider). Requires `docker login harbor.fazer.ai` with the
|
|
7
|
+
# license registry credential (hub: generate_install_script).
|
|
8
|
+
#
|
|
9
|
+
# Secrets come from a .env you fill in (see ./.env.example); Coolify generates those automatically,
|
|
10
|
+
# so it has its own file (./docker-compose.coolify.yml). TLS + reverse proxy are EXTERNAL: this stack
|
|
11
|
+
# only publishes Chatwoot's HTTP port on the host (default 127.0.0.1:3001); terminate TLS in front
|
|
12
|
+
# (the fazer.ai agents bundled Caddy can front chatwoot.<domain> at it; see the agents-onboarding skill).
|
|
13
|
+
#
|
|
14
|
+
# cp .env.example .env # fill every CHANGE_ME, then:
|
|
15
|
+
# docker compose up -d # OSS
|
|
16
|
+
# COMPOSE_PROFILES=pro docker compose up -d # Pro (after the Harbor login)
|
|
17
|
+
|
|
18
|
+
x-chatwoot-env: &chatwoot-env
|
|
19
|
+
NODE_ENV: production
|
|
20
|
+
RAILS_ENV: production
|
|
21
|
+
INSTALLATION_ENV: docker
|
|
22
|
+
DEFAULT_LOCALE: '${CHATWOOT_LOCALE:-en}'
|
|
23
|
+
LOG_LEVEL: '${RAILS_LOG_LEVEL:-info}'
|
|
24
|
+
FRONTEND_URL: '${CHATWOOT_URL}'
|
|
25
|
+
INTERNAL_HOST_URL: 'http://chatwoot:3000'
|
|
26
|
+
POSTGRES_HOST: postgres
|
|
27
|
+
POSTGRES_PORT: '5432'
|
|
28
|
+
POSTGRES_USERNAME: '${POSTGRES_USER}'
|
|
29
|
+
POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}'
|
|
30
|
+
POSTGRES_DATABASE: '${POSTGRES_DB:-chatwoot_production}'
|
|
31
|
+
SECRET_KEY_BASE: '${SECRET_KEY_BASE}'
|
|
32
|
+
REDIS_URL: 'redis://redis:6379'
|
|
33
|
+
REDIS_PASSWORD: '${REDIS_PASSWORD}'
|
|
34
|
+
# Pro-only (Baileys WhatsApp): harmless on OSS (no Baileys inbox can be created there).
|
|
35
|
+
BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME: '${BAILEYS_CLIENT_NAME:-Chatwoot}'
|
|
36
|
+
BAILEYS_PROVIDER_DEFAULT_URL: 'http://baileys-api:3025'
|
|
37
|
+
BAILEYS_PROVIDER_DEFAULT_API_KEY: '${BAILEYS_DEFAULT_API_KEY:-}'
|
|
38
|
+
BAILEYS_PROVIDER_USE_INTERNAL_HOST_URL: 'true'
|
|
39
|
+
|
|
40
|
+
services:
|
|
41
|
+
chatwoot:
|
|
42
|
+
# OSS default (our fork); for Pro set CHATWOOT_IMAGE=harbor.fazer.ai/chatwoot/fazer-ai/chatwoot-pro:latest
|
|
43
|
+
image: '${CHATWOOT_IMAGE:-ghcr.io/fazer-ai/chatwoot:latest}'
|
|
44
|
+
pull_policy: always
|
|
45
|
+
volumes:
|
|
46
|
+
- 'storage:/app/storage'
|
|
47
|
+
depends_on:
|
|
48
|
+
postgres: { condition: service_healthy }
|
|
49
|
+
redis: { condition: service_healthy }
|
|
50
|
+
environment: *chatwoot-env
|
|
51
|
+
entrypoint: docker/entrypoints/rails.sh
|
|
52
|
+
command:
|
|
53
|
+
- sh
|
|
54
|
+
- '-c'
|
|
55
|
+
- 'bundle exec rails db:chatwoot_prepare && exec bundle exec rails s -p 3000 -b 0.0.0.0'
|
|
56
|
+
ports:
|
|
57
|
+
# Host:container. Front with your own TLS-terminating reverse proxy; bound to localhost by default.
|
|
58
|
+
- '${CHATWOOT_PORT:-127.0.0.1:3001}:3000'
|
|
59
|
+
restart: always
|
|
60
|
+
healthcheck:
|
|
61
|
+
test: ['CMD-SHELL', 'wget -qO- --header="Accept: text/html" http://127.0.0.1:3000/ || exit 1']
|
|
62
|
+
interval: 20s
|
|
63
|
+
timeout: 10s
|
|
64
|
+
retries: 10
|
|
65
|
+
|
|
66
|
+
sidekiq:
|
|
67
|
+
image: '${CHATWOOT_IMAGE:-ghcr.io/fazer-ai/chatwoot:latest}'
|
|
68
|
+
pull_policy: always
|
|
69
|
+
volumes:
|
|
70
|
+
- 'storage:/app/storage'
|
|
71
|
+
depends_on:
|
|
72
|
+
postgres: { condition: service_healthy }
|
|
73
|
+
redis: { condition: service_healthy }
|
|
74
|
+
environment: *chatwoot-env
|
|
75
|
+
command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
|
|
76
|
+
restart: always
|
|
77
|
+
healthcheck:
|
|
78
|
+
test: ['CMD-SHELL', 'ps aux | grep "[s]idekiq" || exit 1']
|
|
79
|
+
interval: 20s
|
|
80
|
+
timeout: 10s
|
|
81
|
+
retries: 5
|
|
82
|
+
|
|
83
|
+
postgres:
|
|
84
|
+
# pgvector (Chatwoot 4.x "Captain" AI uses the vector extension). Public image, no auth needed.
|
|
85
|
+
image: '${POSTGRES_IMAGE:-pgvector/pgvector:pg16}'
|
|
86
|
+
restart: always
|
|
87
|
+
volumes:
|
|
88
|
+
- 'postgres:/var/lib/postgresql/data'
|
|
89
|
+
environment:
|
|
90
|
+
POSTGRES_DB: '${POSTGRES_DB:-chatwoot_production}'
|
|
91
|
+
POSTGRES_USER: '${POSTGRES_USER}'
|
|
92
|
+
POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}'
|
|
93
|
+
healthcheck:
|
|
94
|
+
test: ['CMD-SHELL', 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
|
|
95
|
+
interval: 20s
|
|
96
|
+
timeout: 20s
|
|
97
|
+
retries: 10
|
|
98
|
+
|
|
99
|
+
redis:
|
|
100
|
+
image: 'redis:alpine'
|
|
101
|
+
restart: always
|
|
102
|
+
command: ['sh', '-c', 'redis-server --requirepass "$${REDIS_PASSWORD}"']
|
|
103
|
+
volumes:
|
|
104
|
+
- 'redis:/data'
|
|
105
|
+
environment:
|
|
106
|
+
REDIS_PASSWORD: '${REDIS_PASSWORD}'
|
|
107
|
+
healthcheck:
|
|
108
|
+
test: ['CMD-SHELL', 'redis-cli -h localhost -p 6379 -a $${REDIS_PASSWORD} ping | grep -q PONG']
|
|
109
|
+
interval: 20s
|
|
110
|
+
timeout: 20s
|
|
111
|
+
retries: 10
|
|
112
|
+
|
|
113
|
+
# Pro only: the Baileys WhatsApp provider. Starts ONLY with COMPOSE_PROFILES=pro.
|
|
114
|
+
baileys-api:
|
|
115
|
+
image: '${BAILEYS_IMAGE:-ghcr.io/fazer-ai/baileys-api:latest}'
|
|
116
|
+
profiles: ['pro']
|
|
117
|
+
pull_policy: always
|
|
118
|
+
volumes:
|
|
119
|
+
- 'storage:/app/storage'
|
|
120
|
+
environment:
|
|
121
|
+
NODE_ENV: production
|
|
122
|
+
REDIS_URL: 'redis://redis:6379'
|
|
123
|
+
REDIS_PASSWORD: '${REDIS_PASSWORD}'
|
|
124
|
+
LOG_LEVEL: '${BAILEYS_API_LOG_LEVEL:-info}'
|
|
125
|
+
BAILEYS_PROVIDER_DEFAULT_API_KEY: '${BAILEYS_DEFAULT_API_KEY}'
|
|
126
|
+
command: ['sh', '-c', 'bun manage-api-keys create user $${BAILEYS_DEFAULT_API_KEY} && bun start']
|
|
127
|
+
depends_on:
|
|
128
|
+
redis: { condition: service_healthy }
|
|
129
|
+
restart: always
|
|
130
|
+
healthcheck:
|
|
131
|
+
test: ['CMD-SHELL', 'wget -qO- http://localhost:3025/status || exit 1']
|
|
132
|
+
interval: 20s
|
|
133
|
+
timeout: 20s
|
|
134
|
+
retries: 10
|
|
135
|
+
|
|
136
|
+
volumes:
|
|
137
|
+
storage:
|
|
138
|
+
postgres:
|
|
139
|
+
redis:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
services:
|
|
2
|
+
agents:
|
|
3
|
+
# Edition-driven: Free (default, public image) vs Pro — for Pro, set the AGENTS_IMAGE env on
|
|
4
|
+
# the Coolify service to the private Harbor image (project `agents`) AND register the Harbor
|
|
5
|
+
# registry credential in Coolify (hub: create_registry_credential, per-user, same as Chatwoot Pro).
|
|
6
|
+
# PLACEHOLDER: the public Free image is not published yet — swap `agents` once it exists.
|
|
7
|
+
image: '${AGENTS_IMAGE:-ghcr.io/fazer-ai/agents:latest}'
|
|
8
|
+
pull_policy: always
|
|
9
|
+
volumes:
|
|
10
|
+
- 'storage:/app/storage'
|
|
11
|
+
environment:
|
|
12
|
+
- SERVICE_URL_AGENTS
|
|
13
|
+
- NODE_ENV=production
|
|
14
|
+
- PORT=${PORT:-3000}
|
|
15
|
+
- PUBLIC_URL=${SERVICE_URL_AGENTS}
|
|
16
|
+
- LOG_LEVEL=${LOG_LEVEL:-info}
|
|
17
|
+
- JWT_SECRET=${SERVICE_PASSWORD_64_JWTSECRET}
|
|
18
|
+
- ENCRYPTION_KEY=${SERVICE_PASSWORD_64_ENCRYPTIONKEY}
|
|
19
|
+
- CORS_ORIGIN=${CORS_ORIGIN:-*}
|
|
20
|
+
- CDN_URL=${SERVICE_URL_AGENTS}
|
|
21
|
+
# NOTE: opt this onboarding / self-hosted deploy into MCP Dynamic Client Registration so the
|
|
22
|
+
# operator's AI agent (Claude Code / Codex) can `mcp login` to THIS instance's MCP — OAuth DCR
|
|
23
|
+
# mints the client and the metadata then publishes `registration_endpoint`. The app code default
|
|
24
|
+
# is OFF (closed/hardened, see docs/mcp.md); this agent-operated deploy turns it ON deliberately.
|
|
25
|
+
- MCP_DCR_ENABLED=true
|
|
26
|
+
# Persist uploaded branding (logo/favicon) + generated quote PDFs on the `storage` volume.
|
|
27
|
+
# The defaults (./data/*) live on the ephemeral container FS and vanish on every redeploy.
|
|
28
|
+
- BRANDING_STORAGE_DIR=/app/storage/branding
|
|
29
|
+
- QUOTES_STORAGE_DIR=/app/storage/quotes
|
|
30
|
+
# NOTE: TWO roles by design. The superuser (SERVICE_USER_DBUSER, also the postgres
|
|
31
|
+
# container's owner) runs migrations + bootstrap. The app role (SERVICE_USER_APPDBUSER,
|
|
32
|
+
# created by db-bootstrap.ts) is NON-superuser/NON-bypassrls so RLS isolation holds — the
|
|
33
|
+
# boot guard refuses to start the server on a privileged runtime role.
|
|
34
|
+
- 'MIGRATION_DATABASE_URL=postgres://${SERVICE_USER_DBUSER}:${SERVICE_PASSWORD_64_DBPASSWORD}@${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}/${DATABASE_NAME:-secretaria_v4_db}'
|
|
35
|
+
- 'DATABASE_URL=postgres://${SERVICE_USER_APPDBUSER}:${SERVICE_PASSWORD_64_APPDBPASSWORD}@${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}/${DATABASE_NAME:-secretaria_v4_db}'
|
|
36
|
+
- 'LANGGRAPH_DATABASE_URL=postgres://${SERVICE_USER_APPDBUSER}:${SERVICE_PASSWORD_64_APPDBPASSWORD}@${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}/${DATABASE_NAME:-secretaria_v4_db}'
|
|
37
|
+
# NOTE: the boot sequence (provision runtime role -> migrate as owner -> run the interpreted
|
|
38
|
+
# server) is the image CMD in the Dockerfile. Do NOT override `command` here: it once drifted to
|
|
39
|
+
# a stale `./server` (from the pre-interpreted `bun build --compile` era) and crash-looped on
|
|
40
|
+
# `exec: ./server: not found` while the image CMD stayed correct.
|
|
41
|
+
restart: always
|
|
42
|
+
healthcheck:
|
|
43
|
+
test:
|
|
44
|
+
- CMD-SHELL
|
|
45
|
+
- 'wget -qO- http://localhost:3000/api/health'
|
|
46
|
+
interval: 20s
|
|
47
|
+
timeout: 10s
|
|
48
|
+
retries: 5
|
|
49
|
+
depends_on:
|
|
50
|
+
postgres:
|
|
51
|
+
condition: service_healthy
|
|
52
|
+
postgres:
|
|
53
|
+
# pgvector image (NOT plain postgres): the schema needs CREATE EXTENSION vector. pg17 matches
|
|
54
|
+
# the validated isolation spike.
|
|
55
|
+
image: 'pgvector/pgvector:pg17'
|
|
56
|
+
restart: always
|
|
57
|
+
volumes:
|
|
58
|
+
- 'postgres:/var/lib/postgresql/data'
|
|
59
|
+
environment:
|
|
60
|
+
- POSTGRES_DB=${DATABASE_NAME:-secretaria_v4_db}
|
|
61
|
+
- POSTGRES_USER=${SERVICE_USER_DBUSER}
|
|
62
|
+
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_64_DBPASSWORD}
|
|
63
|
+
healthcheck:
|
|
64
|
+
test:
|
|
65
|
+
- CMD-SHELL
|
|
66
|
+
- 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}'
|
|
67
|
+
interval: 20s
|
|
68
|
+
timeout: 20s
|
|
69
|
+
retries: 10
|
|
70
|
+
|
|
71
|
+
volumes:
|
|
72
|
+
storage:
|
|
73
|
+
postgres:
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Self-contained production stack for fazer.ai agents on Portainer (Tier B deploy) — and any host where
|
|
2
|
+
# you want TLS terminated INSIDE the stack instead of by an external proxy.
|
|
3
|
+
#
|
|
4
|
+
# Difference vs docker-compose.prod.yml: prod.yml only publishes the app's HTTP port and expects you to
|
|
5
|
+
# bring your own reverse proxy (Traefik/Caddy/NPM). THIS file bundles a Caddy service that terminates TLS
|
|
6
|
+
# and obtains a REAL certificate automatically (Let's Encrypt / ZeroSSL via the HTTP-01 challenge) for
|
|
7
|
+
# CADDY_DOMAIN — no Traefik labels, no Docker-socket polling. That is the Tier B "agent brings up Caddy"
|
|
8
|
+
# path in the onboarding plan.
|
|
9
|
+
#
|
|
10
|
+
# WHY SELF-CONTAINED (not a `-f` override of prod.yml): Portainer's "deploy from string" API
|
|
11
|
+
# (POST /api/stacks/create/standalone/string) accepts ONE compose document, so this file repeats prod.yml's
|
|
12
|
+
# agents + postgres verbatim and adds caddy. Keep those two blocks in sync with docker-compose.prod.yml.
|
|
13
|
+
#
|
|
14
|
+
# Fill the env with:
|
|
15
|
+
# bun scripts/gen-onboarding-env.ts --public-url https://agentes.example.com [--acme-email you@example.com]
|
|
16
|
+
# It emits CADDY_DOMAIN + the two-role DB URLs + secrets. On Portainer, paste this file as a Stack and set
|
|
17
|
+
# the same variables in the Stack env editor (or pass them as the API's Env[] array). To also serve the
|
|
18
|
+
# Portainer UI itself on a clean HTTPS domain, set PORTAINER_DOMAIN (e.g. portainer.example.com) — Caddy
|
|
19
|
+
# adds a second site reverse-proxying to the host's Portainer (9443, self-signed upstream).
|
|
20
|
+
#
|
|
21
|
+
# TWO database roles by design (docs/deploy.md): MIGRATION_DATABASE_URL = superuser (migrate + bootstrap);
|
|
22
|
+
# DATABASE_URL / LANGGRAPH_DATABASE_URL = NON-superuser runtime role (RLS holds; the boot guard enforces it).
|
|
23
|
+
# SINGLE replica by construction (scheduler / debounce / webhook / alert workers + realtime pub/sub are
|
|
24
|
+
# single-process). Do NOT scale agents to >1 here.
|
|
25
|
+
|
|
26
|
+
services:
|
|
27
|
+
caddy:
|
|
28
|
+
image: 'caddy:2'
|
|
29
|
+
restart: always
|
|
30
|
+
depends_on:
|
|
31
|
+
- agents
|
|
32
|
+
ports:
|
|
33
|
+
- '80:80'
|
|
34
|
+
- '443:443'
|
|
35
|
+
extra_hosts:
|
|
36
|
+
# Lets the optional Portainer-panel site (PORTAINER_DOMAIN) reach the host's Portainer UI on 9443.
|
|
37
|
+
- 'host.docker.internal:host-gateway'
|
|
38
|
+
environment:
|
|
39
|
+
- CADDY_DOMAIN=${CADDY_DOMAIN}
|
|
40
|
+
- ACME_EMAIL=${ACME_EMAIL:-}
|
|
41
|
+
- PORTAINER_DOMAIN=${PORTAINER_DOMAIN:-}
|
|
42
|
+
- PORTAINER_UPSTREAM=${PORTAINER_UPSTREAM:-https://host.docker.internal:9443}
|
|
43
|
+
# Build a Caddyfile from env at start (a string-only Portainer deploy ships no files), then run it.
|
|
44
|
+
# `$$VAR` is escaped from compose interpolation so the SHELL expands it at runtime from `environment:`;
|
|
45
|
+
# `${VAR}` in `environment:` above IS interpolated by compose from the stack env / .env.
|
|
46
|
+
entrypoint:
|
|
47
|
+
- sh
|
|
48
|
+
- -ceu
|
|
49
|
+
- |
|
|
50
|
+
cf=/etc/caddy/Caddyfile
|
|
51
|
+
: > "$$cf"
|
|
52
|
+
if [ -n "$${ACME_EMAIL}" ]; then
|
|
53
|
+
printf '{\n\temail %s\n}\n' "$${ACME_EMAIL}" >> "$$cf"
|
|
54
|
+
fi
|
|
55
|
+
printf '%s {\n\treverse_proxy agents:3000\n}\n' "$${CADDY_DOMAIN}" >> "$$cf"
|
|
56
|
+
if [ -n "$${PORTAINER_DOMAIN}" ]; then
|
|
57
|
+
printf '%s {\n\treverse_proxy %s {\n\t\ttransport http {\n\t\t\ttls_insecure_skip_verify\n\t\t}\n\t}\n}\n' "$${PORTAINER_DOMAIN}" "$${PORTAINER_UPSTREAM}" >> "$$cf"
|
|
58
|
+
fi
|
|
59
|
+
echo '----- generated Caddyfile -----'
|
|
60
|
+
cat "$$cf"
|
|
61
|
+
echo '-------------------------------'
|
|
62
|
+
exec caddy run --config "$$cf" --adapter caddyfile
|
|
63
|
+
volumes:
|
|
64
|
+
- 'caddy_data:/data'
|
|
65
|
+
- 'caddy_config:/config'
|
|
66
|
+
|
|
67
|
+
agents:
|
|
68
|
+
# Edition-driven: Free (default, public image) vs Pro (set AGENTS_IMAGE to the private
|
|
69
|
+
# Harbor image, project `agents`, after `docker login` — hub: create_registry_credential, per-user).
|
|
70
|
+
# PLACEHOLDER: the public Free image is not published yet — swap `agents` once it exists.
|
|
71
|
+
image: ${AGENTS_IMAGE:-ghcr.io/fazer-ai/agents:latest}
|
|
72
|
+
pull_policy: always
|
|
73
|
+
restart: always
|
|
74
|
+
# NOTE: no `command` here on purpose — the image entrypoint runs, in order: db-bootstrap.ts (idempotent
|
|
75
|
+
# runtime-role provisioning) → prisma migrate deploy → serve. Overriding risks drifting from that contract.
|
|
76
|
+
environment:
|
|
77
|
+
- NODE_ENV=production
|
|
78
|
+
- PORT=3000
|
|
79
|
+
- PUBLIC_URL=${PUBLIC_URL}
|
|
80
|
+
- CDN_URL=${PUBLIC_URL}
|
|
81
|
+
- CORS_ORIGIN=${CORS_ORIGIN:-*}
|
|
82
|
+
- LOG_LEVEL=${LOG_LEVEL:-info}
|
|
83
|
+
- JWT_SECRET=${JWT_SECRET}
|
|
84
|
+
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
|
85
|
+
# Superuser/owner connection — migrations + bootstrap only. Matches the postgres container creds below.
|
|
86
|
+
- MIGRATION_DATABASE_URL=${MIGRATION_DATABASE_URL}
|
|
87
|
+
# Runtime role — NON-superuser, NON-bypassrls (db-bootstrap creates it from this URL). RLS holds.
|
|
88
|
+
- DATABASE_URL=${DATABASE_URL}
|
|
89
|
+
# Checkpointer pool — MUST be the runtime (non-superuser) role, never the migration URL.
|
|
90
|
+
- LANGGRAPH_DATABASE_URL=${LANGGRAPH_DATABASE_URL}
|
|
91
|
+
# Persist uploaded branding + generated quote PDFs on the `storage` volume (defaults live on the
|
|
92
|
+
# ephemeral container FS and vanish on every redeploy).
|
|
93
|
+
- BRANDING_STORAGE_DIR=/app/storage/branding
|
|
94
|
+
- QUOTES_STORAGE_DIR=/app/storage/quotes
|
|
95
|
+
# No host port publish: Caddy is the only ingress and reaches the app over the compose network.
|
|
96
|
+
expose:
|
|
97
|
+
- '3000'
|
|
98
|
+
volumes:
|
|
99
|
+
- 'storage:/app/storage'
|
|
100
|
+
healthcheck:
|
|
101
|
+
test: ['CMD-SHELL', 'wget -qO- http://localhost:3000/api/health || exit 1']
|
|
102
|
+
interval: 20s
|
|
103
|
+
timeout: 10s
|
|
104
|
+
retries: 5
|
|
105
|
+
depends_on:
|
|
106
|
+
postgres:
|
|
107
|
+
condition: service_healthy
|
|
108
|
+
|
|
109
|
+
postgres:
|
|
110
|
+
# pgvector image (NOT plain postgres): the schema needs CREATE EXTENSION vector. pg17 matches the
|
|
111
|
+
# validated isolation spike. Not published to the host — only the app reaches it over the network.
|
|
112
|
+
image: 'pgvector/pgvector:pg17'
|
|
113
|
+
restart: always
|
|
114
|
+
environment:
|
|
115
|
+
- POSTGRES_DB=${DATABASE_NAME:-secretaria_v4_db}
|
|
116
|
+
- POSTGRES_USER=${POSTGRES_USER}
|
|
117
|
+
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
118
|
+
volumes:
|
|
119
|
+
- 'postgres:/var/lib/postgresql/data'
|
|
120
|
+
healthcheck:
|
|
121
|
+
test:
|
|
122
|
+
- CMD-SHELL
|
|
123
|
+
- 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}'
|
|
124
|
+
interval: 20s
|
|
125
|
+
timeout: 20s
|
|
126
|
+
retries: 10
|
|
127
|
+
|
|
128
|
+
volumes:
|
|
129
|
+
storage:
|
|
130
|
+
postgres:
|
|
131
|
+
caddy_data:
|
|
132
|
+
caddy_config:
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Platform-neutral production stack for fazer.ai agents (Portainer / EasyPanel / plain `docker compose`
|
|
2
|
+
# on a VM). Coolify has its own file (docker-compose.coolify.yml, magic-vars); THIS file reads plain
|
|
3
|
+
# env vars from a .env you generate with `bun scripts/gen-onboarding-env.ts` (writes every secret/URL
|
|
4
|
+
# below, consistently). TLS + reverse proxy are EXTERNAL (Caddy/Traefik/Nginx Proxy Manager): this
|
|
5
|
+
# stack only publishes the app's HTTP port on the host — terminate TLS in front of it.
|
|
6
|
+
#
|
|
7
|
+
# TWO database roles by design (docs/deploy.md, load-bearing): the SUPERUSER (POSTGRES_USER, the
|
|
8
|
+
# postgres container's owner) runs db-bootstrap + migrations; the NON-superuser runtime role (encoded
|
|
9
|
+
# in DATABASE_URL, created by db-bootstrap from that URL) is what the server runs as, so Postgres RLS
|
|
10
|
+
# isolation holds. The boot guard hard-crashes if the runtime role is privileged.
|
|
11
|
+
#
|
|
12
|
+
# SINGLE replica by construction (the scheduler / debounce / webhook / alert workers + realtime
|
|
13
|
+
# pub/sub are single-process). Do NOT scale `agents` to >1 here. To add web replicas, run the
|
|
14
|
+
# extras with SCHEDULER_WORKER_ENABLED/WEBHOOK_WORKER_ENABLED/DEBOUNCE_WORKER_ENABLED/
|
|
15
|
+
# ALERT_WORKER_ENABLED=false and keep exactly one leader with them on (docs/deploy.md §4).
|
|
16
|
+
|
|
17
|
+
services:
|
|
18
|
+
agents:
|
|
19
|
+
# Edition-driven: Free (default, public image) vs Pro (set AGENTS_IMAGE to the private
|
|
20
|
+
# Harbor image, project `agents`, after `docker login` — hub: create_registry_credential, per-user).
|
|
21
|
+
# PLACEHOLDER: the public Free image is not published yet — swap `agents` once it exists.
|
|
22
|
+
image: ${AGENTS_IMAGE:-ghcr.io/fazer-ai/agents:latest}
|
|
23
|
+
pull_policy: always
|
|
24
|
+
restart: always
|
|
25
|
+
# NOTE: no `command` here on purpose — the image's default entrypoint already runs, in order:
|
|
26
|
+
# db-bootstrap.ts (idempotent runtime-role provisioning) → prisma migrate deploy → serve
|
|
27
|
+
# interpreted (bun src/index.ts). Overriding risks drifting from that contract.
|
|
28
|
+
environment:
|
|
29
|
+
- NODE_ENV=production
|
|
30
|
+
- PORT=3000
|
|
31
|
+
- PUBLIC_URL=${PUBLIC_URL}
|
|
32
|
+
- CDN_URL=${PUBLIC_URL}
|
|
33
|
+
- CORS_ORIGIN=${CORS_ORIGIN:-*}
|
|
34
|
+
- LOG_LEVEL=${LOG_LEVEL:-info}
|
|
35
|
+
- JWT_SECRET=${JWT_SECRET}
|
|
36
|
+
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
|
37
|
+
# Superuser/owner connection — migrations + bootstrap only. Its user/password MUST match the
|
|
38
|
+
# postgres container's POSTGRES_USER/POSTGRES_PASSWORD below.
|
|
39
|
+
- MIGRATION_DATABASE_URL=${MIGRATION_DATABASE_URL}
|
|
40
|
+
# Runtime role — NON-superuser, NON-bypassrls (db-bootstrap creates it from this URL). RLS holds.
|
|
41
|
+
- DATABASE_URL=${DATABASE_URL}
|
|
42
|
+
# Checkpointer pool — MUST be the runtime (non-superuser) role, never the migration URL.
|
|
43
|
+
- LANGGRAPH_DATABASE_URL=${LANGGRAPH_DATABASE_URL}
|
|
44
|
+
# Persist uploaded branding (logo/favicon) + generated quote PDFs on the `storage` volume
|
|
45
|
+
# (mounted below). The defaults (./data/*) live on the ephemeral container FS and vanish on
|
|
46
|
+
# every redeploy.
|
|
47
|
+
- BRANDING_STORAGE_DIR=/app/storage/branding
|
|
48
|
+
- QUOTES_STORAGE_DIR=/app/storage/quotes
|
|
49
|
+
ports:
|
|
50
|
+
# Host:container. Put a TLS-terminating reverse proxy in front; in untrusted networks bind to
|
|
51
|
+
# 127.0.0.1 (set APP_PORT=127.0.0.1:3000) or firewall the port.
|
|
52
|
+
- '${APP_PORT:-3000}:3000'
|
|
53
|
+
volumes:
|
|
54
|
+
- 'storage:/app/storage'
|
|
55
|
+
healthcheck:
|
|
56
|
+
test: ['CMD-SHELL', 'wget -qO- http://localhost:3000/api/health || exit 1']
|
|
57
|
+
interval: 20s
|
|
58
|
+
timeout: 10s
|
|
59
|
+
retries: 5
|
|
60
|
+
depends_on:
|
|
61
|
+
postgres:
|
|
62
|
+
condition: service_healthy
|
|
63
|
+
|
|
64
|
+
postgres:
|
|
65
|
+
# pgvector image (NOT plain postgres): the schema needs CREATE EXTENSION vector. pg17 matches the
|
|
66
|
+
# validated isolation spike. Not published to the host — only the app reaches it over the network.
|
|
67
|
+
image: 'pgvector/pgvector:pg17'
|
|
68
|
+
restart: always
|
|
69
|
+
environment:
|
|
70
|
+
- POSTGRES_DB=${DATABASE_NAME:-secretaria_v4_db}
|
|
71
|
+
- POSTGRES_USER=${POSTGRES_USER}
|
|
72
|
+
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
73
|
+
volumes:
|
|
74
|
+
- 'postgres:/var/lib/postgresql/data'
|
|
75
|
+
healthcheck:
|
|
76
|
+
test:
|
|
77
|
+
- CMD-SHELL
|
|
78
|
+
- 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}'
|
|
79
|
+
interval: 20s
|
|
80
|
+
timeout: 20s
|
|
81
|
+
retries: 10
|
|
82
|
+
|
|
83
|
+
volumes:
|
|
84
|
+
storage:
|
|
85
|
+
postgres:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Langfuse v3 self-hosted: env for the GENERIC flavor (docker-compose.yml).
|
|
2
|
+
# Coolify users do NOT need this file (magic vars are auto-generated); see docker-compose.coolify.yml.
|
|
3
|
+
#
|
|
4
|
+
# cp .env.example .env && fill every CHANGE_ME && docker compose up -d
|
|
5
|
+
#
|
|
6
|
+
# Generate strong secrets, e.g.:
|
|
7
|
+
# openssl rand -base64 32 # SALT, NEXTAUTH_SECRET, passwords
|
|
8
|
+
# openssl rand -hex 32 # ENCRYPTION_KEY (MUST be 64 hex chars / 256-bit)
|
|
9
|
+
# openssl rand -hex 16 # MinIO user/password are arbitrary, just keep them secret
|
|
10
|
+
|
|
11
|
+
# ── public URL (where the browser/SDK reach Langfuse; include scheme) ──
|
|
12
|
+
LANGFUSE_PUBLIC_URL=https://langfuse.example.com
|
|
13
|
+
LANGFUSE_PORT=3000
|
|
14
|
+
|
|
15
|
+
# ── app secrets ──
|
|
16
|
+
LANGFUSE_SALT=CHANGE_ME_base64_32
|
|
17
|
+
LANGFUSE_ENCRYPTION_KEY=CHANGE_ME_hex_64 # exactly 64 hex chars
|
|
18
|
+
LANGFUSE_NEXTAUTH_SECRET=CHANGE_ME_base64_32
|
|
19
|
+
|
|
20
|
+
# ── postgres ──
|
|
21
|
+
POSTGRES_DB=langfuse-db
|
|
22
|
+
POSTGRES_USER=langfuse
|
|
23
|
+
POSTGRES_PASSWORD=CHANGE_ME
|
|
24
|
+
|
|
25
|
+
# ── clickhouse ──
|
|
26
|
+
CLICKHOUSE_DB=default
|
|
27
|
+
CLICKHOUSE_USER=clickhouse
|
|
28
|
+
CLICKHOUSE_PASSWORD=CHANGE_ME
|
|
29
|
+
|
|
30
|
+
# ── redis ──
|
|
31
|
+
REDIS_PASSWORD=CHANGE_ME
|
|
32
|
+
|
|
33
|
+
# ── MinIO (blob storage: MANDATORY; the compose points all S3 families here) ──
|
|
34
|
+
MINIO_ROOT_USER=langfuse-minio
|
|
35
|
+
MINIO_ROOT_PASSWORD=CHANGE_ME
|
|
36
|
+
|
|
37
|
+
# ── optional: SMTP for invitations / password resets (blank = disabled) ──
|
|
38
|
+
LANGFUSE_EMAIL_FROM=admin@example.com
|
|
39
|
+
LANGFUSE_SMTP_URL=
|
|
40
|
+
|
|
41
|
+
# ── headless seed (agent-driven; see references/05-langfuse.md), Langfuse's recommended pattern ──
|
|
42
|
+
# The agent provisions EVERYTHING on the first boot: a user (org OWNER) + org + project + key pair, with
|
|
43
|
+
# AUTH_DISABLE_SIGNUP=true from the start, so signup is never open (Langfuse has no "first admin then
|
|
44
|
+
# close" gate; open signup would let anyone register on the exposed instance). The operator SIGNS IN at
|
|
45
|
+
# /auth/sign-in with the seeded email + password (and changes it); the agent already holds the keys and
|
|
46
|
+
# wires them into fazer.ai agents. One deploy, no open signup window.
|
|
47
|
+
# The USER requires the ORG (seed them together); Langfuse upserts by id, so a redeploy does not duplicate.
|
|
48
|
+
# Password: include a non-alphanumeric char (Langfuse's policy for signup/change; seed + login accept
|
|
49
|
+
# without, but be safe). Leave every LANGFUSE_INIT_* blank for a plain deploy with no seed.
|
|
50
|
+
AUTH_DISABLE_SIGNUP=true
|
|
51
|
+
LANGFUSE_INIT_USER_EMAIL=
|
|
52
|
+
LANGFUSE_INIT_USER_NAME=
|
|
53
|
+
LANGFUSE_INIT_USER_PASSWORD=
|
|
54
|
+
LANGFUSE_INIT_ORG_ID=
|
|
55
|
+
LANGFUSE_INIT_ORG_NAME=
|
|
56
|
+
LANGFUSE_INIT_PROJECT_ID=
|
|
57
|
+
LANGFUSE_INIT_PROJECT_NAME=
|
|
58
|
+
LANGFUSE_INIT_PROJECT_PUBLIC_KEY=
|
|
59
|
+
LANGFUSE_INIT_PROJECT_SECRET_KEY=
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Langfuse (self-hosted) for fazer.ai
|
|
2
|
+
|
|
3
|
+
Optional companion service for **agent tracing**. Langfuse is wired into fazer.ai agents **per-tenant
|
|
4
|
+
via a `langfuse` vault credential** (never a global env var); see `docs/deploy.md`
|
|
5
|
+
and `docs/graph.md`. This folder vendors a deploy that actually works, in the
|
|
6
|
+
project's three flavors (Coolify primary; Portainer and a generic "Outros" path, per
|
|
7
|
+
`docs/deploy.md`).
|
|
8
|
+
|
|
9
|
+
## Why this exists (the bug the one-click hides)
|
|
10
|
+
|
|
11
|
+
Langfuse v3 **requires S3-compatible blob storage for ingestion**: on every
|
|
12
|
+
`POST /api/public/ingestion`, the web container uploads the raw event JSON to S3 **before** queueing
|
|
13
|
+
it to ClickHouse. The official Langfuse one-click / Coolify template declares the `LANGFUSE_S3_*`
|
|
14
|
+
variable **names** but ships them **empty** and bundles **no** object-storage service.
|
|
15
|
+
|
|
16
|
+
Result, observed in production-like testing:
|
|
17
|
+
|
|
18
|
+
- `GET /api/public/projects` reads **Postgres only** → returns `200`. A naive "test connection"
|
|
19
|
+
(which is exactly what the Langfuse credential test does) **passes**, so the instance looks healthy.
|
|
20
|
+
- `POST /api/public/ingestion` tries to upload to S3 with empty creds → the AWS SDK throws
|
|
21
|
+
`Could not load credentials from any providers` → `Failed to upload events to blob storage,
|
|
22
|
+
aborting event processing` → **HTTP 500** (plain text, not JSON).
|
|
23
|
+
- The Langfuse client SDK then chokes parsing the non-JSON 500 body, and in its background flush
|
|
24
|
+
path the error is swallowed. Net effect: **traces silently never arrive**, with no client-side
|
|
25
|
+
error and nothing in the producing app's logs.
|
|
26
|
+
|
|
27
|
+
This is **not** a bug in fazer.ai agents (the trace handler builds, enqueues, and POSTs correctly) and
|
|
28
|
+
**not** a flush/runtime issue: it is a missing-storage deploy gap. These compose files bundle
|
|
29
|
+
**MinIO** and wire all three S3 families (`EVENT_UPLOAD`, `MEDIA_UPLOAD`, `BATCH_EXPORT`) to it.
|
|
30
|
+
|
|
31
|
+
## Files
|
|
32
|
+
|
|
33
|
+
| File | Use |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `docker-compose.coolify.yml` | **Coolify** (primary). Uses Coolify magic vars (`SERVICE_*`); secrets auto-generated. |
|
|
36
|
+
| `docker-compose.yml` | **Generic**: Portainer / EasyPanel / Dokploy / plain Docker. Secrets from `.env`. |
|
|
37
|
+
| `.env.example` | Template for the generic flavor. `cp .env.example .env`, fill every `CHANGE_ME`. |
|
|
38
|
+
|
|
39
|
+
Both composes are identical in topology, `langfuse` (web) + `langfuse-worker` + `postgres` +
|
|
40
|
+
`redis` + `clickhouse` + **`minio`**, and differ only in how secrets are supplied.
|
|
41
|
+
|
|
42
|
+
## Deploy
|
|
43
|
+
|
|
44
|
+
### Coolify (primary)
|
|
45
|
+
|
|
46
|
+
1. New Resource → **Docker Compose** → paste `docker-compose.coolify.yml`.
|
|
47
|
+
2. Set the service **Domain** to your Langfuse FQDN (Coolify fills `SERVICE_FQDN_LANGFUSE_3000` /
|
|
48
|
+
`SERVICE_URL_LANGFUSE`). Coolify generates `SERVICE_USER_MINIO`, `SERVICE_PASSWORD_MINIO`, and all
|
|
49
|
+
other `SERVICE_*` secrets automatically; **do not** set them by hand.
|
|
50
|
+
3. Deploy. Confirm `minio` becomes healthy, then verify ingestion (below).
|
|
51
|
+
|
|
52
|
+
> The blob-storage creds are the same Coolify magic vars used by the `minio` service, so the web,
|
|
53
|
+
> the worker, and MinIO always agree without you copying a secret around. That is the point of using
|
|
54
|
+
> magic vars here instead of hardcoded `LANGFUSE_S3_*` values.
|
|
55
|
+
|
|
56
|
+
### Portainer / EasyPanel / Dokploy / plain Docker (generic)
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
cp .env.example .env # fill every CHANGE_ME (see the openssl hints in the file)
|
|
60
|
+
docker compose up -d
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Portainer: paste `docker-compose.yml` as a Stack and provide the same variables via the Stack env
|
|
64
|
+
editor instead of a `.env` file. Put your own TLS-terminating reverse proxy in front of port 3000.
|
|
65
|
+
|
|
66
|
+
### Magic var ↔ generic env mapping
|
|
67
|
+
|
|
68
|
+
| Coolify magic var | Generic `.env` | Purpose |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `SERVICE_USER_MINIO` / `SERVICE_PASSWORD_MINIO` | `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` | MinIO creds = the S3 access key/secret |
|
|
71
|
+
| `SERVICE_USER_POSTGRES` / `SERVICE_PASSWORD_POSTGRES` | `POSTGRES_USER` / `POSTGRES_PASSWORD` | Postgres |
|
|
72
|
+
| `SERVICE_USER_CLICKHOUSE` / `SERVICE_PASSWORD_CLICKHOUSE` | `CLICKHOUSE_USER` / `CLICKHOUSE_PASSWORD` | ClickHouse |
|
|
73
|
+
| `SERVICE_PASSWORD_REDIS` | `REDIS_PASSWORD` | Redis |
|
|
74
|
+
| `SERVICE_PASSWORD_SALT` | `LANGFUSE_SALT` | Langfuse salt |
|
|
75
|
+
| `SERVICE_PASSWORD_64_LANGFUSE` | `LANGFUSE_ENCRYPTION_KEY` | 256-bit encryption key (64 hex) |
|
|
76
|
+
| `SERVICE_BASE64_NEXTAUTHSECRET` | `LANGFUSE_NEXTAUTH_SECRET` | NextAuth secret |
|
|
77
|
+
| `SERVICE_URL_LANGFUSE` | `LANGFUSE_PUBLIC_URL` | Public URL |
|
|
78
|
+
|
|
79
|
+
## Seed everything headless (agent-driven, one deploy)
|
|
80
|
+
|
|
81
|
+
The agent does **not** ask the user to copy keys out of the UI, and does **not** open signup. It
|
|
82
|
+
provisions everything on the first boot (Langfuse's own recommended headless-init pattern):
|
|
83
|
+
|
|
84
|
+
1. The agent generates a `pk-lf-…` / `sk-lf-…` key pair, an org id + name, a project id + name, and a
|
|
85
|
+
strong temporary password (include a non-alphanumeric char). The user email is the operator's, reused
|
|
86
|
+
from the Chatwoot admin so they have one login across tools.
|
|
87
|
+
2. The agent sets, on the service env (Coolify) or `.env` (generic), and deploys **once** with
|
|
88
|
+
`AUTH_DISABLE_SIGNUP=true` (the template default):
|
|
89
|
+
- `LANGFUSE_INIT_USER_EMAIL` / `_NAME` / `_PASSWORD`
|
|
90
|
+
- `LANGFUSE_INIT_ORG_ID` / `_NAME`
|
|
91
|
+
- `LANGFUSE_INIT_PROJECT_ID` / `_NAME` / `_PUBLIC_KEY` / `_SECRET_KEY`
|
|
92
|
+
|
|
93
|
+
On boot Langfuse creates the **user (org OWNER) + org + project + keys**. The user requires the org
|
|
94
|
+
(seed them together); it upserts by id, so a redeploy never duplicates.
|
|
95
|
+
3. The operator **signs in** at `/auth/sign-in` (never signs up) with the seeded email + the generated
|
|
96
|
+
password (the agent shows it) and changes it on first login. The seed is create-if-not-exists
|
|
97
|
+
(verified: the password change survives redeploys), so the operator's real password never passes
|
|
98
|
+
through the agent. The agent already holds the keys and wires them into fazer.ai agents (below).
|
|
99
|
+
|
|
100
|
+
Empirically validated with this template's compose: the `LANGFUSE_INIT_USER` becomes org `OWNER`, the
|
|
101
|
+
seeded user signs in (session shows `role: OWNER`), the seeded keys authenticate ingestion (`207`), and
|
|
102
|
+
signup returns `422 Sign up is disabled` from the start, with no open-signup window.
|
|
103
|
+
|
|
104
|
+
## Verify ingestion actually works
|
|
105
|
+
|
|
106
|
+
A green health check is **not** enough (it only proves Postgres). Confirm a trace round-trips:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
# 1) use the seeded pk-lf-…/sk-lf-… pair (or create one in the UI), then:
|
|
110
|
+
PK=pk-lf-...; SK=sk-lf-...; BASE=https://langfuse.example.com
|
|
111
|
+
|
|
112
|
+
# 2) POST a trace event, expect HTTP 207/200 (NOT 500):
|
|
113
|
+
curl -s -u "$PK:$SK" -H 'Content-Type: application/json' -X POST "$BASE/api/public/ingestion" \
|
|
114
|
+
-d '{"batch":[{"id":"t1","type":"trace-create","timestamp":"2026-01-01T00:00:00.000Z","body":{"id":"verify-1","name":"verify"}}]}' \
|
|
115
|
+
-w '\n[http %{http_code}]\n'
|
|
116
|
+
|
|
117
|
+
# 3) read it back (give the worker a few seconds to flush to ClickHouse):
|
|
118
|
+
curl -s -u "$PK:$SK" "$BASE/api/public/traces?limit=5"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A `500` here means blob storage is still misconfigured: check the `langfuse` (web) container logs
|
|
122
|
+
for `Failed to upload events to blob storage`.
|
|
123
|
+
|
|
124
|
+
## Wire into fazer.ai agents
|
|
125
|
+
|
|
126
|
+
Tracing is per-tenant. The agent uses the MCP **`langfuse_connect`** tool: it takes the public key +
|
|
127
|
+
secret key + base URL inline, creates the `langfuse` vault credential, and turns tracing on in one call
|
|
128
|
+
(`docs/mcp.md`). The equivalent by hand is a `langfuse` vault credential (`POST /v1/vault` with
|
|
129
|
+
`kind:"langfuse"`, `value:{publicKey, secretKey}`, `baseUrl:"https://langfuse.example.com"`) then
|
|
130
|
+
`PUT /v1/tenant-settings/langfuse {enabled:true, credentialRef:"vault:<id>"}`. The connection test on
|
|
131
|
+
the credential checks reachability + auth (Postgres path); the **ingestion** verification above is what
|
|
132
|
+
proves traces will land. See `references/05-langfuse.md`.
|