vibecarbon 0.1.7 → 0.2.1

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.
Files changed (109) hide show
  1. package/carbon/.env.example +3 -2
  2. package/carbon/DEVELOPMENT.md +18 -3
  3. package/carbon/PRODUCTION.md +1 -1
  4. package/carbon/content/docs/cli.mdx +3 -3
  5. package/carbon/content/docs/deployment.mdx +1 -1
  6. package/carbon/content/docs/development.mdx +1 -1
  7. package/carbon/content/docs/getting-started.mdx +0 -1
  8. package/carbon/docker-compose.yml +6 -1
  9. package/carbon/scripts/dev-init.js +1 -1
  10. package/carbon/scripts/generate-dev-configs.sh +5 -7
  11. package/carbon/scripts/validate-dev-configs.sh +5 -7
  12. package/carbon/src/client/components/TechStackSection.tsx +0 -68
  13. package/carbon/src/client/index.css +0 -14
  14. package/carbon/src/client/locales/de.json +0 -18
  15. package/carbon/src/client/locales/en.json +86 -36
  16. package/carbon/src/client/locales/es.json +0 -18
  17. package/carbon/src/client/locales/fr.json +0 -18
  18. package/carbon/src/client/locales/pt.json +0 -18
  19. package/carbon/src/client/pages/Home.tsx +0 -3
  20. package/package.json +4 -2
  21. package/services/index.json +29 -0
  22. package/services/metabase/compose/docker-compose.override.yml +14 -0
  23. package/services/metabase/compose/docker-compose.prod.yml +29 -0
  24. package/services/metabase/compose/docker-compose.yml +68 -0
  25. package/services/metabase/db/init.sh +29 -0
  26. package/services/metabase/docs/integration.md +191 -0
  27. package/services/metabase/k8s/deployment.yaml +88 -0
  28. package/services/metabase/k8s/ingressroute.yaml +22 -0
  29. package/services/metabase/k8s/kustomization.yaml +9 -0
  30. package/services/metabase/k8s/network-policy.yaml +74 -0
  31. package/services/metabase/k8s/pvc.yaml +12 -0
  32. package/services/metabase/k8s/service.yaml +15 -0
  33. package/services/metabase/manifest.json +34 -0
  34. package/services/metabase/scripts/setup.sh +152 -0
  35. package/services/n8n/compose/docker-compose.override.yml +14 -0
  36. package/services/n8n/compose/docker-compose.prod.yml +32 -0
  37. package/services/n8n/compose/docker-compose.yml +99 -0
  38. package/services/n8n/db/init.sh +25 -0
  39. package/services/n8n/docs/SSO.md +124 -0
  40. package/services/n8n/docs/integration.md +155 -0
  41. package/services/n8n/hooks/hooks.js +131 -0
  42. package/services/n8n/k8s/deployment.yaml +116 -0
  43. package/services/n8n/k8s/ingressroute.yaml +24 -0
  44. package/services/n8n/k8s/kustomization.yaml +9 -0
  45. package/services/n8n/k8s/network-policy.yaml +82 -0
  46. package/services/n8n/k8s/pvc.yaml +14 -0
  47. package/services/n8n/k8s/service.yaml +14 -0
  48. package/services/n8n/manifest.json +36 -0
  49. package/services/n8n/scripts/setup.sh +42 -0
  50. package/{carbon/docker-compose.observability.prod.yml → services/observability/compose/docker-compose.prod.yml} +8 -0
  51. package/services/observability/docs/integration.md +31 -0
  52. package/services/observability/k8s/grafana-deployment.yaml +107 -0
  53. package/services/observability/k8s/grafana-provisioning-configmap.yaml +65 -0
  54. package/services/observability/k8s/grafana-pvc.yaml +14 -0
  55. package/services/observability/k8s/grafana-service.yaml +16 -0
  56. package/services/observability/k8s/ingressroute.yaml +22 -0
  57. package/services/observability/k8s/kustomization.yaml +24 -0
  58. package/services/observability/k8s/loki-configmap.yaml +74 -0
  59. package/services/observability/k8s/loki-deployment.yaml +72 -0
  60. package/services/observability/k8s/loki-pvc.yaml +14 -0
  61. package/services/observability/k8s/loki-service.yaml +16 -0
  62. package/services/observability/k8s/network-policy.yaml +141 -0
  63. package/services/observability/k8s/prometheus-configmap.yaml +80 -0
  64. package/services/observability/k8s/prometheus-deployment.yaml +78 -0
  65. package/services/observability/k8s/prometheus-pvc.yaml +14 -0
  66. package/services/observability/k8s/prometheus-rbac.yaml +55 -0
  67. package/services/observability/k8s/prometheus-service.yaml +16 -0
  68. package/services/observability/manifest.json +28 -0
  69. package/services/redis/compose/docker-compose.prod.yml +10 -0
  70. package/services/redis/compose/docker-compose.yml +24 -0
  71. package/services/redis/docs/integration.md +148 -0
  72. package/services/redis/k8s/deployment.yaml +70 -0
  73. package/services/redis/k8s/kustomization.yaml +8 -0
  74. package/services/redis/k8s/network-policy.yaml +20 -0
  75. package/services/redis/k8s/pvc.yaml +12 -0
  76. package/services/redis/k8s/service.yaml +15 -0
  77. package/services/redis/manifest.json +30 -0
  78. package/services/s3/compose/docker-compose.s3-ha.yml +86 -0
  79. package/services/s3/compose/docker-compose.s3.yml +35 -0
  80. package/services/s3/manifest.json +87 -0
  81. package/src/add.js +57 -89
  82. package/src/cli.js +1 -1
  83. package/src/create.js +18 -87
  84. package/src/destroy.js +23 -1
  85. package/src/lib/deploy/bundle.js +4 -2
  86. package/src/lib/deploy/compose/ha.js +11 -8
  87. package/src/lib/deploy/compose/index.js +45 -17
  88. package/src/lib/deploy/image.js +10 -2
  89. package/src/lib/deploy/k8s/k3s.js +23 -4
  90. package/src/lib/deploy/orchestrator.js +13 -1
  91. package/src/lib/deploy/remote-build.js +29 -8
  92. package/src/remove.js +9 -6
  93. package/src/scale.js +14 -1
  94. package/carbon/content/docs/optional-services.mdx +0 -160
  95. package/carbon/src/client/components/MetricsStrip.tsx +0 -48
  96. /package/{carbon/docker-compose.observability.override.yml → services/observability/compose/docker-compose.override.yml} +0 -0
  97. /package/{carbon/docker-compose.observability.yml → services/observability/compose/docker-compose.yml} +0 -0
  98. /package/{carbon → services/observability}/volumes/grafana/dashboards/logs.json +0 -0
  99. /package/{carbon → services/observability}/volumes/grafana/dashboards/overview.json +0 -0
  100. /package/{carbon → services/observability}/volumes/grafana/dashboards/postgresql.json +0 -0
  101. /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/logs.json +0 -0
  102. /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/overview.json +0 -0
  103. /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/postgresql.json +0 -0
  104. /package/{carbon → services/observability}/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +0 -0
  105. /package/{carbon → services/observability}/volumes/grafana/provisioning/dashboards/dashboards.yml +0 -0
  106. /package/{carbon → services/observability}/volumes/grafana/provisioning/datasources/datasources.yml +0 -0
  107. /package/{carbon → services/observability}/volumes/loki/loki-config.yml +0 -0
  108. /package/{carbon → services/observability}/volumes/prometheus/prometheus.yml +0 -0
  109. /package/{carbon → services/observability}/volumes/promtail/promtail-config.yml +0 -0
@@ -0,0 +1,152 @@
1
+ #!/bin/bash
2
+ # Metabase Auto-Setup Script
3
+ # Automatically configures Metabase with admin user and app database connection
4
+ # Added via: vibecarbon add metabase
5
+
6
+ set -e
7
+
8
+ METABASE_URL="${METABASE_URL:-http://metabase:3000}"
9
+ MAX_RETRIES=60
10
+ RETRY_INTERVAL=5
11
+
12
+ # Admin credentials (from environment)
13
+ ADMIN_EMAIL="${METABASE_ADMIN_EMAIL:-admin@localhost}"
14
+ ADMIN_PASSWORD="${METABASE_ADMIN_PASSWORD}"
15
+ ADMIN_FIRST_NAME="${METABASE_ADMIN_FIRST_NAME:-Admin}"
16
+ ADMIN_LAST_NAME="${METABASE_ADMIN_LAST_NAME:-User}"
17
+
18
+ # App database connection details
19
+ APP_DB_HOST="${APP_DB_HOST:-db}"
20
+ APP_DB_PORT="${APP_DB_PORT:-5432}"
21
+ APP_DB_NAME="${APP_DB_NAME:-postgres}"
22
+ APP_DB_USER="${APP_DB_USER:-postgres}"
23
+ APP_DB_PASSWORD="${DB_PASSWORD}"
24
+
25
+ log() {
26
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >&2
27
+ }
28
+
29
+ wait_for_metabase() {
30
+ log "Waiting for Metabase to be ready..."
31
+ for i in $(seq 1 $MAX_RETRIES); do
32
+ if curl -sf "${METABASE_URL}/api/health" > /dev/null 2>&1; then
33
+ log "Metabase is healthy"
34
+ return 0
35
+ fi
36
+ log "Attempt $i/$MAX_RETRIES - Metabase not ready, waiting ${RETRY_INTERVAL}s..."
37
+ sleep $RETRY_INTERVAL
38
+ done
39
+ log "ERROR: Metabase failed to become healthy after $((MAX_RETRIES * RETRY_INTERVAL)) seconds"
40
+ exit 1
41
+ }
42
+
43
+ get_setup_token() {
44
+ curl -sf "${METABASE_URL}/api/session/properties" | jq -r '.["setup-token"] // empty'
45
+ }
46
+
47
+ check_setup_complete() {
48
+ local props
49
+ props=$(curl -sf "${METABASE_URL}/api/session/properties")
50
+ local setup_token
51
+ setup_token=$(echo "$props" | jq -r '.["setup-token"] // empty')
52
+ # Setup is truly complete only when there's no setup token left
53
+ [ -z "$setup_token" ]
54
+ }
55
+
56
+ run_initial_setup() {
57
+ log "Running initial Metabase setup..."
58
+
59
+ local setup_token
60
+ setup_token=$(get_setup_token)
61
+
62
+ if [ -z "$setup_token" ]; then
63
+ log "No setup token found — setup already complete."
64
+ return 1
65
+ fi
66
+
67
+ log "Got setup token, creating admin user and database connection..."
68
+
69
+ # Complete entire setup in one call: admin user + database + preferences.
70
+ # This properly consumes the setup token so Metabase won't show the wizard.
71
+ local payload
72
+ payload=$(cat <<EOF
73
+ {
74
+ "token": "${setup_token}",
75
+ "user": {
76
+ "email": "${ADMIN_EMAIL}",
77
+ "password": "${ADMIN_PASSWORD}",
78
+ "first_name": "${ADMIN_FIRST_NAME}",
79
+ "last_name": "${ADMIN_LAST_NAME}",
80
+ "site_name": "{{PROJECT_NAME}}"
81
+ },
82
+ "database": {
83
+ "engine": "postgres",
84
+ "name": "Application Data",
85
+ "details": {
86
+ "host": "${APP_DB_HOST}",
87
+ "port": ${APP_DB_PORT},
88
+ "dbname": "${APP_DB_NAME}",
89
+ "user": "${APP_DB_USER}",
90
+ "password": "${APP_DB_PASSWORD}",
91
+ "ssl": false
92
+ }
93
+ },
94
+ "prefs": {
95
+ "site_name": "{{PROJECT_NAME}}",
96
+ "site_locale": "en",
97
+ "allow_tracking": false
98
+ }
99
+ }
100
+ EOF
101
+ )
102
+
103
+ local response
104
+ response=$(curl -sf -X POST "${METABASE_URL}/api/setup" \
105
+ -H "Content-Type: application/json" \
106
+ -d "$payload")
107
+
108
+ if [ $? -eq 0 ]; then
109
+ log "Initial setup complete (admin user + database connection)"
110
+ echo "$response" | jq -r '.id // empty'
111
+ else
112
+ log "ERROR: Initial setup failed"
113
+ return 1
114
+ fi
115
+ }
116
+
117
+ main() {
118
+ log "Starting Metabase auto-setup..."
119
+
120
+ # Validate required environment variables
121
+ if [ -z "$ADMIN_PASSWORD" ]; then
122
+ log "ERROR: METABASE_ADMIN_PASSWORD is required"
123
+ exit 1
124
+ fi
125
+
126
+ if [ -z "$DB_PASSWORD" ]; then
127
+ log "ERROR: DB_PASSWORD is required"
128
+ exit 1
129
+ fi
130
+
131
+ # Wait for Metabase to be ready
132
+ wait_for_metabase
133
+
134
+ # Try to complete the full setup (consumes the setup token).
135
+ # The /api/setup endpoint creates admin user + database connection in one call.
136
+ if ! check_setup_complete; then
137
+ local session_id
138
+ session_id=$(run_initial_setup)
139
+
140
+ if [ -n "$session_id" ]; then
141
+ log "Metabase auto-setup complete!"
142
+ exit 0
143
+ fi
144
+
145
+ log "ERROR: Initial setup failed and setup token is still present"
146
+ exit 1
147
+ fi
148
+
149
+ log "Metabase setup already complete, nothing to do."
150
+ }
151
+
152
+ main "$@"
@@ -0,0 +1,14 @@
1
+ # Development overrides for n8n
2
+ # Use with: docker compose -f docker-compose.yml -f docker-compose.n8n.yml -f docker-compose.n8n.override.yml up -d
3
+
4
+ services:
5
+ n8n:
6
+ labels:
7
+ # Dev: No auth required (same as studio/traefik in local dev)
8
+ # Production uses ForwardAuth via docker-compose.n8n.yml labels
9
+ # Empty middlewares override removes ForwardAuth requirement
10
+ - "traefik.enable=true"
11
+ - "traefik.http.routers.n8n.rule=Host(`n8n.localhost`)"
12
+ - "traefik.http.routers.n8n.entrypoints=web"
13
+ - "traefik.http.routers.n8n.middlewares="
14
+ - "traefik.http.services.n8n.loadbalancer.server.port=5678"
@@ -0,0 +1,32 @@
1
+ # n8n Production Overrides
2
+ # Use with: docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.n8n.yml -f docker-compose.n8n.prod.yml up -d
3
+ #
4
+ # Adds:
5
+ # - SSL subdomain routing for n8n (n8n.yourdomain.com)
6
+ # - ForwardAuth (super_admin role required)
7
+ # - HTTP to HTTPS redirect
8
+
9
+ services:
10
+ n8n:
11
+ environment:
12
+ # Override URLs for production subdomain
13
+ - N8N_HOST=n8n.${DOMAIN}
14
+ - N8N_PROTOCOL=https
15
+ - WEBHOOK_URL=https://n8n.${DOMAIN}/
16
+ - N8N_EDITOR_BASE_URL=https://n8n.${DOMAIN}/
17
+ - N8N_SECURE_COOKIE=true
18
+ ports: [] # Reached via Traefik over the compose network; no host port in prod
19
+ labels:
20
+ # Production Traefik routing with SSL
21
+ - "traefik.enable=true"
22
+ # HTTPS router with super_admin auth + security headers
23
+ - "traefik.http.routers.n8n.rule=Host(`n8n.${DOMAIN}`)"
24
+ - "traefik.http.routers.n8n.entrypoints=websecure"
25
+ - "traefik.http.routers.n8n.tls=true"
26
+ - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
27
+ - "traefik.http.routers.n8n.middlewares=super-admin-auth@file,security-headers@file"
28
+ - "traefik.http.services.n8n.loadbalancer.server.port=5678"
29
+ # HTTP to HTTPS redirect
30
+ - "traefik.http.routers.n8n-http.rule=Host(`n8n.${DOMAIN}`)"
31
+ - "traefik.http.routers.n8n-http.entrypoints=web"
32
+ - "traefik.http.routers.n8n-http.middlewares=redirect-to-https"
@@ -0,0 +1,99 @@
1
+ # n8n Workflow Automation - Docker Compose Override
2
+ # Use with: docker compose -f docker-compose.yml -f docker-compose.n8n.yml up -d
3
+
4
+ services:
5
+ # Extend db service to add n8n database initialization
6
+ db:
7
+ volumes:
8
+ - ./volumes/db/n8n-init.sh:/docker-entrypoint-initdb.d/zz-n8n-init.sh:Z
9
+
10
+ n8n:
11
+ image: n8nio/n8n:latest
12
+ container_name: ${PROJECT_NAME}-n8n
13
+ restart: unless-stopped
14
+ ports:
15
+ - "5678:5678"
16
+ environment:
17
+ # n8n Configuration
18
+ - N8N_HOST=0.0.0.0
19
+ - N8N_PORT=5678
20
+ - N8N_PROTOCOL=http
21
+ - N8N_SECURE_COOKIE=false
22
+ - NODE_ENV=production
23
+ - WEBHOOK_URL=http://localhost:5678/
24
+ - GENERIC_TIMEZONE=UTC
25
+ # ForwardAuth SSO hook
26
+ - EXTERNAL_HOOK_FILES=/home/node/.n8n/hooks.js
27
+
28
+ # Database Configuration (n8n workflow storage)
29
+ # Uses dedicated 'n8n' user created by n8n-init.sh
30
+ - DB_TYPE=postgresdb
31
+ - DB_POSTGRESDB_HOST=db
32
+ - DB_POSTGRESDB_PORT=5432
33
+ - DB_POSTGRESDB_DATABASE=n8n
34
+ - DB_POSTGRESDB_USER=n8n
35
+ - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
36
+
37
+ # Supabase Integration (for workflows)
38
+ # Use these in your n8n workflows to connect to your app's backend
39
+ - SUPABASE_URL=http://kong:8000
40
+ - SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
41
+ - SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
42
+ - SUPABASE_DB_HOST=db
43
+ - SUPABASE_DB_PORT=5432
44
+ - SUPABASE_DB_NAME=postgres
45
+ - SUPABASE_DB_USER=supabase_admin
46
+ - SUPABASE_DB_PASSWORD=${POSTGRES_PASSWORD}
47
+
48
+ # Queue mode (requires Redis — added automatically with n8n)
49
+ - EXECUTIONS_MODE=queue
50
+ - QUEUE_BULL_REDIS_HOST=redis
51
+ - QUEUE_BULL_REDIS_PORT=6379
52
+ - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD:-}
53
+
54
+ # Metrics for Prometheus (optional)
55
+ - N8N_METRICS=true
56
+ - N8N_METRICS_PREFIX=n8n_
57
+ healthcheck:
58
+ test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz || exit 1"]
59
+ interval: 30s
60
+ timeout: 10s
61
+ retries: 3
62
+ start_period: 30s
63
+ volumes:
64
+ - n8n_data:/home/node/.n8n
65
+ - ./volumes/n8n/hooks.js:/home/node/.n8n/hooks.js:ro
66
+ depends_on:
67
+ db:
68
+ condition: service_healthy
69
+ networks:
70
+ - vibecarbon-network
71
+ labels:
72
+ # Traefik routing with ForwardAuth (super_admin only)
73
+ - "traefik.enable=true"
74
+ - "traefik.http.routers.n8n.rule=Host(`n8n.localhost`)"
75
+ - "traefik.http.routers.n8n.entrypoints=web"
76
+ - "traefik.http.routers.n8n.middlewares=super-admin-auth@file"
77
+ - "traefik.http.services.n8n.loadbalancer.server.port=5678"
78
+
79
+ n8n-setup:
80
+ image: alpine:latest
81
+ container_name: ${PROJECT_NAME}-n8n-setup
82
+ volumes:
83
+ - ./volumes/n8n/scripts:/scripts:ro
84
+ environment:
85
+ - N8N_ADMIN_EMAIL=${N8N_ADMIN_EMAIL:-${ADMIN_EMAIL}}
86
+ - N8N_DB_HOST=db
87
+ - N8N_DB_NAME=n8n
88
+ - N8N_DB_USER=n8n
89
+ - N8N_DB_PASSWORD=${POSTGRES_PASSWORD}
90
+ entrypoint: ["/bin/sh", "-c", "apk add --no-cache postgresql16-client && sh /scripts/setup.sh"]
91
+ depends_on:
92
+ db:
93
+ condition: service_healthy
94
+ networks:
95
+ - vibecarbon-network
96
+ restart: "no"
97
+
98
+ volumes:
99
+ n8n_data:
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # n8n Database Initialization
5
+ # Creates dedicated n8n user and database for workflow automation
6
+ # Password is injected from POSTGRES_PASSWORD env var at runtime (never hardcoded)
7
+
8
+ # Create n8n role if it doesn't exist (quoted heredoc — no variable expansion)
9
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<'EOSQL'
10
+ DO $$
11
+ BEGIN
12
+ IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'n8n') THEN
13
+ CREATE ROLE n8n WITH LOGIN;
14
+ END IF;
15
+ END
16
+ $$;
17
+ EOSQL
18
+
19
+ # Set password and create database (unquoted heredoc — POSTGRES_PASSWORD is expanded)
20
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<EOSQL
21
+ ALTER ROLE n8n WITH PASSWORD '${POSTGRES_PASSWORD}';
22
+ SELECT 'CREATE DATABASE n8n WITH OWNER n8n ENCODING ''UTF8'' TEMPLATE template0'
23
+ WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'n8n')\gexec
24
+ GRANT ALL PRIVILEGES ON DATABASE n8n TO n8n;
25
+ EOSQL
@@ -0,0 +1,124 @@
1
+ # n8n Single Sign-On via Traefik ForwardAuth
2
+
3
+ ## How it works
4
+
5
+ ### Production (real domains)
6
+
7
+ In production, the app and n8n share a parent domain (e.g., `app.example.com` and `n8n.example.com`). The Supabase auth cookie is set with `domain=.example.com`, so it's sent to all subdomains. The flow:
8
+
9
+ 1. User visits `n8n.example.com`
10
+ 2. Traefik applies `super-admin-auth@file` middleware (ForwardAuth)
11
+ 3. `verify-role.ts` reads the `sb-auth-token` cookie, validates the JWT, checks `super_admin` role
12
+ 4. On success, Traefik forwards `X-User-Email` header to n8n
13
+ 5. `hooks.js` middleware reads the header, finds/creates the n8n user, issues `n8n-auth` session cookie
14
+ 6. User sees the n8n editor — no login page
15
+
16
+ ### Development (localhost)
17
+
18
+ In dev, `localhost` and `n8n.localhost` are treated as **separate domains** by browsers (`.localhost` is a public suffix). Cookies set on `localhost` are NOT sent to `n8n.localhost`. This means ForwardAuth can't work — the auth cookie never reaches the verify-role endpoint.
19
+
20
+ Instead, n8n uses its **own built-in auth** in dev:
21
+
22
+ 1. `setup.sh` sidecar creates an owner user with a random password hash (stored in the n8n database)
23
+ 2. User visits `http://n8n.localhost` → sees n8n's login page
24
+ 3. Logs in with their admin email and the password configured at project creation
25
+
26
+ The SSO hook (`hooks.js`) is still loaded but has no effect since ForwardAuth headers are absent — it calls `next()` and falls through to n8n's normal auth.
27
+
28
+ ## Architecture
29
+
30
+ ```
31
+ Production:
32
+ Browser → Traefik → ForwardAuth (verify-role) → n8n (hooks.js sets cookie)
33
+ │ │
34
+ Reads sb-auth-token Reads X-User-Email
35
+ Sets X-User-* headers Issues n8n-auth cookie
36
+
37
+ Development:
38
+ Browser → Traefik → n8n (no ForwardAuth, native login)
39
+ ```
40
+
41
+ ## Components
42
+
43
+ | Component | File | Purpose |
44
+ |-----------|------|---------|
45
+ | SSO hook | `volumes/n8n/hooks.js` | Express middleware: reads `X-User-Email`, auto-creates user, issues session cookie |
46
+ | DB setup sidecar | `volumes/n8n/scripts/setup.sh` | Creates owner user in PostgreSQL so setup wizard is skipped |
47
+ | ForwardAuth config (prod) | `volumes/traefik/middlewares.yml` | Chains header stripping → role verification |
48
+ | ForwardAuth config (dev) | `volumes/traefik/middlewares.dev.yml` | Same, routes to `host.docker.internal:3000` |
49
+ | Verify-role endpoint | `src/server/routes/_internal/verify-role.ts` | Validates JWT, checks role, sets `X-User-*` headers |
50
+ | Compose (dev) | `docker-compose.n8n.yml` | No ForwardAuth middleware on n8n router |
51
+ | Compose (prod) | Production overlay adds `super-admin-auth@file` middleware label |
52
+
53
+ ## Key implementation details
54
+
55
+ ### hooks.js (n8n v2.x / Express 5)
56
+
57
+ - **Export format**: `{ n8n: { ready: [fn] } }` — two-level nested, NOT `{ 'n8n.ready': [fn] }`
58
+ - **Router access**: Express 5 uses `app.router.stack` (not `app._router.stack`)
59
+ - **Layer creation**: Must use `require('router/lib/layer')` to create proper Layer instances (plain objects cause `layer.match is not a function`)
60
+ - **User repository**: Available via `this.dbCollections.User` (provided by hook context)
61
+ - **Session cookies**: Use `Container.get(AuthService).issueCookie()` from `@n8n/di` (not `@n8n/api` which doesn't exist)
62
+ - **User role column**: n8n v2.x uses `roleSlug` (FK to `role` table), not `role`
63
+ - **User lookup**: Must include `relations: ['role']` for `issueCookie()` to work
64
+
65
+ ### setup.sh (n8n v2.x schema)
66
+
67
+ n8n v2.x auto-creates a skeleton owner row (no email) during migration. The setup script:
68
+
69
+ 1. Waits for n8n's `user` table to exist
70
+ 2. UPDATEs the skeleton owner to set email, name, bcrypt password, and `lastActiveAt`
71
+ 3. Falls back to INSERT if no skeleton owner exists
72
+ 4. Sets `userManagement.isInstanceOwnerSetUp = true` in the `settings` table
73
+
74
+ All three are required to skip the setup wizard:
75
+ - `hasInstanceOwner()` checks: `password IS NOT NULL` OR `lastActiveAt IS NOT NULL`
76
+ - `showSetupOnFirstLoad` checks: `!(await hasInstanceOwner())`
77
+ - Frontend also checks the `isInstanceOwnerSetUp` setting
78
+
79
+ ### DB password
80
+
81
+ The n8n compose uses `POSTGRES_PASSWORD` (the project's DB password) for the n8n DB connection. This matches what `n8n-init.sh` sets for the `n8n` PostgreSQL role. Do NOT use a separate `N8N_DB_PASSWORD` with a different default.
82
+
83
+ ## Troubleshooting
84
+
85
+ ### n8n crash loop: "Problem loading external hook file"
86
+
87
+ Check the export format. n8n's `loadHooks()` expects `{ resource: { operation: [fn] } }`. A flat key like `'n8n.ready'` causes `Spread syntax requires ...iterable[Symbol.iterator]` because it tries to spread a function.
88
+
89
+ ### n8n crash loop: "layer.match is not a function"
90
+
91
+ The hooks.js spliced a plain object into Express 5's router stack. Use `require('router/lib/layer')` to create proper Layer instances.
92
+
93
+ ### Setup wizard still appears
94
+
95
+ Check all three conditions:
96
+ ```bash
97
+ # 1. Owner has password or lastActiveAt
98
+ docker compose exec db psql -U supabase_admin -d n8n -c \
99
+ "SELECT email, password IS NOT NULL, \"lastActiveAt\" FROM \"user\" WHERE \"roleSlug\" = 'global:owner';"
100
+
101
+ # 2. Setting is true
102
+ docker compose exec db psql -U supabase_admin -d n8n -c \
103
+ "SELECT value FROM settings WHERE key = 'userManagement.isInstanceOwnerSetUp';"
104
+
105
+ # 3. REST API confirms
106
+ curl -s http://n8n.localhost/rest/settings | python3 -m json.tool | grep showSetup
107
+ ```
108
+
109
+ ### "password authentication failed for user n8n"
110
+
111
+ The n8n compose DB password doesn't match what `n8n-init.sh` set. Ensure both use `${POSTGRES_PASSWORD}`.
112
+
113
+ ### ForwardAuth returns 401 in production
114
+
115
+ 1. Check the `sb-auth-token` cookie is set with the correct domain (`.example.com`)
116
+ 2. Check `authRequestHeaders` includes `Cookie`, `Authorization`, and `Accept`
117
+ 3. Check the user has `super_admin` in `app_metadata.role`
118
+
119
+ ### SSO hook doesn't fire (no log output)
120
+
121
+ 1. Verify hooks.js is mounted: `docker exec <n8n> cat /home/node/.n8n/hooks.js`
122
+ 2. Check `EXTERNAL_HOOK_FILES` env var is set
123
+ 3. Check for `[hooks.js] ForwardAuth SSO middleware installed` in logs
124
+ 4. If middleware installed but not firing, check for stale `n8n-auth` cookie (hook skips when present)
@@ -0,0 +1,155 @@
1
+ # n8n Integration with Supabase
2
+
3
+ This guide shows how to connect n8n workflows to your Vibecarbon application's Supabase backend.
4
+
5
+ ## Overview
6
+
7
+ n8n is pre-configured with:
8
+ - Dedicated PostgreSQL database (`n8n`) for workflow storage
9
+ - Environment variables for easy Supabase integration
10
+ - Network access to all your app's services
11
+
12
+ ## Quick Start
13
+
14
+ ### 1. Access n8n
15
+
16
+ ```bash
17
+ # Start services (if not already running)
18
+ pnpm docker:up
19
+
20
+ # Open n8n
21
+ open http://n8n.localhost
22
+ ```
23
+
24
+ **Login:** Use your admin email and the password set during project creation. In development, n8n uses its own built-in authentication (ForwardAuth SSO only applies in production).
25
+
26
+ ### 2. Use Supabase in Workflows
27
+
28
+ n8n has the following environment variables available:
29
+
30
+ ```bash
31
+ SUPABASE_URL=http://kong:8000 # Internal Supabase API Gateway
32
+ SUPABASE_ANON_KEY=your_anon_key # Public API key
33
+ SUPABASE_SERVICE_ROLE_KEY=your_key # Admin API key
34
+ SUPABASE_DB_HOST=db # Direct database access
35
+ SUPABASE_DB_PORT=5432
36
+ SUPABASE_DB_NAME=postgres
37
+ SUPABASE_DB_USER=postgres
38
+ SUPABASE_DB_PASSWORD=your_password
39
+ ```
40
+
41
+ ## Example Workflows
42
+
43
+ ### Example 1: Query Supabase Database
44
+
45
+ Use the **Postgres** node:
46
+
47
+ ```
48
+ Connection Settings:
49
+ - Host: {{$env.SUPABASE_DB_HOST}}
50
+ - Port: {{$env.SUPABASE_DB_PORT}}
51
+ - Database: {{$env.SUPABASE_DB_NAME}}
52
+ - User: {{$env.SUPABASE_DB_USER}}
53
+ - Password: {{$env.SUPABASE_DB_PASSWORD}}
54
+
55
+ Query:
56
+ SELECT * FROM users WHERE created_at > NOW() - INTERVAL '1 day';
57
+ ```
58
+
59
+ ### Example 2: Call Supabase REST API
60
+
61
+ Use the **HTTP Request** node:
62
+
63
+ ```
64
+ Method: POST
65
+ URL: {{$env.SUPABASE_URL}}/rest/v1/todos
66
+
67
+ Headers:
68
+ - apikey: {{$env.SUPABASE_SERVICE_ROLE_KEY}}
69
+ - Authorization: Bearer {{$env.SUPABASE_SERVICE_ROLE_KEY}}
70
+ - Content-Type: application/json
71
+
72
+ Body (JSON):
73
+ {
74
+ "title": "New todo from n8n",
75
+ "completed": false
76
+ }
77
+ ```
78
+
79
+ ### Example 3: Listen to Supabase Webhooks
80
+
81
+ 1. Create a **Webhook** node in n8n
82
+ 2. Copy the webhook URL (e.g., `http://localhost:5678/webhook/my-webhook`)
83
+ 3. In Supabase (or your app), configure a webhook to call this URL
84
+
85
+ ### Example 4: Trigger on Database Changes
86
+
87
+ Use **Postgres Trigger** node:
88
+
89
+ ```
90
+ Connection Settings: (use environment variables as above)
91
+
92
+ Trigger: On Row Insert
93
+ Table: users
94
+
95
+ Returns:
96
+ - NEW row data
97
+ ```
98
+
99
+ ## Common Use Cases
100
+
101
+ ### User Onboarding
102
+
103
+ ```
104
+ Trigger: Postgres Trigger (users table insert)
105
+ Action 1: Send welcome email (Email node)
106
+ Action 2: Create user profile (HTTP Request to your API)
107
+ Action 3: Add to analytics (HTTP Request to analytics service)
108
+ ```
109
+
110
+ ### Scheduled Reports
111
+
112
+ ```
113
+ Trigger: Cron (daily at 9 AM)
114
+ Action 1: Query database for metrics (Postgres node)
115
+ Action 2: Generate report (Code node)
116
+ Action 3: Send email (Email node)
117
+ ```
118
+
119
+ ### API Integration
120
+
121
+ ```
122
+ Trigger: Webhook (from external service)
123
+ Action 1: Transform data (Code node)
124
+ Action 2: Insert into Supabase (HTTP Request or Postgres node)
125
+ Action 3: Notify users (Push notification)
126
+ ```
127
+
128
+ ## Best Practices
129
+
130
+ ### 1. Use Environment Variables
131
+
132
+ Instead of hardcoding credentials, always use:
133
+ ```
134
+ {{$env.SUPABASE_URL}}
135
+ {{$env.SUPABASE_SERVICE_ROLE_KEY}}
136
+ ```
137
+
138
+ ### 2. Error Handling
139
+
140
+ Add **Error Trigger** nodes to handle failures gracefully.
141
+
142
+ ### 3. Testing
143
+
144
+ Use the **Manual Trigger** node during development.
145
+
146
+ ### 4. Security
147
+
148
+ - Use `SUPABASE_SERVICE_ROLE_KEY` only for admin operations
149
+ - Use `SUPABASE_ANON_KEY` for public operations
150
+ - Never expose service role key in client-side workflows
151
+
152
+ ## Learn More
153
+
154
+ - [n8n Documentation](https://docs.n8n.io/)
155
+ - [Supabase API Reference](https://supabase.com/docs/reference/javascript/introduction)