bitferry 0.0.3 → 0.0.4
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.
- checksums.yaml +4 -4
- data/CHANGES.md +5 -0
- data/README.md +1 -1
- data/lib/bitferry/cli.rb +37 -6
- data/lib/bitferry.rb +60 -19
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17d40f40cac1390daae5cb6500acddf6f47bc83cc99b089530b7babf76adfb67
|
4
|
+
data.tar.gz: 4a5652c86fc09e7b6a23fe1bf53b8ffd891ca544782fe73df9eeb4472cdd4495
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95f3e41305bd871b1ca3ce8b8d5172c54006e8b3ca4125ac60dd36a1d4f9b816853f2f30454c7269ef084c596c520c491c0f9202169ef9e2cf34f82a06f3d33f
|
7
|
+
data.tar.gz: 0cc18872c913a6583d72d487191c26bac65f35f09fa051005b6fdc39293eb8d428a151e9643486bd9bb241dbd2cf705081345638c274ce49a2f13ebddd26daed
|
data/CHANGES.md
CHANGED
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)
|
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
|
|
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 '-
|
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
|
@@ -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, '
|
255
|
-
option '-c', :flag, '
|
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', '
|
258
|
-
option ['--check', '-C'], 'OPTIONS', '
|
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.
|
15
|
+
VERSION = '0.0.4'
|
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))
|
@@ -498,6 +498,9 @@ module Bitferry
|
|
498
498
|
attr_reader :modified
|
499
499
|
|
500
500
|
|
501
|
+
attr_reader :include, :exclude
|
502
|
+
|
503
|
+
|
501
504
|
def process_options = @process_options.nil? ? [] : @process_options # As a mandatory option it should never be nil
|
502
505
|
|
503
506
|
|
@@ -530,10 +533,12 @@ module Bitferry
|
|
530
533
|
end
|
531
534
|
|
532
535
|
|
533
|
-
def initialize(tag: Bitferry.tag, modified: DateTime.now)
|
536
|
+
def initialize(tag: Bitferry.tag, modified: DateTime.now, include: [], exclude: [])
|
534
537
|
@tag = tag
|
535
538
|
@generation = 0
|
536
|
-
@
|
539
|
+
@include = include
|
540
|
+
@exclude = exclude
|
541
|
+
@modified = modified.is_a?(DateTime) ? modified : DateTime.parse(modified)
|
537
542
|
# FIXME handle process_options at this level
|
538
543
|
end
|
539
544
|
|
@@ -546,6 +551,8 @@ module Bitferry
|
|
546
551
|
|
547
552
|
|
548
553
|
def restore(hash)
|
554
|
+
@include = hash.fetch(:include, [])
|
555
|
+
@exclude = hash.fetch(:exclude, [])
|
549
556
|
@state = :intact
|
550
557
|
log.info("restored task #{tag}")
|
551
558
|
end
|
@@ -558,7 +565,9 @@ module Bitferry
|
|
558
565
|
def externalize
|
559
566
|
{
|
560
567
|
task: tag,
|
561
|
-
modified:
|
568
|
+
modified: modified,
|
569
|
+
include: include.empty? ? nil : include,
|
570
|
+
exclude: exclude.empty? ? nil : exclude
|
562
571
|
}.compact
|
563
572
|
end
|
564
573
|
|
@@ -584,6 +593,14 @@ module Bitferry
|
|
584
593
|
end
|
585
594
|
|
586
595
|
|
596
|
+
def show_filters
|
597
|
+
xs = []
|
598
|
+
xs << 'include: ' + include.join(',') unless include.empty?
|
599
|
+
xs << 'exclude: ' + exclude.join(',') unless exclude.empty?
|
600
|
+
xs.join(' ').to_s
|
601
|
+
end
|
602
|
+
|
603
|
+
|
587
604
|
def self.[](tag) = @@registry[tag]
|
588
605
|
|
589
606
|
|
@@ -609,8 +626,16 @@ module Bitferry
|
|
609
626
|
def self.reset = @@registry = {}
|
610
627
|
|
611
628
|
|
612
|
-
def self.register(task)
|
613
|
-
|
629
|
+
def self.register(task)
|
630
|
+
# Task with newer timestamp replaces already registered task, if any
|
631
|
+
if (xtag = @@registry[task.tag]).nil?
|
632
|
+
@@registry[task.tag] = task
|
633
|
+
elsif xtag.modified < task.modified
|
634
|
+
@@registry[task.tag] = task
|
635
|
+
else
|
636
|
+
xtag
|
637
|
+
end
|
638
|
+
end
|
614
639
|
|
615
640
|
def self.intact = live.filter { |task| task.intact? }
|
616
641
|
|
@@ -795,7 +820,7 @@ module Bitferry
|
|
795
820
|
end
|
796
821
|
|
797
822
|
|
798
|
-
def show_status = "#{show_operation} #{source.show_status} #{show_direction} #{destination.show_status}"
|
823
|
+
def show_status = "#{show_operation} #{source.show_status} #{show_direction} #{destination.show_status} #{show_filters}"
|
799
824
|
|
800
825
|
|
801
826
|
def show_operation = encryption.nil? ? '' : encryption.show_operation
|
@@ -833,8 +858,14 @@ module Bitferry
|
|
833
858
|
end
|
834
859
|
|
835
860
|
|
861
|
+
def include_filters = include.collect { |x| ['--filter', "+ #{x}"]}.flatten
|
862
|
+
|
863
|
+
|
864
|
+
def exclude_filters = ([Volume::STORAGE, Volume::STORAGE_] + exclude).collect { |x| ['--filter', "- #{x}"]}.flatten
|
865
|
+
|
866
|
+
|
836
867
|
def process_arguments
|
837
|
-
|
868
|
+
include_filters + exclude_filters + common_options + process_options + (
|
838
869
|
encryption.nil? ? [source.root.to_s, destination.root.to_s] : encryption.arguments(self)
|
839
870
|
)
|
840
871
|
end
|
@@ -846,7 +877,7 @@ module Bitferry
|
|
846
877
|
puts cms if Bitferry.verbosity == :verbose
|
847
878
|
log.info(cms)
|
848
879
|
status = Open3.pipeline(cmd).first
|
849
|
-
raise "rclone exit code #{status.exitstatus}" unless status.success?
|
880
|
+
raise RuntimeError, "rclone exit code #{status.exitstatus}" unless status.success?
|
850
881
|
status.success?
|
851
882
|
end
|
852
883
|
|
@@ -1011,6 +1042,9 @@ module Bitferry
|
|
1011
1042
|
def format = nil
|
1012
1043
|
|
1013
1044
|
|
1045
|
+
def include_filters = include.collect { |x| ['--include', x]}.flatten
|
1046
|
+
|
1047
|
+
|
1014
1048
|
def common_options
|
1015
1049
|
[
|
1016
1050
|
case Bitferry.verbosity
|
@@ -1037,7 +1071,8 @@ module Bitferry
|
|
1037
1071
|
begin
|
1038
1072
|
Dir.chdir(chdir) unless chdir.nil?
|
1039
1073
|
status = Open3.pipeline(cmd).first
|
1040
|
-
raise "restic exit code #{status.exitstatus}" unless status.success?
|
1074
|
+
raise RuntimeError, "restic exit code #{status.exitstatus}" unless status.success?
|
1075
|
+
status.success?
|
1041
1076
|
ensure
|
1042
1077
|
Dir.chdir(wd) unless chdir.nil?
|
1043
1078
|
end
|
@@ -1077,7 +1112,7 @@ module Bitferry
|
|
1077
1112
|
|
1078
1113
|
|
1079
1114
|
FORGET = {
|
1080
|
-
default: ['--prune', '--keep-within-hourly', '24h', '--keep-within-daily', '7d', '--keep-within-weekly', '30d', '--keep-within-monthly', '1y', '--keep-within-yearly', '100y']
|
1115
|
+
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
1116
|
}
|
1082
1117
|
FORGET[nil] = nil # Skip processing retention policy by default
|
1083
1118
|
|
@@ -1102,7 +1137,10 @@ module Bitferry
|
|
1102
1137
|
end
|
1103
1138
|
|
1104
1139
|
|
1105
|
-
def
|
1140
|
+
def exclude_filters = ([Volume::STORAGE, Volume::STORAGE_] + exclude).collect { |x| ['--exclude', x]}.flatten
|
1141
|
+
|
1142
|
+
|
1143
|
+
def show_status = "#{show_operation} #{directory.show_status} #{show_direction} #{repository.show_status} #{show_filters}"
|
1106
1144
|
|
1107
1145
|
|
1108
1146
|
def show_operation = 'encrypt+backup'
|
@@ -1114,7 +1152,7 @@ module Bitferry
|
|
1114
1152
|
def process
|
1115
1153
|
begin
|
1116
1154
|
log.info("processing task #{tag}")
|
1117
|
-
execute('backup', '.', '--tag', "bitferry,#{tag}",
|
1155
|
+
execute('backup', '.', '--tag', "bitferry,#{tag}", *exclude_filters, *process_options, *common_options_simulate, chdir: directory.root)
|
1118
1156
|
unless check_options.nil?
|
1119
1157
|
log.info("checking repository in #{repository.root}")
|
1120
1158
|
execute('check', *check_options, *common_options)
|
@@ -1199,7 +1237,10 @@ module Bitferry
|
|
1199
1237
|
end
|
1200
1238
|
|
1201
1239
|
|
1202
|
-
def
|
1240
|
+
def exclude_filters = exclude.collect { |x| ['--exclude', x]}.flatten
|
1241
|
+
|
1242
|
+
|
1243
|
+
def show_status = "#{show_operation} #{repository.show_status} #{show_direction} #{directory.show_status} #{show_filters}"
|
1203
1244
|
|
1204
1245
|
|
1205
1246
|
def show_operation = 'decrypt+restore'
|
@@ -1230,7 +1271,7 @@ module Bitferry
|
|
1230
1271
|
log.info("processing task #{tag}")
|
1231
1272
|
begin
|
1232
1273
|
# FIXME restore specifically tagged latest snapshot
|
1233
|
-
execute('restore', 'latest', '--target',
|
1274
|
+
execute('restore', 'latest', '--target', directory.root.to_s, *include_filters, *exclude_filters, *process_options, *common_options, simulate: Bitferry.simulate?)
|
1234
1275
|
true
|
1235
1276
|
rescue
|
1236
1277
|
false
|
@@ -1326,7 +1367,7 @@ module Bitferry
|
|
1326
1367
|
|
1327
1368
|
def restore(hash)
|
1328
1369
|
@volume_tag = hash.fetch(:volume)
|
1329
|
-
@path = Pathname.new(hash.fetch(:path))
|
1370
|
+
@path = Pathname.new(hash.fetch(:path, ''))
|
1330
1371
|
end
|
1331
1372
|
|
1332
1373
|
|
@@ -1334,8 +1375,8 @@ module Bitferry
|
|
1334
1375
|
{
|
1335
1376
|
endpoint: :bitferry,
|
1336
1377
|
volume: volume_tag,
|
1337
|
-
path: path
|
1338
|
-
}
|
1378
|
+
path: path.to_s.empty? ? nil : path
|
1379
|
+
}.compact
|
1339
1380
|
end
|
1340
1381
|
|
1341
1382
|
|
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.
|
4
|
+
version: 0.0.4
|
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-
|
11
|
+
date: 2024-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|