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,334 @@
1
+ /**
2
+ * Project detection and manifest management utilities
3
+ * Shared across CLI commands (add.js, s3.js, deploy.js, etc.)
4
+ */
5
+
6
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { escapeDotenv } from './shell.js';
9
+
10
+ // ============================================================================
11
+ // PACKAGE MANAGER DETECTION
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Detect the package manager used by a project.
16
+ * Checks lock files first, then package.json packageManager field, falls back to pnpm.
17
+ *
18
+ * @param {string} [cwd] - Working directory (defaults to process.cwd())
19
+ * @returns {'npm'|'pnpm'|'bun'}
20
+ */
21
+ export function detectPackageManager(cwd = process.cwd()) {
22
+ if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
23
+ if (existsSync(join(cwd, 'bun.lock')) || existsSync(join(cwd, 'bun.lockb'))) return 'bun';
24
+ if (existsSync(join(cwd, 'package-lock.json'))) return 'npm';
25
+
26
+ const pkgPath = join(cwd, 'package.json');
27
+ if (existsSync(pkgPath)) {
28
+ try {
29
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
30
+ if (pkg.packageManager) {
31
+ const pm = pkg.packageManager.split('@')[0];
32
+ if (['npm', 'pnpm', 'bun'].includes(pm)) return pm;
33
+ }
34
+ } catch {
35
+ // Ignore parse errors
36
+ }
37
+ }
38
+
39
+ return 'pnpm';
40
+ }
41
+
42
+ // ============================================================================
43
+ // MANIFEST MANAGEMENT (.vibecarbon.json services tracking)
44
+ // ============================================================================
45
+
46
+ /**
47
+ * Load the project manifest (.vibecarbon.json)
48
+ * This tracks which services have been added to the project
49
+ *
50
+ * @param {string} [cwd] - Working directory (defaults to process.cwd())
51
+ * @returns {object} - Manifest object with version and services
52
+ */
53
+ export function loadManifest(cwd = process.cwd()) {
54
+ const manifestPath = join(cwd, '.vibecarbon.json');
55
+ if (existsSync(manifestPath)) {
56
+ return JSON.parse(readFileSync(manifestPath, 'utf-8'));
57
+ }
58
+ return { version: '1', services: {} };
59
+ }
60
+
61
+ /**
62
+ * Save the project manifest (.vibecarbon.json)
63
+ *
64
+ * @param {object} manifest - Manifest object to save
65
+ * @param {string} [cwd] - Working directory (defaults to process.cwd())
66
+ */
67
+ export function saveManifest(manifest, cwd = process.cwd()) {
68
+ const manifestPath = join(cwd, '.vibecarbon.json');
69
+ writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
70
+ }
71
+
72
+ // ============================================================================
73
+ // ENVIRONMENT FILE MANAGEMENT
74
+ // ============================================================================
75
+
76
+ /**
77
+ * Parse a dotenv-format string into a key-value object.
78
+ *
79
+ * Supports:
80
+ * - Single-quoted values (produced by serializeDotenv / escapeDotenv)
81
+ * - Legacy double-quoted values (for backwards compatibility with
82
+ * previously-written files using the old KEY="value" format)
83
+ * - Unquoted values (no embedded whitespace)
84
+ * - Multi-line single-quoted values (value spans multiple lines)
85
+ * - Comment lines (#) and blank lines are ignored
86
+ *
87
+ * The single-quoted branch uses a tiny state machine that correctly handles
88
+ * the '\'' close-reopen escape AND literal backslashes (which are not
89
+ * escape characters in POSIX single-quoted strings).
90
+ */
91
+ export function parseDotenv(text) {
92
+ const out = {};
93
+ const lines = text.split('\n');
94
+ for (let i = 0; i < lines.length; i++) {
95
+ const line = lines[i];
96
+ if (!line || line.startsWith('#')) continue;
97
+ const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
98
+ if (!m) continue;
99
+ const key = m[1];
100
+ const rest = m[2];
101
+
102
+ if (rest.startsWith("'")) {
103
+ // Scan forward, possibly consuming more lines, until we find the
104
+ // closing single-quote. The only "escape" in POSIX single-quoted
105
+ // strings is the close-reopen trick: '\'' produces a literal '.
106
+ // We consume buffer[pos] one char at a time starting after the
107
+ // opening '.
108
+ let buf = rest.slice(1);
109
+ const parts = [];
110
+ let done = false;
111
+ while (!done) {
112
+ let pos = 0;
113
+ while (pos < buf.length) {
114
+ const ch = buf[pos];
115
+ if (ch === "'") {
116
+ // Check for close-reopen '\''
117
+ if (buf.slice(pos, pos + 4) === "'\\''") {
118
+ parts.push("'");
119
+ pos += 4;
120
+ } else {
121
+ // Actual closing quote.
122
+ done = true;
123
+ break;
124
+ }
125
+ } else {
126
+ parts.push(ch);
127
+ pos += 1;
128
+ }
129
+ }
130
+ if (done) break;
131
+ // Exhausted this line without finding the close — consume the next.
132
+ if (i + 1 >= lines.length) {
133
+ // Malformed: unterminated single quote. Salvage what we have.
134
+ done = true;
135
+ break;
136
+ }
137
+ parts.push('\n');
138
+ i += 1;
139
+ buf = lines[i];
140
+ }
141
+ out[key] = parts.join('');
142
+ } else if (rest.startsWith('"')) {
143
+ // Legacy double-quoted. No escape handling (pre-C-8 files used simple "...").
144
+ const closeIdx = rest.indexOf('"', 1);
145
+ if (closeIdx !== -1) out[key] = rest.slice(1, closeIdx);
146
+ } else {
147
+ // Unquoted value — up to first whitespace-then-# or end of line.
148
+ out[key] = rest.replace(/\s+#.*$/, '').trim();
149
+ }
150
+ }
151
+ return out;
152
+ }
153
+
154
+ /**
155
+ * Serialize a key-value object into dotenv format using escapeDotenv
156
+ * (single-quoted, safe for dotenv-package parsers).
157
+ */
158
+ export function serializeDotenv(obj) {
159
+ return `${Object.entries(obj)
160
+ .map(([k, v]) => `${k}=${escapeDotenv(v)}`)
161
+ .join('\n')}\n`;
162
+ }
163
+
164
+ /**
165
+ * Load environment variables from .env.local
166
+ *
167
+ * @param {string} [cwd] - Working directory (defaults to process.cwd())
168
+ * @returns {object} - Key-value object of environment variables
169
+ */
170
+ export function loadEnvVariables(cwd = process.cwd()) {
171
+ const envPath = join(cwd, '.env.local');
172
+ if (!existsSync(envPath)) return {};
173
+ return parseDotenv(readFileSync(envPath, 'utf-8'));
174
+ }
175
+
176
+ /**
177
+ * Append a section of environment variables to .env.local and .env
178
+ *
179
+ * @param {string} sectionName - Name for the section header (e.g., 'N8N', 'S3 STORAGE')
180
+ * @param {object} envVars - Key-value pairs to add
181
+ * @param {string} [cwd] - Working directory (defaults to process.cwd())
182
+ */
183
+ export function appendToEnv(sectionName, envVars, cwd = process.cwd()) {
184
+ const envFiles = ['.env.local', '.env'];
185
+ for (const filename of envFiles) {
186
+ const envPath = join(cwd, filename);
187
+ if (!existsSync(envPath)) continue;
188
+ const content = readFileSync(envPath, 'utf-8');
189
+ const sectionHeader = `# ${sectionName.toUpperCase()}`;
190
+ if (content.includes(sectionHeader)) continue;
191
+ const body = Object.entries(envVars)
192
+ .map(([k, v]) => `${k}=${escapeDotenv(v)}`)
193
+ .join('\n');
194
+ const newSection = `\n\n# =============================================================================\n# ${sectionName.toUpperCase()}\n# =============================================================================\n\n${body}\n`;
195
+ writeFileSync(envPath, content.trimEnd() + newSection);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Set or update a single environment variable in .env.local and .env
201
+ *
202
+ * @param {string} key - Variable name
203
+ * @param {string} value - Variable value
204
+ * @param {string} [cwd] - Working directory (defaults to process.cwd())
205
+ */
206
+ export function setEnvVar(key, value, cwd = process.cwd()) {
207
+ const envFiles = ['.env.local', '.env'];
208
+ for (const filename of envFiles) {
209
+ const envPath = join(cwd, filename);
210
+ if (!existsSync(envPath)) continue;
211
+ const content = readFileSync(envPath, 'utf-8');
212
+ // Match either legacy KEY="..." or new KEY='...' forms at line start.
213
+ const regex = new RegExp(`^${key}=(?:"[^"]*"|'(?:[^']|'\\\\'')*')`, 'm');
214
+ const replacement = `${key}=${escapeDotenv(value)}`;
215
+ if (regex.test(content)) {
216
+ writeFileSync(envPath, content.replace(regex, replacement));
217
+ } else {
218
+ writeFileSync(envPath, `${content.trimEnd()}\n${replacement}\n`);
219
+ }
220
+ }
221
+ }
222
+
223
+ // ============================================================================
224
+ // GIT ADD ALLOWLIST
225
+ // ============================================================================
226
+
227
+ /**
228
+ * Allowlist of paths that vibecarbon knows are safe to commit.
229
+ * Used by add/deploy/push in place of `git add .` to prevent leaking
230
+ * `.vibecarbon/`, `.env.local`, or other untracked secrets.
231
+ */
232
+ const PROJECT_GIT_ADD_ALLOWLIST = [
233
+ // Top-level config
234
+ '.gitignore',
235
+ '.dockerignore',
236
+ '.github/',
237
+ '.claude/',
238
+ 'package.json',
239
+ 'pnpm-lock.yaml',
240
+ 'package-lock.json',
241
+ 'bun.lock',
242
+ 'bun.lockb',
243
+ 'biome.json',
244
+ 'tsconfig.json',
245
+ 'tsconfig.server.json',
246
+ 'vite.config.ts',
247
+ 'components.json',
248
+ // Docker
249
+ 'Dockerfile',
250
+ 'docker-entrypoint.sh',
251
+ 'docker-compose.yml',
252
+ 'docker-compose.prod.yml',
253
+ 'docker-compose.override.yml',
254
+ 'docker-compose.metabase.yml',
255
+ 'docker-compose.metabase.prod.yml',
256
+ 'docker-compose.metabase.override.yml',
257
+ 'docker-compose.n8n.yml',
258
+ 'docker-compose.n8n.prod.yml',
259
+ 'docker-compose.n8n.override.yml',
260
+ 'docker-compose.observability.yml',
261
+ 'docker-compose.observability.prod.yml',
262
+ 'docker-compose.observability.override.yml',
263
+ // Source
264
+ 'src/',
265
+ 'scripts/',
266
+ 'content/',
267
+ 'backup/',
268
+ 'ha/',
269
+ 'k8s/',
270
+ 'supabase/',
271
+ 'cloud-init/',
272
+ 'volumes/',
273
+ // Docs and metadata
274
+ 'README.md',
275
+ 'LICENSE',
276
+ 'AGENTS.md',
277
+ 'CLAUDE.md',
278
+ 'DEVELOPMENT.md',
279
+ 'PRODUCTION.md',
280
+ 'CHANGELOG.md',
281
+ ];
282
+
283
+ /**
284
+ * Build a `git add` argv limited to template-known paths that exist.
285
+ * The first 3 tokens ('git', 'add', '--') are always included; the rest
286
+ * are filtered by existsSync so missing optional paths don't error.
287
+ *
288
+ * If NO allowlisted path exists in cwd, the result is ['git', 'add', '--']
289
+ * with no trailing paths — git will exit 0 with a "nothing added" message
290
+ * on stderr. Callers pass silent:true + ignoreError:true to make this a no-op.
291
+ *
292
+ * @param {string} [cwd] - Working directory (defaults to process.cwd())
293
+ * @param {string[]} [extras] - Additional paths to include (also filtered by existsSync)
294
+ * @returns {string[]} argv array safe to pass to runCommand
295
+ */
296
+ export function buildGitAddArgv(cwd = process.cwd(), extras = []) {
297
+ const paths = [...PROJECT_GIT_ADD_ALLOWLIST, ...extras].filter((p) => existsSync(join(cwd, p)));
298
+ return ['git', 'add', '--', ...paths];
299
+ }
300
+
301
+ // ============================================================================
302
+ // GITIGNORE VALIDATION
303
+ // ============================================================================
304
+
305
+ /**
306
+ * Check that a .gitignore file contains the required security patterns.
307
+ * Returns an array of missing patterns (empty array on success).
308
+ *
309
+ * @param {string} gitignorePath - Absolute path to the .gitignore file
310
+ * @returns {string[]} Array of missing patterns
311
+ */
312
+ export function validateGitignore(gitignorePath) {
313
+ let content;
314
+ try {
315
+ content = readFileSync(gitignorePath, 'utf-8');
316
+ } catch {
317
+ return ['.gitignore is missing'];
318
+ }
319
+ const lines = content
320
+ .split('\n')
321
+ .map((l) => l.trim())
322
+ .filter((l) => l && !l.startsWith('#'));
323
+ const required = [
324
+ '.vibecarbon/',
325
+ '.env',
326
+ '.env.local',
327
+ '.env.*.local',
328
+ '*.pem',
329
+ '*.key',
330
+ '*.tfstate',
331
+ '*.tfstate.*',
332
+ ];
333
+ return required.filter((r) => !lines.includes(r));
334
+ }
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Base Provider Interface
3
+ *
4
+ * Abstract base class that defines the interface for cloud providers.
5
+ * All cloud providers (Hetzner, DigitalOcean, Vultr, etc.) must implement
6
+ * this interface to be used with Vibecarbon.
7
+ *
8
+ * To add a new provider:
9
+ * 1. Create a new file (e.g., digitalocean.js) that extends BaseProvider
10
+ * 2. Implement all abstract methods
11
+ * 3. Add static NAME, REGIONS, and SERVER_TYPES properties
12
+ * 4. Register the provider in index.js
13
+ */
14
+
15
+ export class BaseProvider {
16
+ /**
17
+ * Provider display name (e.g., "Hetzner Cloud")
18
+ * @type {string}
19
+ */
20
+ static NAME = 'Base Provider';
21
+
22
+ /**
23
+ * API base URL
24
+ * @type {string}
25
+ */
26
+ static API_BASE = '';
27
+
28
+ /**
29
+ * Available regions
30
+ * @type {Object<string, string>}
31
+ */
32
+ static REGIONS = {};
33
+
34
+ /**
35
+ * Available server types
36
+ * @type {Object<string, {vcpu: number, ram: number, disk: number, price: string}>}
37
+ */
38
+ static SERVER_TYPES = {};
39
+
40
+ /**
41
+ * Default server type
42
+ * @type {string}
43
+ */
44
+ static DEFAULT_TYPE = '';
45
+
46
+ /**
47
+ * Default region for HA deployments
48
+ * @type {string[]}
49
+ */
50
+ static HA_REGIONS = [];
51
+
52
+ /**
53
+ * Create a new provider instance
54
+ * @param {string} apiToken - API token for authentication
55
+ */
56
+ constructor(apiToken) {
57
+ if (!apiToken) {
58
+ throw new Error('API token is required');
59
+ }
60
+ this.apiToken = apiToken;
61
+ }
62
+
63
+ /**
64
+ * Get the provider name
65
+ * @returns {string}
66
+ */
67
+ getName() {
68
+ return this.constructor.NAME;
69
+ }
70
+
71
+ /**
72
+ * Get available regions
73
+ * @returns {Object<string, string>}
74
+ */
75
+ getRegions() {
76
+ return this.constructor.REGIONS;
77
+ }
78
+
79
+ /**
80
+ * Get available server types
81
+ * @returns {Object<string, object>}
82
+ */
83
+ getServerTypes() {
84
+ return this.constructor.SERVER_TYPES;
85
+ }
86
+
87
+ /**
88
+ * Get the default server type
89
+ * @returns {string}
90
+ */
91
+ getDefaultType() {
92
+ return this.constructor.DEFAULT_TYPE;
93
+ }
94
+
95
+ /**
96
+ * Get default regions for HA deployment
97
+ * @returns {string[]}
98
+ */
99
+ getHARegions() {
100
+ return this.constructor.HA_REGIONS;
101
+ }
102
+
103
+ // ============================================================================
104
+ // ABSTRACT METHODS - Must be implemented by subclasses
105
+ // ============================================================================
106
+
107
+ /**
108
+ * Create a new server
109
+ * @param {object} config - Server configuration
110
+ * @param {string} config.name - Server name
111
+ * @param {string} config.serverType - Server type/size
112
+ * @param {string} config.region - Region/location
113
+ * @param {string|number} config.sshKeyId - SSH key ID
114
+ * @param {string} config.environment - Environment name
115
+ * @returns {Promise<{id: string|number, server?: object}>}
116
+ */
117
+ async createServer(_config) {
118
+ throw new Error('createServer() must be implemented by subclass');
119
+ }
120
+
121
+ /**
122
+ * Delete a server
123
+ * @param {string|number} serverId - Server ID
124
+ * @returns {Promise<void>}
125
+ */
126
+ async deleteServer(_serverId) {
127
+ throw new Error('deleteServer() must be implemented by subclass');
128
+ }
129
+
130
+ /**
131
+ * Get server details
132
+ * @param {string|number} serverId - Server ID
133
+ * @returns {Promise<object>}
134
+ */
135
+ async getServer(_serverId) {
136
+ throw new Error('getServer() must be implemented by subclass');
137
+ }
138
+
139
+ /**
140
+ * Wait for a server to become ready/running
141
+ * @param {string|number} serverId - Server ID
142
+ * @param {number} [timeout=300000] - Timeout in milliseconds
143
+ * @returns {Promise<object>} Server details when ready
144
+ */
145
+ async waitForServer(_serverId, _timeout = 300000) {
146
+ throw new Error('waitForServer() must be implemented by subclass');
147
+ }
148
+
149
+ /**
150
+ * Create or get an SSH key
151
+ * @param {string} name - SSH key name
152
+ * @param {string} publicKey - Public key content
153
+ * @returns {Promise<string|number>} SSH key ID
154
+ */
155
+ async createSSHKey(_name, _publicKey) {
156
+ throw new Error('createSSHKey() must be implemented by subclass');
157
+ }
158
+
159
+ /**
160
+ * Delete an SSH key
161
+ * @param {string|number} keyId - SSH key ID
162
+ * @returns {Promise<void>}
163
+ */
164
+ async deleteSSHKey(_keyId) {
165
+ throw new Error('deleteSSHKey() must be implemented by subclass');
166
+ }
167
+
168
+ /**
169
+ * Create a firewall and attach it to a server
170
+ * @param {string} name - Firewall name
171
+ * @param {string|number} serverId - Server ID to attach to
172
+ * @param {number[]} [ports=[22, 80, 443]] - Ports to open
173
+ * @returns {Promise<object>}
174
+ */
175
+ async createFirewall(_name, _serverId, _ports = [22, 80, 443]) {
176
+ throw new Error('createFirewall() must be implemented by subclass');
177
+ }
178
+
179
+ /**
180
+ * Delete a firewall
181
+ * @param {string|number} firewallId - Firewall ID
182
+ * @returns {Promise<void>}
183
+ */
184
+ async deleteFirewall(_firewallId) {
185
+ throw new Error('deleteFirewall() must be implemented by subclass');
186
+ }
187
+
188
+ /**
189
+ * List servers matching labels/tags
190
+ * @param {object} labels - Labels to filter by
191
+ * @returns {Promise<object[]>}
192
+ */
193
+ async listServers(_labels = {}) {
194
+ throw new Error('listServers() must be implemented by subclass');
195
+ }
196
+
197
+ /**
198
+ * List firewalls matching labels/tags
199
+ * @param {object} labels - Labels to filter by
200
+ * @returns {Promise<object[]>}
201
+ */
202
+ async listFirewalls(_labels = {}) {
203
+ throw new Error('listFirewalls() must be implemented by subclass');
204
+ }
205
+
206
+ /**
207
+ * List SSH keys
208
+ * @returns {Promise<object[]>}
209
+ */
210
+ async listSSHKeys() {
211
+ throw new Error('listSSHKeys() must be implemented by subclass');
212
+ }
213
+
214
+ // ============================================================================
215
+ // HELPER METHODS - Can be overridden if needed
216
+ // ============================================================================
217
+
218
+ /**
219
+ * Make an authenticated API request
220
+ * @param {string} endpoint - API endpoint (relative to API_BASE)
221
+ * @param {object} [options={}] - Fetch options
222
+ * @returns {Promise<Response>}
223
+ */
224
+ async apiRequest(endpoint, options = {}) {
225
+ const url = `${this.constructor.API_BASE}${endpoint}`;
226
+ const headers = {
227
+ Authorization: `Bearer ${this.apiToken}`,
228
+ 'Content-Type': 'application/json',
229
+ ...options.headers,
230
+ };
231
+
232
+ // Delegate to the shared fetch-retry helper so "fetch failed" /
233
+ // ECONNRESET / EAI_AGAIN from Hetzner gets 5 tries with exponential
234
+ // backoff instead of the previous 3. Transient 5xx / 429 also retry.
235
+ const { fetchWithRetry } = await import('../fetch-retry.js');
236
+ return await fetchWithRetry(url, { ...options, headers });
237
+ }
238
+
239
+ /**
240
+ * Validate that a region is supported
241
+ * @param {string} region - Region ID
242
+ * @returns {boolean}
243
+ */
244
+ isValidRegion(region) {
245
+ return region in this.constructor.REGIONS;
246
+ }
247
+
248
+ /**
249
+ * Validate that a server type is supported
250
+ * @param {string} serverType - Server type ID
251
+ * @returns {boolean}
252
+ */
253
+ isValidServerType(serverType) {
254
+ return serverType in this.constructor.SERVER_TYPES;
255
+ }
256
+
257
+ /**
258
+ * Format a region ID to display name
259
+ * @param {string} region - Region ID
260
+ * @returns {string}
261
+ */
262
+ formatRegion(region) {
263
+ return this.constructor.REGIONS[region] || region;
264
+ }
265
+
266
+ /**
267
+ * Format a server type to display string
268
+ * @param {string} serverType - Server type ID
269
+ * @returns {string}
270
+ */
271
+ formatServerType(serverType) {
272
+ const type = this.constructor.SERVER_TYPES[serverType];
273
+ if (!type) return serverType;
274
+ return `${serverType} (${type.vcpu} vCPU, ${type.ram}GB RAM, ${type.disk}GB SSD, ${type.price})`;
275
+ }
276
+ }