bannister 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +31 -0
- data/Rakefile +14 -0
- data/bin/bannister +21 -0
- data/lib/bannister/apache.rb +102 -0
- data/lib/bannister/recurse.rb +40 -0
- data/lib/bannister/version.rb +3 -0
- data/lib/bannister.rb +194 -0
- metadata +77 -0
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Bannister
|
2
|
+
=========
|
3
|
+
|
4
|
+
'Cos it's a runner.
|
5
|
+
|
6
|
+
INTRODUCTION
|
7
|
+
------------
|
8
|
+
|
9
|
+
|
10
|
+
Generalised app runner. Launches all of the scripts in $(pwd)/script/startup
|
11
|
+
in sort order. All the scripts must respond to the TERM signal by shutting
|
12
|
+
down, and they will be passed the -X script if they are to run in the
|
13
|
+
foreground.
|
14
|
+
|
15
|
+
|
16
|
+
USAGE
|
17
|
+
-----
|
18
|
+
|
19
|
+
$(pwd) must be the root of the application.
|
20
|
+
|
21
|
+
To daemonize an application, run:
|
22
|
+
|
23
|
+
$ bannister start
|
24
|
+
|
25
|
+
To start an application in screen, run:
|
26
|
+
|
27
|
+
$ bannister run
|
28
|
+
|
29
|
+
To stop a daemonized application, run:
|
30
|
+
|
31
|
+
$ bannister stop
|
data/Rakefile
ADDED
data/bin/bannister
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bannister'
|
4
|
+
include Bannister
|
5
|
+
|
6
|
+
|
7
|
+
trap("TERM") { quit() }
|
8
|
+
|
9
|
+
case ARGV.shift
|
10
|
+
when "start"
|
11
|
+
start()
|
12
|
+
when "run"
|
13
|
+
run()
|
14
|
+
when "stop"
|
15
|
+
stop()
|
16
|
+
when "restart"
|
17
|
+
stop()
|
18
|
+
start()
|
19
|
+
else
|
20
|
+
err "usage: #{$0} (run|start|stop|restart)"
|
21
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
|
2
|
+
module Bannister
|
3
|
+
# A standardised apache launcher.
|
4
|
+
class Apache
|
5
|
+
|
6
|
+
# Location of apache's pidfile. This must agree with apache.conf.
|
7
|
+
PidFilename = "tmp/apache2.pid"
|
8
|
+
|
9
|
+
# @param [Hash] config A config hash to be merged with ENV for
|
10
|
+
# substitution into apache.conf.
|
11
|
+
def initialize(config)
|
12
|
+
raise ArgumentError.new("config cannot be nil") if config.nil?
|
13
|
+
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
# Send a TERM signal to the apache process. If the process
|
20
|
+
# is still running after 2 seconds, a RuntimeError is thrown.
|
21
|
+
def stop
|
22
|
+
if pid = read_pid()
|
23
|
+
Process.kill("TERM", pid)
|
24
|
+
# the apache process isn't a child process, so we can't
|
25
|
+
# use Process.waitpid. Instead we use kill -0, which tests
|
26
|
+
# whether we can send the given pid a signal.
|
27
|
+
catch :done do
|
28
|
+
20.times do
|
29
|
+
sleep(0.1)
|
30
|
+
next if system "kill -0 #{pid} 2> /dev/null"
|
31
|
+
throw :done
|
32
|
+
end
|
33
|
+
raise "Apache2 failed to stop!"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Start apache, then sleep. If foreground is true, then pass the -X option
|
40
|
+
# to apache. This makes it run in the foreground.
|
41
|
+
#
|
42
|
+
# Before sleeping, trap TERM and HUP so that we can signal apache to stop.
|
43
|
+
# We need to do this so that when screen exits, we can tell apache to exit
|
44
|
+
# instead of restart, which is its usual HUP response.
|
45
|
+
#
|
46
|
+
# @param [Boolean] foreground Set true to run in the foreground.
|
47
|
+
def start(foreground=false)
|
48
|
+
@apache2_conf = @config['Apache2Conf']
|
49
|
+
rewrite_envvars(ENV)
|
50
|
+
system "mkdir -p tmp"
|
51
|
+
cmd = foreground ? "/usr/sbin/apache2 -f #{@apache2_conf} -X &" :
|
52
|
+
"/usr/sbin/apache2 -f #{@apache2_conf}"
|
53
|
+
if system( cmd )
|
54
|
+
# runner sends TERM, but screen sends a HUP when it quits.
|
55
|
+
# This is why we can't just exec apache -X; apache restarts
|
56
|
+
# on HUP.
|
57
|
+
trap("TERM"){ stop() }
|
58
|
+
trap("HUP"){ stop() }
|
59
|
+
|
60
|
+
# Apache2's pidfile is written by the child process
|
61
|
+
# after the parent process (the one we kick off) terminates.
|
62
|
+
# This means that we need to wait for it.
|
63
|
+
catch :done do
|
64
|
+
20.times do
|
65
|
+
sleep(0.1)
|
66
|
+
next if !File.file?(PidFilename)
|
67
|
+
throw :done
|
68
|
+
end
|
69
|
+
raise "Apache2 failed to write #{PidFilename}"
|
70
|
+
end
|
71
|
+
|
72
|
+
sleep # Sleep to wait for the TERM signal
|
73
|
+
|
74
|
+
else
|
75
|
+
raise "Apache2 failed to start!"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def read_pid()
|
84
|
+
if File.file?(PidFilename)
|
85
|
+
return Integer(File.read(PidFilename).strip)
|
86
|
+
else
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Merge the config hash into ENV
|
93
|
+
#
|
94
|
+
# @param [Hash] vars The config hash
|
95
|
+
def rewrite_envvars(vars={})
|
96
|
+
merged_opts = @config['DefaultConfig'].merge(vars)
|
97
|
+
merged_opts.each_pair do |k,v|
|
98
|
+
ENV[k] = v.to_s
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Bannister
|
2
|
+
|
3
|
+
# Use the Recurse class to launch other bannister apps from this one.
|
4
|
+
class Recurse
|
5
|
+
|
6
|
+
# @param [String] dir Directory to chdir to before running bannister
|
7
|
+
def initialize(dir)
|
8
|
+
raise ArgumentError.new("dir cannot be nil!") if dir.nil?
|
9
|
+
|
10
|
+
@dir=dir
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
# Run "bannister stop" and exit.
|
15
|
+
def stop
|
16
|
+
system "bannister stop"
|
17
|
+
exit(0)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# If foreground is true, exec "bannister run" in @dir. Otherwise set up
|
22
|
+
# traps for the right signals to a backgrounded "bannister start" process.
|
23
|
+
def start(foreground=false)
|
24
|
+
Dir.chdir(@dir) do
|
25
|
+
if foreground
|
26
|
+
exec "bannister run"
|
27
|
+
else
|
28
|
+
trap("TERM"){stop()}
|
29
|
+
trap("HUP"){stop()}
|
30
|
+
|
31
|
+
system "bannister start &"
|
32
|
+
|
33
|
+
sleep()
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/bannister.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Bannister
|
4
|
+
|
5
|
+
|
6
|
+
SCRIPTS = Dir['script/startup/*'].sort
|
7
|
+
LAUNCHED_PIDS = []
|
8
|
+
PID_FILENAME = "pids/bannister.pid"
|
9
|
+
|
10
|
+
|
11
|
+
# Daemonize and launch the managed processes.
|
12
|
+
def start()
|
13
|
+
if running?
|
14
|
+
err "This app is already running! Run #{$0} stop first."
|
15
|
+
else
|
16
|
+
daemonize(){ do_start() }
|
17
|
+
wait_for_all()
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Launch the managed processes in screen so that they are all foreground
|
23
|
+
# processes.
|
24
|
+
#
|
25
|
+
# If run from inside an existing screen session, it will be reused, and the
|
26
|
+
# managed processes will be added to it. Otherwise a new screen session is
|
27
|
+
# started.
|
28
|
+
#
|
29
|
+
# Starting a new screen session is done by writing a temporary screenrc
|
30
|
+
# to /tmp, so $HOME/.screenrc is ignored.
|
31
|
+
def run()
|
32
|
+
screenrc_filename = "/tmp/screenrc.#{$$}"
|
33
|
+
|
34
|
+
if ENV['STY'] && !ENV['STY'].empty?
|
35
|
+
# then we already have a screen, so reuse it
|
36
|
+
# by simply running all the commands
|
37
|
+
|
38
|
+
SCRIPTS.each do |script|
|
39
|
+
system( command_for_script(script) )
|
40
|
+
end
|
41
|
+
|
42
|
+
else # create it. The easiest way to do this is to write
|
43
|
+
# out a screenrc containing the relevant commands and
|
44
|
+
# let screen launch them
|
45
|
+
|
46
|
+
begin
|
47
|
+
write_screenrc(screenrc_filename)
|
48
|
+
|
49
|
+
system "screen","-c",screenrc_filename
|
50
|
+
ensure
|
51
|
+
FileUtils.rm_f screenrc_filename
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Shut down all the managed processes, remove the bannister pidfile and exit.
|
59
|
+
def quit
|
60
|
+
kill_all()
|
61
|
+
remove_pid()
|
62
|
+
exit(0)
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
# Send a TERM signal to a #start'ed bannister process.
|
68
|
+
def stop()
|
69
|
+
old_pid = read_pid()
|
70
|
+
if old_pid
|
71
|
+
begin
|
72
|
+
Process.kill("TERM", old_pid)
|
73
|
+
Process.waitpid(old_pid)
|
74
|
+
rescue SystemCallError
|
75
|
+
# which gets thrown if the old process is already dead
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
remove_pid()
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Used when you have erred. Shows the passed error message and exits
|
84
|
+
# with a status of 1.
|
85
|
+
#
|
86
|
+
# @param [String] msg Message to display.
|
87
|
+
def err(msg)
|
88
|
+
$stderr.puts(msg)
|
89
|
+
exit(1)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
private
|
94
|
+
def running?
|
95
|
+
return File.exists?(PID_FILENAME)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def command_for_script(script)
|
100
|
+
title = File.basename(FileUtils.pwd)+':'+File.basename(script)
|
101
|
+
return "screen -t '#{title}' #{script} -X"
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def write_screenrc(screenrc_filename)
|
106
|
+
File.open(screenrc_filename, "wb") do |f|
|
107
|
+
f.write <<-SCREENRC
|
108
|
+
zombie cr onerror
|
109
|
+
hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"
|
110
|
+
|
111
|
+
SCREENRC
|
112
|
+
SCRIPTS.each do |script|
|
113
|
+
f.puts( command_for_script(script) )
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
def daemonize()
|
121
|
+
exit!(0) if fork
|
122
|
+
Process::setsid
|
123
|
+
exit!(0) if fork
|
124
|
+
File::umask(0)
|
125
|
+
STDIN.reopen("/dev/null")
|
126
|
+
STDOUT.reopen("/dev/null", "w")
|
127
|
+
STDERR.reopen("/dev/null", "w")
|
128
|
+
|
129
|
+
yield if block_given?
|
130
|
+
|
131
|
+
# chdir after yield so that the script paths remain correct
|
132
|
+
# and if any of them rely on relative paths, they won't get
|
133
|
+
# broken
|
134
|
+
Dir::chdir("/")
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def do_start()
|
139
|
+
set_name()
|
140
|
+
drop_pid()
|
141
|
+
launch_all()
|
142
|
+
end
|
143
|
+
|
144
|
+
def set_name
|
145
|
+
$0 = "bannister:"+File.basename(FileUtils.pwd)
|
146
|
+
end
|
147
|
+
|
148
|
+
def drop_pid
|
149
|
+
system "mkdir -p #{File.dirname PID_FILENAME}"
|
150
|
+
File.open(PID_FILENAME, "w"){|f| f.write $$.to_s}
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def read_pid
|
155
|
+
if File.exists?(PID_FILENAME)
|
156
|
+
return Integer(File.read(PID_FILENAME).strip)
|
157
|
+
else
|
158
|
+
return nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
def remove_pid
|
164
|
+
FileUtils.rm_f(PID_FILENAME)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def launch_all()
|
169
|
+
SCRIPTS.each do |script|
|
170
|
+
LAUNCHED_PIDS << fork do
|
171
|
+
exec script
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
def wait_for_all()
|
178
|
+
LAUNCHED_PIDS.each do |pid|
|
179
|
+
Process.waitpid(pid)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def kill_all()
|
185
|
+
LAUNCHED_PIDS.each do |pid|
|
186
|
+
system "kill -15 #{pid}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
|
193
|
+
|
194
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bannister
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Alex Young
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-22 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |
|
23
|
+
An application launcher which maintains a tree of processes to keep background
|
24
|
+
processes tied to the launcher.
|
25
|
+
|
26
|
+
email:
|
27
|
+
- alex@blackkettle.org
|
28
|
+
executables:
|
29
|
+
- bannister
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files: []
|
33
|
+
|
34
|
+
files:
|
35
|
+
- bin/bannister
|
36
|
+
- lib/bannister/apache.rb
|
37
|
+
- lib/bannister/recurse.rb
|
38
|
+
- lib/bannister/version.rb
|
39
|
+
- lib/bannister.rb
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
has_rdoc: true
|
43
|
+
homepage:
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.7
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: A generalised application launcher with apache support.
|
76
|
+
test_files: []
|
77
|
+
|