appserver 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/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
|
+
|