@@ -7,13 +7,12 @@ import refCache from "@cocalc/util/refcache";
7
7
import { exists , listdir , mkdirp , sudo } from "./util" ;
8
8
import { join , normalize } from "path" ;
9
9
import { updateRollingSnapshots , type SnapshotCounts } from "./snapshots" ;
10
- import { type DirectoryListingEntry } from "@cocalc/util/types" ;
11
- import getLogger from "@cocalc/backend/logger" ;
12
10
import { SubvolumeFilesystem } from "./subvolume-fs" ;
11
+ import { SubvolumeBup } from "./subvolume-bup" ;
12
+ import getLogger from "@cocalc/backend/logger" ;
13
13
14
14
export const SNAPSHOTS = ".snapshots" ;
15
15
const SEND_SNAPSHOT_PREFIX = "send-" ;
16
- const BUP_SNAPSHOT = "temp-bup-snapshot" ;
17
16
const PAD = 4 ;
18
17
19
18
const logger = getLogger ( "file-server:storage-btrfs:subvolume" ) ;
@@ -26,17 +25,19 @@ interface Options {
26
25
export class Subvolume {
27
26
public readonly name : string ;
28
27
29
- private filesystem : Filesystem ;
28
+ public readonly filesystem : Filesystem ;
30
29
public readonly path : string ;
31
30
public readonly snapshotsDir : string ;
32
31
public readonly fs : SubvolumeFilesystem ;
32
+ public readonly bup : SubvolumeBup ;
33
33
34
34
constructor ( { filesystem, name } : Options ) {
35
35
this . filesystem = filesystem ;
36
36
this . name = name ;
37
37
this . path = join ( filesystem . opts . mount , name ) ;
38
38
this . snapshotsDir = join ( this . path , SNAPSHOTS ) ;
39
39
this . fs = new SubvolumeFilesystem ( this ) ;
40
+ this . bup = new SubvolumeBup ( this ) ;
40
41
}
41
42
42
43
init = async ( ) => {
@@ -81,10 +82,6 @@ export class Subvolume {
81
82
return join ( this . path , normalize ( path ) ) ;
82
83
} ;
83
84
84
- /////////////
85
- // Files
86
- /////////////
87
-
88
85
/////////////
89
86
// QUOTA
90
87
/////////////
@@ -259,158 +256,6 @@ export class Subvolume {
259
256
return snapGen < pathGen ;
260
257
} ;
261
258
262
- /////////////
263
- // BACKUPS
264
- // There is a single global dedup'd backup archive stored in the btrfs filesystem.
265
- // Obviously, admins should rsync this regularly to a separate location as a genuine
266
- // backup strategy.
267
- /////////////
268
-
269
- // create a new bup backup
270
- createBupBackup = async ( {
271
- // timeout used for bup index and bup save commands
272
- timeout = 30 * 60 * 1000 ,
273
- } : { timeout ?: number } = { } ) => {
274
- if ( await this . snapshotExists ( BUP_SNAPSHOT ) ) {
275
- logger . debug ( `createBupBackup: deleting existing ${ BUP_SNAPSHOT } ` ) ;
276
- await this . deleteSnapshot ( BUP_SNAPSHOT ) ;
277
- }
278
- try {
279
- logger . debug (
280
- `createBupBackup: creating ${ BUP_SNAPSHOT } to get a consistent backup` ,
281
- ) ;
282
- await this . createSnapshot ( BUP_SNAPSHOT ) ;
283
- const target = join ( this . snapshotsDir , BUP_SNAPSHOT ) ;
284
- logger . debug ( `createBupBackup: indexing ${ BUP_SNAPSHOT } ` ) ;
285
- await sudo ( {
286
- command : "bup" ,
287
- args : [
288
- "-d" ,
289
- this . filesystem . bup ,
290
- "index" ,
291
- "--exclude" ,
292
- join ( target , ".snapshots" ) ,
293
- "-x" ,
294
- target ,
295
- ] ,
296
- timeout,
297
- } ) ;
298
- logger . debug ( `createBupBackup: saving ${ BUP_SNAPSHOT } ` ) ;
299
- await sudo ( {
300
- command : "bup" ,
301
- args : [
302
- "-d" ,
303
- this . filesystem . bup ,
304
- "save" ,
305
- "--strip" ,
306
- "-n" ,
307
- this . name ,
308
- target ,
309
- ] ,
310
- timeout,
311
- } ) ;
312
- } finally {
313
- logger . debug ( `createBupBackup: deleting temporary ${ BUP_SNAPSHOT } ` ) ;
314
- await this . deleteSnapshot ( BUP_SNAPSHOT ) ;
315
- }
316
- } ;
317
-
318
- bupBackups = async ( ) : Promise < string [ ] > => {
319
- const { stdout } = await sudo ( {
320
- command : "bup" ,
321
- args : [ "-d" , this . filesystem . bup , "ls" , this . name ] ,
322
- } ) ;
323
- return stdout
324
- . split ( "\n" )
325
- . map ( ( x ) => x . split ( " " ) . slice ( - 1 ) [ 0 ] )
326
- . filter ( ( x ) => x ) ;
327
- } ;
328
-
329
- bupRestore = async ( path : string ) => {
330
- // path -- branch/revision/path/to/dir
331
- if ( path . startsWith ( "/" ) ) {
332
- path = path . slice ( 1 ) ;
333
- }
334
- path = normalize ( path ) ;
335
- // ... but to avoid potential data loss, we make a snapshot before deleting it.
336
- await this . createSnapshot ( ) ;
337
- const i = path . indexOf ( "/" ) ; // remove the commit name
338
- await sudo ( {
339
- command : "rm" ,
340
- args : [ "-rf" , this . normalize ( path . slice ( i + 1 ) ) ] ,
341
- } ) ;
342
- await sudo ( {
343
- command : "bup" ,
344
- args : [
345
- "-d" ,
346
- this . filesystem . bup ,
347
- "restore" ,
348
- "-C" ,
349
- this . path ,
350
- join ( `/${ this . name } ` , path ) ,
351
- "--quiet" ,
352
- ] ,
353
- } ) ;
354
- } ;
355
-
356
- bupLs = async ( path : string ) : Promise < DirectoryListingEntry [ ] > => {
357
- path = normalize ( path ) ;
358
- const { stdout } = await sudo ( {
359
- command : "bup" ,
360
- args : [
361
- "-d" ,
362
- this . filesystem . bup ,
363
- "ls" ,
364
- "--almost-all" ,
365
- "--file-type" ,
366
- "-l" ,
367
- join ( `/${ this . name } ` , path ) ,
368
- ] ,
369
- } ) ;
370
- const v : DirectoryListingEntry [ ] = [ ] ;
371
- for ( const x of stdout . split ( "\n" ) ) {
372
- // [-rw-------","6b851643360e435eb87ef9a6ab64a8b1/6b851643360e435eb87ef9a6ab64a8b1","5","2025-07-15","06:12","a.txt"]
373
- const w = x . split ( / \s + / ) ;
374
- if ( w . length >= 6 ) {
375
- let isdir , name ;
376
- if ( w [ 5 ] . endsWith ( "@" ) || w [ 5 ] . endsWith ( "=" ) || w [ 5 ] . endsWith ( "|" ) ) {
377
- w [ 5 ] = w [ 5 ] . slice ( 0 , - 1 ) ;
378
- }
379
- if ( w [ 5 ] . endsWith ( "/" ) ) {
380
- isdir = true ;
381
- name = w [ 5 ] . slice ( 0 , - 1 ) ;
382
- } else {
383
- name = w [ 5 ] ;
384
- isdir = false ;
385
- }
386
- const size = parseInt ( w [ 2 ] ) ;
387
- const mtime = new Date ( w [ 3 ] + "T" + w [ 4 ] ) . valueOf ( ) / 1000 ;
388
- v . push ( { name, size, mtime, isdir } ) ;
389
- }
390
- }
391
- return v ;
392
- } ;
393
-
394
- bupPrune = async ( {
395
- dailies = "1w" ,
396
- monthlies = "4m" ,
397
- all = "3d" ,
398
- } : { dailies ?: string ; monthlies ?: string ; all ?: string } = { } ) => {
399
- await sudo ( {
400
- command : "bup" ,
401
- args : [
402
- "-d" ,
403
- this . filesystem . bup ,
404
- "prune-older" ,
405
- `--keep-dailies-for=${ dailies } ` ,
406
- `--keep-monthlies-for=${ monthlies } ` ,
407
- `--keep-all-for=${ all } ` ,
408
- "--unsafe" ,
409
- this . name ,
410
- ] ,
411
- } ) ;
412
- } ;
413
-
414
259
/////////////
415
260
// BTRFS send/recv
416
261
// Not used. Instead we will rely on bup (and snapshots of the underlying disk) for backups, since:
0 commit comments