ruby-livesync 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|