vibecarbon 0.1.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 +663 -0
- package/README.md +188 -0
- package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
- package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
- package/carbon/.claude/settings.json +29 -0
- package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
- package/carbon/.dockerignore +57 -0
- package/carbon/.env.example +219 -0
- package/carbon/.github/copilot-instructions.md +3 -0
- package/carbon/.github/workflows/deploy.yml +346 -0
- package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
- package/carbon/.windsurfrules +74 -0
- package/carbon/AGENTS.md +422 -0
- package/carbon/CLAUDE.md +3 -0
- package/carbon/DEVELOPMENT.md +187 -0
- package/carbon/Dockerfile +98 -0
- package/carbon/LICENSE +21 -0
- package/carbon/PRODUCTION.md +364 -0
- package/carbon/README.md +193 -0
- package/carbon/TESTING.md +138 -0
- package/carbon/backup/Dockerfile +75 -0
- package/carbon/backup/backup.sh +95 -0
- package/carbon/backup/compose-backup.sh +140 -0
- package/carbon/biome.json +83 -0
- package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
- package/carbon/cloud-init/k3s/master-init.sh +215 -0
- package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
- package/carbon/cloud-init/k3s/worker-init.sh +147 -0
- package/carbon/components.json +24 -0
- package/carbon/content/blog/authentication-guide.mdx +34 -0
- package/carbon/content/blog/getting-started.mdx +41 -0
- package/carbon/content/changelog/v0-1-0.mdx +40 -0
- package/carbon/content/docs/analytics.mdx +90 -0
- package/carbon/content/docs/authentication.mdx +231 -0
- package/carbon/content/docs/background-jobs.mdx +116 -0
- package/carbon/content/docs/cli.mdx +630 -0
- package/carbon/content/docs/database.mdx +236 -0
- package/carbon/content/docs/deployment.mdx +227 -0
- package/carbon/content/docs/development.mdx +238 -0
- package/carbon/content/docs/environments.mdx +84 -0
- package/carbon/content/docs/getting-started.mdx +112 -0
- package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
- package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
- package/carbon/content/docs/optional-services.mdx +160 -0
- package/carbon/db/Dockerfile +23 -0
- package/carbon/docker-compose.dev-init.yml +5 -0
- package/carbon/docker-compose.metabase.override.yml +14 -0
- package/carbon/docker-compose.metabase.prod.yml +28 -0
- package/carbon/docker-compose.metabase.yml +84 -0
- package/carbon/docker-compose.n8n.override.yml +14 -0
- package/carbon/docker-compose.n8n.prod.yml +31 -0
- package/carbon/docker-compose.n8n.yml +97 -0
- package/carbon/docker-compose.observability.override.yml +18 -0
- package/carbon/docker-compose.observability.prod.yml +28 -0
- package/carbon/docker-compose.observability.yml +125 -0
- package/carbon/docker-compose.override.yml +28 -0
- package/carbon/docker-compose.prod.yml +294 -0
- package/carbon/docker-compose.yml +508 -0
- package/carbon/docker-entrypoint.sh +12 -0
- package/carbon/ha/activate-standby.sh +52 -0
- package/carbon/ha/primary-init.sql +36 -0
- package/carbon/ha/standby-init.sh +74 -0
- package/carbon/k8s/LICENSE +99 -0
- package/carbon/k8s/README.md +272 -0
- package/carbon/k8s/base/app/deployment.yaml +130 -0
- package/carbon/k8s/base/app/hpa.yaml +44 -0
- package/carbon/k8s/base/app/kustomization.yaml +10 -0
- package/carbon/k8s/base/app/network-policy.yaml +124 -0
- package/carbon/k8s/base/app/pdb.yaml +14 -0
- package/carbon/k8s/base/app/rbac.yaml +36 -0
- package/carbon/k8s/base/app/service.yaml +16 -0
- package/carbon/k8s/base/backup/cronjob.yaml +120 -0
- package/carbon/k8s/base/backup/kustomization.yaml +7 -0
- package/carbon/k8s/base/backup/network-policy.yaml +31 -0
- package/carbon/k8s/base/backup/rbac.yaml +7 -0
- package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
- package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
- package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
- package/carbon/k8s/base/config/configmap.yaml +48 -0
- package/carbon/k8s/base/config/kustomization.yaml +8 -0
- package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
- package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
- package/carbon/k8s/base/kustomization.yaml +43 -0
- package/carbon/k8s/base/namespace.yaml +7 -0
- package/carbon/k8s/base/network-policies.yaml +95 -0
- package/carbon/k8s/base/registry/kustomization.yaml +5 -0
- package/carbon/k8s/base/registry/local-registry.yaml +193 -0
- package/carbon/k8s/base/traefik/certificate.yaml +21 -0
- package/carbon/k8s/base/traefik/configmap.yaml +13 -0
- package/carbon/k8s/base/traefik/deployment.yaml +165 -0
- package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
- package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
- package/carbon/k8s/base/traefik/middleware.yaml +109 -0
- package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
- package/carbon/k8s/base/traefik/service.yaml +38 -0
- package/carbon/k8s/flux/README.md +49 -0
- package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
- package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
- package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
- package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
- package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
- package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
- package/carbon/k8s/infra/kustomization.yaml +21 -0
- package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
- package/carbon/k8s/overlays/local/configmap.yaml +23 -0
- package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
- package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
- package/carbon/k8s/overlays/local/namespace.yaml +8 -0
- package/carbon/k8s/overlays/local/secrets.yaml +32 -0
- package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
- package/carbon/k8s/test-local.sh +318 -0
- package/carbon/k8s/values/supabase.values.yaml +133 -0
- package/carbon/package.json +154 -0
- package/carbon/runtime.Dockerfile +17 -0
- package/carbon/scripts/_dev-jwt.mjs +45 -0
- package/carbon/scripts/check-rls.ts +53 -0
- package/carbon/scripts/dev-init.js +191 -0
- package/carbon/scripts/dev.js +191 -0
- package/carbon/scripts/docker-down.js +63 -0
- package/carbon/scripts/docker-logs.js +59 -0
- package/carbon/scripts/docker-up.js +222 -0
- package/carbon/scripts/generate-dev-configs.sh +131 -0
- package/carbon/scripts/generate-rss.ts +116 -0
- package/carbon/scripts/generate-sitemap.ts +102 -0
- package/carbon/scripts/k8s-apply.js +71 -0
- package/carbon/scripts/k8s-delete.js +75 -0
- package/carbon/scripts/lib/manifest.js +176 -0
- package/carbon/scripts/secret-scan.mjs +278 -0
- package/carbon/scripts/validate-dev-configs.sh +101 -0
- package/carbon/src/client/App.tsx +202 -0
- package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
- package/carbon/src/client/assets/hyperformant-light.svg +29 -0
- package/carbon/src/client/assets/logos/aider.svg +1 -0
- package/carbon/src/client/assets/logos/antigravity.svg +1 -0
- package/carbon/src/client/assets/logos/bolt.svg +1 -0
- package/carbon/src/client/assets/logos/claude-code.svg +1 -0
- package/carbon/src/client/assets/logos/copilot.svg +1 -0
- package/carbon/src/client/assets/logos/cursor.svg +1 -0
- package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
- package/carbon/src/client/assets/logos/lovable.svg +1 -0
- package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
- package/carbon/src/client/assets/logos/v0.svg +1 -0
- package/carbon/src/client/assets/logos/windsurf.svg +1 -0
- package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
- package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
- package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
- package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
- package/carbon/src/client/components/AppSidebar.tsx +760 -0
- package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
- package/carbon/src/client/components/CTAFooter.tsx +59 -0
- package/carbon/src/client/components/ComparisonSection.tsx +132 -0
- package/carbon/src/client/components/ContentPanel.tsx +66 -0
- package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
- package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
- package/carbon/src/client/components/FAQSection.tsx +76 -0
- package/carbon/src/client/components/FileUpload.tsx +210 -0
- package/carbon/src/client/components/HeaderActions.tsx +17 -0
- package/carbon/src/client/components/Hero.tsx +608 -0
- package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
- package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
- package/carbon/src/client/components/Logo.tsx +87 -0
- package/carbon/src/client/components/LogoStrip.tsx +76 -0
- package/carbon/src/client/components/MetricsStrip.tsx +48 -0
- package/carbon/src/client/components/Nav.tsx +195 -0
- package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
- package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
- package/carbon/src/client/components/PageHeader.tsx +24 -0
- package/carbon/src/client/components/PlanGate.tsx +36 -0
- package/carbon/src/client/components/PricingSection.tsx +371 -0
- package/carbon/src/client/components/SEO.tsx +34 -0
- package/carbon/src/client/components/SmoothScroll.tsx +45 -0
- package/carbon/src/client/components/TechStackSection.tsx +165 -0
- package/carbon/src/client/components/WorkflowSection.tsx +604 -0
- package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
- package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
- package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
- package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
- package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
- package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
- package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
- package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
- package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
- package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
- package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
- package/carbon/src/client/components/effects/index.ts +2 -0
- package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
- package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
- package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
- package/carbon/src/client/components/scroll/index.ts +2 -0
- package/carbon/src/client/components/ui/accordion.tsx +71 -0
- package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
- package/carbon/src/client/components/ui/alert.tsx +73 -0
- package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
- package/carbon/src/client/components/ui/avatar.tsx +91 -0
- package/carbon/src/client/components/ui/badge.tsx +50 -0
- package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
- package/carbon/src/client/components/ui/button-group.tsx +78 -0
- package/carbon/src/client/components/ui/button.tsx +120 -0
- package/carbon/src/client/components/ui/calendar.tsx +182 -0
- package/carbon/src/client/components/ui/card.tsx +85 -0
- package/carbon/src/client/components/ui/carousel.tsx +227 -0
- package/carbon/src/client/components/ui/chart.tsx +357 -0
- package/carbon/src/client/components/ui/checkbox.tsx +27 -0
- package/carbon/src/client/components/ui/collapsible.tsx +15 -0
- package/carbon/src/client/components/ui/command.tsx +178 -0
- package/carbon/src/client/components/ui/context-menu.tsx +233 -0
- package/carbon/src/client/components/ui/dialog.tsx +132 -0
- package/carbon/src/client/components/ui/drawer.tsx +118 -0
- package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
- package/carbon/src/client/components/ui/empty.tsx +94 -0
- package/carbon/src/client/components/ui/field.tsx +226 -0
- package/carbon/src/client/components/ui/hover-card.tsx +44 -0
- package/carbon/src/client/components/ui/input-group.tsx +146 -0
- package/carbon/src/client/components/ui/input-otp.tsx +83 -0
- package/carbon/src/client/components/ui/input.tsx +20 -0
- package/carbon/src/client/components/ui/item.tsx +188 -0
- package/carbon/src/client/components/ui/kbd.tsx +26 -0
- package/carbon/src/client/components/ui/label.tsx +21 -0
- package/carbon/src/client/components/ui/menubar.tsx +250 -0
- package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
- package/carbon/src/client/components/ui/pagination.tsx +102 -0
- package/carbon/src/client/components/ui/popover.tsx +75 -0
- package/carbon/src/client/components/ui/progress.tsx +66 -0
- package/carbon/src/client/components/ui/radio-group.tsx +36 -0
- package/carbon/src/client/components/ui/resizable.tsx +46 -0
- package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
- package/carbon/src/client/components/ui/select.tsx +186 -0
- package/carbon/src/client/components/ui/separator.tsx +21 -0
- package/carbon/src/client/components/ui/sheet.tsx +124 -0
- package/carbon/src/client/components/ui/sidebar.tsx +702 -0
- package/carbon/src/client/components/ui/skeleton.tsx +13 -0
- package/carbon/src/client/components/ui/slider.tsx +57 -0
- package/carbon/src/client/components/ui/sonner.tsx +45 -0
- package/carbon/src/client/components/ui/spinner.tsx +15 -0
- package/carbon/src/client/components/ui/switch.tsx +30 -0
- package/carbon/src/client/components/ui/table.tsx +89 -0
- package/carbon/src/client/components/ui/tabs.tsx +73 -0
- package/carbon/src/client/components/ui/textarea.tsx +18 -0
- package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
- package/carbon/src/client/components/ui/toggle.tsx +44 -0
- package/carbon/src/client/components/ui/tooltip.tsx +56 -0
- package/carbon/src/client/hooks/api/index.ts +14 -0
- package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
- package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
- package/carbon/src/client/hooks/use-mobile.ts +21 -0
- package/carbon/src/client/hooks/useMousePosition.ts +91 -0
- package/carbon/src/client/hooks/useNotifications.tsx +124 -0
- package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
- package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
- package/carbon/src/client/hooks/usePackageManager.ts +69 -0
- package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
- package/carbon/src/client/hooks/useRunningServices.ts +114 -0
- package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
- package/carbon/src/client/index.css +467 -0
- package/carbon/src/client/index.html +56 -0
- package/carbon/src/client/lib/admin-services.ts +151 -0
- package/carbon/src/client/lib/api.ts +32 -0
- package/carbon/src/client/lib/blog.ts +35 -0
- package/carbon/src/client/lib/changelog.ts +33 -0
- package/carbon/src/client/lib/docs-search.ts +101 -0
- package/carbon/src/client/lib/docs.ts +37 -0
- package/carbon/src/client/lib/i18n.ts +32 -0
- package/carbon/src/client/lib/supabase.ts +72 -0
- package/carbon/src/client/lib/tailwind-colors.ts +357 -0
- package/carbon/src/client/lib/theme.ts +117 -0
- package/carbon/src/client/lib/utils.ts +22 -0
- package/carbon/src/client/locales/de.json +529 -0
- package/carbon/src/client/locales/en.json +461 -0
- package/carbon/src/client/locales/es.json +529 -0
- package/carbon/src/client/locales/fr.json +529 -0
- package/carbon/src/client/locales/pt.json +529 -0
- package/carbon/src/client/main.tsx +56 -0
- package/carbon/src/client/mdx.d.ts +13 -0
- package/carbon/src/client/pages/ApiDocs.tsx +76 -0
- package/carbon/src/client/pages/AuthCallback.tsx +34 -0
- package/carbon/src/client/pages/Blog.tsx +167 -0
- package/carbon/src/client/pages/Changelog.tsx +171 -0
- package/carbon/src/client/pages/Charts.tsx +388 -0
- package/carbon/src/client/pages/Checkout.tsx +227 -0
- package/carbon/src/client/pages/Contact.tsx +174 -0
- package/carbon/src/client/pages/Dashboard.tsx +368 -0
- package/carbon/src/client/pages/Docs.tsx +372 -0
- package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
- package/carbon/src/client/pages/Home.tsx +187 -0
- package/carbon/src/client/pages/Legal.tsx +100 -0
- package/carbon/src/client/pages/Login.tsx +408 -0
- package/carbon/src/client/pages/MFAVerify.tsx +156 -0
- package/carbon/src/client/pages/NotFound.tsx +21 -0
- package/carbon/src/client/pages/Onboarding.tsx +246 -0
- package/carbon/src/client/pages/ResetPassword.tsx +200 -0
- package/carbon/src/client/pages/UIComponents.tsx +390 -0
- package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
- package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
- package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
- package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
- package/carbon/src/client/pages/admin/Logs.tsx +18 -0
- package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
- package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
- package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
- package/carbon/src/client/pages/admin/Settings.tsx +314 -0
- package/carbon/src/client/pages/admin/Theme.tsx +465 -0
- package/carbon/src/client/pages/admin/Users.tsx +365 -0
- package/carbon/src/client/pages/api-docs.css +156 -0
- package/carbon/src/client/pages/organizations/Details.tsx +200 -0
- package/carbon/src/client/pages/organizations/Members.tsx +402 -0
- package/carbon/src/client/pages/settings/Billing.tsx +473 -0
- package/carbon/src/client/pages/settings/Profile.tsx +160 -0
- package/carbon/src/client/pages/settings/Security.tsx +341 -0
- package/carbon/src/client/public/favicon.svg +19 -0
- package/carbon/src/client/public/robots.txt +8 -0
- package/carbon/src/server/billing/index.ts +104 -0
- package/carbon/src/server/billing/provider.ts +81 -0
- package/carbon/src/server/billing/providers/paddle.ts +314 -0
- package/carbon/src/server/billing/providers/polar.ts +325 -0
- package/carbon/src/server/billing/providers/stripe.ts +233 -0
- package/carbon/src/server/emails/templates.ts +116 -0
- package/carbon/src/server/index.ts +554 -0
- package/carbon/src/server/lib/auth.ts +6 -0
- package/carbon/src/server/lib/email.ts +64 -0
- package/carbon/src/server/lib/env.ts +112 -0
- package/carbon/src/server/lib/errors.ts +21 -0
- package/carbon/src/server/lib/logger.ts +17 -0
- package/carbon/src/server/lib/rate-limiter.ts +288 -0
- package/carbon/src/server/lib/request.ts +34 -0
- package/carbon/src/server/lib/stripe.ts +42 -0
- package/carbon/src/server/lib/supabase.ts +65 -0
- package/carbon/src/server/middleware/requirePlan.ts +80 -0
- package/carbon/src/server/routes/_internal/services-status.ts +958 -0
- package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
- package/carbon/src/server/routes/health.ts +48 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
- package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
- package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
- package/carbon/src/server/routes/v1/auth.ts +390 -0
- package/carbon/src/server/routes/v1/billing.ts +718 -0
- package/carbon/src/server/routes/v1/contact.ts +93 -0
- package/carbon/src/server/routes/v1/index.ts +1333 -0
- package/carbon/src/server/routes/v1/newsletter.ts +181 -0
- package/carbon/src/server/routes/v1/performance.ts +157 -0
- package/carbon/src/server/routes/v1/stats.ts +170 -0
- package/carbon/src/server/routes/v1/theme.ts +106 -0
- package/carbon/src/server/routes/webhooks/billing.ts +376 -0
- package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
- package/carbon/src/server/types.ts +11 -0
- package/carbon/src/shared/pricing.ts +155 -0
- package/carbon/src/shared/types.ts +338 -0
- package/carbon/supabase/migrations/00001_init.sql +717 -0
- package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
- package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
- package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
- package/carbon/supabase/seed.sql +16 -0
- package/carbon/tests/_helpers/app.ts +45 -0
- package/carbon/tests/_helpers/env.ts +37 -0
- package/carbon/tests/_helpers/factories.ts +69 -0
- package/carbon/tests/_helpers/jwt.ts +23 -0
- package/carbon/tests/_helpers/setup-integration.ts +20 -0
- package/carbon/tests/_helpers/setup-rtl.ts +12 -0
- package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
- package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
- package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
- package/carbon/tests/integration/server/routes/health.test.ts +61 -0
- package/carbon/tests/structural/i18n-parity.test.ts +42 -0
- package/carbon/tests/unit/client/utils.test.ts +49 -0
- package/carbon/tests/unit/shared/pricing.test.ts +93 -0
- package/carbon/tsconfig.json +27 -0
- package/carbon/tsconfig.server.json +27 -0
- package/carbon/tsconfig.test.json +9 -0
- package/carbon/vite.config.ts +110 -0
- package/carbon/vitest.config.ts +74 -0
- package/carbon/volumes/db/jwt.sql +57 -0
- package/carbon/volumes/db/metabase-init.sh +29 -0
- package/carbon/volumes/db/n8n-init.sh +25 -0
- package/carbon/volumes/db/realtime.sql +33 -0
- package/carbon/volumes/db/roles.sql +93 -0
- package/carbon/volumes/db/set-passwords.sh +12 -0
- package/carbon/volumes/db/super-admin.dev.sql +113 -0
- package/carbon/volumes/db/super-admin.generated.sql +113 -0
- package/carbon/volumes/db/super-admin.sql +114 -0
- package/carbon/volumes/grafana/dashboards/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards/overview.json +523 -0
- package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
- package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
- package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
- package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
- package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
- package/carbon/volumes/kong/kong.yml +208 -0
- package/carbon/volumes/loki/loki-config.yml +58 -0
- package/carbon/volumes/n8n/hooks.js +131 -0
- package/carbon/volumes/n8n/scripts/setup.sh +66 -0
- package/carbon/volumes/prometheus/prometheus.yml +43 -0
- package/carbon/volumes/promtail/promtail-config.yml +64 -0
- package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
- package/carbon/volumes/traefik/middlewares.yml +95 -0
- package/carbon/volumes/traefik/vite-dev.yml +20 -0
- package/package.json +95 -0
- package/src/access.js +354 -0
- package/src/activate.js +187 -0
- package/src/add.js +718 -0
- package/src/backup.js +786 -0
- package/src/cli.js +350 -0
- package/src/configure.js +967 -0
- package/src/console.js +155 -0
- package/src/create.js +1499 -0
- package/src/deploy.js +311 -0
- package/src/destroy.js +2033 -0
- package/src/diagnose.js +735 -0
- package/src/down.js +80 -0
- package/src/failover.js +1032 -0
- package/src/lib/backup-s3.js +179 -0
- package/src/lib/build.js +33 -0
- package/src/lib/checksum.js +28 -0
- package/src/lib/ci-setup.js +666 -0
- package/src/lib/cli/help.js +129 -0
- package/src/lib/cli/parse-flags.js +160 -0
- package/src/lib/cli/select-action.js +65 -0
- package/src/lib/cli/select-environment.js +108 -0
- package/src/lib/cli/tty-guard.js +75 -0
- package/src/lib/cloudflare.js +447 -0
- package/src/lib/colors.js +52 -0
- package/src/lib/command.js +361 -0
- package/src/lib/config.js +359 -0
- package/src/lib/cost.js +103 -0
- package/src/lib/deploy/bundle.js +231 -0
- package/src/lib/deploy/compose/acme-verify.js +121 -0
- package/src/lib/deploy/compose/build-args.js +78 -0
- package/src/lib/deploy/compose/ha.js +1874 -0
- package/src/lib/deploy/compose/index.js +1294 -0
- package/src/lib/deploy/compose/reconcile.js +74 -0
- package/src/lib/deploy/github.js +382 -0
- package/src/lib/deploy/image.js +191 -0
- package/src/lib/deploy/index.js +119 -0
- package/src/lib/deploy/k8s/LICENSE +99 -0
- package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
- package/src/lib/deploy/k8s/ha/index.js +1000 -0
- package/src/lib/deploy/k8s/index.js +62 -0
- package/src/lib/deploy/k8s/k3s.js +2515 -0
- package/src/lib/deploy/orchestrator.js +1401 -0
- package/src/lib/deploy/prompts.js +545 -0
- package/src/lib/deploy/remote-build.js +130 -0
- package/src/lib/deploy/state.js +120 -0
- package/src/lib/deploy/utils.js +328 -0
- package/src/lib/deploy-logger.js +93 -0
- package/src/lib/dns-propagation.js +48 -0
- package/src/lib/environment.js +58 -0
- package/src/lib/fetch-retry.js +103 -0
- package/src/lib/github-environments.js +335 -0
- package/src/lib/hetzner-dns.js +377 -0
- package/src/lib/hetzner-guided-setup.js +275 -0
- package/src/lib/host-keys.js +37 -0
- package/src/lib/iac/cloud-init.js +52 -0
- package/src/lib/iac/index.js +325 -0
- package/src/lib/iac/programs/hetzner-compose.js +130 -0
- package/src/lib/iac/programs/hetzner-k8s.js +320 -0
- package/src/lib/kubectl.js +81 -0
- package/src/lib/licensing/index.js +259 -0
- package/src/lib/licensing/tiers.js +181 -0
- package/src/lib/licensing/validator.js +171 -0
- package/src/lib/merge-package-json.js +90 -0
- package/src/lib/operator-ip.js +381 -0
- package/src/lib/package-manager.js +111 -0
- package/src/lib/perf.js +71 -0
- package/src/lib/project-guard.js +39 -0
- package/src/lib/project.js +334 -0
- package/src/lib/providers/base.js +276 -0
- package/src/lib/providers/hetzner-s3.js +656 -0
- package/src/lib/providers/hetzner.js +755 -0
- package/src/lib/providers/index.js +164 -0
- package/src/lib/s3.js +510 -0
- package/src/lib/secret-scan.js +583 -0
- package/src/lib/secrets.js +63 -0
- package/src/lib/server-types.js +195 -0
- package/src/lib/shell.js +91 -0
- package/src/lib/ssh.js +241 -0
- package/src/lib/tracker.js +242 -0
- package/src/lib/upgrade-policy.js +170 -0
- package/src/lib/validators.js +105 -0
- package/src/lib/version.js +5 -0
- package/src/remove.js +292 -0
- package/src/reset.js +97 -0
- package/src/restore.js +871 -0
- package/src/scale.js +1734 -0
- package/src/shell.js +222 -0
- package/src/status.js +981 -0
- package/src/up.js +264 -0
- package/src/upgrade.js +721 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delete Kubernetes manifests using Kustomize
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/k8s-delete.js # Delete local overlay
|
|
7
|
+
* node scripts/k8s-delete.js production-nbg1 # Delete specific overlay
|
|
8
|
+
* node scripts/k8s-delete.js --dry-run # Show what would be deleted
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { loadManifest, getEnabledFeatures, parseArgs } from './lib/manifest.js';
|
|
15
|
+
|
|
16
|
+
const args = parseArgs(process.argv.slice(2));
|
|
17
|
+
|
|
18
|
+
if (args.help) {
|
|
19
|
+
console.log(`
|
|
20
|
+
Usage: node scripts/k8s-delete.js [overlay] [options]
|
|
21
|
+
|
|
22
|
+
Arguments:
|
|
23
|
+
overlay Kustomize overlay to delete (default: local)
|
|
24
|
+
Available: local, production-nbg1, production-hel1
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--dry-run Show what would be deleted without executing
|
|
28
|
+
--verbose Verbose output
|
|
29
|
+
-h, --help Show this help message
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
node scripts/k8s-delete.js # Delete local overlay
|
|
33
|
+
node scripts/k8s-delete.js production-nbg1 # Delete production overlay
|
|
34
|
+
pnpm k8s:delete # Via package.json script
|
|
35
|
+
`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const manifest = loadManifest();
|
|
40
|
+
const overlay = args.positional[0] || 'local';
|
|
41
|
+
const overlayPath = join(process.cwd(), 'k8s', 'overlays', overlay);
|
|
42
|
+
|
|
43
|
+
if (!existsSync(overlayPath)) {
|
|
44
|
+
console.error(`Error: Overlay '${overlay}' not found at ${overlayPath}`);
|
|
45
|
+
console.error('Available overlays: local, production-nbg1, production-hel1');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const features = getEnabledFeatures(manifest);
|
|
50
|
+
if (args.verbose && features.length > 0) {
|
|
51
|
+
console.log('Enabled features:', features.join(', '));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build the kubectl delete command
|
|
55
|
+
const dryRunFlag = args.dryRun ? ' --dry-run=client' : '';
|
|
56
|
+
const cmd = `kubectl delete -k ${overlayPath}${dryRunFlag}`;
|
|
57
|
+
|
|
58
|
+
if (args.dryRun) {
|
|
59
|
+
console.log('Would run:', cmd);
|
|
60
|
+
console.log('\nDry run output:');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (args.verbose) {
|
|
64
|
+
console.log('Running:', cmd);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Exit code 1 from kubectl delete is common (resources not found), don't fail hard
|
|
71
|
+
if (args.verbose) {
|
|
72
|
+
console.log('kubectl delete exited with code:', error.status);
|
|
73
|
+
}
|
|
74
|
+
process.exit(error.status || 1);
|
|
75
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared manifest utilities for Vibecarbon scripts
|
|
3
|
+
* Reads .vibecarbon.json to determine which services are enabled
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Load the vibecarbon manifest from the current directory
|
|
11
|
+
* @returns {Object} The manifest object with version and services
|
|
12
|
+
*/
|
|
13
|
+
export function loadManifest() {
|
|
14
|
+
const path = join(process.cwd(), '.vibecarbon.json');
|
|
15
|
+
if (!existsSync(path)) {
|
|
16
|
+
return { version: '1', services: {} };
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get list of enabled service names from manifest
|
|
23
|
+
* Supports both "services" (new) and "features" (legacy) keys
|
|
24
|
+
* @param {Object} manifest - The vibecarbon manifest
|
|
25
|
+
* @returns {string[]} Array of service names
|
|
26
|
+
*/
|
|
27
|
+
export function getEnabledServices(manifest) {
|
|
28
|
+
// Support both "services" (new) and "features" (legacy)
|
|
29
|
+
const services = manifest.services || manifest.features || {};
|
|
30
|
+
return Object.keys(services);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Discover all optional service compose files in the project directory
|
|
35
|
+
* Excludes core files (prod, override, ha-*) and service-specific overrides
|
|
36
|
+
* @returns {string[]} Array of optional service compose file names
|
|
37
|
+
*/
|
|
38
|
+
export function discoverOptionalServices() {
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const corePatterns = [
|
|
41
|
+
'docker-compose.yml',
|
|
42
|
+
'docker-compose.prod.yml',
|
|
43
|
+
'docker-compose.override.yml',
|
|
44
|
+
/^docker-compose\.ha-.*\.yml$/,
|
|
45
|
+
/^docker-compose\..*\.override\.yml$/, // Service-specific overrides (handled separately)
|
|
46
|
+
/^docker-compose\..*\.prod\.yml$/, // Service-specific prod configs (use --prod flag)
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const files = readdirSync(cwd).filter((file) => {
|
|
50
|
+
if (!file.startsWith('docker-compose.') || !file.endsWith('.yml')) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Exclude core patterns
|
|
54
|
+
for (const pattern of corePatterns) {
|
|
55
|
+
if (typeof pattern === 'string' && file === pattern) return false;
|
|
56
|
+
if (pattern instanceof RegExp && pattern.test(file)) return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get list of Docker Compose files to use based on enabled services
|
|
66
|
+
* @param {Object} manifest - The vibecarbon manifest
|
|
67
|
+
* @param {Object} options - Options for file selection
|
|
68
|
+
* @param {boolean} options.prod - Include production overlay
|
|
69
|
+
* @param {boolean} options.all - Include all optional services (ignores manifest)
|
|
70
|
+
* @returns {string[]} Array of compose file paths
|
|
71
|
+
*/
|
|
72
|
+
export function getComposeFiles(manifest, options = {}) {
|
|
73
|
+
const files = ['docker-compose.yml'];
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
|
|
76
|
+
if (options.prod) {
|
|
77
|
+
files.push('docker-compose.prod.yml');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Collect service files (either from --all discovery or manifest)
|
|
81
|
+
let serviceFiles = [];
|
|
82
|
+
if (options.all) {
|
|
83
|
+
serviceFiles = discoverOptionalServices();
|
|
84
|
+
} else {
|
|
85
|
+
for (const service of getEnabledServices(manifest)) {
|
|
86
|
+
const serviceFile = `docker-compose.${service}.yml`;
|
|
87
|
+
if (existsSync(join(cwd, serviceFile))) {
|
|
88
|
+
serviceFiles.push(serviceFile);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add each service file, followed by its environment-specific override
|
|
94
|
+
for (const serviceFile of serviceFiles) {
|
|
95
|
+
files.push(serviceFile);
|
|
96
|
+
|
|
97
|
+
if (options.prod) {
|
|
98
|
+
// In prod mode, check for service-specific prod file
|
|
99
|
+
// e.g., docker-compose.observability.yml -> docker-compose.observability.prod.yml
|
|
100
|
+
const prodFile = serviceFile.replace('.yml', '.prod.yml');
|
|
101
|
+
if (existsSync(join(cwd, prodFile))) {
|
|
102
|
+
files.push(prodFile);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// In dev mode, check for service-specific override file
|
|
106
|
+
// e.g., docker-compose.observability.yml -> docker-compose.observability.override.yml
|
|
107
|
+
const overrideFile = serviceFile.replace('.yml', '.override.yml');
|
|
108
|
+
if (existsSync(join(cwd, overrideFile))) {
|
|
109
|
+
files.push(overrideFile);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// In dev mode, include the main override file LAST so it can override
|
|
115
|
+
// service-specific compose files with dev passwords/config
|
|
116
|
+
if (!options.prod && existsSync(join(cwd, 'docker-compose.override.yml'))) {
|
|
117
|
+
files.push('docker-compose.override.yml');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// dev-init.js generates this file to mount the admin SQL with real credentials
|
|
121
|
+
// (only exists when developing the CLI itself, not in derived projects)
|
|
122
|
+
if (!options.prod && existsSync(join(cwd, 'docker-compose.dev-init.yml'))) {
|
|
123
|
+
files.push('docker-compose.dev-init.yml');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return files;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if a specific service is enabled
|
|
131
|
+
* @param {Object} manifest - The vibecarbon manifest
|
|
132
|
+
* @param {string} serviceName - Name of the service to check
|
|
133
|
+
* @returns {boolean} True if service is enabled
|
|
134
|
+
*/
|
|
135
|
+
export function isServiceEnabled(manifest, serviceName) {
|
|
136
|
+
const services = manifest.services || manifest.features || {};
|
|
137
|
+
return Boolean(services[serviceName]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Build docker compose command with appropriate files
|
|
142
|
+
* @param {string} action - The compose action (up, down, logs, etc.)
|
|
143
|
+
* @param {Object} manifest - The vibecarbon manifest
|
|
144
|
+
* @param {Object} options - Options for command building
|
|
145
|
+
* @param {boolean} options.prod - Use production config
|
|
146
|
+
* @param {boolean} options.all - Include all optional services
|
|
147
|
+
* @param {string[]} options.extraArgs - Additional arguments
|
|
148
|
+
* @returns {string} The full docker compose command
|
|
149
|
+
*/
|
|
150
|
+
export function buildComposeCommand(action, manifest, options = {}) {
|
|
151
|
+
const files = getComposeFiles(manifest, { prod: options.prod, all: options.all });
|
|
152
|
+
const fileArgs = files.map((f) => `-f ${f}`).join(' ');
|
|
153
|
+
const extraArgs = options.extraArgs ? options.extraArgs.join(' ') : '';
|
|
154
|
+
|
|
155
|
+
return `docker compose ${fileArgs} ${action} ${extraArgs}`.trim();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Parse CLI arguments for common flags
|
|
160
|
+
* @param {string[]} args - CLI arguments
|
|
161
|
+
* @returns {Object} Parsed arguments
|
|
162
|
+
*/
|
|
163
|
+
export function parseArgs(args) {
|
|
164
|
+
return {
|
|
165
|
+
prod: args.includes('--prod'),
|
|
166
|
+
all: args.includes('--all'),
|
|
167
|
+
dryRun: args.includes('--dry-run'),
|
|
168
|
+
verbose: args.includes('--verbose') || args.includes('-v'),
|
|
169
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
170
|
+
positional: args.filter((arg) => !arg.startsWith('-')),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Legacy aliases for backwards compatibility
|
|
175
|
+
export const getEnabledFeatures = getEnabledServices;
|
|
176
|
+
export const isFeatureEnabled = isServiceEnabled;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Self-contained secret scanner for the vibecarbon-managed pre-commit
|
|
3
|
+
// hook. Mirrors the rules in src/lib/secret-scan.js (vibecarbon CLI)
|
|
4
|
+
// so the same allowlist + behaviour applies offline / without the CLI
|
|
5
|
+
// installed. Keep the rule set in sync — the test suite over there is
|
|
6
|
+
// the source of truth.
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// node scripts/secret-scan.mjs <file> [<file>...]
|
|
10
|
+
// node scripts/secret-scan.mjs --stdin # reads file paths from stdin, one per line
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const SECRET_RULES = [
|
|
16
|
+
{ id: 'aws-access-key', description: 'AWS access key ID', pattern: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
17
|
+
{
|
|
18
|
+
id: 'aws-secret-key',
|
|
19
|
+
description: 'AWS secret access key',
|
|
20
|
+
pattern:
|
|
21
|
+
/(?:aws[_-]?secret[_-]?access[_-]?key|secretAccessKey)\s*[:=]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/gi,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'github-pat',
|
|
25
|
+
description: 'GitHub personal access token',
|
|
26
|
+
pattern: /\bgh[posru]_[A-Za-z0-9]{36,}\b/g,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'stripe-live-key',
|
|
30
|
+
description: 'Stripe live secret key',
|
|
31
|
+
pattern: /\bsk_live_[A-Za-z0-9]{24,}\b/g,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'stripe-test-key',
|
|
35
|
+
description: 'Stripe test secret key',
|
|
36
|
+
pattern: /\bsk_test_[A-Za-z0-9]{24,}\b/g,
|
|
37
|
+
},
|
|
38
|
+
{ id: 'slack-token', description: 'Slack token', pattern: /\bxox[abprs]-[0-9A-Za-z-]{10,}\b/g },
|
|
39
|
+
{ id: 'openai-key', description: 'OpenAI API key', pattern: /\bsk-[A-Za-z0-9]{20,}\b/g },
|
|
40
|
+
{
|
|
41
|
+
id: 'anthropic-key',
|
|
42
|
+
description: 'Anthropic API key',
|
|
43
|
+
pattern: /\bsk-ant-[A-Za-z0-9_-]{40,}\b/g,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'google-service-account-json',
|
|
47
|
+
description: 'Google Cloud service account JSON',
|
|
48
|
+
pattern: /"type"\s*:\s*"service_account"/g,
|
|
49
|
+
context: /"private_key"\s*:\s*"-----BEGIN[ A-Z]+PRIVATE KEY-----/,
|
|
50
|
+
},
|
|
51
|
+
{ id: 'google-api-key', description: 'Google API key', pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
52
|
+
{
|
|
53
|
+
id: 'private-key-block',
|
|
54
|
+
description: 'PEM-encoded private key',
|
|
55
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'hetzner-token',
|
|
59
|
+
description: 'Hetzner Cloud API token',
|
|
60
|
+
pattern: /['"]([A-Za-z0-9]{64})['"]/g,
|
|
61
|
+
context: /(?:hetzner|hcloud|HCLOUD_TOKEN|HETZNER_API_TOKEN)/i,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'cloudflare-api-token',
|
|
65
|
+
description: 'Cloudflare scoped API token',
|
|
66
|
+
pattern: /\b[A-Za-z0-9_-]{40}\b/g,
|
|
67
|
+
context: /(?:cloudflare|cf[_-]?api[_-]?token|CLOUDFLARE_API_TOKEN)/i,
|
|
68
|
+
predicate: (m) => !/^[a-f0-9]+$/i.test(m),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'supabase-service-role-jwt',
|
|
72
|
+
description: 'Supabase service-role JWT',
|
|
73
|
+
pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
74
|
+
predicate: (m) => {
|
|
75
|
+
const parts = m.split('.');
|
|
76
|
+
if (parts.length !== 3) return false;
|
|
77
|
+
const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
78
|
+
const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
|
|
79
|
+
try {
|
|
80
|
+
const payload = JSON.parse(Buffer.from(padded, 'base64').toString('utf-8'));
|
|
81
|
+
return payload?.role === 'service_role' || payload?.role === 'admin';
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'generic-secret-assignment',
|
|
89
|
+
description: 'High-entropy value assigned to a secret-named variable',
|
|
90
|
+
pattern:
|
|
91
|
+
/(?:password|passwd|secret|token|api[_-]?key|access[_-]?key|private[_-]?key|credentials?)\s*[:=]\s*['"]([^'"\s]{20,})['"]/gi,
|
|
92
|
+
predicate: (_m, captured) => {
|
|
93
|
+
const value = captured?.[1];
|
|
94
|
+
if (!value) return false;
|
|
95
|
+
if (/^(?:REPLACE|TODO|CHANGE|YOUR|EXAMPLE|PLACEHOLDER|FIXME)/i.test(value)) return false;
|
|
96
|
+
if (/^(?:local|dev|test|sample|fake|demo)[-_]/i.test(value)) return false;
|
|
97
|
+
const freq = new Map();
|
|
98
|
+
for (const c of value) freq.set(c, (freq.get(c) ?? 0) + 1);
|
|
99
|
+
let h = 0;
|
|
100
|
+
for (const n of freq.values()) {
|
|
101
|
+
const p = n / value.length;
|
|
102
|
+
h -= p * Math.log2(p);
|
|
103
|
+
}
|
|
104
|
+
return h >= 3.5;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const SKIP_EXTS = new Set([
|
|
110
|
+
'.png',
|
|
111
|
+
'.jpg',
|
|
112
|
+
'.jpeg',
|
|
113
|
+
'.gif',
|
|
114
|
+
'.webp',
|
|
115
|
+
'.ico',
|
|
116
|
+
'.pdf',
|
|
117
|
+
'.zip',
|
|
118
|
+
'.gz',
|
|
119
|
+
'.tar',
|
|
120
|
+
'.tgz',
|
|
121
|
+
'.7z',
|
|
122
|
+
'.rar',
|
|
123
|
+
'.mp3',
|
|
124
|
+
'.mp4',
|
|
125
|
+
'.mov',
|
|
126
|
+
'.avi',
|
|
127
|
+
'.webm',
|
|
128
|
+
'.woff',
|
|
129
|
+
'.woff2',
|
|
130
|
+
'.ttf',
|
|
131
|
+
'.otf',
|
|
132
|
+
'.eot',
|
|
133
|
+
'.so',
|
|
134
|
+
'.dll',
|
|
135
|
+
'.dylib',
|
|
136
|
+
'.bin',
|
|
137
|
+
'.wasm',
|
|
138
|
+
]);
|
|
139
|
+
const MAX_FILE_BYTES = 1_048_576;
|
|
140
|
+
|
|
141
|
+
function loadAllowlist() {
|
|
142
|
+
try {
|
|
143
|
+
return readFileSync(join(process.cwd(), '.vibecarbonignore'), 'utf-8')
|
|
144
|
+
.split('\n')
|
|
145
|
+
.map((l) => l.trim())
|
|
146
|
+
.filter((l) => l && !l.startsWith('#'));
|
|
147
|
+
} catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function matchesAllowlist(value, allowlist) {
|
|
153
|
+
for (const entry of allowlist) {
|
|
154
|
+
if (entry.startsWith('regex:')) {
|
|
155
|
+
try {
|
|
156
|
+
if (new RegExp(entry.slice(6)).test(value)) return true;
|
|
157
|
+
} catch {}
|
|
158
|
+
} else if (value.includes(entry)) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function locate(content, offset) {
|
|
166
|
+
let line = 1;
|
|
167
|
+
let lineStart = 0;
|
|
168
|
+
for (let i = 0; i < offset; i++) {
|
|
169
|
+
if (content.charCodeAt(i) === 10) {
|
|
170
|
+
line++;
|
|
171
|
+
lineStart = i + 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { line, column: offset - lineStart + 1 };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function scanContent(content, allowlist) {
|
|
178
|
+
if (!content) return [];
|
|
179
|
+
const findings = [];
|
|
180
|
+
for (const rule of SECRET_RULES) {
|
|
181
|
+
rule.pattern.lastIndex = 0;
|
|
182
|
+
let m = rule.pattern.exec(content);
|
|
183
|
+
while (m !== null) {
|
|
184
|
+
const [whole] = m;
|
|
185
|
+
if (rule.context) {
|
|
186
|
+
const start = Math.max(0, m.index - 128);
|
|
187
|
+
const end = Math.min(content.length, m.index + whole.length + 128);
|
|
188
|
+
if (!rule.context.test(content.slice(start, end))) {
|
|
189
|
+
m = rule.pattern.exec(content);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (rule.predicate && !rule.predicate(whole, m)) {
|
|
194
|
+
m = rule.pattern.exec(content);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (matchesAllowlist(whole, allowlist)) {
|
|
198
|
+
m = rule.pattern.exec(content);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const { line, column } = locate(content, m.index);
|
|
202
|
+
findings.push({
|
|
203
|
+
ruleId: rule.id,
|
|
204
|
+
description: rule.description,
|
|
205
|
+
match: whole.length > 64 ? `${whole.slice(0, 32)}…${whole.slice(-16)}` : whole,
|
|
206
|
+
line,
|
|
207
|
+
column,
|
|
208
|
+
});
|
|
209
|
+
m = rule.pattern.exec(content);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return findings;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function readStdinList() {
|
|
216
|
+
const chunks = [];
|
|
217
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
218
|
+
return Buffer.concat(chunks)
|
|
219
|
+
.toString('utf-8')
|
|
220
|
+
.split('\n')
|
|
221
|
+
.map((l) => l.trim())
|
|
222
|
+
.filter(Boolean);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function main() {
|
|
226
|
+
const argv = process.argv.slice(2);
|
|
227
|
+
let files = [];
|
|
228
|
+
if (argv.includes('--stdin')) {
|
|
229
|
+
files = await readStdinList();
|
|
230
|
+
} else {
|
|
231
|
+
files = argv;
|
|
232
|
+
}
|
|
233
|
+
if (files.length === 0) process.exit(0);
|
|
234
|
+
|
|
235
|
+
const allowlist = loadAllowlist();
|
|
236
|
+
const failures = [];
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
if (!existsSync(file)) continue;
|
|
239
|
+
const ext = file.includes('.') ? file.slice(file.lastIndexOf('.')).toLowerCase() : '';
|
|
240
|
+
if (SKIP_EXTS.has(ext)) continue;
|
|
241
|
+
let stat;
|
|
242
|
+
try {
|
|
243
|
+
stat = statSync(file);
|
|
244
|
+
} catch {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (!stat.isFile() || stat.size > MAX_FILE_BYTES) continue;
|
|
248
|
+
let content;
|
|
249
|
+
try {
|
|
250
|
+
content = readFileSync(file, 'utf-8');
|
|
251
|
+
} catch {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const findings = scanContent(content, allowlist);
|
|
255
|
+
for (const f of findings) failures.push({ file, ...f });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (failures.length === 0) process.exit(0);
|
|
259
|
+
process.stderr.write(
|
|
260
|
+
`\n[X] Refusing commit: ${failures.length} potential secret${failures.length === 1 ? '' : 's'} detected:\n\n`,
|
|
261
|
+
);
|
|
262
|
+
for (const f of failures) {
|
|
263
|
+
process.stderr.write(` ${f.file}:${f.line}:${f.column} [${f.ruleId}] ${f.description}\n`);
|
|
264
|
+
process.stderr.write(` -> ${f.match}\n`);
|
|
265
|
+
}
|
|
266
|
+
process.stderr.write(
|
|
267
|
+
'\nIf a finding is a false positive, add it to .vibecarbonignore at the repo root:\n',
|
|
268
|
+
);
|
|
269
|
+
process.stderr.write(' - one entry per line\n');
|
|
270
|
+
process.stderr.write(' - literal substring match, OR `regex:<pattern>` for a regex\n');
|
|
271
|
+
process.stderr.write(' - `#` comments are ignored\n\n');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
main().catch((err) => {
|
|
276
|
+
process.stderr.write(`secret-scan: ${err.message}\n`);
|
|
277
|
+
process.exit(2);
|
|
278
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Validate that all template files have corresponding dev files
|
|
4
|
+
# Run in CI to catch missing dev configs
|
|
5
|
+
#
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
CARBON_DIR="$(dirname "$SCRIPT_DIR")"
|
|
10
|
+
|
|
11
|
+
cd "$CARBON_DIR"
|
|
12
|
+
|
|
13
|
+
errors=0
|
|
14
|
+
|
|
15
|
+
# Known template/dev file pairs that must exist
|
|
16
|
+
declare -a REQUIRED_PAIRS=(
|
|
17
|
+
# Grafana dashboards
|
|
18
|
+
"volumes/grafana/provisioning/dashboards/dashboards.yml:volumes/grafana/provisioning/dashboards/dashboards.dev.yml"
|
|
19
|
+
# Database initialization (only super-admin needs dev variant — roles/services use runtime env vars)
|
|
20
|
+
"volumes/db/super-admin.sql:volumes/db/super-admin.dev.sql"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Directories where each template file needs a dev counterpart
|
|
24
|
+
declare -a REQUIRED_DIR_PAIRS=(
|
|
25
|
+
"volumes/grafana/dashboards:volumes/grafana/dashboards.dev"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
echo "Validating dev config files..."
|
|
29
|
+
echo ""
|
|
30
|
+
|
|
31
|
+
# Check individual file pairs
|
|
32
|
+
echo "Required file pairs:"
|
|
33
|
+
for pair in "${REQUIRED_PAIRS[@]}"; do
|
|
34
|
+
template="${pair%%:*}"
|
|
35
|
+
dev_file="${pair##*:}"
|
|
36
|
+
|
|
37
|
+
if [[ ! -f "$template" ]]; then
|
|
38
|
+
echo " SKIP: $template (template not found)"
|
|
39
|
+
continue
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if [[ -f "$dev_file" ]]; then
|
|
43
|
+
echo " OK: $dev_file"
|
|
44
|
+
else
|
|
45
|
+
echo " MISSING: $dev_file"
|
|
46
|
+
((errors++))
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
echo ""
|
|
51
|
+
|
|
52
|
+
# Check directory pairs
|
|
53
|
+
echo "Required directory pairs:"
|
|
54
|
+
for dir_pair in "${REQUIRED_DIR_PAIRS[@]}"; do
|
|
55
|
+
template_dir="${dir_pair%%:*}"
|
|
56
|
+
dev_dir="${dir_pair##*:}"
|
|
57
|
+
|
|
58
|
+
if [[ ! -d "$template_dir" ]]; then
|
|
59
|
+
echo " SKIP: $template_dir (template dir not found)"
|
|
60
|
+
continue
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
for template_file in "$template_dir"/*; do
|
|
64
|
+
if [[ -f "$template_file" ]]; then
|
|
65
|
+
filename=$(basename "$template_file")
|
|
66
|
+
dev_file="$dev_dir/$filename"
|
|
67
|
+
|
|
68
|
+
if [[ -f "$dev_file" ]]; then
|
|
69
|
+
echo " OK: $dev_file"
|
|
70
|
+
else
|
|
71
|
+
echo " MISSING: $dev_file"
|
|
72
|
+
((errors++))
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
done
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
echo ""
|
|
79
|
+
|
|
80
|
+
# Check for template placeholders in mounted files
|
|
81
|
+
echo "Checking for unsubstituted placeholders in dev files..."
|
|
82
|
+
dev_files_with_placeholders=$(find volumes -name "*.dev.*" -o -name "*.dev" -type f 2>/dev/null | xargs grep -l '{{[A-Z_]*}}' 2>/dev/null || true)
|
|
83
|
+
|
|
84
|
+
if [[ -n "$dev_files_with_placeholders" ]]; then
|
|
85
|
+
echo " WARNING: Dev files still contain template placeholders:"
|
|
86
|
+
echo "$dev_files_with_placeholders" | while read -r f; do
|
|
87
|
+
echo " $f"
|
|
88
|
+
done
|
|
89
|
+
((errors++))
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo ""
|
|
93
|
+
|
|
94
|
+
if [[ $errors -gt 0 ]]; then
|
|
95
|
+
echo "FAILED: $errors issue(s) found"
|
|
96
|
+
echo "Run ./scripts/generate-dev-configs.sh to fix"
|
|
97
|
+
exit 1
|
|
98
|
+
else
|
|
99
|
+
echo "PASSED: All dev configs present"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|