@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.
- package/dist/backends/backend.d.ts +2 -2
- package/dist/backends/backend.js +2 -5
- package/dist/backends/backend.js.map +1 -1
- package/dist/backends/local.backend.d.ts +1 -0
- package/dist/backends/local.backend.js +3 -0
- package/dist/backends/local.backend.js.map +1 -1
- package/dist/backends/s3.backend.d.ts +1 -0
- package/dist/backends/s3.backend.js +3 -0
- package/dist/backends/s3.backend.js.map +1 -1
- package/dist/backends/yucca.backend.d.ts +19 -3
- package/dist/backends/yucca.backend.js +47 -22
- package/dist/backends/yucca.backend.js.map +1 -1
- package/dist/const.d.ts +2 -1
- package/dist/const.js +3 -2
- package/dist/const.js.map +1 -1
- package/dist/controllers/onboarding.controller.d.ts +2 -0
- package/dist/controllers/onboarding.controller.js +18 -0
- package/dist/controllers/onboarding.controller.js.map +1 -1
- package/dist/controllers/repository.controller.d.ts +3 -2
- package/dist/controllers/repository.controller.js +17 -3
- package/dist/controllers/repository.controller.js.map +1 -1
- package/dist/dto/backend.dto.d.ts +1 -0
- package/dist/dto/backend.dto.js +5 -0
- package/dist/dto/backend.dto.js.map +1 -1
- package/dist/dto/onboarding.dto.d.ts +4 -0
- package/dist/dto/onboarding.dto.js +16 -0
- package/dist/dto/onboarding.dto.js.map +1 -1
- package/dist/dto/repository.dto.d.ts +9 -0
- package/dist/dto/repository.dto.js +33 -1
- package/dist/dto/repository.dto.js.map +1 -1
- package/dist/enum.d.ts +10 -0
- package/dist/enum.js +13 -1
- package/dist/enum.js.map +1 -1
- package/dist/interceptors/telemetry-error.interceptor.d.ts +8 -0
- package/dist/interceptors/telemetry-error.interceptor.js +47 -0
- package/dist/interceptors/telemetry-error.interceptor.js.map +1 -0
- package/dist/moduleConfig.d.ts +1 -1
- package/dist/orchestrationApi.module.d.ts +40 -1
- package/dist/orchestrationApi.module.js +18 -9
- package/dist/orchestrationApi.module.js.map +1 -1
- package/dist/repositories/backend.repository.d.ts +2 -0
- package/dist/repositories/backend.repository.js +8 -3
- package/dist/repositories/backend.repository.js.map +1 -1
- package/dist/repositories/bootstrap.repository.d.ts +9 -0
- package/dist/repositories/bootstrap.repository.js +33 -0
- package/dist/repositories/bootstrap.repository.js.map +1 -0
- package/dist/repositories/config.repository.d.ts +2 -0
- package/dist/repositories/config.repository.js +6 -0
- package/dist/repositories/config.repository.js.map +1 -1
- package/dist/repositories/repository.repository.d.ts +2 -1
- package/dist/repositories/repository.repository.js +9 -9
- package/dist/repositories/repository.repository.js.map +1 -1
- package/dist/repositories/restic.repository.d.ts +32 -3
- package/dist/repositories/restic.repository.js +3 -7
- package/dist/repositories/restic.repository.js.map +1 -1
- package/dist/repositories/runHistory.repository.d.ts +2 -2
- package/dist/repositories/schedule.repository.d.ts +2 -2
- package/dist/schema/migrations/20260603120000-AddRepositoryRemoteId.js +13 -0
- package/dist/schema/migrations/20260603120000-AddRepositoryRemoteId.js.map +1 -0
- package/dist/schema/tables/backend.table.d.ts +1 -0
- package/dist/schema/tables/backend.table.js.map +1 -1
- package/dist/schema/tables/repository.table.d.ts +1 -0
- package/dist/schema/tables/repository.table.js +1 -0
- package/dist/schema/tables/repository.table.js.map +1 -1
- package/dist/services/auth.service.d.ts +3 -1
- package/dist/services/auth.service.js +20 -7
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/backend.service.d.ts +3 -3
- package/dist/services/backend.service.js +18 -8
- package/dist/services/backend.service.js.map +1 -1
- package/dist/services/bootstrap.service.d.ts +3 -1
- package/dist/services/bootstrap.service.js +16 -6
- package/dist/services/bootstrap.service.js.map +1 -1
- package/dist/services/integrations.service.d.ts +3 -1
- package/dist/services/integrations.service.js +10 -2
- package/dist/services/integrations.service.js.map +1 -1
- package/dist/services/onboarding.service.d.ts +7 -1
- package/dist/services/onboarding.service.js +37 -2
- package/dist/services/onboarding.service.js.map +1 -1
- package/dist/services/repository.service.d.ts +6 -3
- package/dist/services/repository.service.js +252 -79
- package/dist/services/repository.service.js.map +1 -1
- package/dist/services/schedule.service.d.ts +4 -1
- package/dist/services/schedule.service.js +38 -2
- package/dist/services/schedule.service.js.map +1 -1
- package/dist/services/telemetry.service.d.ts +9 -0
- package/dist/services/telemetry.service.js +59 -0
- package/dist/services/telemetry.service.js.map +1 -0
- package/package.json +6 -4
- package/dist/schema/migrations/20260512120000-AddRepositoryRetentionPreset.js +0 -18
- package/dist/schema/migrations/20260512120000-AddRepositoryRetentionPreset.js.map +0 -1
- package/dist/services/database.service.d.ts +0 -11
- package/dist/services/database.service.js +0 -39
- package/dist/services/database.service.js.map +0 -1
- /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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
96
|
+
await this.repositoryPath.create({ id, path });
|
|
93
97
|
}
|
|
94
98
|
const repository = {
|
|
95
|
-
...(await this.getLocalRepository(
|
|
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,
|
|
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][
|
|
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][
|
|
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
|
|
196
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
252
|
+
({ repository: remote } = await backend.updateRepository(remoteId, dto));
|
|
234
253
|
}
|
|
235
254
|
else {
|
|
236
|
-
({ repository: remote } = await
|
|
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:
|
|
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(
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
295
|
-
const
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
422
|
-
|
|
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(
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
|
472
|
-
|
|
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: '
|
|
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(
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|