smoonb 0.0.27 → 0.0.28
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/package.json +1 -1
- package/src/commands/backup.js +194 -7
package/package.json
CHANGED
package/src/commands/backup.js
CHANGED
|
@@ -108,32 +108,37 @@ async function performFullBackup(config, options) {
|
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
// 1. Backup Database via pg_dumpall Docker (idêntico ao Dashboard)
|
|
111
|
-
console.log(chalk.blue('\n📊 1/
|
|
111
|
+
console.log(chalk.blue('\n📊 1/7 - Backup da Database PostgreSQL via pg_dumpall Docker...'));
|
|
112
112
|
const databaseResult = await backupDatabase(config.supabase.projectId, backupDir);
|
|
113
113
|
manifest.components.database = databaseResult;
|
|
114
114
|
|
|
115
115
|
// 2. Backup Edge Functions via Docker
|
|
116
|
-
console.log(chalk.blue('\n⚡ 2/
|
|
116
|
+
console.log(chalk.blue('\n⚡ 2/7 - Backup das Edge Functions via Docker...'));
|
|
117
117
|
const functionsResult = await backupEdgeFunctionsWithDocker(config.supabase.projectId, config.supabase.accessToken, backupDir);
|
|
118
118
|
manifest.components.edge_functions = functionsResult;
|
|
119
119
|
|
|
120
120
|
// 3. Backup Auth Settings via API
|
|
121
|
-
console.log(chalk.blue('\n🔐 3/
|
|
121
|
+
console.log(chalk.blue('\n🔐 3/7 - Backup das Auth Settings via API...'));
|
|
122
122
|
const authResult = await backupAuthSettings(config.supabase.projectId, config.supabase.accessToken, backupDir);
|
|
123
123
|
manifest.components.auth_settings = authResult;
|
|
124
124
|
|
|
125
125
|
// 4. Backup Storage via API
|
|
126
|
-
console.log(chalk.blue('\n📦 4/
|
|
126
|
+
console.log(chalk.blue('\n📦 4/7 - Backup do Storage via API...'));
|
|
127
127
|
const storageResult = await backupStorage(config.supabase.projectId, config.supabase.accessToken, backupDir);
|
|
128
128
|
manifest.components.storage = storageResult;
|
|
129
129
|
|
|
130
130
|
// 5. Backup Custom Roles via SQL
|
|
131
|
-
console.log(chalk.blue('\n👥 5/
|
|
131
|
+
console.log(chalk.blue('\n👥 5/7 - Backup dos Custom Roles via SQL...'));
|
|
132
132
|
const rolesResult = await backupCustomRoles(config.supabase.databaseUrl, backupDir);
|
|
133
133
|
manifest.components.custom_roles = rolesResult;
|
|
134
134
|
|
|
135
|
-
// 6. Backup
|
|
136
|
-
console.log(chalk.blue('\n
|
|
135
|
+
// 6. Backup das Database Extensions and Settings via SQL
|
|
136
|
+
console.log(chalk.blue('\n🔧 6/7 - Backup das Database Extensions and Settings via SQL...'));
|
|
137
|
+
const databaseSettingsResult = await backupDatabaseSettings(config.supabase.projectId, backupDir);
|
|
138
|
+
manifest.components.database_settings = databaseSettingsResult;
|
|
139
|
+
|
|
140
|
+
// 7. Backup Realtime Settings via Captura Interativa
|
|
141
|
+
console.log(chalk.blue('\n🔄 7/7 - Backup das Realtime Settings via Captura Interativa...'));
|
|
137
142
|
const realtimeResult = await backupRealtimeSettings(config.supabase.projectId, backupDir, options.skipRealtime);
|
|
138
143
|
manifest.components.realtime = realtimeResult;
|
|
139
144
|
|
|
@@ -143,6 +148,7 @@ async function performFullBackup(config, options) {
|
|
|
143
148
|
console.log(chalk.green('\n🎉 BACKUP COMPLETO FINALIZADO VIA DOCKER!'));
|
|
144
149
|
console.log(chalk.blue(`📁 Localização: ${backupDir}`));
|
|
145
150
|
console.log(chalk.green(`📊 Database: ${databaseResult.fileName} (${databaseResult.size} KB) - Idêntico ao Dashboard`));
|
|
151
|
+
console.log(chalk.green(`🔧 Database Settings: ${databaseSettingsResult.fileName} (${databaseSettingsResult.size} KB) - Extensions e Configurações`));
|
|
146
152
|
console.log(chalk.green(`⚡ Edge Functions: ${functionsResult.success_count || 0}/${functionsResult.functions_count || 0} functions baixadas via Docker`));
|
|
147
153
|
console.log(chalk.green(`🔐 Auth Settings: ${authResult.success ? 'Exportadas via API' : 'Falharam'}`));
|
|
148
154
|
console.log(chalk.green(`📦 Storage: ${storageResult.buckets?.length || 0} buckets verificados via API`));
|
|
@@ -533,6 +539,187 @@ async function backupCustomRoles(databaseUrl, backupDir) {
|
|
|
533
539
|
}
|
|
534
540
|
}
|
|
535
541
|
|
|
542
|
+
// Backup das Database Extensions and Settings via SQL
|
|
543
|
+
async function backupDatabaseSettings(projectId, backupDir) {
|
|
544
|
+
try {
|
|
545
|
+
console.log(chalk.gray(' - Capturando Database Extensions and Settings...'));
|
|
546
|
+
|
|
547
|
+
const { execSync } = require('child_process');
|
|
548
|
+
const config = await readConfig();
|
|
549
|
+
|
|
550
|
+
// Extrair credenciais da databaseUrl
|
|
551
|
+
const dbUrl = config.supabase.databaseUrl;
|
|
552
|
+
const urlMatch = dbUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
|
|
553
|
+
|
|
554
|
+
if (!urlMatch) {
|
|
555
|
+
throw new Error('Database URL inválida');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const [, username, password, host, port, database] = urlMatch;
|
|
559
|
+
|
|
560
|
+
// Gerar nome do arquivo
|
|
561
|
+
const now = new Date();
|
|
562
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
563
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
564
|
+
const year = now.getFullYear();
|
|
565
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
566
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
567
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
568
|
+
|
|
569
|
+
const fileName = `database-settings-${day}-${month}-${year}@${hours}-${minutes}-${seconds}.json`;
|
|
570
|
+
|
|
571
|
+
// Usar caminho absoluto igual às outras funções
|
|
572
|
+
const backupDirAbs = path.resolve(backupDir);
|
|
573
|
+
|
|
574
|
+
// Script SQL para capturar todas as configurações
|
|
575
|
+
const sqlScript = `
|
|
576
|
+
-- Database Extensions and Settings Backup
|
|
577
|
+
-- Generated at: ${new Date().toISOString()}
|
|
578
|
+
|
|
579
|
+
-- 1. Capturar extensões instaladas
|
|
580
|
+
SELECT json_agg(
|
|
581
|
+
json_build_object(
|
|
582
|
+
'name', extname,
|
|
583
|
+
'version', extversion,
|
|
584
|
+
'schema', extnamespace::regnamespace
|
|
585
|
+
)
|
|
586
|
+
) as extensions
|
|
587
|
+
FROM pg_extension
|
|
588
|
+
ORDER BY extname;
|
|
589
|
+
|
|
590
|
+
-- 2. Capturar configurações PostgreSQL importantes
|
|
591
|
+
SELECT json_agg(
|
|
592
|
+
json_build_object(
|
|
593
|
+
'name', name,
|
|
594
|
+
'setting', setting,
|
|
595
|
+
'unit', unit,
|
|
596
|
+
'context', context,
|
|
597
|
+
'description', short_desc
|
|
598
|
+
)
|
|
599
|
+
) as postgres_settings
|
|
600
|
+
FROM pg_settings
|
|
601
|
+
WHERE name IN (
|
|
602
|
+
'statement_timeout',
|
|
603
|
+
'idle_in_transaction_session_timeout',
|
|
604
|
+
'lock_timeout',
|
|
605
|
+
'shared_buffers',
|
|
606
|
+
'work_mem',
|
|
607
|
+
'maintenance_work_mem',
|
|
608
|
+
'effective_cache_size',
|
|
609
|
+
'max_connections',
|
|
610
|
+
'log_statement',
|
|
611
|
+
'log_min_duration_statement',
|
|
612
|
+
'timezone',
|
|
613
|
+
'log_timezone',
|
|
614
|
+
'default_transaction_isolation',
|
|
615
|
+
'default_transaction_read_only',
|
|
616
|
+
'checkpoint_completion_target',
|
|
617
|
+
'wal_buffers',
|
|
618
|
+
'max_wal_size',
|
|
619
|
+
'min_wal_size'
|
|
620
|
+
)
|
|
621
|
+
ORDER BY name;
|
|
622
|
+
|
|
623
|
+
-- 3. Capturar configurações específicas dos roles Supabase
|
|
624
|
+
SELECT json_agg(
|
|
625
|
+
json_build_object(
|
|
626
|
+
'role', rolname,
|
|
627
|
+
'config', rolconfig
|
|
628
|
+
)
|
|
629
|
+
) as role_configurations
|
|
630
|
+
FROM pg_roles
|
|
631
|
+
WHERE rolname IN ('anon', 'authenticated', 'authenticator', 'postgres', 'service_role')
|
|
632
|
+
AND rolconfig IS NOT NULL
|
|
633
|
+
ORDER BY rolname;
|
|
634
|
+
|
|
635
|
+
-- 4. Capturar configurações de PGAudit (se existir)
|
|
636
|
+
SELECT json_agg(
|
|
637
|
+
json_build_object(
|
|
638
|
+
'role', rolname,
|
|
639
|
+
'config', rolconfig
|
|
640
|
+
)
|
|
641
|
+
) as pgaudit_configurations
|
|
642
|
+
FROM pg_roles
|
|
643
|
+
WHERE rolconfig IS NOT NULL
|
|
644
|
+
AND EXISTS (
|
|
645
|
+
SELECT 1 FROM unnest(rolconfig) AS config
|
|
646
|
+
WHERE config LIKE '%pgaudit%'
|
|
647
|
+
)
|
|
648
|
+
ORDER BY rolname;
|
|
649
|
+
`;
|
|
650
|
+
|
|
651
|
+
// Salvar script SQL temporário
|
|
652
|
+
const sqlFile = path.join(backupDir, 'temp_settings.sql');
|
|
653
|
+
await fs.writeFile(sqlFile, sqlScript);
|
|
654
|
+
|
|
655
|
+
// Executar via Docker
|
|
656
|
+
const dockerCmd = [
|
|
657
|
+
'docker run --rm --network host',
|
|
658
|
+
`-v "${backupDirAbs}:/host"`,
|
|
659
|
+
`-e PGPASSWORD="${password}"`,
|
|
660
|
+
'postgres:17 psql',
|
|
661
|
+
`-h ${host}`,
|
|
662
|
+
`-p ${port}`,
|
|
663
|
+
`-U ${username}`,
|
|
664
|
+
`-d ${database}`,
|
|
665
|
+
'-f /host/temp_settings.sql',
|
|
666
|
+
'-t', // Tuples only
|
|
667
|
+
'-A', // Unaligned output
|
|
668
|
+
'-F', // Field separator
|
|
669
|
+
'|'
|
|
670
|
+
].join(' ');
|
|
671
|
+
|
|
672
|
+
console.log(chalk.gray(' - Executando queries de configurações via Docker...'));
|
|
673
|
+
const output = execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
674
|
+
|
|
675
|
+
// Processar output e criar JSON estruturado
|
|
676
|
+
const lines = output.trim().split('\n').filter(line => line.trim());
|
|
677
|
+
|
|
678
|
+
const result = {
|
|
679
|
+
database_settings: {
|
|
680
|
+
note: "Configurações específicas do database Supabase capturadas via SQL",
|
|
681
|
+
captured_at: new Date().toISOString(),
|
|
682
|
+
project_id: projectId,
|
|
683
|
+
extensions: lines[0] ? JSON.parse(lines[0]) : [],
|
|
684
|
+
postgres_settings: lines[1] ? JSON.parse(lines[1]) : [],
|
|
685
|
+
role_configurations: lines[2] ? JSON.parse(lines[2]) : [],
|
|
686
|
+
pgaudit_configurations: lines[3] ? JSON.parse(lines[3]) : [],
|
|
687
|
+
restore_instructions: {
|
|
688
|
+
note: "Estas configurações precisam ser aplicadas manualmente após a restauração do database",
|
|
689
|
+
steps: [
|
|
690
|
+
"1. Restaurar o database usando o arquivo .backup.gz",
|
|
691
|
+
"2. Aplicar configurações de Postgres via SQL:",
|
|
692
|
+
" ALTER DATABASE postgres SET setting_name TO 'value';",
|
|
693
|
+
"3. Aplicar configurações de roles via SQL:",
|
|
694
|
+
" ALTER ROLE role_name SET setting_name TO 'value';",
|
|
695
|
+
"4. Habilitar extensões necessárias via Dashboard ou SQL:",
|
|
696
|
+
" CREATE EXTENSION IF NOT EXISTS extension_name;",
|
|
697
|
+
"5. Verificar configurações aplicadas:",
|
|
698
|
+
" SELECT name, setting FROM pg_settings WHERE name IN (...);"
|
|
699
|
+
]
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// Salvar arquivo JSON
|
|
705
|
+
const jsonFile = path.join(backupDir, fileName);
|
|
706
|
+
await fs.writeFile(jsonFile, JSON.stringify(result, null, 2));
|
|
707
|
+
|
|
708
|
+
// Limpar arquivo temporário
|
|
709
|
+
await fs.unlink(sqlFile);
|
|
710
|
+
|
|
711
|
+
const stats = fs.statSync(jsonFile);
|
|
712
|
+
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
713
|
+
|
|
714
|
+
console.log(chalk.green(` ✅ Database Settings: ${fileName} (${sizeKB} KB)`));
|
|
715
|
+
|
|
716
|
+
return { success: true, size: sizeKB, fileName: fileName };
|
|
717
|
+
} catch (error) {
|
|
718
|
+
console.log(chalk.yellow(` ⚠️ Erro no backup das Database Settings: ${error.message}`));
|
|
719
|
+
return { success: false };
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
536
723
|
// Backup das Realtime Settings via Captura Interativa
|
|
537
724
|
async function backupRealtimeSettings(projectId, backupDir, skipInteractive = false) {
|
|
538
725
|
try {
|