-
Notifications
You must be signed in to change notification settings - Fork 0
/
backup.ts
474 lines (422 loc) · 21.9 KB
/
backup.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
import * as path from 'path';
import Log from './logger';
import * as common from './common';
import * as btrfs from './btrfs';
import * as influxdb from './influxdb';
// Do not create incremental backup if last full was less than 10Mb
const minIncrementalSize = 1024 * 1024 * 10;
const fileDeleteCommand = "rm -v '{FILE}'";
export async function runSnapshotRequest(log:Log, item:SnapshotRequest) : Promise<void>
{
log = log.child( `snap-${item.name}` );
const name = item.name;
const dir = item.snapshotsDir;
log.log( 'Create snapshot' );
const { path: dstPath, tag: timestampTag } = await btrfs.snapshotCreate({ log: log, name: name, srcSubvolume: item.subvolume, dstDirectory: dir });
if (item.metric != null) {
await sendMetrics({
log,
subvolSize: { name: item.name, isContainer: item.metric.isContainer, path: dstPath, timestampTag },
});
}
if( item.snapshotsRotation != null )
{
log.log( 'Run rotation' );
await item.snapshotsRotation({ log:log.child('rot'), name:name, dir:dir });
}
log.log( 'Snapshot terminated' );
}
export async function runSendRequest(log:Log, item:SendRequest) : Promise<void>
{
log = log.child( `send-${item.name}` );
const [ srcs, dsts ] = await Promise.all([
btrfs.listSnapshots({ log:log.child('listsrcs'), name:item.name, dir:item.srcDir }),
btrfs.listSnapshots({ log:log.child('listdsts'), name:item.name, dir:item.dstDir }) ]);
if( srcs.list.length == 0 )
throw `There is no snapshot available for '${item.name}'`;
const srcsLast = srcs.last!;
if ((dsts.last != null) && (srcsLast.tag == dsts.last.tag)) {
log.log(`Nothing to do: last snapshot '${srcsLast.subvolumeName}' has already been sent to '${item.dstDir}'`);
return;
}
let parent : btrfs.SnapshotEntry|undefined = undefined;
if( dsts.last == null )
{
log.log( `No previous snapshots present in '${item.dstDir}' => Sending the full subvolume` );
}
else
{
parent = srcs.list.find( (e)=>(e.tag == dsts.last!.tag) );
if( parent == null )
log.log( `*** WARNING *** Could not find parent snapshot '${dsts.last.subvolumeName}' in source directory '${item.srcDir}' => Sending the full subvolume` );
else
log.log( `Using parent subvolume '${parent.subvolumeName}'` );
}
const { destinationSubvolume } = await btrfs.send({ log: log, snapshot: srcsLast, parent: parent, destinationDir: item.dstDir });
if( item.srcRemove === true )
{
log.log('Remove obsolete source snapshots');
const obsoletes = srcs.list.slice( 0, srcs.list.length-1 ); // Remove all except the last one (which have just been backuped)
await Promise.all( obsoletes.map(e=>btrfs.snapshotDelete({ log : log.child('del.'+e.subvolumeName),
subvolume : e.subvolumeName,
dir : e.containerDir })) );
}
if (item.metric != null) {
await sendMetrics({
log,
subvolSize: { name: item.name, isContainer: item.metric.isContainer, path: destinationSubvolume, timestampTag: srcsLast.tag },
});
}
if( item.dstRotation != null )
{
log.log( 'Run destination snapshots rotation' );
await item.dstRotation({ log:log.child('dstrot'), name:item.name, dir:item.dstDir });
}
}
export async function runBackupRequest(log:Log, item:BackupRequest) : Promise<void>
{
log = log.child( `bkp-${item.name}` );
const [ backups, snapshots ] = await Promise.all([
btrfs.listBackups({ log:log.child('listbkp'), name:item.name, dir:item.destinationBackupsDir }),
btrfs.listSnapshots({ log:log.child('listsnaps'), name:item.name, dir:item.sourceSnapshotsDir, remoteServer:item.sourceSnapshotServer }) ]);
if( snapshots.first == null )
throw "There is no snapshot available for '"+item.name+"'";
const lastSnapshot = snapshots.last!; // Assume not null: if there's a first, there is a last
log.log( `Backups state: lastFull.size: ${common.humanFileSize(backups.lastFull?.size)??'<null>'} ; last.sizeCumulated: ${common.humanFileSize(backups.last?.sizeCumulated)} ; ratio: ${backups.last?.sizeCumulated / (backups.lastFull?.size??backups.last?.sizeCumulated)}` );
let parentSnapshot : btrfs.SnapshotEntry|undefined;
if( backups.last == null )
{
log.log( 'Create a full backup: no backups available yet' );
parentSnapshot = undefined;
}
else
{
if( lastSnapshot.tag == backups.last.tag )
{
log.log( "Nothing to do: last snapshot '"+lastSnapshot.subvolumeName+"' has already been backuped into '"+backups.last.backupName+"'" );
return;
}
parentSnapshot = snapshots.list.find( (e)=>(e.tag == backups.last.tag) );
if( backups.lastFull == null )
{
log.log( "Create a full backup: ERROR: could not find previous last full backup" );
parentSnapshot = undefined;
}
else if( parentSnapshot == null )
{
log.log( "Create a full backup: Could not find snapshot of last backup '"+backups.last.backupName+"'" );
}
else if( backups.lastFull.size < minIncrementalSize )
{
log.log( `Create a full backup: Last full backup size '${common.humanFileSize(backups.lastFull.size)}' was below minimum of '${common.humanFileSize(minIncrementalSize)}'` );
parentSnapshot = undefined;
}
else if( (item.fullThreshold != null)
&& (backups.last.sizeCumulated != null)
&& ((backups.last.sizeCumulated / backups.lastFull.size) > item.fullThreshold) )
{
// Cumulated size of the snapshots has reach threshold
log.log( `Create a full backup: Full backup size is '${common.humanFileSize(backups.lastFull.size)}' and cumulated snapshots size is '${common.humanFileSize(backups.last.sizeCumulated)}'` );
parentSnapshot = undefined;
}
else if( (item.fullMaxAgeDays != null)
&& (backups.lastFull.diffDays >= item.fullMaxAgeDays) )
{
log.log( `Create a full backup: Last full backup too old (${backups.lastFull.tag})` );
parentSnapshot = undefined;
}
else
{
log.log( "Create an incremental backup" );
}
}
let destinationSnapshotDir : string|undefined;
if( item.destinationSnapshot != null )
destinationSnapshotDir = item.destinationSnapshot.dir;
else
destinationSnapshotDir = undefined;
await btrfs.backupCreate({ log:log, snapshot:lastSnapshot, parent:parentSnapshot, subvolumeDestinationDir:destinationSnapshotDir, backupDestinationDir:item.destinationBackupsDir });
if (item.metric != null) {
await sendMetrics({
log,
backupSize: { name: item.name, isContainer: item.metric.isContainer, backupsDir: item.destinationBackupsDir },
});
}
if( item.sourceSnapshotsRemove )
{
log.log( 'Remove obsolete snapshots' );
if( item.sourceSnapshotServer != null )
throw 'NYI: Remove obsolete remote snapshots is not yet implemented!';
const obsoletes = snapshots.list.slice( 0, snapshots.list.length-1 ); // Remove all except the last one (which have just been backuped)
await Promise.all( obsoletes.map(e=>btrfs.snapshotDelete({ log : log.child('del.'+e.subvolumeName),
subvolume : e.subvolumeName,
dir : e.containerDir })) );
}
if( item.backupRotation != null )
{
log.log( 'Run backups rotation' );
await item.backupRotation({ log:log.child('rot'), name:item.name, dir:item.destinationBackupsDir });
}
if( (item.destinationSnapshot != null) && (item.destinationSnapshot.rotation != null) )
{
log.log( 'Run destination snapshots rotation' );
await item.destinationSnapshot.rotation({ log:log.child('rot'), name:item.name, dir:item.destinationSnapshot.dir });
}
}
async function sendMetrics({ log, subvolSize, backupSize }: {
log: Log,
subvolSize?: { name: string, isContainer: boolean, path: string, timestampTag?: string },
backupSize?: { name: string, isContainer: boolean, backupsDir: string },
}) {
log = log.child('sendmetrics');
log.log('Start');
const influxItems = [] as influxdb.Item[];
if (subvolSize != null) {
const value = await btrfs.dirSize({ log, dir: subvolSize.path });
influxItems.push(influxdb.createItem({
metric: influxdb.metrics.subvolume._,
timestamp: influxdb.createTimeStampFromTag(),
tags: {
name: subvolSize.name,
[influxdb.metrics.subvolume.isContainer]: `${subvolSize.isContainer}`,
},
values: { [influxdb.metrics.subvolume.size]: value },
}));
}
if (backupSize != null) {
const list = await btrfs.listBackups({ log: log.child('listbkp'), name: backupSize.name, dir: backupSize.backupsDir });
if (list.list.length == 0) {
log.log(`No backup found`);
} else {
const entry = list.last;
influxItems.push(influxdb.createItem({
metric: influxdb.metrics.subvolume._,
timestamp: influxdb.createTimeStampFromTag(entry.tag),
tags: {
name: backupSize.name,
[influxdb.metrics.subvolume.isContainer]: `${backupSize.isContainer}`,
[influxdb.metrics.subvolume.isFullBackup]: `${entry.parent == null}`,
},
values: {
[influxdb.metrics.subvolume.backupSize]: entry.size,
[influxdb.metrics.subvolume.backupSizeCumulated]: entry.sizeCumulated,
},
}));
}
}
const sendTasks = [] as Promise<void>[];
if (influxItems.length > 0) {
log.log('Send to InfluxDB');
sendTasks.push(influxdb.send({ log: log.child('influx'), items: influxItems }));
}
await Promise.all(sendTasks);
log.log('End');
}
/** Keep only the last X snapshots */
export async function snapshotsRotation_keepOnlyLast1(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepOnlyLastX({ log:p.log, name:p.name, dir:p.dir, n:1 }); }
export async function snapshotsRotation_keepOnlyLast2(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepOnlyLastX({ log:p.log, name:p.name, dir:p.dir, n:2 }); }
export async function snapshotsRotation_keepOnlyLast3(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepOnlyLastX({ log:p.log, name:p.name, dir:p.dir, n:3 }); }
export async function snapshotsRotation_keepOnlyLast5(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepOnlyLastX({ log:p.log, name:p.name, dir:p.dir, n:5 }); }
export async function snapshotsRotation_keepOnlyLast10(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepOnlyLastX({ log:p.log, name:p.name, dir:p.dir, n:10 }); }
export async function snapshotsRotation_keepOnlyLast15(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepOnlyLastX({ log:p.log, name:p.name, dir:p.dir, n:15 }); }
export async function snapshotsRotation_keepOnlyLast30(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepOnlyLastX({ log:p.log, name:p.name, dir:p.dir, n:30 }); }
async function snapshotsRotation_keepOnlyLastX(p:{ log:Log, name:string, dir:string, n:number }) : Promise<void>
{
p.log.log( 'Get list of existing snapshots' );
const entries = await btrfs.listSnapshots({ log:p.log.child('list'), name:p.name, dir:p.dir });
if( entries.last == null )
{
p.log.log( 'Nothing found' );
return;
}
// nb: 'entries.list' sorted chronologically
const toDelete = entries.list.slice( 0, -p.n );
// NB: 20180716: Deleting them 1 by 1 ; I've seen a kernel panic when deleting them all at the same time ... o_0
for( let i=0; i<toDelete.length; ++i )
await btrfs.snapshotDelete({ log:p.log.child('delete'), dir:p.dir, subvolume:toDelete[i].subvolumeName });
}
/** Keep everything during X days */
export function snapshotsRotation_keep3Days(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nDays:3 }); }
export function snapshotsRotation_keep5Days(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nDays:5 }); }
export function snapshotsRotation_keep1Week(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nDays:7 }); }
export function snapshotsRotation_keep2Week(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nDays:14 }); }
export function snapshotsRotation_keep3Week(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nDays:21 }); }
export function snapshotsRotation_keep1Month(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nMonths:1 }); }
export function snapshotsRotation_keep2Months(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nMonths:2 }); }
export function snapshotsRotation_keep3Months(p:{ log:Log, name:string, dir:string }) : Promise<void> { return snapshotsRotation_keepNXXX({ log:p.log, name:p.name, dir:p.dir, nMonths:3 }); }
async function snapshotsRotation_keepNXXX(p:{ log:Log, name:string, dir:string, nDays?:number, nMonths?:number }) : Promise<void>
{
p.log.log( 'Get list of existing snapshots' );
const entries = await btrfs.listSnapshots({ log:p.log.child('list'), name:p.name, dir:p.dir });
const toDelete : btrfs.SnapshotEntry[] = [];
entries.list.forEach( (entry, i)=>
{
let remove = true;
if( entry.tag == entries.last!.tag )
// Always keep the last one (should not be needed, but there for safety ...)
remove = false;
if( p.nDays != null )
{
if( entry.diffDays <= p.nDays )
remove = false;
}
else if( p.nMonths != null )
{
if( entry.diffMonths <= p.nMonths )
remove = false;
}
else
{
throw "snapshotsRotation_keepNDays: Parameter 'nDays' or 'nMonths' must be specified";
}
if(! remove )
p.log.log( 'Leave', entry.subvolumeName );
else
toDelete.push( entry );
} );
// NB: 20180716: Deleting them 1 by 1 ; I've seen a kernel panic when deleting them all at the same time ... o_0
for( let i=0; i<toDelete.length; ++i )
await btrfs.snapshotDelete({ log:p.log.child('delete'), dir:p.dir, subvolume:toDelete[i].subvolumeName });
}
/** Keep everything from the last 2 months then 1 per month
* TODO: Keep only 1 per year after 1 year */
export async function snapshotsRotation_timeMachine(p:{ log:Log, name:string, dir:string }) : Promise<void>// SnapshotsRotation = async function(p)
{
p.log.log( 'Get list of existing snapshots' );
const entries = await btrfs.listSnapshots({ log:p.log.child('list'), name:p.name, dir:p.dir });
const toDelete : btrfs.SnapshotEntry[] = [];
entries.list.forEach( (entry, i)=>
{
let remove = true;
if( entry.tag == entries.last!.tag )
// Always keep the last one (should not be needed, but there for safety ...)
remove = false;
if( entry.firstOfMonth )
remove = false;
if( entry.diffMonths < 2 )
remove = false;
if(! remove )
p.log.log( 'Leave', entry.subvolumeName );
else
toDelete.push( entry );
} );
// NB: 20180716: Deleting them 1 by 1 ; I've seen a kernel panic when deleting them all at the same time ... o_0
for( let i=0; i<toDelete.length; ++i )
await btrfs.snapshotDelete({ log:p.log.child('delete'), dir:p.dir, subvolume:toDelete[i].subvolumeName });
}
/** Keep N full backups and all their related incremental backups */
export async function backupsRotation_keep1Full(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:1 }); }
export async function backupsRotation_keep2Fulls(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:2 }); }
export async function backupsRotation_keep3Fulls(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:3 }); }
export async function backupsRotation_keep4Fulls(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:4 }); }
export async function backupsRotation_keep5Fulls(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:5 }); }
export async function backupsRotation_keep10Fulls(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:10 }); }
export async function backupsRotation_keep15Fulls(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:15 }); }
export async function backupsRotation_keep30Fulls(p:{ log:Log, name:string, dir:string }) : Promise<void> { await backupsRotation_keepNFulls({ log:p.log, name:p.name, dir:p.dir, nToKeep:30 }); }
async function backupsRotation_keepNFulls(p:{ log:Log, name:string, dir:string, nToKeep:number, debug?:boolean }) : Promise<void>
{
p.log.log( 'Get list of existing backups' );
const entries = await btrfs.listBackups({ log:p.log.child('list'), name:p.name, dir:p.dir });
let promises : Promise<{stdout:string,stderr:string}>[] = [];
entries.list.forEach( (entry, i)=>
{
let remove = false;
if( entry.fullNumber > p.nToKeep )
remove = true;
if(! remove )
p.log.log( 'Leave', entry.backupName );
else if( p.debug )
p.log.log( 'Would delete', entry.backupName );
else
promises.push(
common.run({ log:p.log.child('run'), command:fileDeleteCommand, 'FILE':path.join(entry.containerDir, entry.backupName) })
);
} );
await Promise.all( promises );
}
export function backupsRotation_keepAtLeast3Days(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, daysToKeep:3 }); }
export function backupsRotation_keepAtLeast7Days(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, daysToKeep:7 }); }
export function backupsRotation_keepAtLeast14Days(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, daysToKeep:14 }); }
export function backupsRotation_keepAtLeast21Days(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, daysToKeep:21 }); }
export function backupsRotation_keepAtLeast1Month(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, monthsToKeep:1 }); }
export function backupsRotation_keepAtLeast2Months(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, monthsToKeep:2 }); }
export function backupsRotation_keepAtLeast3Months(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, monthsToKeep:3 }); }
export function backupsRotation_keepAtLeast6Months(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, monthsToKeep:6 }); }
export function backupsRotation_keepAtLeast1Year(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, monthsToKeep:12 }); }
export function backupsRotation_keepAtLeast2Year(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, monthsToKeep:24 }); }
export function backupsRotation_keepAtLeast3Year(p:{ log:Log, name:string, dir:string }) : Promise<void> { return backupsRotation_keepAtLeastNDays({ log:p.log, name:p.name, dir:p.dir, monthsToKeep:36 }); }
async function backupsRotation_keepAtLeastNDays(p:{ log:Log, name:string, dir:string, daysToKeep?:number, monthsToKeep?:number, debug?:boolean }) : Promise<void>
{
p.log.log( 'Get list of existing backups' );
const entries = await btrfs.listBackups({ log:p.log.child('list'), name:p.name, dir:p.dir });
// List of all entry's tags to keep
const toKeep : {[key:string]:boolean} = {};
// Recursive function that add's specified entry and it's parents to 'toKeep'
function keepWithParents(entry:btrfs.BackupEntry) : void
{
toKeep[ entry.tag ] = true;
if( entry.parent != null )
keepWithParents( entry.parent );
};
// Flag all tags that must be kept
entries.list.forEach( (entry)=>
{
if( (p.daysToKeep != null) && (entry.diffDays <= p.daysToKeep) )
// This entry (and all it's parents) must be kept
keepWithParents( entry );
else if( (p.monthsToKeep != null) && (entry.diffMonths < p.monthsToKeep) )
// This entry (and all it's parents) must be kept
keepWithParents( entry );
else
{/*Do not tag*/}
} );
// Actually perform rotation
const tasks = entries.list.map( async (entry)=>
{
if( toKeep[entry.tag] == true )
p.log.log( `Leave`, entry.backupName, `(diffDays:${entry.diffDays} ; diffMonths:${entry.diffMonths})` );
else if( p.debug )
p.log.log( `Would delete`, entry.backupName, `(diffDays:${entry.diffDays} ; diffMonths:${entry.diffMonths})` );
else
await common.run({ log:p.log.child('run'), command:fileDeleteCommand, 'FILE':path.join(entry.containerDir, entry.backupName) })
} );
await Promise.all( tasks );
}
export type SnapshotsRotation = (p:{ log:Log, name:string, dir:string })=>Promise<void>;
export type BackupsRotation = (p:{ log:Log, name:string, dir:string })=>Promise<void>;
export type Metric = {
isContainer: boolean,
}
export interface SnapshotRequest
{
name : string;
subvolume : string;
snapshotsDir : string;
snapshotsRotation? : SnapshotsRotation;
metric? : Metric;
}
export interface SendRequest
{
name : string;
srcDir : string;
dstDir : string;
srcRemove : boolean;
dstRotation? : SnapshotsRotation;
metric? : Metric;
}
export interface BackupRequest
{
name : string;
sourceSnapshotServer? : string;
sourceSnapshotsDir : string;
sourceSnapshotsRemove : boolean;
destinationBackupsDir : string;
destinationSnapshot? : {
dir : string;
rotation? : SnapshotsRotation;
};
fullThreshold? : number;
fullMaxAgeDays? : number;
metric? : Metric;
backupRotation? : BackupsRotation;
}