@futo-org/backups-orchestrator-api 0.1.72 → 0.4.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 (95) hide show
  1. package/dist/backends/backend.d.ts +2 -2
  2. package/dist/backends/backend.js +2 -5
  3. package/dist/backends/backend.js.map +1 -1
  4. package/dist/backends/local.backend.d.ts +1 -0
  5. package/dist/backends/local.backend.js +3 -0
  6. package/dist/backends/local.backend.js.map +1 -1
  7. package/dist/backends/s3.backend.d.ts +1 -0
  8. package/dist/backends/s3.backend.js +3 -0
  9. package/dist/backends/s3.backend.js.map +1 -1
  10. package/dist/backends/yucca.backend.d.ts +19 -3
  11. package/dist/backends/yucca.backend.js +47 -22
  12. package/dist/backends/yucca.backend.js.map +1 -1
  13. package/dist/const.d.ts +2 -1
  14. package/dist/const.js +3 -2
  15. package/dist/const.js.map +1 -1
  16. package/dist/controllers/onboarding.controller.d.ts +2 -0
  17. package/dist/controllers/onboarding.controller.js +18 -0
  18. package/dist/controllers/onboarding.controller.js.map +1 -1
  19. package/dist/controllers/repository.controller.d.ts +3 -2
  20. package/dist/controllers/repository.controller.js +17 -3
  21. package/dist/controllers/repository.controller.js.map +1 -1
  22. package/dist/dto/backend.dto.d.ts +1 -0
  23. package/dist/dto/backend.dto.js +5 -0
  24. package/dist/dto/backend.dto.js.map +1 -1
  25. package/dist/dto/onboarding.dto.d.ts +4 -0
  26. package/dist/dto/onboarding.dto.js +16 -0
  27. package/dist/dto/onboarding.dto.js.map +1 -1
  28. package/dist/dto/repository.dto.d.ts +9 -0
  29. package/dist/dto/repository.dto.js +33 -1
  30. package/dist/dto/repository.dto.js.map +1 -1
  31. package/dist/enum.d.ts +10 -0
  32. package/dist/enum.js +13 -1
  33. package/dist/enum.js.map +1 -1
  34. package/dist/interceptors/telemetry-error.interceptor.d.ts +8 -0
  35. package/dist/interceptors/telemetry-error.interceptor.js +47 -0
  36. package/dist/interceptors/telemetry-error.interceptor.js.map +1 -0
  37. package/dist/moduleConfig.d.ts +1 -1
  38. package/dist/orchestrationApi.module.d.ts +40 -1
  39. package/dist/orchestrationApi.module.js +18 -9
  40. package/dist/orchestrationApi.module.js.map +1 -1
  41. package/dist/repositories/backend.repository.d.ts +2 -0
  42. package/dist/repositories/backend.repository.js +8 -3
  43. package/dist/repositories/backend.repository.js.map +1 -1
  44. package/dist/repositories/bootstrap.repository.d.ts +9 -0
  45. package/dist/repositories/bootstrap.repository.js +33 -0
  46. package/dist/repositories/bootstrap.repository.js.map +1 -0
  47. package/dist/repositories/config.repository.d.ts +2 -0
  48. package/dist/repositories/config.repository.js +6 -0
  49. package/dist/repositories/config.repository.js.map +1 -1
  50. package/dist/repositories/repository.repository.d.ts +2 -1
  51. package/dist/repositories/repository.repository.js +9 -9
  52. package/dist/repositories/repository.repository.js.map +1 -1
  53. package/dist/repositories/restic.repository.d.ts +32 -3
  54. package/dist/repositories/restic.repository.js +3 -7
  55. package/dist/repositories/restic.repository.js.map +1 -1
  56. package/dist/repositories/runHistory.repository.d.ts +2 -2
  57. package/dist/repositories/schedule.repository.d.ts +2 -2
  58. package/dist/schema/migrations/20260603120000-AddRepositoryRemoteId.js +13 -0
  59. package/dist/schema/migrations/20260603120000-AddRepositoryRemoteId.js.map +1 -0
  60. package/dist/schema/tables/backend.table.d.ts +1 -0
  61. package/dist/schema/tables/backend.table.js.map +1 -1
  62. package/dist/schema/tables/repository.table.d.ts +1 -0
  63. package/dist/schema/tables/repository.table.js +1 -0
  64. package/dist/schema/tables/repository.table.js.map +1 -1
  65. package/dist/services/auth.service.d.ts +3 -1
  66. package/dist/services/auth.service.js +20 -7
  67. package/dist/services/auth.service.js.map +1 -1
  68. package/dist/services/backend.service.d.ts +3 -3
  69. package/dist/services/backend.service.js +18 -8
  70. package/dist/services/backend.service.js.map +1 -1
  71. package/dist/services/bootstrap.service.d.ts +3 -1
  72. package/dist/services/bootstrap.service.js +16 -6
  73. package/dist/services/bootstrap.service.js.map +1 -1
  74. package/dist/services/integrations.service.d.ts +3 -1
  75. package/dist/services/integrations.service.js +10 -2
  76. package/dist/services/integrations.service.js.map +1 -1
  77. package/dist/services/onboarding.service.d.ts +7 -1
  78. package/dist/services/onboarding.service.js +37 -2
  79. package/dist/services/onboarding.service.js.map +1 -1
  80. package/dist/services/repository.service.d.ts +6 -3
  81. package/dist/services/repository.service.js +252 -79
  82. package/dist/services/repository.service.js.map +1 -1
  83. package/dist/services/schedule.service.d.ts +4 -1
  84. package/dist/services/schedule.service.js +38 -2
  85. package/dist/services/schedule.service.js.map +1 -1
  86. package/dist/services/telemetry.service.d.ts +9 -0
  87. package/dist/services/telemetry.service.js +59 -0
  88. package/dist/services/telemetry.service.js.map +1 -0
  89. package/package.json +6 -4
  90. package/dist/schema/migrations/20260512120000-AddRepositoryRetentionPreset.js +0 -18
  91. package/dist/schema/migrations/20260512120000-AddRepositoryRetentionPreset.js.map +0 -1
  92. package/dist/services/database.service.d.ts +0 -11
  93. package/dist/services/database.service.js +0 -39
  94. package/dist/services/database.service.js.map +0 -1
  95. /package/dist/schema/migrations/{20260512120000-AddRepositoryRetentionPreset.d.ts → 20260603120000-AddRepositoryRemoteId.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import { FilesystemListingRequestDto, FilesystemListingResponseDto } from '../dto/filesystem.dto';
3
- import { ListSnapshotsResponseDto, RepositoryCheckImportResponseDto, RepositoryCreateRequestDto, RepositoryCreateResponseDto, RepositoryInspectResponseDto, RepositoryListResponseDto, RepositorySnapshotRestoreFromPointRequestDto, RepositorySnapshotRestoreRequestDto, RepositoryUpdateRequestDto, RepositoryUpdateResponseDto, RunHistoryResponseDto } from '../dto/repository.dto';
3
+ import { ListSnapshotsResponseDto, RepositoryCheckImportResponseDto, RepositoryCreateRequestDto, RepositoryCreateResponseDto, RepositoryInspectResponseDto, RepositoryListResponseDto, RepositoryPrimaryBackendReconfigureRequestDto, RepositorySnapshotRestoreFromPointRequestDto, RepositorySnapshotRestoreRequestDto, RepositoryUpdateRequestDto, RepositoryUpdateResponseDto, RunHistoryResponseDto } from '../dto/repository.dto';
4
4
  import { EventsGateway } from '../events/events.gateway';
5
5
  import { BackendRepository } from '../repositories/backend.repository';
6
6
  import { ConfigRepository } from '../repositories/config.repository';
@@ -14,6 +14,7 @@ import { RunHistoryRepository } from '../repositories/runHistory.repository';
14
14
  import { RunningTasksRepository } from '../repositories/runningTasks.repository';
15
15
  import { StorageRepository } from '../repositories/storage.repository';
16
16
  import { BootstrapService } from './bootstrap.service';
17
+ import { TelemetryService } from './telemetry.service';
17
18
  export declare class RepositoryService {
18
19
  private readonly tasks;
19
20
  private readonly events;
@@ -28,11 +29,12 @@ export declare class RepositoryService {
28
29
  private readonly moduleConfig;
29
30
  private readonly storage;
30
31
  private readonly bootstrap;
31
- constructor(tasks: RunningTasksRepository, events: EventsGateway, backend: BackendRepository, config: ConfigRepository, database: DatabaseRepository, restic: ResticRepository, runHistory: RunHistoryRepository, repository: RepositoryRepository, repositoryPath: RepositoryPathRepository, repositoryLocalMetrics: RepositoryLocalMetricsRepository, moduleConfig: ModuleConfigRepository, storage: StorageRepository, bootstrap: BootstrapService);
32
+ private readonly telemetry;
33
+ constructor(tasks: RunningTasksRepository, events: EventsGateway, backend: BackendRepository, config: ConfigRepository, database: DatabaseRepository, restic: ResticRepository, runHistory: RunHistoryRepository, repository: RepositoryRepository, repositoryPath: RepositoryPathRepository, repositoryLocalMetrics: RepositoryLocalMetricsRepository, moduleConfig: ModuleConfigRepository, storage: StorageRepository, bootstrap: BootstrapService, telemetry: TelemetryService);
32
34
  private getLocalRepository;
33
35
  createRepository(dto: RepositoryCreateRequestDto, backendId?: string): Promise<RepositoryCreateResponseDto>;
34
36
  getRepositories(): Promise<RepositoryListResponseDto>;
35
- inspectRepositories(): Promise<RepositoryInspectResponseDto>;
37
+ inspectRepositories(backendId?: string): Promise<RepositoryInspectResponseDto>;
36
38
  private mapSnapshot;
37
39
  updateRepository(id: string, dto: RepositoryUpdateRequestDto, backendId?: string): Promise<RepositoryUpdateResponseDto>;
38
40
  deleteRepository(id: string): Promise<void>;
@@ -49,6 +51,7 @@ export declare class RepositoryService {
49
51
  }>;
50
52
  checkImportRepository(id: string, backendId: string): Promise<RepositoryCheckImportResponseDto>;
51
53
  importRepository(id: string, backendId: string): Promise<RepositoryCreateResponseDto>;
54
+ reconfigureRepositoryPrimaryBackend(id: string, dto: RepositoryPrimaryBackendReconfigureRequestDto): Promise<RepositoryCreateResponseDto>;
52
55
  getSnapshots(id: string): Promise<ListSnapshotsResponseDto>;
53
56
  restoreSnapshot(id: string, snapshotId: string, dto: RepositorySnapshotRestoreRequestDto): Promise<{
54
57
  logId: string;
@@ -14,8 +14,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.RepositoryService = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
+ const node_crypto_1 = require("node:crypto");
17
18
  const node_path_1 = require("node:path");
18
- const backend_1 = require("../backends/backend");
19
19
  const enum_1 = require("../enum");
20
20
  const events_gateway_1 = require("../events/events.gateway");
21
21
  const backend_repository_1 = require("../repositories/backend.repository");
@@ -31,6 +31,7 @@ const runningTasks_repository_1 = require("../repositories/runningTasks.reposito
31
31
  const storage_repository_1 = require("../repositories/storage.repository");
32
32
  const restic_1 = require("../utils/restic");
33
33
  const bootstrap_service_1 = require("./bootstrap.service");
34
+ const telemetry_service_1 = require("./telemetry.service");
34
35
  let RepositoryService = class RepositoryService {
35
36
  tasks;
36
37
  events;
@@ -45,7 +46,8 @@ let RepositoryService = class RepositoryService {
45
46
  moduleConfig;
46
47
  storage;
47
48
  bootstrap;
48
- constructor(tasks, events, backend, config, database, restic, runHistory, repository, repositoryPath, repositoryLocalMetrics, moduleConfig, storage, bootstrap) {
49
+ telemetry;
50
+ constructor(tasks, events, backend, config, database, restic, runHistory, repository, repositoryPath, repositoryLocalMetrics, moduleConfig, storage, bootstrap, telemetry) {
49
51
  this.tasks = tasks;
50
52
  this.events = events;
51
53
  this.backend = backend;
@@ -59,6 +61,7 @@ let RepositoryService = class RepositoryService {
59
61
  this.moduleConfig = moduleConfig;
60
62
  this.storage = storage;
61
63
  this.bootstrap = bootstrap;
64
+ this.telemetry = telemetry;
62
65
  }
63
66
  async getLocalRepository(id, configuration, metrics) {
64
67
  if (!configuration) {
@@ -76,24 +79,26 @@ let RepositoryService = class RepositoryService {
76
79
  const backends = await this.backend.getBackends();
77
80
  backendId = backends[0].id;
78
81
  }
79
- const { configuration } = await this.backend.getBackend(backendId);
80
- const backend = backend_1.Backend.from(configuration, this.moduleConfig.get());
82
+ const { backend, configuration } = await this.backend.getBackend(backendId);
81
83
  const { repository: remote } = await backend.createRepository(dto);
84
+ const id = (0, node_crypto_1.randomUUID)();
82
85
  const endpoint = await backend.getResticEndpoint(remote.id);
83
86
  const key = await this.config.deriveEncryptionKey(`repository-${remote.id}`);
84
87
  await this.restic.init(endpoint, key);
85
88
  await this.repository.create({
86
- id: remote.id,
89
+ id,
90
+ remoteId: remote.id,
87
91
  backendId,
88
92
  retentionPolicy: restic_1.DEFAULT_RETENTION_POLICY,
89
93
  });
90
94
  const paths = dto.paths ?? [];
91
95
  for (const path of paths) {
92
- await this.repositoryPath.create({ id: remote.id, path });
96
+ await this.repositoryPath.create({ id, path });
93
97
  }
94
98
  const repository = {
95
- ...(await this.getLocalRepository(remote.id, { paths, retentionPolicy: restic_1.DEFAULT_RETENTION_POLICY })),
99
+ ...(await this.getLocalRepository(id, { paths, retentionPolicy: restic_1.DEFAULT_RETENTION_POLICY })),
96
100
  ...remote,
101
+ id,
97
102
  backends: {
98
103
  primary: {
99
104
  id: backendId,
@@ -103,6 +108,10 @@ let RepositoryService = class RepositoryService {
103
108
  secondary: [],
104
109
  },
105
110
  };
111
+ this.telemetry.submitStructuredLog('Created repository', {
112
+ repositoryId: remote.id,
113
+ backendId,
114
+ });
106
115
  this.events.publish({
107
116
  type: 'RepositoryCreate',
108
117
  repository,
@@ -116,8 +125,7 @@ let RepositoryService = class RepositoryService {
116
125
  const repositories = [];
117
126
  const backendsById = Object.fromEntries(backends.map((backend) => [backend.id, backend]));
118
127
  const remoteRepositories = {};
119
- for (const { id: backendId, configuration } of backends) {
120
- const backend = backend_1.Backend.from(configuration, this.moduleConfig.get());
128
+ for (const { id: backendId, backend } of backends) {
121
129
  remoteRepositories[backendId] = {};
122
130
  try {
123
131
  const { repositories: list } = await backend.getRepositories();
@@ -132,8 +140,8 @@ let RepositoryService = class RepositoryService {
132
140
  const localRepositories = await this.repository.getAll();
133
141
  const localPaths = await this.repositoryPath.getAll();
134
142
  const localMetrics = await this.repositoryLocalMetrics.getAll();
135
- for (const { id, backendId, retentionPolicy } of localRepositories) {
136
- const remoteRepository = remoteRepositories[backendId][id];
143
+ for (const { id, remoteId, backendId, retentionPolicy } of localRepositories) {
144
+ const remoteRepository = remoteRepositories[backendId][remoteId];
137
145
  const configuration = {
138
146
  paths: localPaths.filter((entry) => entry.id === id).map(({ path }) => path),
139
147
  retentionPolicy,
@@ -142,6 +150,7 @@ let RepositoryService = class RepositoryService {
142
150
  if (remoteRepository) {
143
151
  repositories.push({
144
152
  ...remoteRepository,
153
+ id,
145
154
  ...(await this.getLocalRepository(id, configuration, metrics)),
146
155
  backends: {
147
156
  primary: {
@@ -152,7 +161,7 @@ let RepositoryService = class RepositoryService {
152
161
  secondary: [],
153
162
  },
154
163
  });
155
- delete remoteRepositories[backendId][id];
164
+ delete remoteRepositories[backendId][remoteId];
156
165
  }
157
166
  else {
158
167
  repositories.push({
@@ -190,14 +199,20 @@ let RepositoryService = class RepositoryService {
190
199
  repositories,
191
200
  };
192
201
  }
193
- async inspectRepositories() {
202
+ async inspectRepositories(backendId) {
194
203
  const { repositories } = await this.getRepositories();
195
- const snapshots = await Promise.allSettled(repositories.map(async (repository) => {
196
- const { endpoint, key } = await this.getResticParameters(repository.id, repository.backends?.primary.id);
204
+ const list = repositories.filter((repository) => !repository.configuration &&
205
+ repository.backends &&
206
+ (!backendId || repository.backends.primary.id === backendId));
207
+ const snapshots = await Promise.allSettled(list.map(async (repository) => {
208
+ const { endpoint, key } = await this.getResticParameters({
209
+ backendId: repository.backends.primary.id,
210
+ remoteId: repository.id,
211
+ });
197
212
  return this.restic.snapshots(endpoint, key);
198
213
  }));
199
214
  return {
200
- repositories: repositories.map((repository, idx) => ({
215
+ repositories: list.map((repository, idx) => ({
201
216
  ...repository,
202
217
  snapshots: snapshots[idx].status === 'fulfilled'
203
218
  ? snapshots[idx].value.map((snapshot) => this.mapSnapshot(snapshot))
@@ -222,18 +237,22 @@ let RepositoryService = class RepositoryService {
222
237
  };
223
238
  }
224
239
  async updateRepository(id, dto, backendId) {
225
- if (!backendId) {
240
+ let remoteId;
241
+ if (backendId) {
242
+ remoteId = id;
243
+ }
244
+ else {
226
245
  const localRepository = await this.repository.get(id);
227
246
  backendId = localRepository.backendId;
247
+ remoteId = localRepository.remoteId;
228
248
  }
229
- const backend = await this.backend.getBackend(backendId);
230
- const backendInstance = backend_1.Backend.from(backend.configuration, this.moduleConfig.get());
249
+ const { backend, configuration } = await this.backend.getBackend(backendId);
231
250
  let remote;
232
251
  if (dto.name) {
233
- ({ repository: remote } = await backendInstance.updateRepository(id, dto));
252
+ ({ repository: remote } = await backend.updateRepository(remoteId, dto));
234
253
  }
235
254
  else {
236
- ({ repository: remote } = await backendInstance.getRepository(id));
255
+ ({ repository: remote } = await backend.getRepository(remoteId));
237
256
  }
238
257
  if (dto.paths) {
239
258
  const currentPaths = new Set(await this.repositoryPath.get(id));
@@ -256,16 +275,20 @@ let RepositoryService = class RepositoryService {
256
275
  const metrics = await this.repositoryLocalMetrics.get(id);
257
276
  const repository = {
258
277
  ...remote,
278
+ id,
259
279
  ...(await this.getLocalRepository(id, undefined, metrics)),
260
280
  backends: {
261
281
  primary: {
262
282
  id: backendId,
263
- type: backend.configuration.type,
283
+ type: configuration.type,
264
284
  online: true,
265
285
  },
266
286
  secondary: [],
267
287
  },
268
288
  };
289
+ this.telemetry.submitStructuredLog('Updated repository', {
290
+ repositoryId: id,
291
+ });
269
292
  this.events.publish({
270
293
  type: 'RepositoryUpdate',
271
294
  repositoryId: id,
@@ -277,23 +300,31 @@ let RepositoryService = class RepositoryService {
277
300
  }
278
301
  async deleteRepository(id) {
279
302
  await this.repository.delete(id);
303
+ this.telemetry.submitStructuredLog('Deleted repository', {
304
+ repositoryId: id,
305
+ });
280
306
  this.events.publish({
281
307
  type: 'RepositoryDelete',
282
308
  repositoryId: id,
283
309
  });
284
310
  }
285
- async getResticParameters(id, backendId) {
286
- if (!backendId) {
287
- const localRepository = await this.repository.get(id);
311
+ async getResticParameters(repository) {
312
+ let backendId;
313
+ let remoteId;
314
+ if (typeof repository === 'string') {
315
+ const localRepository = await this.repository.get(repository);
288
316
  if (!localRepository) {
289
317
  throw new common_1.BadRequestException('Repository not found locally');
290
318
  }
291
319
  backendId = localRepository.backendId;
320
+ remoteId = localRepository.remoteId;
321
+ }
322
+ else {
323
+ ({ backendId, remoteId } = repository);
292
324
  }
293
- const backend = await this.backend.getBackend(backendId);
294
- const backendInstance = backend_1.Backend.from(backend.configuration, this.moduleConfig.get());
295
- const endpoint = await backendInstance.getResticEndpoint(id);
296
- const key = await this.config.deriveEncryptionKey(`repository-${id}`);
325
+ const { backend } = await this.backend.getBackend(backendId);
326
+ const endpoint = await backend.getResticEndpoint(remoteId);
327
+ const key = await this.config.deriveEncryptionKey(`repository-${remoteId}`);
297
328
  return { endpoint, key };
298
329
  }
299
330
  async updateLocalMetrics(id, options) {
@@ -315,11 +346,10 @@ let RepositoryService = class RepositoryService {
315
346
  },
316
347
  });
317
348
  if (metrics.sizeBytes) {
318
- const { backendId } = await this.repository.get(id);
319
- const { configuration } = await this.backend.getBackend(backendId);
320
- const backend = backend_1.Backend.from(configuration, this.moduleConfig.get());
349
+ const { backendId, remoteId } = await this.repository.get(id);
350
+ const { backend } = await this.backend.getBackend(backendId);
321
351
  if (backend.isMetricsCapable()) {
322
- await backend.submitMetricRepositorySize(id, metrics.sizeBytes);
352
+ await backend.submitMetricRepositorySize(remoteId, metrics.sizeBytes);
323
353
  }
324
354
  }
325
355
  }
@@ -328,11 +358,16 @@ let RepositoryService = class RepositoryService {
328
358
  }
329
359
  async createBackup(id, signal) {
330
360
  if (!this.tasks.canStart(id)) {
361
+ this.telemetry.submitStructuredLog('Backup rejected, task already running', {
362
+ repositoryId: id,
363
+ });
331
364
  throw new common_1.BadRequestException('Task already running!');
332
365
  }
333
- const { backendId } = await this.repository.get(id);
334
- const { configuration } = await this.backend.getBackend(backendId);
335
- const backend = backend_1.Backend.from(configuration, this.moduleConfig.get());
366
+ this.telemetry.submitStructuredLog('Running backup', {
367
+ repositoryId: id,
368
+ });
369
+ const { backendId, remoteId } = await this.repository.get(id);
370
+ const { backend } = await this.backend.getBackend(backendId);
336
371
  const { endpoint, key } = await this.getResticParameters(id);
337
372
  const paths = await this.repositoryPath.get(id);
338
373
  if (paths.length === 0) {
@@ -346,15 +381,22 @@ let RepositoryService = class RepositoryService {
346
381
  logId,
347
382
  });
348
383
  if (backend.isMetricsCapable()) {
349
- await backend.submitMetricBackupStart(id);
384
+ await backend.submitMetricBackupStart(remoteId);
350
385
  }
351
386
  try {
352
387
  const taskSignal = this.tasks.startTask(id, enum_1.TaskType.Backup, logId, signal);
353
388
  await this.restic.unlockAll(endpoint, key);
354
- await this.restic.backup(endpoint, key, paths, log, taskSignal);
389
+ const summary = await this.restic.backup(endpoint, key, paths, log, taskSignal);
390
+ this.telemetry.submitStructuredLog('Finished backup to primary backend', {
391
+ repositoryId: id,
392
+ summary,
393
+ });
355
394
  const { retentionPolicy: policy } = await this.repository.get(id);
356
395
  if (policy) {
357
396
  await this.runForgetAndPrune(endpoint, key, policy, log, taskSignal);
397
+ this.telemetry.submitStructuredLog('Finished prune on primary backend', {
398
+ repositoryId: id,
399
+ });
358
400
  }
359
401
  }
360
402
  finally {
@@ -372,6 +414,10 @@ let RepositoryService = class RepositoryService {
372
414
  lastBackupDuration,
373
415
  },
374
416
  });
417
+ this.telemetry.submitStructuredLog('Backup finished', {
418
+ repositoryId: id,
419
+ error,
420
+ });
375
421
  if (error) {
376
422
  fail(error);
377
423
  }
@@ -379,7 +425,7 @@ let RepositoryService = class RepositoryService {
379
425
  complete();
380
426
  }
381
427
  if (backend.isMetricsCapable()) {
382
- await backend.submitMetricBackupEnd(id, !error, lastBackupDuration);
428
+ await backend.submitMetricBackupEnd(remoteId, !error, lastBackupDuration);
383
429
  }
384
430
  }));
385
431
  task.catch(() => { });
@@ -408,22 +454,39 @@ let RepositoryService = class RepositoryService {
408
454
  }
409
455
  async pruneRepository(id, signal) {
410
456
  if (!this.tasks.canStart(id)) {
457
+ this.telemetry.submitStructuredLog('Repository prune rejected, task already running', {
458
+ repositoryId: id,
459
+ });
411
460
  throw new common_1.BadRequestException('Task already running!');
412
461
  }
413
462
  const { retentionPolicy: policy } = await this.repository.get(id);
414
463
  if (!policy) {
415
464
  throw new common_1.BadRequestException('No retention policy configured for this repository');
416
465
  }
466
+ this.telemetry.submitStructuredLog('Running repository prune', {
467
+ repositoryId: id,
468
+ retentionPolicy: policy,
469
+ });
417
470
  const { endpoint, key } = await this.getResticParameters(id);
418
471
  return new Promise((resolve) => {
419
472
  const task = new Promise((complete, fail) => void this.runHistory.createLog(id, enum_1.TaskType.Forget, async (log, logId) => {
420
473
  resolve({ task, logId });
421
- await this.restic.unlockAll(endpoint, key);
422
- await this.runForgetAndPrune(endpoint, key, policy, log, signal);
474
+ try {
475
+ const taskSignal = this.tasks.startTask(id, enum_1.TaskType.Forget, logId, signal);
476
+ await this.restic.unlockAll(endpoint, key);
477
+ await this.runForgetAndPrune(endpoint, key, policy, log, taskSignal);
478
+ }
479
+ finally {
480
+ this.tasks.endTask(id);
481
+ }
423
482
  }, (error) => {
424
483
  void this.updateLocalMetrics(id, {
425
484
  resticParameters: { endpoint, key },
426
485
  });
486
+ this.telemetry.submitStructuredLog('Finished repository prune', {
487
+ repositoryId: id,
488
+ error,
489
+ });
427
490
  if (error) {
428
491
  fail(error);
429
492
  }
@@ -435,7 +498,7 @@ let RepositoryService = class RepositoryService {
435
498
  });
436
499
  }
437
500
  async checkImportRepository(id, backendId) {
438
- const { endpoint, key } = await this.getResticParameters(id, backendId);
501
+ const { endpoint, key } = await this.getResticParameters({ backendId, remoteId: id });
439
502
  try {
440
503
  await this.restic.snapshots(endpoint, key);
441
504
  return {
@@ -449,31 +512,84 @@ let RepositoryService = class RepositoryService {
449
512
  }
450
513
  }
451
514
  async importRepository(id, backendId) {
452
- const { configuration } = await this.backend.getBackend(backendId);
453
- const backend = backend_1.Backend.from(configuration, this.moduleConfig.get());
454
- const { repository: remote } = await backend.getRepository(id);
455
- const endpoint = await backend.getResticEndpoint(remote.id);
456
- const key = await this.config.deriveEncryptionKey(`repository-${remote.id}`);
457
- await this.restic.keyList(endpoint, key);
458
- let paths = [];
515
+ this.telemetry.submitStructuredLog('Running repository import', {
516
+ repositoryId: id,
517
+ backendId,
518
+ });
459
519
  try {
460
- const snapshots = await this.restic.snapshots(endpoint, key);
461
- snapshots.sort((a, b) => +b.time - +a.time);
462
- paths = snapshots[0].paths;
520
+ const { configuration, backend } = await this.backend.getBackend(backendId);
521
+ const { repository: remote } = await backend.getRepository(id);
522
+ const localId = (0, node_crypto_1.randomUUID)();
523
+ const endpoint = await backend.getResticEndpoint(remote.id);
524
+ const key = await this.config.deriveEncryptionKey(`repository-${remote.id}`);
525
+ await this.restic.keyList(endpoint, key);
526
+ let paths = [];
527
+ try {
528
+ const snapshots = await this.restic.snapshots(endpoint, key);
529
+ snapshots.sort((a, b) => +b.time - +a.time);
530
+ paths = snapshots[0].paths;
531
+ }
532
+ catch {
533
+ }
534
+ await this.repository.create({
535
+ id: localId,
536
+ remoteId: remote.id,
537
+ backendId,
538
+ retentionPolicy: restic_1.DEFAULT_RETENTION_POLICY,
539
+ });
540
+ const repository = {
541
+ ...(await this.getLocalRepository(localId, { paths, retentionPolicy: restic_1.DEFAULT_RETENTION_POLICY })),
542
+ ...remote,
543
+ id: localId,
544
+ backends: {
545
+ primary: {
546
+ id: backendId,
547
+ online: true,
548
+ type: configuration.type,
549
+ },
550
+ secondary: [],
551
+ },
552
+ };
553
+ this.telemetry.submitStructuredLog('Finished repository import', {
554
+ repositoryId: id,
555
+ backendId,
556
+ });
557
+ this.events.publish({
558
+ type: 'RepositoryCreate',
559
+ repository,
560
+ });
561
+ return {
562
+ repository,
563
+ };
463
564
  }
464
- catch {
565
+ catch (error) {
566
+ this.telemetry.submitStructuredLog('Finished repository import', {
567
+ repositoryId: id,
568
+ backendId,
569
+ error,
570
+ });
571
+ throw error;
465
572
  }
466
- await this.repository.create({
467
- id: remote.id,
468
- backendId,
469
- retentionPolicy: restic_1.DEFAULT_RETENTION_POLICY,
573
+ }
574
+ async reconfigureRepositoryPrimaryBackend(id, dto) {
575
+ const { backend, configuration } = await this.backend.getBackend(dto.backendId);
576
+ const { repository: remote } = await backend.createRepository({
577
+ name: 'Restored Repository',
578
+ worm: false,
470
579
  });
471
- const repository = {
472
- ...(await this.getLocalRepository(id, { paths, retentionPolicy: restic_1.DEFAULT_RETENTION_POLICY })),
580
+ const endpoint = await backend.getResticEndpoint(remote.id);
581
+ const key = await this.config.deriveEncryptionKey(`repository-${remote.id}`);
582
+ await this.restic.init(endpoint, key);
583
+ await this.repository.update(id, {
584
+ remoteId: remote.id,
585
+ backendId: dto.backendId,
586
+ });
587
+ const { id: _, ...repository } = {
588
+ ...(await this.getLocalRepository(id)),
473
589
  ...remote,
474
590
  backends: {
475
591
  primary: {
476
- id: backendId,
592
+ id: dto.backendId,
477
593
  online: true,
478
594
  type: configuration.type,
479
595
  },
@@ -481,11 +597,15 @@ let RepositoryService = class RepositoryService {
481
597
  },
482
598
  };
483
599
  this.events.publish({
484
- type: 'RepositoryCreate',
600
+ type: 'RepositoryUpdate',
601
+ repositoryId: id,
485
602
  repository,
486
603
  });
487
604
  return {
488
- repository,
605
+ repository: {
606
+ id,
607
+ ...repository,
608
+ },
489
609
  };
490
610
  }
491
611
  async getSnapshots(id) {
@@ -496,7 +616,12 @@ let RepositoryService = class RepositoryService {
496
616
  };
497
617
  }
498
618
  async restoreSnapshot(id, snapshotId, dto) {
619
+ this.telemetry.submitStructuredLog('Running repository snapshot restore', {
620
+ repositoryId: id,
621
+ snapshotId,
622
+ });
499
623
  return new Promise((resolve) => {
624
+ let summary;
500
625
  const task = new Promise((complete, fail) => void this.runHistory.createLog(id, enum_1.TaskType.Restore, async (log, logId) => {
501
626
  resolve({
502
627
  task,
@@ -505,12 +630,18 @@ let RepositoryService = class RepositoryService {
505
630
  const { endpoint, key } = await this.getResticParameters(id);
506
631
  try {
507
632
  const signal = this.tasks.startTask(id, enum_1.TaskType.Restore, logId);
508
- await this.restic.restore(endpoint, key, snapshotId, dto, log, signal);
633
+ summary = await this.restic.restore(endpoint, key, snapshotId, dto, log, signal);
509
634
  }
510
635
  finally {
511
636
  this.tasks.endTask(id);
512
637
  }
513
638
  }, (error) => {
639
+ this.telemetry.submitStructuredLog('Finished repository snapshot restore', {
640
+ repositoryId: id,
641
+ snapshotId,
642
+ summary,
643
+ error,
644
+ });
514
645
  if (error) {
515
646
  fail(error);
516
647
  }
@@ -522,16 +653,21 @@ let RepositoryService = class RepositoryService {
522
653
  });
523
654
  }
524
655
  async restoreFromPoint(id, snapshotId, backendId, dto) {
656
+ this.telemetry.submitStructuredLog('Running repository restore from point', {
657
+ repositoryId: id,
658
+ snapshotId,
659
+ });
525
660
  return new Promise((resolve) => {
661
+ let summary;
526
662
  const task = new Promise((complete, fail) => void this.runHistory.createEphemeralLog(async (log, logId) => {
527
663
  resolve({
528
664
  task,
529
665
  logId,
530
666
  });
531
- const { endpoint, key } = await this.getResticParameters(id, backendId);
667
+ const { endpoint, key } = await this.getResticParameters({ backendId, remoteId: id });
532
668
  try {
533
669
  const signal = this.tasks.startTask(id, enum_1.TaskType.Restore, logId);
534
- await this.restic.restore(endpoint, key, snapshotId, { include: dto.include }, log, signal);
670
+ summary = await this.restic.restore(endpoint, key, snapshotId, { include: dto.include }, log, signal);
535
671
  if (dto.yuccaConfig) {
536
672
  const target = await this.storage.tempdir();
537
673
  await this.restic.restore(endpoint, key, snapshotId, { include: [dto.yuccaConfig], target }, log, signal);
@@ -549,6 +685,12 @@ let RepositoryService = class RepositoryService {
549
685
  this.tasks.endTask(id);
550
686
  }
551
687
  }, (error) => {
688
+ this.telemetry.submitStructuredLog('Finished repository restore from point', {
689
+ repositoryId: id,
690
+ snapshotId,
691
+ summary,
692
+ error,
693
+ });
552
694
  if (error) {
553
695
  fail(error);
554
696
  }
@@ -561,36 +703,66 @@ let RepositoryService = class RepositoryService {
561
703
  }
562
704
  async forgetSnapshot(id, snapshotId) {
563
705
  if (!this.tasks.canStart(id)) {
706
+ this.telemetry.submitStructuredLog('Repository snapshot forget rejected, task already running', {
707
+ repositoryId: id,
708
+ snapshotId,
709
+ });
564
710
  throw new common_1.BadRequestException('Task already running!');
565
711
  }
712
+ this.telemetry.submitStructuredLog('Running repository snapshot forget', {
713
+ repositoryId: id,
714
+ snapshotId,
715
+ });
566
716
  const { endpoint, key } = await this.getResticParameters(id);
717
+ let error;
567
718
  try {
568
719
  const signal = this.tasks.startTask(id, enum_1.TaskType.Forget);
569
720
  await this.restic.unlockAll(endpoint, key);
570
721
  await this.restic.forget(endpoint, key, snapshotId, true, signal);
571
722
  }
723
+ catch (error_) {
724
+ error = error_;
725
+ }
572
726
  finally {
573
727
  this.tasks.endTask(id);
574
728
  }
729
+ this.telemetry.submitStructuredLog('Finished repository snapshot forget', {
730
+ repositoryId: id,
731
+ snapshotId,
732
+ error,
733
+ });
734
+ if (error) {
735
+ throw error;
736
+ }
575
737
  await this.updateLocalMetrics(id, {
576
738
  resticParameters: { endpoint, key },
577
739
  });
578
740
  }
579
741
  async getSnapshotListing(id, snapshotId, dto) {
580
742
  const path = dto.path ?? '/';
581
- const { endpoint, key } = await this.getResticParameters(id);
582
- const files = await this.restic.ls(endpoint, key, snapshotId, path);
583
- return {
584
- parent: (0, node_path_1.dirname)(path),
585
- path,
586
- items: files
587
- .filter((file) => file.message_type === 'node')
588
- .filter((file) => file.path !== path)
589
- .map((file) => ({
590
- path: file.path,
591
- isDirectory: file.type === 'dir',
592
- })),
593
- };
743
+ try {
744
+ const { endpoint, key } = await this.getResticParameters(id);
745
+ const files = await this.restic.ls(endpoint, key, snapshotId, path);
746
+ return {
747
+ parent: (0, node_path_1.dirname)(path),
748
+ path,
749
+ items: files
750
+ .filter((file) => file.message_type === 'node')
751
+ .filter((file) => file.path !== path)
752
+ .map((file) => ({
753
+ path: file.path,
754
+ isDirectory: file.type === 'dir',
755
+ })),
756
+ };
757
+ }
758
+ catch (error) {
759
+ this.telemetry.submitStructuredLog('Failed to get repository snapshot listing', {
760
+ repositoryId: id,
761
+ snapshotId,
762
+ error,
763
+ });
764
+ throw error;
765
+ }
594
766
  }
595
767
  async getRunHistory(id) {
596
768
  const runs = await this.runHistory.getAll(id);
@@ -618,6 +790,7 @@ exports.RepositoryService = RepositoryService = __decorate([
618
790
  repositoryLocalMetrics_repository_1.RepositoryLocalMetricsRepository,
619
791
  moduleConfig_repository_1.ModuleConfigRepository,
620
792
  storage_repository_1.StorageRepository,
621
- bootstrap_service_1.BootstrapService])
793
+ bootstrap_service_1.BootstrapService,
794
+ telemetry_service_1.TelemetryService])
622
795
  ], RepositoryService);
623
796
  //# sourceMappingURL=repository.service.js.map