daemons 0.0.1
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 +91 -0
- data/Rakefile +71 -0
- data/Releases +6 -0
- data/TODO +2 -0
- data/lib/daemons.rb +308 -0
- data/lib/daemons/cmdline.rb +101 -0
- data/lib/daemons/daemonize.rb +146 -0
- data/lib/daemons/exceptions.rb +28 -0
- data/lib/daemons/pidfile.rb +120 -0
- data/setup.rb +1360 -0
- data/test/tc_main.rb +24 -0
- data/test/test1.rb +19 -0
- data/test/testapp.rb +11 -0
- metadata +54 -0
data/README
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
= Daemons Version 0.0.1
|
2
|
+
|
3
|
+
(See Releases for release-specific information)
|
4
|
+
|
5
|
+
== What is Daemons?
|
6
|
+
|
7
|
+
Daemons provides an easy way to wrap existing ruby scripts (for example a self-written server)
|
8
|
+
to be <i>run as a daemon</i> and to be <i>controlled by simple start/stop/restart commands</i>.
|
9
|
+
|
10
|
+
Daemons includes the <tt>daemonize.rb</tt> script written by <i>Travis Whitton</i> to do the daemonization
|
11
|
+
process.
|
12
|
+
|
13
|
+
== Basic Usage
|
14
|
+
|
15
|
+
Layout: suppose you have your self-written server <tt>myserver.rb</tt>:
|
16
|
+
|
17
|
+
# this is myserver.rb
|
18
|
+
# it does nothing really useful at the moment
|
19
|
+
|
20
|
+
loop do
|
21
|
+
sleep(5)
|
22
|
+
end
|
23
|
+
|
24
|
+
To use <tt>myserver.rb</tt> in a production environment, you need to be able to
|
25
|
+
run <tt>myserver.rb</tt> in the _background_ (this means detach it from the console, fork it
|
26
|
+
in the background, release any directories and file descriptors).
|
27
|
+
|
28
|
+
Just create <tt>myserver_control.rb</tt> like this:
|
29
|
+
|
30
|
+
# this is myserver_control.rb
|
31
|
+
|
32
|
+
require 'rubygems' # if you use RubyGems
|
33
|
+
require 'daemons'
|
34
|
+
|
35
|
+
Daemons.run('myserver.rb')
|
36
|
+
|
37
|
+
And use it like this from the console:
|
38
|
+
|
39
|
+
$ ruby myserver_control.rb start
|
40
|
+
(myserver.rb is now running in the background)
|
41
|
+
$ ruby myserver_control.rb restart
|
42
|
+
(...)
|
43
|
+
$ ruby myserver_control.rb stop
|
44
|
+
|
45
|
+
For testing purposes you can even run <tt>myserver.rb</tt> <i>without forking</i> in the background:
|
46
|
+
|
47
|
+
$ ruby myserver_control.rb run
|
48
|
+
|
49
|
+
An additional nice feature of Daemons is that you can pass <i>additional arguments</i> to the script that
|
50
|
+
should be daemonized by seperating them by two _hyphens_:
|
51
|
+
|
52
|
+
$ ruby myserver_control.rb start -- --file=anyfile --a_switch another_argument
|
53
|
+
|
54
|
+
For further documentation, refer to the module documentation of Daemons.
|
55
|
+
|
56
|
+
|
57
|
+
== Download and Installation
|
58
|
+
|
59
|
+
*Download*: just go to http://rubyforge.org/projects/daemons/
|
60
|
+
|
61
|
+
Installation *with* RubyGems:
|
62
|
+
$ su
|
63
|
+
# gem install daemons
|
64
|
+
|
65
|
+
Installation *without* RubyGems:
|
66
|
+
$ tar xfz daemons-x.x.x.tar.gz
|
67
|
+
$ cd daemons-x.x.x
|
68
|
+
$ su
|
69
|
+
# ruby setup.rb
|
70
|
+
|
71
|
+
== Documentation
|
72
|
+
|
73
|
+
For further documentation, refer to the module documentation of Daemons (click on Daemons).
|
74
|
+
|
75
|
+
The RDoc documentation is also online at http://daemons.rubyforge.org
|
76
|
+
|
77
|
+
|
78
|
+
== Author
|
79
|
+
|
80
|
+
Written in 2005 by Thomas Uehlinger <mailto:th.uehlinger@gmx.ch>
|
81
|
+
|
82
|
+
== License
|
83
|
+
|
84
|
+
The Daemons script is copywrited free software by Thomas Uehlinger
|
85
|
+
<mailto:th.uehlinger@gmx.ch>. You can redistribute it under the terms specified in
|
86
|
+
the COPYING file of the Ruby distribution.
|
87
|
+
|
88
|
+
|
89
|
+
== Feedback and other resources
|
90
|
+
|
91
|
+
At http://rubyforge.org/projects/daemons.
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
Gem::manage_gems
|
3
|
+
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rake/packagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
|
9
|
+
$LOAD_PATH << './lib'
|
10
|
+
require 'daemons'
|
11
|
+
|
12
|
+
|
13
|
+
PKG_NAME = "daemons"
|
14
|
+
|
15
|
+
PKG_FILES = FileList[
|
16
|
+
"Rakefile", "Releases", "TODO", "README",
|
17
|
+
"setup.rb",
|
18
|
+
"lib/**/*.rb",
|
19
|
+
"test/**/*"
|
20
|
+
]
|
21
|
+
PKG_FILES.exclude(%r(^test/tmp/.+))
|
22
|
+
|
23
|
+
|
24
|
+
spec = Gem::Specification.new do |s|
|
25
|
+
s.name = PKG_NAME
|
26
|
+
#s.version = "0.0.1"
|
27
|
+
s.version = Daemons::VERSION
|
28
|
+
s.author = "Thomas Uehlinger"
|
29
|
+
s.email = "th.uehlinger@gmx.ch"
|
30
|
+
s.homepage = "http://daemons.rubyforge.org"
|
31
|
+
s.platform = Gem::Platform::RUBY
|
32
|
+
s.summary = "A toolkit to convert your script to a controllable daemon"
|
33
|
+
#s.files = FileList["{test,lib}/**/*"].exclude("rdoc").to_a
|
34
|
+
s.files = PKG_FILES
|
35
|
+
s.require_path = "lib"
|
36
|
+
s.autorequire = "daemons"
|
37
|
+
s.test_file = "test/tc_main.rb"
|
38
|
+
s.has_rdoc = true
|
39
|
+
s.extra_rdoc_files = ["README", "Releases", "TODO"]
|
40
|
+
end
|
41
|
+
|
42
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
43
|
+
pkg.need_tar = true
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
#Rake::PackageTask.new("package") do |p|
|
48
|
+
# p.name = PKG_NAME
|
49
|
+
# p.version = Daemons::VERSION
|
50
|
+
# p.need_tar = true
|
51
|
+
# p.need_zip = true
|
52
|
+
# p.package_files = PKG_FILES
|
53
|
+
#end
|
54
|
+
|
55
|
+
|
56
|
+
task :default => [:test]
|
57
|
+
|
58
|
+
Rake::TestTask.new(:test) do |t|
|
59
|
+
t.test_files = FileList['test/tc_*.rb']
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
desc "Create the RDOC html files"
|
64
|
+
rd = Rake::RDocTask.new("rdoc") { |rdoc|
|
65
|
+
rdoc.rdoc_dir = 'html'
|
66
|
+
rdoc.title = "Daemons"
|
67
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
68
|
+
rdoc.rdoc_files.include('README', 'TODO', 'Releases')
|
69
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
70
|
+
rdoc.rdoc_files.include('test/**/*.rb')
|
71
|
+
}
|
data/Releases
ADDED
data/TODO
ADDED
data/lib/daemons.rb
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'optparse/time'
|
3
|
+
#require 'ostruct'
|
4
|
+
|
5
|
+
require 'daemons/pidfile'
|
6
|
+
require 'daemons/cmdline'
|
7
|
+
require 'daemons/exceptions'
|
8
|
+
|
9
|
+
|
10
|
+
# All functions and classes that Daemons provides reside in this module.
|
11
|
+
#
|
12
|
+
# The function you should me most interested in is Daemons#run, because it is
|
13
|
+
# the only function you need to invoke directly from your scripts.
|
14
|
+
#
|
15
|
+
# Also, you are maybe interested in reading the documentation for the class PidFile.
|
16
|
+
# There you can find out about how Daemons works internally and how and where the so
|
17
|
+
# called <i>Pid-Files</i> are stored.
|
18
|
+
#
|
19
|
+
module Daemons
|
20
|
+
|
21
|
+
VERSION = "0.0.1"
|
22
|
+
|
23
|
+
require 'daemons/daemonize'
|
24
|
+
|
25
|
+
|
26
|
+
class Application
|
27
|
+
|
28
|
+
attr_accessor :app_argv
|
29
|
+
attr_accessor :controller_argv
|
30
|
+
|
31
|
+
attr_reader :pid_file
|
32
|
+
|
33
|
+
|
34
|
+
def initialize(group, pid_file = nil)
|
35
|
+
@group = group
|
36
|
+
|
37
|
+
@pid_file = (pid_file || PidFile.new(pidfile_dir(), @group.app_name, @group.multiple))
|
38
|
+
end
|
39
|
+
|
40
|
+
def script
|
41
|
+
@script || @group.script
|
42
|
+
end
|
43
|
+
|
44
|
+
def pidfile_dir
|
45
|
+
PidFile.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script)
|
46
|
+
end
|
47
|
+
|
48
|
+
def start
|
49
|
+
#puts "starting..."
|
50
|
+
|
51
|
+
unless @group.controller.options[:ontop]
|
52
|
+
Daemonize.daemonize()
|
53
|
+
end
|
54
|
+
|
55
|
+
@pid_file.write
|
56
|
+
|
57
|
+
at_exit {
|
58
|
+
@pid_file.remove rescue nil
|
59
|
+
}
|
60
|
+
|
61
|
+
trap('TERM') {
|
62
|
+
@pid_file.remove rescue nil
|
63
|
+
exit
|
64
|
+
}
|
65
|
+
|
66
|
+
run()
|
67
|
+
end
|
68
|
+
|
69
|
+
def run
|
70
|
+
$DAEMONS_ARGV = @controller_argv
|
71
|
+
|
72
|
+
ARGV.clear
|
73
|
+
ARGV.concat @app_argv if @app_argv
|
74
|
+
|
75
|
+
load script()
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop
|
79
|
+
Process.kill('TERM', @pid_file.read)
|
80
|
+
|
81
|
+
# begin
|
82
|
+
# @pidfile.remove
|
83
|
+
# rescue ::Exception
|
84
|
+
# end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
class ApplicationGroup
|
90
|
+
|
91
|
+
attr_reader :app_name
|
92
|
+
attr_reader :script
|
93
|
+
|
94
|
+
attr_reader :controller
|
95
|
+
|
96
|
+
# True if one can start multiple instances of the application
|
97
|
+
#attr_reader :multiple
|
98
|
+
|
99
|
+
#attr_reader :options
|
100
|
+
|
101
|
+
attr_reader :applications
|
102
|
+
|
103
|
+
attr_accessor :controller_argv
|
104
|
+
attr_accessor :app_argv
|
105
|
+
|
106
|
+
attr_accessor :dir_mode
|
107
|
+
attr_accessor :dir
|
108
|
+
|
109
|
+
attr_reader :multiple
|
110
|
+
|
111
|
+
def initialize(app_name, script, controller) #multiple = false)
|
112
|
+
@app_name = app_name
|
113
|
+
@script = script
|
114
|
+
@controller = controller
|
115
|
+
|
116
|
+
options = controller.options
|
117
|
+
|
118
|
+
@multiple = options[:multiple] || false
|
119
|
+
|
120
|
+
@dir_mode = options[:dir_mode] || :script
|
121
|
+
@dir = options[:dir] || ''
|
122
|
+
|
123
|
+
#@multiple = multiple
|
124
|
+
|
125
|
+
@applications = find_applications(pidfile_dir())
|
126
|
+
end
|
127
|
+
|
128
|
+
def pidfile_dir
|
129
|
+
PidFile.dir(@dir_mode, @dir, script)
|
130
|
+
end
|
131
|
+
|
132
|
+
def find_applications(dir)
|
133
|
+
pid_files = PidFile.find_files(dir, app_name)
|
134
|
+
|
135
|
+
#pp pid_files
|
136
|
+
|
137
|
+
return pid_files.map {|f| Application.new(self, PidFile.existing(f))}
|
138
|
+
end
|
139
|
+
|
140
|
+
def new_application(script = nil)
|
141
|
+
if @applications.size > 0 and not @multiple
|
142
|
+
raise RuntimeException.new('there is already one or more instance(s) of the program running')
|
143
|
+
end
|
144
|
+
|
145
|
+
app = Application.new(self)
|
146
|
+
|
147
|
+
app.controller_argv = @controller_argv
|
148
|
+
app.app_argv = @app_argv
|
149
|
+
|
150
|
+
@applications << app
|
151
|
+
|
152
|
+
return app
|
153
|
+
end
|
154
|
+
|
155
|
+
def start_all
|
156
|
+
@applications.each {|a| fork { a.start } }
|
157
|
+
end
|
158
|
+
|
159
|
+
def stop_all
|
160
|
+
@applications.each {|a| a.stop}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
class Controller
|
167
|
+
|
168
|
+
attr_reader :app_name
|
169
|
+
attr_reader :options
|
170
|
+
|
171
|
+
|
172
|
+
COMMANDS = [
|
173
|
+
'start',
|
174
|
+
'stop',
|
175
|
+
'restart',
|
176
|
+
'run'
|
177
|
+
]
|
178
|
+
|
179
|
+
def initialize(script, argv = [])
|
180
|
+
@argv = argv
|
181
|
+
@script = File.expand_path(script)
|
182
|
+
|
183
|
+
@app_name = File.split(@script)[1]
|
184
|
+
|
185
|
+
@command, @controller_part, @app_part = Controller.split_argv(argv)
|
186
|
+
|
187
|
+
#@options[:dir_mode] ||= :script
|
188
|
+
|
189
|
+
@optparse = Optparse.new(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
def setup_options
|
194
|
+
#@options[:ontop] ||= true
|
195
|
+
end
|
196
|
+
|
197
|
+
def run(options = {})
|
198
|
+
@options = options
|
199
|
+
|
200
|
+
@options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
|
201
|
+
|
202
|
+
setup_options()
|
203
|
+
|
204
|
+
#pp @options
|
205
|
+
|
206
|
+
@group = ApplicationGroup.new(@app_name, @script, self) #options)
|
207
|
+
@group.controller_argv = @controller_part
|
208
|
+
@group.app_argv = @app_part
|
209
|
+
|
210
|
+
case @command
|
211
|
+
when 'start'
|
212
|
+
@group.new_application.start
|
213
|
+
when 'run'
|
214
|
+
@group.new_application.run
|
215
|
+
when 'stop'
|
216
|
+
@group.stop_all
|
217
|
+
when 'restart'
|
218
|
+
@group.stop_all
|
219
|
+
sleep 1
|
220
|
+
@group.start_all
|
221
|
+
when nil
|
222
|
+
raise CmdException.new('no command given')
|
223
|
+
#puts "ERROR: No command given"; puts
|
224
|
+
|
225
|
+
#print_usage()
|
226
|
+
#raise('usage function not implemented')
|
227
|
+
else
|
228
|
+
raise Error.new("not implemented command '#{@command}'")
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# Split an _argv_ array.
|
234
|
+
# +argv+ is assumed to be in the following format:
|
235
|
+
# ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
|
236
|
+
#
|
237
|
+
# <tt>command</tt> must be one of the commands listed in <tt>COMMANDS</tt>
|
238
|
+
#
|
239
|
+
# *Returns*: the command as a string, the controller options as an array, the appliation options
|
240
|
+
# as an array
|
241
|
+
#
|
242
|
+
def Controller.split_argv(argv)
|
243
|
+
argv = argv.dup
|
244
|
+
|
245
|
+
command = nil
|
246
|
+
controller_part = []
|
247
|
+
app_part = []
|
248
|
+
|
249
|
+
if COMMANDS.include? argv[0]
|
250
|
+
command = argv.shift
|
251
|
+
end
|
252
|
+
|
253
|
+
if i = argv.index('--')
|
254
|
+
controller_part = argv[0..i-1]
|
255
|
+
app_part = argv[i+1..-1]
|
256
|
+
else
|
257
|
+
controller_part = argv[0..-1]
|
258
|
+
end
|
259
|
+
|
260
|
+
return command, controller_part, app_part
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
# Passes control to Daemons.
|
266
|
+
#
|
267
|
+
# +script+:: This is the path to the script that should be run as a daemon.
|
268
|
+
# Please note that Daemons runs this script with <tt>load <script></tt>.
|
269
|
+
# Also note that Daemons cannot detect the directory in which the controlling
|
270
|
+
# script resides, so this has to be either an absolute path or you have to run
|
271
|
+
# the controlling script from the appropriate directory.
|
272
|
+
#
|
273
|
+
# +options+:: A hash that may contain one or more of options listed below
|
274
|
+
#
|
275
|
+
# === Options:
|
276
|
+
# <tt>:dir_mode</tt>:: Either <tt>:script</tt> (the directory for writing the pid files to
|
277
|
+
# given by <tt>:dir</tt> is interpreted relative
|
278
|
+
# to the script location given by +script+) or <tt>:normal</tt> (the directory given by
|
279
|
+
# <tt>:dir</tt> is interpreted relative to the current directory) or <tt>:system</tt>
|
280
|
+
# (<tt>/var/run</tt> is used as the pid file directory)
|
281
|
+
#
|
282
|
+
# <tt>:dir</tt>:: Used in combination with <tt>:dir_mode</tt> (description above)
|
283
|
+
# <tt>:multiple</tt>:: Specifies whether multiple instances of the same script are allowed to run at the
|
284
|
+
# same time
|
285
|
+
#
|
286
|
+
# -----
|
287
|
+
#
|
288
|
+
# === Example:
|
289
|
+
# options = {
|
290
|
+
# :dir_mode => :script,
|
291
|
+
# :dir => 'pids',
|
292
|
+
# :multiple => true
|
293
|
+
# }
|
294
|
+
#
|
295
|
+
# Daemons.run(File.join(File.split(__FILE__)[0], 'myscript.rb'), options)
|
296
|
+
#
|
297
|
+
def run(script, options = {})
|
298
|
+
@controller = Controller.new(script, ARGV)
|
299
|
+
|
300
|
+
#pp @controller
|
301
|
+
|
302
|
+
@controller.catch_exceptions {
|
303
|
+
@controller.run(options)
|
304
|
+
}
|
305
|
+
end
|
306
|
+
module_function :run
|
307
|
+
|
308
|
+
end
|