@camstack/system 1.2.19 → 1.2.21

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.
@@ -3,6 +3,15 @@ export declare class BackupOrchestratorAddon extends BaseAddon<Record<string, ne
3
3
  private systemBackup;
4
4
  private policies;
5
5
  private dataDir;
6
+ /**
7
+ * Transient build buffer for archive fan-out. The tar is built here
8
+ * ONCE, streamed to every destination via the storage cap's chunked
9
+ * upload, then removed. Lives in the addon's private `ctx.dataDir`
10
+ * (never `api.storage.resolve`, per the AddonContext contract) — a
11
+ * `cache` storage location is never declared, so resolving one threw
12
+ * and aborted every run before a byte was written.
13
+ */
14
+ private stagingDir;
6
15
  private scheduleTimer;
7
16
  /**
8
17
  * Baseline used as the "after" anchor when a policy has a cron but
@@ -47,12 +56,13 @@ export declare class BackupOrchestratorAddon extends BaseAddon<Record<string, ne
47
56
  * unreachable destination doesn't blank out the whole UI.
48
57
  */
49
58
  private aggregatedList;
59
+ private stagingPath;
50
60
  /**
51
- * Build a single archive in the `cache` storage location, then push
61
+ * Build a single archive in the private staging dir, then push
52
62
  * it to every selected destination via the storage cap's chunked
53
63
  * upload protocol. Per-destination errors are logged and counted
54
64
  * via allSettled semantics — one bad sink doesn't kill the run. The
55
- * cache file is deleted in `finally` so a crash mid-fanout doesn't
65
+ * staging file is deleted in `finally` so a crash mid-fanout doesn't
56
66
  * leak.
57
67
  */
58
68
  private triggerBackup;
@@ -99,8 +109,8 @@ export declare class BackupOrchestratorAddon extends BaseAddon<Record<string, ne
99
109
  /**
100
110
  * Schedule a restore for the next boot. Pulls the archive bytes
101
111
  * down from the destination's storage location via the chunked-
102
- * download protocol into the `cache` location, then writes the
103
- * usual restore marker against the cached path so the boot-time
112
+ * download protocol into the private staging dir, then writes the
113
+ * usual restore marker against the staged path so the boot-time
104
114
  * apply hook (in server-backend launcher) extracts before any
105
115
  * addon starts.
106
116
  *
@@ -3130,13 +3130,20 @@ var SystemBackupService = class {
3130
3130
  try {
3131
3131
  const stagedInDataDir = node_path.join(this.dataDir, ARCHIVE_MANIFEST_NAME);
3132
3132
  node_fs.copyFileSync(manifestStagePath, stagedInDataDir);
3133
+ const stagingRel = node_path.relative(this.dataDir, node_path.dirname(opts.archivePath));
3134
+ const excludePrefix = stagingRel && !stagingRel.startsWith("..") && !node_path.isAbsolute(stagingRel) ? stagingRel : null;
3133
3135
  try {
3134
3136
  await Kn({
3135
3137
  file: opts.archivePath,
3136
3138
  gzip: true,
3137
3139
  cwd: this.dataDir,
3138
3140
  follow: true,
3139
- portable: true
3141
+ portable: true,
3142
+ filter: (entryPath) => {
3143
+ if (!excludePrefix) return true;
3144
+ const norm = entryPath.replace(/^\.\//, "").replace(/\/+$/, "");
3145
+ return norm !== excludePrefix && !norm.startsWith(`${excludePrefix}/`);
3146
+ }
3140
3147
  }, [ARCHIVE_MANIFEST_NAME, ...present]);
3141
3148
  } finally {
3142
3149
  try {
@@ -4587,8 +4594,8 @@ function previewNextRuns(cron, count, after) {
4587
4594
  * that list with the `backup_destination_policies` table to surface
4588
4595
  * `enabled` / `retentionCount` / `label`. The actual archive bytes
4589
4596
  * travel through `api.storage.beginUpload` / `writeChunk` /
4590
- * `finalizeUpload`, with the staging archive built into the `cache`
4591
- * location.
4597
+ * `finalizeUpload`, with the staging archive built into the addon's
4598
+ * private `ctx.dataDir/staging` dir.
4592
4599
  */
4593
4600
  /**
4594
4601
  * Hardcoded seed for newly-discovered `backups` locations. Operators
@@ -4636,6 +4643,15 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
4636
4643
  systemBackup = null;
4637
4644
  policies = null;
4638
4645
  dataDir = "";
4646
+ /**
4647
+ * Transient build buffer for archive fan-out. The tar is built here
4648
+ * ONCE, streamed to every destination via the storage cap's chunked
4649
+ * upload, then removed. Lives in the addon's private `ctx.dataDir`
4650
+ * (never `api.storage.resolve`, per the AddonContext contract) — a
4651
+ * `cache` storage location is never declared, so resolving one threw
4652
+ * and aborted every run before a byte was written.
4653
+ */
4654
+ stagingDir = "";
4639
4655
  scheduleTimer = null;
4640
4656
  /**
4641
4657
  * Baseline used as the "after" anchor when a policy has a cron but
@@ -4650,6 +4666,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
4650
4666
  }
4651
4667
  async onInitialize() {
4652
4668
  this.dataDir = this.ctx.nodeDataDir;
4669
+ this.stagingDir = node_path.join(this.ctx.dataDir, "staging");
4653
4670
  this.systemBackup = new SystemBackupService(this.dataDir, this.ctx.logger);
4654
4671
  this.policies = this.resolvePolicyService();
4655
4672
  if (this.policies) try {
@@ -4826,12 +4843,16 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
4826
4843
  }
4827
4844
  return out;
4828
4845
  }
4846
+ async stagingPath(filename) {
4847
+ await node_fs_promises.mkdir(this.stagingDir, { recursive: true });
4848
+ return node_path.join(this.stagingDir, filename);
4849
+ }
4829
4850
  /**
4830
- * Build a single archive in the `cache` storage location, then push
4851
+ * Build a single archive in the private staging dir, then push
4831
4852
  * it to every selected destination via the storage cap's chunked
4832
4853
  * upload protocol. Per-destination errors are logged and counted
4833
4854
  * via allSettled semantics — one bad sink doesn't kill the run. The
4834
- * cache file is deleted in `finally` so a crash mid-fanout doesn't
4855
+ * staging file is deleted in `finally` so a crash mid-fanout doesn't
4835
4856
  * leak.
4836
4857
  */
4837
4858
  async triggerBackup(input) {
@@ -4844,11 +4865,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
4844
4865
  const policies = await this.loadPoliciesIndexed(targets.map((t) => t.id));
4845
4866
  const archiveId = (0, node_crypto.randomUUID)();
4846
4867
  const archiveFilename = `${archiveId}.tar.gz`;
4847
- const cacheRelativePath = `${archiveId}.tar.gz`;
4848
- const cachePath = await this.ctx.api.storage.resolve.query({
4849
- location: "cache",
4850
- relativePath: cacheRelativePath
4851
- });
4868
+ const cachePath = await this.stagingPath(archiveFilename);
4852
4869
  const result = await this.systemBackup.createArchive({
4853
4870
  archivePath: cachePath,
4854
4871
  ...input?.locations ? { locations: input.locations } : {}
@@ -5092,8 +5109,8 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
5092
5109
  /**
5093
5110
  * Schedule a restore for the next boot. Pulls the archive bytes
5094
5111
  * down from the destination's storage location via the chunked-
5095
- * download protocol into the `cache` location, then writes the
5096
- * usual restore marker against the cached path so the boot-time
5112
+ * download protocol into the private staging dir, then writes the
5113
+ * usual restore marker against the staged path so the boot-time
5097
5114
  * apply hook (in server-backend launcher) extracts before any
5098
5115
  * addon starts.
5099
5116
  *
@@ -5104,10 +5121,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
5104
5121
  if (!this.systemBackup) throw new Error("orchestrator not initialized");
5105
5122
  const entry = (await readLocationManifest(this.storageApi(), input.destinationId)).archives.find((a) => a.id === input.backupId);
5106
5123
  if (!entry) throw new Error(`backup restore: backup "${input.backupId}" not found at location "${input.destinationId}"`);
5107
- const cachePath = await this.ctx.api.storage.resolve.query({
5108
- location: "cache",
5109
- relativePath: `restore-${input.backupId}.tar.gz`
5110
- });
5124
+ const cachePath = await this.stagingPath(`restore-${input.backupId}.tar.gz`);
5111
5125
  await downloadArchiveToCache({
5112
5126
  api: this.ctx.api.storage,
5113
5127
  locationId: input.destinationId,
@@ -3115,13 +3115,20 @@ var SystemBackupService = class {
3115
3115
  try {
3116
3116
  const stagedInDataDir = path$1.join(this.dataDir, ARCHIVE_MANIFEST_NAME);
3117
3117
  fs.copyFileSync(manifestStagePath, stagedInDataDir);
3118
+ const stagingRel = path$1.relative(this.dataDir, path$1.dirname(opts.archivePath));
3119
+ const excludePrefix = stagingRel && !stagingRel.startsWith("..") && !path$1.isAbsolute(stagingRel) ? stagingRel : null;
3118
3120
  try {
3119
3121
  await Kn({
3120
3122
  file: opts.archivePath,
3121
3123
  gzip: true,
3122
3124
  cwd: this.dataDir,
3123
3125
  follow: true,
3124
- portable: true
3126
+ portable: true,
3127
+ filter: (entryPath) => {
3128
+ if (!excludePrefix) return true;
3129
+ const norm = entryPath.replace(/^\.\//, "").replace(/\/+$/, "");
3130
+ return norm !== excludePrefix && !norm.startsWith(`${excludePrefix}/`);
3131
+ }
3125
3132
  }, [ARCHIVE_MANIFEST_NAME, ...present]);
3126
3133
  } finally {
3127
3134
  try {
@@ -4572,8 +4579,8 @@ function previewNextRuns(cron, count, after) {
4572
4579
  * that list with the `backup_destination_policies` table to surface
4573
4580
  * `enabled` / `retentionCount` / `label`. The actual archive bytes
4574
4581
  * travel through `api.storage.beginUpload` / `writeChunk` /
4575
- * `finalizeUpload`, with the staging archive built into the `cache`
4576
- * location.
4582
+ * `finalizeUpload`, with the staging archive built into the addon's
4583
+ * private `ctx.dataDir/staging` dir.
4577
4584
  */
4578
4585
  /**
4579
4586
  * Hardcoded seed for newly-discovered `backups` locations. Operators
@@ -4621,6 +4628,15 @@ var BackupOrchestratorAddon = class extends BaseAddon {
4621
4628
  systemBackup = null;
4622
4629
  policies = null;
4623
4630
  dataDir = "";
4631
+ /**
4632
+ * Transient build buffer for archive fan-out. The tar is built here
4633
+ * ONCE, streamed to every destination via the storage cap's chunked
4634
+ * upload, then removed. Lives in the addon's private `ctx.dataDir`
4635
+ * (never `api.storage.resolve`, per the AddonContext contract) — a
4636
+ * `cache` storage location is never declared, so resolving one threw
4637
+ * and aborted every run before a byte was written.
4638
+ */
4639
+ stagingDir = "";
4624
4640
  scheduleTimer = null;
4625
4641
  /**
4626
4642
  * Baseline used as the "after" anchor when a policy has a cron but
@@ -4635,6 +4651,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
4635
4651
  }
4636
4652
  async onInitialize() {
4637
4653
  this.dataDir = this.ctx.nodeDataDir;
4654
+ this.stagingDir = path$1.join(this.ctx.dataDir, "staging");
4638
4655
  this.systemBackup = new SystemBackupService(this.dataDir, this.ctx.logger);
4639
4656
  this.policies = this.resolvePolicyService();
4640
4657
  if (this.policies) try {
@@ -4811,12 +4828,16 @@ var BackupOrchestratorAddon = class extends BaseAddon {
4811
4828
  }
4812
4829
  return out;
4813
4830
  }
4831
+ async stagingPath(filename) {
4832
+ await fsp.mkdir(this.stagingDir, { recursive: true });
4833
+ return path$1.join(this.stagingDir, filename);
4834
+ }
4814
4835
  /**
4815
- * Build a single archive in the `cache` storage location, then push
4836
+ * Build a single archive in the private staging dir, then push
4816
4837
  * it to every selected destination via the storage cap's chunked
4817
4838
  * upload protocol. Per-destination errors are logged and counted
4818
4839
  * via allSettled semantics — one bad sink doesn't kill the run. The
4819
- * cache file is deleted in `finally` so a crash mid-fanout doesn't
4840
+ * staging file is deleted in `finally` so a crash mid-fanout doesn't
4820
4841
  * leak.
4821
4842
  */
4822
4843
  async triggerBackup(input) {
@@ -4829,11 +4850,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
4829
4850
  const policies = await this.loadPoliciesIndexed(targets.map((t) => t.id));
4830
4851
  const archiveId = randomUUID();
4831
4852
  const archiveFilename = `${archiveId}.tar.gz`;
4832
- const cacheRelativePath = `${archiveId}.tar.gz`;
4833
- const cachePath = await this.ctx.api.storage.resolve.query({
4834
- location: "cache",
4835
- relativePath: cacheRelativePath
4836
- });
4853
+ const cachePath = await this.stagingPath(archiveFilename);
4837
4854
  const result = await this.systemBackup.createArchive({
4838
4855
  archivePath: cachePath,
4839
4856
  ...input?.locations ? { locations: input.locations } : {}
@@ -5077,8 +5094,8 @@ var BackupOrchestratorAddon = class extends BaseAddon {
5077
5094
  /**
5078
5095
  * Schedule a restore for the next boot. Pulls the archive bytes
5079
5096
  * down from the destination's storage location via the chunked-
5080
- * download protocol into the `cache` location, then writes the
5081
- * usual restore marker against the cached path so the boot-time
5097
+ * download protocol into the private staging dir, then writes the
5098
+ * usual restore marker against the staged path so the boot-time
5082
5099
  * apply hook (in server-backend launcher) extracts before any
5083
5100
  * addon starts.
5084
5101
  *
@@ -5089,10 +5106,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
5089
5106
  if (!this.systemBackup) throw new Error("orchestrator not initialized");
5090
5107
  const entry = (await readLocationManifest(this.storageApi(), input.destinationId)).archives.find((a) => a.id === input.backupId);
5091
5108
  if (!entry) throw new Error(`backup restore: backup "${input.backupId}" not found at location "${input.destinationId}"`);
5092
- const cachePath = await this.ctx.api.storage.resolve.query({
5093
- location: "cache",
5094
- relativePath: `restore-${input.backupId}.tar.gz`
5095
- });
5109
+ const cachePath = await this.stagingPath(`restore-${input.backupId}.tar.gz`);
5096
5110
  await downloadArchiveToCache({
5097
5111
  api: this.ctx.api.storage,
5098
5112
  locationId: input.destinationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",