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 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
@@ -0,0 +1,14 @@
1
+ require 'rake/clean'
2
+ CLEAN << ".yardoc"
3
+ CLEAN << FileList["*.gem"]
4
+ CLEAN << "doc"
5
+
6
+ desc "Build the gem"
7
+ task :gem do
8
+ system "gem build bannister.gemspec"
9
+ end
10
+
11
+ desc "Build the YARD docs"
12
+ task :docs do
13
+ system "yardoc"
14
+ end
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
@@ -0,0 +1,3 @@
1
+ module Bannister
2
+ VERSION="0.0.1"
3
+ 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
+