vibecarbon 0.5.1 → 0.6.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 (61) hide show
  1. package/README.md +1 -1
  2. package/carbon/.env.example +3 -0
  3. package/carbon/.github/workflows/deploy.yml +24 -21
  4. package/carbon/.github/workflows/vibecarbon-build.yml +14 -0
  5. package/carbon/Dockerfile +3 -0
  6. package/carbon/cloud-init/k3s/master-init.sh +26 -2
  7. package/carbon/cloud-init/k3s/supabase-init.sh +22 -2
  8. package/carbon/cloud-init/k3s/worker-init.sh +22 -2
  9. package/carbon/content/docs/cli.mdx +1 -1
  10. package/carbon/docker-compose.yml +6 -0
  11. package/carbon/k8s/base/app/deployment.yaml +5 -0
  12. package/carbon/k8s/values/supabase.values.yaml +30 -0
  13. package/carbon/package.json +14 -14
  14. package/carbon/scripts/generate-sitemap.ts +18 -3
  15. package/carbon/src/client/App.tsx +2 -0
  16. package/carbon/src/client/components/NewsletterSignup.tsx +4 -3
  17. package/carbon/src/client/components/PricingSection.tsx +44 -1
  18. package/carbon/src/client/components/SEO.tsx +6 -3
  19. package/carbon/src/client/index.css +9 -1
  20. package/carbon/src/client/index.html +3 -3
  21. package/carbon/src/client/locales/en.json +1 -1
  22. package/carbon/src/client/pages/Pricing.tsx +169 -0
  23. package/carbon/src/client/pages/settings/Billing.tsx +2 -6
  24. package/carbon/src/server/index.ts +23 -4
  25. package/carbon/src/shared/billing-catalog.ts +25 -0
  26. package/carbon/src/shared/billing-catalog.types.ts +35 -0
  27. package/carbon/src/shared/pricing.ts +56 -1
  28. package/package.json +1 -1
  29. package/src/activate.js +1 -1
  30. package/src/backup.js +33 -87
  31. package/src/configure.js +365 -137
  32. package/src/create.js +5 -2
  33. package/src/deploy.js +24 -7
  34. package/src/destroy.js +11 -4
  35. package/src/failover.js +64 -47
  36. package/src/lib/backup-format.js +84 -0
  37. package/src/lib/billing/stripe-catalog.js +152 -0
  38. package/src/lib/billing/write-catalog.js +123 -0
  39. package/src/lib/ci-setup.js +11 -1
  40. package/src/lib/config-registry.js +116 -0
  41. package/src/lib/deploy/compose/build-args.js +6 -0
  42. package/src/lib/deploy/compose/ha.js +25 -25
  43. package/src/lib/deploy/compose/index.js +29 -18
  44. package/src/lib/deploy/k8s/gitops-deploy.js +38 -14
  45. package/src/lib/deploy/k8s/k3s.js +19 -9
  46. package/src/lib/deploy/orchestrator.js +22 -1
  47. package/src/lib/deploy/prompts.js +21 -2
  48. package/src/lib/deploy/utils.js +56 -32
  49. package/src/lib/github-environments.js +29 -0
  50. package/src/lib/licensing/index.js +8 -3
  51. package/src/lib/licensing/tiers.js +0 -3
  52. package/src/lib/pod-backups.js +3 -3
  53. package/src/lib/providers/base.js +1 -1
  54. package/src/lib/providers/hetzner.js +74 -82
  55. package/src/lib/server-types.js +3 -31
  56. package/src/lib/ssh.js +19 -18
  57. package/src/lib/walg-backups.js +81 -0
  58. package/src/restore.js +122 -202
  59. package/src/scale.js +25 -29
  60. package/src/status.js +0 -69
  61. package/src/lib/cost.js +0 -103
package/src/status.js CHANGED
@@ -407,57 +407,6 @@ function checkGitSync(envName, envConfig) {
407
407
  }
408
408
  }
409
409
 
410
- /**
411
- * Calculate monthly cost for a single environment.
412
- * Includes servers, floating IP, CSI volumes, and S3 storage.
413
- */
414
- function calculateEnvCost(envConfig) {
415
- const { INFRA_COSTS, VOLUME_SIZES, SERVER_TYPES } = HetznerProvider;
416
- let cost = 0;
417
-
418
- // Floating IP
419
- if (envConfig.floatingIp) {
420
- cost += INFRA_COSTS.floatingIp;
421
- }
422
-
423
- // Servers
424
- const servers = envConfig.servers || [];
425
- for (const server of servers) {
426
- const serverType = server.serverType || server.type || 'cpx11';
427
- const spec = SERVER_TYPES[serverType];
428
- if (spec) {
429
- const priceMatch = spec.price.match(/[\d.]+/);
430
- if (priceMatch) cost += Number.parseFloat(priceMatch[0]);
431
- }
432
- }
433
-
434
- // CSI Volumes (base supabase PVCs are always provisioned)
435
- if (servers.length > 0) {
436
- let volumeGb = VOLUME_SIZES.base;
437
- const services = envConfig.services || {};
438
- if (envConfig.observability || services.observability) volumeGb += VOLUME_SIZES.observability;
439
- if (services.n8n) volumeGb += VOLUME_SIZES.n8n;
440
- if (services.metabase) volumeGb += VOLUME_SIZES.metabase;
441
- if (services.redis) volumeGb += VOLUME_SIZES.redis;
442
- cost += volumeGb * INFRA_COSTS.volumePerGb;
443
- }
444
-
445
- // S3 Object Storage
446
- if (envConfig.s3) {
447
- cost += INFRA_COSTS.s3MinBucket;
448
- }
449
-
450
- return cost;
451
- }
452
-
453
- function calculateMonthlyCost(environments) {
454
- let total = 0;
455
- for (const [, envConfig] of Object.entries(environments)) {
456
- total += calculateEnvCost(envConfig);
457
- }
458
- return total > 0 ? `\u20AC${total.toFixed(2)}/mo` : null;
459
- }
460
-
461
410
  // ============================================================================
462
411
  // RENDERING FUNCTIONS
463
412
  // ============================================================================
@@ -625,14 +574,6 @@ function renderEnvironment(envName, envConfig, checks) {
625
574
  );
626
575
  }
627
576
 
628
- // Cost estimate
629
- if (servers.length > 0) {
630
- const cost = calculateEnvCost(envConfig);
631
- if (cost > 0) {
632
- lines.push(`${c.dim('Est. Cost')} ${c.boldYellow(`\u20AC${cost.toFixed(2)}/mo`)}`);
633
- }
634
- }
635
-
636
577
  // Git sync
637
578
  if (checks.gitSync) {
638
579
  lines.push('');
@@ -705,14 +646,6 @@ function renderSummary(allData) {
705
646
  }
706
647
  }
707
648
 
708
- // Total cost
709
- const totalCost = calculateMonthlyCost(
710
- Object.fromEntries(Object.entries(allData.environments || {}).map(([k, v]) => [k, v.config])),
711
- );
712
- if (totalCost) {
713
- lines.push(`${c.dim('Est. Total')} ${c.boldYellow(totalCost)}`);
714
- }
715
-
716
649
  p.note(lines.join('\n'), 'Summary');
717
650
  }
718
651
 
@@ -964,8 +897,6 @@ export async function run(args) {
964
897
  // ============================================================================
965
898
 
966
899
  export {
967
- calculateEnvCost,
968
- calculateMonthlyCost,
969
900
  checkDockerContainers,
970
901
  checkGitSync,
971
902
  checkHttpEndpoint,
package/src/lib/cost.js DELETED
@@ -1,103 +0,0 @@
1
- /**
2
- * Shared cost breakdown utilities for deploy and scale commands.
3
- */
4
-
5
- import { c } from './colors.js';
6
- import { HetznerProvider } from './providers/hetzner.js';
7
- import { parsePrice } from './server-types.js';
8
-
9
- /**
10
- * Build an itemized cost breakdown for the deployment.
11
- * Returns { lines: [{label, cost}], total }.
12
- */
13
- export function buildCostBreakdown({
14
- isComposeDeploy,
15
- ha,
16
- serverType,
17
- masterServerType,
18
- supabaseServerType,
19
- workerServerType,
20
- region,
21
- services,
22
- }) {
23
- const { INFRA_COSTS, VOLUME_SIZES } = HetznerProvider;
24
- const lines = [];
25
- let total = 0;
26
-
27
- if (isComposeDeploy) {
28
- const price = parsePrice(HetznerProvider.getPrice(serverType, region));
29
- if (ha) {
30
- const cost = price * 2;
31
- lines.push({ label: 'Server:', value: `${serverType} ×2`, cost });
32
- total += cost;
33
- } else {
34
- lines.push({ label: 'Server:', value: serverType, cost: price });
35
- total += price;
36
- }
37
- } else {
38
- const regionMultiplier = ha ? 2 : 1;
39
- const suffix = ha ? ' ×2' : '';
40
-
41
- const masterPrice = parsePrice(HetznerProvider.getPrice(masterServerType, region));
42
- const supabasePrice = parsePrice(HetznerProvider.getPrice(supabaseServerType, region));
43
- const workerPrice = parsePrice(HetznerProvider.getPrice(workerServerType, region));
44
-
45
- const masterCost = masterPrice * regionMultiplier;
46
- const supabaseCost = supabasePrice * regionMultiplier;
47
- const workerCost = workerPrice * regionMultiplier;
48
- lines.push({ label: 'Master:', value: `${masterServerType}${suffix}`, cost: masterCost });
49
- lines.push({ label: 'Supabase:', value: `${supabaseServerType}${suffix}`, cost: supabaseCost });
50
- lines.push({ label: 'Worker:', value: `${workerServerType}${suffix}`, cost: workerCost });
51
- total += masterCost + supabaseCost + workerCost;
52
-
53
- // Floating IP
54
- const fipCost = INFRA_COSTS.floatingIp * regionMultiplier;
55
- lines.push({ label: `Floating IP${suffix}`, cost: fipCost });
56
- total += fipCost;
57
-
58
- // CSI Volumes
59
- let volumeGb = VOLUME_SIZES.base;
60
- if (services.observability) volumeGb += VOLUME_SIZES.observability;
61
- if (services.n8n) volumeGb += VOLUME_SIZES.n8n;
62
- if (services.metabase) volumeGb += VOLUME_SIZES.metabase;
63
- if (services.redis) volumeGb += VOLUME_SIZES.redis;
64
- const volumeCost = volumeGb * INFRA_COSTS.volumePerGb * regionMultiplier;
65
- lines.push({ label: 'Volumes:', value: `${volumeGb}GB${suffix}`, cost: volumeCost });
66
- total += volumeCost;
67
- }
68
-
69
- // S3 Object Storage (shared, not multiplied by region)
70
- lines.push({ label: 'S3 Backups', cost: INFRA_COSTS.s3MinBucket });
71
- total += INFRA_COSTS.s3MinBucket;
72
-
73
- return { lines, total };
74
- }
75
-
76
- /**
77
- * Format a cost breakdown into a colored, grid-aligned display string.
78
- */
79
- export function formatCostLines({ lines, total }) {
80
- const labelCol = 14;
81
- const colWidth = 30;
82
- const header = `\n${c.bold('Estimated Monthly Cost')}`;
83
-
84
- const priceWidth = 10;
85
-
86
- const rows = lines.map((item) => {
87
- const priceStr = `€${item.cost.toFixed(2)}`.padStart(priceWidth);
88
- if (item.value) {
89
- const paddedLabel = item.label.padEnd(labelCol);
90
- const fullLabel = `${paddedLabel}${c.bold(item.value)}`;
91
- // Pad the visible width (without ANSI codes) to colWidth
92
- const visibleLen = paddedLabel.length + item.value.length;
93
- const gap = Math.max(0, colWidth - visibleLen);
94
- return `${fullLabel}${' '.repeat(gap)}${c.bold(priceStr)}`;
95
- }
96
- return `${item.label.padEnd(colWidth)}${c.bold(priceStr)}`;
97
- });
98
-
99
- const separator = c.dim('─'.repeat(colWidth + priceWidth));
100
- const totalLine = `${'Est. Total'.padEnd(colWidth)}${c.boldYellow(`€${total.toFixed(2)}/mo`.padStart(priceWidth))}`;
101
-
102
- return [header, ...rows, separator, totalLine].join('\n');
103
- }