ruby-livesync 1.0.0.beta1 → 1.0.0.beta2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7413a1c3e43254fbaa87386c88f0a550ec6e9f7a17331e74daa182c3aee7ee67
4
- data.tar.gz: 34320b16a5886835146b9eb89a408e3481b9afdd62eafbd450ec2b83767aa8a9
3
+ metadata.gz: 7d19adaa07cc52746ac91f99dc954b5b85c3d1150b41ffb4e789c453efe404ae
4
+ data.tar.gz: bf4db87f65060754f52063a41dfc27094027fd8165e4b722f051e6af069dd6cf
5
5
  SHA512:
6
- metadata.gz: 604aec4e0bfa4cf29cfbfec5837672d36289e98af3a537cf6dc4ad11706110a2265f079ace49820926eaf5358160caae85b6042ae15b855fa3af4fd4cd0ef012
7
- data.tar.gz: fc87708fba1a5db5c1a88eb81bca51268a80bcf2d30bfe9ddf80d7df9656ba3c2c28de7558e8abe375809a8432ec819de5afbe88e6a7295a19a3b0d7b1067696
6
+ metadata.gz: 4caca9a0bb7a879d9133d0aadf74768f2252866f6b5729eb45935ee5d975bb7029a77a40d2e9ada50026ee196bf2ca1a54e5aa4169ca0e87892670a8dc818587
7
+ data.tar.gz: 50b8a7101b60a5f4b29906f38b92a1cfc1671ebac8cd7aba1fe78ae879cda7d4f2a441ec248e68a7201959f356e12dc0e5583c8cee6e9ea234ba8290b0385e7a
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,6 +23,8 @@ 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
30
  raise "#{ctx}: source not found" unless File.exist? source
@@ -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
+ @watcher.dir_rwatch source, &method(:track)
53
62
  @rsync.initial
54
63
  schedule
55
64
  sleep 1.day while true
@@ -68,7 +77,7 @@ module LiveSync
68
77
  return if @rsync.running?
69
78
  @watcher.process # calls #track
70
79
  return if @to_sync.blank?
71
- @rsync.from_list @to_sync
80
+ @rsync.partial @to_sync
72
81
  @to_sync.clear
73
82
  ensure
74
83
  schedule
@@ -1,5 +1,5 @@
1
1
  module LiveSync
2
2
 
3
- VERSION = '1.0.0.beta1'
3
+ VERSION = '1.0.0.beta2'
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.beta2
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