hazo_env 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 CHANGED
@@ -235,6 +235,47 @@ Key sections:
235
235
 
236
236
  Secret placeholders use `${ENV_VAR_NAME}` syntax — hazo_env substitutes them from `.env.local` at runtime.
237
237
 
238
+ ## dump-restore transport
239
+
240
+ The `dump-restore` transport copies a database via SSH `pg_dump`/`pg_restore` (binary format). Use it when the source and target share the same Postgres server (e.g. prod→dev on the same VPS) and you want a fast binary copy without going through the PostgREST row-copy loop.
241
+
242
+ ### Config: `[backup.<env>]`
243
+
244
+ Add a section to `config/hazo_env_config.ini`:
245
+
246
+ ```ini
247
+ [backup.dev]
248
+ ssh_env = prod ; reuse [transport.ssh.prod] SSH connection
249
+ target_db = myapp_dev ; DB to drop/recreate
250
+ owner = adminuser ; createdb -O <owner>
251
+ dump_source = /var/backups/myapp-*.pgdump ; glob (newest used) or "fresh"
252
+ fresh_dump_db = myapp ; source DB for pg_dump when dump_source=fresh
253
+ pre_restore_cmd = sudo systemctl stop myapp-postgrest-dev
254
+ post_restore_cmd= sudo systemctl start myapp-postgrest-dev
255
+ ```
256
+
257
+ > **No PII scrubbing on this path.** The binary `pg_restore` copies raw rows; hazo_env's `scrubHook` only runs inside the PostgREST row-copy loop. If you need scrubbed data in dev, use the default `api` transport instead.
258
+
259
+ ### CLI
260
+
261
+ ```bash
262
+ npx hazo-env migrate --from prod --to dev --transport dump-restore
263
+ ```
264
+
265
+ ### Programmatic API
266
+
267
+ ```ts
268
+ import { resolveBackupConfig, restoreDbViaDump } from 'hazo_env';
269
+
270
+ const backup = resolveBackupConfig('dev'); // reads [backup.dev] from INI
271
+ if (backup) {
272
+ const result = await restoreDbViaDump(backup, {
273
+ onProgress: (msg) => console.log(msg),
274
+ });
275
+ // result.mode === 'dump-restore'
276
+ }
277
+ ```
278
+
238
279
  ## License
239
280
 
240
281
  MIT
package/dist/cli.js CHANGED
@@ -81,6 +81,8 @@ async function runMigrate() {
81
81
  const tablesIdx = args.findIndex((a) => a === '--tables');
82
82
  const tablesArg = tablesIdx >= 0 ? args[tablesIdx + 1] : undefined;
83
83
  const tables = tablesArg ? tablesArg.split(',').map((t) => t.trim()) : undefined;
84
+ const transportIdx = args.findIndex((a) => a === '--transport');
85
+ const transport = transportIdx >= 0 ? args[transportIdx + 1] : undefined;
84
86
  if (!from || !to) {
85
87
  console.error(pc.red('Error: migrate requires --from <env> and --to <env>'));
86
88
  process.exit(1);
@@ -96,6 +98,7 @@ async function runMigrate() {
96
98
  dryRun,
97
99
  allowProdTarget,
98
100
  confirmToken,
101
+ transport: transport,
99
102
  onProgress: (p) => {
100
103
  const pct = p.percent != null ? `${p.percent}%`.padStart(4) + ' ' : ' ';
101
104
  console.log(` ${pct}${pc.dim(p.phase.padEnd(12))} ${p.message}`);
package/dist/index.d.ts CHANGED
@@ -15,4 +15,8 @@ export { loadRuleset, syncRulesetFromIni, parseIniRules } from './mask/ruleset.j
15
15
  export type { MaskRule } from './mask/ruleset.js';
16
16
  export type { MaskTransform } from './types/index.js';
17
17
  export * from './lib/index.js';
18
+ export { resolveBackupConfig } from './resolve/backup.js';
19
+ export type { BackupConfig } from './resolve/backup.js';
20
+ export { runSsh, assertSafeSshField } from './migrate/ssh-exec.js';
21
+ export { restoreDbViaDump } from './migrate/db-dump-restore.js';
18
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEvG,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE9G,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnF,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEvG,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE9G,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnF,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC"}
package/dist/index.js CHANGED
@@ -23,3 +23,7 @@ export { registerMask } from './mask/registry.js';
23
23
  export { loadRuleset, syncRulesetFromIni, parseIniRules } from './mask/ruleset.js';
24
24
  // Lib
25
25
  export * from './lib/index.js';
26
+ // dump-restore transport
27
+ export { resolveBackupConfig } from './resolve/backup.js';
28
+ export { runSsh, assertSafeSshField } from './migrate/ssh-exec.js';
29
+ export { restoreDbViaDump } from './migrate/db-dump-restore.js';
@@ -0,0 +1,24 @@
1
+ import type { BackupConfig } from '../resolve/backup.js';
2
+ export interface DbDumpRestoreResult {
3
+ tables: number;
4
+ rows: number;
5
+ scrubbed: number;
6
+ mode: string;
7
+ }
8
+ /**
9
+ * Restore a Postgres database on a remote server via SSH pg_dump/pg_restore.
10
+ *
11
+ * Steps executed remotely:
12
+ * 1. Obtain a dump file (fresh pg_dump or locate an existing .pgdump)
13
+ * 2. Run pre_restore_cmd (if configured)
14
+ * 3. dropdb --if-exists + createdb -O <owner>
15
+ * 4. pg_restore -d <target_db>
16
+ * 5. Run post_restore_cmd (if configured)
17
+ *
18
+ * Returns a DbDumpRestoreResult with mode: 'dump-restore' and zero
19
+ * row/table counts (binary restore does not report these).
20
+ */
21
+ export declare function restoreDbViaDump(backup: BackupConfig, opts?: {
22
+ onProgress?: (msg: string) => void;
23
+ }): Promise<DbDumpRestoreResult>;
24
+ //# sourceMappingURL=db-dump-restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-dump-restore.d.ts","sourceRoot":"","sources":["../../src/migrate/db-dump-restore.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,YAAY,EACpB,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAC5C,OAAO,CAAC,mBAAmB,CAAC,CA6D9B"}
@@ -0,0 +1,72 @@
1
+ // hazo_env/src/migrate/db-dump-restore.ts — DB copy via SSH pg_dump/pg_restore
2
+ //
3
+ // Builds a single remote bash script and runs it via runSsh.
4
+ // ⚠️ No PII scrubbing on this path — pg_restore copies raw binary rows.
5
+ // Use the default api transport when scrubbed data is required in dev.
6
+ import { resolveSshConfig } from '../resolve/ssh.js';
7
+ import { runSsh } from './ssh-exec.js';
8
+ /**
9
+ * Restore a Postgres database on a remote server via SSH pg_dump/pg_restore.
10
+ *
11
+ * Steps executed remotely:
12
+ * 1. Obtain a dump file (fresh pg_dump or locate an existing .pgdump)
13
+ * 2. Run pre_restore_cmd (if configured)
14
+ * 3. dropdb --if-exists + createdb -O <owner>
15
+ * 4. pg_restore -d <target_db>
16
+ * 5. Run post_restore_cmd (if configured)
17
+ *
18
+ * Returns a DbDumpRestoreResult with mode: 'dump-restore' and zero
19
+ * row/table counts (binary restore does not report these).
20
+ */
21
+ export async function restoreDbViaDump(backup, opts) {
22
+ const ssh = resolveSshConfig(backup.ssh_env);
23
+ if (!ssh) {
24
+ throw new Error(`dump-restore: no [transport.ssh.${backup.ssh_env}] config found — ` +
25
+ `set ssh_env to a configured SSH transport in [backup.*]`);
26
+ }
27
+ // Build remote bash script lines
28
+ const lines = ['set -euo pipefail', ''];
29
+ // Step 1: Get or create dump
30
+ lines.push('# Step 1: Get or create dump');
31
+ if (backup.dump_source === 'fresh') {
32
+ lines.push('DUMP_FILE="/tmp/he-$(date +%s).pgdump"');
33
+ lines.push(`echo "Creating fresh dump of ${backup.fresh_dump_db}..."`);
34
+ lines.push(`pg_dump -Fc ${backup.fresh_dump_db} > "$DUMP_FILE"`);
35
+ }
36
+ else {
37
+ lines.push(`echo "Locating dump file matching ${backup.dump_source}..."`);
38
+ lines.push(`DUMP_FILE=$(ls -t ${backup.dump_source} 2>/dev/null | head -1)`);
39
+ lines.push(`if [ -z "$DUMP_FILE" ]; then echo "No dump file found matching ${backup.dump_source}"; exit 1; fi`);
40
+ lines.push('echo "Using dump: $DUMP_FILE"');
41
+ }
42
+ lines.push('');
43
+ // Step 2: Pre-restore command (optional)
44
+ if (backup.pre_restore_cmd) {
45
+ lines.push('# Step 2: Pre-restore command');
46
+ lines.push('echo "Running pre-restore..."');
47
+ lines.push(backup.pre_restore_cmd);
48
+ lines.push('');
49
+ }
50
+ // Step 3: Drop and recreate target DB
51
+ lines.push('# Step 3: Drop and recreate target DB');
52
+ lines.push(`echo "Dropping ${backup.target_db}..."`);
53
+ lines.push(`dropdb --if-exists ${backup.target_db}`);
54
+ lines.push(`echo "Creating ${backup.target_db}..."`);
55
+ lines.push(`createdb -O ${backup.owner} ${backup.target_db}`);
56
+ lines.push('');
57
+ // Step 4: Restore
58
+ lines.push('# Step 4: Restore');
59
+ lines.push(`echo "Restoring to ${backup.target_db}..."`);
60
+ lines.push(`pg_restore -d ${backup.target_db} "$DUMP_FILE"`);
61
+ lines.push('echo "Restore complete."');
62
+ // Step 5: Post-restore command (optional)
63
+ if (backup.post_restore_cmd) {
64
+ lines.push('');
65
+ lines.push('# Step 5: Post-restore command');
66
+ lines.push('echo "Running post-restore..."');
67
+ lines.push(backup.post_restore_cmd);
68
+ }
69
+ const script = lines.join('\n');
70
+ await runSsh(ssh, script, { onProgress: opts?.onProgress });
71
+ return { tables: 0, rows: 0, scrubbed: 0, mode: 'dump-restore' };
72
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrate/files.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AA6GD,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkDhF"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrate/files.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AAoFD,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkDhF"}
@@ -9,6 +9,7 @@ import path from 'node:path';
9
9
  import { spawn } from 'node:child_process';
10
10
  import { resolveFilesRoot } from '../resolve/files.js';
11
11
  import { resolveSshConfig } from '../resolve/ssh.js';
12
+ import { assertSafeSshField, setupKnownHosts } from './ssh-exec.js';
12
13
  function resolveEnvFilesRoot(env) {
13
14
  return resolveFilesRoot(env);
14
15
  }
@@ -31,17 +32,6 @@ async function scrubPdfFile(filePath) {
31
32
  // Non-fatal — PDF scrubbing failure is a warning, not an abort
32
33
  }
33
34
  }
34
- /**
35
- * Reject ssh config values that could smuggle extra ssh options through rsync's
36
- * `-e` command string. rsync re-splits `-e` on whitespace, so a value starting
37
- * with `-` (e.g. `-oProxyCommand=...`) or containing whitespace would be parsed
38
- * as additional ssh flags rather than data. Fail loud instead of running.
39
- */
40
- function assertSafeSshField(name, value) {
41
- if (/^-/.test(value) || /\s/.test(value)) {
42
- throw new Error(`Unsafe transport.ssh.${name} value: must not start with '-' or contain whitespace`);
43
- }
44
- }
45
35
  async function copyFilesRsync(opts, ssh) {
46
36
  const tgtRoot = resolveEnvFilesRoot(opts.toEnv);
47
37
  fs.mkdirSync(tgtRoot, { recursive: true });
@@ -49,19 +39,7 @@ async function copyFilesRsync(opts, ssh) {
49
39
  assertSafeSshField('host', ssh.host);
50
40
  assertSafeSshField('key', ssh.key);
51
41
  assertSafeSshField('path', ssh.path);
52
- // Persistent known_hosts so host-key trust survives across runs (no TOFU reset).
53
- const knownHosts = path.resolve(process.cwd(), 'config', 'hazo_env_known_hosts');
54
- fs.mkdirSync(path.dirname(knownHosts), { recursive: true });
55
- if (ssh.hostKey) {
56
- // Pin the operator-provided host key; strict checking, no first-use prompt.
57
- const line = `${ssh.host} ${ssh.hostKey}\n`;
58
- const existing = fs.existsSync(knownHosts) ? fs.readFileSync(knownHosts, 'utf8') : '';
59
- if (!existing.includes(line.trim()))
60
- fs.appendFileSync(knownHosts, line);
61
- }
62
- // Pinned key → strict verify; otherwise accept-new (trust on first use, then
63
- // persist) — never disable host-key checking outright.
64
- const strict = ssh.hostKey ? 'yes' : 'accept-new';
42
+ const { knownHosts, strict } = setupKnownHosts(ssh);
65
43
  const remote = `${ssh.user}@${ssh.host}:${ssh.path}/`;
66
44
  const sshCmd = `ssh -i ${ssh.key} -o StrictHostKeyChecking=${strict} -o UserKnownHostsFile=${knownHosts}`;
67
45
  const args = ['-az', '--partial', '--info=progress2', '-e', sshCmd, remote, tgtRoot + '/'];
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAkC,MAAM,mBAAmB,CAAC;AA0D3G,wBAAsB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwLlF"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAkC,MAAM,mBAAmB,CAAC;AA0D3G,wBAAsB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAyMlF"}
@@ -172,22 +172,40 @@ export async function runMigration(req) {
172
172
  // Step 5: DB copy
173
173
  if (includeDb) {
174
174
  emitProgress(req, { phase: 'db', message: 'Copying database tables', percent: 20 });
175
- const scrubHook = await buildScrubHook(req.to, scrubMode, tgtAdapter);
176
- const result = await copyDb(srcAdapter, tgtAdapter, {
177
- type: fromDbConfig.type,
178
- tables: req.tables ?? migrateConfig.tables,
179
- preserve: migrateConfig.preserve,
180
- pkOverrides: migrateConfig.pkOverrides,
181
- scrubHook,
182
- onProgress: (msg) => emitProgress(req, { phase: 'db', message: msg }),
183
- onTableProgress: (idx, total, name) => emitProgress(req, {
184
- phase: 'db',
185
- message: `table ${idx}/${total}: ${name}`,
186
- percent: 20 + Math.round((idx / total) * 50),
187
- }),
188
- });
189
- dbResult = result;
190
- emitProgress(req, { phase: 'db', message: `DB copy complete: ${result.tables} tables, ${result.rows} rows`, percent: 70 });
175
+ if (req.transport === 'dump-restore') {
176
+ const { resolveBackupConfig } = await import('../resolve/backup.js');
177
+ const { restoreDbViaDump } = await import('./db-dump-restore.js');
178
+ const backup = resolveBackupConfig(req.to);
179
+ if (!backup) {
180
+ throw new HazoError({
181
+ code: 'HAZO_ENV_INVALID_REQUEST',
182
+ pkg: 'hazo_env',
183
+ message: `[backup.${req.to}] config section is required for dump-restore transport`,
184
+ });
185
+ }
186
+ dbResult = await restoreDbViaDump(backup, {
187
+ onProgress: (msg) => emitProgress(req, { phase: 'db', message: msg }),
188
+ });
189
+ emitProgress(req, { phase: 'db', message: 'DB restore via dump complete', percent: 70 });
190
+ }
191
+ else {
192
+ const scrubHook = await buildScrubHook(req.to, scrubMode, tgtAdapter);
193
+ const result = await copyDb(srcAdapter, tgtAdapter, {
194
+ type: fromDbConfig.type,
195
+ tables: req.tables ?? migrateConfig.tables,
196
+ preserve: migrateConfig.preserve,
197
+ pkOverrides: migrateConfig.pkOverrides,
198
+ scrubHook,
199
+ onProgress: (msg) => emitProgress(req, { phase: 'db', message: msg }),
200
+ onTableProgress: (idx, total, name) => emitProgress(req, {
201
+ phase: 'db',
202
+ message: `table ${idx}/${total}: ${name}`,
203
+ percent: 20 + Math.round((idx / total) * 50),
204
+ }),
205
+ });
206
+ dbResult = result;
207
+ emitProgress(req, { phase: 'db', message: `DB copy complete: ${result.tables} tables, ${result.rows} rows`, percent: 70 });
208
+ }
191
209
  }
192
210
  // Step 6: File copy
193
211
  if (includeFiles) {
@@ -0,0 +1,30 @@
1
+ import type { SshConfig } from '../resolve/ssh.js';
2
+ /**
3
+ * Reject SSH config values that could smuggle extra SSH options through
4
+ * shell argument expansion. Values starting with '-' (e.g. -oProxyCommand=...)
5
+ * or containing whitespace would be re-split by SSH and treated as flags.
6
+ */
7
+ export declare function assertSafeSshField(name: string, value: string): void;
8
+ /**
9
+ * Set up a persistent known_hosts file for SSH host-key pinning.
10
+ * When ssh.hostKey is set, the key is pinned and strict checking is enabled
11
+ * (no TOFU). Otherwise, accept-new is used — trust on first use, then persist.
12
+ *
13
+ * Returns { knownHosts: absolute path, strict: 'yes' | 'accept-new' }.
14
+ */
15
+ export declare function setupKnownHosts(ssh: SshConfig): {
16
+ knownHosts: string;
17
+ strict: string;
18
+ };
19
+ /**
20
+ * Run a bash script on a remote host via SSH, piping remoteScript to stdin.
21
+ *
22
+ * Spawns: ssh -i <key> -o StrictHostKeyChecking=<strict>
23
+ * -o UserKnownHostsFile=<knownHosts> <user>@<host> bash -s
24
+ *
25
+ * Streams stdout/stderr lines to opts.onProgress. Rejects on non-zero exit.
26
+ */
27
+ export declare function runSsh(ssh: SshConfig, remoteScript: string, opts?: {
28
+ onProgress?: (msg: string) => void;
29
+ }): Promise<void>;
30
+ //# sourceMappingURL=ssh-exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-exec.d.ts","sourceRoot":"","sources":["../../src/migrate/ssh-exec.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAYtF;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,GAAG,EAAE,SAAS,EACd,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAsCf"}
@@ -0,0 +1,82 @@
1
+ // hazo_env/src/migrate/ssh-exec.ts — Shared SSH execution helpers
2
+ //
3
+ // Extracted from files.ts so that db-dump-restore.ts can reuse the same
4
+ // known_hosts pinning and safe-field validation without duplication.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { spawn } from 'node:child_process';
8
+ /**
9
+ * Reject SSH config values that could smuggle extra SSH options through
10
+ * shell argument expansion. Values starting with '-' (e.g. -oProxyCommand=...)
11
+ * or containing whitespace would be re-split by SSH and treated as flags.
12
+ */
13
+ export function assertSafeSshField(name, value) {
14
+ if (/^-/.test(value) || /\s/.test(value)) {
15
+ throw new Error(`Unsafe transport.ssh.${name} value: must not start with '-' or contain whitespace`);
16
+ }
17
+ }
18
+ /**
19
+ * Set up a persistent known_hosts file for SSH host-key pinning.
20
+ * When ssh.hostKey is set, the key is pinned and strict checking is enabled
21
+ * (no TOFU). Otherwise, accept-new is used — trust on first use, then persist.
22
+ *
23
+ * Returns { knownHosts: absolute path, strict: 'yes' | 'accept-new' }.
24
+ */
25
+ export function setupKnownHosts(ssh) {
26
+ const knownHosts = path.resolve(process.cwd(), 'config', 'hazo_env_known_hosts');
27
+ fs.mkdirSync(path.dirname(knownHosts), { recursive: true });
28
+ if (ssh.hostKey) {
29
+ // Pin the operator-provided host key; strict checking, no first-use prompt.
30
+ const line = `${ssh.host} ${ssh.hostKey}\n`;
31
+ const existing = fs.existsSync(knownHosts) ? fs.readFileSync(knownHosts, 'utf8') : '';
32
+ if (!existing.includes(line.trim()))
33
+ fs.appendFileSync(knownHosts, line);
34
+ }
35
+ // Pinned key → strict verify; otherwise accept-new — never disable host-key checking.
36
+ const strict = ssh.hostKey ? 'yes' : 'accept-new';
37
+ return { knownHosts, strict };
38
+ }
39
+ /**
40
+ * Run a bash script on a remote host via SSH, piping remoteScript to stdin.
41
+ *
42
+ * Spawns: ssh -i <key> -o StrictHostKeyChecking=<strict>
43
+ * -o UserKnownHostsFile=<knownHosts> <user>@<host> bash -s
44
+ *
45
+ * Streams stdout/stderr lines to opts.onProgress. Rejects on non-zero exit.
46
+ */
47
+ export async function runSsh(ssh, remoteScript, opts) {
48
+ const { knownHosts, strict } = setupKnownHosts(ssh);
49
+ const sshArgs = [
50
+ '-i', ssh.key,
51
+ '-o', `StrictHostKeyChecking=${strict}`,
52
+ '-o', `UserKnownHostsFile=${knownHosts}`,
53
+ `${ssh.user}@${ssh.host}`,
54
+ 'bash', '-s',
55
+ ];
56
+ return new Promise((resolve, reject) => {
57
+ const proc = spawn('ssh', sshArgs);
58
+ proc.stdin.write(remoteScript);
59
+ proc.stdin.end();
60
+ proc.stdout.on('data', (chunk) => {
61
+ for (const line of chunk.toString().split('\n')) {
62
+ if (line.trim())
63
+ opts?.onProgress?.(line);
64
+ }
65
+ });
66
+ proc.stderr.on('data', (chunk) => {
67
+ for (const line of chunk.toString().split('\n')) {
68
+ if (line.trim())
69
+ opts?.onProgress?.(line);
70
+ }
71
+ });
72
+ proc.on('error', (err) => reject(err));
73
+ proc.on('close', (code) => {
74
+ if (code !== 0) {
75
+ reject(new Error(`ssh exited with code ${code}`));
76
+ }
77
+ else {
78
+ resolve();
79
+ }
80
+ });
81
+ });
82
+ }
@@ -1,3 +1,3 @@
1
1
  import type { TransportMode, DbEnvConfig } from '../types/index.js';
2
- export declare function resolveTransport(fromConfig: DbEnvConfig, toConfig: DbEnvConfig, requested?: TransportMode): 'local' | 'api' | 'rsync';
2
+ export declare function resolveTransport(fromConfig: DbEnvConfig, toConfig: DbEnvConfig, requested?: TransportMode): 'local' | 'api' | 'rsync' | 'dump-restore';
3
3
  //# sourceMappingURL=transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/migrate/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpE,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,WAAW,EACvB,QAAQ,EAAE,WAAW,EACrB,SAAS,GAAE,aAAsB,GAChC,OAAO,GAAG,KAAK,GAAG,OAAO,CAM3B"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/migrate/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpE,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,WAAW,EACvB,QAAQ,EAAE,WAAW,EACrB,SAAS,GAAE,aAAsB,GAChC,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,cAAc,CAO5C"}
@@ -1,4 +1,6 @@
1
1
  export function resolveTransport(fromConfig, toConfig, requested = 'auto') {
2
+ if (requested === 'dump-restore')
3
+ return 'dump-restore';
2
4
  if (requested === 'ssh')
3
5
  return 'rsync';
4
6
  if (requested === 'api')
@@ -0,0 +1,23 @@
1
+ export interface BackupConfig {
2
+ /** Which [transport.ssh.*] connection to reuse for the SSH session */
3
+ ssh_env: string;
4
+ /** The Postgres database to drop/recreate on the remote */
5
+ target_db: string;
6
+ /** Owner passed to createdb -O */
7
+ owner: string;
8
+ /** Glob matching an existing dump file, or the literal string "fresh" */
9
+ dump_source: string;
10
+ /** Source DB for pg_dump — required when dump_source === 'fresh' */
11
+ fresh_dump_db?: string;
12
+ /** Shell command to run on the remote before dropping/restoring the DB */
13
+ pre_restore_cmd?: string;
14
+ /** Shell command to run on the remote after a successful restore */
15
+ post_restore_cmd?: string;
16
+ }
17
+ /**
18
+ * Resolve the [backup.<env>] section from hazo_env_config.ini.
19
+ * Returns null when the section is missing (transport not configured).
20
+ * Throws when required fields are absent or ssh_env is unsafe.
21
+ */
22
+ export declare function resolveBackupConfig(env: string): BackupConfig | null;
23
+ //# sourceMappingURL=backup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../src/resolve/backup.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAcD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA8BpE"}
@@ -0,0 +1,52 @@
1
+ // hazo_env/src/resolve/backup.ts — dump-restore backup transport config resolver
2
+ //
3
+ // Reads [backup.<env>] from config/hazo_env_config.ini and returns a BackupConfig,
4
+ // or null when the section is absent (transport not configured for that env).
5
+ import path from 'node:path';
6
+ import { HazoConfig } from 'hazo_config/server';
7
+ import { assertSafeSshField } from '../migrate/ssh-exec.js';
8
+ function tryLoadEnvConfig() {
9
+ try {
10
+ return new HazoConfig({ filePath: path.resolve(process.cwd(), 'config', 'hazo_env_config.ini') });
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ function expandEnvVars(value) {
17
+ return value.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? '');
18
+ }
19
+ /**
20
+ * Resolve the [backup.<env>] section from hazo_env_config.ini.
21
+ * Returns null when the section is missing (transport not configured).
22
+ * Throws when required fields are absent or ssh_env is unsafe.
23
+ */
24
+ export function resolveBackupConfig(env) {
25
+ const config = tryLoadEnvConfig();
26
+ if (!config)
27
+ return null;
28
+ const section = config.getSection(`backup.${env}`);
29
+ if (!section)
30
+ return null;
31
+ const ssh_env_raw = section['ssh_env'];
32
+ const target_db_raw = section['target_db'];
33
+ const owner_raw = section['owner'];
34
+ const dump_source_raw = section['dump_source'];
35
+ const fresh_dump_db_raw = section['fresh_dump_db'];
36
+ const pre_restore_cmd_raw = section['pre_restore_cmd'];
37
+ const post_restore_cmd_raw = section['post_restore_cmd'];
38
+ if (!ssh_env_raw || !target_db_raw || !owner_raw || !dump_source_raw)
39
+ return null;
40
+ const ssh_env = expandEnvVars(ssh_env_raw);
41
+ // ssh_env is used as an SSH argument — must not contain shell-hostile chars
42
+ assertSafeSshField('ssh_env', ssh_env);
43
+ return {
44
+ ssh_env,
45
+ target_db: expandEnvVars(target_db_raw),
46
+ owner: expandEnvVars(owner_raw),
47
+ dump_source: expandEnvVars(dump_source_raw),
48
+ fresh_dump_db: fresh_dump_db_raw ? expandEnvVars(fresh_dump_db_raw) : undefined,
49
+ pre_restore_cmd: pre_restore_cmd_raw ? expandEnvVars(pre_restore_cmd_raw) : undefined,
50
+ post_restore_cmd: post_restore_cmd_raw ? expandEnvVars(post_restore_cmd_raw) : undefined,
51
+ };
52
+ }
@@ -43,7 +43,7 @@ export interface ResolveConnectOptions {
43
43
  env?: HazoEnv;
44
44
  allowOtherEnv?: boolean;
45
45
  }
46
- export type TransportMode = 'auto' | 'local' | 'ssh' | 'api';
46
+ export type TransportMode = 'auto' | 'local' | 'ssh' | 'api' | 'dump-restore';
47
47
  export type ScrubMode = 'auto' | 'none';
48
48
  export type HashMode = 'none' | 'sample' | 'full';
49
49
  export interface MigrationProgress {
@@ -75,6 +75,7 @@ export interface MigrationResult {
75
75
  tables: number;
76
76
  rows: number;
77
77
  scrubbed: number;
78
+ mode?: string;
78
79
  };
79
80
  files?: {
80
81
  copied: number;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnE,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;AAE5E,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC;AAElC,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAErD,2CAA2C;AAC3C,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtD,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC7B,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAED,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC9B,CAAC;CACH;AAED,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,UAAU,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAID,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,CAAC;AAC7D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AACxC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,IAAI,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;IACrH,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAGD,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_env",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Canonical environment resolver — typed env names, per-env DB/file/secret config, doctor and CLI for hazo apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",