vibecarbon 0.9.0 → 0.10.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.
- package/LICENSE +110 -663
- package/README.md +4 -4
- package/carbon/.env.example +11 -0
- package/carbon/backup/compose-backup.sh +8 -0
- package/carbon/docker-compose.yml +11 -0
- package/carbon/ha/primary-init.sql +10 -2
- package/carbon/k8s/base/backup/cronjob.yaml +9 -0
- package/carbon/k8s/base/repl-gateway/kustomization.yaml +11 -0
- package/carbon/k8s/base/repl-gateway/repl-gateway.yaml +92 -0
- package/carbon/k8s/values/supabase.values.yaml +6 -1
- package/carbon/src/client/lib/supabase.ts +41 -0
- package/carbon/src/client/locales/de.json +27 -12
- package/carbon/src/client/locales/en.json +30 -15
- package/carbon/src/client/locales/es.json +27 -12
- package/carbon/src/client/locales/fr.json +27 -12
- package/carbon/src/client/locales/pt.json +27 -12
- package/carbon/src/client/pages/Checkout.tsx +1 -1
- package/carbon/src/server/billing/providers/paddle.ts +21 -2
- package/carbon/src/server/billing/providers/polar.ts +14 -0
- package/carbon/src/server/index.ts +12 -9
- package/carbon/src/server/lib/client-ip.ts +68 -0
- package/carbon/src/server/lib/env.ts +20 -6
- package/carbon/src/server/lib/rate-limiter.ts +11 -65
- package/carbon/src/server/lib/supabase.ts +10 -4
- package/carbon/src/server/middleware/requireOrgRole.ts +76 -0
- package/carbon/src/server/middleware/requirePlan.ts +64 -34
- package/carbon/src/server/middleware/requireSuperAdmin.ts +26 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +5 -20
- package/carbon/src/server/routes/v1/admin/jobs.ts +6 -20
- package/carbon/src/server/routes/v1/admin/newsletter.ts +26 -23
- package/carbon/src/server/routes/v1/auth.ts +5 -2
- package/carbon/src/server/routes/v1/billing.ts +24 -64
- package/carbon/src/server/routes/v1/index.ts +8 -1
- package/carbon/src/server/routes/v1/newsletter.ts +18 -12
- package/carbon/src/server/routes/v1/theme.ts +36 -15
- package/carbon/supabase/migrations/00003_pg_cron.sql +2 -2
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +24 -14
- package/carbon/tests/component/supabase-env-guard.test.ts +54 -0
- package/carbon/tests/integration/server/middleware/rate-limit-ip-spoof.test.ts +48 -0
- package/carbon/tests/integration/server/middleware/require-plan.test.ts +101 -0
- package/carbon/tests/integration/server/routes/admin-newsletter.test.ts +93 -0
- package/carbon/tests/integration/server/routes/billing-subscription-idor.test.ts +92 -0
- package/carbon/tests/integration/server/routes/newsletter-unsubscribe.test.ts +65 -0
- package/carbon/tests/integration/server/routes/theme-validation.test.ts +66 -0
- package/carbon/tests/unit/server/client-ip.test.ts +76 -0
- package/carbon/tests/unit/server/env-bool.test.ts +63 -0
- package/carbon/tests/unit/server/paddle-webhook.test.ts +57 -0
- package/carbon/tests/unit/server/polar-webhook.test.ts +63 -0
- package/carbon/tests/unit/server/supabase-client.test.ts +30 -0
- package/carbon/volumes/db/wal-archive.sh +36 -0
- package/package.json +2 -2
- package/src/backup.js +0 -2
- package/src/cli.js +16 -0
- package/src/deploy.js +16 -5
- package/src/destroy.js +78 -0
- package/src/failover.js +118 -237
- package/src/lib/command.js +19 -1
- package/src/lib/deploy/compose/ha.js +180 -102
- package/src/lib/deploy/compose/index.js +63 -20
- package/src/lib/deploy/image.js +19 -23
- package/src/lib/deploy/k8s/ha/index.js +397 -150
- package/src/lib/deploy/k8s/k3s.js +65 -14
- package/src/lib/deploy/orchestrator.js +176 -6
- package/src/lib/deploy/remote-build.js +8 -1
- package/src/lib/deploy/replication.js +540 -0
- package/src/lib/deploy/state.js +62 -5
- package/src/lib/deploy/utils.js +30 -2
- package/src/lib/deploy/wireguard.js +146 -0
- package/src/lib/host-keys.js +120 -5
- package/src/lib/iac/index.js +57 -23
- package/src/lib/licensing/gate.js +59 -0
- package/src/lib/licensing/index.js +1 -1
- package/src/lib/licensing/tiers.js +6 -5
- package/src/lib/prod-confirm.js +65 -0
- package/src/lib/providers/hetzner-s3.js +85 -0
- package/src/lib/ssh.js +35 -35
- package/src/restore.js +102 -14
- package/src/scale.js +4 -2
- package/src/status.js +122 -0
- package/src/upgrade.js +0 -4
- package/carbon/ha/activate-standby.sh +0 -52
- package/carbon/ha/standby-init.sh +0 -74
- package/carbon/src/server/lib/request.ts +0 -34
- 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,
|
|
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
|
|
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
|
-
>
|
|
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
|
|
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
|
|
package/carbon/.env.example
CHANGED
|
@@ -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 '
|
|
18
|
+
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '{{REPL_PASSWORD}}';
|
|
11
19
|
ELSE
|
|
12
|
-
ALTER ROLE replicator WITH 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
|
|
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
|
|
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
|
|
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": [
|
|
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
|
|
410
|
-
"features": [
|
|
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": "
|
|
416
|
-
"features": [
|
|
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": [
|
|
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": "
|
|
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": "
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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": [
|
|
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
|
|
410
|
-
"features": [
|
|
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": "
|
|
416
|
-
"features": [
|
|
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": [
|
|
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": "
|
|
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": "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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": [
|
|
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
|
|
410
|
-
"features": [
|
|
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
|
|
416
|
-
"features": [
|
|
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": [
|
|
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": "
|
|
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": "
|
|
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
|
|
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
|
|
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?",
|