bitferry 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3cf2cfbd34a736bdab0ca41d188a732b5e848be7a746ae9d351424e305ac59d
4
- data.tar.gz: 42c91f3371a2c61afdc813870550d19341b2b2e0e8096b5ec2b41adb05fd0670
3
+ metadata.gz: bb10ff66d68e1981c2011621d1d216cfe759e14378844f5101310b78e5938974
4
+ data.tar.gz: 0341d89784f9acaa607f9267ddcbd494901efa80ca9954d60d84484d177c63ff
5
5
  SHA512:
6
- metadata.gz: 3c4f17e69351732f951a411bfc2f62f5052838abfa81d79655e9f6e5b9ee7826dc98a0b40005437d613155071fb71d028dcdddfb7bdcf8e29bd3e6a0ce022bcc
7
- data.tar.gz: 64e48fa1d13e51f28c598ff57022d9d300b901a9db2b98af0b446bcfe540d6dd218e8da8d1b08a4ec7b2700af63b6e1d693d340685d49674f69b4004a30fd6d6
6
+ metadata.gz: 2ac8f19ed6ef8607082b8943d0cab8ae4fa973694bfac1ddc219fe34e2421c05116d9d4ea20213440b192a7a1eae50cc11a485f489976dd182ff5ea1dc5bf1d2
7
+ data.tar.gz: cf4dabac47d7b46ed13129b749d82c92cd216f91b3fb41c6a8d2db9114738ae875f38db99d0112d26a7176db639d5cba68ebdeaee8e9e6f61f7f21dc658de568
data/CHANGES.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.0.5
2
+
3
+ - Disable cache usage in Restic check profiles
4
+ - Show stale tasks in verbose mode only
5
+ - Pick the innermost Bitferry volume in case of volumes nesting
6
+ - Fix an options profile application bug
7
+
8
+ ## 0.0.4
9
+
10
+ - Include/exclude path filters
11
+ - Include user's home in the Bitferry volume lookup list
12
+
1
13
  ## 0.0.3
2
14
 
3
15
  - Windows bundle
data/README.md CHANGED
@@ -6,7 +6,7 @@ The [Bitferry](https://github.com/okhlybov/bitferry) is aimed at establishing th
6
6
 
7
7
  The intended usage ranges from maintaining simple directory copy to another location (disk, mount point) to complex many-to-many (online/offline) data replication/backup solution employing portable media as additional data storage and a means of data propagation between the offsites.
8
8
 
9
- The core idea that drives Bitferry is the conversion of full (absolute) endpoint's paths into the volume-relative ones, where the volume is a data file which is put along the endpoint's data and denotes the root of the directory hierarchy. This leads to the important location independence property meaning that Bitferry is then able to restore the tasks' source-destination endpoint connections in spite of the volume location changes, which is a likely scenario in case of portable storage (different UNIX mount points, Windows drives etc.).
9
+ The core idea that drives Bitferry is the conversion of full (absolute) endpoints' paths into the volume-relative ones, where the volume is a data file which is put along the endpoint's data and denotes the root of the directory hierarchy. This makes data position-independent which means that Bitferry is then able to restore the tasks' source-destination endpoint connections in spite of the volume location changes, which is a likely scenario in case of portable storage (different UNIX mount points, Windows drives etc.).
10
10
 
11
11
  Bitferry is effectively a frontend to the [Rclone](https://rclone.org) and [Restic](https://restic.net) utilities.
12
12
 
@@ -211,7 +211,7 @@ Parameters:
211
211
  Options:
212
212
  -e Encrypt files in destination using default profile (alias for -E default)
213
213
  -d Decrypt source files using default profile (alias for -D default)
214
- -x Use extended encryption profile options (applies to -e, -d)
214
+ -u Use extended encryption profile options (applies to -e, -d)
215
215
  --process, -X OPTIONS Extra task processing profile/options
216
216
  --encrypt, -E OPTIONS Encrypt files in destination using specified profile/options
217
217
  --decrypt, -D OPTIONS Decrypt source files using specified profile/options
data/lib/bitferry/cli.rb CHANGED
@@ -22,6 +22,31 @@ Encryption = %{
22
22
  }
23
23
 
24
24
 
25
+ $process = nil
26
+ $encryption = nil
27
+ $include = []
28
+ $exclude = []
29
+
30
+
31
+ def ext_globs(exts) = exts.split(',').collect { |ext| "*.#{ext}" }
32
+
33
+
34
+ def setup_task(x, include: true)
35
+ x.option ['-i'], 'EXTS', 'Include file extensions (comma-separated list)', multivalued: true, attribute_name: :include_exts do |exts|
36
+ $include << ext_globs(exts)
37
+ end if include
38
+ x.option ['-x'], 'EXTS', 'Exclude file extensions (comma-separated list)', multivalued: true, attribute_name: :exclude_exts do |exts|
39
+ $exclude << ext_globs(exts)
40
+ end
41
+ x.option ['--include'], 'GLOBS', 'Include path specifications (comma-separated list)', multivalued: true, attribute_name: :include do |globs|
42
+ $include << globs.split(',')
43
+ end if include
44
+ x.option ['--exclude'], 'GLOBS', 'Exclude path specifications (comma-separated list)', multivalued: true, attribute_name: :exclude do |globs|
45
+ $exclude << globs.split(',')
46
+ end
47
+ end
48
+
49
+
25
50
  def setup_rclone_task(x)
26
51
  x.parameter 'SOURCE', 'Source endpoint specifier'
27
52
  x.parameter 'DESTINATION', 'Destination endpoint specifier'
@@ -33,7 +58,7 @@ def setup_rclone_task(x)
33
58
  $encryption = Bitferry::Rclone::Decrypt
34
59
  $profile = :default
35
60
  end
36
- x.option '-x', :flag, 'Use extended encryption profile options (applies to -e, -d)', attribute_name: :x do
61
+ x.option '-u', :flag, 'Apply extended (unicode) encryption profile options (alias for -E extended / -D extended)', attribute_name: :u do
37
62
  $extended = true
38
63
  end
39
64
  x.option ['--process', '-X'], 'OPTIONS', 'Extra task processing profile/options' do |opts|
@@ -47,6 +72,7 @@ def setup_rclone_task(x)
47
72
  $encryption = Bitferry::Rclone::Decrypt
48
73
  $profile = opts
49
74
  end
75
+ setup_task(x)
50
76
  end
51
77
 
52
78
 
@@ -54,6 +80,7 @@ def create_rclone_task(task, *args, **opts)
54
80
  task.new(*args,
55
81
  process: $process,
56
82
  encryption: $encryption&.new(obtain_password, process: $extended ? :extended : $profile),
83
+ include: $include.flatten.uniq, exclude: $exclude.flatten.uniq,
57
84
  **opts
58
85
  )
59
86
  end
@@ -131,7 +158,7 @@ Clamp do
131
158
  puts " #{task.tag} #{task.show_status}"
132
159
  end
133
160
  end
134
- unless (xs = Bitferry::Task.stale).empty?
161
+ if !(xs = Bitferry::Task.stale).empty? && Bitferry.verbosity == :verbose
135
162
  puts
136
163
  puts '# Stale tasks'
137
164
  puts
@@ -251,13 +278,14 @@ Clamp do
251
278
  }
252
279
  option '--force', :flag, 'Force overwriting existing repository' do $format = true end
253
280
  option ['--attach', '-a'], :flag, 'Attach to existing repository' do $format = false end
254
- option '-f', :flag, 'Rig for application of the snapshot retention policy (alias for -F default)', attribute_name: :f do $forget = :default end
255
- option '-c', :flag, 'Rig for repository checking (alias for -C default)', attribute_name: :c do $check = :default end
281
+ option '-f', :flag, 'Apply default snapshot retention policy options (alias for -F default)', attribute_name: :f do $forget = :default end
282
+ option '-c', :flag, 'Apply default repository checking options (alias for -C default)', attribute_name: :c do $check = :default end
256
283
  option ['--process', '-X'], 'OPTIONS', 'Extra task processing profile/options' do |opts| $process = opts end
257
- option ['--forget', '-F'], 'OPTIONS', 'Rig for snapshot retention policy with profile/options' do |opts| $forget = opts end
258
- option ['--check', '-C'], 'OPTIONS', 'Rig for repository checking with profile/options' do |opts| $check = opts end
284
+ option ['--forget', '-F'], 'OPTIONS', 'Snapshot retention policy with profile/options' do |opts| $forget = opts end
285
+ option ['--check', '-C'], 'OPTIONS', 'Repository checking with profile/options' do |opts| $check = opts end
259
286
  parameter 'SOURCE', 'Source endpoint specifier'
260
287
  parameter 'REPOSITORY', 'Destination repository endpoint specifier'
288
+ setup_task(self, include: false)
261
289
  def execute
262
290
  bitferry {
263
291
  Bitferry::Restic::Backup.new(
@@ -265,7 +293,8 @@ Clamp do
265
293
  format: $format,
266
294
  process: $process,
267
295
  check: $check,
268
- forget: $forget
296
+ forget: $forget,
297
+ exclude: $exclude.flatten.uniq
269
298
  )
270
299
  }
271
300
  end
@@ -280,11 +309,13 @@ Clamp do
280
309
  option ['--process', '-X'], 'OPTIONS', 'Extra task processing profile/options' do |opts| $process = opts end
281
310
  parameter 'REPOSITORY', 'Source repository endpoint specifier'
282
311
  parameter 'DESTINATION', 'Destination endpoint specifier'
312
+ setup_task(self)
283
313
  def execute
284
314
  bitferry {
285
315
  Bitferry::Restic::Restore.new(
286
316
  destination, repository, obtain_password,
287
317
  process: $process,
318
+ include: $include.flatten.uniq, exclude: $exclude.flatten.uniq
288
319
  )
289
320
  }
290
321
  end
data/lib/bitferry.rb CHANGED
@@ -12,7 +12,7 @@ require 'shellwords'
12
12
  module Bitferry
13
13
 
14
14
 
15
- VERSION = '0.0.3'
15
+ VERSION = '0.0.5'
16
16
 
17
17
 
18
18
  module Logging
@@ -39,7 +39,7 @@ module Bitferry
39
39
  reset
40
40
  log.info('restoring volumes')
41
41
  result = true
42
- roots = (environment_mounts + system_mounts).uniq
42
+ roots = (environment_mounts + system_mounts + [Dir.home]).uniq
43
43
  log.info("distilled volume search path: #{roots.join(', ')}")
44
44
  roots.each do |root|
45
45
  if File.exist?(File.join(root, Volume::STORAGE))
@@ -341,12 +341,10 @@ module Bitferry
341
341
 
342
342
  def self.endpoint(root)
343
343
  path = Pathname.new(root).realdirpath
344
- # FIXME select innermost or outermost volume in case of nested volumes?
345
- intact.sort { |v1, v2| v2.root.size <=> v1.root.size }.each do |volume|
344
+ intact.sort { |v1, v2| v2.root.to_s.size <=> v1.root.to_s.size }.each do |volume|
346
345
  begin
347
- # FIXME chop trailing slashes
348
- stem = path.relative_path_from(volume.root)
349
- case stem.to_s
346
+ stem = path.relative_path_from(volume.root).to_s #.chomp('/')
347
+ case stem
350
348
  when '.' then return volume.endpoint
351
349
  when /^[^\.].*/ then return volume.endpoint(stem)
352
350
  end
@@ -477,7 +475,7 @@ module Bitferry
477
475
  when Array then option # Array is passed verbatim
478
476
  when '-' then nil # Disable adding any options with -
479
477
  when /^-/ then option.split(',') # Split comma-separated string into array --foo,bar --> [--foo, bar]
480
- else route.fetch(option) # Obtain array from the database
478
+ else route.fetch(option.nil? ? nil : option.to_sym) # Obtain options from the profile database
481
479
  end
482
480
  end
483
481
 
@@ -498,6 +496,9 @@ module Bitferry
498
496
  attr_reader :modified
499
497
 
500
498
 
499
+ attr_reader :include, :exclude
500
+
501
+
501
502
  def process_options = @process_options.nil? ? [] : @process_options # As a mandatory option it should never be nil
502
503
 
503
504
 
@@ -530,10 +531,12 @@ module Bitferry
530
531
  end
531
532
 
532
533
 
533
- def initialize(tag: Bitferry.tag, modified: DateTime.now)
534
+ def initialize(tag: Bitferry.tag, modified: DateTime.now, include: [], exclude: [])
534
535
  @tag = tag
535
536
  @generation = 0
536
- @modified = modified
537
+ @include = include
538
+ @exclude = exclude
539
+ @modified = modified.is_a?(DateTime) ? modified : DateTime.parse(modified)
537
540
  # FIXME handle process_options at this level
538
541
  end
539
542
 
@@ -546,6 +549,8 @@ module Bitferry
546
549
 
547
550
 
548
551
  def restore(hash)
552
+ @include = hash.fetch(:include, [])
553
+ @exclude = hash.fetch(:exclude, [])
549
554
  @state = :intact
550
555
  log.info("restored task #{tag}")
551
556
  end
@@ -558,7 +563,9 @@ module Bitferry
558
563
  def externalize
559
564
  {
560
565
  task: tag,
561
- modified: @modified
566
+ modified: modified,
567
+ include: include.empty? ? nil : include,
568
+ exclude: exclude.empty? ? nil : exclude
562
569
  }.compact
563
570
  end
564
571
 
@@ -584,6 +591,14 @@ module Bitferry
584
591
  end
585
592
 
586
593
 
594
+ def show_filters
595
+ xs = []
596
+ xs << 'include: ' + include.join(',') unless include.empty?
597
+ xs << 'exclude: ' + exclude.join(',') unless exclude.empty?
598
+ xs.join(' ').to_s
599
+ end
600
+
601
+
587
602
  def self.[](tag) = @@registry[tag]
588
603
 
589
604
 
@@ -609,8 +624,16 @@ module Bitferry
609
624
  def self.reset = @@registry = {}
610
625
 
611
626
 
612
- def self.register(task) = @@registry[task.tag] = task # TODO settle on task with the latest timestamp
613
-
627
+ def self.register(task)
628
+ # Task with newer timestamp replaces already registered task, if any
629
+ if (xtag = @@registry[task.tag]).nil?
630
+ @@registry[task.tag] = task
631
+ elsif xtag.modified < task.modified
632
+ @@registry[task.tag] = task
633
+ else
634
+ xtag
635
+ end
636
+ end
614
637
 
615
638
  def self.intact = live.filter { |task| task.intact? }
616
639
 
@@ -795,7 +818,7 @@ module Bitferry
795
818
  end
796
819
 
797
820
 
798
- def show_status = "#{show_operation} #{source.show_status} #{show_direction} #{destination.show_status}"
821
+ def show_status = "#{show_operation} #{source.show_status} #{show_direction} #{destination.show_status} #{show_filters}"
799
822
 
800
823
 
801
824
  def show_operation = encryption.nil? ? '' : encryption.show_operation
@@ -833,8 +856,14 @@ module Bitferry
833
856
  end
834
857
 
835
858
 
859
+ def include_filters = include.collect { |x| ['--filter', "+ #{x}"]}.flatten
860
+
861
+
862
+ def exclude_filters = ([Volume::STORAGE, Volume::STORAGE_] + exclude).collect { |x| ['--filter', "- #{x}"]}.flatten
863
+
864
+
836
865
  def process_arguments
837
- ['--filter', "- #{Volume::STORAGE}", '--filter', "- #{Volume::STORAGE_}"] + common_options + process_options + (
866
+ include_filters + exclude_filters + common_options + process_options + (
838
867
  encryption.nil? ? [source.root.to_s, destination.root.to_s] : encryption.arguments(self)
839
868
  )
840
869
  end
@@ -846,7 +875,7 @@ module Bitferry
846
875
  puts cms if Bitferry.verbosity == :verbose
847
876
  log.info(cms)
848
877
  status = Open3.pipeline(cmd).first
849
- raise "rclone exit code #{status.exitstatus}" unless status.success?
878
+ raise RuntimeError, "rclone exit code #{status.exitstatus}" unless status.success?
850
879
  status.success?
851
880
  end
852
881
 
@@ -1011,6 +1040,9 @@ module Bitferry
1011
1040
  def format = nil
1012
1041
 
1013
1042
 
1043
+ def include_filters = include.collect { |x| ['--include', x]}.flatten
1044
+
1045
+
1014
1046
  def common_options
1015
1047
  [
1016
1048
  case Bitferry.verbosity
@@ -1037,7 +1069,8 @@ module Bitferry
1037
1069
  begin
1038
1070
  Dir.chdir(chdir) unless chdir.nil?
1039
1071
  status = Open3.pipeline(cmd).first
1040
- raise "restic exit code #{status.exitstatus}" unless status.success?
1072
+ raise RuntimeError, "restic exit code #{status.exitstatus}" unless status.success?
1073
+ status.success?
1041
1074
  ensure
1042
1075
  Dir.chdir(wd) unless chdir.nil?
1043
1076
  end
@@ -1077,14 +1110,14 @@ module Bitferry
1077
1110
 
1078
1111
 
1079
1112
  FORGET = {
1080
- default: ['--prune', '--keep-within-hourly', '24h', '--keep-within-daily', '7d', '--keep-within-weekly', '30d', '--keep-within-monthly', '1y', '--keep-within-yearly', '100y']
1113
+ default: ['--prune', '--no-cache', '--keep-within-hourly', '24h', '--keep-within-daily', '7d', '--keep-within-weekly', '30d', '--keep-within-monthly', '1y', '--keep-within-yearly', '100y']
1081
1114
  }
1082
1115
  FORGET[nil] = nil # Skip processing retention policy by default
1083
1116
 
1084
1117
 
1085
1118
  CHECK = {
1086
- default: [],
1087
- full: ['--read-data']
1119
+ default: ['--no-cache'],
1120
+ full: ['--no-cache', '--read-data']
1088
1121
  }
1089
1122
  CHECK[nil] = nil # Skip integrity checking by default
1090
1123
 
@@ -1102,7 +1135,10 @@ module Bitferry
1102
1135
  end
1103
1136
 
1104
1137
 
1105
- def show_status = "#{show_operation} #{directory.show_status} #{show_direction} #{repository.show_status}"
1138
+ def exclude_filters = ([Volume::STORAGE, Volume::STORAGE_] + exclude).collect { |x| ['--exclude', x]}.flatten
1139
+
1140
+
1141
+ def show_status = "#{show_operation} #{directory.show_status} #{show_direction} #{repository.show_status} #{show_filters}"
1106
1142
 
1107
1143
 
1108
1144
  def show_operation = 'encrypt+backup'
@@ -1114,7 +1150,7 @@ module Bitferry
1114
1150
  def process
1115
1151
  begin
1116
1152
  log.info("processing task #{tag}")
1117
- execute('backup', '.', '--tag', "bitferry,#{tag}", '--exclude', Volume::STORAGE, '--exclude', Volume::STORAGE_, *process_options, *common_options_simulate, chdir: directory.root)
1153
+ execute('backup', '.', '--tag', "bitferry,#{tag}", *exclude_filters, *process_options, *common_options_simulate, chdir: directory.root)
1118
1154
  unless check_options.nil?
1119
1155
  log.info("checking repository in #{repository.root}")
1120
1156
  execute('check', *check_options, *common_options)
@@ -1199,7 +1235,10 @@ module Bitferry
1199
1235
  end
1200
1236
 
1201
1237
 
1202
- def show_status = "#{show_operation} #{repository.show_status} #{show_direction} #{directory.show_status}"
1238
+ def exclude_filters = exclude.collect { |x| ['--exclude', x]}.flatten
1239
+
1240
+
1241
+ def show_status = "#{show_operation} #{repository.show_status} #{show_direction} #{directory.show_status} #{show_filters}"
1203
1242
 
1204
1243
 
1205
1244
  def show_operation = 'decrypt+restore'
@@ -1230,7 +1269,7 @@ module Bitferry
1230
1269
  log.info("processing task #{tag}")
1231
1270
  begin
1232
1271
  # FIXME restore specifically tagged latest snapshot
1233
- execute('restore', 'latest', '--target', '.', *process_options, *common_options, simulate: Bitferry.simulate?, chdir: directory.root)
1272
+ execute('restore', 'latest', '--target', directory.root.to_s, *include_filters, *exclude_filters, *process_options, *common_options, simulate: Bitferry.simulate?)
1234
1273
  true
1235
1274
  rescue
1236
1275
  false
@@ -1326,7 +1365,7 @@ module Bitferry
1326
1365
 
1327
1366
  def restore(hash)
1328
1367
  @volume_tag = hash.fetch(:volume)
1329
- @path = Pathname.new(hash.fetch(:path))
1368
+ @path = Pathname.new(hash.fetch(:path, ''))
1330
1369
  end
1331
1370
 
1332
1371
 
@@ -1334,8 +1373,8 @@ module Bitferry
1334
1373
  {
1335
1374
  endpoint: :bitferry,
1336
1375
  volume: volume_tag,
1337
- path: path
1338
- }
1376
+ path: path.to_s.empty? ? nil : path
1377
+ }.compact
1339
1378
  end
1340
1379
 
1341
1380
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitferry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg A. Khlybov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-07 00:00:00.000000000 Z
11
+ date: 2024-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake