bitferry 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|