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,131 @@
1
+ /**
2
+ * n8n ForwardAuth SSO Hook
3
+ *
4
+ * Reads X-User-Email set by Traefik ForwardAuth, looks up or auto-creates
5
+ * the n8n user, and issues a session cookie — bypassing n8n's login page.
6
+ *
7
+ * When ForwardAuth headers are absent (dev mode without Traefik, or direct
8
+ * container access), falls through to n8n's normal login flow.
9
+ *
10
+ * Loaded via EXTERNAL_HOOK_FILES environment variable.
11
+ */
12
+
13
+ const SKIP_PATHS = ['/healthz', '/webhook', '/assets', '/rest/oauth2-credential'];
14
+
15
+ function shouldSkip(url) {
16
+ return SKIP_PATHS.some((p) => url.startsWith(p));
17
+ }
18
+
19
+ module.exports = {
20
+ n8n: {
21
+ ready: [
22
+ async function (server) {
23
+ const userRepo = this.dbCollections?.User;
24
+ if (!userRepo) {
25
+ console.warn('[hooks.js] User repository not available — SSO hook disabled');
26
+ return;
27
+ }
28
+
29
+ // Get AuthService from DI container for issueCookie
30
+ let authService;
31
+ try {
32
+ const { Container } = require('@n8n/di');
33
+ const { AuthService } = require('/usr/local/lib/node_modules/n8n/dist/auth/auth.service');
34
+ authService = Container.get(AuthService);
35
+ } catch (err) {
36
+ console.warn('[hooks.js] Could not resolve AuthService:', err.message);
37
+ }
38
+
39
+ const app = server.app;
40
+ if (!app) {
41
+ console.warn('[hooks.js] server.app not available');
42
+ return;
43
+ }
44
+
45
+ // Express 5 uses app.router.stack, Express 4 uses app._router.stack
46
+ const layers = app.router?.stack ?? app._router?.stack;
47
+ if (!layers) {
48
+ console.warn('[hooks.js] Could not access Express router stack — SSO hook disabled');
49
+ return;
50
+ }
51
+
52
+ let cookieParserIdx = -1;
53
+ for (let i = 0; i < layers.length; i++) {
54
+ if (layers[i].name === 'cookieParser') {
55
+ cookieParserIdx = i;
56
+ break;
57
+ }
58
+ }
59
+
60
+ const middleware = async (req, res, next) => {
61
+ try {
62
+ if (shouldSkip(req.url)) return next();
63
+
64
+ // Skip if session already exists
65
+ if (req.cookies?.['n8n-auth']) return next();
66
+
67
+ const email = req.headers['x-user-email'];
68
+ if (!email) return next(); // No ForwardAuth headers — normal login
69
+
70
+ let user = await userRepo.findOne({
71
+ where: { email },
72
+ relations: ['role'],
73
+ });
74
+
75
+ if (!user) {
76
+ // Auto-create user with owner role and random password
77
+ const crypto = require('node:crypto');
78
+ const { hashSync } = require('bcryptjs');
79
+ const randomPassword = crypto.randomBytes(32).toString('hex');
80
+
81
+ user = userRepo.create({
82
+ email,
83
+ firstName: email.split('@')[0],
84
+ lastName: '',
85
+ password: hashSync(randomPassword, 10),
86
+ roleSlug: 'global:owner',
87
+ });
88
+ user = await userRepo.save(user);
89
+ // Reload with role relation
90
+ user = await userRepo.findOne({
91
+ where: { email },
92
+ relations: ['role'],
93
+ });
94
+ }
95
+
96
+ if (!user) return next();
97
+
98
+ // Issue session cookie via AuthService
99
+ if (authService) {
100
+ try {
101
+ authService.issueCookie(res, user, false, undefined);
102
+ } catch (cookieErr) {
103
+ console.error('[hooks.js] SSO: issueCookie failed:', cookieErr.message);
104
+ }
105
+ }
106
+
107
+ return next();
108
+ } catch (err) {
109
+ console.error('[hooks.js] ForwardAuth SSO error:', err.message);
110
+ return next();
111
+ }
112
+ };
113
+
114
+ // Inject middleware after cookie-parser so req.cookies is available.
115
+ // Must create a proper Layer instance (Express 5 router calls layer.match()).
116
+ if (cookieParserIdx >= 0) {
117
+ try {
118
+ const Layer = require('router/lib/layer');
119
+ const layer = new Layer('/', { end: false }, middleware);
120
+ layers.splice(cookieParserIdx + 1, 0, layer);
121
+ console.log('[hooks.js] ForwardAuth SSO middleware installed');
122
+ } catch (err) {
123
+ console.warn('[hooks.js] Could not create router Layer:', err.message);
124
+ }
125
+ } else {
126
+ console.warn('[hooks.js] cookie-parser not found — SSO hook disabled');
127
+ }
128
+ },
129
+ ],
130
+ },
131
+ };
@@ -0,0 +1,116 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: n8n
5
+ labels:
6
+ app: vibecarbon-n8n
7
+ component: automation
8
+ spec:
9
+ replicas: 1
10
+ selector:
11
+ matchLabels:
12
+ app: vibecarbon-n8n
13
+ template:
14
+ metadata:
15
+ labels:
16
+ app: vibecarbon-n8n
17
+ component: automation
18
+ spec:
19
+ securityContext:
20
+ fsGroup: 1000
21
+ seccompProfile:
22
+ type: RuntimeDefault
23
+ containers:
24
+ - name: n8n
25
+ image: n8nio/n8n:latest
26
+ imagePullPolicy: IfNotPresent
27
+ ports:
28
+ - containerPort: 5678
29
+ name: http
30
+ env:
31
+ - name: N8N_BASIC_AUTH_ACTIVE
32
+ value: 'false'
33
+ - name: N8N_HOST
34
+ value: 0.0.0.0
35
+ - name: N8N_PORT
36
+ value: '5678'
37
+ - name: N8N_PROTOCOL
38
+ value: http
39
+ - name: N8N_PATH
40
+ value: /admin/n8n/
41
+ - name: NODE_ENV
42
+ value: production
43
+ - name: WEBHOOK_URL
44
+ valueFrom:
45
+ configMapKeyRef:
46
+ name: vibecarbon-config
47
+ key: SITE_URL
48
+ - name: GENERIC_TIMEZONE
49
+ value: UTC
50
+ - name: DB_TYPE
51
+ value: postgresdb
52
+ - name: DB_POSTGRESDB_HOST
53
+ value: postgres
54
+ - name: DB_POSTGRESDB_PORT
55
+ value: '5432'
56
+ - name: DB_POSTGRESDB_DATABASE
57
+ value: n8n
58
+ - name: DB_POSTGRESDB_USER
59
+ value: n8n
60
+ - name: DB_POSTGRESDB_PASSWORD
61
+ valueFrom:
62
+ secretKeyRef:
63
+ name: vibecarbon-secrets
64
+ key: DB_PASSWORD
65
+ - name: SUPABASE_URL
66
+ value: http://kong:8000
67
+ - name: SUPABASE_ANON_KEY
68
+ valueFrom:
69
+ secretKeyRef:
70
+ name: vibecarbon-secrets
71
+ key: ANON_KEY
72
+ - name: SUPABASE_SERVICE_ROLE_KEY
73
+ valueFrom:
74
+ secretKeyRef:
75
+ name: vibecarbon-secrets
76
+ key: SERVICE_ROLE_KEY
77
+ - name: N8N_SECURE_COOKIE
78
+ value: 'true'
79
+ volumeMounts:
80
+ - name: n8n-data
81
+ mountPath: /home/node/.n8n
82
+ - name: tmp-vol-0
83
+ mountPath: /tmp
84
+ resources:
85
+ requests:
86
+ cpu: 100m
87
+ memory: 128Mi
88
+ limits:
89
+ cpu: 1000m
90
+ memory: 1Gi
91
+ livenessProbe:
92
+ httpGet:
93
+ path: /healthz
94
+ port: http
95
+ initialDelaySeconds: 30
96
+ periodSeconds: 10
97
+ timeoutSeconds: 5
98
+ failureThreshold: 3
99
+ readinessProbe:
100
+ httpGet:
101
+ path: /healthz
102
+ port: http
103
+ initialDelaySeconds: 10
104
+ periodSeconds: 5
105
+ timeoutSeconds: 3
106
+ failureThreshold: 3
107
+ securityContext:
108
+ allowPrivilegeEscalation: false
109
+ readOnlyRootFilesystem: true
110
+ runAsNonRoot: true
111
+ volumes:
112
+ - name: n8n-data
113
+ persistentVolumeClaim:
114
+ claimName: n8n-data
115
+ - name: tmp-vol-0
116
+ emptyDir: {}
@@ -0,0 +1,24 @@
1
+ apiVersion: traefik.io/v1alpha1
2
+ kind: IngressRoute
3
+ metadata:
4
+ name: vibecarbon-n8n
5
+ namespace: vibecarbon
6
+ spec:
7
+ entryPoints:
8
+ - websecure
9
+ routes:
10
+ # N8N_PATH=/admin/n8n/ makes n8n generate HTML with /admin/n8n/assets/... paths.
11
+ # strip-prefix removes /admin/n8n before forwarding, so n8n serves assets at /assets/...
12
+ # and the REST API at /rest/... — exactly what the browser requests after stripping.
13
+ - match: PathPrefix(`/admin/n8n`)
14
+ kind: Rule
15
+ priority: 20
16
+ services:
17
+ - name: n8n
18
+ port: 5678
19
+ middlewares:
20
+ - name: admin-auth
21
+ - name: security-headers
22
+ - name: strip-admin-n8n-prefix
23
+ tls:
24
+ secretName: vibecarbon-tls
@@ -0,0 +1,9 @@
1
+ apiVersion: kustomize.config.k8s.io/v1beta1
2
+ kind: Kustomization
3
+
4
+ resources:
5
+ - deployment.yaml
6
+ - service.yaml
7
+ - pvc.yaml
8
+ - network-policy.yaml
9
+ - ingressroute.yaml
@@ -0,0 +1,82 @@
1
+ ---
2
+ # Network policy for n8n automation service
3
+ apiVersion: networking.k8s.io/v1
4
+ kind: NetworkPolicy
5
+ metadata:
6
+ name: n8n-policy
7
+ namespace: vibecarbon
8
+ spec:
9
+ podSelector:
10
+ matchLabels:
11
+ app: vibecarbon-n8n
12
+ policyTypes:
13
+ - Ingress
14
+ - Egress
15
+ ingress:
16
+ - from:
17
+ - podSelector:
18
+ matchLabels:
19
+ app: vibecarbon-traefik
20
+ - podSelector:
21
+ matchLabels:
22
+ app: vibecarbon-app
23
+ ports:
24
+ - protocol: TCP
25
+ port: 5678
26
+ egress:
27
+ # DNS resolution
28
+ - ports:
29
+ - protocol: UDP
30
+ port: 53
31
+ - protocol: TCP
32
+ port: 53
33
+ # PostgreSQL
34
+ - to:
35
+ - podSelector:
36
+ matchLabels:
37
+ app: vibecarbon-postgres
38
+ ports:
39
+ - protocol: TCP
40
+ port: 5432
41
+ # Kong (Supabase API gateway)
42
+ - to:
43
+ - podSelector:
44
+ matchLabels:
45
+ app: vibecarbon-kong
46
+ ports:
47
+ - protocol: TCP
48
+ port: 8000
49
+ # External HTTP/HTTPS (for webhook calls, npm packages, etc.)
50
+ - to:
51
+ - ipBlock:
52
+ cidr: 0.0.0.0/0
53
+ except:
54
+ - 10.0.0.0/8
55
+ - 172.16.0.0/12
56
+ - 192.168.0.0/16
57
+ ports:
58
+ - protocol: TCP
59
+ port: 443
60
+ - protocol: TCP
61
+ port: 80
62
+ ---
63
+ # Allow n8n to connect to postgres
64
+ apiVersion: networking.k8s.io/v1
65
+ kind: NetworkPolicy
66
+ metadata:
67
+ name: n8n-database-ingress
68
+ namespace: vibecarbon
69
+ spec:
70
+ podSelector:
71
+ matchLabels:
72
+ app: vibecarbon-postgres
73
+ policyTypes:
74
+ - Ingress
75
+ ingress:
76
+ - from:
77
+ - podSelector:
78
+ matchLabels:
79
+ app: vibecarbon-n8n
80
+ ports:
81
+ - protocol: TCP
82
+ port: 5432
@@ -0,0 +1,14 @@
1
+ apiVersion: v1
2
+ kind: PersistentVolumeClaim
3
+ metadata:
4
+ name: n8n-data
5
+ labels:
6
+ app: vibecarbon-n8n
7
+ component: automation
8
+ spec:
9
+ accessModes:
10
+ - ReadWriteOnce
11
+ storageClassName: hcloud-volumes
12
+ resources:
13
+ requests:
14
+ storage: 5Gi
@@ -0,0 +1,14 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: n8n
5
+ labels:
6
+ app: vibecarbon-n8n
7
+ component: automation
8
+ spec:
9
+ selector:
10
+ app: vibecarbon-n8n
11
+ ports:
12
+ - port: 5678
13
+ targetPort: 5678
14
+ name: http
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "n8n",
3
+ "description": "Workflow automation platform",
4
+ "version": "1.0.0",
5
+ "port": 5678,
6
+ "url": "http://n8n.localhost",
7
+
8
+ "files": {
9
+ "compose/docker-compose.yml": "docker-compose.n8n.yml",
10
+ "compose/docker-compose.prod.yml": "docker-compose.n8n.prod.yml",
11
+ "compose/docker-compose.override.yml": "docker-compose.n8n.override.yml",
12
+ "k8s/": "k8s/base/n8n/",
13
+ "db/init.sh": "volumes/db/n8n-init.sh",
14
+ "scripts/setup.sh": "volumes/n8n/scripts/setup.sh",
15
+ "hooks/hooks.js": "volumes/n8n/hooks.js",
16
+ "docs/integration.md": "n8n-integration.md"
17
+ },
18
+
19
+ "envVars": {
20
+ "N8N_WEBHOOK_URL": { "value": "http://localhost:5678" },
21
+ "N8N_API_KEY": { "value": "" },
22
+ "N8N_ADMIN_EMAIL": { "from": "ADMIN_EMAIL", "default": "admin@localhost" }
23
+ },
24
+
25
+ "credentials": {
26
+ "title": "n8n",
27
+ "fields": [
28
+ { "label": "URL", "value": "http://n8n.localhost" },
29
+ { "label": "Auth", "value": "Traefik ForwardAuth SSO (super_admin)" }
30
+ ]
31
+ },
32
+
33
+ "kustomization": {
34
+ "entry": "n8n/"
35
+ }
36
+ }
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ # n8n DB-Only Setup Script
3
+ # Inserts the owner user directly into PostgreSQL so n8n's setup wizard is skipped.
4
+ # Auth is handled by ForwardAuth + hooks.js — the password here is a placeholder.
5
+ # Added via: vibecarbon add n8n
6
+
7
+ set -e
8
+
9
+ DB_HOST="${N8N_DB_HOST:-db}"
10
+ DB_NAME="${N8N_DB_NAME:-n8n}"
11
+ DB_USER="${N8N_DB_USER:-n8n}"
12
+ DB_PASSWORD="${N8N_DB_PASSWORD}"
13
+ ADMIN_EMAIL="${N8N_ADMIN_EMAIL:-admin@localhost}"
14
+
15
+ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >&2; }
16
+
17
+ if [ -z "$DB_PASSWORD" ]; then
18
+ log "ERROR: N8N_DB_PASSWORD is required"
19
+ exit 1
20
+ fi
21
+
22
+ # Wait for n8n's user table to exist (created by n8n's own migrations)
23
+ log "Waiting for n8n user table..."
24
+ for i in $(seq 1 60); do
25
+ if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -tAc \
26
+ "SELECT 1 FROM information_schema.tables WHERE table_name='user'" 2>/dev/null | grep -q 1; then
27
+ break
28
+ fi
29
+ [ "$i" -eq 60 ] && { log "ERROR: user table not found after 5 minutes"; exit 1; }
30
+ sleep 5
31
+ done
32
+
33
+ # Insert owner with random password placeholder and global:owner role.
34
+ # ON CONFLICT DO NOTHING — idempotent across restarts.
35
+ RANDOM_HASH=$(head -c 32 /dev/urandom | base64)
36
+ PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -q <<SQL
37
+ INSERT INTO "user" (email, "firstName", "lastName", password, role)
38
+ VALUES ('${ADMIN_EMAIL}', 'Admin', 'User', '${RANDOM_HASH}', 'global:owner')
39
+ ON CONFLICT (email) DO NOTHING;
40
+ SQL
41
+
42
+ log "n8n owner setup complete (email: ${ADMIN_EMAIL})"
@@ -26,3 +26,11 @@ services:
26
26
  - "traefik.http.routers.grafana-http.rule=Host(`grafana.${DOMAIN}`)"
27
27
  - "traefik.http.routers.grafana-http.entrypoints=web"
28
28
  - "traefik.http.routers.grafana-http.middlewares=redirect-to-https"
29
+
30
+ # Prometheus and Loki have no Traefik route; Grafana/Promtail reach them over
31
+ # the compose network. Drop the dev host-port publishing so prod never binds
32
+ # them on the host (defense-in-depth behind the 22/80/443-only firewall).
33
+ prometheus:
34
+ ports: []
35
+ loki:
36
+ ports: []
@@ -0,0 +1,31 @@
1
+ # Observability (Prometheus · Grafana · Loki)
2
+
3
+ This add-on installs a self-contained monitoring stack into your project for
4
+ both the Docker Compose and Kubernetes deploy modes.
5
+
6
+ ## What gets installed
7
+
8
+ | File / dir | Purpose |
9
+ |------------|---------|
10
+ | `docker-compose.observability.yml` (+ `.prod.yml`, `.override.yml`) | Compose overlay for Prometheus, Grafana, Loki, Promtail, postgres-exporter |
11
+ | `volumes/prometheus/`, `volumes/loki/`, `volumes/promtail/`, `volumes/grafana/` | Scrape config, log pipeline, datasources, and dashboards (prod + dev variants) |
12
+ | `k8s/base/observability/` | Kubernetes manifests + a kustomization, wired into `k8s/base/kustomization.yaml` |
13
+
14
+ The `observability/` entry is added to `k8s/base/kustomization.yaml`, so a
15
+ `kubectl apply -k k8s/base` (run by `vibecarbon deploy` on the k8s tiers) brings
16
+ the stack up alongside the app.
17
+
18
+ ## Access
19
+
20
+ - **Compose:** Grafana at `http://grafana.localhost`, Prometheus at
21
+ `http://prometheus.localhost`, Loki at `http://loki.localhost`. Dev mode skips
22
+ Grafana auth; production puts Grafana behind Traefik with TLS.
23
+ - **Kubernetes:** Grafana is exposed at `/admin/grafana` behind the Traefik
24
+ `admin-auth` middleware (super-admin SSO). Prometheus (`:9090`) and Loki
25
+ (`:3100`) stay in-cluster.
26
+
27
+ ## Removing
28
+
29
+ `vibecarbon remove observability` deletes the installed configs and the
30
+ kustomization entry. Persisted volume data (Grafana DB, Prometheus/Loki storage)
31
+ is left in place.
@@ -0,0 +1,107 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: grafana
5
+ labels:
6
+ app: vibecarbon-grafana
7
+ component: observability
8
+ spec:
9
+ replicas: 1
10
+ strategy:
11
+ type: Recreate
12
+ selector:
13
+ matchLabels:
14
+ app: vibecarbon-grafana
15
+ template:
16
+ metadata:
17
+ labels:
18
+ app: vibecarbon-grafana
19
+ component: observability
20
+ spec:
21
+ securityContext:
22
+ fsGroup: 472
23
+ runAsUser: 472
24
+ runAsGroup: 472
25
+ seccompProfile:
26
+ type: RuntimeDefault
27
+ containers:
28
+ - name: grafana
29
+ image: grafana/grafana:latest
30
+ imagePullPolicy: IfNotPresent
31
+ ports:
32
+ - containerPort: 3000
33
+ name: http
34
+ env:
35
+ - name: GF_SECURITY_ADMIN_USER
36
+ valueFrom:
37
+ secretKeyRef:
38
+ name: vibecarbon-secrets
39
+ key: ADMIN_EMAIL
40
+ - name: GF_SECURITY_ADMIN_PASSWORD
41
+ valueFrom:
42
+ secretKeyRef:
43
+ name: vibecarbon-secrets
44
+ key: ADMIN_PASSWORD
45
+ - name: GF_AUTH_ANONYMOUS_ENABLED
46
+ value: 'true'
47
+ - name: GF_AUTH_ANONYMOUS_ORG_ROLE
48
+ value: Admin
49
+ - name: SITE_URL
50
+ valueFrom:
51
+ configMapKeyRef:
52
+ name: vibecarbon-config
53
+ key: SITE_URL
54
+ - name: GF_SERVER_ROOT_URL
55
+ value: $(SITE_URL)/admin/grafana/
56
+ - name: GF_SERVER_SERVE_FROM_SUB_PATH
57
+ value: 'true'
58
+ volumeMounts:
59
+ - name: data
60
+ mountPath: /var/lib/grafana
61
+ - name: datasources
62
+ mountPath: /etc/grafana/provisioning/datasources
63
+ readOnly: true
64
+ - name: dashboards-provisioning
65
+ mountPath: /etc/grafana/provisioning/dashboards
66
+ readOnly: true
67
+ resources:
68
+ requests:
69
+ cpu: 100m
70
+ memory: 256Mi
71
+ limits:
72
+ cpu: 500m
73
+ memory: 512Mi
74
+ livenessProbe:
75
+ httpGet:
76
+ path: /api/health
77
+ port: http
78
+ initialDelaySeconds: 15
79
+ periodSeconds: 20
80
+ timeoutSeconds: 5
81
+ failureThreshold: 3
82
+ readinessProbe:
83
+ httpGet:
84
+ path: /api/health
85
+ port: http
86
+ initialDelaySeconds: 5
87
+ periodSeconds: 10
88
+ timeoutSeconds: 3
89
+ failureThreshold: 3
90
+ securityContext:
91
+ allowPrivilegeEscalation: false
92
+ volumes:
93
+ - name: data
94
+ persistentVolumeClaim:
95
+ claimName: grafana-pvc
96
+ - name: datasources
97
+ configMap:
98
+ name: grafana-provisioning
99
+ items:
100
+ - key: datasources.yml
101
+ path: datasources.yml
102
+ - name: dashboards-provisioning
103
+ configMap:
104
+ name: grafana-provisioning
105
+ items:
106
+ - key: dashboards.yml
107
+ path: dashboards.yml
@@ -0,0 +1,65 @@
1
+ # =============================================================================
2
+ # GRAFANA PROVISIONING CONFIGMAP
3
+ # =============================================================================
4
+ # Pre-configures Grafana datasources (Prometheus, Loki, PostgreSQL) and
5
+ # dashboard provisioning so Grafana starts ready-to-use without manual setup.
6
+ # Mirrors the Docker Compose provisioning in volumes/grafana/provisioning/.
7
+ #
8
+ # Note: $DB_PASSWORD in the PostgreSQL datasource is a Grafana variable
9
+ # reference (resolved by Grafana from its own env), not a shell variable.
10
+ apiVersion: v1
11
+ kind: ConfigMap
12
+ metadata:
13
+ name: grafana-provisioning
14
+ labels:
15
+ app: vibecarbon-grafana
16
+ component: observability
17
+ data:
18
+ datasources.yml: |
19
+ apiVersion: 1
20
+ datasources:
21
+ - name: Prometheus
22
+ type: prometheus
23
+ uid: prometheus
24
+ access: proxy
25
+ url: http://prometheus:9090
26
+ isDefault: true
27
+ editable: false
28
+ jsonData:
29
+ httpMethod: POST
30
+ timeInterval: "15s"
31
+ - name: Loki
32
+ type: loki
33
+ uid: loki
34
+ access: proxy
35
+ url: http://loki:3100
36
+ editable: false
37
+ jsonData:
38
+ maxLines: 1000
39
+ - name: PostgreSQL
40
+ type: postgres
41
+ uid: postgres
42
+ url: postgres:5432
43
+ database: postgres
44
+ user: supabase_admin
45
+ jsonData:
46
+ sslmode: disable
47
+ maxOpenConns: 5
48
+ maxIdleConns: 2
49
+ connMaxLifetime: 14400
50
+ secureJsonData:
51
+ password: $DB_PASSWORD
52
+ editable: false
53
+ dashboards.yml: |
54
+ apiVersion: 1
55
+ providers:
56
+ - name: 'vibecarbon'
57
+ orgId: 1
58
+ folder: 'vibecarbon'
59
+ folderUid: 'vibecarbon'
60
+ type: file
61
+ disableDeletion: false
62
+ updateIntervalSeconds: 30
63
+ allowUiUpdates: true
64
+ options:
65
+ path: /var/lib/grafana/dashboards