synco 1.0.0

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +4 -0
  4. data/.simplecov +9 -0
  5. data/.travis.yml +14 -0
  6. data/Gemfile +11 -0
  7. data/README.md +246 -0
  8. data/Rakefile +8 -0
  9. data/bin/synco +30 -0
  10. data/lib/synco.rb +51 -0
  11. data/lib/synco/command.rb +71 -0
  12. data/lib/synco/command/disk.rb +55 -0
  13. data/lib/synco/command/prune.rb +166 -0
  14. data/lib/synco/command/rotate.rb +86 -0
  15. data/lib/synco/command/spawn.rb +39 -0
  16. data/lib/synco/compact_formatter.rb +115 -0
  17. data/lib/synco/controller.rb +119 -0
  18. data/lib/synco/directory.rb +60 -0
  19. data/lib/synco/disk.rb +68 -0
  20. data/lib/synco/method.rb +56 -0
  21. data/lib/synco/methods/rsync.rb +162 -0
  22. data/lib/synco/methods/scp.rb +44 -0
  23. data/lib/synco/methods/zfs.rb +60 -0
  24. data/lib/synco/scope.rb +247 -0
  25. data/lib/synco/script.rb +128 -0
  26. data/lib/synco/server.rb +90 -0
  27. data/lib/synco/shell.rb +44 -0
  28. data/lib/synco/shells/ssh.rb +52 -0
  29. data/lib/synco/version.rb +23 -0
  30. data/media/LSync Logo.artx/Preview/preview.png +0 -0
  31. data/media/LSync Logo.artx/QuickLook/Preview.pdf +0 -0
  32. data/media/LSync Logo.artx/doc.thread +0 -0
  33. data/media/LSync Logo.png +0 -0
  34. data/spec/synco/backup_script.rb +63 -0
  35. data/spec/synco/directory_spec.rb +33 -0
  36. data/spec/synco/local_backup.rb +56 -0
  37. data/spec/synco/local_sync.rb +91 -0
  38. data/spec/synco/method_spec.rb +62 -0
  39. data/spec/synco/rsync_spec.rb +89 -0
  40. data/spec/synco/scp_spec.rb +58 -0
  41. data/spec/synco/script_spec.rb +51 -0
  42. data/spec/synco/shell_spec.rb +42 -0
  43. data/spec/synco/usb_spec.rb +76 -0
  44. data/spec/synco/zfs_spec.rb +50 -0
  45. data/synco.gemspec +35 -0
  46. metadata +254 -0
@@ -0,0 +1,90 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'controller'
22
+ require_relative 'directory'
23
+ require_relative 'shells/ssh'
24
+
25
+ require 'shellwords'
26
+
27
+ module Synco
28
+ class Server < Controller
29
+ def initialize(name, root: '/', shell: nil, **options)
30
+ super()
31
+
32
+ @name = name
33
+
34
+ case @name
35
+ when Symbol
36
+ @host = "localhost"
37
+ else
38
+ @host = name.to_s
39
+ end
40
+
41
+ @root = root
42
+ @shell = shell || Shells::SSH.new
43
+
44
+ @options = options
45
+ end
46
+
47
+ # The name of the server in the configuration (might be the same as the host).
48
+ attr :name
49
+
50
+ # The host name (e.g. DNS entry) for the given server.
51
+ attr_accessor :host
52
+
53
+ # The root path on the server in which all other directories will be relative to.
54
+ attr_accessor :root
55
+
56
+ # The shell to use to connect to the server.
57
+ attr_accessor :shell
58
+
59
+ attr_accessor :mountpoint
60
+
61
+ # Give the full path for a particular subdirectory.
62
+ def full_path(directory = "")
63
+ path = File.expand_path(directory.to_s, @root)
64
+
65
+ Directory.normalize(path)
66
+ end
67
+
68
+ # Give a general connection string (e.g +"host:/directory"+ or +"/directory"+ if local).
69
+ def connection_string(directory, on: nil)
70
+ if self.host == on.host
71
+ return full_path(directory).to_s
72
+ else
73
+ return @host + ":" + Shellwords.escape(full_path(directory))
74
+ end
75
+ end
76
+
77
+ def connection_command
78
+ @shell.connection_command(self)
79
+ end
80
+
81
+ def same_host?(other)
82
+ @host == other.host
83
+ end
84
+
85
+ # String representation of the server for logging.
86
+ def to_s
87
+ "#{@host}:#{full_path}"
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'pathname'
22
+
23
+ module Synco
24
+ # A shell provides access to a server, typically to run commands.
25
+ class Shell
26
+ def initialize(*command, arguments: [], **options)
27
+ @command = command.empty? ? default_command : command
28
+
29
+ @arguments = arguments
30
+ @options = options
31
+ end
32
+
33
+ attr :arguments
34
+
35
+ # The command required to connect to the remote machine.
36
+ def connection_command(server, *arguments)
37
+ [*@command, *@arguments, *arguments, server.host]
38
+ end
39
+
40
+ def to_s
41
+ "<#{self.class} #{@command} #{@options.inspect}>"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative '../shell'
22
+
23
+ module Synco
24
+ module Shells
25
+ # SSH shell provides access to a remote server using SSH.
26
+ class SSH < Shell
27
+ def default_command
28
+ ['ssh']
29
+ end
30
+
31
+ def initialize(*command, arguments: [], port: nil, key: nil, user: nil, batch_mode: nil, **options)
32
+ if port
33
+ arguments << '-p' << port
34
+ end
35
+
36
+ if key
37
+ arguments << '-i' << value
38
+ end
39
+
40
+ if user
41
+ arguments << '-l' << value
42
+ end
43
+
44
+ unless batch_mode.nil?
45
+ arguments << '-o' << "BatchMode=#{batch_mode ? 'yes' : 'no'}"
46
+ end
47
+
48
+ super
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Synco
22
+ VERSION = "1.0.0"
23
+ end
Binary file
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'fingerprint'
24
+ require 'process/group'
25
+ require 'fileutils'
26
+ require 'digest'
27
+
28
+ require 'synco/scope'
29
+ require 'synco/script'
30
+
31
+ SYNCO_PATH = File.expand_path("../../bin/synco", __dir__)
32
+
33
+ RSpec.shared_context "backup script" do
34
+ def create_files(master_path, target_path)
35
+ FileUtils.rm_rf master_path
36
+ FileUtils.rm_rf target_path
37
+
38
+ FileUtils.mkdir_p master_path
39
+ FileUtils.mkdir_p target_path
40
+
41
+ (1...10).each do |i|
42
+ path = File.join(master_path, i.to_s)
43
+
44
+ FileUtils.mkdir(path)
45
+
46
+ text = Digest::MD5.hexdigest(i.to_s)
47
+
48
+ File.open(File.join(path, i.to_s), "w") { |f| f.write(text) }
49
+ end
50
+ end
51
+
52
+ let(:tmp_path) {File.join(__dir__, 'tmp')}
53
+ let(:master_path) {File.join(__dir__, 'tmp/master')}
54
+ let(:target_path) {File.join(__dir__, 'tmp/target')}
55
+
56
+ before(:each) do
57
+ create_files(master_path, target_path)
58
+ end
59
+
60
+ after(:each) do
61
+ # FileUtils.rm_rf tmp_path
62
+ end
63
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'synco/directory'
24
+
25
+ describe Synco::Directory.new(".", arguments: ['--foo']) do
26
+ it "should have arguments" do
27
+ expect(subject.arguments).to include('--foo')
28
+ end
29
+
30
+ it "must be relative path" do
31
+ expect{Synco::Directory.new("/var")}.to raise_error(ArgumentError)
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'synco'
4
+ require 'synco/shells/ssh'
5
+ require 'synco/methods/rsync'
6
+
7
+ # Check we've got two paths for sync.
8
+ unless ARGV.size == 2
9
+ $stderr.puts "Usage: #{$0} [master] [copy]"
10
+ exit(255)
11
+ end
12
+
13
+ # $growl = Growl.new "localhost", "Synco", ["Backup Status"]
14
+ # $growl.register
15
+
16
+ $stdout.sync = true
17
+ $stderr.sync = true
18
+
19
+ Synco::run_script do |script|
20
+ self.method = Synco::Methods::RSyncSnapshot.new(:push, "--archive", "--compress", "--stats")
21
+
22
+ self.master = :src
23
+
24
+ server(:src) do |server|
25
+ server.root = ARGV[0]
26
+ end
27
+
28
+ server(:dst) do |server|
29
+ server.root = ARGV[1]
30
+
31
+ # Runs after all directories have been successfully backed up.
32
+ server.on(:success) do
33
+ target.run "synco", "rotate"
34
+ target.run "synco", "prune"
35
+ end
36
+ end
37
+
38
+ # This event is fired before the backup starts
39
+ #script.on(:prepare) do
40
+ # $growl.notify "Backup Status", "Starting Backup", "Starting at #{Time.now.to_s}"
41
+ #end
42
+ #
43
+ ## This event occurs if the backup is successful
44
+ #script.on(:success) do
45
+ # $growl.notify "Backup Status", "Backup Successful", script.log.string
46
+ #end
47
+ #
48
+ ## This event occurs if any part of the backup fails and is not handled elsewhere.
49
+ #script.on(:failure) do |failure|
50
+ # $growl.notify "Backup Status", "Backup Failure", failure.to_s
51
+ #
52
+ # raise failure
53
+ #end
54
+
55
+ backup('./')
56
+ end
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # You need to have the FSSM gem installed for this to work.
4
+ require 'fssm'
5
+
6
+ require 'synco'
7
+ require 'synco/shells/ssh'
8
+ require 'synco/methods/rsync'
9
+ require 'synco/event_timer'
10
+
11
+ MONITOR = ARGV.delete "-m"
12
+
13
+ # Check we've got two paths for sync.
14
+ unless ARGV.size == 2
15
+ $stderr.puts "Usage: #{$0} [master] [copy]"
16
+ exit(255)
17
+ end
18
+
19
+ script = Synco::Script.new do |script|
20
+ script.method = Synco::Methods::RSync.new(:push, :arguments => ["--archive", "--delete"])
21
+
22
+ script.master = :src
23
+
24
+ server(:src) do |server|
25
+ server.root = ARGV[0]
26
+ end
27
+
28
+ server(:dst) do |server|
29
+ server.root = ARGV[1]
30
+
31
+ #server.on(:prepare) do
32
+ # logger.warn "Aborting backup!"
33
+ # server.abort!
34
+ #end
35
+ end
36
+
37
+ #server(:remote) do |server|
38
+ # server.host = "ayako.oriontransfer.org"
39
+ # server.shell = Synco::Shells::SSH.new(:user => "nobody")
40
+ # server.root = "/tmp/sync-test"
41
+ #end
42
+
43
+ #server(:remote2) do |server|
44
+ # server.host = "remote2.example.com"
45
+ # server.root = "/tmp/sync-test"
46
+ #
47
+ # server.on(:prepare) do |controller|
48
+ # controller.run! "mysqldump", "..."
49
+ # end
50
+ #
51
+ # server.on(:success) do |controller|
52
+ # controller.run! "uname", "-a"
53
+ # end
54
+ #end
55
+
56
+ backup('./')
57
+ end
58
+
59
+ # Initial sync:
60
+
61
+ context = Synco::Context.new(script)
62
+ context.run!
63
+
64
+ if MONITOR
65
+ # Monitor directories for changes:
66
+ monitor = FSSM::Monitor.new(:directories => true)
67
+
68
+ # The event timer aggregates events into a single callback which will be called at most
69
+ # once every k seconds (where k = 10 in this case).
70
+ $event_timer = Synco::EventTimer.new(10) do
71
+ $script.run!
72
+ end
73
+
74
+ $script.directories.each do |directory|
75
+ full_path = $script[:src].full_path(directory)
76
+ puts "Monitoring path: #{full_path}"
77
+
78
+ monitor.path(full_path, "**/*") do
79
+ update { $event_timer.trigger! }
80
+ delete { $event_timer.trigger! }
81
+ create { $event_timer.trigger! }
82
+ end
83
+ end
84
+
85
+ begin
86
+ monitor.run
87
+ ensure
88
+ # We should wait for the backup to complete nicely.
89
+ $event_timer.join
90
+ end
91
+ end