rolo 1.0.2
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.
- data/README.md +111 -0
- data/bin/rolo +144 -0
- metadata +47 -0
data/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
## NAME
|
|
2
|
+
|
|
3
|
+
`rolo.rb` -- Prevents a program from running more than one copy at a time
|
|
4
|
+
|
|
5
|
+
## SYNOPSIS
|
|
6
|
+
|
|
7
|
+
<pre>
|
|
8
|
+
$0 [-v] [--test] [-a address] -p port command [arguments]
|
|
9
|
+
</pre>
|
|
10
|
+
|
|
11
|
+
## DESCRIPTION
|
|
12
|
+
|
|
13
|
+
`rolo.rb` prevents a program from running more than one copy at a time;
|
|
14
|
+
it is useful with cron to make sure that a job doesn't run before a
|
|
15
|
+
previous one has finished. `robo.rb` is a ruby version of Timothy
|
|
16
|
+
program `solo` with more options.
|
|
17
|
+
|
|
18
|
+
## OPTIONS
|
|
19
|
+
|
|
20
|
+
* `-v` (`--verbose`)
|
|
21
|
+
Print verbose message
|
|
22
|
+
* `-t` (`--test`)
|
|
23
|
+
Test of program is running. Don't execute command.
|
|
24
|
+
* `-a` (`--address`)
|
|
25
|
+
Address to check / listen on. By default, this address is
|
|
26
|
+
`127.x.y.1:<port>` where `x.y` is translated from process's user
|
|
27
|
+
identity number and this allows two different users on the system
|
|
28
|
+
can use the same port with `rolo.rb`
|
|
29
|
+
* `-p` (`--port`)
|
|
30
|
+
Port to check / on which rolo will listen
|
|
31
|
+
|
|
32
|
+
In `<command>` and `<arguments>`, you can use `%address`, `%port` which
|
|
33
|
+
are replaced by the socket address and port that the problem uses to
|
|
34
|
+
check for status of your command. This is very useful if your command
|
|
35
|
+
closes all file descriptors at the time it runs, but it has some ways
|
|
36
|
+
to listen on `%address:%port`. See EXAMPLE for details.
|
|
37
|
+
|
|
38
|
+
## HOW IT WORKS
|
|
39
|
+
|
|
40
|
+
Before starting your `<command>` (using `exec`), `rolo.rb` will open a
|
|
41
|
+
socket on a local address (or address specified by option `--address`.)
|
|
42
|
+
This socket will be closed after your command exits, and as long as
|
|
43
|
+
your command is running, we have a chance to check its status by
|
|
44
|
+
checking the status of this socket. If it is still open when `rolo.rb`
|
|
45
|
+
is invoked, `rolo.rb` exits without invoking a new instance of command.
|
|
46
|
+
|
|
47
|
+
However, if your `<command>` closes all file descriptors at the time it
|
|
48
|
+
is executed, `rolo.rb` will be sucked. (See `EXAMPLE` for details and for
|
|
49
|
+
a trick when using `rolo.rb` with `ssh`.) If that the cases, you may
|
|
50
|
+
use the option `--address` and `--port` to specify a socket that your
|
|
51
|
+
command binds on.
|
|
52
|
+
|
|
53
|
+
## EXAMPLE
|
|
54
|
+
|
|
55
|
+
To create tunnel to a remote server, you can use this ssh command
|
|
56
|
+
|
|
57
|
+
<pre>
|
|
58
|
+
ssh -fN remote -L localhost:1234:localhost:10000
|
|
59
|
+
</pre>
|
|
60
|
+
|
|
61
|
+
This allows you to connect to the local port 1234 on your mahince
|
|
62
|
+
as same as conneting to address `localhost:10000` on remote server.
|
|
63
|
+
The process `ssh` will go to background immediately after it authenticates
|
|
64
|
+
successfully with the remote.
|
|
65
|
+
|
|
66
|
+
To keep this tunnel persistent, you can add this to your crontab
|
|
67
|
+
|
|
68
|
+
<pre>
|
|
69
|
+
rolo.rb -p 4567 \
|
|
70
|
+
ssh remote -fNL localhost:1234:localhost:10000
|
|
71
|
+
</pre>
|
|
72
|
+
|
|
73
|
+
and allows this line to be executed once every 5 minutes. `rolo.rb`
|
|
74
|
+
will check if your ssh command is still running. If 'yes', it will
|
|
75
|
+
simply exit; if 'no', `rolo.rb` will start the ssh command.
|
|
76
|
+
|
|
77
|
+
However, if you use *OpenSSH 5.6p1* (or later), `ssh` will close all file
|
|
78
|
+
descriptors from the parent (except for `STDIN`, `STDOUT` and `STDERR`).
|
|
79
|
+
As the socket opened by `rolo.rb` is closed, `rolo.rb` will always
|
|
80
|
+
start new instance of the `ssh` tunnel. (Actually I had process `bomb`
|
|
81
|
+
on my system when I used the original program `solo` to launch my
|
|
82
|
+
tunnels.)
|
|
83
|
+
|
|
84
|
+
Fortunately, `ssh` has option to bind on the local address.
|
|
85
|
+
Using this option we can trick `rolo.rb` as below
|
|
86
|
+
<pre>
|
|
87
|
+
rolo.rb -p 4567 \
|
|
88
|
+
ssh remote -fN \
|
|
89
|
+
-L localhost:1234:localhost:10000 \
|
|
90
|
+
-L %address:%port:localhost:12345
|
|
91
|
+
</pre>
|
|
92
|
+
|
|
93
|
+
The last use of option `-L` will ask `ssh` to open a socket on
|
|
94
|
+
`%address:%port` (the real values will be provided by `rolo.rb`),
|
|
95
|
+
and it will be checked by `rolo.rb` in its next run. Please note that
|
|
96
|
+
we use a random port `12345` to prevent local connections to
|
|
97
|
+
`%address:%port` from being forwarded to remote.
|
|
98
|
+
|
|
99
|
+
Another way is to use option `--address`
|
|
100
|
+
|
|
101
|
+
<pre>
|
|
102
|
+
rolo.rb -p 1234 -a 127.0.0.1 \
|
|
103
|
+
ssh remote -fNL localhost:1234:localhost:10000
|
|
104
|
+
</pre>
|
|
105
|
+
|
|
106
|
+
And this is another way
|
|
107
|
+
|
|
108
|
+
<pre>
|
|
109
|
+
rolo.rb -p 1234 -a 127.0.0.1 \
|
|
110
|
+
ssh remote -fNL %address:%port:localhost:10000
|
|
111
|
+
</pre>
|
data/bin/rolo
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
#
|
|
3
|
+
# Purpose: Prevents a program from running more than one copy at a time
|
|
4
|
+
# Author : Anh K. Huynh
|
|
5
|
+
# License: GPL2
|
|
6
|
+
# Date : 2012 July 16th
|
|
7
|
+
# Source : http://github.com/icy/rolo
|
|
8
|
+
# Link : [1] Tim's solo: http://github.com/timkay/solo/
|
|
9
|
+
# [2] Fork-exec : http://en.wikipedia.org/wiki/Fork-exec
|
|
10
|
+
# [3] Unix fork : http://www-h.eng.cam.ac.uk/help/tpl/unix/fork.html
|
|
11
|
+
# [4] Fork-exec : http://jacktang.github.com/2009/01/04/ruby-fork-exec-socket-hang.html
|
|
12
|
+
# [5] ^F in Perl: http://perldoc.perl.org/perlvar.html#%24^F
|
|
13
|
+
# [6] RubyPaint : http://ruby.runpaint.org/io
|
|
14
|
+
# [7] Secure FD : http://udrepper.livejournal.com/20407.html
|
|
15
|
+
# [8] OpenSSH : http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-5.9p1.tar.gz
|
|
16
|
+
# [9] closefrom : http://www.unix.com/man-page/All/3c/closefrom/
|
|
17
|
+
#
|
|
18
|
+
# Note [5]:
|
|
19
|
+
# The maximum system file descriptor, ordinarily 2. System file descrip-
|
|
20
|
+
# tors are passed to exec()ed processes, while higher file descriptors
|
|
21
|
+
# are not. Also, during an open(), system file descriptors are preserved
|
|
22
|
+
# even if the open() fails (ordinary file descriptors are closed before
|
|
23
|
+
# the open() is attempted). The close-on-exec status of a file descrip-
|
|
24
|
+
# tor will be decided according to the value of $^F when the correspon-
|
|
25
|
+
# ding file, pipe, or socket was opened, not the time of the exec().
|
|
26
|
+
#
|
|
27
|
+
# Note [6]:
|
|
28
|
+
# On a Unix-based system a process created by Kernel.exec, Kernel.fork,
|
|
29
|
+
# or IO.popen inherits the file descriptors of its parent. Depending on
|
|
30
|
+
# the application, this may constitute an information leak in that the
|
|
31
|
+
# child is able to access data that he shouldn’t have access to. If
|
|
32
|
+
# given a true argument, IO#close_on_exec= ensures that its receiver
|
|
33
|
+
# is closed before the new process is created; otherwise, it does not.
|
|
34
|
+
# IO#close_on_exec? returns the status of this flag as either true or
|
|
35
|
+
# false. On systems that don’t support this feature, these methods
|
|
36
|
+
# raise NotImplementedError.
|
|
37
|
+
#
|
|
38
|
+
# Note [8]:
|
|
39
|
+
# In the source code of OpenSSH 5.9p1, ssh.c::268 we can see that all
|
|
40
|
+
# file descriptors from (STDERR_FILENO + 1) will be closed by the
|
|
41
|
+
# function (closefrom) (see [9].) This may cause problem!
|
|
42
|
+
#
|
|
43
|
+
# /*
|
|
44
|
+
# * Discard other fds that are hanging around. These can cause problem
|
|
45
|
+
# * with backgrounded ssh processes started by ControlPersist.
|
|
46
|
+
# */
|
|
47
|
+
# closefrom(STDERR_FILENO + 1);
|
|
48
|
+
#
|
|
49
|
+
# On my system (ArchLinux, 3.3.8), (closefrom) is not found. The method
|
|
50
|
+
# should be used from (openbsd-compact/bsd-closefrom.c).s
|
|
51
|
+
#
|
|
52
|
+
# This behavior is implemented since OpenSSH 5.6p1.
|
|
53
|
+
#
|
|
54
|
+
|
|
55
|
+
require 'socket'
|
|
56
|
+
|
|
57
|
+
class String
|
|
58
|
+
def die(ret = 1, dev = STDERR, prefix = ":: ")
|
|
59
|
+
dev.puts("#{prefix}#{self}")
|
|
60
|
+
exit(ret)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def verbose(v = false)
|
|
64
|
+
STDERR.puts(":: #{self}") if v
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Based on the original code taken from from [4]
|
|
69
|
+
def close_on_exec(att = true)
|
|
70
|
+
ObjectSpace.each_object(IO) do |io|
|
|
71
|
+
begin
|
|
72
|
+
if io.respond_to?(:close_on_exec?)
|
|
73
|
+
io.close_on_exec = attr
|
|
74
|
+
else
|
|
75
|
+
io.fcntl(Fcntl::F_SETFD, attr ? Fcntl::FD_CLOEXEC : 0)
|
|
76
|
+
end
|
|
77
|
+
rescue ::Exception => err
|
|
78
|
+
end unless [STDIN, STDOUT, STDERR].include?(io) or io.closed?
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
OPTIONS = {:verbose => false, :port => 0}
|
|
83
|
+
|
|
84
|
+
"Syntax: rolo [--verbose] [--test] [--address <address>] --port <port_number> <command> [<arguments>]".die if ARGV.empty?
|
|
85
|
+
|
|
86
|
+
while true
|
|
87
|
+
f = ARGV.first
|
|
88
|
+
if %w{-p --port}.include?(f)
|
|
89
|
+
ARGV.shift
|
|
90
|
+
OPTIONS[:port] = ARGV.shift.to_s.to_i
|
|
91
|
+
"Port must be a positive number".die if OPTIONS[:port] == 0
|
|
92
|
+
elsif %w{-v --verbose}.include?(f)
|
|
93
|
+
OPTIONS[:verbose] = true
|
|
94
|
+
ARGV.shift
|
|
95
|
+
elsif %w{-a --address}.include?(f)
|
|
96
|
+
ARGV.shift
|
|
97
|
+
OPTIONS[:address] = ARGV.shift.to_s.strip
|
|
98
|
+
"Invalid address '#{OPTIONS[:address]}' was provided. Should be in format 'x.y.z.t'".die \
|
|
99
|
+
unless OPTIONS[:address].match(/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/)
|
|
100
|
+
elsif %w{-m --message}.include?(f)
|
|
101
|
+
ARGV.shift
|
|
102
|
+
OPTIONS[:message] = ARGV.shift
|
|
103
|
+
elsif %w{-t --test}.include?(f)
|
|
104
|
+
OPTIONS[:test] = true
|
|
105
|
+
ARGV.shift
|
|
106
|
+
elsif %w{--}.include?(f)
|
|
107
|
+
ARGV.shift
|
|
108
|
+
break
|
|
109
|
+
else
|
|
110
|
+
break
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
"Port must be a positive number".die if OPTIONS[:port] == 0
|
|
115
|
+
|
|
116
|
+
cmd = ARGV.shift.to_s
|
|
117
|
+
"You must provide a command".die if cmd.empty?
|
|
118
|
+
cmd = "#{cmd} #{ARGV.join(' ')}" unless ARGV.empty?
|
|
119
|
+
|
|
120
|
+
address = OPTIONS[:address] || [127, Process.uid, 1].pack("CnC").unpack("C4").join(".")
|
|
121
|
+
|
|
122
|
+
cmd = cmd.gsub("%port", OPTIONS[:port].to_s).gsub("%address", address)
|
|
123
|
+
|
|
124
|
+
("Will bind on %s:%d, command = '%s'" \
|
|
125
|
+
% [address, OPTIONS[:port], cmd]).verbose(OPTIONS[:verbose])
|
|
126
|
+
|
|
127
|
+
# Taken from example in the source code documetation
|
|
128
|
+
# Link: http://ruby-doc.org/stdlib-1.8.7/
|
|
129
|
+
# libdoc/socket/rdoc/Socket.html#method-i-bind
|
|
130
|
+
begin
|
|
131
|
+
include Socket::Constants
|
|
132
|
+
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
|
|
133
|
+
socket.bind(Socket.pack_sockaddr_in(OPTIONS[:port], address))
|
|
134
|
+
socket.close if OPTIONS[:address]
|
|
135
|
+
rescue Errno::EADDRINUSE
|
|
136
|
+
(OPTIONS[:message] || ":: Address is in use. Is your application running?").die(0, STDOUT, "")
|
|
137
|
+
rescue => e
|
|
138
|
+
e.to_s.die(1)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
unless OPTIONS[:test]
|
|
142
|
+
close_on_exec(false)
|
|
143
|
+
exec cmd
|
|
144
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rolo
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.2
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Anh K. Huynh
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-07-20 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: Prevents a program from running more than one copy at a time
|
|
15
|
+
email: kyanh@viettug.org
|
|
16
|
+
executables:
|
|
17
|
+
- rolo
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- README.md
|
|
22
|
+
- bin/rolo
|
|
23
|
+
homepage: https://github.com/icy/rolo
|
|
24
|
+
licenses: []
|
|
25
|
+
post_install_message:
|
|
26
|
+
rdoc_options: []
|
|
27
|
+
require_paths:
|
|
28
|
+
- lib
|
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
30
|
+
none: false
|
|
31
|
+
requirements:
|
|
32
|
+
- - ! '>='
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '0'
|
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
36
|
+
none: false
|
|
37
|
+
requirements:
|
|
38
|
+
- - ! '>='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
requirements: []
|
|
42
|
+
rubyforge_project:
|
|
43
|
+
rubygems_version: 1.8.16
|
|
44
|
+
signing_key:
|
|
45
|
+
specification_version: 3
|
|
46
|
+
summary: ! '`rolo` prevents a program from running more than one copy at a time'
|
|
47
|
+
test_files: []
|