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.
- package/carbon/.env.example +3 -2
- package/carbon/DEVELOPMENT.md +18 -3
- package/carbon/PRODUCTION.md +1 -1
- package/carbon/content/docs/cli.mdx +3 -3
- package/carbon/content/docs/deployment.mdx +1 -1
- package/carbon/content/docs/development.mdx +1 -1
- package/carbon/content/docs/getting-started.mdx +0 -1
- package/carbon/docker-compose.yml +6 -1
- package/carbon/scripts/dev-init.js +1 -1
- package/carbon/scripts/generate-dev-configs.sh +5 -7
- package/carbon/scripts/validate-dev-configs.sh +5 -7
- package/carbon/src/client/components/TechStackSection.tsx +0 -68
- package/carbon/src/client/index.css +0 -14
- package/carbon/src/client/locales/de.json +0 -18
- package/carbon/src/client/locales/en.json +86 -36
- package/carbon/src/client/locales/es.json +0 -18
- package/carbon/src/client/locales/fr.json +0 -18
- package/carbon/src/client/locales/pt.json +0 -18
- package/carbon/src/client/pages/Home.tsx +0 -3
- package/package.json +4 -2
- package/services/index.json +29 -0
- package/services/metabase/compose/docker-compose.override.yml +14 -0
- package/services/metabase/compose/docker-compose.prod.yml +29 -0
- package/services/metabase/compose/docker-compose.yml +68 -0
- package/services/metabase/db/init.sh +29 -0
- package/services/metabase/docs/integration.md +191 -0
- package/services/metabase/k8s/deployment.yaml +88 -0
- package/services/metabase/k8s/ingressroute.yaml +22 -0
- package/services/metabase/k8s/kustomization.yaml +9 -0
- package/services/metabase/k8s/network-policy.yaml +74 -0
- package/services/metabase/k8s/pvc.yaml +12 -0
- package/services/metabase/k8s/service.yaml +15 -0
- package/services/metabase/manifest.json +34 -0
- package/services/metabase/scripts/setup.sh +152 -0
- package/services/n8n/compose/docker-compose.override.yml +14 -0
- package/services/n8n/compose/docker-compose.prod.yml +32 -0
- package/services/n8n/compose/docker-compose.yml +99 -0
- package/services/n8n/db/init.sh +25 -0
- package/services/n8n/docs/SSO.md +124 -0
- package/services/n8n/docs/integration.md +155 -0
- package/services/n8n/hooks/hooks.js +131 -0
- package/services/n8n/k8s/deployment.yaml +116 -0
- package/services/n8n/k8s/ingressroute.yaml +24 -0
- package/services/n8n/k8s/kustomization.yaml +9 -0
- package/services/n8n/k8s/network-policy.yaml +82 -0
- package/services/n8n/k8s/pvc.yaml +14 -0
- package/services/n8n/k8s/service.yaml +14 -0
- package/services/n8n/manifest.json +36 -0
- package/services/n8n/scripts/setup.sh +42 -0
- package/{carbon/docker-compose.observability.prod.yml → services/observability/compose/docker-compose.prod.yml} +8 -0
- package/services/observability/docs/integration.md +31 -0
- package/services/observability/k8s/grafana-deployment.yaml +107 -0
- package/services/observability/k8s/grafana-provisioning-configmap.yaml +65 -0
- package/services/observability/k8s/grafana-pvc.yaml +14 -0
- package/services/observability/k8s/grafana-service.yaml +16 -0
- package/services/observability/k8s/ingressroute.yaml +22 -0
- package/services/observability/k8s/kustomization.yaml +24 -0
- package/services/observability/k8s/loki-configmap.yaml +74 -0
- package/services/observability/k8s/loki-deployment.yaml +72 -0
- package/services/observability/k8s/loki-pvc.yaml +14 -0
- package/services/observability/k8s/loki-service.yaml +16 -0
- package/services/observability/k8s/network-policy.yaml +141 -0
- package/services/observability/k8s/prometheus-configmap.yaml +80 -0
- package/services/observability/k8s/prometheus-deployment.yaml +78 -0
- package/services/observability/k8s/prometheus-pvc.yaml +14 -0
- package/services/observability/k8s/prometheus-rbac.yaml +55 -0
- package/services/observability/k8s/prometheus-service.yaml +16 -0
- package/services/observability/manifest.json +28 -0
- package/services/redis/compose/docker-compose.prod.yml +10 -0
- package/services/redis/compose/docker-compose.yml +24 -0
- package/services/redis/docs/integration.md +148 -0
- package/services/redis/k8s/deployment.yaml +70 -0
- package/services/redis/k8s/kustomization.yaml +8 -0
- package/services/redis/k8s/network-policy.yaml +20 -0
- package/services/redis/k8s/pvc.yaml +12 -0
- package/services/redis/k8s/service.yaml +15 -0
- package/services/redis/manifest.json +30 -0
- package/services/s3/compose/docker-compose.s3-ha.yml +86 -0
- package/services/s3/compose/docker-compose.s3.yml +35 -0
- package/services/s3/manifest.json +87 -0
- package/src/add.js +57 -89
- package/src/cli.js +1 -1
- package/src/create.js +18 -87
- package/src/destroy.js +23 -1
- package/src/lib/deploy/bundle.js +4 -2
- package/src/lib/deploy/compose/ha.js +11 -8
- package/src/lib/deploy/compose/index.js +45 -17
- package/src/lib/deploy/image.js +10 -2
- package/src/lib/deploy/k8s/k3s.js +23 -4
- package/src/lib/deploy/orchestrator.js +13 -1
- package/src/lib/deploy/remote-build.js +29 -8
- package/src/remove.js +9 -6
- package/src/scale.js +14 -1
- package/carbon/content/docs/optional-services.mdx +0 -160
- package/carbon/src/client/components/MetricsStrip.tsx +0 -48
- /package/{carbon/docker-compose.observability.override.yml → services/observability/compose/docker-compose.override.yml} +0 -0
- /package/{carbon/docker-compose.observability.yml → services/observability/compose/docker-compose.yml} +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards/logs.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards/overview.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards/postgresql.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/logs.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/overview.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/postgresql.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +0 -0
- /package/{carbon → services/observability}/volumes/grafana/provisioning/dashboards/dashboards.yml +0 -0
- /package/{carbon → services/observability}/volumes/grafana/provisioning/datasources/datasources.yml +0 -0
- /package/{carbon → services/observability}/volumes/loki/loki-config.yml +0 -0
- /package/{carbon → services/observability}/volumes/prometheus/prometheus.yml +0 -0
- /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)
|