bannister 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.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
|
+
|