ruby-livesync 1.0.0.beta1
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 +7 -0
- data/bin/livesync +9 -0
- data/lib/ruby-livesync/live_sync/daemon.rb +49 -0
- data/lib/ruby-livesync/live_sync/dsl.rb +38 -0
- data/lib/ruby-livesync/live_sync/log.rb +45 -0
- data/lib/ruby-livesync/live_sync/rsync.rb +45 -0
- data/lib/ruby-livesync/live_sync/ssh.rb +42 -0
- data/lib/ruby-livesync/live_sync/sync.rb +80 -0
- data/lib/ruby-livesync/live_sync/user.rb +13 -0
- data/lib/ruby-livesync/live_sync/version.rb +5 -0
- data/lib/ruby-livesync/live_sync/watcher.rb +37 -0
- data/lib/ruby-livesync/live_sync.rb +14 -0
- data/lib/ruby-livesync.rb +8 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7413a1c3e43254fbaa87386c88f0a550ec6e9f7a17331e74daa182c3aee7ee67
|
4
|
+
data.tar.gz: 34320b16a5886835146b9eb89a408e3481b9afdd62eafbd450ec2b83767aa8a9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 604aec4e0bfa4cf29cfbfec5837672d36289e98af3a537cf6dc4ad11706110a2265f079ace49820926eaf5358160caae85b6042ae15b855fa3af4fd4cd0ef012
|
7
|
+
data.tar.gz: fc87708fba1a5db5c1a88eb81bca51268a80bcf2d30bfe9ddf80d7df9656ba3c2c28de7558e8abe375809a8432ec819de5afbe88e6a7295a19a3b0d7b1067696
|
data/bin/livesync
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Daemon
|
3
|
+
|
4
|
+
def self.start config
|
5
|
+
new(config).start
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :config
|
9
|
+
|
10
|
+
def initialize config
|
11
|
+
@config = config
|
12
|
+
@pids = []
|
13
|
+
@syncs = Hash.new{ |h, k| h[k] = [] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
Process.setpgrp
|
18
|
+
Process.setproctitle 'livesync'
|
19
|
+
instance_eval File.read(config), config
|
20
|
+
run
|
21
|
+
Process.waitall
|
22
|
+
end
|
23
|
+
|
24
|
+
def sync name_or_path, &block
|
25
|
+
s = Sync.new name_or_path
|
26
|
+
s.dsl_apply(&block)
|
27
|
+
@syncs[s.user] << s
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def run
|
33
|
+
@syncs.each do |user, syncs|
|
34
|
+
User.wrap user do
|
35
|
+
syncs.each do |s|
|
36
|
+
s.run
|
37
|
+
s.guard
|
38
|
+
rescue => e
|
39
|
+
msg = e.message
|
40
|
+
msg += "\n#{e.backtrace.join "\n"}" if Log.debug
|
41
|
+
Log.fatal msg
|
42
|
+
end
|
43
|
+
Process.waitall
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module LiveSync
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
|
8
|
+
class_attribute :ctx
|
9
|
+
|
10
|
+
class_attribute :attrs
|
11
|
+
self.attrs = []
|
12
|
+
|
13
|
+
def self.dsl attr, default: nil, type: nil, &block
|
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
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def dsl_apply &block
|
24
|
+
if b = binding and bs = block.source.match(/do(.+)end$/m)&.captures&.first
|
25
|
+
b.eval bs
|
26
|
+
attrs.each do |a| # read local variables
|
27
|
+
next unless a.in? b.local_variables
|
28
|
+
send a, b.local_variable_get(a)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
instance_exec &block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Log
|
3
|
+
|
4
|
+
class_attribute :ctx
|
5
|
+
class_attribute :global
|
6
|
+
self.global = self.new
|
7
|
+
class << self
|
8
|
+
delegate_missing_to :global
|
9
|
+
end
|
10
|
+
|
11
|
+
class_attribute :debug
|
12
|
+
self.debug = !!ENV['DEBUG']
|
13
|
+
|
14
|
+
def initialize ctx=nil
|
15
|
+
self.ctx = ctx
|
16
|
+
end
|
17
|
+
|
18
|
+
def debug msg
|
19
|
+
return unless debug
|
20
|
+
puts "DEBUG: #{parse msg}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def info msg
|
24
|
+
puts "INFO: #{parse msg}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def warning msg
|
28
|
+
STDERR.puts "WARNING: #{parse msg}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def error msg
|
32
|
+
STDERR.puts "ERROR: #{parse msg}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def fatal msg
|
36
|
+
STDERR.puts "FATAL: #{parse msg}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse msg
|
40
|
+
msg = "#{ctx}: #{msg}" if ctx
|
41
|
+
msg
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Rsync
|
3
|
+
|
4
|
+
attr_reader :sync, :opts
|
5
|
+
|
6
|
+
def initialize sync
|
7
|
+
@sync = sync
|
8
|
+
@opts = '-ax --partial'
|
9
|
+
end
|
10
|
+
|
11
|
+
def running?
|
12
|
+
@wait_thr
|
13
|
+
end
|
14
|
+
|
15
|
+
def initial
|
16
|
+
run :initial
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_list paths
|
20
|
+
run :partial, '--files-from=-' do |stdin, stdout, stderr|
|
21
|
+
stdin.write paths.join "\n"
|
22
|
+
stdin.close
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
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}"
|
31
|
+
stdin, stdout, stderr, @wait_thr = Open3.popen3 cmd
|
32
|
+
yield stdin, stdout, stderr if block_given?
|
33
|
+
Thread.new do
|
34
|
+
Process.wait @wait_thr.pid rescue Errno::ECHILD; nil
|
35
|
+
@wait_thr = nil
|
36
|
+
sync.log.info "#{type}: finished"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def rsh
|
41
|
+
"ssh -o ControlPath=#{sync.ssh.cpath}"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module LiveSync
|
5
|
+
class Ssh
|
6
|
+
|
7
|
+
class_attribute :cache
|
8
|
+
self.cache = {}
|
9
|
+
|
10
|
+
def self.connect userhost
|
11
|
+
cache[userhost] ||= new userhost
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :userhost, :cpath
|
15
|
+
|
16
|
+
def initialize userhost
|
17
|
+
@userhost = userhost
|
18
|
+
@tmpfile = Tempfile.new "livesync-controlpath"
|
19
|
+
@cpath = @tmpfile.path
|
20
|
+
File.unlink @cpath
|
21
|
+
connect
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
Thread.new do
|
26
|
+
loop do
|
27
|
+
cmd = "ssh -nN -o ControlMaster=yes -o ControlPath=#{cpath} #{userhost}"
|
28
|
+
stdin, stdout, stderr, @wait_thr = Open3.popen3 cmd
|
29
|
+
Process.wait @wait_thr.pid
|
30
|
+
Log.error "ssh/#{userhost}: #{stderr.read}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
sleep 1 while !available?
|
34
|
+
end
|
35
|
+
|
36
|
+
def available?
|
37
|
+
return false unless File.exist? cpath
|
38
|
+
system "ssh -O check -o ControlPath=#{cpath} dummy > /dev/null 2>&1"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Sync
|
3
|
+
|
4
|
+
include DSL
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :scheduler
|
8
|
+
attr_reader :log
|
9
|
+
attr_reader :watcher
|
10
|
+
attr_reader :ssh
|
11
|
+
attr_reader :rsync
|
12
|
+
|
13
|
+
def initialize name = nil
|
14
|
+
fill_name name
|
15
|
+
source name if File.exist? name
|
16
|
+
@to_sync = Set.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def fill_name name
|
20
|
+
return if @name
|
21
|
+
self.ctx = @name = name
|
22
|
+
@log = Log.new ctx
|
23
|
+
end
|
24
|
+
|
25
|
+
dsl :user, default: :root
|
26
|
+
dsl :source do |source|
|
27
|
+
raise "#{ctx}: source not found" unless File.exist? source
|
28
|
+
fill_name source
|
29
|
+
@pathname = Pathname.new source
|
30
|
+
end
|
31
|
+
|
32
|
+
dsl :target do |target|
|
33
|
+
@userhost, @target_path = target.split(':')
|
34
|
+
raise "#{ctx}: missing target path" unless @target_path
|
35
|
+
@user, @host = if @userhost.index '@' then @userhost.split('@') else [@user, @userhost] end
|
36
|
+
end
|
37
|
+
|
38
|
+
dsl :delay, default: 5, type: Integer
|
39
|
+
|
40
|
+
def run
|
41
|
+
raise "#{ctx}: missing target" unless @target
|
42
|
+
@ssh = Ssh.connect @userhost
|
43
|
+
@rsync = Rsync.new self
|
44
|
+
end
|
45
|
+
|
46
|
+
def guard
|
47
|
+
fork do
|
48
|
+
Process.setproctitle "livesync: sync #{ctx}"
|
49
|
+
@watcher = Watcher.new
|
50
|
+
@scheduler = Rufus::Scheduler.new
|
51
|
+
|
52
|
+
@watcher.dir_rwatch source, *%i[create modify], &method(:track)
|
53
|
+
@rsync.initial
|
54
|
+
schedule
|
55
|
+
sleep 1.day while true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def track event
|
60
|
+
@to_sync << Pathname.new(event.absolute_name).relative_path_from(@pathname).to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def schedule
|
64
|
+
@scheduler.in "#{delay}s", &method(:check)
|
65
|
+
end
|
66
|
+
|
67
|
+
def check
|
68
|
+
return if @rsync.running?
|
69
|
+
@watcher.process # calls #track
|
70
|
+
return if @to_sync.blank?
|
71
|
+
@rsync.from_list @to_sync
|
72
|
+
@to_sync.clear
|
73
|
+
ensure
|
74
|
+
schedule
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Watcher
|
3
|
+
|
4
|
+
attr_reader :notifier
|
5
|
+
delegate_missing_to :notifier
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@notifier = INotify::Notifier.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def watch path, *modes
|
12
|
+
Log.info "#{path}: watching for #{modes.join ','}"
|
13
|
+
modes = %i[all_events] if modes.blank?
|
14
|
+
|
15
|
+
notifier.watch path, *modes do |event|
|
16
|
+
yield event
|
17
|
+
end
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def dir_rwatch path, *modes
|
22
|
+
raise "#{path}: not a directory" unless File.directory? path
|
23
|
+
modes = %i[create modify] if modes.blank?
|
24
|
+
|
25
|
+
tgts = [path] + Dir.glob("#{path}/**/*/")
|
26
|
+
tgts.each do |t|
|
27
|
+
notifier.watch t, *modes do |event|
|
28
|
+
yield event
|
29
|
+
end
|
30
|
+
rescue => e
|
31
|
+
Log.warning "#{t}: skipping due to #{e.class}: #{e.message}"
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module LiveSync
|
2
|
+
|
3
|
+
end
|
4
|
+
|
5
|
+
require_relative 'live_sync/version'
|
6
|
+
require_relative 'live_sync/dsl'
|
7
|
+
require_relative 'live_sync/log'
|
8
|
+
require_relative 'live_sync/user'
|
9
|
+
require_relative 'live_sync/ssh'
|
10
|
+
require_relative 'live_sync/rsync'
|
11
|
+
require_relative 'live_sync/watcher'
|
12
|
+
require_relative 'live_sync/sync'
|
13
|
+
require_relative 'live_sync/daemon'
|
14
|
+
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-livesync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.beta1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Braulio Oliveira
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-05-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pry
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rb-inotify
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rufus-scheduler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- brauliobo@gmail.com
|
72
|
+
executables:
|
73
|
+
- livesync
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- bin/livesync
|
78
|
+
- lib/ruby-livesync.rb
|
79
|
+
- lib/ruby-livesync/live_sync.rb
|
80
|
+
- lib/ruby-livesync/live_sync/daemon.rb
|
81
|
+
- lib/ruby-livesync/live_sync/dsl.rb
|
82
|
+
- lib/ruby-livesync/live_sync/log.rb
|
83
|
+
- lib/ruby-livesync/live_sync/rsync.rb
|
84
|
+
- lib/ruby-livesync/live_sync/ssh.rb
|
85
|
+
- lib/ruby-livesync/live_sync/sync.rb
|
86
|
+
- lib/ruby-livesync/live_sync/user.rb
|
87
|
+
- lib/ruby-livesync/live_sync/version.rb
|
88
|
+
- lib/ruby-livesync/live_sync/watcher.rb
|
89
|
+
homepage: https://github.com/brauliobo/ruby-livesync
|
90
|
+
licenses:
|
91
|
+
- GPLv3
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">"
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 1.3.1
|
107
|
+
requirements: []
|
108
|
+
rubygems_version: 3.3.25
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Lightweight and fast live sync daemon
|
112
|
+
test_files: []
|