bannister 0.0.2 → 0.0.3
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/Rakefile +2 -4
- data/bin/bannister +3 -0
- data/lib/bannister.rb +7 -9
- data/lib/bannister.rb.orig +279 -0
- data/lib/bannister.remote.rb +273 -0
- data/lib/bannister.remote.rb~ +278 -0
- data/lib/bannister/apache.rb +40 -55
- data/lib/bannister/utils.rb +46 -0
- data/lib/bannister/version.rb +1 -1
- data/lib/bannister/version.rb.orig +3 -0
- metadata +9 -4
data/Rakefile
CHANGED
@@ -3,8 +3,6 @@ CLEAN << ".yardoc"
|
|
3
3
|
CLEAN << FileList["*.gem"]
|
4
4
|
CLEAN << "doc"
|
5
5
|
|
6
|
-
DOC_FILES=Dir['*.md']
|
7
|
-
|
8
6
|
desc "Build the gem"
|
9
7
|
task :gem do
|
10
8
|
system "gem build bannister.gemspec"
|
@@ -12,12 +10,12 @@ end
|
|
12
10
|
|
13
11
|
desc "Build the YARD docs"
|
14
12
|
task :docs do
|
15
|
-
system "yardoc
|
13
|
+
system "yardoc"
|
16
14
|
end
|
17
15
|
|
18
16
|
task :doc => :docs
|
19
17
|
|
20
18
|
desc "Build the YARD developer docs, including private methods"
|
21
19
|
task :dev_docs do
|
22
|
-
system "yardoc --private
|
20
|
+
system "yardoc --private"
|
23
21
|
end
|
data/bin/bannister
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'bannister'
|
4
|
+
require 'bannister/version'
|
4
5
|
include Bannister
|
5
6
|
|
6
7
|
|
@@ -16,6 +17,8 @@ when "stop"
|
|
16
17
|
when "restart"
|
17
18
|
stop()
|
18
19
|
start()
|
20
|
+
when "version", "--version", "-v"
|
21
|
+
$stdout.puts ::Bannister::VERSION.to_s
|
19
22
|
else
|
20
23
|
err "usage: #{$0} (run|start|stop|restart)"
|
21
24
|
end
|
data/lib/bannister.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'bannister/utils'
|
2
3
|
|
3
4
|
module Bannister
|
4
5
|
|
@@ -20,7 +21,7 @@ module Bannister
|
|
20
21
|
# Daemonize and launch the managed processes.
|
21
22
|
def start()
|
22
23
|
if running?
|
23
|
-
err "This app is already running! Run #{$0} stop first."
|
24
|
+
err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
|
24
25
|
else
|
25
26
|
daemonize(){ do_start() }
|
26
27
|
wait_for_all()
|
@@ -67,7 +68,6 @@ module Bannister
|
|
67
68
|
def quit
|
68
69
|
kill_all()
|
69
70
|
remove_pid()
|
70
|
-
exit(0)
|
71
71
|
end
|
72
72
|
|
73
73
|
|
@@ -76,14 +76,12 @@ module Bannister
|
|
76
76
|
def stop()
|
77
77
|
old_pid = read_pid()
|
78
78
|
if old_pid
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
nil
|
79
|
+
Process.kill("TERM", old_pid)
|
80
|
+
if ::Bannister::Utils.wait_foreign_pid(old_pid)
|
81
|
+
remove_pid()
|
82
|
+
else
|
83
|
+
err "Bannister (pid=#{old_pid}) failed to die!"
|
85
84
|
end
|
86
|
-
remove_pid()
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'bannister/utils'
|
3
|
+
|
4
|
+
module Bannister
|
5
|
+
|
6
|
+
|
7
|
+
# List of scripts in script/startup sorted asciibetically. This is the order
|
8
|
+
# they will be launched in, but there is no further dependency checking
|
9
|
+
# between the scripts.
|
10
|
+
SCRIPTS = Dir['script/startup/*'].sort
|
11
|
+
|
12
|
+
# A list of pids of the started scripts. These will be sent TERM signals when
|
13
|
+
# bannister is stopped.
|
14
|
+
LAUNCHED_PIDS = []
|
15
|
+
|
16
|
+
# The location of the pidfile for the master process. This will be
|
17
|
+
# de-hard-coded in a future release.
|
18
|
+
PID_FILENAME = "pids/bannister.pid"
|
19
|
+
|
20
|
+
|
21
|
+
# Daemonize and launch the managed processes.
|
22
|
+
def start()
|
23
|
+
if running?
|
24
|
+
err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
|
25
|
+
else
|
26
|
+
daemonize(){ do_start() }
|
27
|
+
wait_for_all()
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Launch the managed processes in screen so that they are all foreground
|
33
|
+
# processes.
|
34
|
+
#
|
35
|
+
# If run from inside an existing screen session, it will be reused, and the
|
36
|
+
# managed processes will be added to it. Otherwise a new screen session is
|
37
|
+
# started.
|
38
|
+
#
|
39
|
+
# Starting a new screen session is done by writing a temporary screenrc
|
40
|
+
# to /tmp, so $HOME/.screenrc is ignored.
|
41
|
+
def run()
|
42
|
+
screenrc_filename = "/tmp/screenrc.#{$$}"
|
43
|
+
|
44
|
+
if ENV['STY'] && !ENV['STY'].empty?
|
45
|
+
# then we already have a screen, so reuse it
|
46
|
+
# by simply running all the commands
|
47
|
+
|
48
|
+
SCRIPTS.each do |script|
|
49
|
+
system( command_for_script(script) )
|
50
|
+
end
|
51
|
+
|
52
|
+
else # create it. The easiest way to do this is to write
|
53
|
+
# out a screenrc containing the relevant commands and
|
54
|
+
# let screen launch them
|
55
|
+
begin
|
56
|
+
write_screenrc(screenrc_filename)
|
57
|
+
|
58
|
+
system "screen","-c",screenrc_filename
|
59
|
+
ensure
|
60
|
+
FileUtils.rm_f screenrc_filename
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Shut down all the managed processes, remove the bannister pidfile and exit.
|
68
|
+
def quit
|
69
|
+
$stderr.reopen("/tmp/bannister.stop.err.log", "wb")
|
70
|
+
$stdout.reopen("/tmp/bannister.stop.out.log", "wb")
|
71
|
+
kill_all()
|
72
|
+
remove_pid()
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
# Send a TERM signal to a #start'ed bannister process.
|
78
|
+
def stop()
|
79
|
+
old_pid = read_pid()
|
80
|
+
if old_pid
|
81
|
+
if ::Bannister::Utils.foreign_pid_alive?(old_pid)
|
82
|
+
Process.kill("TERM", old_pid)
|
83
|
+
if ::Bannister::Utils.wait_foreign_pid(old_pid)
|
84
|
+
remove_pid()
|
85
|
+
else
|
86
|
+
err "Bannister (pid=#{old_pid}) failed to die!"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
err "Bannister (pid=#{old_pid}) wasn't alive."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# Used when you have erred. Shows the passed error message and exits
|
96
|
+
# with a status of 1.
|
97
|
+
#
|
98
|
+
# @param [String] msg Message to display.
|
99
|
+
def err(msg)
|
100
|
+
$stderr.puts(msg)
|
101
|
+
exit(1)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
private
|
107
|
+
# Method to see whether a bannister process for the current application
|
108
|
+
# is running or not.
|
109
|
+
#
|
110
|
+
# @return [Boolean] true if PID_FILENAME exists, false otherwise.
|
111
|
+
def running?
|
112
|
+
return File.exists?(PID_FILENAME)
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# Generate the screen command for the passed script filename. Since this
|
117
|
+
# will only ever be called when running in the foreground, we append -X
|
118
|
+
# to the command. A title for the screen window is also generated based
|
119
|
+
# on the current directory name and the script filename.
|
120
|
+
#
|
121
|
+
# @param [String] script the filename of a script to run.
|
122
|
+
# @return [String] the screen command to run the script.
|
123
|
+
def command_for_script(script)
|
124
|
+
title = File.basename(FileUtils.pwd)+':'+File.basename(script)
|
125
|
+
return "screen -t '#{title}' #{script} -X"
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Generate a screen config file and save it to the passed screenrc_filename.
|
130
|
+
# Screen is set up to allow restarting of dead processes with the 'r' key,
|
131
|
+
# and to give each script a meaningful title in the status line.
|
132
|
+
#
|
133
|
+
# @param [String] screenrc_filename the filename to save the
|
134
|
+
# generated config file to.
|
135
|
+
# @return [NilClass] nil.
|
136
|
+
def write_screenrc(screenrc_filename)
|
137
|
+
File.open(screenrc_filename, "wb") do |f|
|
138
|
+
f.write <<-SCREENRC
|
139
|
+
zombie cr onerror
|
140
|
+
hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"
|
141
|
+
|
142
|
+
SCREENRC
|
143
|
+
SCRIPTS.each do |script|
|
144
|
+
f.puts( command_for_script(script) )
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
return nil
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
# Daemonize the bannister process and yield. Used when running in
|
154
|
+
# the background. This is a slightly different daemonize method to
|
155
|
+
# the usual, in that we need to be sure that the script filenames in
|
156
|
+
# SCRIPT don't lose their relative location, so we chdir to '/'
|
157
|
+
# *after* kicking them off. It's each individual script's
|
158
|
+
# responsibility to daemonise properly.
|
159
|
+
def daemonize()
|
160
|
+
exit!(0) if fork
|
161
|
+
Process::setsid
|
162
|
+
exit!(0) if fork
|
163
|
+
File::umask(0)
|
164
|
+
STDIN.reopen("/dev/null")
|
165
|
+
STDOUT.reopen("/dev/null", "w")
|
166
|
+
STDERR.reopen("/dev/null", "w")
|
167
|
+
|
168
|
+
yield if block_given?
|
169
|
+
|
170
|
+
# chdir after yield so that the script paths remain correct
|
171
|
+
# and if any of them rely on relative paths, they won't get
|
172
|
+
# broken
|
173
|
+
Dir::chdir("/")
|
174
|
+
|
175
|
+
return nil
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# Set up our environment and launch everything in the background.
|
180
|
+
def do_start()
|
181
|
+
$stderr.reopen("/tmp/bannister.start.err.log", "wb")
|
182
|
+
$stdout.reopen("/tmp/bannister.start.out.log","wb")
|
183
|
+
|
184
|
+
set_name()
|
185
|
+
drop_pid()
|
186
|
+
launch_all()
|
187
|
+
end
|
188
|
+
|
189
|
+
# Set the process name to something meaningful. In this case,
|
190
|
+
# "something meaningful" means "bannister:" + the name of the
|
191
|
+
# directory we're in. This makes it easy to see which bannister pids
|
192
|
+
# are responsible for which applications.
|
193
|
+
#
|
194
|
+
# @return [NilClass] nil.
|
195
|
+
def set_name
|
196
|
+
$0 = "bannister:"+File.basename(FileUtils.pwd)
|
197
|
+
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
|
201
|
+
# Record the current pid somewhere it can be found later
|
202
|
+
def drop_pid
|
203
|
+
system "mkdir -p #{File.dirname PID_FILENAME}"
|
204
|
+
File.open(PID_FILENAME, "w"){|f| f.write $$.to_s}
|
205
|
+
|
206
|
+
return nil
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# Read the pid from where drop_pid left it, or nil if it's not there.
|
211
|
+
#
|
212
|
+
# @return [Fixnum] pid as integer if it exists
|
213
|
+
# @return [NilClass] nil otherwise.
|
214
|
+
def read_pid
|
215
|
+
if File.exists?(PID_FILENAME)
|
216
|
+
return Integer(File.read(PID_FILENAME).strip)
|
217
|
+
else
|
218
|
+
return nil
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
# Clear the pid that drop_pid left behind.
|
224
|
+
#
|
225
|
+
# @return [NilClass] nil.
|
226
|
+
def remove_pid
|
227
|
+
FileUtils.rm_f(PID_FILENAME)
|
228
|
+
|
229
|
+
return nil
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# Fork and exec each of SCRIPTS.
|
234
|
+
#
|
235
|
+
# @return [NilClass] nil.
|
236
|
+
def launch_all()
|
237
|
+
SCRIPTS.each do |script|
|
238
|
+
LAUNCHED_PIDS << fork do
|
239
|
+
exec script
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
return nil
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
# Call Process.waitpid on each process launched by launch_all.
|
248
|
+
#
|
249
|
+
# @return [NilClass] nil.
|
250
|
+
def wait_for_all()
|
251
|
+
LAUNCHED_PIDS.each do |pid|
|
252
|
+
Process.waitpid(pid)
|
253
|
+
end
|
254
|
+
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# Send the TERM signal to each process launched by launch_all.
|
260
|
+
#
|
261
|
+
# @return [NilClass] nil.
|
262
|
+
def kill_all()
|
263
|
+
LAUNCHED_PIDS.each do |pid|
|
264
|
+
if ::Bannister::Utils.foreign_pid_alive?(pid)
|
265
|
+
system "kill -15 #{pid}"
|
266
|
+
else
|
267
|
+
$stderr.puts "Pid #{pid} doesn't seem to be running!"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
return nil
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
|
276
|
+
|
277
|
+
|
278
|
+
end
|
279
|
+
Connection to alpha.bytemark.co.uk closed.
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'bannister/utils'
|
3
|
+
|
4
|
+
module Bannister
|
5
|
+
|
6
|
+
|
7
|
+
# List of scripts in script/startup sorted asciibetically. This is the order
|
8
|
+
# they will be launched in, but there is no further dependency checking
|
9
|
+
# between the scripts.
|
10
|
+
SCRIPTS = Dir['script/startup/*'].sort
|
11
|
+
|
12
|
+
# A list of pids of the started scripts. These will be sent TERM signals when
|
13
|
+
# bannister is stopped.
|
14
|
+
LAUNCHED_PIDS = []
|
15
|
+
|
16
|
+
# The location of the pidfile for the master process. This will be
|
17
|
+
# de-hard-coded in a future release.
|
18
|
+
PID_FILENAME = "pids/bannister.pid"
|
19
|
+
|
20
|
+
|
21
|
+
# Daemonize and launch the managed processes.
|
22
|
+
def start()
|
23
|
+
if running?
|
24
|
+
err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
|
25
|
+
else
|
26
|
+
daemonize(){ do_start() }
|
27
|
+
wait_for_all()
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Launch the managed processes in screen so that they are all foreground
|
33
|
+
# processes.
|
34
|
+
#
|
35
|
+
# If run from inside an existing screen session, it will be reused, and the
|
36
|
+
# managed processes will be added to it. Otherwise a new screen session is
|
37
|
+
# started.
|
38
|
+
#
|
39
|
+
# Starting a new screen session is done by writing a temporary screenrc
|
40
|
+
# to /tmp, so $HOME/.screenrc is ignored.
|
41
|
+
def run()
|
42
|
+
screenrc_filename = "/tmp/screenrc.#{$$}"
|
43
|
+
|
44
|
+
if ENV['STY'] && !ENV['STY'].empty?
|
45
|
+
# then we already have a screen, so reuse it
|
46
|
+
# by simply running all the commands
|
47
|
+
|
48
|
+
SCRIPTS.each do |script|
|
49
|
+
system( command_for_script(script) )
|
50
|
+
end
|
51
|
+
|
52
|
+
else # create it. The easiest way to do this is to write
|
53
|
+
# out a screenrc containing the relevant commands and
|
54
|
+
# let screen launch them
|
55
|
+
begin
|
56
|
+
write_screenrc(screenrc_filename)
|
57
|
+
|
58
|
+
system "screen","-c",screenrc_filename
|
59
|
+
ensure
|
60
|
+
FileUtils.rm_f screenrc_filename
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Shut down all the managed processes, remove the bannister pidfile and exit.
|
68
|
+
def quit
|
69
|
+
kill_all()
|
70
|
+
remove_pid()
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
# Send a TERM signal to a #start'ed bannister process.
|
76
|
+
def stop()
|
77
|
+
old_pid = read_pid()
|
78
|
+
if old_pid
|
79
|
+
if ::Bannister::Utils.foreign_pid_alive?(old_pid)
|
80
|
+
Process.kill("TERM", old_pid)
|
81
|
+
if ::Bannister::Utils.wait_foreign_pid(old_pid)
|
82
|
+
remove_pid()
|
83
|
+
else
|
84
|
+
err "Bannister (pid=#{old_pid}) failed to die!"
|
85
|
+
end
|
86
|
+
else
|
87
|
+
err "Bannister (pid=#{old_pid}) wasn't alive."
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Used when you have erred. Shows the passed error message and exits
|
94
|
+
# with a status of 1.
|
95
|
+
#
|
96
|
+
# @param [String] msg Message to display.
|
97
|
+
def err(msg)
|
98
|
+
$stderr.puts(msg)
|
99
|
+
exit(1)
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
private
|
105
|
+
# Method to see whether a bannister process for the current application
|
106
|
+
# is running or not.
|
107
|
+
#
|
108
|
+
# @return [Boolean] true if PID_FILENAME exists, false otherwise.
|
109
|
+
def running?
|
110
|
+
return File.exists?(PID_FILENAME)
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Generate the screen command for the passed script filename. Since this
|
115
|
+
# will only ever be called when running in the foreground, we append -X
|
116
|
+
# to the command. A title for the screen window is also generated based
|
117
|
+
# on the current directory name and the script filename.
|
118
|
+
#
|
119
|
+
# @param [String] script the filename of a script to run.
|
120
|
+
# @return [String] the screen command to run the script.
|
121
|
+
def command_for_script(script)
|
122
|
+
title = File.basename(FileUtils.pwd)+':'+File.basename(script)
|
123
|
+
return "screen -t '#{title}' #{script} -X"
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Generate a screen config file and save it to the passed screenrc_filename.
|
128
|
+
# Screen is set up to allow restarting of dead processes with the 'r' key,
|
129
|
+
# and to give each script a meaningful title in the status line.
|
130
|
+
#
|
131
|
+
# @param [String] screenrc_filename the filename to save the
|
132
|
+
# generated config file to.
|
133
|
+
# @return [NilClass] nil.
|
134
|
+
def write_screenrc(screenrc_filename)
|
135
|
+
File.open(screenrc_filename, "wb") do |f|
|
136
|
+
f.write <<-SCREENRC
|
137
|
+
zombie cr onerror
|
138
|
+
hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"
|
139
|
+
|
140
|
+
SCREENRC
|
141
|
+
SCRIPTS.each do |script|
|
142
|
+
f.puts( command_for_script(script) )
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
return nil
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
# Daemonize the bannister process and yield. Used when running in
|
152
|
+
# the background. This is a slightly different daemonize method to
|
153
|
+
# the usual, in that we need to be sure that the script filenames in
|
154
|
+
# SCRIPT don't lose their relative location, so we chdir to '/'
|
155
|
+
# *after* kicking them off. It's each individual script's
|
156
|
+
# responsibility to daemonise properly.
|
157
|
+
def daemonize()
|
158
|
+
exit!(0) if fork
|
159
|
+
Process::setsid
|
160
|
+
exit!(0) if fork
|
161
|
+
File::umask(0)
|
162
|
+
STDIN.reopen("/dev/null")
|
163
|
+
STDOUT.reopen("/dev/null", "w")
|
164
|
+
STDERR.reopen("/dev/null", "w")
|
165
|
+
|
166
|
+
yield if block_given?
|
167
|
+
|
168
|
+
# chdir after yield so that the script paths remain correct
|
169
|
+
# and if any of them rely on relative paths, they won't get
|
170
|
+
# broken
|
171
|
+
Dir::chdir("/")
|
172
|
+
|
173
|
+
return nil
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
# Set up our environment and launch everything in the background.
|
178
|
+
def do_start()
|
179
|
+
set_name()
|
180
|
+
drop_pid()
|
181
|
+
launch_all()
|
182
|
+
end
|
183
|
+
|
184
|
+
# Set the process name to something meaningful. In this case,
|
185
|
+
# "something meaningful" means "bannister:" + the name of the
|
186
|
+
# directory we're in. This makes it easy to see which bannister pids
|
187
|
+
# are responsible for which applications.
|
188
|
+
#
|
189
|
+
# @return [NilClass] nil.
|
190
|
+
def set_name
|
191
|
+
$0 = "bannister:"+File.basename(FileUtils.pwd)
|
192
|
+
|
193
|
+
return nil
|
194
|
+
end
|
195
|
+
|
196
|
+
# Record the current pid somewhere it can be found later
|
197
|
+
def drop_pid
|
198
|
+
system "mkdir -p #{File.dirname PID_FILENAME}"
|
199
|
+
File.open(PID_FILENAME, "w"){|f| f.write $$.to_s}
|
200
|
+
|
201
|
+
return nil
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
# Read the pid from where drop_pid left it, or nil if it's not there.
|
206
|
+
#
|
207
|
+
# @return [Fixnum] pid as integer if it exists
|
208
|
+
# @return [NilClass] nil otherwise.
|
209
|
+
def read_pid
|
210
|
+
if File.exists?(PID_FILENAME)
|
211
|
+
return Integer(File.read(PID_FILENAME).strip)
|
212
|
+
else
|
213
|
+
return nil
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
# Clear the pid that drop_pid left behind.
|
219
|
+
#
|
220
|
+
# @return [NilClass] nil.
|
221
|
+
def remove_pid
|
222
|
+
FileUtils.rm_f(PID_FILENAME)
|
223
|
+
|
224
|
+
return nil
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
# Fork and exec each of SCRIPTS.
|
229
|
+
#
|
230
|
+
# @return [NilClass] nil.
|
231
|
+
def launch_all()
|
232
|
+
SCRIPTS.each do |script|
|
233
|
+
LAUNCHED_PIDS << fork do
|
234
|
+
exec script
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
return nil
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
# Call Process.waitpid on each process launched by launch_all.
|
243
|
+
#
|
244
|
+
# @return [NilClass] nil.
|
245
|
+
def wait_for_all()
|
246
|
+
LAUNCHED_PIDS.each do |pid|
|
247
|
+
Process.waitpid(pid)
|
248
|
+
end
|
249
|
+
|
250
|
+
return nil
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
# Send the TERM signal to each process launched by launch_all.
|
255
|
+
#
|
256
|
+
# @return [NilClass] nil.
|
257
|
+
def kill_all()
|
258
|
+
LAUNCHED_PIDS.each do |pid|
|
259
|
+
if ::Bannister::Utils.foreign_pid_alive?(pid)
|
260
|
+
system "kill -15 #{pid}"
|
261
|
+
else
|
262
|
+
$stderr.puts "Pid #{pid} doesn't seem to be running!"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
return nil
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'bannister/utils'
|
3
|
+
|
4
|
+
module Bannister
|
5
|
+
|
6
|
+
|
7
|
+
# List of scripts in script/startup sorted asciibetically. This is the order
|
8
|
+
# they will be launched in, but there is no further dependency checking
|
9
|
+
# between the scripts.
|
10
|
+
SCRIPTS = Dir['script/startup/*'].sort
|
11
|
+
|
12
|
+
# A list of pids of the started scripts. These will be sent TERM signals when
|
13
|
+
# bannister is stopped.
|
14
|
+
LAUNCHED_PIDS = []
|
15
|
+
|
16
|
+
# The location of the pidfile for the master process. This will be
|
17
|
+
# de-hard-coded in a future release.
|
18
|
+
PID_FILENAME = "pids/bannister.pid"
|
19
|
+
|
20
|
+
|
21
|
+
# Daemonize and launch the managed processes.
|
22
|
+
def start()
|
23
|
+
if running?
|
24
|
+
err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
|
25
|
+
else
|
26
|
+
daemonize(){ do_start() }
|
27
|
+
wait_for_all()
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Launch the managed processes in screen so that they are all foreground
|
33
|
+
# processes.
|
34
|
+
#
|
35
|
+
# If run from inside an existing screen session, it will be reused, and the
|
36
|
+
# managed processes will be added to it. Otherwise a new screen session is
|
37
|
+
# started.
|
38
|
+
#
|
39
|
+
# Starting a new screen session is done by writing a temporary screenrc
|
40
|
+
# to /tmp, so $HOME/.screenrc is ignored.
|
41
|
+
def run()
|
42
|
+
screenrc_filename = "/tmp/screenrc.#{$$}"
|
43
|
+
|
44
|
+
if ENV['STY'] && !ENV['STY'].empty?
|
45
|
+
# then we already have a screen, so reuse it
|
46
|
+
# by simply running all the commands
|
47
|
+
|
48
|
+
SCRIPTS.each do |script|
|
49
|
+
system( command_for_script(script) )
|
50
|
+
end
|
51
|
+
|
52
|
+
else # create it. The easiest way to do this is to write
|
53
|
+
# out a screenrc containing the relevant commands and
|
54
|
+
# let screen launch them
|
55
|
+
begin
|
56
|
+
write_screenrc(screenrc_filename)
|
57
|
+
|
58
|
+
system "screen","-c",screenrc_filename
|
59
|
+
ensure
|
60
|
+
FileUtils.rm_f screenrc_filename
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Shut down all the managed processes, remove the bannister pidfile and exit.
|
68
|
+
def quit
|
69
|
+
$stderr.reopen("/tmp/bannister.stop.err.log", "wb")
|
70
|
+
$stdout.reopen("/tmp/bannister.stop.out.log", "wb")
|
71
|
+
kill_all()
|
72
|
+
remove_pid()
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
# Send a TERM signal to a #start'ed bannister process.
|
78
|
+
def stop()
|
79
|
+
old_pid = read_pid()
|
80
|
+
if old_pid
|
81
|
+
if ::Bannister::Utils.foreign_pid_alive?(old_pid)
|
82
|
+
Process.kill("TERM", old_pid)
|
83
|
+
if ::Bannister::Utils.wait_foreign_pid(old_pid)
|
84
|
+
remove_pid()
|
85
|
+
else
|
86
|
+
err "Bannister (pid=#{old_pid}) failed to die!"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
err "Bannister (pid=#{old_pid}) wasn't alive."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# Used when you have erred. Shows the passed error message and exits
|
96
|
+
# with a status of 1.
|
97
|
+
#
|
98
|
+
# @param [String] msg Message to display.
|
99
|
+
def err(msg)
|
100
|
+
$stderr.puts(msg)
|
101
|
+
exit(1)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
private
|
107
|
+
# Method to see whether a bannister process for the current application
|
108
|
+
# is running or not.
|
109
|
+
#
|
110
|
+
# @return [Boolean] true if PID_FILENAME exists, false otherwise.
|
111
|
+
def running?
|
112
|
+
return File.exists?(PID_FILENAME)
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# Generate the screen command for the passed script filename. Since this
|
117
|
+
# will only ever be called when running in the foreground, we append -X
|
118
|
+
# to the command. A title for the screen window is also generated based
|
119
|
+
# on the current directory name and the script filename.
|
120
|
+
#
|
121
|
+
# @param [String] script the filename of a script to run.
|
122
|
+
# @return [String] the screen command to run the script.
|
123
|
+
def command_for_script(script)
|
124
|
+
title = File.basename(FileUtils.pwd)+':'+File.basename(script)
|
125
|
+
return "screen -t '#{title}' #{script} -X"
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Generate a screen config file and save it to the passed screenrc_filename.
|
130
|
+
# Screen is set up to allow restarting of dead processes with the 'r' key,
|
131
|
+
# and to give each script a meaningful title in the status line.
|
132
|
+
#
|
133
|
+
# @param [String] screenrc_filename the filename to save the
|
134
|
+
# generated config file to.
|
135
|
+
# @return [NilClass] nil.
|
136
|
+
def write_screenrc(screenrc_filename)
|
137
|
+
File.open(screenrc_filename, "wb") do |f|
|
138
|
+
f.write <<-SCREENRC
|
139
|
+
zombie cr onerror
|
140
|
+
hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"
|
141
|
+
|
142
|
+
SCREENRC
|
143
|
+
SCRIPTS.each do |script|
|
144
|
+
f.puts( command_for_script(script) )
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
return nil
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
# Daemonize the bannister process and yield. Used when running in
|
154
|
+
# the background. This is a slightly different daemonize method to
|
155
|
+
# the usual, in that we need to be sure that the script filenames in
|
156
|
+
# SCRIPT don't lose their relative location, so we chdir to '/'
|
157
|
+
# *after* kicking them off. It's each individual script's
|
158
|
+
# responsibility to daemonise properly.
|
159
|
+
def daemonize()
|
160
|
+
exit!(0) if fork
|
161
|
+
Process::setsid
|
162
|
+
exit!(0) if fork
|
163
|
+
File::umask(0)
|
164
|
+
STDIN.reopen("/dev/null")
|
165
|
+
STDOUT.reopen("/dev/null", "w")
|
166
|
+
STDERR.reopen("/dev/null", "w")
|
167
|
+
|
168
|
+
yield if block_given?
|
169
|
+
|
170
|
+
# chdir after yield so that the script paths remain correct
|
171
|
+
# and if any of them rely on relative paths, they won't get
|
172
|
+
# broken
|
173
|
+
Dir::chdir("/")
|
174
|
+
|
175
|
+
return nil
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# Set up our environment and launch everything in the background.
|
180
|
+
def do_start()
|
181
|
+
$stderr.reopen("/tmp/bannister.start.err.log", "wb")
|
182
|
+
$stdout.reopen("/tmp/bannister.start.out.log","wb")
|
183
|
+
|
184
|
+
set_name()
|
185
|
+
drop_pid()
|
186
|
+
launch_all()
|
187
|
+
end
|
188
|
+
|
189
|
+
# Set the process name to something meaningful. In this case,
|
190
|
+
# "something meaningful" means "bannister:" + the name of the
|
191
|
+
# directory we're in. This makes it easy to see which bannister pids
|
192
|
+
# are responsible for which applications.
|
193
|
+
#
|
194
|
+
# @return [NilClass] nil.
|
195
|
+
def set_name
|
196
|
+
$0 = "bannister:"+File.basename(FileUtils.pwd)
|
197
|
+
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
|
201
|
+
# Record the current pid somewhere it can be found later
|
202
|
+
def drop_pid
|
203
|
+
system "mkdir -p #{File.dirname PID_FILENAME}"
|
204
|
+
File.open(PID_FILENAME, "w"){|f| f.write $$.to_s}
|
205
|
+
|
206
|
+
return nil
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# Read the pid from where drop_pid left it, or nil if it's not there.
|
211
|
+
#
|
212
|
+
# @return [Fixnum] pid as integer if it exists
|
213
|
+
# @return [NilClass] nil otherwise.
|
214
|
+
def read_pid
|
215
|
+
if File.exists?(PID_FILENAME)
|
216
|
+
return Integer(File.read(PID_FILENAME).strip)
|
217
|
+
else
|
218
|
+
return nil
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
# Clear the pid that drop_pid left behind.
|
224
|
+
#
|
225
|
+
# @return [NilClass] nil.
|
226
|
+
def remove_pid
|
227
|
+
FileUtils.rm_f(PID_FILENAME)
|
228
|
+
|
229
|
+
return nil
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# Fork and exec each of SCRIPTS.
|
234
|
+
#
|
235
|
+
# @return [NilClass] nil.
|
236
|
+
def launch_all()
|
237
|
+
SCRIPTS.each do |script|
|
238
|
+
LAUNCHED_PIDS << fork do
|
239
|
+
exec script
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
return nil
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
# Call Process.waitpid on each process launched by launch_all.
|
248
|
+
#
|
249
|
+
# @return [NilClass] nil.
|
250
|
+
def wait_for_all()
|
251
|
+
LAUNCHED_PIDS.each do |pid|
|
252
|
+
Process.waitpid(pid)
|
253
|
+
end
|
254
|
+
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# Send the TERM signal to each process launched by launch_all.
|
260
|
+
#
|
261
|
+
# @return [NilClass] nil.
|
262
|
+
def kill_all()
|
263
|
+
LAUNCHED_PIDS.each do |pid|
|
264
|
+
if ::Bannister::Utils.foreign_pid_alive?(pid)
|
265
|
+
system "kill -15 #{pid}"
|
266
|
+
else
|
267
|
+
$stderr.puts "Pid #{pid} doesn't seem to be running!"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
return nil
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
|
276
|
+
|
277
|
+
|
278
|
+
end
|
data/lib/bannister/apache.rb
CHANGED
@@ -1,17 +1,36 @@
|
|
1
|
+
require 'bannister/utils'
|
1
2
|
|
2
3
|
module Bannister
|
3
4
|
# A standardised apache launcher.
|
4
5
|
class Apache
|
5
6
|
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
7
|
+
# @param [Hash] config A config hash to be merged with ENV for
|
8
|
+
# substitution into apache.conf.
|
9
|
+
# @param [String] apacheconf_filename Location of apache.conf (only used if
|
10
|
+
# config['DefaultConfig'] doesn't exist, otherwise
|
11
|
+
# config['Apache2Conf'] is used instead
|
12
|
+
# @param [String] pid_filename Location where apache will write its pidfile
|
13
|
+
# (only used if config['DefaultConfig'] doesn't exist, otherwise
|
14
|
+
# config['DefaultConfig']['
|
15
|
+
def initialize(config, apacheconf_filename=nil, pid_filename=nil)
|
12
16
|
raise ArgumentError.new("config cannot be nil") if config.nil?
|
13
17
|
|
14
|
-
|
18
|
+
|
19
|
+
# FIXME: This is pig-ugly, and must be changed for 1.0.0. We do it
|
20
|
+
# this way for compatibility with existing deployments, but the old
|
21
|
+
# way hardcodes the pid filename, which is problematic and won't
|
22
|
+
# work across what's already actually deployed.
|
23
|
+
if config.has_key?("DefaultConfig")
|
24
|
+
@config = config['DefaultConfig']
|
25
|
+
@apacheconf_filename = config['Apache2Conf']
|
26
|
+
@pid_filename = @config['PID_FILE']
|
27
|
+
else
|
28
|
+
@config = config
|
29
|
+
@apacheconf_filename = apacheconf_filename
|
30
|
+
@pid_filename = pid_filename
|
31
|
+
end
|
32
|
+
|
33
|
+
@pid = nil
|
15
34
|
end
|
16
35
|
|
17
36
|
|
@@ -19,22 +38,10 @@ module Bannister
|
|
19
38
|
# Send a TERM signal to the apache process. If the process
|
20
39
|
# is still running after 2 seconds, a RuntimeError is thrown.
|
21
40
|
def stop
|
22
|
-
if pid =
|
41
|
+
if pid = @pid
|
23
42
|
Process.kill("TERM", pid)
|
24
|
-
|
25
|
-
|
26
|
-
# whether we can send the given pid a signal.
|
27
|
-
catch :done do
|
28
|
-
20.times do
|
29
|
-
sleep(0.1)
|
30
|
-
# No need to trap error messages here, because `kill`
|
31
|
-
# gives us a meaningful error code
|
32
|
-
next if system "kill -0 #{pid} 2> /dev/null"
|
33
|
-
throw :done
|
34
|
-
end
|
35
|
-
|
36
|
-
raise "Apache2 (pid=#{pid}) failed to stop!"
|
37
|
-
end
|
43
|
+
else
|
44
|
+
$stderr.puts "No apache pid found!"
|
38
45
|
end
|
39
46
|
end
|
40
47
|
|
@@ -48,48 +55,26 @@ module Bannister
|
|
48
55
|
#
|
49
56
|
# @param [Boolean] foreground Set true to run in the foreground.
|
50
57
|
def start(foreground=false)
|
51
|
-
@apache2_conf = @config['Apache2Conf']
|
52
58
|
rewrite_envvars(ENV)
|
53
|
-
system "mkdir -p tmp"
|
54
|
-
cmd = foreground ? "/usr/sbin/apache2 -f #{@
|
55
|
-
"/usr/sbin/apache2 -f #{@
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
trap("TERM"){ stop() }
|
61
|
-
trap("HUP"){ stop() }
|
62
|
-
|
63
|
-
# Apache2's pidfile is written by the child process
|
64
|
-
# after the parent process (the one we kick off) terminates.
|
65
|
-
# This means that we need to wait for it.
|
66
|
-
catch :done do
|
67
|
-
20.times do
|
68
|
-
sleep(0.1)
|
69
|
-
next if !File.file?(PidFilename)
|
70
|
-
throw :done
|
71
|
-
end
|
72
|
-
raise "Apache2 failed to write #{PidFilename}"
|
73
|
-
end
|
74
|
-
|
75
|
-
sleep # Sleep to wait for the TERM signal
|
76
|
-
|
77
|
-
else
|
78
|
-
raise "Apache2 failed to start!"
|
79
|
-
end
|
80
|
-
|
59
|
+
system "mkdir -p tmp" # FIXME: check pid_filename
|
60
|
+
cmd = foreground ? "/usr/sbin/apache2 -f #{@apacheconf_filename} -X" :
|
61
|
+
"/usr/sbin/apache2 -f #{@apacheconf_filename} -D FOREGROUND"
|
62
|
+
trap("TERM"){ stop() }
|
63
|
+
trap("HUP"){ stop() }
|
64
|
+
@pid = fork { exec cmd }
|
65
|
+
Process.waitpid(@pid)
|
81
66
|
end
|
82
67
|
|
83
68
|
|
84
69
|
private
|
85
70
|
|
86
|
-
# Read apache2's pid from {
|
71
|
+
# Read apache2's pid from {@pid_filename}.
|
87
72
|
#
|
88
73
|
# @return [Integer] Apache's process id.
|
89
74
|
# @return nil if it's not there.
|
90
75
|
def read_pid()
|
91
|
-
if File.file?(
|
92
|
-
return Integer(File.read(
|
76
|
+
if File.file?(@pid_filename)
|
77
|
+
return Integer(File.read(@pid_filename).strip)
|
93
78
|
else
|
94
79
|
return nil
|
95
80
|
end
|
@@ -100,7 +85,7 @@ module Bannister
|
|
100
85
|
#
|
101
86
|
# @param [Hash] vars The config hash
|
102
87
|
def rewrite_envvars(vars={})
|
103
|
-
merged_opts = @config
|
88
|
+
merged_opts = @config.merge(vars)
|
104
89
|
merged_opts.each_pair do |k,v|
|
105
90
|
ENV[k] = v.to_s
|
106
91
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Bannister
|
2
|
+
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
|
6
|
+
def self.wait_while(timeout=2.0, attempts=20)
|
7
|
+
raise ArgumentError.new("No block given!") unless block_given?
|
8
|
+
attempts=20
|
9
|
+
sleepy_time = timeout/attempts
|
10
|
+
|
11
|
+
attempts.times do
|
12
|
+
return true unless yield
|
13
|
+
sleep sleepy_time
|
14
|
+
end
|
15
|
+
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Waits for process with process id `pid` to die. This is needed
|
20
|
+
# because Process.waitpid can only wait for child pids.
|
21
|
+
#
|
22
|
+
# @param [Integer] pid process id to wait for
|
23
|
+
# @param [Float] timeout time in seconds to wait for
|
24
|
+
# @return [Boolean] true on success, nil otherwise.
|
25
|
+
def self.wait_foreign_pid(pid, timeout=2.0)
|
26
|
+
raise ArgumentError.new("pid cannot be nil!") unless pid
|
27
|
+
|
28
|
+
return wait_while(){ foreign_pid_alive?(pid) }
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Check to see if the process with id `pid` responds to signals.
|
33
|
+
#
|
34
|
+
# @param [Integer] pid process id to check
|
35
|
+
# @return [Boolean] true if process `pid` responds.
|
36
|
+
def self.foreign_pid_alive?(pid)
|
37
|
+
raise ArgumentError.new("pid cannot be nil!") unless pid
|
38
|
+
|
39
|
+
return system "kill -0 #{pid} 2> /dev/null"
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
data/lib/bannister/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bannister
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex Young
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-10-
|
18
|
+
date: 2010-10-15 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -47,9 +47,14 @@ extra_rdoc_files: []
|
|
47
47
|
files:
|
48
48
|
- bin/bannister
|
49
49
|
- lib/bannister/version.rb
|
50
|
+
- lib/bannister/version.rb.orig
|
50
51
|
- lib/bannister/recurse.rb
|
51
52
|
- lib/bannister/apache.rb
|
53
|
+
- lib/bannister/utils.rb
|
54
|
+
- lib/bannister.remote.rb
|
52
55
|
- lib/bannister.rb
|
56
|
+
- lib/bannister.remote.rb~
|
57
|
+
- lib/bannister.rb.orig
|
53
58
|
- README.md
|
54
59
|
- Rakefile
|
55
60
|
has_rdoc: true
|