vibecarbon 0.5.0 → 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.
- package/README.md +1 -1
- package/carbon/.env.example +3 -0
- package/carbon/.github/workflows/deploy.yml +24 -21
- package/carbon/.github/workflows/vibecarbon-build.yml +14 -0
- package/carbon/Dockerfile +3 -0
- package/carbon/biome.json +1 -1
- package/carbon/cloud-init/k3s/master-init.sh +26 -2
- package/carbon/cloud-init/k3s/supabase-init.sh +22 -2
- package/carbon/cloud-init/k3s/worker-init.sh +22 -2
- package/carbon/content/docs/cli.mdx +1 -1
- package/carbon/docker-compose.yml +6 -0
- package/carbon/k8s/base/app/deployment.yaml +5 -0
- package/carbon/k8s/values/supabase.values.yaml +30 -0
- package/carbon/package.json +34 -36
- package/carbon/scripts/generate-sitemap.ts +18 -3
- package/carbon/src/client/App.tsx +2 -0
- package/carbon/src/client/components/NewsletterSignup.tsx +4 -3
- package/carbon/src/client/components/PricingSection.tsx +44 -1
- package/carbon/src/client/components/SEO.tsx +6 -3
- package/carbon/src/client/components/ui/calendar.tsx +1 -1
- package/carbon/src/client/index.css +9 -1
- package/carbon/src/client/index.html +3 -3
- package/carbon/src/client/locales/en.json +1 -1
- package/carbon/src/client/pages/Pricing.tsx +169 -0
- package/carbon/src/client/pages/settings/Billing.tsx +2 -6
- package/carbon/src/server/index.ts +23 -4
- package/carbon/src/shared/billing-catalog.ts +25 -0
- package/carbon/src/shared/billing-catalog.types.ts +35 -0
- package/carbon/src/shared/pricing.ts +56 -1
- package/package.json +13 -13
- package/src/activate.js +1 -1
- package/src/backup.js +33 -87
- package/src/configure.js +365 -137
- package/src/create.js +5 -2
- package/src/deploy.js +24 -7
- package/src/destroy.js +11 -4
- package/src/failover.js +64 -47
- package/src/lib/backup-format.js +84 -0
- package/src/lib/billing/stripe-catalog.js +152 -0
- package/src/lib/billing/write-catalog.js +123 -0
- package/src/lib/ci-setup.js +11 -1
- package/src/lib/config-registry.js +116 -0
- package/src/lib/deploy/compose/build-args.js +6 -0
- package/src/lib/deploy/compose/ha.js +25 -25
- package/src/lib/deploy/compose/index.js +29 -18
- package/src/lib/deploy/k8s/gitops-deploy.js +38 -14
- package/src/lib/deploy/k8s/k3s.js +20 -10
- package/src/lib/deploy/orchestrator.js +22 -1
- package/src/lib/deploy/prompts.js +21 -2
- package/src/lib/deploy/utils.js +56 -32
- package/src/lib/github-environments.js +29 -0
- package/src/lib/licensing/index.js +8 -3
- package/src/lib/licensing/tiers.js +0 -3
- package/src/lib/pod-backups.js +3 -3
- package/src/lib/providers/base.js +1 -1
- package/src/lib/providers/hetzner.js +74 -82
- package/src/lib/server-types.js +3 -31
- package/src/lib/ssh.js +19 -18
- package/src/lib/walg-backups.js +81 -0
- package/src/restore.js +122 -202
- package/src/scale.js +25 -29
- package/src/status.js +0 -69
- 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
|
-
}
|