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,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,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,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
|