vibecarbon 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +110 -663
  2. package/README.md +4 -4
  3. package/carbon/.env.example +11 -0
  4. package/carbon/backup/compose-backup.sh +8 -0
  5. package/carbon/docker-compose.yml +11 -0
  6. package/carbon/ha/primary-init.sql +10 -2
  7. package/carbon/k8s/base/backup/cronjob.yaml +9 -0
  8. package/carbon/k8s/base/repl-gateway/kustomization.yaml +11 -0
  9. package/carbon/k8s/base/repl-gateway/repl-gateway.yaml +92 -0
  10. package/carbon/k8s/values/supabase.values.yaml +6 -1
  11. package/carbon/src/client/lib/supabase.ts +41 -0
  12. package/carbon/src/client/locales/de.json +27 -12
  13. package/carbon/src/client/locales/en.json +30 -15
  14. package/carbon/src/client/locales/es.json +27 -12
  15. package/carbon/src/client/locales/fr.json +27 -12
  16. package/carbon/src/client/locales/pt.json +27 -12
  17. package/carbon/src/client/pages/Checkout.tsx +1 -1
  18. package/carbon/src/server/billing/providers/paddle.ts +21 -2
  19. package/carbon/src/server/billing/providers/polar.ts +14 -0
  20. package/carbon/src/server/index.ts +12 -9
  21. package/carbon/src/server/lib/client-ip.ts +68 -0
  22. package/carbon/src/server/lib/env.ts +20 -6
  23. package/carbon/src/server/lib/rate-limiter.ts +11 -65
  24. package/carbon/src/server/lib/supabase.ts +10 -4
  25. package/carbon/src/server/middleware/requireOrgRole.ts +76 -0
  26. package/carbon/src/server/middleware/requirePlan.ts +64 -34
  27. package/carbon/src/server/middleware/requireSuperAdmin.ts +26 -0
  28. package/carbon/src/server/routes/v1/admin/contact.ts +5 -20
  29. package/carbon/src/server/routes/v1/admin/jobs.ts +6 -20
  30. package/carbon/src/server/routes/v1/admin/newsletter.ts +26 -23
  31. package/carbon/src/server/routes/v1/auth.ts +5 -2
  32. package/carbon/src/server/routes/v1/billing.ts +24 -64
  33. package/carbon/src/server/routes/v1/index.ts +8 -1
  34. package/carbon/src/server/routes/v1/newsletter.ts +18 -12
  35. package/carbon/src/server/routes/v1/theme.ts +36 -15
  36. package/carbon/supabase/migrations/00003_pg_cron.sql +2 -2
  37. package/carbon/supabase/migrations/00004_contact_newsletter.sql +24 -14
  38. package/carbon/tests/component/supabase-env-guard.test.ts +54 -0
  39. package/carbon/tests/integration/server/middleware/rate-limit-ip-spoof.test.ts +48 -0
  40. package/carbon/tests/integration/server/middleware/require-plan.test.ts +101 -0
  41. package/carbon/tests/integration/server/routes/admin-newsletter.test.ts +93 -0
  42. package/carbon/tests/integration/server/routes/billing-subscription-idor.test.ts +92 -0
  43. package/carbon/tests/integration/server/routes/newsletter-unsubscribe.test.ts +65 -0
  44. package/carbon/tests/integration/server/routes/theme-validation.test.ts +66 -0
  45. package/carbon/tests/unit/server/client-ip.test.ts +76 -0
  46. package/carbon/tests/unit/server/env-bool.test.ts +63 -0
  47. package/carbon/tests/unit/server/paddle-webhook.test.ts +57 -0
  48. package/carbon/tests/unit/server/polar-webhook.test.ts +63 -0
  49. package/carbon/tests/unit/server/supabase-client.test.ts +30 -0
  50. package/carbon/volumes/db/wal-archive.sh +36 -0
  51. package/package.json +2 -2
  52. package/src/backup.js +0 -2
  53. package/src/cli.js +16 -0
  54. package/src/deploy.js +16 -5
  55. package/src/destroy.js +968 -886
  56. package/src/failover.js +118 -237
  57. package/src/lib/command.js +19 -1
  58. package/src/lib/deploy/compose/ha.js +180 -102
  59. package/src/lib/deploy/compose/index.js +106 -57
  60. package/src/lib/deploy/compose/single.js +421 -0
  61. package/src/lib/deploy/image.js +38 -30
  62. package/src/lib/deploy/k8s/ha/index.js +397 -150
  63. package/src/lib/deploy/k8s/k3s.js +411 -279
  64. package/src/lib/deploy/orchestrator.js +274 -453
  65. package/src/lib/deploy/remote-build.js +8 -1
  66. package/src/lib/deploy/replication.js +540 -0
  67. package/src/lib/deploy/state.js +62 -5
  68. package/src/lib/deploy/tier-registry.js +30 -0
  69. package/src/lib/deploy/utils.js +30 -2
  70. package/src/lib/deploy/wireguard.js +146 -0
  71. package/src/lib/host-keys.js +120 -5
  72. package/src/lib/iac/index.js +57 -23
  73. package/src/lib/licensing/gate.js +59 -0
  74. package/src/lib/licensing/index.js +1 -1
  75. package/src/lib/licensing/tiers.js +6 -5
  76. package/src/lib/prod-confirm.js +65 -0
  77. package/src/lib/providers/hetzner-s3.js +85 -0
  78. package/src/lib/retry.js +59 -0
  79. package/src/lib/scale-plan.js +159 -0
  80. package/src/lib/ssh.js +35 -35
  81. package/src/restore.js +102 -14
  82. package/src/scale.js +105 -133
  83. package/src/status.js +122 -0
  84. package/src/upgrade.js +0 -4
  85. package/carbon/ha/activate-standby.sh +0 -52
  86. package/carbon/ha/standby-init.sh +0 -74
  87. package/carbon/src/server/lib/request.ts +0 -34
  88. package/src/lib/pod-backups.js +0 -53
package/README.md CHANGED
@@ -18,7 +18,7 @@ Vibecarbon is a full-stack SaaS starter that combines React 19, Vite, Tailwind v
18
18
 
19
19
  Skip the choice between vendor lock-in and DIY infrastructure. Create web applications with authentication, billing, teams, email, blog, docs, i18n, and production deployment — without a dedicated DevOps engineer.
20
20
 
21
- Includes Docker Compose, Kubernetes, High-Availability with replication and failover, auto-scaling, and automated backups.
21
+ Includes Docker Compose, Kubernetes, High-Availability with replication and one-command failover, scaling, and automated backups.
22
22
 
23
23
  ---
24
24
 
@@ -39,9 +39,9 @@ npx vibecarbon deploy
39
39
  npx vibecarbon destroy
40
40
  ```
41
41
 
42
- > **Create for free, always. Pay to deploy.** `create`, `up`, `down`, and all local development are free forever. `deploy`, `destroy`, `backup`, `restore`, `failover`, and `scale` require a Diamond or Fullerene license — a one-time payment, no subscriptions, your servers.
42
+ > **Create for free, always. Pay to deploy.** `create`, `up`, `down`, and all local development are free forever — and so is `destroy`, because teardown is never held hostage. `deploy`, `backup`, `restore`, `failover`, `scale`, and `upgrade` require a Diamond or Fullerene license — a one-time payment, no subscriptions, your servers.
43
43
 
44
- > Open source under AGPL v3. Generated project code is MIT — you own your app.
44
+ > Fair Source (FSL-1.1-MIT) — every release becomes MIT open source after two years. Generated project code is MIT today — you own your app.
45
45
 
46
46
  ---
47
47
 
@@ -171,7 +171,7 @@ _End-to-end wall-clock from the last fully-green e2e matrix run against real Het
171
171
 
172
172
  ## License & Terms
173
173
 
174
- The CLI is open source under the [GNU Affero General Public License v3.0](./LICENSE). **Building from source is free for any use, forever.**
174
+ The CLI is Fair Source under the [Functional Source License 1.1 with MIT future license](./LICENSE). **Building from source is free for any non-competing use — and every release automatically becomes MIT open source two years after publication.**
175
175
 
176
176
  Using the distributed `npx vibecarbon` package requires a license for deployment commands:
177
177
 
@@ -34,6 +34,17 @@ VITE_PUBLIC_URL="http://localhost:5173"
34
34
  ADMIN_EMAIL="admin@example.com"
35
35
  ADMIN_PASSWORD="your-password-here"
36
36
 
37
+ # =============================================================================
38
+ # REVERSE PROXY (optional)
39
+ # =============================================================================
40
+ # Number of trusted reverse-proxy hops in front of the API server. The server
41
+ # uses this to pick the real client IP out of X-Forwarded-For for rate limiting
42
+ # and login lockout — only the entry this many positions from the RIGHT is
43
+ # trusted; everything to its left is treated as attacker-controlled. The default
44
+ # of 1 matches the standard single-Traefik deployment. Increase it only if you
45
+ # add another trusted proxy in front (e.g. Cloudflare -> Traefik -> app = 2).
46
+ TRUSTED_PROXY_HOPS="1"
47
+
37
48
  # =============================================================================
38
49
  # OAUTH PROVIDERS (optional)
39
50
  # =============================================================================
@@ -54,6 +54,14 @@ docker compose exec -T db bash -c '
54
54
  echo "scheduled backup skipped: no S3 credentials configured (empty AWS_ACCESS_KEY_ID/SECRET)."
55
55
  exit 0
56
56
  fi
57
+ # WRITE-GUARD (finding #3): a standby must never base-backup into the single
58
+ # canonical prefix. WALG_ROLE=standby is set on the standby db env. This closes
59
+ # the bring-up window the pg_is_in_recovery() check below cannot (an
60
+ # independent primary not yet in recovery).
61
+ if [ "${WALG_ROLE:-primary}" = "standby" ]; then
62
+ echo "WALG_ROLE=standby — skipping base backup (only the primary writes to the canonical prefix)."
63
+ exit 0
64
+ fi
57
65
  if [ "$(psql -U supabase_admin -d postgres -tAc "SELECT pg_is_in_recovery()")" != "f" ]; then
58
66
  echo "supabase-db is in recovery (standby) — skipping base backup."
59
67
  exit 0
@@ -200,7 +200,18 @@ services:
200
200
  # `WARN The "S3_*" variable is not set` for every compose invocation.
201
201
  # WAL-G is dormant locally without these anyway; the warns were pure
202
202
  # noise.
203
+ #
204
+ # WALG_S3_PREFIX is a SINGLE canonical path (NO role segment). Reads
205
+ # (backup-fetch/restore/reseed) and writes (backup-push/wal-push) must all
206
+ # agree on ONE prefix — otherwise a standby restoring/reseeding would read
207
+ # an empty `…/walg/standby` and fail with "No backups found". Anti-collision
208
+ # (finding #3: a standby / bring-up-phase independent primary must never
209
+ # WRITE into this prefix) is enforced by the WALG_ROLE WRITE-GUARD:
210
+ # wal-archive.sh and the backup path both no-op when WALG_ROLE=standby.
211
+ # WALG_ROLE is exposed to the container so those write guards can read it;
212
+ # deployComposeHA writes WALG_ROLE=standby into the standby's .env.
203
213
  WALG_S3_PREFIX: s3://${S3_BACKUP_BUCKET:-${S3_BUCKET:-}}/backups/${PROJECT_NAME}/walg
214
+ WALG_ROLE: ${WALG_ROLE:-primary}
204
215
  AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY:-}
205
216
  AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY:-}
206
217
  AWS_ENDPOINT: ${S3_ENDPOINT:-}
@@ -2,14 +2,22 @@
2
2
  -- Run on the primary database before initializing the standby.
3
3
  --
4
4
  -- This script is idempotent: safe to run multiple times.
5
+ --
6
+ -- SECURITY: `{{REPL_PASSWORD}}` below is a PLACEHOLDER, not a real password.
7
+ -- This file MUST be rendered with a real per-deploy password before running;
8
+ -- never run it raw. The vibecarbon deploy tooling renders it automatically
9
+ -- (src/lib/deploy/k8s/ha/index.js substitutes '{{REPL_PASSWORD}}' with the
10
+ -- random REPL_PASSWORD generated at create time). Running this file unrendered
11
+ -- would either fail (invalid SQL) or, if hand-edited, risk a weak-cred
12
+ -- `replicator` role — do not do it.
5
13
 
6
14
  -- Create dedicated replication role (if not exists), or update its password on re-runs.
7
15
  DO $$
8
16
  BEGIN
9
17
  IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'replicator') THEN
10
- CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'repl_password';
18
+ CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '{{REPL_PASSWORD}}';
11
19
  ELSE
12
- ALTER ROLE replicator WITH PASSWORD 'repl_password';
20
+ ALTER ROLE replicator WITH PASSWORD '{{REPL_PASSWORD}}';
13
21
  END IF;
14
22
  END $$;
15
23
 
@@ -74,6 +74,15 @@ spec:
74
74
  # (wal-push/archiving and backup-fetch/restore do not connect to
75
75
  # PG, which is why this only bit backup-push. RCA 2026-05-30.)
76
76
  export PGUSER=supabase_admin PGHOST=localhost PGPORT=5432 PGDATABASE=postgres
77
+ # WRITE-GUARD (finding #3): never base-backup on a standby into
78
+ # the single canonical WALG_S3_PREFIX. WALG_ROLE is rendered
79
+ # into the db container env (supabase.values.yaml) and inherited
80
+ # by this kubectl-exec. Closes the bring-up window the recovery
81
+ # check below cannot (independent primary, not yet in recovery).
82
+ if [ "${WALG_ROLE:-primary}" = "standby" ]; then
83
+ echo "WALG_ROLE=standby — skipping base backup (only the primary writes the canonical prefix)."
84
+ exit 0
85
+ fi
77
86
  if [ "$(psql -U supabase_admin -d postgres -tAc "SELECT pg_is_in_recovery()")" != "f" ]; then
78
87
  echo "supabase-db is in recovery (standby) — skipping base backup."
79
88
  exit 0
@@ -0,0 +1,11 @@
1
+ # repl-gateway — HA-only cross-cluster replication transport (WireGuard + socat).
2
+ #
3
+ # NOTE: this kustomization is intentionally NOT referenced by the parent
4
+ # carbon/k8s/base/kustomization.yaml. The gateway is applied only on HA k8s
5
+ # deploys (by setupReplication), with per-cluster placeholders rendered at apply
6
+ # time. Keeping it as a standalone base lets `kustomize build` validate the
7
+ # manifest in isolation.
8
+ apiVersion: kustomize.config.k8s.io/v1beta1
9
+ kind: Kustomization
10
+ resources:
11
+ - repl-gateway.yaml
@@ -0,0 +1,92 @@
1
+ # =============================================================================
2
+ # VIBECARBON REPLICATION GATEWAY (HA k8s only)
3
+ # =============================================================================
4
+ # Cross-cluster PostgreSQL streaming replication runs over a point-to-point
5
+ # WireGuard tunnel between the two clusters' supabase nodes (wg0, UDP 51821).
6
+ # This gateway is a hostNetwork socat relay pinned to the supabase node; it
7
+ # bridges the intra-cluster db pod to the tunnel.
8
+ #
9
+ # standby db pod ──tcp <node-priv-ip>:15433──► standby repl-gateway (this pod)
10
+ # └─ socat into wg0 → 10.99.0.1:15433 ──► primary repl-gateway (this pod)
11
+ # └─ socat → 127.0.0.1:5433 (primary postgres hostPort)
12
+ #
13
+ # The WireGuard device (wg0) is owned by the HOST kernel (brought up over SSH by
14
+ # setupReplication / exchangeAndBringUpTunnel). Because this pod is
15
+ # `hostNetwork: true`, its socat shares the host netns and reaches wg0 + the
16
+ # node's private IP directly — so the pod itself needs NO extra capabilities.
17
+ #
18
+ # This manifest is NOT part of the base kustomization: it is HA-only and needs
19
+ # per-cluster values (relay direction + node private IP). setupReplication
20
+ # renders the placeholders below and applies it to each cluster via stdin.
21
+ #
22
+ # Placeholders (patched at apply time by setupReplication — NEVER at runtime):
23
+ # __RELAY_LISTEN__ socat listen address spec (differs per primary/standby)
24
+ # __RELAY_TARGET__ socat downstream target (differs per primary/standby)
25
+ # __SUPABASE_PRIVATE_IP__ this cluster's supabase node private IP (egress netpol)
26
+ # =============================================================================
27
+ apiVersion: v1
28
+ kind: Pod
29
+ metadata:
30
+ name: repl-gateway
31
+ namespace: vibecarbon
32
+ labels:
33
+ app: repl-gateway
34
+ spec:
35
+ # Share the host netns so socat can bind the node's private IP / the wg0
36
+ # tunnel IP and reach the host WireGuard device.
37
+ hostNetwork: true
38
+ dnsPolicy: ClusterFirstWithHostNet
39
+ # The supabase node carries a dedicated taint; without this toleration the
40
+ # pod stays Pending ("untolerated taint {dedicated: supabase}").
41
+ tolerations:
42
+ - key: dedicated
43
+ operator: Equal
44
+ value: supabase
45
+ effect: NoSchedule
46
+ nodeSelector:
47
+ dedicated: supabase
48
+ restartPolicy: Always
49
+ containers:
50
+ - name: relay
51
+ # Floating tag (no digest pin — users always get the latest socat). The
52
+ # image ENTRYPOINT is `socat`; args are its two address specs.
53
+ image: alpine/socat
54
+ imagePullPolicy: IfNotPresent
55
+ args:
56
+ - "__RELAY_LISTEN__"
57
+ - "__RELAY_TARGET__"
58
+ resources:
59
+ requests:
60
+ cpu: 10m
61
+ memory: 16Mi
62
+ limits:
63
+ cpu: 250m
64
+ memory: 64Mi
65
+ ---
66
+ # The ONE additive egress NetworkPolicy that admits the db pod → local gateway
67
+ # hop. The gateway presents as the node's private IP (inside the 10.0.0.0/8
68
+ # range excluded by supabase-db-s3-egress) on tcp 15433, so the hop is blocked
69
+ # by default. This policy is additive to default-deny-all and scoped to exactly
70
+ # one destination IP + one port. NetworkPolicy is connection-stateful, so
71
+ # allowing the egress SYN clears the return path too — no ingress rule needed
72
+ # (and none applies to the hostNetwork gateway). Shipped on BOTH clusters (each
73
+ # targeting its own node) for failover symmetry.
74
+ apiVersion: networking.k8s.io/v1
75
+ kind: NetworkPolicy
76
+ metadata:
77
+ name: allow-db-repl-gateway-egress
78
+ namespace: vibecarbon
79
+ spec:
80
+ podSelector:
81
+ matchLabels:
82
+ app.kubernetes.io/instance: supabase
83
+ app.kubernetes.io/name: supabase-db
84
+ policyTypes:
85
+ - Egress
86
+ egress:
87
+ - to:
88
+ - ipBlock:
89
+ cidr: __SUPABASE_PRIVATE_IP__/32
90
+ ports:
91
+ - protocol: TCP
92
+ port: 15433
@@ -270,9 +270,14 @@ environment:
270
270
  # SECRET keys (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) are injected
271
271
  # separately via a StatefulSet env patch in applyK3sManifests.
272
272
  # {{WALG_S3_PREFIX}} resolves to s3://<backup-bucket|storage-bucket>/
273
- # backups/<project>/walg (fallback handled in installSupabase).
273
+ # backups/<project>/walg (a SINGLE canonical prefix; fallback handled in
274
+ # installSupabase). {{WALG_ROLE}} is 'primary' or 'standby' — it is NOT part
275
+ # of the prefix; it's the WRITE-GUARD signal that wal-archive.sh + the backup
276
+ # CronJob read to no-op on the standby (so a standby never writes into the
277
+ # canonical prefix), while restore/reseed still READ the one canonical prefix.
274
278
  db:
275
279
  WALG_S3_PREFIX: "{{WALG_S3_PREFIX}}"
280
+ WALG_ROLE: "{{WALG_ROLE}}"
276
281
  WALG_COMPRESSION_METHOD: lz4
277
282
  WALG_UPLOAD_CONCURRENCY: "2"
278
283
  AWS_ENDPOINT: "{{S3_ENDPOINT}}"
@@ -5,9 +5,50 @@ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
5
5
  const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
6
6
 
7
7
  if (!supabaseUrl || !supabaseAnonKey) {
8
+ const missing = [
9
+ !supabaseUrl ? 'VITE_SUPABASE_URL' : null,
10
+ !supabaseAnonKey ? 'VITE_SUPABASE_ANON_KEY' : null,
11
+ ].filter((name): name is string => name !== null);
12
+ renderConfigError(missing);
8
13
  throw new Error('Missing Supabase environment variables');
9
14
  }
10
15
 
16
+ // This module is evaluated before React mounts, so a bare throw leaves #root
17
+ // empty — a black page with the only clue in the console. Paint a readable
18
+ // error with plain DOM APIs first, then let the throw halt the app.
19
+ function renderConfigError(missing: string[]): void {
20
+ if (typeof document === 'undefined') return;
21
+
22
+ const screen = document.createElement('div');
23
+ screen.style.cssText =
24
+ 'position:fixed;inset:0;display:flex;align-items:center;justify-content:center;' +
25
+ 'background:#0a0f14;color:#e6edf3;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;' +
26
+ 'padding:2rem;z-index:2147483647;text-align:left';
27
+
28
+ const box = document.createElement('div');
29
+ box.style.cssText = 'max-width:40rem';
30
+
31
+ const heading = document.createElement('h1');
32
+ heading.textContent = 'Configuration error';
33
+ heading.style.cssText = 'font-size:1.25rem;margin:0 0 0.75rem;color:#f47067';
34
+
35
+ const detail = document.createElement('p');
36
+ detail.textContent =
37
+ `This build is missing required environment variables: ${missing.join(', ')}. ` +
38
+ 'The frontend cannot start without them.';
39
+ detail.style.cssText = 'margin:0 0 0.75rem;line-height:1.5';
40
+
41
+ const hint = document.createElement('p');
42
+ hint.textContent =
43
+ 'Rebuild with the variables set — deploys inject them automatically; ' +
44
+ 'for local development, check your .env file.';
45
+ hint.style.cssText = 'margin:0;line-height:1.5;opacity:0.7';
46
+
47
+ box.append(heading, detail, hint);
48
+ screen.append(box);
49
+ document.body.append(screen);
50
+ }
51
+
11
52
  /**
12
53
  * Cookie-based storage adapter for Supabase auth.
13
54
  * Stores auth tokens in cookies so they're sent with requests to /admin/* routes,
@@ -293,7 +293,7 @@
293
293
  },
294
294
  "deploy": {
295
295
  "label": "Deployen",
296
- "description": "In Produktion pushen mit Auto-Scaling, Hochverfügbarkeit, Monitoring, CI/CD und mehr."
296
+ "description": "In Produktion pushen mit Skalierung, Hochverfügbarkeit, Monitoring, CI/CD und mehr."
297
297
  }
298
298
  },
299
299
  "techStack": {
@@ -352,7 +352,7 @@
352
352
  "traffic": {
353
353
  "risk": "Traffic überlastet",
354
354
  "protection": "Auto-Scaling",
355
- "how": "Kubernetes-Autoscaling über Pods und VPS-Knoten"
355
+ "how": "Kubernetes-Pod-Autoscaling plus Worker-Skalierung per Befehl"
356
356
  },
357
357
  "redundancy": {
358
358
  "risk": "Keine Redundanz",
@@ -401,25 +401,40 @@
401
401
  "compose": {
402
402
  "name": "Docker Compose",
403
403
  "tagline": "Ein VPS, dein kompletter Stack inklusive.",
404
- "features": ["1 Server", "Einzelne Region"],
404
+ "features": [
405
+ "1 Server",
406
+ "Einzelne Region"
407
+ ],
405
408
  "bestFor": "MVPs & interne Tools"
406
409
  },
407
410
  "composeHa": {
408
411
  "name": "Compose HA",
409
- "tagline": "Zwei Server, automatisches Failover.",
410
- "features": ["2 Server", "Streaming-Replikation", "Auto-Failover"],
412
+ "tagline": "Zwei Regionen, Failover per Befehl.",
413
+ "features": [
414
+ "2 Server",
415
+ "Streaming-Replikation",
416
+ "Failover per Befehl"
417
+ ],
411
418
  "bestFor": "Produktions-SaaS ohne Kubernetes"
412
419
  },
413
420
  "k8s": {
414
421
  "name": "Kubernetes",
415
- "tagline": "Automatisch skalierender k3s-Cluster.",
416
- "features": ["Einzelne Region", "Horizontale Autoskalierung", "Rollierende Updates"],
422
+ "tagline": "Produktionsreifer k3s-Cluster.",
423
+ "features": [
424
+ "Einzelne Region",
425
+ "Horizontale Autoskalierung",
426
+ "Rollierende Updates"
427
+ ],
417
428
  "bestFor": "Anwendungen mit hohem Traffic"
418
429
  },
419
430
  "k8sHa": {
420
431
  "name": "Kubernetes HA",
421
432
  "tagline": "Multi-Region-Cluster, maximale Verfügbarkeit.",
422
- "features": ["2 Regionen", "Regions-Failover", "Autoskalierung"],
433
+ "features": [
434
+ "2 Regionen",
435
+ "Regions-Failover",
436
+ "Pod-Autoscaling"
437
+ ],
423
438
  "bestFor": "Geschäftskritisch & global"
424
439
  }
425
440
  },
@@ -459,7 +474,7 @@
459
474
  "backups": "Automatische S3-Backups",
460
475
  "cicd": "CI/CD via GitHub Actions",
461
476
  "unlimited": "Unbegrenzte Server & Projekte",
462
- "commercial": "Kommerzielle Nutzungsrechte"
477
+ "commercial": "FSL-Ausnahme (Einbetten, White-Label, Weiterverkauf)"
463
478
  }
464
479
  },
465
480
  "fullerene": {
@@ -467,7 +482,7 @@
467
482
  "cta": "Fullerene kaufen",
468
483
  "features": {
469
484
  "diamond": "Alles in Diamond",
470
- "commercial": "Kommerzielle Nutzungsrechte (AGPL-Ausnahme)",
485
+ "commercial": "FSL-Ausnahme: Vibecarbon einbetten, als White-Label anbieten oder Vibecarbon-basierte Services weiterverkaufen",
471
486
  "clients": "Bereitstellung für Kunden & Organisationen",
472
487
  "corporate": "Agenturen & Enterprise-Teams",
473
488
  "support": "Prioritäts-E-Mail-Support",
@@ -496,7 +511,7 @@
496
511
  },
497
512
  "q5": {
498
513
  "question": "Wie funktioniert die Preisgestaltung?",
499
- "answer": "Graphite ist dauerhaft kostenlos — Projekte erstellen, lokal entwickeln, alle Add-ons nutzen. Diamond (149 $ einmalig) bietet volle Automatisierung für Indie-Entwickler und Startups — alle Deployment-Modi, Backups, CI/CD und Skalierung. Fullerene (499 $ einmalig) ist die kommerzielle Lizenz für Agenturen und Unternehmen — für Kunden deployen, ohne AGPL-Auflagen. Einmalige Zahlung, kein Abo."
514
+ "answer": "Graphite ist dauerhaft kostenlos — Projekte erstellen, lokal entwickeln, alle Add-ons nutzen. Diamond (149 $ einmalig) bietet volle Automatisierung für Indie-Entwickler und Startups — alle Deployment-Modi, Backups, CI/CD und Skalierung. Fullerene (499 $ einmalig) richtet sich an Agenturen und Unternehmen — für Kunden deployen, plus die FSL-Ausnahme, um Vibecarbon einzubetten, als White-Label anzubieten oder Vibecarbon-basierte Services weiterzuverkaufen. Einmalige Zahlung, kein Abo."
500
515
  },
501
516
  "q6": {
502
517
  "question": "Wie viele Projekte kann ich mit einer Lizenz erstellen?",
@@ -508,7 +523,7 @@
508
523
  },
509
524
  "q8": {
510
525
  "question": "Was passiert, wenn Vibecarbon eingestellt wird?",
511
- "answer": "Für deine App ändert sich nichts. Der generierte Code steht unter MIT-Lizenz ohne jegliche Laufzeitabhängigkeit von Vibecarbon — es ist einfach eine Codebasis, die dir vollständig gehört. Die CLI ist Open Source unter AGPL, du kannst sie also dauerhaft aus dem Quellcode ausführen. Deine Infrastruktur läuft auf deinen eigenen Servern."
526
+ "answer": "Für deine App ändert sich nichts. Der generierte Code steht unter MIT-Lizenz ohne jegliche Laufzeitabhängigkeit von Vibecarbon — es ist einfach eine Codebasis, die dir vollständig gehört. Die CLI ist Fair Source (FSL) du kannst sie für jede nicht konkurrierende Nutzung dauerhaft aus dem Quellcode ausführen, und jedes Release wird zwei Jahre nach Veröffentlichung MIT-Open-Source. Deine Infrastruktur läuft auf deinen eigenen Servern."
512
527
  },
513
528
  "q9": {
514
529
  "question": "Brauche ich DevOps-Erfahrung, um bereitzustellen?",
@@ -217,11 +217,11 @@
217
217
  "charts": {
218
218
  "title": "Charts",
219
219
  "revenue": "Revenue",
220
- "revenueDescription": "Monthly revenue, Jan\u2013Jun",
220
+ "revenueDescription": "Monthly revenue, Jan–Jun",
221
221
  "revenueSparkline": "$29.8k",
222
222
  "newUsers": "New Users",
223
223
  "activeUsers": "Active Users",
224
- "activeUsersDescription": "Weekly signups \u2014 new vs. returning",
224
+ "activeUsersDescription": "Weekly signups new vs. returning",
225
225
  "mrr": "MRR",
226
226
  "mrrGrowth": "MRR Growth",
227
227
  "mrrGrowthDescription": "Cumulative monthly recurring revenue",
@@ -293,7 +293,7 @@
293
293
  },
294
294
  "deploy": {
295
295
  "label": "Deploy",
296
- "description": "Deploy on Hetzner with Docker Compose or Kubernetes. Complete with backups, auto-scaling, high-availability, and more advanced features with simple commands."
296
+ "description": "Deploy on Hetzner with Docker Compose or Kubernetes. Complete with backups, scaling, high-availability, and more advanced features with simple commands."
297
297
  }
298
298
  },
299
299
  "techStack": {
@@ -352,7 +352,7 @@
352
352
  "traffic": {
353
353
  "risk": "Can't handle traffic",
354
354
  "protection": "Auto-scaling",
355
- "how": "Kubernetes autoscaling across pods and VPS nodes"
355
+ "how": "Kubernetes pod autoscaling plus one-command worker scaling"
356
356
  },
357
357
  "redundancy": {
358
358
  "risk": "Zero redundancy",
@@ -401,25 +401,40 @@
401
401
  "compose": {
402
402
  "name": "Docker Compose",
403
403
  "tagline": "One VPS, your full stack included.",
404
- "features": ["1 server", "Single region"],
404
+ "features": [
405
+ "1 server",
406
+ "Single region"
407
+ ],
405
408
  "bestFor": "MVPs & internal tools"
406
409
  },
407
410
  "composeHa": {
408
411
  "name": "Compose HA",
409
- "tagline": "Two servers, automatic failover.",
410
- "features": ["2 servers", "Streaming replication", "Auto-failover"],
412
+ "tagline": "Two regions, one-command failover.",
413
+ "features": [
414
+ "2 servers",
415
+ "Streaming replication",
416
+ "One-command failover"
417
+ ],
411
418
  "bestFor": "Production SaaS without Kubernetes"
412
419
  },
413
420
  "k8s": {
414
421
  "name": "Kubernetes",
415
- "tagline": "Auto-scaling k3s cluster.",
416
- "features": ["Single region", "Horizontal autoscaling", "Rolling updates"],
422
+ "tagline": "Production k3s cluster.",
423
+ "features": [
424
+ "Single region",
425
+ "Horizontal autoscaling",
426
+ "Rolling updates"
427
+ ],
417
428
  "bestFor": "High-traffic apps"
418
429
  },
419
430
  "k8sHa": {
420
431
  "name": "Kubernetes HA",
421
432
  "tagline": "Multi-region clusters, maximum uptime.",
422
- "features": ["2 regions", "Region failover", "Autoscaling"],
433
+ "features": [
434
+ "2 regions",
435
+ "Region failover",
436
+ "Pod autoscaling"
437
+ ],
423
438
  "bestFor": "Mission-critical & global"
424
439
  }
425
440
  },
@@ -459,7 +474,7 @@
459
474
  "backups": "Automated S3 backups",
460
475
  "cicd": "CI/CD via GitHub Actions",
461
476
  "unlimited": "Unlimited servers & projects",
462
- "commercial": "Commercial use rights"
477
+ "commercial": "FSL exception (embed, white-label, resell)"
463
478
  }
464
479
  },
465
480
  "fullerene": {
@@ -467,7 +482,7 @@
467
482
  "cta": "Get Fullerene",
468
483
  "features": {
469
484
  "diamond": "Everything in Diamond",
470
- "commercial": "Commercial use rights (AGPL exception)",
485
+ "commercial": "FSL exception: embed, white-label, or resell Vibecarbon-powered services",
471
486
  "clients": "Deploy for clients & organizations",
472
487
  "corporate": "Agency & enterprise teams",
473
488
  "support": "Priority email support",
@@ -496,7 +511,7 @@
496
511
  },
497
512
  "q5": {
498
513
  "question": "How does pricing work?",
499
- "answer": "Graphite is free forever — create projects, develop locally, and use every add-on. Diamond ($149 lifetime) is full automation for indie hackers and startups — all deployment modes, backups, CI/CD, and scaling. Fullerene ($499 lifetime) is the commercial license for agencies and enterprise — deploy for clients and organizations without AGPL restrictions. One-time payment, no subscriptions, your infrastructure."
514
+ "answer": "Graphite is free forever — create projects, develop locally, and use every add-on. Diamond ($149 lifetime) is full automation for indie hackers and startups — all deployment modes, backups, CI/CD, and scaling. Fullerene ($499 lifetime) is for agencies and enterprise — deploy for clients, plus the FSL exception to embed, white-label, or resell Vibecarbon-powered services. One-time payment, no subscriptions, your infrastructure."
500
515
  },
501
516
  "q6": {
502
517
  "question": "How many projects can I create with one license?",
@@ -508,7 +523,7 @@
508
523
  },
509
524
  "q8": {
510
525
  "question": "What happens if Vibecarbon shuts down?",
511
- "answer": "Nothing changes for your app. The generated code is MIT-licensed with zero runtime dependency on Vibecarbon — it's just a codebase you own outright. The CLI is open source under AGPL, so you can run it from source indefinitely. Your infrastructure runs on your own servers."
526
+ "answer": "Nothing changes for your app. The generated code is MIT-licensed with zero runtime dependency on Vibecarbon — it's just a codebase you own outright. The CLI is Fair Source (FSL) you can run it from source indefinitely for any non-competing use, and every release becomes MIT open source two years after publication. Your infrastructure runs on your own servers."
512
527
  },
513
528
  "q9": {
514
529
  "question": "Do I need DevOps experience to deploy?",
@@ -537,4 +552,4 @@
537
552
  "terms": "Terms"
538
553
  }
539
554
  }
540
- }
555
+ }
@@ -293,7 +293,7 @@
293
293
  },
294
294
  "deploy": {
295
295
  "label": "Desplegar",
296
- "description": "Despliega a producción con auto-escalado, alta disponibilidad, monitoreo, CI/CD y más."
296
+ "description": "Despliega a producción con escalado, alta disponibilidad, monitoreo, CI/CD y más."
297
297
  }
298
298
  },
299
299
  "techStack": {
@@ -352,7 +352,7 @@
352
352
  "traffic": {
353
353
  "risk": "No soporta el tráfico",
354
354
  "protection": "Auto-escalado",
355
- "how": "Kubernetes autoscaling en pods y nodos VPS"
355
+ "how": "Autoescalado de pods de Kubernetes más escalado de workers con un comando"
356
356
  },
357
357
  "redundancy": {
358
358
  "risk": "Sin redundancia",
@@ -401,25 +401,40 @@
401
401
  "compose": {
402
402
  "name": "Docker Compose",
403
403
  "tagline": "Un VPS, con todo tu stack incluido.",
404
- "features": ["1 servidor", "Región única"],
404
+ "features": [
405
+ "1 servidor",
406
+ "Región única"
407
+ ],
405
408
  "bestFor": "MVPs y herramientas internas"
406
409
  },
407
410
  "composeHa": {
408
411
  "name": "Compose HA",
409
- "tagline": "Dos servidores, conmutación automática.",
410
- "features": ["2 servidores", "Replicación en streaming", "Conmutación automática"],
412
+ "tagline": "Dos regiones, conmutación con un comando.",
413
+ "features": [
414
+ "2 servidores",
415
+ "Replicación en streaming",
416
+ "Conmutación con un comando"
417
+ ],
411
418
  "bestFor": "SaaS en producción sin Kubernetes"
412
419
  },
413
420
  "k8s": {
414
421
  "name": "Kubernetes",
415
- "tagline": "Clúster k3s con autoescalado.",
416
- "features": ["Región única", "Autoescalado horizontal", "Actualizaciones progresivas"],
422
+ "tagline": "Clúster k3s listo para producción.",
423
+ "features": [
424
+ "Región única",
425
+ "Autoescalado horizontal",
426
+ "Actualizaciones progresivas"
427
+ ],
417
428
  "bestFor": "Apps de alto tráfico"
418
429
  },
419
430
  "k8sHa": {
420
431
  "name": "Kubernetes HA",
421
432
  "tagline": "Clústeres multirregión, máxima disponibilidad.",
422
- "features": ["2 regiones", "Conmutación de región", "Autoescalado"],
433
+ "features": [
434
+ "2 regiones",
435
+ "Conmutación de región",
436
+ "Autoescalado de pods"
437
+ ],
423
438
  "bestFor": "Críticos y globales"
424
439
  }
425
440
  },
@@ -459,7 +474,7 @@
459
474
  "backups": "Copias de seguridad automáticas en S3",
460
475
  "cicd": "CI/CD mediante GitHub Actions",
461
476
  "unlimited": "Servidores y proyectos ilimitados",
462
- "commercial": "Derechos de uso comercial"
477
+ "commercial": "Excepción FSL (integración, marca blanca, reventa)"
463
478
  }
464
479
  },
465
480
  "fullerene": {
@@ -467,7 +482,7 @@
467
482
  "cta": "Ir a Fullerene",
468
483
  "features": {
469
484
  "diamond": "Todo lo de Diamond",
470
- "commercial": "Derechos de uso comercial (excepción AGPL)",
485
+ "commercial": "Excepción FSL: integra, ofrece marca blanca o revende servicios impulsados por Vibecarbon",
471
486
  "clients": "Despliega para clientes y organizaciones",
472
487
  "corporate": "Equipos de agencias y empresas",
473
488
  "support": "Soporte prioritario por correo",
@@ -496,7 +511,7 @@
496
511
  },
497
512
  "q5": {
498
513
  "question": "¿Cómo funciona el precio?",
499
- "answer": "Graphite es gratuito para siempre — crea proyectos, desarrolla localmente y usa todos los complementos. Diamond ($149 de por vida) es automatización completa para indie hackers y startups — todos los modos de despliegue, backups, CI/CD y escalado. Fullerene ($499 de por vida) es la licencia comercial para agencias y empresas — despliega para clientes y organizaciones sin restricciones AGPL. Pago único, sin suscripciones."
514
+ "answer": "Graphite es gratuito para siempre — crea proyectos, desarrolla localmente y usa todos los complementos. Diamond ($149 de por vida) es automatización completa para indie hackers y startups — todos los modos de despliegue, backups, CI/CD y escalado. Fullerene ($499 de por vida) es para agencias y empresas — despliega para clientes, más la excepción FSL para integrar, ofrecer marca blanca o revender servicios impulsados por Vibecarbon. Pago único, sin suscripciones."
500
515
  },
501
516
  "q6": {
502
517
  "question": "¿Cuántos proyectos puedo crear con una licencia?",
@@ -508,7 +523,7 @@
508
523
  },
509
524
  "q8": {
510
525
  "question": "¿Qué pasa si Vibecarbon cierra?",
511
- "answer": "No cambia nada para tu aplicación. El código generado está bajo licencia MIT y sin ninguna dependencia en tiempo de ejecución de Vibecarbon: es simplemente una base de código que te pertenece por completo. La CLI es código abierto bajo AGPL, así que puedes ejecutarla desde el código fuente indefinidamente. Tu infraestructura corre en tus propios servidores."
526
+ "answer": "No cambia nada para tu aplicación. El código generado está bajo licencia MIT y sin ninguna dependencia en tiempo de ejecución de Vibecarbon: es simplemente una base de código que te pertenece por completo. La CLI es Fair Source (FSL): puedes ejecutarla desde el código fuente indefinidamente para cualquier uso no competidor, y cada versión se convierte en código abierto MIT dos años después de su publicación. Tu infraestructura corre en tus propios servidores."
512
527
  },
513
528
  "q9": {
514
529
  "question": "¿Necesito experiencia en DevOps para desplegar?",