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,138 @@
1
+ # Testing
2
+
3
+ Three tiers, picked to mirror what each tier actually proves:
4
+
5
+ | Tier | Env | Wall-clock | What it tests |
6
+ |---|---|---|---|
7
+ | `unit` | node | <1s | Pure functions, validators, pricing/format helpers, cross-cutting invariants |
8
+ | `component` | jsdom | a few s | React components + custom hooks via React Testing Library |
9
+ | `integration` | node | <30s | Hono route handlers end-to-end with externals (Supabase, Stripe, SMTP) mocked |
10
+
11
+ Run them:
12
+
13
+ ```bash
14
+ pnpm test # every tier
15
+ pnpm test:unit
16
+ pnpm test:component
17
+ pnpm test:integration
18
+ pnpm test:watch # vitest watch mode (all tiers)
19
+ pnpm test:coverage # writes coverage/ HTML (v8)
20
+ pnpm test:prepush # what the pre-push git hook runs (lint + all three tiers)
21
+ ```
22
+
23
+ The pre-push hook is installed automatically by `vibecarbon create` — bypass with `git push --no-verify` only if you understand why the tests are red.
24
+
25
+ ## Where to put a new test
26
+
27
+ Mirror `src/` under the tier directory:
28
+
29
+ ```
30
+ tests/
31
+ ├── _helpers/ shared harness
32
+ ├── unit/
33
+ │ ├── shared/pricing.test.ts src/shared/pricing.ts
34
+ │ └── client/utils.test.ts src/client/lib/utils.ts
35
+ ├── structural/
36
+ │ └── i18n-parity.test.ts cross-file invariant
37
+ ├── component/
38
+ │ ├── ErrorBoundary.test.tsx src/client/components/ErrorBoundary.tsx
39
+ │ └── use-auth-settings.test.tsx src/client/hooks/api/useAuthSettings.ts
40
+ └── integration/
41
+ └── server/routes/
42
+ ├── health.test.ts src/server/routes/health.ts
43
+ └── contact.test.ts src/server/routes/v1/contact.ts
44
+ ```
45
+
46
+ **Heuristic** — if the test imports `react` or `@testing-library/react`, it's `component`. If it imports anything from `src/server/`, it's `integration`. Otherwise it's `unit`.
47
+
48
+ ## Tier picking guide
49
+
50
+ - **Could this be a pure function?** Refactor it into one and write a `unit` test. Cheap, fast, never flaky.
51
+ - **Behavior depends on the DOM, focus, keyboard, or React state transitions?** `component` tier.
52
+ - **You want to assert what the Hono route returns for a real request?** `integration` tier.
53
+ - **You want to prove two files agree on a key set, schema, or constants?** `structural` test under `tests/structural/` — runs under `unit`.
54
+
55
+ ## Helpers
56
+
57
+ All under `tests/_helpers/`:
58
+
59
+ | Helper | Use it when |
60
+ |---|---|
61
+ | `app.ts` → `mountRoute(prefix, routes)` | Building a one-route Hono app for an integration test. No global middleware so assertions stay focused. |
62
+ | `app.ts` → `jsonPost(body)` | POSTing JSON to `app.request()` without re-typing headers. |
63
+ | `factories.ts` → `makeUser`, `makeOrg`, `makeContactSubmission` | Plausible domain objects to hand to mocked Supabase resolved values. |
64
+ | `jwt.ts` → `mockJwt(payload, secret?)` | HS256-signed bearer token for auth-protected routes. |
65
+ | `env.ts` → `mockEnv({...})` | Temporarily swap process.env vars for one test; returns a restore closure. |
66
+ | `setup-rtl.ts` | Wired into the `component` project — loads jest-dom matchers and runs RTL's cleanup between tests. |
67
+ | `setup-integration.ts` | Wired into the `integration` project — seeds plausible Supabase env values so server modules pass zod validation when imported. |
68
+
69
+ ## Mocking the external boundary
70
+
71
+ Server-side modules are imported with their relative paths inside `src/server/`. Pass the path alias `@server/...` to `vi.mock` — vitest matches by resolved file path, so it'll intercept whichever path the production code happens to use.
72
+
73
+ ```ts
74
+ // Supabase
75
+ vi.mock('@server/lib/supabase', () => ({
76
+ supabaseAdmin: { from: () => ({ insert: vi.fn().mockResolvedValue({ error: null }) }) },
77
+ }));
78
+
79
+ // Stripe
80
+ vi.mock('@server/lib/stripe', () => ({
81
+ stripe: { checkout: { sessions: { create: vi.fn() } } },
82
+ }));
83
+
84
+ // SMTP — fire-and-forget
85
+ vi.mock('@server/lib/email', () => ({
86
+ sendEmail: vi.fn().mockResolvedValue(undefined),
87
+ }));
88
+
89
+ // Rate limiter — the real one starts a 60s setInterval at module load;
90
+ // replace with a pass-through middleware so tests exit cleanly.
91
+ vi.mock('@server/lib/rate-limiter', () => ({
92
+ createRateLimiter: () => async (_c: unknown, next: () => Promise<void>) => {
93
+ await next();
94
+ },
95
+ }));
96
+
97
+ // Logger — quiet the expected error-path log
98
+ vi.mock('@server/lib/logger', () => ({
99
+ logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn(), debug: vi.fn() },
100
+ }));
101
+ ```
102
+
103
+ Client-side fetch — stub the global and unstub in afterEach:
104
+
105
+ ```ts
106
+ beforeEach(() => {
107
+ vi.stubGlobal('fetch', vi.fn(async () => new Response('{}', { status: 200 })));
108
+ });
109
+ afterEach(() => vi.unstubAllGlobals());
110
+ ```
111
+
112
+ TanStack Query in component tests — wrap with a fresh QueryClient each render, `retry: false` so rejected fetches surface immediately:
113
+
114
+ ```tsx
115
+ function makeWrapper() {
116
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
117
+ return ({ children }) => <QueryClientProvider client={client}>{children}</QueryClientProvider>;
118
+ }
119
+
120
+ const { result } = renderHook(() => useMyHook(), { wrapper: makeWrapper() });
121
+ ```
122
+
123
+ ## Don't mock these
124
+
125
+ - `zod` schemas — they're the contract; test the route's behavior through them.
126
+ - The route file under test (obviously).
127
+ - `clsx`, `class-variance-authority`, or other pure UI utilities — let them run.
128
+
129
+ ## What we deliberately don't do (yet)
130
+
131
+ - **No real Postgres in `integration`.** The Supabase dev container doesn't expose Postgres on the host. Mocking at the Supabase client boundary gives fast, deterministic tests and covers the route logic. If you need SQL-level coverage (RLS, triggers, complex joins), add testcontainers or expose a dev port and write a `tests/database/` tier — the boundary stays the same.
132
+ - **No E2E / Playwright.** Add when you actually have flows worth black-box testing through the browser.
133
+ - **No snapshot tests.** Brittle and noisy in a fast-moving template; assert behavior instead.
134
+ - **No MSW.** `vi.stubGlobal('fetch', ...)` covers the seed cases; adopt MSW if your fetch surface grows.
135
+
136
+ ## When you use the test-maintainer agent
137
+
138
+ It reads `tests/_helpers/` and the existing test files to learn this project's conventions, then matches the style when writing new tests. The smaller and more consistent the seed it sees, the more consistent its output. If you find yourself adjusting its output the same way twice, encode the convention here.
@@ -0,0 +1,75 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM ubuntu:22.04
3
+
4
+ # Base image is ubuntu, not alpine, because wal-g v3.x stopped publishing
5
+ # alpine-musl prebuilt binaries — only ubuntu glibc builds remain. We need
6
+ # wal-g v3+ for the current S3 + Postgres 16 wire protocol support.
7
+
8
+ ENV DEBIAN_FRONTEND=noninteractive
9
+
10
+ # Install PostgreSQL 16 client + utilities. PG 16 isn't in the default
11
+ # ubuntu 22.04 repos; pull it from the postgresql apt repo. AWS CLI is
12
+ # installed as the standalone v2 binary further down (the apt-shipped
13
+ # `awscli` is v1 and pulls ~500MB of Python deps; v2 is ~80MB self-
14
+ # contained and the only version AWS itself supports for new code).
15
+ #
16
+ # apt cache mount: keeps /var/cache/apt populated across rebuilds so
17
+ # `apt-get install` reuses .deb files. The cache lives outside the layer,
18
+ # so we don't need the historical `rm -rf /var/lib/apt/lists/*` to keep
19
+ # the final image small — that's still done below as a belt-and-braces
20
+ # measure for cases where the cache mount isn't honored (e.g. legacy
21
+ # `docker build` without BuildKit).
22
+ # `sharing=locked` serializes parallel builds against the same cache
23
+ # (apt's lock semantics expect exclusive access).
24
+ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
25
+ --mount=type=cache,target=/var/lib/apt,sharing=locked \
26
+ apt-get update && apt-get install -y --no-install-recommends \
27
+ ca-certificates curl gnupg lsb-release \
28
+ bash tar gzip unzip && \
29
+ install -d /usr/share/postgresql-common/pgdg && \
30
+ curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
31
+ -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc && \
32
+ echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt jammy-pgdg main" \
33
+ > /etc/apt/sources.list.d/pgdg.list && \
34
+ apt-get update && apt-get install -y --no-install-recommends \
35
+ postgresql-client-16
36
+
37
+ # Install AWS CLI v2 standalone binary (~80MB) instead of `apt-get install
38
+ # awscli` (v1 with ~500MB of Python/boto3 deps). Saves ~400MB on the
39
+ # final image, which matters because the backup image is sideloaded to
40
+ # every k3s node (~6 nodes in HA = ~2.4GB less network per deploy).
41
+ # --fail makes curl exit non-zero on HTTP 4xx/5xx instead of piping a
42
+ # 404 HTML body into unzip.
43
+ RUN curl -fL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip \
44
+ -o /tmp/awscli.zip && \
45
+ unzip -q /tmp/awscli.zip -d /tmp && \
46
+ /tmp/aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli && \
47
+ rm -rf /tmp/aws /tmp/awscli.zip
48
+
49
+ # Install WAL-G v3.0.5 (ubuntu 22.04 amd64 build).
50
+ # --fail makes curl exit non-zero on HTTP 4xx/5xx instead of piping a 404
51
+ # HTML body into tar (the previous v3.0.3-alpine URL 404'd, tar saw "<html>"
52
+ # and errored "not in gzip format" — observed 2026-04-26 matrix run #2).
53
+ RUN curl -fL https://github.com/wal-g/wal-g/releases/download/v3.0.5/wal-g-pg-ubuntu-22.04-amd64.tar.gz \
54
+ | tar -xz && \
55
+ mv wal-g-pg-ubuntu-22.04-amd64 /usr/local/bin/wal-g && \
56
+ chmod +x /usr/local/bin/wal-g
57
+
58
+ # Security: non-root system user. We can't reuse the name `backup` —
59
+ # ubuntu pre-populates a `backup` system group (GID 34, used for system
60
+ # backup operations) and `groupadd backup` errors with exit 9 ("group
61
+ # 'backup' already exists"). The previous alpine version sidestepped
62
+ # this because alpine doesn't ship the same default. Use a unique name
63
+ # (`vbbackup`) and let --system pick a free system GID/UID.
64
+ # The k8s manifest doesn't pin runAsUser so any UID works.
65
+ RUN groupadd --system vbbackup && \
66
+ useradd --system -g vbbackup -s /usr/sbin/nologin -M vbbackup
67
+
68
+ # Copy backup script
69
+ COPY backup.sh /usr/local/bin/backup.sh
70
+ RUN chmod +x /usr/local/bin/backup.sh
71
+
72
+ USER vbbackup
73
+
74
+ # CronJob handles scheduling — script runs once and exits
75
+ ENTRYPOINT ["/usr/local/bin/backup.sh"]
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+ set -e
3
+ # pipefail is critical: the pg_dump | gzip pipeline below silently uploaded
4
+ # 170-byte gzip-header-only tarballs when pg_dump failed (wrong PGHOST),
5
+ # making restore think backups existed when they were empty. Observed
6
+ # 2026-04-27 k8s-ha matrix #6 — restore failed "No backups found in S3"
7
+ # because the broken tarball had no SQL inside.
8
+ set -o pipefail
9
+
10
+ # ============================================================================
11
+ # Vibecarbon Blazing Fast Backup Script (WAL-G)
12
+ #
13
+ # Optimized for performance and robustness using continuous archiving.
14
+ # Fallback to pg_dump for legacy compatibility.
15
+ #
16
+ # Environment Variables:
17
+ # PGHOST, PGPORT, PGUSER, PGDATABASE, PGPASSWORD - PostgreSQL connection
18
+ # S3_BUCKET, S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_REGION - S3 config
19
+ # S3_BACKUP_BUCKET - Dedicated backup bucket (falls back to S3_BUCKET)
20
+ # BACKUP_RETENTION_DAYS - Days to keep backups (default: 7)
21
+ # PROJECT_NAME - Project name for backup naming
22
+ # BACKUP_MODE - "walg" (default) or "pg_dump"
23
+ # ============================================================================
24
+
25
+ TIMESTAMP=$(date +%Y%m%d_%H%M%S)
26
+ PROJECT="${PROJECT_NAME:-vibecarbon}"
27
+ MODE="${BACKUP_MODE:-walg}"
28
+ RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
29
+
30
+ echo "=== Vibecarbon Backup (${MODE}) ==="
31
+ echo "Started at $(date)"
32
+
33
+ # --- S3 CONFIGURATION ---
34
+ UPLOAD_BUCKET="${S3_BACKUP_BUCKET:-${S3_BUCKET}}"
35
+ export AWS_ACCESS_KEY_ID="${S3_ACCESS_KEY}"
36
+ export AWS_SECRET_ACCESS_KEY="${S3_SECRET_KEY}"
37
+ export AWS_DEFAULT_REGION="${S3_REGION:-us-east-1}"
38
+ export AWS_ENDPOINT="${S3_ENDPOINT}"
39
+
40
+ if [ -z "${UPLOAD_BUCKET}" ] || [ -z "${AWS_ACCESS_KEY_ID}" ] || [ -z "${AWS_SECRET_ACCESS_KEY}" ] || [ -z "${AWS_ENDPOINT}" ]; then
41
+ echo "Error: S3 configuration missing. Backup cannot proceed."
42
+ exit 1
43
+ fi
44
+
45
+ # --- WAL-G CONFIGURATION ---
46
+ export WALG_S3_PREFIX="s3://${UPLOAD_BUCKET}/backups/${PROJECT}/walg"
47
+ # Use the same credentials for WAL-G (it uses AWS_* by default, but we can be explicit)
48
+ export WALE_S3_PREFIX="${WALG_S3_PREFIX}"
49
+ # Parallelism for faster compression/upload
50
+ export WALG_COMPRESSION_METHOD="lz4"
51
+ export WALG_UPLOAD_CONCURRENCY=4
52
+
53
+ if [ "${MODE}" = "walg" ]; then
54
+ echo "Creating full base backup with WAL-G..."
55
+ # WAL-G requires PGHOST to be reachable.
56
+ # If running in a sidecar, it might need to connect via socket or network.
57
+ # We assume PGHOST is set correctly (e.g. "db").
58
+ export PGDATA="/var/lib/postgresql/data"
59
+
60
+ # Check if we can run backup-push.
61
+ # Note: backup-push usually needs to run on the database server or have access to PGDATA
62
+ # if it's doing a direct filesystem scan.
63
+ # If this script runs in a separate container, it uses the network protocol.
64
+
65
+ wal-g backup-push "${PGDATA}"
66
+
67
+ echo "Full base backup completed."
68
+
69
+ echo "Cleaning up old backups (retention: ${RETENTION_DAYS} days)..."
70
+ wal-g delete before "$(date -d "-${RETENTION_DAYS} days" +%Y-%m-%dT%H:%M:%SZ)" --confirm
71
+
72
+ else
73
+ # --- LEGACY PG_DUMP FALLBACK ---
74
+ echo "Falling back to pg_dump..."
75
+ WORK_DIR="/tmp/backup_${TIMESTAMP}"
76
+ mkdir -p "${WORK_DIR}"
77
+ ARCHIVE_NAME="${PROJECT}_${TIMESTAMP}_full.tar.gz"
78
+
79
+ PGPASSWORD="${PGPASSWORD}" pg_dump \
80
+ -h "${PGHOST:-postgres}" \
81
+ -p "${PGPORT:-5432}" \
82
+ -U "${PGUSER:-supabase_admin}" \
83
+ "${PGDATABASE:-postgres}" | gzip > "${WORK_DIR}/postgres.sql.gz"
84
+
85
+ tar czf "/tmp/${ARCHIVE_NAME}" -C "${WORK_DIR}" .
86
+
87
+ aws s3 cp "/tmp/${ARCHIVE_NAME}" \
88
+ "s3://${UPLOAD_BUCKET}/backups/${ARCHIVE_NAME}" \
89
+ --endpoint-url "${S3_ENDPOINT}"
90
+
91
+ rm -rf "${WORK_DIR}"
92
+ rm "/tmp/${ARCHIVE_NAME}"
93
+ fi
94
+
95
+ echo "=== Backup completed at $(date) ==="
@@ -0,0 +1,140 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # ============================================================================
5
+ # Vibecarbon Compose Backup Script
6
+ #
7
+ # Runs via cron on the VPS. Creates a full backup archive containing all
8
+ # PostgreSQL databases and storage files, uploads to S3, and cleans up
9
+ # old backups based on retention policy.
10
+ #
11
+ # Environment Variables (loaded from .backup-env):
12
+ # PROJECT_NAME - Project name for backup naming
13
+ # BACKUP_RETENTION_DAYS - Days to keep backups (default: 30)
14
+ # S3_BACKUP_BUCKET - S3 bucket for backup storage
15
+ # S3_ACCESS_KEY - S3 access key
16
+ # S3_SECRET_KEY - S3 secret key
17
+ # S3_ENDPOINT - S3 endpoint URL
18
+ # S3_REGION - S3 region
19
+ # ============================================================================
20
+
21
+ # Prevent concurrent backups
22
+ LOCKFILE="/tmp/vibecarbon-backup.lock"
23
+ exec 200>"${LOCKFILE}"
24
+ flock -n 200 || { echo "Backup already running — skipping"; exit 0; }
25
+
26
+ PROJECT="${PROJECT_NAME:?PROJECT_NAME is required}"
27
+ REMOTE_DIR="/opt/${PROJECT}"
28
+ TIMESTAMP=$(date +%Y%m%d_%H%M%S)
29
+ WORK_DIR="/tmp/backup_${TIMESTAMP}"
30
+ ARCHIVE_NAME="${PROJECT}_${TIMESTAMP}_full.tar.gz"
31
+ RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-30}"
32
+
33
+ echo "=== Vibecarbon Compose Backup ==="
34
+ echo "Started at $(date)"
35
+ echo "Retention: ${RETENTION_DAYS} days"
36
+
37
+ mkdir -p "${WORK_DIR}"
38
+
39
+ # ============================================================================
40
+ # Step 1: Dump postgres database
41
+ # ============================================================================
42
+
43
+ echo "Backing up postgres database..."
44
+ cd "${REMOTE_DIR}" && docker compose exec -T db pg_dump -U postgres postgres \
45
+ | gzip > "${WORK_DIR}/postgres.sql.gz"
46
+ DB_SIZE=$(du -h "${WORK_DIR}/postgres.sql.gz" | cut -f1)
47
+ echo "postgres: ${DB_SIZE}"
48
+
49
+ # ============================================================================
50
+ # Step 2: Dump optional service databases (n8n, metabase)
51
+ # ============================================================================
52
+
53
+ for EXTRA_DB in n8n metabase; do
54
+ DB_EXISTS=$(cd "${REMOTE_DIR}" && docker compose exec -T db \
55
+ psql -U postgres -tAc "SELECT 1 FROM pg_database WHERE datname='${EXTRA_DB}'" 2>/dev/null || echo "")
56
+ if [ "${DB_EXISTS}" = "1" ]; then
57
+ echo "Backing up ${EXTRA_DB} database..."
58
+ cd "${REMOTE_DIR}" && docker compose exec -T db pg_dump -U postgres "${EXTRA_DB}" \
59
+ | gzip > "${WORK_DIR}/${EXTRA_DB}.sql.gz"
60
+ EXTRA_SIZE=$(du -h "${WORK_DIR}/${EXTRA_DB}.sql.gz" | cut -f1)
61
+ echo "${EXTRA_DB}: ${EXTRA_SIZE}"
62
+ fi
63
+ done
64
+
65
+ # ============================================================================
66
+ # Step 3: Storage files (file backend only)
67
+ # ============================================================================
68
+
69
+ STORAGE_COUNT=$(cd "${REMOTE_DIR}" && docker compose exec -T storage \
70
+ sh -c 'find /var/lib/storage -type f 2>/dev/null | wc -l' 2>/dev/null || echo "0")
71
+ if [ "${STORAGE_COUNT}" -gt 0 ] 2>/dev/null; then
72
+ echo "Backing up ${STORAGE_COUNT} storage files..."
73
+ cd "${REMOTE_DIR}" && docker compose exec -T storage \
74
+ tar czf - -C / var/lib/storage > "${WORK_DIR}/storage.tar.gz"
75
+ STORAGE_SIZE=$(du -h "${WORK_DIR}/storage.tar.gz" | cut -f1)
76
+ echo "Storage: ${STORAGE_SIZE}"
77
+ fi
78
+
79
+ # ============================================================================
80
+ # Step 4: Create combined archive
81
+ # ============================================================================
82
+
83
+ echo "Creating archive: ${ARCHIVE_NAME}"
84
+ tar czf "${REMOTE_DIR}/backups/${ARCHIVE_NAME}" -C "${WORK_DIR}" .
85
+ TOTAL_SIZE=$(du -h "${REMOTE_DIR}/backups/${ARCHIVE_NAME}" | cut -f1)
86
+ echo "Total archive size: ${TOTAL_SIZE}"
87
+
88
+ # ============================================================================
89
+ # Step 5: Upload to S3 (if configured)
90
+ # ============================================================================
91
+
92
+ if [ -n "${S3_BACKUP_BUCKET}" ] && [ -n "${S3_ACCESS_KEY}" ] && [ -n "${S3_SECRET_KEY}" ] && [ -n "${S3_ENDPOINT}" ]; then
93
+ echo "Uploading to S3: s3://${S3_BACKUP_BUCKET}/backups/${ARCHIVE_NAME}"
94
+
95
+ export AWS_ACCESS_KEY_ID="${S3_ACCESS_KEY}"
96
+ export AWS_SECRET_ACCESS_KEY="${S3_SECRET_KEY}"
97
+ export AWS_DEFAULT_REGION="${S3_REGION:-us-east-1}"
98
+
99
+ aws s3 cp "${REMOTE_DIR}/backups/${ARCHIVE_NAME}" \
100
+ "s3://${S3_BACKUP_BUCKET}/backups/${ARCHIVE_NAME}" \
101
+ --endpoint-url "${S3_ENDPOINT}"
102
+
103
+ echo "Upload complete"
104
+
105
+ # S3 retention cleanup
106
+ echo "Cleaning up S3 backups older than ${RETENTION_DAYS} days..."
107
+ CUTOFF_DATE=$(date -d "-${RETENTION_DAYS} days" +%Y%m%d 2>/dev/null || echo "")
108
+ if [ -n "${CUTOFF_DATE}" ]; then
109
+ aws s3api list-objects-v2 \
110
+ --bucket "${S3_BACKUP_BUCKET}" \
111
+ --prefix "backups/${PROJECT}_" \
112
+ --endpoint-url "${S3_ENDPOINT}" \
113
+ --query "Contents[].Key" \
114
+ --output text 2>/dev/null | tr '\t' '\n' | while read -r key; do
115
+ [ -z "${key}" ] || [ "${key}" = "None" ] && continue
116
+ FILE_DATE=$(echo "${key}" | sed -n 's/.*_\([0-9]\{8\}\)_[0-9]\{6\}_full\.tar\.gz$/\1/p')
117
+ if [ -n "${FILE_DATE}" ] && [ "${FILE_DATE}" -lt "${CUTOFF_DATE}" ] 2>/dev/null; then
118
+ echo "Deleting expired backup: ${key}"
119
+ aws s3 rm "s3://${S3_BACKUP_BUCKET}/${key}" --endpoint-url "${S3_ENDPOINT}"
120
+ fi
121
+ done
122
+ fi
123
+ else
124
+ echo "No S3 configured — backup stored locally only"
125
+ fi
126
+
127
+ # ============================================================================
128
+ # Step 6: Local retention cleanup
129
+ # ============================================================================
130
+
131
+ find "${REMOTE_DIR}/backups" -name "${PROJECT}_*_full.tar.gz" -mtime "+${RETENTION_DAYS}" -delete 2>/dev/null || true
132
+
133
+ # ============================================================================
134
+ # Cleanup
135
+ # ============================================================================
136
+
137
+ rm -rf "${WORK_DIR}"
138
+
139
+ echo "=== Backup completed at $(date) ==="
140
+ echo "Archive: ${ARCHIVE_NAME} (${TOTAL_SIZE})"
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.9/schema.json",
3
+ "linter": {
4
+ "enabled": true,
5
+ "rules": {
6
+ "recommended": true,
7
+ "complexity": {
8
+ "noUselessFragments": "warn"
9
+ },
10
+ "correctness": {
11
+ "noUnusedImports": "error",
12
+ "noUnusedVariables": "warn",
13
+ "useExhaustiveDependencies": "warn"
14
+ },
15
+ "style": {
16
+ "noNonNullAssertion": "warn"
17
+ },
18
+ "suspicious": {
19
+ "noExplicitAny": "warn",
20
+ "noArrayIndexKey": "warn",
21
+ "noDoubleEquals": "warn",
22
+ "noDocumentCookie": "off"
23
+ },
24
+ "a11y": {
25
+ "useSemanticElements": "off",
26
+ "useFocusableInteractive": "warn",
27
+ "noRedundantRoles": "warn",
28
+ "noLabelWithoutControl": "warn",
29
+ "noPositiveTabindex": "warn",
30
+ "useKeyWithClickEvents": "off",
31
+ "useAriaPropsForRole": "warn",
32
+ "useValidAriaProps": "warn",
33
+ "useAriaPropsSupportedByRole": "off"
34
+ },
35
+ "security": {
36
+ "noDangerouslySetInnerHtml": "warn"
37
+ }
38
+ }
39
+ },
40
+ "formatter": {
41
+ "enabled": true,
42
+ "indentStyle": "space",
43
+ "indentWidth": 2,
44
+ "lineWidth": 100
45
+ },
46
+ "javascript": {
47
+ "formatter": {
48
+ "quoteStyle": "single",
49
+ "semicolons": "always",
50
+ "trailingCommas": "es5"
51
+ }
52
+ },
53
+ "css": {
54
+ "parser": {
55
+ "cssModules": true
56
+ },
57
+ "formatter": {
58
+ "enabled": false
59
+ },
60
+ "linter": {
61
+ "enabled": false
62
+ }
63
+ },
64
+ "assist": {
65
+ "actions": {
66
+ "source": {
67
+ "organizeImports": "on"
68
+ }
69
+ }
70
+ },
71
+ "files": {
72
+ "includes": [
73
+ "src/**/*.ts",
74
+ "src/**/*.tsx",
75
+ "src/**/*.js",
76
+ "src/**/*.jsx",
77
+ "*.ts",
78
+ "*.tsx",
79
+ "*.js",
80
+ "*.jsx"
81
+ ]
82
+ }
83
+ }
@@ -0,0 +1,55 @@
1
+ #cloud-config
2
+ # Vibecarbon compose-deploy bootstrap.
3
+ #
4
+ # This user-data runs at VPS boot time in parallel with Hetzner's default
5
+ # docker-ce app (Ubuntu 24.04 with Docker + compose-plugin pre-installed).
6
+ # By finishing UFW + unattended-upgrades before SSH is even reachable, the
7
+ # CLI's post-SSH setupServer becomes a ~2-second marker-file probe instead
8
+ # of a ~90-second serial apt-get chain.
9
+ #
10
+ # Writing the marker file `/var/lib/vibecarbon/ready` is the contract that
11
+ # tells the CLI it's safe to proceed. If user-data fails, the marker never
12
+ # appears; the CLI reports and dumps /var/log/cloud-init-output.log.
13
+
14
+ package_update: true
15
+ packages:
16
+ - ufw
17
+ - unattended-upgrades
18
+
19
+ write_files:
20
+ - path: /etc/apt/apt.conf.d/20auto-upgrades
21
+ permissions: '0644'
22
+ content: |
23
+ APT::Periodic::Update-Package-Lists "1";
24
+ APT::Periodic::Unattended-Upgrade "1";
25
+
26
+ runcmd:
27
+ # UFW defaults + the three ports Traefik needs. Ordering matters: we enable
28
+ # last so the allow rules are in place before the first policy flip.
29
+ - [ufw, default, deny, incoming]
30
+ - [ufw, default, allow, outgoing]
31
+ - [ufw, allow, '22/tcp']
32
+ - [ufw, allow, '80/tcp']
33
+ - [ufw, allow, '443/tcp']
34
+ - [sh, -c, 'echo y | ufw enable']
35
+
36
+ # Ensure Docker is running (docker-ce app usually starts it, but be defensive).
37
+ - [systemctl, enable, --now, docker]
38
+
39
+ # Note: an earlier iteration (iter-perfwave4b 2026-05-06) added parallel
40
+ # `docker pull` of supabase service images here to overlap pulls with
41
+ # iac.upStack. Net result was -25s: the unauthenticated pulls from
42
+ # Hetzner DC IPs hit Docker Hub's per-IP rate limit, serializing/
43
+ # throttling them, which extended cloudInitReady by ~61s while only
44
+ # saving ~35s on compose.up. The marker file can't land until cloud-init
45
+ # finishes runcmd, so a slow pre-pull blocks the operator longer than
46
+ # paying the same cost during compose.up (which uses authenticated
47
+ # pulls — quota-exempt). Reverted; reconcile.sh's `docker compose pull`
48
+ # is the right place for this work, post-dockerLoginOnServer.
49
+
50
+ # Marker file — the CLI's setupServer polls for this. The parent dir may
51
+ # not exist yet on a first boot; mkdir -p is safe either way.
52
+ - [mkdir, -p, /var/lib/vibecarbon]
53
+ - [sh, -c, 'printf "%s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /var/lib/vibecarbon/ready']
54
+
55
+ final_message: "vibecarbon cloud-init complete after $UPTIME seconds"