ruby-livesync 1.0.0.beta1 → 1.0.0.beta3

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: 7413a1c3e43254fbaa87386c88f0a550ec6e9f7a17331e74daa182c3aee7ee67
4
- data.tar.gz: 34320b16a5886835146b9eb89a408e3481b9afdd62eafbd450ec2b83767aa8a9
3
+ metadata.gz: 5b752ff64938cd1e8c5abd600ce03aebc501b0a45088aea45d73f14f3cf1bd8f
4
+ data.tar.gz: a68ed7c8374b0e380d8dc183c222d42c904edbecaf4b198fc82a89e960e3419d
5
5
  SHA512:
6
- metadata.gz: 604aec4e0bfa4cf29cfbfec5837672d36289e98af3a537cf6dc4ad11706110a2265f079ace49820926eaf5358160caae85b6042ae15b855fa3af4fd4cd0ef012
7
- data.tar.gz: fc87708fba1a5db5c1a88eb81bca51268a80bcf2d30bfe9ddf80d7df9656ba3c2c28de7558e8abe375809a8432ec819de5afbe88e6a7295a19a3b0d7b1067696
6
+ metadata.gz: 9e1e8e35696105846bde4cd4592f70bce67007e0cd570299993587a276845b4366cc71d91608637ae8745ded4c2b61ac247825190b9dbba87ff4d90e11ada15d
7
+ data.tar.gz: 930d1b2b01093cb90e06ed60316822b0c36d2e17f20089b0d0c30b54faeb358a65d77973c0ccfe30bc1d39960e28fb2d4e400cfd4844128952a5c5ada4ac3ff6
data/config/sample.rb ADDED
@@ -0,0 +1,25 @@
1
+ # name of the sync
2
+ # if it is an existing path then `source` is set this value
3
+ sync '4tb' do
4
+ enabled = false
5
+
6
+ # fork to user below, usually associated with private keys
7
+ user = :root
8
+
9
+ delay = 5
10
+
11
+ source = '/mnt/4tb/'
12
+ target = 'root@bhavapower:/mnt/extensor/4tb'
13
+
14
+ rsync.opts = '-ax --partial' # default
15
+
16
+ # possible values are: true, false, :initial, :watched
17
+ delete = true
18
+
19
+ excludes = [
20
+ '.snapshots',
21
+ ]
22
+
23
+ log.info 'starting'
24
+ end
25
+
@@ -14,7 +14,7 @@ module LiveSync
14
14
  end
15
15
 
16
16
  def start
17
- Process.setpgrp
17
+ Process.setpgrp rescue nil # not allowed in systemd
18
18
  Process.setproctitle 'livesync'
19
19
  instance_eval File.read(config), config
20
20
  run
@@ -33,11 +33,10 @@ module LiveSync
33
33
  @syncs.each do |user, syncs|
34
34
  User.wrap user do
35
35
  syncs.each do |s|
36
- s.run
37
- s.guard
36
+ s.guard if s.start
38
37
  rescue => e
39
38
  msg = e.message
40
- msg += "\n#{e.backtrace.join "\n"}" if Log.debug
39
+ msg += "\n#{e.backtrace.join "\n"}" if Log.debug?
41
40
  Log.fatal msg
42
41
  end
43
42
  Process.waitall
@@ -10,13 +10,16 @@ module LiveSync
10
10
  class_attribute :attrs
11
11
  self.attrs = []
12
12
 
13
- def self.dsl attr, default: nil, type: nil, &block
13
+ def self.dsl attr, default: nil, enum: nil, type: nil, &block
14
14
  self.attrs << attr
15
- define_method attr do |value=nil|
16
- return instance_variable_get("@#{attr}") || default unless value
17
- raise "#{ctx}: incorrect type for `#{attr}`" if type and !value.is_a? type
18
- instance_exec value, &block if block
19
- instance_variable_set "@#{attr}", value
15
+ define_method attr do |sv=nil|
16
+ (v = instance_variable_get("@#{attr}"); return(if v.nil? then default else v end)) if sv.nil?
17
+
18
+ raise "#{ctx}/#{attr}: incorrect type" if type and !sv.is_a? type
19
+ raise "#{ctx}/#{attr}: value not one of following #{enum}" if enum and !sv.in? enum
20
+
21
+ instance_variable_set "@#{attr}", sv
22
+ instance_exec sv, &block if block
20
23
  end
21
24
  end
22
25
 
@@ -1,41 +1,53 @@
1
1
  module LiveSync
2
2
  class Log
3
3
 
4
- class_attribute :ctx
5
4
  class_attribute :global
6
5
  self.global = self.new
7
6
  class << self
8
7
  delegate_missing_to :global
9
8
  end
10
9
 
11
- class_attribute :debug
12
- self.debug = !!ENV['DEBUG']
10
+ class_attribute :ctx
11
+
12
+ LEVELS = {
13
+ debug: 0,
14
+ verbose: 1,
15
+ info: 2,
16
+ warning: 3,
17
+ error: 4,
18
+ fatal: 5,
19
+ }
20
+ class_attribute :level
21
+ self.level = if !!ENV['DEBUG'] then :debug else ENV['LEVEL']&.to_sym || :info end
22
+ def self.debug?; level == :debug; end
13
23
 
14
24
  def initialize ctx=nil
15
25
  self.ctx = ctx
16
26
  end
17
27
 
18
- def debug msg
19
- return unless debug
20
- puts "DEBUG: #{parse msg}"
28
+ def log? level
29
+ LEVELS[level] >= LEVELS[self.level]
21
30
  end
22
31
 
32
+ def debug msg
33
+ puts "DEBUG: #{parse msg}" if log? :debug
34
+ end
23
35
  def info msg
24
- puts "INFO: #{parse msg}"
36
+ puts "INFO: #{parse msg}" if log? :info
25
37
  end
26
38
 
27
39
  def warning msg
28
- STDERR.puts "WARNING: #{parse msg}"
40
+ STDERR.puts "WARNING: #{parse msg}" if log? :warning
29
41
  end
30
-
31
42
  def error msg
32
- STDERR.puts "ERROR: #{parse msg}"
43
+ STDERR.puts "ERROR: #{parse msg}" if log? :error
33
44
  end
34
-
35
45
  def fatal msg
36
- STDERR.puts "FATAL: #{parse msg}"
46
+ STDERR.puts "FATAL: #{parse msg}" if log? :fatal
37
47
  end
38
48
 
49
+ protected
50
+
39
51
  def parse msg
40
52
  msg = "#{ctx}: #{msg}" if ctx
41
53
  msg
@@ -1,7 +1,8 @@
1
1
  module LiveSync
2
2
  class Rsync
3
3
 
4
- attr_reader :sync, :opts
4
+ attr_reader :sync
5
+ attr_accessor :opts
5
6
 
6
7
  def initialize sync
7
8
  @sync = sync
@@ -13,11 +14,15 @@ module LiveSync
13
14
  end
14
15
 
15
16
  def initial
16
- run :initial
17
+ args = []
18
+ args << '--delete' if sync.delete.in? [true, :initial]
19
+ run :initial, *args, loglevel: :info
17
20
  end
18
21
 
19
- def from_list paths
20
- run :partial, '--files-from=-' do |stdin, stdout, stderr|
22
+ def partial paths
23
+ args = ['--files-from=-']
24
+ args << '--delete-missing-args' if sync.delete.in? [true, :watched]
25
+ run :partial, *args do |stdin, stdout, stderr|
21
26
  stdin.write paths.join "\n"
22
27
  stdin.close
23
28
  end
@@ -25,15 +30,17 @@ module LiveSync
25
30
 
26
31
  protected
27
32
 
28
- def run type, args=nil
29
- cmd = "rsync -e '#{rsh}' #{opts} #{sync.source} #{sync.target} #{args}"
30
- sync.log.info "#{type}: starting with cmd: #{cmd}"
33
+ def run type, *args, loglevel: :debug
34
+ cmd = "rsync -e '#{rsh}' #{opts} #{sync.source} #{sync.target} #{args.join ' '}"
35
+ sync.excludes.each{ |e| cmd << " --exclude='#{e}'" }
36
+
37
+ sync.log.send loglevel, "#{type}: starting with cmd: #{cmd}"
31
38
  stdin, stdout, stderr, @wait_thr = Open3.popen3 cmd
32
39
  yield stdin, stdout, stderr if block_given?
33
40
  Thread.new do
34
41
  Process.wait @wait_thr.pid rescue Errno::ECHILD; nil
35
42
  @wait_thr = nil
36
- sync.log.info "#{type}: finished"
43
+ sync.log.send loglevel, "#{type}: finished"
37
44
  end
38
45
  end
39
46
 
@@ -30,7 +30,6 @@ module LiveSync
30
30
  Log.error "ssh/#{userhost}: #{stderr.read}"
31
31
  end
32
32
  end
33
- sleep 1 while !available?
34
33
  end
35
34
 
36
35
  def available?
@@ -14,6 +14,7 @@ module LiveSync
14
14
  fill_name name
15
15
  source name if File.exist? name
16
16
  @to_sync = Set.new
17
+ @rsync = Rsync.new self
17
18
  end
18
19
 
19
20
  def fill_name name
@@ -22,9 +23,11 @@ module LiveSync
22
23
  @log = Log.new ctx
23
24
  end
24
25
 
26
+ dsl :enabled, default: true
27
+
25
28
  dsl :user, default: :root
26
29
  dsl :source do |source|
27
- raise "#{ctx}: source not found" unless File.exist? source
30
+ raise "#{ctx}: source isn't a directory" unless File.directory? source
28
31
  fill_name source
29
32
  @pathname = Pathname.new source
30
33
  end
@@ -32,24 +35,30 @@ module LiveSync
32
35
  dsl :target do |target|
33
36
  @userhost, @target_path = target.split(':')
34
37
  raise "#{ctx}: missing target path" unless @target_path
35
- @user, @host = if @userhost.index '@' then @userhost.split('@') else [@user, @userhost] end
38
+ source File.join(@source, '') if File.basename(@source) == File.basename(@target_path)
36
39
  end
37
40
 
38
41
  dsl :delay, default: 5, type: Integer
39
42
 
40
- def run
43
+ dsl :delete, default: false, enum: [true,false] + %i[initial watched]
44
+
45
+ dsl :excludes, default: []
46
+
47
+ def start
48
+ return log.warning('skipping disabled sync') && false unless enabled
41
49
  raise "#{ctx}: missing target" unless @target
42
- @ssh = Ssh.connect @userhost
43
- @rsync = Rsync.new self
50
+ @ssh = Ssh.connect @userhost
51
+ sleep 1 and log.warning 'waiting for ssh' while !@ssh.available?
52
+ true
44
53
  end
45
54
 
46
55
  def guard
47
56
  fork do
48
57
  Process.setproctitle "livesync: sync #{ctx}"
49
- @watcher = Watcher.new
58
+ @watcher = Watcher.new self
50
59
  @scheduler = Rufus::Scheduler.new
51
60
 
52
- @watcher.dir_rwatch source, *%i[create modify], &method(:track)
61
+ watch source
53
62
  @rsync.initial
54
63
  schedule
55
64
  sleep 1.day while true
@@ -57,7 +66,13 @@ module LiveSync
57
66
  end
58
67
 
59
68
  def track event
60
- @to_sync << Pathname.new(event.absolute_name).relative_path_from(@pathname).to_s
69
+ path = event.absolute_name
70
+ watch path if File.directory?(path) and :create.in? event.flags
71
+ @to_sync << Pathname.new(path).relative_path_from(@pathname).to_s
72
+ end
73
+
74
+ def watch dir
75
+ @watcher.dir_rwatch dir, &method(:track)
61
76
  end
62
77
 
63
78
  def schedule
@@ -68,7 +83,7 @@ module LiveSync
68
83
  return if @rsync.running?
69
84
  @watcher.process # calls #track
70
85
  return if @to_sync.blank?
71
- @rsync.from_list @to_sync
86
+ @rsync.partial @to_sync
72
87
  @to_sync.clear
73
88
  ensure
74
89
  schedule
@@ -1,5 +1,5 @@
1
1
  module LiveSync
2
2
 
3
- VERSION = '1.0.0.beta1'
3
+ VERSION = '1.0.0.beta3'
4
4
 
5
5
  end
@@ -4,13 +4,16 @@ module LiveSync
4
4
  attr_reader :notifier
5
5
  delegate_missing_to :notifier
6
6
 
7
- def initialize
7
+ attr_reader :sync
8
+
9
+ def initialize sync=nil
10
+ @sync = sync
8
11
  @notifier = INotify::Notifier.new
9
12
  end
10
13
 
11
14
  def watch path, *modes
12
- Log.info "#{path}: watching for #{modes.join ','}"
13
- modes = %i[all_events] if modes.blank?
15
+ Log.debug "#{path}: watching for #{modes.join ','}"
16
+ modes = %i[all_events] if modes.blank?
14
17
 
15
18
  notifier.watch path, *modes do |event|
16
19
  yield event
@@ -20,15 +23,26 @@ module LiveSync
20
23
 
21
24
  def dir_rwatch path, *modes
22
25
  raise "#{path}: not a directory" unless File.directory? path
23
- modes = %i[create modify] if modes.blank?
26
+ modes = %i[create modify] if modes.blank?
27
+ modes << :delete if sync&.delete&.in? [true, :watched]
28
+
29
+ excs = sync&.excludes.map{ |e| Regexp.new e } || []
30
+ tgts = [path] + Dir.glob("#{path}/{.**,**}/")
31
+ rtgts = tgts.map{ |t| Pathname.new(t).relative_path_from(path).to_s }
32
+ excs.each do |e|
33
+ next unless mt = rtgts.find{ |rt| e.match rt }
34
+ Log.debug "watcher: skipping #{path}/#{mt} with subdirs"
35
+ rtgts.delete mt
36
+ rtgts.delete_if{ |rt| rt.start_with? mt } if File.directory? "#{path}/#{mt}"
37
+ end
24
38
 
25
- tgts = [path] + Dir.glob("#{path}/**/*/")
26
- tgts.each do |t|
39
+ rtgts.each do |rt|
40
+ t = "#{path}/#{rt}"
27
41
  notifier.watch t, *modes do |event|
28
42
  yield event
29
43
  end
30
44
  rescue => e
31
- Log.warning "#{t}: skipping due to #{e.class}: #{e.message}"
45
+ Log.warning "watcher: #{t}: skipping due to #{e.class}: #{e.message}"
32
46
  end
33
47
  self
34
48
  end
data/livesync.service ADDED
@@ -0,0 +1,13 @@
1
+ [Unit]
2
+ Description=Livesync Daemon
3
+ After=network-online.target
4
+ Wants=network-online.target
5
+
6
+ [Service]
7
+ Type=Simple
8
+ ExecStart=/usr/bin/livesync /etc/livesync/config.rb
9
+ Restart=always
10
+
11
+ [Install]
12
+ WantedBy=multi-user.target
13
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-livesync
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta1
4
+ version: 1.0.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Braulio Oliveira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-12 00:00:00.000000000 Z
11
+ date: 2024-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -75,6 +75,7 @@ extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - bin/livesync
78
+ - config/sample.rb
78
79
  - lib/ruby-livesync.rb
79
80
  - lib/ruby-livesync/live_sync.rb
80
81
  - lib/ruby-livesync/live_sync/daemon.rb
@@ -86,6 +87,7 @@ files:
86
87
  - lib/ruby-livesync/live_sync/user.rb
87
88
  - lib/ruby-livesync/live_sync/version.rb
88
89
  - lib/ruby-livesync/live_sync/watcher.rb
90
+ - livesync.service
89
91
  homepage: https://github.com/brauliobo/ruby-livesync
90
92
  licenses:
91
93
  - GPLv3