signalk-backup 0.4.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.
Files changed (40) hide show
  1. package/README.md +35 -25
  2. package/package.json +6 -4
  3. package/plugin/config/image-tag.d.ts +3 -0
  4. package/plugin/config/image-tag.d.ts.map +1 -0
  5. package/plugin/config/image-tag.js +9 -0
  6. package/plugin/config/image-tag.js.map +1 -0
  7. package/plugin/config/schema.d.ts +2 -0
  8. package/plugin/config/schema.d.ts.map +1 -1
  9. package/plugin/config/schema.js +16 -4
  10. package/plugin/config/schema.js.map +1 -1
  11. package/plugin/database-export/grafana.d.ts +25 -0
  12. package/plugin/database-export/grafana.d.ts.map +1 -0
  13. package/plugin/database-export/grafana.js +262 -0
  14. package/plugin/database-export/grafana.js.map +1 -0
  15. package/plugin/database-export/index.d.ts +9 -2
  16. package/plugin/database-export/index.d.ts.map +1 -1
  17. package/plugin/database-export/index.js +24 -6
  18. package/plugin/database-export/index.js.map +1 -1
  19. package/plugin/database-export/signalk-database.d.ts +21 -0
  20. package/plugin/database-export/signalk-database.d.ts.map +1 -0
  21. package/plugin/database-export/signalk-database.js +148 -0
  22. package/plugin/database-export/signalk-database.js.map +1 -0
  23. package/plugin/database-export/staging-routes.d.ts +19 -0
  24. package/plugin/database-export/staging-routes.d.ts.map +1 -0
  25. package/plugin/database-export/staging-routes.js +205 -0
  26. package/plugin/database-export/staging-routes.js.map +1 -0
  27. package/plugin/index.d.ts.map +1 -1
  28. package/plugin/index.js +147 -44
  29. package/plugin/index.js.map +1 -1
  30. package/plugin/restore-host-write.d.ts +26 -0
  31. package/plugin/restore-host-write.d.ts.map +1 -0
  32. package/plugin/restore-host-write.js +363 -0
  33. package/plugin/restore-host-write.js.map +1 -0
  34. package/public/assets/{index-DMT6pdi_.js → index-C8j20cZ-.js} +2 -2
  35. package/public/assets/{index-DMT6pdi_.js.map → index-C8j20cZ-.js.map} +1 -1
  36. package/public/assets/main-B53J3gmZ.js +11 -0
  37. package/public/assets/main-B53J3gmZ.js.map +1 -0
  38. package/public/index.html +1 -1
  39. package/public/assets/main-BZ1xX0IR.js +0 -11
  40. package/public/assets/main-BZ1xX0IR.js.map +0 -1
@@ -5,12 +5,14 @@
5
5
  * exporter pulls data via the source plugin's HTTP route on the SignalK
6
6
  * server itself — no container exec or shared filesystems involved.
7
7
  *
8
- * Currently supports QuestDB only. InfluxDB is intentionally out of
9
- * scope (see design doc).
8
+ * Supports QuestDB and Grafana. InfluxDB is intentionally out of scope
9
+ * (see design doc).
10
10
  */
11
11
  import { mkdir } from 'node:fs/promises';
12
12
  import { join } from 'node:path';
13
+ import { GrafanaExporter } from './grafana.js';
13
14
  import { QuestDBExporter } from './questdb.js';
15
+ import { SignalKDatabaseExporter } from './signalk-database.js';
14
16
  const PLUGIN_ID = 'signalk-backup';
15
17
  const STAGING_SUBDIR = 'database-exports';
16
18
  /**
@@ -26,12 +28,26 @@ const STAGING_SUBDIR = 'database-exports';
26
28
  export async function runAllExports(opts) {
27
29
  const stagingRoot = join(opts.signalkConfigRoot, 'plugin-config-data', PLUGIN_ID, STAGING_SUBDIR);
28
30
  await mkdir(stagingRoot, { recursive: true });
29
- const exporters = [
30
- new QuestDBExporter({
31
+ const enabled = opts.enabled ?? {};
32
+ const exporters = [];
33
+ if (enabled.questdb === true) {
34
+ exporters.push(new QuestDBExporter({
31
35
  signalkBaseUrl: opts.signalkBaseUrl,
32
36
  log: opts.log
33
- })
34
- ];
37
+ }));
38
+ }
39
+ if (enabled.grafana === true) {
40
+ exporters.push(new GrafanaExporter({
41
+ signalkBaseUrl: opts.signalkBaseUrl,
42
+ log: opts.log
43
+ }));
44
+ }
45
+ if (enabled.signalkDatabase === true) {
46
+ exporters.push(new SignalKDatabaseExporter({
47
+ signalkBaseUrl: opts.signalkBaseUrl,
48
+ log: opts.log
49
+ }));
50
+ }
35
51
  const results = [];
36
52
  for (const exporter of exporters) {
37
53
  if (!(await exporter.detect())) {
@@ -51,5 +67,7 @@ export async function runAllExports(opts) {
51
67
  }
52
68
  return results;
53
69
  }
70
+ export { GrafanaExporter } from './grafana.js';
54
71
  export { QuestDBExporter } from './questdb.js';
72
+ export { SignalKDatabaseExporter } from './signalk-database.js';
55
73
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/database-export/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAG9C,MAAM,SAAS,GAAG,gBAAgB,CAAA;AAClC,MAAM,cAAc,GAAG,kBAAkB,CAAA;AAWzC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAA+B;IACjE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;IACjG,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE7C,MAAM,SAAS,GAAuB;QACpC,IAAI,eAAe,CAAC;YAClB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC;KACH,CAAA;IAED,MAAM,OAAO,GAAmB,EAAE,CAAA;IAClC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,EAAE,CAAC,wBAAwB,QAAQ,CAAC,QAAQ,kBAAkB,CAAC,CAAA;YACvE,SAAQ;QACV,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;QACvD,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACf,IAAI,CAAC,GAAG,EAAE,CACR,eAAe,QAAQ,CAAC,QAAQ,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,WAAW;gBAC7D,GAAG,CAAC,CAAC,UAAU,WAAW,CAAC,CAAC,UAAU,IAAI,CAC7C,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,EAAE,CACR,eAAe,QAAQ,CAAC,QAAQ,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/F,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAGD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/database-export/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { DatabaseExporter, ExportResult } from './types.js';
2
+ export interface SignalKDatabaseExporterOptions {
3
+ signalkBaseUrl?: string;
4
+ log?: (msg: string) => void;
5
+ /** Override fetch (tests). */
6
+ fetch?: typeof fetch;
7
+ }
8
+ export declare class SignalKDatabaseExporter implements DatabaseExporter {
9
+ readonly pluginId = "signalk-database";
10
+ private readonly baseUrl;
11
+ private readonly fetchImpl;
12
+ private readonly log;
13
+ constructor(opts?: SignalKDatabaseExporterOptions);
14
+ detect(): Promise<boolean>;
15
+ exportAll(stagingDir: string): Promise<ExportResult>;
16
+ private fetchManifest;
17
+ private parseEntry;
18
+ private exportOne;
19
+ private resetStagingDir;
20
+ }
21
+ //# sourceMappingURL=signalk-database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signalk-database.d.ts","sourceRoot":"","sources":["../../src/database-export/signalk-database.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAe,MAAM,YAAY,CAAA;AAS7E,MAAM,WAAW,8BAA8B;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,8BAA8B;IAC9B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB;AAQD,qBAAa,uBAAwB,YAAW,gBAAgB;IAC9D,QAAQ,CAAC,QAAQ,sBAA6B;IAE9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuB;gBAE/B,IAAI,GAAE,8BAAmC;IAM/C,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAY1B,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAgD5C,aAAa;IAkB3B,OAAO,CAAC,UAAU;YAQJ,SAAS;YAmBT,eAAe;CAG9B"}
@@ -0,0 +1,148 @@
1
+ // Mirrors the Grafana exporter: pulls signalk-database's /api/full-export/{databases,<id>} over loopback and stages one db.sqlite per consumer-plugin into kopia's snapshot tree.
2
+ import { mkdir, rename, rm, unlink } from 'node:fs/promises';
3
+ import { createWriteStream } from 'node:fs';
4
+ import { Readable } from 'node:stream';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import { join } from 'node:path';
7
+ const SIGNALK_DATABASE_PLUGIN_ID = 'signalk-database';
8
+ const DEFAULT_SIGNALK_BASE = 'http://127.0.0.1:3000';
9
+ const FETCH_TIMEOUT_MS = 600_000;
10
+ // Same id grammar the source plugin enforces — reject early so we never
11
+ // even try a fetch the server would 400 on.
12
+ const SAFE_ID = /^[A-Za-z0-9._-]+$/;
13
+ export class SignalKDatabaseExporter {
14
+ pluginId = SIGNALK_DATABASE_PLUGIN_ID;
15
+ baseUrl;
16
+ fetchImpl;
17
+ log;
18
+ constructor(opts = {}) {
19
+ this.baseUrl = (opts.signalkBaseUrl ?? DEFAULT_SIGNALK_BASE).replace(/\/$/, '');
20
+ this.fetchImpl = opts.fetch ?? fetch;
21
+ this.log = (msg) => opts.log?.(`[signalk-database-export] ${msg}`);
22
+ }
23
+ async detect() {
24
+ // Manifest is cheap — GET returning 200 with a `databases` array even
25
+ // when there are zero consumer DBs yet (empty plugin-config-data).
26
+ try {
27
+ const manifest = await this.fetchManifest(5_000);
28
+ return Array.isArray(manifest);
29
+ }
30
+ catch (err) {
31
+ this.log(`detect failed: ${errMsg(err)}`);
32
+ return false;
33
+ }
34
+ }
35
+ async exportAll(stagingDir) {
36
+ const startedAt = Date.now();
37
+ await mkdir(stagingDir, { recursive: true });
38
+ // Reset the staging tree so DBs removed from disk (because the
39
+ // consumer plugin was uninstalled) don't linger in kopia forever.
40
+ await this.resetStagingDir(stagingDir);
41
+ await mkdir(stagingDir, { recursive: true });
42
+ const manifest = await this.fetchManifest(10_000);
43
+ const exports = [];
44
+ let totalBytes = 0;
45
+ for (const entry of manifest) {
46
+ if (!SAFE_ID.test(entry.id) || entry.id === '.' || entry.id === '..') {
47
+ this.log(`skip suspicious id: ${entry.id}`);
48
+ continue;
49
+ }
50
+ try {
51
+ const bytes = await this.exportOne(entry, stagingDir);
52
+ exports.push({
53
+ // `table` is the per-row label in the result manifest. For us,
54
+ // each row is a whole consumer DB — use the id verbatim.
55
+ table: entry.id,
56
+ tableDir: join(stagingDir, entry.id),
57
+ shardsWritten: 1,
58
+ shardsSkipped: 0,
59
+ bytes
60
+ });
61
+ totalBytes += bytes;
62
+ }
63
+ catch (err) {
64
+ this.log(`export ${entry.id} failed: ${errMsg(err)}`);
65
+ }
66
+ }
67
+ return {
68
+ pluginId: this.pluginId,
69
+ tables: exports,
70
+ totalBytes,
71
+ durationMs: Date.now() - startedAt
72
+ };
73
+ }
74
+ // -------------------------------------------------------------------
75
+ // Internal
76
+ // -------------------------------------------------------------------
77
+ async fetchManifest(timeoutMs) {
78
+ const url = `${this.baseUrl}/plugins/${this.pluginId}/api/full-export/databases`;
79
+ const res = await this.fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
80
+ if (!res.ok) {
81
+ throw new Error(`manifest HTTP ${res.status}`);
82
+ }
83
+ const body = (await res.json());
84
+ if (!Array.isArray(body.databases)) {
85
+ throw new Error(`manifest missing 'databases' array`);
86
+ }
87
+ const out = [];
88
+ for (const raw of body.databases) {
89
+ const parsed = this.parseEntry(raw);
90
+ if (parsed)
91
+ out.push(parsed);
92
+ }
93
+ return out;
94
+ }
95
+ parseEntry(raw) {
96
+ if (!raw || typeof raw !== 'object')
97
+ return null;
98
+ const o = raw;
99
+ if (typeof o.id !== 'string' || typeof o.bytes !== 'number')
100
+ return null;
101
+ if (typeof o.modifiedAt !== 'string')
102
+ return null;
103
+ return { id: o.id, bytes: o.bytes, modifiedAt: o.modifiedAt };
104
+ }
105
+ async exportOne(entry, stagingDir) {
106
+ const dir = join(stagingDir, entry.id);
107
+ await mkdir(dir, { recursive: true });
108
+ const finalPath = join(dir, 'db.sqlite');
109
+ const url = `${this.baseUrl}/plugins/${this.pluginId}/api/full-export/${encodeURIComponent(entry.id)}`;
110
+ const res = await this.fetchImpl(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
111
+ if (!res.ok) {
112
+ throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`);
113
+ }
114
+ if (!res.body) {
115
+ throw new Error('response body is empty');
116
+ }
117
+ const bytes = await atomicWrite(res.body, finalPath);
118
+ this.log(`wrote ${entry.id}/db.sqlite (${bytes} bytes)`);
119
+ return bytes;
120
+ }
121
+ // Errors propagate so a failed reset reports as failed-section instead of emitting a half-fresh half-stale tree.
122
+ async resetStagingDir(root) {
123
+ await rm(root, { recursive: true, force: true });
124
+ }
125
+ }
126
+ // Failed writes unlink the partial so half-written data can't slip into the next snapshot.
127
+ async function atomicWrite(body, finalPath) {
128
+ const partialPath = `${finalPath}.partial`;
129
+ const nodeStream = Readable.fromWeb(body);
130
+ const sink = createWriteStream(partialPath);
131
+ let bytes = 0;
132
+ nodeStream.on('data', (chunk) => {
133
+ bytes += chunk.length;
134
+ });
135
+ try {
136
+ await pipeline(nodeStream, sink);
137
+ await rename(partialPath, finalPath);
138
+ return bytes;
139
+ }
140
+ catch (err) {
141
+ await unlink(partialPath).catch(() => undefined);
142
+ throw err;
143
+ }
144
+ }
145
+ function errMsg(err) {
146
+ return err instanceof Error ? err.message : String(err);
147
+ }
148
+ //# sourceMappingURL=signalk-database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signalk-database.js","sourceRoot":"","sources":["../../src/database-export/signalk-database.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { IRouter } from 'express';
2
+ export interface StagingEntry {
3
+ path: string;
4
+ size: number;
5
+ mtime: string;
6
+ }
7
+ export interface StagingRoutesOptions {
8
+ getStagingRoot: () => string;
9
+ log?: (msg: string) => void;
10
+ }
11
+ export declare function registerStagingRoutes(router: IRouter, opts: StagingRoutesOptions): void;
12
+ export declare function listStagingFiles(stagingRoot: string): Promise<StagingEntry[]>;
13
+ export declare class StagingPathError extends Error {
14
+ readonly code: 'INVALID_FILE' | 'OUTSIDE_ROOT';
15
+ readonly status: number;
16
+ constructor(message: string, code: 'INVALID_FILE' | 'OUTSIDE_ROOT', status: number);
17
+ }
18
+ export declare function resolveStagingFile(stagingRoot: string, file: string): Promise<string>;
19
+ //# sourceMappingURL=staging-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staging-routes.d.ts","sourceRoot":"","sources":["../../src/database-export/staging-routes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAA;AAEzD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,MAAM,MAAM,CAAA;IAC5B,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5B;AAGD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAyGvF;AAID,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAMnF;AA8BD,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,IAAI,EAAE,cAAc,GAAG,cAAc;aACrC,MAAM,EAAE,MAAM;gBAF9B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,cAAc,GAAG,cAAc,EACrC,MAAM,EAAE,MAAM;CAKjC;AAID,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+B3F"}
@@ -0,0 +1,205 @@
1
+ // stagingRoot is hard-pinned by the caller; the file query param is
2
+ // realpath-resolved against it so a manipulated request can't escape.
3
+ import { createReadStream } from 'node:fs';
4
+ import { lstat, readdir, realpath, stat } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ // Must register before /api/* proxy catch-all to avoid forwarding to backup-server.
7
+ export function registerStagingRoutes(router, opts) {
8
+ router.get('/api/db-export/staging', async (_req, res) => {
9
+ const root = opts.getStagingRoot();
10
+ try {
11
+ const entries = await listStagingFiles(root);
12
+ res.json({
13
+ success: true,
14
+ data: { stagingRoot: root, entries },
15
+ timestamp: new Date().toISOString()
16
+ });
17
+ }
18
+ catch (err) {
19
+ opts.log?.(`staging list failed: ${errMsg(err)}`);
20
+ if (isENOENT(err)) {
21
+ res.json({
22
+ success: true,
23
+ data: { stagingRoot: root, entries: [] },
24
+ timestamp: new Date().toISOString()
25
+ });
26
+ return;
27
+ }
28
+ res.status(500).json({
29
+ success: false,
30
+ error: { code: 'STAGING_LIST_FAILED', message: errMsg(err) },
31
+ timestamp: new Date().toISOString()
32
+ });
33
+ }
34
+ });
35
+ router.get('/api/db-export/staging/download', async (req, res) => {
36
+ const fileParam = typeof req.query.file === 'string' ? req.query.file : '';
37
+ if (!fileParam) {
38
+ res.status(400).json({
39
+ success: false,
40
+ error: {
41
+ code: 'INVALID_INPUT',
42
+ message: 'file query parameter is required'
43
+ },
44
+ timestamp: new Date().toISOString()
45
+ });
46
+ return;
47
+ }
48
+ const root = opts.getStagingRoot();
49
+ let absoluteFile;
50
+ try {
51
+ absoluteFile = await resolveStagingFile(root, fileParam);
52
+ }
53
+ catch (err) {
54
+ const status = err instanceof StagingPathError ? err.status : 500;
55
+ res.status(status).json({
56
+ success: false,
57
+ error: {
58
+ code: err instanceof StagingPathError ? err.code : 'INTERNAL',
59
+ message: errMsg(err)
60
+ },
61
+ timestamp: new Date().toISOString()
62
+ });
63
+ return;
64
+ }
65
+ // Stat is the final gate: if the resolved path is a directory we
66
+ // refuse — directory downloads go through the server's zip-subtree
67
+ // endpoint, not here.
68
+ let s;
69
+ try {
70
+ s = await stat(absoluteFile);
71
+ }
72
+ catch (err) {
73
+ const status = isENOENT(err) ? 404 : 500;
74
+ res.status(status).json({
75
+ success: false,
76
+ error: {
77
+ code: status === 404 ? 'FILE_NOT_FOUND' : 'STAT_FAILED',
78
+ message: errMsg(err)
79
+ },
80
+ timestamp: new Date().toISOString()
81
+ });
82
+ return;
83
+ }
84
+ if (!s.isFile()) {
85
+ res.status(400).json({
86
+ success: false,
87
+ error: {
88
+ code: 'NOT_A_FILE',
89
+ message: 'staging download only serves regular files; use the snapshot subtree endpoint for directories'
90
+ },
91
+ timestamp: new Date().toISOString()
92
+ });
93
+ return;
94
+ }
95
+ const safeName = path.basename(fileParam).replace(/[^A-Za-z0-9._-]/g, '_') || 'staging-file';
96
+ res.setHeader('Content-Type', 'application/octet-stream');
97
+ res.setHeader('Content-Disposition', `attachment; filename="${safeName}"`);
98
+ res.setHeader('Content-Length', String(s.size));
99
+ const stream = createReadStream(absoluteFile);
100
+ stream.on('error', (err) => {
101
+ opts.log?.(`staging stream error for ${absoluteFile}: ${errMsg(err)}`);
102
+ if (!res.headersSent) {
103
+ res.status(500).end();
104
+ }
105
+ else {
106
+ res.end();
107
+ }
108
+ });
109
+ stream.pipe(res);
110
+ });
111
+ }
112
+ // Recursively lists regular files under stagingRoot; throws ENOENT if
113
+ // root is missing (route handler maps that to an empty response).
114
+ export async function listStagingFiles(stagingRoot) {
115
+ const root = await realpath(stagingRoot);
116
+ const out = [];
117
+ await walk(root, root, out);
118
+ out.sort((a, b) => a.path.localeCompare(b.path));
119
+ return out;
120
+ }
121
+ async function walk(root, current, out) {
122
+ let entries;
123
+ try {
124
+ entries = await readdir(current, { withFileTypes: true });
125
+ }
126
+ catch (err) {
127
+ if (isENOENT(err))
128
+ return;
129
+ throw err;
130
+ }
131
+ for (const entry of entries) {
132
+ const child = path.join(current, entry.name);
133
+ if (entry.isDirectory()) {
134
+ await walk(root, child, out);
135
+ continue;
136
+ }
137
+ if (!entry.isFile())
138
+ continue;
139
+ // lstat (not stat) so a symlink swapped in between readdir and here
140
+ // doesn't get followed; re-verify isFile after the lstat in case the
141
+ // entry was replaced.
142
+ const s = await lstat(child);
143
+ if (!s.isFile())
144
+ continue;
145
+ out.push({
146
+ path: path.relative(root, child).split(path.sep).join('/'),
147
+ size: s.size,
148
+ mtime: s.mtime.toISOString()
149
+ });
150
+ }
151
+ }
152
+ export class StagingPathError extends Error {
153
+ code;
154
+ status;
155
+ constructor(message, code, status) {
156
+ super(message);
157
+ this.code = code;
158
+ this.status = status;
159
+ this.name = 'StagingPathError';
160
+ }
161
+ }
162
+ // Realpath both ends so a symlink-hop in the middle is resolved before
163
+ // we compare paths — otherwise a symlink could escape stagingRoot.
164
+ export async function resolveStagingFile(stagingRoot, file) {
165
+ if (file.includes('\0')) {
166
+ throw new StagingPathError('file must not contain NUL bytes', 'INVALID_FILE', 400);
167
+ }
168
+ if (path.isAbsolute(file)) {
169
+ throw new StagingPathError('file must be a relative path under the staging root', 'INVALID_FILE', 400);
170
+ }
171
+ // Normalize separators so callers can pass forward-slash paths on Windows too.
172
+ const normalized = file.split(/[/\\]/).filter((p) => p.length > 0);
173
+ if (normalized.some((p) => p === '..')) {
174
+ throw new StagingPathError('file must not contain ".." segments', 'INVALID_FILE', 400);
175
+ }
176
+ const root = await realpath(stagingRoot);
177
+ const joined = path.join(root, ...normalized);
178
+ // Walk up until we find an ancestor that exists; realpath that so a
179
+ // symlink hop in the middle of the path is resolved before the check.
180
+ const resolvedAncestor = await resolveExistingAncestor(joined);
181
+ const rel = path.relative(root, resolvedAncestor);
182
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
183
+ throw new StagingPathError(`file ${file} resolves outside the staging root`, 'OUTSIDE_ROOT', 403);
184
+ }
185
+ return joined;
186
+ }
187
+ async function resolveExistingAncestor(target) {
188
+ let current = target;
189
+ while (current !== path.dirname(current)) {
190
+ try {
191
+ return await realpath(current);
192
+ }
193
+ catch {
194
+ current = path.dirname(current);
195
+ }
196
+ }
197
+ return await realpath(current);
198
+ }
199
+ function isENOENT(err) {
200
+ return typeof err === 'object' && err !== null && err.code === 'ENOENT';
201
+ }
202
+ function errMsg(err) {
203
+ return err instanceof Error ? err.message : String(err);
204
+ }
205
+ //# sourceMappingURL=staging-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staging-routes.js","sourceRoot":"","sources":["../../src/database-export/staging-routes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAK5C,OAAO,EACL,eAAe,EAKhB,MAAM,YAAY,CAAA;AAqGnB,MAAM,CAAC,OAAO,WAAW,GAAG,EAAE,eAAe,GAAG,MAAM,CA8hBrD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAK5C,OAAO,EACL,eAAe,EAKhB,MAAM,YAAY,CAAA;AA6GnB,MAAM,CAAC,OAAO,WAAW,GAAG,EAAE,eAAe,GAAG,MAAM,CA8oBrD"}