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.
Files changed (495) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +188 -0
  3. package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
  4. package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
  5. package/carbon/.claude/settings.json +29 -0
  6. package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
  7. package/carbon/.dockerignore +57 -0
  8. package/carbon/.env.example +219 -0
  9. package/carbon/.github/copilot-instructions.md +3 -0
  10. package/carbon/.github/workflows/deploy.yml +346 -0
  11. package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
  12. package/carbon/.windsurfrules +74 -0
  13. package/carbon/AGENTS.md +422 -0
  14. package/carbon/CLAUDE.md +3 -0
  15. package/carbon/DEVELOPMENT.md +187 -0
  16. package/carbon/Dockerfile +98 -0
  17. package/carbon/LICENSE +21 -0
  18. package/carbon/PRODUCTION.md +364 -0
  19. package/carbon/README.md +193 -0
  20. package/carbon/TESTING.md +138 -0
  21. package/carbon/backup/Dockerfile +75 -0
  22. package/carbon/backup/backup.sh +95 -0
  23. package/carbon/backup/compose-backup.sh +140 -0
  24. package/carbon/biome.json +83 -0
  25. package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
  26. package/carbon/cloud-init/k3s/master-init.sh +215 -0
  27. package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
  28. package/carbon/cloud-init/k3s/worker-init.sh +147 -0
  29. package/carbon/components.json +24 -0
  30. package/carbon/content/blog/authentication-guide.mdx +34 -0
  31. package/carbon/content/blog/getting-started.mdx +41 -0
  32. package/carbon/content/changelog/v0-1-0.mdx +40 -0
  33. package/carbon/content/docs/analytics.mdx +90 -0
  34. package/carbon/content/docs/authentication.mdx +231 -0
  35. package/carbon/content/docs/background-jobs.mdx +116 -0
  36. package/carbon/content/docs/cli.mdx +630 -0
  37. package/carbon/content/docs/database.mdx +236 -0
  38. package/carbon/content/docs/deployment.mdx +227 -0
  39. package/carbon/content/docs/development.mdx +238 -0
  40. package/carbon/content/docs/environments.mdx +84 -0
  41. package/carbon/content/docs/getting-started.mdx +112 -0
  42. package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
  43. package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
  44. package/carbon/content/docs/optional-services.mdx +160 -0
  45. package/carbon/db/Dockerfile +23 -0
  46. package/carbon/docker-compose.dev-init.yml +5 -0
  47. package/carbon/docker-compose.metabase.override.yml +14 -0
  48. package/carbon/docker-compose.metabase.prod.yml +28 -0
  49. package/carbon/docker-compose.metabase.yml +84 -0
  50. package/carbon/docker-compose.n8n.override.yml +14 -0
  51. package/carbon/docker-compose.n8n.prod.yml +31 -0
  52. package/carbon/docker-compose.n8n.yml +97 -0
  53. package/carbon/docker-compose.observability.override.yml +18 -0
  54. package/carbon/docker-compose.observability.prod.yml +28 -0
  55. package/carbon/docker-compose.observability.yml +125 -0
  56. package/carbon/docker-compose.override.yml +28 -0
  57. package/carbon/docker-compose.prod.yml +294 -0
  58. package/carbon/docker-compose.yml +508 -0
  59. package/carbon/docker-entrypoint.sh +12 -0
  60. package/carbon/ha/activate-standby.sh +52 -0
  61. package/carbon/ha/primary-init.sql +36 -0
  62. package/carbon/ha/standby-init.sh +74 -0
  63. package/carbon/k8s/LICENSE +99 -0
  64. package/carbon/k8s/README.md +272 -0
  65. package/carbon/k8s/base/app/deployment.yaml +130 -0
  66. package/carbon/k8s/base/app/hpa.yaml +44 -0
  67. package/carbon/k8s/base/app/kustomization.yaml +10 -0
  68. package/carbon/k8s/base/app/network-policy.yaml +124 -0
  69. package/carbon/k8s/base/app/pdb.yaml +14 -0
  70. package/carbon/k8s/base/app/rbac.yaml +36 -0
  71. package/carbon/k8s/base/app/service.yaml +16 -0
  72. package/carbon/k8s/base/backup/cronjob.yaml +120 -0
  73. package/carbon/k8s/base/backup/kustomization.yaml +7 -0
  74. package/carbon/k8s/base/backup/network-policy.yaml +31 -0
  75. package/carbon/k8s/base/backup/rbac.yaml +7 -0
  76. package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
  77. package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
  78. package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
  79. package/carbon/k8s/base/config/configmap.yaml +48 -0
  80. package/carbon/k8s/base/config/kustomization.yaml +8 -0
  81. package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
  82. package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
  83. package/carbon/k8s/base/kustomization.yaml +43 -0
  84. package/carbon/k8s/base/namespace.yaml +7 -0
  85. package/carbon/k8s/base/network-policies.yaml +95 -0
  86. package/carbon/k8s/base/registry/kustomization.yaml +5 -0
  87. package/carbon/k8s/base/registry/local-registry.yaml +193 -0
  88. package/carbon/k8s/base/traefik/certificate.yaml +21 -0
  89. package/carbon/k8s/base/traefik/configmap.yaml +13 -0
  90. package/carbon/k8s/base/traefik/deployment.yaml +165 -0
  91. package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
  92. package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
  93. package/carbon/k8s/base/traefik/middleware.yaml +109 -0
  94. package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
  95. package/carbon/k8s/base/traefik/service.yaml +38 -0
  96. package/carbon/k8s/flux/README.md +49 -0
  97. package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
  98. package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
  99. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
  100. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
  101. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
  102. package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
  103. package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
  104. package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
  105. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
  106. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
  107. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
  108. package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
  109. package/carbon/k8s/infra/kustomization.yaml +21 -0
  110. package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
  111. package/carbon/k8s/overlays/local/configmap.yaml +23 -0
  112. package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
  113. package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
  114. package/carbon/k8s/overlays/local/namespace.yaml +8 -0
  115. package/carbon/k8s/overlays/local/secrets.yaml +32 -0
  116. package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
  117. package/carbon/k8s/test-local.sh +318 -0
  118. package/carbon/k8s/values/supabase.values.yaml +133 -0
  119. package/carbon/package.json +154 -0
  120. package/carbon/runtime.Dockerfile +17 -0
  121. package/carbon/scripts/_dev-jwt.mjs +45 -0
  122. package/carbon/scripts/check-rls.ts +53 -0
  123. package/carbon/scripts/dev-init.js +191 -0
  124. package/carbon/scripts/dev.js +191 -0
  125. package/carbon/scripts/docker-down.js +63 -0
  126. package/carbon/scripts/docker-logs.js +59 -0
  127. package/carbon/scripts/docker-up.js +222 -0
  128. package/carbon/scripts/generate-dev-configs.sh +131 -0
  129. package/carbon/scripts/generate-rss.ts +116 -0
  130. package/carbon/scripts/generate-sitemap.ts +102 -0
  131. package/carbon/scripts/k8s-apply.js +71 -0
  132. package/carbon/scripts/k8s-delete.js +75 -0
  133. package/carbon/scripts/lib/manifest.js +176 -0
  134. package/carbon/scripts/secret-scan.mjs +278 -0
  135. package/carbon/scripts/validate-dev-configs.sh +101 -0
  136. package/carbon/src/client/App.tsx +202 -0
  137. package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
  138. package/carbon/src/client/assets/hyperformant-light.svg +29 -0
  139. package/carbon/src/client/assets/logos/aider.svg +1 -0
  140. package/carbon/src/client/assets/logos/antigravity.svg +1 -0
  141. package/carbon/src/client/assets/logos/bolt.svg +1 -0
  142. package/carbon/src/client/assets/logos/claude-code.svg +1 -0
  143. package/carbon/src/client/assets/logos/copilot.svg +1 -0
  144. package/carbon/src/client/assets/logos/cursor.svg +1 -0
  145. package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
  146. package/carbon/src/client/assets/logos/lovable.svg +1 -0
  147. package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
  148. package/carbon/src/client/assets/logos/v0.svg +1 -0
  149. package/carbon/src/client/assets/logos/windsurf.svg +1 -0
  150. package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
  151. package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
  152. package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
  153. package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
  154. package/carbon/src/client/components/AppSidebar.tsx +760 -0
  155. package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
  156. package/carbon/src/client/components/CTAFooter.tsx +59 -0
  157. package/carbon/src/client/components/ComparisonSection.tsx +132 -0
  158. package/carbon/src/client/components/ContentPanel.tsx +66 -0
  159. package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
  160. package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
  161. package/carbon/src/client/components/FAQSection.tsx +76 -0
  162. package/carbon/src/client/components/FileUpload.tsx +210 -0
  163. package/carbon/src/client/components/HeaderActions.tsx +17 -0
  164. package/carbon/src/client/components/Hero.tsx +608 -0
  165. package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
  166. package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
  167. package/carbon/src/client/components/Logo.tsx +87 -0
  168. package/carbon/src/client/components/LogoStrip.tsx +76 -0
  169. package/carbon/src/client/components/MetricsStrip.tsx +48 -0
  170. package/carbon/src/client/components/Nav.tsx +195 -0
  171. package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
  172. package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
  173. package/carbon/src/client/components/PageHeader.tsx +24 -0
  174. package/carbon/src/client/components/PlanGate.tsx +36 -0
  175. package/carbon/src/client/components/PricingSection.tsx +371 -0
  176. package/carbon/src/client/components/SEO.tsx +34 -0
  177. package/carbon/src/client/components/SmoothScroll.tsx +45 -0
  178. package/carbon/src/client/components/TechStackSection.tsx +165 -0
  179. package/carbon/src/client/components/WorkflowSection.tsx +604 -0
  180. package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
  181. package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
  182. package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
  183. package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
  184. package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
  185. package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
  186. package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
  187. package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
  188. package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
  189. package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
  190. package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
  191. package/carbon/src/client/components/effects/index.ts +2 -0
  192. package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
  193. package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
  194. package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
  195. package/carbon/src/client/components/scroll/index.ts +2 -0
  196. package/carbon/src/client/components/ui/accordion.tsx +71 -0
  197. package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
  198. package/carbon/src/client/components/ui/alert.tsx +73 -0
  199. package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
  200. package/carbon/src/client/components/ui/avatar.tsx +91 -0
  201. package/carbon/src/client/components/ui/badge.tsx +50 -0
  202. package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
  203. package/carbon/src/client/components/ui/button-group.tsx +78 -0
  204. package/carbon/src/client/components/ui/button.tsx +120 -0
  205. package/carbon/src/client/components/ui/calendar.tsx +182 -0
  206. package/carbon/src/client/components/ui/card.tsx +85 -0
  207. package/carbon/src/client/components/ui/carousel.tsx +227 -0
  208. package/carbon/src/client/components/ui/chart.tsx +357 -0
  209. package/carbon/src/client/components/ui/checkbox.tsx +27 -0
  210. package/carbon/src/client/components/ui/collapsible.tsx +15 -0
  211. package/carbon/src/client/components/ui/command.tsx +178 -0
  212. package/carbon/src/client/components/ui/context-menu.tsx +233 -0
  213. package/carbon/src/client/components/ui/dialog.tsx +132 -0
  214. package/carbon/src/client/components/ui/drawer.tsx +118 -0
  215. package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
  216. package/carbon/src/client/components/ui/empty.tsx +94 -0
  217. package/carbon/src/client/components/ui/field.tsx +226 -0
  218. package/carbon/src/client/components/ui/hover-card.tsx +44 -0
  219. package/carbon/src/client/components/ui/input-group.tsx +146 -0
  220. package/carbon/src/client/components/ui/input-otp.tsx +83 -0
  221. package/carbon/src/client/components/ui/input.tsx +20 -0
  222. package/carbon/src/client/components/ui/item.tsx +188 -0
  223. package/carbon/src/client/components/ui/kbd.tsx +26 -0
  224. package/carbon/src/client/components/ui/label.tsx +21 -0
  225. package/carbon/src/client/components/ui/menubar.tsx +250 -0
  226. package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
  227. package/carbon/src/client/components/ui/pagination.tsx +102 -0
  228. package/carbon/src/client/components/ui/popover.tsx +75 -0
  229. package/carbon/src/client/components/ui/progress.tsx +66 -0
  230. package/carbon/src/client/components/ui/radio-group.tsx +36 -0
  231. package/carbon/src/client/components/ui/resizable.tsx +46 -0
  232. package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
  233. package/carbon/src/client/components/ui/select.tsx +186 -0
  234. package/carbon/src/client/components/ui/separator.tsx +21 -0
  235. package/carbon/src/client/components/ui/sheet.tsx +124 -0
  236. package/carbon/src/client/components/ui/sidebar.tsx +702 -0
  237. package/carbon/src/client/components/ui/skeleton.tsx +13 -0
  238. package/carbon/src/client/components/ui/slider.tsx +57 -0
  239. package/carbon/src/client/components/ui/sonner.tsx +45 -0
  240. package/carbon/src/client/components/ui/spinner.tsx +15 -0
  241. package/carbon/src/client/components/ui/switch.tsx +30 -0
  242. package/carbon/src/client/components/ui/table.tsx +89 -0
  243. package/carbon/src/client/components/ui/tabs.tsx +73 -0
  244. package/carbon/src/client/components/ui/textarea.tsx +18 -0
  245. package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
  246. package/carbon/src/client/components/ui/toggle.tsx +44 -0
  247. package/carbon/src/client/components/ui/tooltip.tsx +56 -0
  248. package/carbon/src/client/hooks/api/index.ts +14 -0
  249. package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
  250. package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
  251. package/carbon/src/client/hooks/use-mobile.ts +21 -0
  252. package/carbon/src/client/hooks/useMousePosition.ts +91 -0
  253. package/carbon/src/client/hooks/useNotifications.tsx +124 -0
  254. package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
  255. package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
  256. package/carbon/src/client/hooks/usePackageManager.ts +69 -0
  257. package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
  258. package/carbon/src/client/hooks/useRunningServices.ts +114 -0
  259. package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
  260. package/carbon/src/client/index.css +467 -0
  261. package/carbon/src/client/index.html +56 -0
  262. package/carbon/src/client/lib/admin-services.ts +151 -0
  263. package/carbon/src/client/lib/api.ts +32 -0
  264. package/carbon/src/client/lib/blog.ts +35 -0
  265. package/carbon/src/client/lib/changelog.ts +33 -0
  266. package/carbon/src/client/lib/docs-search.ts +101 -0
  267. package/carbon/src/client/lib/docs.ts +37 -0
  268. package/carbon/src/client/lib/i18n.ts +32 -0
  269. package/carbon/src/client/lib/supabase.ts +72 -0
  270. package/carbon/src/client/lib/tailwind-colors.ts +357 -0
  271. package/carbon/src/client/lib/theme.ts +117 -0
  272. package/carbon/src/client/lib/utils.ts +22 -0
  273. package/carbon/src/client/locales/de.json +529 -0
  274. package/carbon/src/client/locales/en.json +461 -0
  275. package/carbon/src/client/locales/es.json +529 -0
  276. package/carbon/src/client/locales/fr.json +529 -0
  277. package/carbon/src/client/locales/pt.json +529 -0
  278. package/carbon/src/client/main.tsx +56 -0
  279. package/carbon/src/client/mdx.d.ts +13 -0
  280. package/carbon/src/client/pages/ApiDocs.tsx +76 -0
  281. package/carbon/src/client/pages/AuthCallback.tsx +34 -0
  282. package/carbon/src/client/pages/Blog.tsx +167 -0
  283. package/carbon/src/client/pages/Changelog.tsx +171 -0
  284. package/carbon/src/client/pages/Charts.tsx +388 -0
  285. package/carbon/src/client/pages/Checkout.tsx +227 -0
  286. package/carbon/src/client/pages/Contact.tsx +174 -0
  287. package/carbon/src/client/pages/Dashboard.tsx +368 -0
  288. package/carbon/src/client/pages/Docs.tsx +372 -0
  289. package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
  290. package/carbon/src/client/pages/Home.tsx +187 -0
  291. package/carbon/src/client/pages/Legal.tsx +100 -0
  292. package/carbon/src/client/pages/Login.tsx +408 -0
  293. package/carbon/src/client/pages/MFAVerify.tsx +156 -0
  294. package/carbon/src/client/pages/NotFound.tsx +21 -0
  295. package/carbon/src/client/pages/Onboarding.tsx +246 -0
  296. package/carbon/src/client/pages/ResetPassword.tsx +200 -0
  297. package/carbon/src/client/pages/UIComponents.tsx +390 -0
  298. package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
  299. package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
  300. package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
  301. package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
  302. package/carbon/src/client/pages/admin/Logs.tsx +18 -0
  303. package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
  304. package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
  305. package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
  306. package/carbon/src/client/pages/admin/Settings.tsx +314 -0
  307. package/carbon/src/client/pages/admin/Theme.tsx +465 -0
  308. package/carbon/src/client/pages/admin/Users.tsx +365 -0
  309. package/carbon/src/client/pages/api-docs.css +156 -0
  310. package/carbon/src/client/pages/organizations/Details.tsx +200 -0
  311. package/carbon/src/client/pages/organizations/Members.tsx +402 -0
  312. package/carbon/src/client/pages/settings/Billing.tsx +473 -0
  313. package/carbon/src/client/pages/settings/Profile.tsx +160 -0
  314. package/carbon/src/client/pages/settings/Security.tsx +341 -0
  315. package/carbon/src/client/public/favicon.svg +19 -0
  316. package/carbon/src/client/public/robots.txt +8 -0
  317. package/carbon/src/server/billing/index.ts +104 -0
  318. package/carbon/src/server/billing/provider.ts +81 -0
  319. package/carbon/src/server/billing/providers/paddle.ts +314 -0
  320. package/carbon/src/server/billing/providers/polar.ts +325 -0
  321. package/carbon/src/server/billing/providers/stripe.ts +233 -0
  322. package/carbon/src/server/emails/templates.ts +116 -0
  323. package/carbon/src/server/index.ts +554 -0
  324. package/carbon/src/server/lib/auth.ts +6 -0
  325. package/carbon/src/server/lib/email.ts +64 -0
  326. package/carbon/src/server/lib/env.ts +112 -0
  327. package/carbon/src/server/lib/errors.ts +21 -0
  328. package/carbon/src/server/lib/logger.ts +17 -0
  329. package/carbon/src/server/lib/rate-limiter.ts +288 -0
  330. package/carbon/src/server/lib/request.ts +34 -0
  331. package/carbon/src/server/lib/stripe.ts +42 -0
  332. package/carbon/src/server/lib/supabase.ts +65 -0
  333. package/carbon/src/server/middleware/requirePlan.ts +80 -0
  334. package/carbon/src/server/routes/_internal/services-status.ts +958 -0
  335. package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
  336. package/carbon/src/server/routes/health.ts +48 -0
  337. package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
  338. package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
  339. package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
  340. package/carbon/src/server/routes/v1/auth.ts +390 -0
  341. package/carbon/src/server/routes/v1/billing.ts +718 -0
  342. package/carbon/src/server/routes/v1/contact.ts +93 -0
  343. package/carbon/src/server/routes/v1/index.ts +1333 -0
  344. package/carbon/src/server/routes/v1/newsletter.ts +181 -0
  345. package/carbon/src/server/routes/v1/performance.ts +157 -0
  346. package/carbon/src/server/routes/v1/stats.ts +170 -0
  347. package/carbon/src/server/routes/v1/theme.ts +106 -0
  348. package/carbon/src/server/routes/webhooks/billing.ts +376 -0
  349. package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
  350. package/carbon/src/server/types.ts +11 -0
  351. package/carbon/src/shared/pricing.ts +155 -0
  352. package/carbon/src/shared/types.ts +338 -0
  353. package/carbon/supabase/migrations/00001_init.sql +717 -0
  354. package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
  355. package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
  356. package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
  357. package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
  358. package/carbon/supabase/seed.sql +16 -0
  359. package/carbon/tests/_helpers/app.ts +45 -0
  360. package/carbon/tests/_helpers/env.ts +37 -0
  361. package/carbon/tests/_helpers/factories.ts +69 -0
  362. package/carbon/tests/_helpers/jwt.ts +23 -0
  363. package/carbon/tests/_helpers/setup-integration.ts +20 -0
  364. package/carbon/tests/_helpers/setup-rtl.ts +12 -0
  365. package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
  366. package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
  367. package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
  368. package/carbon/tests/integration/server/routes/health.test.ts +61 -0
  369. package/carbon/tests/structural/i18n-parity.test.ts +42 -0
  370. package/carbon/tests/unit/client/utils.test.ts +49 -0
  371. package/carbon/tests/unit/shared/pricing.test.ts +93 -0
  372. package/carbon/tsconfig.json +27 -0
  373. package/carbon/tsconfig.server.json +27 -0
  374. package/carbon/tsconfig.test.json +9 -0
  375. package/carbon/vite.config.ts +110 -0
  376. package/carbon/vitest.config.ts +74 -0
  377. package/carbon/volumes/db/jwt.sql +57 -0
  378. package/carbon/volumes/db/metabase-init.sh +29 -0
  379. package/carbon/volumes/db/n8n-init.sh +25 -0
  380. package/carbon/volumes/db/realtime.sql +33 -0
  381. package/carbon/volumes/db/roles.sql +93 -0
  382. package/carbon/volumes/db/set-passwords.sh +12 -0
  383. package/carbon/volumes/db/super-admin.dev.sql +113 -0
  384. package/carbon/volumes/db/super-admin.generated.sql +113 -0
  385. package/carbon/volumes/db/super-admin.sql +114 -0
  386. package/carbon/volumes/grafana/dashboards/logs.json +179 -0
  387. package/carbon/volumes/grafana/dashboards/overview.json +523 -0
  388. package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
  389. package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
  390. package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
  391. package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
  392. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
  393. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
  394. package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
  395. package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
  396. package/carbon/volumes/kong/kong.yml +208 -0
  397. package/carbon/volumes/loki/loki-config.yml +58 -0
  398. package/carbon/volumes/n8n/hooks.js +131 -0
  399. package/carbon/volumes/n8n/scripts/setup.sh +66 -0
  400. package/carbon/volumes/prometheus/prometheus.yml +43 -0
  401. package/carbon/volumes/promtail/promtail-config.yml +64 -0
  402. package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
  403. package/carbon/volumes/traefik/middlewares.yml +95 -0
  404. package/carbon/volumes/traefik/vite-dev.yml +20 -0
  405. package/package.json +95 -0
  406. package/src/access.js +354 -0
  407. package/src/activate.js +187 -0
  408. package/src/add.js +718 -0
  409. package/src/backup.js +786 -0
  410. package/src/cli.js +350 -0
  411. package/src/configure.js +967 -0
  412. package/src/console.js +155 -0
  413. package/src/create.js +1499 -0
  414. package/src/deploy.js +311 -0
  415. package/src/destroy.js +2033 -0
  416. package/src/diagnose.js +735 -0
  417. package/src/down.js +80 -0
  418. package/src/failover.js +1032 -0
  419. package/src/lib/backup-s3.js +179 -0
  420. package/src/lib/build.js +33 -0
  421. package/src/lib/checksum.js +28 -0
  422. package/src/lib/ci-setup.js +666 -0
  423. package/src/lib/cli/help.js +129 -0
  424. package/src/lib/cli/parse-flags.js +160 -0
  425. package/src/lib/cli/select-action.js +65 -0
  426. package/src/lib/cli/select-environment.js +108 -0
  427. package/src/lib/cli/tty-guard.js +75 -0
  428. package/src/lib/cloudflare.js +447 -0
  429. package/src/lib/colors.js +52 -0
  430. package/src/lib/command.js +361 -0
  431. package/src/lib/config.js +359 -0
  432. package/src/lib/cost.js +103 -0
  433. package/src/lib/deploy/bundle.js +231 -0
  434. package/src/lib/deploy/compose/acme-verify.js +121 -0
  435. package/src/lib/deploy/compose/build-args.js +78 -0
  436. package/src/lib/deploy/compose/ha.js +1874 -0
  437. package/src/lib/deploy/compose/index.js +1294 -0
  438. package/src/lib/deploy/compose/reconcile.js +74 -0
  439. package/src/lib/deploy/github.js +382 -0
  440. package/src/lib/deploy/image.js +191 -0
  441. package/src/lib/deploy/index.js +119 -0
  442. package/src/lib/deploy/k8s/LICENSE +99 -0
  443. package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
  444. package/src/lib/deploy/k8s/ha/index.js +1000 -0
  445. package/src/lib/deploy/k8s/index.js +62 -0
  446. package/src/lib/deploy/k8s/k3s.js +2515 -0
  447. package/src/lib/deploy/orchestrator.js +1401 -0
  448. package/src/lib/deploy/prompts.js +545 -0
  449. package/src/lib/deploy/remote-build.js +130 -0
  450. package/src/lib/deploy/state.js +120 -0
  451. package/src/lib/deploy/utils.js +328 -0
  452. package/src/lib/deploy-logger.js +93 -0
  453. package/src/lib/dns-propagation.js +48 -0
  454. package/src/lib/environment.js +58 -0
  455. package/src/lib/fetch-retry.js +103 -0
  456. package/src/lib/github-environments.js +335 -0
  457. package/src/lib/hetzner-dns.js +377 -0
  458. package/src/lib/hetzner-guided-setup.js +275 -0
  459. package/src/lib/host-keys.js +37 -0
  460. package/src/lib/iac/cloud-init.js +52 -0
  461. package/src/lib/iac/index.js +325 -0
  462. package/src/lib/iac/programs/hetzner-compose.js +130 -0
  463. package/src/lib/iac/programs/hetzner-k8s.js +320 -0
  464. package/src/lib/kubectl.js +81 -0
  465. package/src/lib/licensing/index.js +259 -0
  466. package/src/lib/licensing/tiers.js +181 -0
  467. package/src/lib/licensing/validator.js +171 -0
  468. package/src/lib/merge-package-json.js +90 -0
  469. package/src/lib/operator-ip.js +381 -0
  470. package/src/lib/package-manager.js +111 -0
  471. package/src/lib/perf.js +71 -0
  472. package/src/lib/project-guard.js +39 -0
  473. package/src/lib/project.js +334 -0
  474. package/src/lib/providers/base.js +276 -0
  475. package/src/lib/providers/hetzner-s3.js +656 -0
  476. package/src/lib/providers/hetzner.js +755 -0
  477. package/src/lib/providers/index.js +164 -0
  478. package/src/lib/s3.js +510 -0
  479. package/src/lib/secret-scan.js +583 -0
  480. package/src/lib/secrets.js +63 -0
  481. package/src/lib/server-types.js +195 -0
  482. package/src/lib/shell.js +91 -0
  483. package/src/lib/ssh.js +241 -0
  484. package/src/lib/tracker.js +242 -0
  485. package/src/lib/upgrade-policy.js +170 -0
  486. package/src/lib/validators.js +105 -0
  487. package/src/lib/version.js +5 -0
  488. package/src/remove.js +292 -0
  489. package/src/reset.js +97 -0
  490. package/src/restore.js +871 -0
  491. package/src/scale.js +1734 -0
  492. package/src/shell.js +222 -0
  493. package/src/status.js +981 -0
  494. package/src/up.js +264 -0
  495. package/src/upgrade.js +721 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Pulumi program for a single Hetzner k3s cluster.
3
+ *
4
+ * One call to `buildHetznerK8sProgram(config)` returns a Pulumi inline
5
+ * program (a function) that declares the cloud resources. The Automation
6
+ * API wrapper in `../index.js` runs it.
7
+ *
8
+ * Resources declared:
9
+ * - SshKey (optional — reuse existing by ID for HA)
10
+ * - Network + Subnet
11
+ * - Firewall
12
+ * - Floating IP
13
+ * - Master server (cloud-init: master-init.sh)
14
+ * - Supabase server (cloud-init: supabase-init.sh)
15
+ * - Worker servers × N (cloud-init: worker-init.sh)
16
+ * - Placement group for workers (spread)
17
+ *
18
+ * Outputs: masterIp, masterPrivateIp, supabaseIp, workerIps, floatingIp,
19
+ * networkId, sshKeyId, k3sToken.
20
+ */
21
+
22
+ import { randomBytes } from 'node:crypto';
23
+ import * as hcloud from '@pulumi/hcloud';
24
+ import * as pulumi from '@pulumi/pulumi';
25
+ import { loadCloudInit, renderScript } from '../cloud-init.js';
26
+
27
+ /**
28
+ * @typedef {Object} K8sStackConfig
29
+ * @property {string} projectName
30
+ * @property {string} environment Environment name (used for cluster name + labels)
31
+ * @property {string} sshPublicKey OpenSSH-format public key
32
+ * @property {string} [existingSshKeyId] If set, skip creating a new SSH key
33
+ * (HA case: shared key across clusters)
34
+ * @property {string} [k3sToken] Pre-shared k3s node-join token
35
+ * (auto-generated if omitted)
36
+ * @property {string} location Hetzner datacenter (nbg1/hel1/fsn1/hil/ash/sin)
37
+ * @property {string} networkZone eu-central / us-east / us-west / ap-southeast
38
+ * @property {string} masterServerType
39
+ * @property {string} supabaseServerType
40
+ * @property {string} workerServerType
41
+ * @property {number} minWorkers Static floor of worker servers (provisioned by Pulumi). Default 1.
42
+ * @property {number} maxWorkers Upper bound for cluster-autoscaler. Not consumed by Pulumi; flows to the CA Deployment in applyK3sManifests. Default 3.
43
+ * @property {string} k3sVersion
44
+ * @property {string} privateNetworkRange Default 10.0.0.0/8
45
+ * @property {string} subnetRange Default 10.0.1.0/24
46
+ * @property {Record<string,string>} labels
47
+ * @property {string[]} [allowedSshIps] CIDR list, defaults to 0.0.0.0/0
48
+ * @property {string[]} [allowedK8sApiIps] CIDR list, defaults to 0.0.0.0/0
49
+ * @property {string} apiToken Hetzner Cloud API token. Baked into
50
+ * the master cloud-init so the node
51
+ * can write the `hcloud` and
52
+ * `hcloud-csi` kube-system Secrets
53
+ * that hcloud-cloud-controller-manager
54
+ * and hcloud-csi-driver read on boot.
55
+ */
56
+
57
+ /**
58
+ * Build an inline Pulumi program for the given cluster config.
59
+ * @param {K8sStackConfig} config
60
+ * @returns {() => Promise<Record<string, unknown>>}
61
+ */
62
+ export function buildHetznerK8sProgram(config) {
63
+ const clusterName = `${config.projectName}-${config.environment}`;
64
+ const labels = { project: config.projectName, environment: config.environment, ...config.labels };
65
+ // Operator-IP firewall lock (H-2). The deploy flow populates these from
66
+ // projectConfig.operatorCidrs; an empty list = nothing can SSH in or hit
67
+ // the k8s API. We refuse to build a program that would do that — far
68
+ // better to fail loudly than to silently apply an open `0.0.0.0/0`.
69
+ if (!config.allowedSshIps?.length) {
70
+ throw new Error(
71
+ 'allowedSshIps required (no default; pass operatorCidrs from .vibecarbon.json or set ALLOWED_SSH_IPS=...)',
72
+ );
73
+ }
74
+ if (!config.allowedK8sApiIps?.length) {
75
+ throw new Error(
76
+ 'allowedK8sApiIps required (no default; pass operatorCidrs from .vibecarbon.json or set ALLOWED_SSH_IPS=...)',
77
+ );
78
+ }
79
+ const allowedSshIps = config.allowedSshIps;
80
+ const allowedK8sApiIps = config.allowedK8sApiIps;
81
+ const k3sToken = config.k3sToken ?? randomBytes(32).toString('hex');
82
+ const privateNetworkRange = config.privateNetworkRange ?? '10.0.0.0/8';
83
+ const subnetRange = config.subnetRange ?? '10.0.1.0/24';
84
+ const minWorkers = config.minWorkers ?? 1;
85
+
86
+ const masterInitTemplate = loadCloudInit('master-init.sh');
87
+ const supabaseInitTemplate = loadCloudInit('supabase-init.sh');
88
+ const workerInitTemplate = loadCloudInit('worker-init.sh');
89
+
90
+ return async () => {
91
+ // SSH key — either reuse an existing one (HA shared key) or create.
92
+ let sshKeyId;
93
+ if (config.existingSshKeyId) {
94
+ sshKeyId = config.existingSshKeyId;
95
+ } else {
96
+ const sshKey = new hcloud.SshKey('ssh-key', {
97
+ name: `${clusterName}-${config.location}-key`,
98
+ publicKey: config.sshPublicKey,
99
+ labels,
100
+ });
101
+ sshKeyId = sshKey.id;
102
+ }
103
+
104
+ // Private network + subnet
105
+ const network = new hcloud.Network('network', {
106
+ name: `${clusterName}-network`,
107
+ ipRange: privateNetworkRange,
108
+ labels,
109
+ });
110
+ const subnet = new hcloud.NetworkSubnet('subnet', {
111
+ networkId: network.id,
112
+ type: 'cloud',
113
+ networkZone: config.networkZone,
114
+ ipRange: subnetRange,
115
+ });
116
+
117
+ // Firewall. `replaceOnChanges: ['*']` works around an upstream
118
+ // hcloud-go@1.32.1 bug: the Firewall `Update` code path nil-pointer
119
+ // panics when rules change on a live firewall. Forcing replace avoids
120
+ // the Update call site entirely; recreate is cheap.
121
+ const firewall = new hcloud.Firewall(
122
+ 'firewall',
123
+ {
124
+ name: `${clusterName}-firewall`,
125
+ labels,
126
+ rules: [
127
+ {
128
+ direction: 'in',
129
+ protocol: 'tcp',
130
+ port: '22',
131
+ sourceIps: allowedSshIps,
132
+ description: 'SSH access',
133
+ },
134
+ {
135
+ direction: 'in',
136
+ protocol: 'tcp',
137
+ port: '80',
138
+ sourceIps: ['0.0.0.0/0', '::/0'],
139
+ description: 'HTTP',
140
+ },
141
+ {
142
+ direction: 'in',
143
+ protocol: 'tcp',
144
+ port: '443',
145
+ sourceIps: ['0.0.0.0/0', '::/0'],
146
+ description: 'HTTPS',
147
+ },
148
+ {
149
+ direction: 'in',
150
+ protocol: 'tcp',
151
+ port: '6443',
152
+ sourceIps: allowedK8sApiIps,
153
+ description: 'Kubernetes API',
154
+ },
155
+ {
156
+ direction: 'in',
157
+ protocol: 'tcp',
158
+ port: 'any',
159
+ sourceIps: [privateNetworkRange],
160
+ description: 'Internal cluster traffic (TCP)',
161
+ },
162
+ {
163
+ direction: 'in',
164
+ protocol: 'udp',
165
+ port: 'any',
166
+ sourceIps: [privateNetworkRange],
167
+ description: 'Internal cluster traffic (UDP)',
168
+ },
169
+ ],
170
+ },
171
+ { replaceOnChanges: ['*'], deleteBeforeReplace: true },
172
+ );
173
+
174
+ // Floating IP (stable ingress) — created before master so we can pass
175
+ // its address into the master's cloud-init script. Name includes the
176
+ // region (config.location) so parallel deploys across regions don't
177
+ // collide on the shared-across-project Hetzner namespace.
178
+ const floatingIp = new hcloud.FloatingIp('ingress', {
179
+ type: 'ipv4',
180
+ homeLocation: config.location,
181
+ name: `${clusterName}-${config.location}-ingress`,
182
+ labels,
183
+ });
184
+
185
+ // Master server — cloud-init script is rendered with floating IP, network
186
+ // id, and the Hetzner API token. The token + network id flow into
187
+ // `hcloud` and `hcloud-csi` kube-system Secrets; hcloud-cloud-controller-
188
+ // manager and hcloud-csi-driver fail to authenticate without them,
189
+ // which leaves PVCs Pending and the whole Supabase install hanging on
190
+ // wait-for-db.
191
+ const masterUserData = pulumi.all([floatingIp.ipAddress, network.id]).apply(([ip, netId]) =>
192
+ renderScript(masterInitTemplate, {
193
+ k3s_version: config.k3sVersion,
194
+ k3s_token: k3sToken,
195
+ cluster_name: clusterName,
196
+ disable_traefik: 'true',
197
+ hcloud_token: config.apiToken ?? '',
198
+ network_id: netId,
199
+ floating_ip: ip,
200
+ }),
201
+ );
202
+
203
+ const master = new hcloud.Server(
204
+ 'master',
205
+ {
206
+ name: `${clusterName}-master`,
207
+ serverType: config.masterServerType,
208
+ image: 'ubuntu-24.04',
209
+ location: config.location,
210
+ sshKeys: [sshKeyId],
211
+ firewallIds: [firewall.id.apply((id) => Number.parseInt(id, 10))],
212
+ labels: { ...labels, role: 'master' },
213
+ networks: [
214
+ { networkId: network.id.apply((id) => Number.parseInt(id, 10)), ip: '10.0.1.1' },
215
+ ],
216
+ userData: masterUserData,
217
+ },
218
+ { dependsOn: [subnet] },
219
+ );
220
+
221
+ // Attach floating IP to master
222
+ new hcloud.FloatingIpAssignment('ingress-assignment', {
223
+ floatingIpId: floatingIp.id.apply((id) => Number.parseInt(id, 10)),
224
+ serverId: master.id.apply((id) => Number.parseInt(id, 10)),
225
+ });
226
+
227
+ // Supabase server — joins master via k3s agent. master_ip MUST be the
228
+ // master's private IP: Hetzner firewall (above) restricts public 6443
229
+ // to operator IPs only, so the supabase node cannot reach the master's
230
+ // public IP on 6443 at all. Mirrors the worker-init pattern below.
231
+ // Hardcoding 10.0.1.1 also drops the master.ipv4Address.apply() wrapper,
232
+ // removing a needless cross-resource dep that delayed cloud-init render.
233
+ const supabaseUserData = renderScript(supabaseInitTemplate, {
234
+ k3s_version: config.k3sVersion,
235
+ k3s_token: k3sToken,
236
+ master_ip: '10.0.1.1',
237
+ });
238
+ // NOTE: previously `dependsOn: [master]` — we dropped that so Hetzner
239
+ // provisions master + supabase in parallel, saving ~30-60s on the
240
+ // critical path. The supabase cloud-init (supabase-init.sh) already
241
+ // polls the master k3s API with a 120-attempt retry loop, so it
242
+ // tolerates master coming up slightly later.
243
+ const supabase = new hcloud.Server(
244
+ 'supabase',
245
+ {
246
+ name: `${clusterName}-supabase`,
247
+ serverType: config.supabaseServerType,
248
+ image: 'ubuntu-24.04',
249
+ location: config.location,
250
+ sshKeys: [sshKeyId],
251
+ firewallIds: [firewall.id.apply((id) => Number.parseInt(id, 10))],
252
+ labels: { ...labels, role: 'supabase', 'node-pool': 'supabase-pool' },
253
+ networks: [
254
+ { networkId: network.id.apply((id) => Number.parseInt(id, 10)), ip: '10.0.1.2' },
255
+ ],
256
+ userData: supabaseUserData,
257
+ },
258
+ { dependsOn: [subnet] },
259
+ );
260
+
261
+ // Workers (spread across placement group). Name includes region so
262
+ // re-runs / scale across regions don't collide in Hetzner's per-project
263
+ // placement-group namespace.
264
+ const placementGroup = new hcloud.PlacementGroup('workers-pg', {
265
+ name: `${clusterName}-${config.location}-workers-pg`,
266
+ type: 'spread',
267
+ labels,
268
+ });
269
+ const workerUserData = renderScript(workerInitTemplate, {
270
+ k3s_version: config.k3sVersion,
271
+ k3s_token: k3sToken,
272
+ master_ip: '10.0.1.1',
273
+ cluster_name: clusterName,
274
+ });
275
+ const workers = [];
276
+ for (let i = 0; i < minWorkers; i++) {
277
+ workers.push(
278
+ new hcloud.Server(
279
+ `worker-${i + 1}`,
280
+ {
281
+ name: `${clusterName}-worker-${i + 1}`,
282
+ serverType: config.workerServerType,
283
+ image: 'ubuntu-24.04',
284
+ location: config.location,
285
+ sshKeys: [sshKeyId],
286
+ firewallIds: [firewall.id.apply((id) => Number.parseInt(id, 10))],
287
+ placementGroupId: placementGroup.id.apply((id) => Number.parseInt(id, 10)),
288
+ labels: {
289
+ ...labels,
290
+ role: 'worker',
291
+ 'node-pool': 'worker-pool',
292
+ 'cluster-autoscaler/node': 'static',
293
+ },
294
+ networks: [
295
+ {
296
+ networkId: network.id.apply((id) => Number.parseInt(id, 10)),
297
+ ip: `10.0.1.${10 + i}`,
298
+ },
299
+ ],
300
+ userData: workerUserData,
301
+ },
302
+ { dependsOn: [subnet] },
303
+ ),
304
+ );
305
+ }
306
+
307
+ return {
308
+ masterIp: master.ipv4Address,
309
+ masterPrivateIp: '10.0.1.1',
310
+ supabaseIp: supabase.ipv4Address,
311
+ supabasePrivateIp: '10.0.1.2',
312
+ workerIps: workers.map((w) => w.ipv4Address),
313
+ floatingIp: floatingIp.ipAddress,
314
+ networkId: network.id,
315
+ sshKeyId: typeof sshKeyId === 'string' ? sshKeyId : sshKeyId.apply((id) => String(id)),
316
+ k3sToken,
317
+ clusterName,
318
+ };
319
+ };
320
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Kubectl helpers that avoid exposing secrets in argv.
3
+ *
4
+ * Any Kubernetes manifest containing secret data MUST use
5
+ * kubectlApplyManifest so the bytes flow through stdin instead of the
6
+ * shell command line.
7
+ */
8
+
9
+ import { spawnSync } from 'node:child_process';
10
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+
14
+ /**
15
+ * Apply a Kubernetes manifest via `kubectl apply -f -` piped from stdin.
16
+ * The manifest body (including any secret data) never appears in argv.
17
+ *
18
+ * @param {object} manifest - Kubernetes resource as a plain object
19
+ * @param {object} [options]
20
+ * @param {string} [options.namespace]
21
+ * @param {object} [options.env]
22
+ * @param {number} [options.timeout=60000]
23
+ * @returns {string} - kubectl stdout
24
+ */
25
+ export function kubectlApplyManifest(manifest, options = {}) {
26
+ const yaml = JSON.stringify(manifest);
27
+ const args = ['apply', '-f', '-'];
28
+ if (options.namespace) args.push('-n', options.namespace);
29
+ const res = spawnSync('kubectl', args, {
30
+ input: yaml,
31
+ encoding: 'utf-8',
32
+ env: options.env ?? process.env,
33
+ timeout: options.timeout ?? 60_000,
34
+ });
35
+ if (res.status !== 0) {
36
+ const stderr = (res.stderr || '').trim();
37
+ const exitCode = res.status ?? '(null)';
38
+ // NEVER include res.stdout — kubectl may echo the submitted manifest on
39
+ // failure, and the manifest may contain secret data.
40
+ throw new Error(`kubectl apply failed (exit ${exitCode}): ${stderr || '(no stderr)'}`);
41
+ }
42
+ return res.stdout;
43
+ }
44
+
45
+ /**
46
+ * Apply a strategic-merge patch to a resource via a temp patch file.
47
+ * Use this instead of interpolating JSON into a shell command line.
48
+ *
49
+ * @param {string} resourceRef - e.g. 'deployment/app' or 'serviceaccount/default'
50
+ * @param {string} namespace
51
+ * @param {object} patch - patch object
52
+ * @param {object} [options]
53
+ * @param {string} [options.type='strategic'] - 'strategic' | 'merge' | 'json'
54
+ * @param {object} [options.env]
55
+ * @param {boolean} [options.ignoreError]
56
+ * @param {number} [options.timeout=60000]
57
+ * @returns {string|null} - kubectl stdout, or null when ignoreError + failure
58
+ */
59
+ export function kubectlPatch(resourceRef, namespace, patch, options = {}) {
60
+ const { type = 'strategic', env, ignoreError = false, timeout = 60_000 } = options;
61
+ const dir = mkdtempSync(join(tmpdir(), 'vibecarbon-patch-'));
62
+ const patchFile = join(dir, 'patch.json');
63
+ try {
64
+ writeFileSync(patchFile, JSON.stringify(patch), { mode: 0o600 });
65
+ const args = ['patch', resourceRef, '-n', namespace, '--type', type, '--patch-file', patchFile];
66
+ const res = spawnSync('kubectl', args, {
67
+ encoding: 'utf-8',
68
+ env: env ?? process.env,
69
+ timeout,
70
+ });
71
+ if (res.status !== 0) {
72
+ if (ignoreError) return null;
73
+ const stderr = (res.stderr || '').trim();
74
+ const exitCode = res.status ?? '(null)';
75
+ throw new Error(`kubectl patch failed (exit ${exitCode}): ${stderr || '(no stderr)'}`);
76
+ }
77
+ return res.stdout;
78
+ } finally {
79
+ rmSync(dir, { recursive: true, force: true });
80
+ }
81
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * License management for Vibecarbon
3
+ *
4
+ * Handles license storage, retrieval, activation, and validation.
5
+ * Licenses are stored at ~/.vibecarbon/license
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ import { c } from '../colors.js';
12
+ import { allowsFlag as _allowsFlag, getTier, TIERS } from './tiers.js';
13
+ import { validateLicenseKey } from './validator.js';
14
+
15
+ // License storage location
16
+ const CONFIG_DIR = join(homedir(), '.vibecarbon');
17
+ const LICENSE_FILE = join(CONFIG_DIR, 'license');
18
+
19
+ /**
20
+ * Ensure the config directory exists
21
+ */
22
+ function ensureConfigDir() {
23
+ if (!existsSync(CONFIG_DIR)) {
24
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Read the stored license file
30
+ * @returns {object|null} License data or null if not found
31
+ */
32
+ function _readLicenseFile() {
33
+ if (!existsSync(LICENSE_FILE)) {
34
+ return null;
35
+ }
36
+
37
+ try {
38
+ const content = readFileSync(LICENSE_FILE, 'utf-8');
39
+ return JSON.parse(content);
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Write license data to file
47
+ * @param {object} data - License data to store
48
+ */
49
+ function writeLicenseFile(data) {
50
+ ensureConfigDir();
51
+ writeFileSync(LICENSE_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
52
+ }
53
+
54
+ /**
55
+ * Get the current license status
56
+ * @returns {object} License information with tier and features
57
+ */
58
+ export function getLicense() {
59
+ const stored = _readLicenseFile();
60
+
61
+ // No license file → Graphite tier (free)
62
+ if (!stored?.key) {
63
+ return {
64
+ tier: 'graphite',
65
+ ...TIERS.graphite,
66
+ active: false,
67
+ message: 'No license activated. Using Graphite tier.',
68
+ };
69
+ }
70
+
71
+ // Re-validate the stored key
72
+ const validation = validateLicenseKey(stored.key);
73
+
74
+ // If licensing is disabled in validator, return fullerene
75
+ if (validation.licensingDisabled) {
76
+ return {
77
+ tier: 'fullerene',
78
+ ...TIERS.fullerene,
79
+ active: true,
80
+ licensingDisabled: true,
81
+ displayName: 'All Features Enabled',
82
+ message: 'Licensing disabled - all features available.',
83
+ };
84
+ }
85
+
86
+ if (!validation.valid) {
87
+ // Stored key is no longer valid (corrupted, etc.)
88
+ return {
89
+ tier: 'graphite',
90
+ ...TIERS.graphite,
91
+ active: false,
92
+ message: `License invalid: ${validation.error}. Reverted to Graphite tier.`,
93
+ };
94
+ }
95
+
96
+ const tier = getTier(validation.tier);
97
+
98
+ return {
99
+ tier: validation.tier,
100
+ ...tier,
101
+ active: true,
102
+ customerId: validation.customerId || stored.customerId,
103
+ activatedAt: stored.activatedAt,
104
+ isLifetime: true,
105
+ devMode: validation.devMode,
106
+ verified: validation.verified,
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Activate a license key
112
+ * @param {string} key - The license key to activate
113
+ * @returns {object} Activation result
114
+ */
115
+ export function activateLicense(key) {
116
+ if (!key || typeof key !== 'string') {
117
+ return { success: false, error: 'License key is required' };
118
+ }
119
+
120
+ const validation = validateLicenseKey(key.trim());
121
+
122
+ if (!validation.valid) {
123
+ return { success: false, error: validation.error };
124
+ }
125
+
126
+ const licenseData = {
127
+ key: key.trim(),
128
+ tier: validation.tier,
129
+ customerId: validation.customerId,
130
+ activatedAt: new Date().toISOString(),
131
+ };
132
+
133
+ try {
134
+ writeLicenseFile(licenseData);
135
+ } catch (error) {
136
+ return { success: false, error: `Failed to save license: ${error.message}` };
137
+ }
138
+
139
+ const tier = getTier(validation.tier);
140
+
141
+ return {
142
+ success: true,
143
+ tier: validation.tier,
144
+ tierName: tier.displayName,
145
+ features: tier.features,
146
+ isLifetime: true,
147
+ devMode: validation.devMode,
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Deactivate (remove) the current license
153
+ * @returns {object} Deactivation result
154
+ */
155
+ export function deactivateLicense() {
156
+ if (!existsSync(LICENSE_FILE)) {
157
+ return { success: true, message: 'No license was activated' };
158
+ }
159
+
160
+ try {
161
+ unlinkSync(LICENSE_FILE);
162
+ return { success: true, message: 'License deactivated. Using Graphite tier.' };
163
+ } catch (error) {
164
+ return { success: false, error: `Failed to remove license: ${error.message}` };
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Check if the current license allows a specific deployment mode
170
+ * @param {string} deploymentFlag - The deployment flag (e.g., '--k8s', '--ha')
171
+ * @returns {object} Access check result
172
+ */
173
+ export function checkDeploymentAccess(deploymentFlag) {
174
+ const license = getLicense();
175
+
176
+ // If licensing is disabled in the validator, allow everything
177
+ if (license.licensingDisabled) {
178
+ return {
179
+ allowed: true,
180
+ tier: license.tier,
181
+ licensingDisabled: true,
182
+ };
183
+ }
184
+
185
+ const allowed = _allowsFlag(license.tier, deploymentFlag);
186
+
187
+ return {
188
+ allowed,
189
+ tier: license.tier,
190
+ requiredTier: allowed ? license.tier : 'diamond',
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Guard function: require any paid license or exit with upgrade message
196
+ * @param {string} commandName - The command that requires a license
197
+ */
198
+ export function requireLicense(commandName) {
199
+ const license = getLicense();
200
+
201
+ if (license.licensingDisabled) return;
202
+ if (license.active) return;
203
+
204
+ console.log('');
205
+ console.log(` ${c.warning('License required')}`);
206
+ console.log('');
207
+ console.log(
208
+ ` ${c.dim(`The ${c.bold(commandName)} command requires a license.`)} ${c.dim('(create, up, down, reset, status are always free)')}`,
209
+ );
210
+ console.log('');
211
+ console.log(
212
+ ` ${c.bold('Vibecarbon Diamond')} — ${c.success('$149 lifetime')} ${c.dim('— deploy to production, indie & startup use')}`,
213
+ );
214
+ console.log(
215
+ ` ${c.bold('Vibecarbon Fullerene')} — ${c.success('$499 lifetime')} ${c.dim('— deploy for clients & agencies, commercial use rights')}`,
216
+ );
217
+ console.log('');
218
+ console.log(` ${c.dim('Purchase:')} ${c.info('https://vibecarbon.dev/pricing')}`);
219
+ console.log(` ${c.dim('Activate:')} ${c.info('vibecarbon activate <key>')}`);
220
+ console.log(` ${c.dim('Terms:')} ${c.dim('TERMS.md or https://vibecarbon.dev/terms')}`);
221
+ console.log('');
222
+ process.exit(0);
223
+ }
224
+
225
+ /**
226
+ * Get a formatted status message for the current license
227
+ * @returns {string} Status message
228
+ */
229
+ export function getLicenseStatus() {
230
+ const license = getLicense();
231
+
232
+ if (!license.active) {
233
+ return `License: Graphite tier (free, no license activated)
234
+ Features: ${TIERS.graphite.features.join(', ')}
235
+
236
+ Upgrade to deploy:
237
+ Diamond ($149 lifetime) — deploy to production, indie & startup use
238
+ Fullerene ($499 lifetime) — deploy for clients & agencies, commercial use rights
239
+
240
+ Purchase at https://vibecarbon.dev/pricing`;
241
+ }
242
+
243
+ let status = `License: ${license.displayName}
244
+ Customer ID: ${license.customerId}
245
+ Activated: ${license.activatedAt}
246
+ Features: ${license.features.join(', ')}
247
+ Max servers: ${license.maxServers === Infinity ? 'Unlimited' : license.maxServers}
248
+ Expires: Never (lifetime license)`;
249
+
250
+ if (license.devMode) {
251
+ status += '\n\n[Development Mode - Signature not verified]';
252
+ }
253
+
254
+ return status;
255
+ }
256
+
257
+ // Re-export tier utilities
258
+ export { allowsFlag, compareTiers, getRequiredTier, getTier, TIERS } from './tiers.js';
259
+ export { generateDevLicenseKey, validateLicenseKey } from './validator.js';