bannister 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+