vibecarbon 0.4.0 → 0.5.1
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/carbon/backup/compose-backup.sh +45 -121
- package/carbon/biome.json +1 -1
- package/carbon/docker-compose.metabase.prod.yml +1 -2
- package/carbon/docker-compose.n8n.prod.yml +1 -2
- package/carbon/docker-compose.prod.yml +9 -7
- package/carbon/k8s/base/traefik/certificate.yaml +13 -7
- package/carbon/k8s/values/supabase.values.yaml +8 -0
- package/carbon/package.json +25 -27
- package/carbon/scripts/docker-up.js +13 -1
- package/carbon/src/client/components/DeploymentScenariosSection.tsx +171 -0
- package/carbon/src/client/components/ui/calendar.tsx +1 -1
- package/carbon/src/client/locales/de.json +29 -0
- package/carbon/src/client/locales/en.json +29 -0
- package/carbon/src/client/locales/es.json +29 -0
- package/carbon/src/client/locales/fr.json +29 -0
- package/carbon/src/client/locales/pt.json +29 -0
- package/carbon/src/client/pages/Home.tsx +5 -0
- package/package.json +13 -13
- package/src/backup.js +11 -30
- package/src/destroy.js +5 -43
- package/src/lib/deploy/bundle.js +120 -5
- package/src/lib/deploy/compose/ha.js +114 -1
- package/src/lib/deploy/compose/index.js +243 -177
- package/src/lib/deploy/k8s/k3s.js +120 -67
- package/src/lib/deploy/orchestrator.js +22 -0
- package/src/lib/iac/index.js +45 -7
- package/src/restore.js +135 -54
- package/src/scale.js +33 -57
package/src/scale.js
CHANGED
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
* prompt covers that need without tying up two top-level letters.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { existsSync, readFileSync
|
|
17
|
-
import os from 'node:os';
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
17
|
import { join } from 'node:path';
|
|
19
18
|
import * as p from '@clack/prompts';
|
|
20
19
|
import { getGHCRCredentials } from './lib/build.js';
|
|
@@ -49,7 +48,7 @@ import { assertInProjectDir } from './lib/project-guard.js';
|
|
|
49
48
|
import { HetznerProvider } from './lib/providers/hetzner.js';
|
|
50
49
|
import { buildComposeTypeOptions, buildSimpleTypeOptions } from './lib/server-types.js';
|
|
51
50
|
import { parseDotenv } from './lib/shell.js';
|
|
52
|
-
import {
|
|
51
|
+
import { sshRun, sshRunScript } from './lib/ssh.js';
|
|
53
52
|
import { VERSION } from './lib/version.js';
|
|
54
53
|
|
|
55
54
|
// ============================================================================
|
|
@@ -296,12 +295,14 @@ async function scaleCompose(environment, envConfig, projectConfig, options = {})
|
|
|
296
295
|
envConfig.deployMode === 'compose-ha' ? `${server.role} (${server.ip})` : server.ip;
|
|
297
296
|
const s = p.spinner();
|
|
298
297
|
|
|
299
|
-
// 1. Backup database on old server
|
|
298
|
+
// 1. Backup database on old server via wal-g (pushes directly to S3)
|
|
300
299
|
s.start(`Backing up database on ${label}...`);
|
|
301
|
-
|
|
302
|
-
backupCompose(server.ip, sshKeyPath, projectName, {
|
|
300
|
+
await perfAsync('scale.backupOldServer', async () =>
|
|
301
|
+
backupCompose(server.ip, sshKeyPath, projectName, {
|
|
302
|
+
retain: envConfig.backup?.retentionDays,
|
|
303
|
+
}),
|
|
303
304
|
);
|
|
304
|
-
s.stop(
|
|
305
|
+
s.stop('Database backed up (wal-g base backup pushed to S3)');
|
|
305
306
|
|
|
306
307
|
// 2. Create new server. user_data front-loads ufw + unattended-upgrades
|
|
307
308
|
// during boot so setupServer is a ~2s marker-file probe below.
|
|
@@ -487,41 +488,24 @@ async function scaleCompose(environment, envConfig, projectConfig, options = {})
|
|
|
487
488
|
);
|
|
488
489
|
s.stop('Services started');
|
|
489
490
|
|
|
490
|
-
// 7.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
`cd ${remoteDir} && for i in $(seq 1 30); do docker compose exec -T db pg_isready -U postgres >/dev/null 2>&1 && break || sleep 2; done`,
|
|
498
|
-
{ timeout: 120_000 },
|
|
499
|
-
);
|
|
500
|
-
s.stop('Database ready for restore');
|
|
501
|
-
|
|
502
|
-
// 8. Transfer backup from old server to new server
|
|
503
|
-
s.start('Transferring backup to new server...');
|
|
504
|
-
const localTmpBackup = join(os.tmpdir(), archiveName);
|
|
505
|
-
try {
|
|
506
|
-
scpDownload(server.ip, sshKeyPath, `${remoteDir}/backups/${archiveName}`, localTmpBackup);
|
|
507
|
-
sshRun(newIp, sshKeyPath, ['mkdir', '-p', `${remoteDir}/backups`]);
|
|
508
|
-
scpUpload(newIp, sshKeyPath, localTmpBackup, `${remoteDir}/backups/${archiveName}`);
|
|
509
|
-
} finally {
|
|
510
|
-
try {
|
|
511
|
-
unlinkSync(localTmpBackup);
|
|
512
|
-
} catch {
|
|
513
|
-
// ignore cleanup errors
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
s.stop('Backup transferred');
|
|
517
|
-
|
|
518
|
-
// 9. Restore database on new server
|
|
519
|
-
s.start('Restoring database...');
|
|
491
|
+
// 7. Restore database on new server from S3 via wal-g. The base backup +
|
|
492
|
+
// WAL were pushed straight to S3 by step 1's backupCompose, so the
|
|
493
|
+
// restore fetches LATEST directly — no local archive to transfer.
|
|
494
|
+
// S3 credentials come from the db container's env (sourced from .env)
|
|
495
|
+
// so no opts are needed; restoreCompose handles its own verify loop
|
|
496
|
+
// (pg_isready + pg_is_in_recovery() = f) before returning.
|
|
497
|
+
s.start('Restoring database from S3 via wal-g...');
|
|
520
498
|
await perfAsync('scale.restoreDb', async () =>
|
|
521
|
-
restoreCompose(newIp, sshKeyPath, projectName,
|
|
499
|
+
restoreCompose(newIp, sshKeyPath, projectName, 'latest'),
|
|
522
500
|
);
|
|
523
501
|
s.stop('Database restored');
|
|
524
502
|
|
|
503
|
+
// Remote compose project dir on the new server — used by the ACME reset
|
|
504
|
+
// and the post-restore service recreate below. (Previously declared by a
|
|
505
|
+
// since-removed pre-restore readiness step; restoreCompose now owns that
|
|
506
|
+
// wait, so declare it here.)
|
|
507
|
+
const remoteDir = `/opt/${projectName}`;
|
|
508
|
+
|
|
525
509
|
// 9a. Update DNS to the new server. On HTTP-01 this must happen BEFORE
|
|
526
510
|
// recreating services (Traefik challenges the A record on startup; a
|
|
527
511
|
// stale record fails all challenges with a 30+ min retry). On DNS-01 the
|
|
@@ -577,34 +561,26 @@ async function scaleCompose(environment, envConfig, projectConfig, options = {})
|
|
|
577
561
|
: []),
|
|
578
562
|
...(services.redis ? ['-f docker-compose.redis.yml'] : []),
|
|
579
563
|
].join(' ');
|
|
564
|
+
// On compose-ha, docker-compose.replication.yml (written by configurePrimaryReplication)
|
|
565
|
+
// publishes 5433 → db:5432 for pg_basebackup / streaming replication.
|
|
566
|
+
// Append it when present so force-recreate doesn't silently drop the 5433 mapping.
|
|
567
|
+
// On single-server compose the file never exists — the shell conditional is a no-op.
|
|
568
|
+
const replOverlayFlag =
|
|
569
|
+
"$([ -f docker-compose.replication.yml ] && echo '-f docker-compose.replication.yml')";
|
|
580
570
|
s.start('Recreating all services after restore...');
|
|
581
571
|
sshRunScript(
|
|
582
572
|
newIp,
|
|
583
573
|
sshKeyPath,
|
|
584
|
-
`cd ${remoteDir} && docker compose ${composeFlags} up -d --force-recreate 2>&1`,
|
|
574
|
+
`cd ${remoteDir} && docker compose ${composeFlags} ${replOverlayFlag} up -d --force-recreate 2>&1`,
|
|
585
575
|
{ timeout: 600_000 },
|
|
586
576
|
);
|
|
587
577
|
s.stop('All services running');
|
|
588
578
|
|
|
589
|
-
// 11. Re-setup backup cron if configured.
|
|
590
|
-
//
|
|
591
|
-
//
|
|
592
|
-
// Without merging them in, setupComposeBackupCron would write a
|
|
593
|
-
// /etc/vibecarbon/backup.env with empty AWS_ACCESS_KEY_ID /
|
|
594
|
-
// AWS_SECRET_ACCESS_KEY and every cron-triggered backup would 403
|
|
595
|
-
// against S3.
|
|
579
|
+
// 11. Re-setup backup cron if configured. wal-g reads its S3 config from
|
|
580
|
+
// the db container's env (rendered into .env at deploy), so the cron
|
|
581
|
+
// just runs compose-backup.sh on schedule — no separate S3 plumbing.
|
|
596
582
|
if (envConfig.backup) {
|
|
597
|
-
|
|
598
|
-
envConfig.s3 && creds.s3
|
|
599
|
-
? {
|
|
600
|
-
bucket: envConfig.s3.bucket,
|
|
601
|
-
accessKey: creds.s3.accessKey,
|
|
602
|
-
secretKey: creds.s3.secretKey,
|
|
603
|
-
endpoint: envConfig.s3.endpoint,
|
|
604
|
-
region: envConfig.s3.region,
|
|
605
|
-
}
|
|
606
|
-
: null;
|
|
607
|
-
setupComposeBackupCron(newIp, sshKeyPath, projectName, backupS3Config, envConfig.backup);
|
|
583
|
+
setupComposeBackupCron(newIp, sshKeyPath, projectName, envConfig.backup);
|
|
608
584
|
p.log.info('Backup cron restored on new server');
|
|
609
585
|
}
|
|
610
586
|
|