appserver 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +83 -0
- data/bin/appserver +45 -0
- data/lib/appserver.rb +8 -0
- data/lib/appserver/app.rb +110 -0
- data/lib/appserver/appserver.yml +85 -0
- data/lib/appserver/command.rb +43 -0
- data/lib/appserver/repository.rb +48 -0
- data/lib/appserver/server.rb +136 -0
- data/lib/appserver/unicorn.conf.rb +60 -0
- data/lib/appserver/utils.rb +34 -0
- metadata +86 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Andreas Neuhaus
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
Software), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
Automagic application server configurator
|
2
|
+
=========================================
|
3
|
+
|
4
|
+
Monit/Nginx/Unicorn application server configurator using deployment via git
|
5
|
+
(simply git push applications to your server to deploy them).
|
6
|
+
|
7
|
+
This little tool automatically generates server configs for [Monit][monit],
|
8
|
+
[Nginx][nginx] and [Unicorn][unicorn] to host your [Rack][rack]-based (Rails)
|
9
|
+
applications. Running it automatically in git post-receive hooks provides
|
10
|
+
an automatic deployment of applications whenever the repository is updated
|
11
|
+
on the server.
|
12
|
+
|
13
|
+
Requirements
|
14
|
+
------------
|
15
|
+
|
16
|
+
A server running [Monit][monit], [Nginx][nginx] and having [Git][git] and
|
17
|
+
Ruby with RubyGems installed.
|
18
|
+
|
19
|
+
Install
|
20
|
+
-------
|
21
|
+
|
22
|
+
gem install appserver
|
23
|
+
|
24
|
+
Or check out the [repository][repo] on github.
|
25
|
+
|
26
|
+
Setup
|
27
|
+
-----
|
28
|
+
|
29
|
+
To run applications, you need to initialize an appserver directory first. To
|
30
|
+
do so, create an empty directory and run `appserver init` in it.
|
31
|
+
|
32
|
+
$ mkdir /var/webapps
|
33
|
+
$ cd /var/webapps
|
34
|
+
$ appserver init
|
35
|
+
|
36
|
+
An appserver directory holds configuration files and everything needed to run
|
37
|
+
multiple applications (application code, temp files, log files, ...). You can
|
38
|
+
customize settings by editing the `appserver.yml` configuration file. **All
|
39
|
+
other files are updated automatically and should not be modified manually.**
|
40
|
+
|
41
|
+
Modify your system's Nginx configuration (e.g. `/etc/nginx/nginx.conf` on
|
42
|
+
Ubuntu) to include the generated `nginx.conf` **inside a `http` statement**.
|
43
|
+
Reload Nginx to apply the configuration changes.
|
44
|
+
|
45
|
+
*/etc/nginx/nginx.conf:*
|
46
|
+
|
47
|
+
⋮
|
48
|
+
http {
|
49
|
+
⋮
|
50
|
+
include /var/www/nginx.conf;
|
51
|
+
}
|
52
|
+
⋮
|
53
|
+
|
54
|
+
Modify your system's Monit configuration (e.g. `/etc/monit/monitrc` on Ubuntu)
|
55
|
+
to include the generated `monitrc` at the bottom. Reload Monit to apply the
|
56
|
+
configuration changes.
|
57
|
+
|
58
|
+
*/etc/monit/monitrc:*
|
59
|
+
|
60
|
+
⋮
|
61
|
+
include /var/webapps/monitrc
|
62
|
+
|
63
|
+
Deploying an application
|
64
|
+
------------------------
|
65
|
+
|
66
|
+
to be done...
|
67
|
+
|
68
|
+
How it works
|
69
|
+
------------
|
70
|
+
|
71
|
+
to be done...
|
72
|
+
|
73
|
+
Author
|
74
|
+
------
|
75
|
+
|
76
|
+
Andreas Neuhaus :: <http://zargony.com/>
|
77
|
+
|
78
|
+
[repo]: http://github.com/zargony/appserver/
|
79
|
+
[monit]: http://mmonit.com/monit/
|
80
|
+
[nginx]: http://nginx.com/
|
81
|
+
[unicorn]: http://unicorn.bogomips.org/
|
82
|
+
[git]: http://git-scm.com/
|
83
|
+
[rack]: http://rack.rubyforge.org/
|
data/bin/appserver
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'appserver'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
opts = OptionParser.new(nil, 20, ' ') do |opts|
|
10
|
+
opts.banner = 'Usage: appserver [options] init|deploy|update [arguments]'
|
11
|
+
opts.separator ''
|
12
|
+
opts.separator 'appserver [options] init'
|
13
|
+
opts.separator ' Initializes an appserver directory. Run this command once in an empty'
|
14
|
+
opts.separator ' directory to set it up for deploying applications. After this, you can'
|
15
|
+
opts.separator ' customize settings in appserver.yml inside this directory.'
|
16
|
+
opts.separator ''
|
17
|
+
opts.separator 'appserver [options] deploy <git-repository>'
|
18
|
+
opts.separator ' Deploys an application to the appserver directory and updates configurations.'
|
19
|
+
opts.separator ' Additionally, a hook is installed to the git repository, that auto-deploys'
|
20
|
+
opts.separator ' the application from now on, if sombody pushes to it.'
|
21
|
+
opts.separator ''
|
22
|
+
opts.separator 'appserver [options] update'
|
23
|
+
opts.separator ' Updates all generated configuration files.'
|
24
|
+
opts.separator ''
|
25
|
+
opts.separator 'Options:'
|
26
|
+
opts.on '-d', '--dir PATH', 'Change to the given directory before running the command' do |dir|
|
27
|
+
options[:dir] = dir
|
28
|
+
end
|
29
|
+
opts.on '-f', '--force', 'Force command execution even if it will overwrite files' do
|
30
|
+
options[:force] = true
|
31
|
+
end
|
32
|
+
opts.separator ''
|
33
|
+
opts.separator 'Common options:'
|
34
|
+
opts.on '-h', '--help', 'Show this message' do
|
35
|
+
puts opts; exit
|
36
|
+
end
|
37
|
+
opts.separator ''
|
38
|
+
opts.separator 'See http://github.com/zargony/appserver for more information'
|
39
|
+
opts.separator ''
|
40
|
+
end
|
41
|
+
|
42
|
+
args = opts.parse!
|
43
|
+
|
44
|
+
(puts opts; exit) if args.size < 1
|
45
|
+
Appserver::Command.run!(args.shift, args, options)
|
data/lib/appserver.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
module Appserver
|
2
|
+
ROOT = File.expand_path('..', __FILE__)
|
3
|
+
autoload :Utils, "#{ROOT}/appserver/utils"
|
4
|
+
autoload :Command, "#{ROOT}/appserver/command"
|
5
|
+
autoload :Server, "#{ROOT}/appserver/server"
|
6
|
+
autoload :App, "#{ROOT}/appserver/app"
|
7
|
+
autoload :Repository, "#{ROOT}/appserver/repository"
|
8
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Appserver
|
2
|
+
class App < Struct.new(:server, :name, :unicorn, :environment, :instances,
|
3
|
+
:max_cpu_usage, :max_memory_usage, :usage_check_cycles,
|
4
|
+
:http_check_timeout, :hostname, :public_dir)
|
5
|
+
DEFAULTS = {
|
6
|
+
:unicorn => '/usr/local/bin/unicorn',
|
7
|
+
:environment => 'production',
|
8
|
+
:instances => 3,
|
9
|
+
:max_cpu_usage => nil,
|
10
|
+
:max_memory_usage => nil,
|
11
|
+
:usage_check_cycles => 5,
|
12
|
+
:http_check_timeout => 30,
|
13
|
+
:hostname => `/bin/hostname -f`.chomp.gsub(/^[^.]+\./, ''),
|
14
|
+
:public_dir => 'public',
|
15
|
+
}
|
16
|
+
|
17
|
+
def self.unicorn_config
|
18
|
+
File.expand_path('../unicorn.conf.rb', __FILE__)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize (server, name, config)
|
22
|
+
super()
|
23
|
+
self.server, self.name = server, name
|
24
|
+
appconfig = (config[:apps] || {})[name.to_sym] || {}
|
25
|
+
DEFAULTS.each do |key, default_value|
|
26
|
+
self[key] = appconfig[key] || config[key] || default_value
|
27
|
+
end
|
28
|
+
# Use a subdomain of the default hostname if no hostname was given specifically for this app
|
29
|
+
self.hostname = "#{name}.#{hostname}" unless appconfig[:hostname]
|
30
|
+
end
|
31
|
+
|
32
|
+
def dir
|
33
|
+
File.join(server.dir, name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def rack_config
|
37
|
+
File.join(dir, 'config.ru')
|
38
|
+
end
|
39
|
+
|
40
|
+
def rack?
|
41
|
+
File.exist?(rack_config)
|
42
|
+
end
|
43
|
+
|
44
|
+
def pid_file
|
45
|
+
File.join(server.tmp_dir, "#{name}.pid")
|
46
|
+
end
|
47
|
+
|
48
|
+
def socket
|
49
|
+
File.join(server.tmp_dir, "#{name}.socket")
|
50
|
+
end
|
51
|
+
|
52
|
+
def server_log
|
53
|
+
File.join(server.log_dir, "#{name}.server.log")
|
54
|
+
end
|
55
|
+
|
56
|
+
def access_log
|
57
|
+
File.join(server.log_dir, "#{name}.access.log")
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_monit_config (f)
|
61
|
+
f.puts %Q()
|
62
|
+
f.puts %Q(# Application: #{name})
|
63
|
+
if rack?
|
64
|
+
cyclecheck = usage_check_cycles > 1 ? " for #{usage_check_cycles} cycles" : ''
|
65
|
+
f.puts %Q(check process #{name} with pidfile #{expand_path(pid_file)})
|
66
|
+
f.puts %Q( start program = "#{unicorn} -E #{environment} -Dc #{self.class.unicorn_config} #{rack_config}")
|
67
|
+
f.puts %Q( stop program = "/bin/kill `cat #{expand_path(pid_file)}`")
|
68
|
+
f.puts %Q( if totalcpu usage > #{max_cpu_usage}#{cyclecheck} then restart) if max_cpu_usage
|
69
|
+
f.puts %Q( if totalmemory usage > #{max_memory_usage}#{cyclecheck} then restart) if max_memory_usage
|
70
|
+
f.puts %Q( if failed unixsocket #{expand_path(socket)} protocol http request "/" timeout #{http_check_timeout} seconds then restart) if http_check_timeout > 0
|
71
|
+
f.puts %Q( if 5 restarts within 5 cycles then timeout)
|
72
|
+
f.puts %Q( group #{name})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def write_nginx_config (f)
|
77
|
+
f.puts ""
|
78
|
+
f.puts "# Application: #{name}"
|
79
|
+
if rack?
|
80
|
+
f.puts "upstream #{name}_cluster {"
|
81
|
+
f.puts " server unix:#{expand_path(socket)} fail_timeout=0;"
|
82
|
+
f.puts "}"
|
83
|
+
f.puts "server {"
|
84
|
+
f.puts " listen 80;"
|
85
|
+
f.puts " server_name #{hostname};"
|
86
|
+
f.puts " root #{expand_path(public_dir)};"
|
87
|
+
f.puts " access_log #{expand_path(access_log)};"
|
88
|
+
f.puts " location / {"
|
89
|
+
f.puts " proxy_set_header X-Real-IP $remote_addr;"
|
90
|
+
f.puts " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"
|
91
|
+
f.puts " proxy_set_header Host $http_host;"
|
92
|
+
f.puts " proxy_redirect off;"
|
93
|
+
# TODO: maintenance mode rewriting
|
94
|
+
f.puts " try_files $uri/index.html $uri.html $uri @#{name}_cluster;"
|
95
|
+
f.puts " error_page 500 502 503 504 /500.html;"
|
96
|
+
f.puts " }"
|
97
|
+
f.puts " location @#{name}_cluster {"
|
98
|
+
f.puts " proxy_pass http://#{name}_cluster;"
|
99
|
+
f.puts " }"
|
100
|
+
f.puts "}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def expand_path (path)
|
107
|
+
File.expand_path(path, dir)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# This is an appserver directory configuration of the "appserver" gem. Use the
|
2
|
+
# "appserver" command or visit http://github.com/zargony/appserver for details
|
3
|
+
|
4
|
+
#
|
5
|
+
# SERVER SETTINGS
|
6
|
+
# Non application specific. Paths are relative to the appserver directory. The
|
7
|
+
# appserver directory is the directory, that contains this configuration file.
|
8
|
+
#
|
9
|
+
|
10
|
+
# Path to the default directory of git repositories that contain the
|
11
|
+
# applications to deploy. Defaults to the home directory of the user 'git'
|
12
|
+
#repo_dir: /var/git
|
13
|
+
|
14
|
+
# Path/name of the Monit configuration snippet that should be written
|
15
|
+
#monit_conf: monitrc
|
16
|
+
|
17
|
+
# Command to execute to tell Monit to reload the configuration. Used within
|
18
|
+
# the Monit snippet, so this command will be called as root
|
19
|
+
#monit_reload: /usr/sbin/monit reload
|
20
|
+
|
21
|
+
# Path/name of the Nginx configuration snippet that should be written
|
22
|
+
#nginx_conf: nginx.conf
|
23
|
+
|
24
|
+
# Command to execute to tell Nginx to reload the configuration. Used within
|
25
|
+
# the Monit snippet, so this command will be called as root
|
26
|
+
#nginx_reload: /usr/sbin/nginx -s reload
|
27
|
+
|
28
|
+
|
29
|
+
#
|
30
|
+
# APPLICATION SETTINGS
|
31
|
+
# Can be either specified globally for all applications or application-
|
32
|
+
# specific under the "apps" subtree (see examples at the bottom of this file).
|
33
|
+
# Paths are relative to the respective application directory. Every deployed
|
34
|
+
# application has it's own directory under the appserver directory.
|
35
|
+
#
|
36
|
+
|
37
|
+
# Name/path of unicorn
|
38
|
+
#unicorn: /usr/local/bin/unicorn
|
39
|
+
|
40
|
+
# Environment to run the application in. Defaults to 'production'
|
41
|
+
#environment: production
|
42
|
+
|
43
|
+
# Default number of application instances (unicorn workers)
|
44
|
+
#instances: 3
|
45
|
+
|
46
|
+
# Let Monit watch the CPU usage of instances and restart them if their
|
47
|
+
# CPU usage exceeds this value
|
48
|
+
#max_cpu_usage:
|
49
|
+
|
50
|
+
# Let Monit watch the memory usage of instances and restart them if their
|
51
|
+
# memory usage exceeds this value
|
52
|
+
#max_memory_usage:
|
53
|
+
|
54
|
+
# When doing CPU/memory usage checks, only restart an instance if it exceeds
|
55
|
+
# a resource for at least this number of Monit cycles
|
56
|
+
#usage_check_cycles: 5
|
57
|
+
|
58
|
+
# Let Monit check periodically, if instances provide an answer to HTTP
|
59
|
+
# requests within the given timeout, or restart them if they don't. Set
|
60
|
+
# to 0 to disable
|
61
|
+
#http_check_timeout: 30
|
62
|
+
|
63
|
+
# The hostname, Nginx should accept requests for. You most porbably want to
|
64
|
+
# specify the hostname for every application below. If an application has no
|
65
|
+
# hostname set, a subdomain of this default hostname will be used. Defaults
|
66
|
+
# to the system's domainname.
|
67
|
+
#hostname: example.com
|
68
|
+
|
69
|
+
# Path where public static files should be served from. Defaults to the public
|
70
|
+
# directory in the application
|
71
|
+
#public_dir: public
|
72
|
+
|
73
|
+
|
74
|
+
#
|
75
|
+
# APPLICATIONS
|
76
|
+
# All application default settings from above can be overridden for every
|
77
|
+
# application. You most probably want to set "hostname" to your liking here.
|
78
|
+
# Most other settings should do well with their defaults in most cases.
|
79
|
+
#
|
80
|
+
|
81
|
+
#apps:
|
82
|
+
# # A simple blog application named "myblog"
|
83
|
+
# myblog:
|
84
|
+
# hostname: blog.example.com
|
85
|
+
# instances: 1
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Appserver
|
2
|
+
class UnknownCommandError < RuntimeError; end
|
3
|
+
|
4
|
+
class Command
|
5
|
+
def self.run! (*args)
|
6
|
+
new(*args).run!
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :command, :arguments, :options
|
10
|
+
|
11
|
+
def initialize (command, arguments, options = {})
|
12
|
+
@command, @arguments, @options = command, arguments, options
|
13
|
+
end
|
14
|
+
|
15
|
+
def run!
|
16
|
+
Dir.chdir(options[:dir]) if options[:dir]
|
17
|
+
|
18
|
+
Server.initialize_dir(options) if command == 'init'
|
19
|
+
|
20
|
+
server = Server.new(options)
|
21
|
+
|
22
|
+
case command
|
23
|
+
when 'init'
|
24
|
+
server.write_configs
|
25
|
+
puts 'Initialized appserver directory.'
|
26
|
+
puts 'Wrote Monit and Nginx configuration snippets. Make sure to include them into'
|
27
|
+
puts 'your system\'s Monit and Nginx configuration to become active.'
|
28
|
+
|
29
|
+
when 'deploy'
|
30
|
+
repository = server.repository(arguments[0])
|
31
|
+
# TODO
|
32
|
+
repository.install_hook
|
33
|
+
|
34
|
+
when 'update'
|
35
|
+
server.write_configs
|
36
|
+
puts 'Wrote Monit and Nginx configuration snippets.'
|
37
|
+
|
38
|
+
else
|
39
|
+
raise UnknownCommandError
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Appserver
|
2
|
+
class Repository < Struct.new(:server, :dir)
|
3
|
+
class InvalidRepositoryError < RuntimeError; end
|
4
|
+
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
def initialize (server, dir, config)
|
8
|
+
self.server, self.dir = server, dir.chomp('/')
|
9
|
+
raise InvalidRepositoryError unless valid?
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
File.basename(dir, '.git')
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
File.directory?(File.join(dir, 'hooks')) && File.directory?(File.join(dir, 'refs'))
|
18
|
+
end
|
19
|
+
|
20
|
+
def post_receive_hook
|
21
|
+
File.join(dir, 'hooks', 'post-receive')
|
22
|
+
end
|
23
|
+
|
24
|
+
def install_hook
|
25
|
+
deploy_cmd = "#{File.expand_path($0)} -d #{server.dir} deploy #{dir}"
|
26
|
+
puts deploy_cmd
|
27
|
+
if !File.exist?(post_receive_hook) || !File.executable?(post_receive_hook)
|
28
|
+
puts "Installing git post-receive hook to repository #{dir}..."
|
29
|
+
safe_replace_file(post_receive_hook) do |f|
|
30
|
+
f.puts '#!/bin/sh'
|
31
|
+
f.puts deploy_cmd
|
32
|
+
f.chown File.stat(dir).uid, File.stat(dir).gid
|
33
|
+
f.chmod 0755
|
34
|
+
end
|
35
|
+
elsif !File.readlines(post_receive_hook).any? { |line| line =~ /^#{Regexp.escape(deploy_cmd)}/ }
|
36
|
+
puts "Couldn't install post-receive hook. Foreign hook script already present in repository #{dir}!"
|
37
|
+
else
|
38
|
+
#puts "Hook already installed in repository #{dir}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def expand_path (path)
|
45
|
+
File.expand_path(path, dir)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Appserver
|
5
|
+
class Server < Struct.new(:dir, :repo_dir, :monit_conf, :monit_reload, :nginx_conf, :nginx_reload)
|
6
|
+
class AlreadyInitializedError < RuntimeError; end
|
7
|
+
class DirectoryNotEmptyError < RuntimeError; end
|
8
|
+
class NotInitializedError < RuntimeError; end
|
9
|
+
|
10
|
+
include Utils
|
11
|
+
|
12
|
+
DEFAULTS = {
|
13
|
+
:repo_dir => (Etc.getpwnam('git') rescue {})[:dir],
|
14
|
+
:monit_conf => 'monitrc',
|
15
|
+
:monit_reload => '/usr/sbin/monit reload',
|
16
|
+
:nginx_conf => 'nginx.conf',
|
17
|
+
:nginx_reload => '/usr/sbin/nginx -s reload',
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.config_file_template
|
21
|
+
File.expand_path('../appserver.yml', __FILE__)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.search_dir (path = Dir.pwd)
|
25
|
+
if File.exist?(File.join(path, 'appserver.yml'))
|
26
|
+
path
|
27
|
+
elsif path =~ %r(/)
|
28
|
+
search_dir(path.sub(%r(/[^/]*$), ''))
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.initialize_dir (options = {})
|
35
|
+
raise AlreadyInitializedError if search_dir && !options[:force]
|
36
|
+
raise DirectoryNotEmptyError if Dir.glob('*') != [] && !options[:force]
|
37
|
+
safe_replace_file('appserver.yml') do |f|
|
38
|
+
f.puts File.read(config_file_template)
|
39
|
+
end
|
40
|
+
['tmp', 'log'].each do |dir|
|
41
|
+
Dir.mkdir(dir) if !File.directory?(dir)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize (options = {})
|
46
|
+
super()
|
47
|
+
# Search upwards for the appserver dir
|
48
|
+
self.dir = self.class.search_dir
|
49
|
+
raise NotInitializedError unless dir
|
50
|
+
# Load configuration settings
|
51
|
+
@config = load_config(config_file)
|
52
|
+
DEFAULTS.each do |key, default_value|
|
53
|
+
self[key] = @config[key] || default_value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def config_file
|
58
|
+
File.join(dir, 'appserver.yml')
|
59
|
+
end
|
60
|
+
|
61
|
+
def tmp_dir
|
62
|
+
File.join(dir, 'tmp')
|
63
|
+
end
|
64
|
+
|
65
|
+
def log_dir
|
66
|
+
File.join(dir, 'log')
|
67
|
+
end
|
68
|
+
|
69
|
+
def app (name)
|
70
|
+
@apps ||= {}
|
71
|
+
@apps[name] ||= App.new(self, name, @config)
|
72
|
+
end
|
73
|
+
|
74
|
+
def apps
|
75
|
+
Dir.glob(File.join(dir, '*')).select { |f| File.directory?(f) }.map { |f| File.basename(f) }.map { |name| app(name) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def repository (name_or_path)
|
79
|
+
path = name_or_path =~ %r(/) ? expand_path(name_or_path) : File.expand_path("#{name_or_path}.git", repo_dir)
|
80
|
+
@repositories ||= {}
|
81
|
+
@repositories[path] ||= Repository.new(self, path, @config)
|
82
|
+
end
|
83
|
+
|
84
|
+
def repositories
|
85
|
+
Dir.glob(File.join(repo_dir, '*.git')).select { |f| File.directory?(f) }.map { |path| repository(path) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def write_configs
|
89
|
+
# Write Monit configuration file
|
90
|
+
safe_replace_file(monit_conf) do |f|
|
91
|
+
f.puts %Q(# Monit configuration automagically generated by the "appserver" gem using)
|
92
|
+
f.puts %Q(# the appserver directory config #{expand_path(config_file)})
|
93
|
+
f.puts %Q(# Include this file into your system's monitrc (using an include statement))
|
94
|
+
f.puts %Q(# to use it. See http://github.com/zargony/appserver for details.)
|
95
|
+
# Let Monit reload itself if this configuration changes
|
96
|
+
f.puts %Q(check file monit_conf with path #{expand_path(monit_conf)})
|
97
|
+
f.puts %Q( if changed checksum then exec "#{monit_reload}")
|
98
|
+
# Reload Nginx if its configuration changes
|
99
|
+
f.puts %Q(check file nginx_conf with path #{expand_path(nginx_conf)})
|
100
|
+
f.puts %Q( if changed checksum then exec "#{nginx_reload}")
|
101
|
+
# Add application-specific Monit configuration
|
102
|
+
apps.each do |app|
|
103
|
+
app.write_monit_config(f)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# Write Nginx configuration file
|
107
|
+
safe_replace_file(nginx_conf) do |f|
|
108
|
+
f.puts %Q(# Nginx configuration automagically generated by the "appserver" gem using)
|
109
|
+
f.puts %Q(# the appserver directory config #{expand_path(config_file)})
|
110
|
+
f.puts %Q(# Include this file into your system's nginx.conf \(using an include statement)
|
111
|
+
f.puts %Q(# inside a http statement\) to use it. See http://github.com/zargony/appserver)
|
112
|
+
f.puts %Q(# for details.)
|
113
|
+
# The default server always responds with 403 Forbidden
|
114
|
+
f.puts %Q(server {)
|
115
|
+
f.puts %Q( listen 80 default;)
|
116
|
+
f.puts %Q( server_name _;)
|
117
|
+
f.puts %Q( deny all;)
|
118
|
+
f.puts %Q(})
|
119
|
+
# Add application-specific Nginx configuration
|
120
|
+
apps.each do |app|
|
121
|
+
app.write_nginx_config(f)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
|
128
|
+
def expand_path (path)
|
129
|
+
File.expand_path(path, dir)
|
130
|
+
end
|
131
|
+
|
132
|
+
def load_config (filename)
|
133
|
+
symbolize_keys(YAML.load_file(filename) || {})
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.expand_path('../../appserver', __FILE__)
|
2
|
+
# We assume that the last argument to unicorn is the full path to config.ru
|
3
|
+
Dir.chdir(File.expand_path('..', Unicorn::HttpServer::START_CTX[:argv][-1]))
|
4
|
+
app = Appserver::Server.new.app(File.basename(Dir.pwd))
|
5
|
+
|
6
|
+
working_directory app.dir
|
7
|
+
stderr_path app.server_log
|
8
|
+
stdout_path app.server_log
|
9
|
+
puts "Appserver unicorn configuration for #{app.dir}"
|
10
|
+
pid app.pid_file
|
11
|
+
listen "unix:#{app.socket}", :backlog => 64
|
12
|
+
#user 'user', 'group'
|
13
|
+
worker_processes app.instances
|
14
|
+
timeout 30
|
15
|
+
preload_app true
|
16
|
+
|
17
|
+
# Use COW-friendly REE for memory saving, especially with preloaded apps
|
18
|
+
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
|
19
|
+
GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
|
20
|
+
|
21
|
+
before_fork do |server, worker|
|
22
|
+
# For preloaded apps, it is highly recommended to disconnect any database
|
23
|
+
# connection and reconnect it in the worker
|
24
|
+
if server.config[:preload_app]
|
25
|
+
ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
|
26
|
+
end
|
27
|
+
|
28
|
+
# The following is only recommended for memory/DB-constrained
|
29
|
+
# installations. It is not needed if your system can house
|
30
|
+
# twice as many worker_processes as you have configured.
|
31
|
+
#
|
32
|
+
# # This allows a new master process to incrementally
|
33
|
+
# # phase out the old master process with SIGTTOU to avoid a
|
34
|
+
# # thundering herd (especially in the "preload_app false" case)
|
35
|
+
# # when doing a transparent upgrade. The last worker spawned
|
36
|
+
# # will then kill off the old master process with a SIGQUIT.
|
37
|
+
# old_pid = "#{server.config[:pid]}.oldbin"
|
38
|
+
# if old_pid != server.pid
|
39
|
+
# begin
|
40
|
+
# sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
|
41
|
+
# Process.kill(sig, File.read(old_pid).to_i)
|
42
|
+
# rescue Errno::ENOENT, Errno::ESRCH
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # *optionally* throttle the master from forking too quickly by sleeping
|
47
|
+
# sleep 1
|
48
|
+
end
|
49
|
+
|
50
|
+
after_fork do |server, worker|
|
51
|
+
# Per-process listener ports for debugging/admin/migrations
|
52
|
+
#addr = "127.0.0.1:#{9293 + worker.nr}"
|
53
|
+
#server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
|
54
|
+
|
55
|
+
# Reconnect the database connection in the worker (see disconnect above)
|
56
|
+
if server.config[:preload_app]
|
57
|
+
ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
|
58
|
+
# TODO: check for other database mapper and reconnect them (mongo, memcache, redis)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Appserver
|
4
|
+
module Utils
|
5
|
+
def self.included (base)
|
6
|
+
base.class_eval do
|
7
|
+
extend Methods
|
8
|
+
include Methods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Methods
|
13
|
+
def safe_replace_file (filename)
|
14
|
+
tempfile = Tempfile.new(File.basename(filename) + '.', File.dirname(filename))
|
15
|
+
if File.exist?(filename)
|
16
|
+
tempfile.chown(File.stat(filename).uid, File.stat(filename).gid)
|
17
|
+
tempfile.chmod(File.stat(filename).mode)
|
18
|
+
end
|
19
|
+
yield tempfile
|
20
|
+
tempfile.close
|
21
|
+
File.unlink(filename) if File.exist?(filename)
|
22
|
+
File.rename(tempfile, filename)
|
23
|
+
end
|
24
|
+
|
25
|
+
def symbolize_keys (hash)
|
26
|
+
hash.inject({}) do |memo, (key, value)|
|
27
|
+
value = symbolize_keys(value) if Hash === value
|
28
|
+
memo[key.to_sym] = value
|
29
|
+
memo
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: appserver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Andreas Neuhaus
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-28 00:00:00 +02:00
|
18
|
+
default_executable: appserver
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: unicorn
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 97
|
30
|
+
version: "0.97"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description: This little tool automatically generates server configs for Monit, Nginx and Unicorn to host your Rack-based (Rails) applications. Running it automatically in git post-receive hooks provides an automatic deployment of applications whenever the repository is updated on the server.
|
34
|
+
email: zargony@gmail.com
|
35
|
+
executables:
|
36
|
+
- appserver
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- LICENSE
|
41
|
+
- README.md
|
42
|
+
files:
|
43
|
+
- LICENSE
|
44
|
+
- README.md
|
45
|
+
- bin/appserver
|
46
|
+
- lib/appserver.rb
|
47
|
+
- lib/appserver/app.rb
|
48
|
+
- lib/appserver/appserver.yml
|
49
|
+
- lib/appserver/command.rb
|
50
|
+
- lib/appserver/repository.rb
|
51
|
+
- lib/appserver/server.rb
|
52
|
+
- lib/appserver/unicorn.conf.rb
|
53
|
+
- lib/appserver/utils.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/zargony/appserver
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --charset=UTF-8
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements:
|
78
|
+
- nginx v0.7.x or greater
|
79
|
+
- monit v5.x or greater
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.6
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Monit/nginx/unicorn application server configurator using deployment via git
|
85
|
+
test_files: []
|
86
|
+
|