smoonb 1.0.7 → 1.0.9
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/CHANGELOG.md +43 -0
- package/README.md +27 -7
- package/README.pt-BR.md +27 -7
- package/package.json +4 -3
- package/src/commands/backup/steps/05-auth-settings.js +6 -2
- package/src/commands/backup/steps/06-realtime-settings.js +19 -33
- package/src/commands/backup/steps/07-storage.js +7 -12
- package/src/commands/backup/steps/09-edge-functions.js +76 -202
- package/src/commands/restore/auth-fields.json +237 -0
- package/src/commands/restore/auth.js +65 -0
- package/src/commands/restore/dump.js +284 -0
- package/src/commands/restore/index.js +8 -127
- package/src/commands/restore/management.js +96 -0
- package/src/commands/restore/pg.js +83 -0
- package/src/commands/restore/runner.js +140 -0
- package/src/commands/restore/steps/03-database.js +18 -84
- package/src/commands/restore/steps/04-edge-functions.js +95 -122
- package/src/commands/restore/steps/05-auth-settings.js +15 -54
- package/src/commands/restore/steps/06-storage.js +203 -584
- package/src/commands/restore/steps/07-database-settings.js +87 -69
- package/src/commands/restore/steps/08-realtime-settings.js +61 -53
- package/src/i18n/locales/en.json +803 -797
- package/src/i18n/locales/pt-BR.json +802 -796
- package/src/utils/auth-secrets.js +23 -0
- package/src/utils/supabaseCli.js +32 -0
|
@@ -1,69 +1,87 @@
|
|
|
1
|
-
const
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { runSql, ident, literal } = require('../pg');
|
|
4
|
+
module.exports = async ({ backupPath, targetProject, postgresMajor = 17 }) => {
|
|
5
|
+
const file = fs
|
|
6
|
+
.readdirSync(backupPath)
|
|
7
|
+
.find((f) => f.startsWith('database-settings-') && f.endsWith('.json'));
|
|
8
|
+
if (!file) throw new Error('Database settings missing from backup');
|
|
9
|
+
const data = JSON.parse(fs.readFileSync(path.join(backupPath, file), 'utf8'));
|
|
10
|
+
const settings = data.database_settings || data;
|
|
11
|
+
const query = async (sql) =>
|
|
12
|
+
JSON.parse(
|
|
13
|
+
await runSql(targetProject.targetDatabaseUrl, sql, { postgresMajor }),
|
|
14
|
+
);
|
|
15
|
+
const actual = await query(
|
|
16
|
+
"SELECT json_build_object('settings',(SELECT json_object_agg(name,setting) FROM pg_settings),'roles',(SELECT json_object_agg(rolname,rolconfig) FROM pg_roles),'extensions',(SELECT json_object_agg(extname,extversion) FROM pg_extension));",
|
|
17
|
+
);
|
|
18
|
+
const commands = [],
|
|
19
|
+
pending = [];
|
|
20
|
+
for (const ext of settings.extensions || []) {
|
|
21
|
+
if (!actual.extensions[ext.name])
|
|
22
|
+
commands.push(
|
|
23
|
+
`CREATE EXTENSION IF NOT EXISTS ${ident(ext.name)} WITH SCHEMA ${ident(ext.schema)};`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
for (const item of settings.postgres_settings || []) {
|
|
27
|
+
if (actual.settings[item.name] === item.setting) continue;
|
|
28
|
+
if (!['user', 'superuser'].includes(item.context)) {
|
|
29
|
+
pending.push(item.name);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const value =
|
|
33
|
+
item.unit && Number(item.setting) >= 0
|
|
34
|
+
? `${Number(item.setting) * (item.unit === '8kB' ? 8 : 1)}${item.unit === '8kB' ? 'kB' : item.unit}`
|
|
35
|
+
: item.setting;
|
|
36
|
+
commands.push(
|
|
37
|
+
`ALTER DATABASE ${ident(new URL(targetProject.targetDatabaseUrl).pathname.slice(1) || 'postgres')} SET ${ident(item.name)} TO ${literal(value)};`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
for (const role of [
|
|
41
|
+
...(settings.role_configurations || []),
|
|
42
|
+
...(settings.pgaudit_configurations || []),
|
|
43
|
+
]) {
|
|
44
|
+
for (const config of role.config || []) {
|
|
45
|
+
if ((actual.roles[role.role] || []).includes(config)) continue;
|
|
46
|
+
const eq = config.indexOf('=');
|
|
47
|
+
commands.push(
|
|
48
|
+
`ALTER ROLE ${ident(role.role)} SET ${ident(config.slice(0, eq))} TO ${literal(config.slice(eq + 1))};`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (pending.length)
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Database platform settings require compatible destination configuration: ${pending.join(', ')}`,
|
|
55
|
+
);
|
|
56
|
+
if (commands.length)
|
|
57
|
+
await runSql(
|
|
58
|
+
targetProject.targetDatabaseUrl,
|
|
59
|
+
'BEGIN;\n' + commands.join('\n') + '\nCOMMIT;',
|
|
60
|
+
{ postgresMajor },
|
|
61
|
+
);
|
|
62
|
+
const after = await query(
|
|
63
|
+
"SELECT json_build_object('settings',(SELECT json_object_agg(name,setting) FROM pg_settings),'roles',(SELECT json_object_agg(rolname,rolconfig) FROM pg_roles),'extensions',(SELECT json_object_agg(extname,extversion) FROM pg_extension));",
|
|
64
|
+
);
|
|
65
|
+
const differences = (settings.postgres_settings || [])
|
|
66
|
+
.filter((s) => after.settings[s.name] !== s.setting)
|
|
67
|
+
.map((s) => s.name);
|
|
68
|
+
for (const role of settings.role_configurations || [])
|
|
69
|
+
if (
|
|
70
|
+
(role.config || []).some(
|
|
71
|
+
(c) => !(after.roles[role.role] || []).includes(c),
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
differences.push(`role:${role.role}`);
|
|
75
|
+
for (const ext of settings.extensions || [])
|
|
76
|
+
if (!after.extensions[ext.name]) differences.push(`extension:${ext.name}`);
|
|
77
|
+
if (differences.length)
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Database settings verification failed: ${differences.join(', ')}`,
|
|
80
|
+
);
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
verified: true,
|
|
84
|
+
extensions_count: (settings.extensions || []).length,
|
|
85
|
+
settings_count: (settings.postgres_settings || []).length,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -1,53 +1,61 @@
|
|
|
1
|
-
const
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { applyConfig } = require('../management');
|
|
4
|
+
const allowed = [
|
|
5
|
+
'private_only',
|
|
6
|
+
'connection_pool',
|
|
7
|
+
'postgres_changes_pool',
|
|
8
|
+
'max_concurrent_users',
|
|
9
|
+
'max_events_per_second',
|
|
10
|
+
'max_bytes_per_second',
|
|
11
|
+
'max_channels_per_client',
|
|
12
|
+
'max_joins_per_second',
|
|
13
|
+
'max_presence_events_per_second',
|
|
14
|
+
'max_payload_size_in_kb',
|
|
15
|
+
'suspend',
|
|
16
|
+
'presence_enabled',
|
|
17
|
+
];
|
|
18
|
+
module.exports = async ({ backupPath, targetProject }) => {
|
|
19
|
+
const backup = JSON.parse(
|
|
20
|
+
fs.readFileSync(path.join(backupPath, 'realtime-settings.json'), 'utf8'),
|
|
21
|
+
);
|
|
22
|
+
let desired = backup.settings;
|
|
23
|
+
if (!desired) {
|
|
24
|
+
const legacy = backup.realtime_settings?.settings;
|
|
25
|
+
if (!legacy) throw new Error('Realtime configuration missing from backup');
|
|
26
|
+
const mapping = {
|
|
27
|
+
enable_realtime_service: 'suspend',
|
|
28
|
+
allow_public_access: 'private_only',
|
|
29
|
+
database_connection_pool_size: 'connection_pool',
|
|
30
|
+
max_concurrent_clients: 'max_concurrent_users',
|
|
31
|
+
max_events_per_second: 'max_events_per_second',
|
|
32
|
+
max_presence_events_per_second: 'max_presence_events_per_second',
|
|
33
|
+
max_payload_size_kb: 'max_payload_size_in_kb',
|
|
34
|
+
};
|
|
35
|
+
desired = Object.fromEntries(
|
|
36
|
+
Object.entries(legacy).map(([key, item]) => {
|
|
37
|
+
if (!mapping[key])
|
|
38
|
+
throw new Error(`Unsupported Realtime configuration: ${key}`);
|
|
39
|
+
return [
|
|
40
|
+
mapping[key],
|
|
41
|
+
['enable_realtime_service', 'allow_public_access'].includes(key)
|
|
42
|
+
? !item.value
|
|
43
|
+
: item.value,
|
|
44
|
+
];
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
console.log(
|
|
48
|
+
`Realtime: restoring the recorded snapshot (${backup.realtime_settings.captured_at || 'date unavailable'}).`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const result = await applyConfig(
|
|
52
|
+
targetProject,
|
|
53
|
+
'config/realtime',
|
|
54
|
+
desired,
|
|
55
|
+
allowed,
|
|
56
|
+
);
|
|
57
|
+
return {
|
|
58
|
+
...result,
|
|
59
|
+
captured_at: backup.captured_at || backup.realtime_settings?.captured_at,
|
|
60
|
+
};
|
|
61
|
+
};
|