appserver 0.0.1 → 0.0.2

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.
@@ -0,0 +1,3 @@
1
+ /appserver.gemspec
2
+ /pkg
3
+ /VERSION
data/README.md CHANGED
@@ -4,10 +4,10 @@ Automagic application server configurator
4
4
  Monit/Nginx/Unicorn application server configurator using deployment via git
5
5
  (simply git push applications to your server to deploy them).
6
6
 
7
- This little tool automatically generates server configs for [Monit][monit],
7
+ This tool automatically generates server configs for [Monit][monit],
8
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
9
+ applications. Running it automatically in git update hooks provides an
10
+ automatic deployment of applications whenever the repository is updated
11
11
  on the server.
12
12
 
13
13
  Requirements
@@ -26,18 +26,20 @@ Or check out the [repository][repo] on github.
26
26
  Setup
27
27
  -----
28
28
 
29
+ ### Initialize an appserver directory
30
+
29
31
  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.
32
+ do so, run `appserver init`.
31
33
 
32
- $ mkdir /var/webapps
33
- $ cd /var/webapps
34
- $ appserver init
34
+ $ appserver init /var/webapps
35
35
 
36
36
  An appserver directory holds configuration files and everything needed to run
37
37
  multiple applications (application code, temp files, log files, ...). You can
38
- customize settings by editing the `appserver.yml` configuration file. **All
38
+ customize settings by editing the `appserver.conf.rb` configuration file. **All
39
39
  other files are updated automatically and should not be modified manually.**
40
40
 
41
+ ### Activate generated Nginx configuration
42
+
41
43
  Modify your system's Nginx configuration (e.g. `/etc/nginx/nginx.conf` on
42
44
  Ubuntu) to include the generated `nginx.conf` **inside a `http` statement**.
43
45
  Reload Nginx to apply the configuration changes.
@@ -47,10 +49,12 @@ Reload Nginx to apply the configuration changes.
47
49
 
48
50
  http {
49
51
 
50
- include /var/www/nginx.conf;
52
+ include /var/webapps/nginx.conf;
51
53
  }
52
54
 
53
55
 
56
+ ### Activate generated Monit configuration
57
+
54
58
  Modify your system's Monit configuration (e.g. `/etc/monit/monitrc` on Ubuntu)
55
59
  to include the generated `monitrc` at the bottom. Reload Monit to apply the
56
60
  configuration changes.
@@ -60,6 +64,18 @@ configuration changes.
60
64
 
61
65
  include /var/webapps/monitrc
62
66
 
67
+ ### Optional: Activate generated Logrotate configuration
68
+
69
+ Modify your system's Logrotate configuration (e.g. `/etc/logrotate.conf` on
70
+ Ubuntu) to include the generated `logrotate.conf` at the bottom. Logrotate
71
+ is typically executed from cron, so there's no daemon to reload to apply the
72
+ configuration changes.
73
+
74
+ */etc/logrotate.conf:*
75
+
76
+
77
+ include /var/webapps/logrotate.conf
78
+
63
79
  Deploying an application
64
80
  ------------------------
65
81
 
@@ -70,6 +86,11 @@ How it works
70
86
 
71
87
  to be done...
72
88
 
89
+ Security considerations
90
+ -----------------------
91
+
92
+ to be done...
93
+
73
94
  Author
74
95
  ------
75
96
 
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = 'appserver'
8
+ gem.summary = 'Monit/Nginx/Unicorn application server configurator using deployment via git'
9
+ gem.description = 'This tool automatically generates server configs for Monit, Nginx and Unicorn to host your Rack-based (Rails) applications. Running it automatically in git update hooks provides an automatic deployment of applications whenever the repository is updated on the server.'
10
+ gem.email = 'zargony@gmail.com'
11
+ gem.homepage = 'http://github.com/zargony/appserver'
12
+ gem.authors = ['Andreas Neuhaus']
13
+ gem.requirements << 'a server with Monit, Nginx and Git'
14
+ gem.add_dependency 'unicorn', '~> 0.97'
15
+ gem.add_dependency 'git', '~> 1.2'
16
+ gem.add_dependency 'bundler', '>= 0.9.24'
17
+ gem.has_rdoc = false
18
+ gem.add_development_dependency 'mocha'
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ task :default => :test
27
+ Rake::TestTask.new(:test) do |test|
28
+ test.libs << 'lib' << 'test'
29
+ test.pattern = 'test/**/test_*.rb'
30
+ test.verbose = true
31
+ end
@@ -9,18 +9,21 @@ options = {}
9
9
  opts = OptionParser.new(nil, 20, ' ') do |opts|
10
10
  opts.banner = 'Usage: appserver [options] init|deploy|update [arguments]'
11
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.'
12
+ opts.separator 'appserver [options] init <path>'
13
+ opts.separator ' Initializes an appserver directory. Run this command to set up the given'
14
+ opts.separator ' directory for deploying applications. After this, you can customize'
15
+ opts.separator " settings in #{Appserver::ServerDir::CONFIG_FILE_NAME} inside the directory."
16
+ opts.separator ''
17
+ opts.separator 'appserver [options] update'
18
+ opts.separator ' Updates all generated configuration files.'
16
19
  opts.separator ''
17
20
  opts.separator 'appserver [options] deploy <git-repository>'
18
21
  opts.separator ' Deploys an application to the appserver directory and updates configurations.'
19
22
  opts.separator ' Additionally, a hook is installed to the git repository, that auto-deploys'
20
23
  opts.separator ' the application from now on, if sombody pushes to it.'
21
24
  opts.separator ''
22
- opts.separator 'appserver [options] update'
23
- opts.separator ' Updates all generated configuration files.'
25
+ opts.separator 'NOTE: For all commands (except "init"), you need to be inside an appserver'
26
+ opts.separator ' directory, or specify an appserver directory using the --dir option.'
24
27
  opts.separator ''
25
28
  opts.separator 'Options:'
26
29
  opts.on '-d', '--dir PATH', 'Change to the given directory before running the command' do |dir|
@@ -1,8 +1,12 @@
1
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"
2
+ ROOT = File.expand_path('..', __FILE__) unless const_defined?(:ROOT)
3
+ autoload :Utils, "#{ROOT}/appserver/utils"
4
+ autoload :Command, "#{ROOT}/appserver/command"
5
+ autoload :Configurator, "#{ROOT}/appserver/configurator"
6
+ autoload :ServerDir, "#{ROOT}/appserver/server_dir"
7
+ autoload :App, "#{ROOT}/appserver/app"
8
+ autoload :Repository, "#{ROOT}/appserver/repository"
9
+ autoload :Monit, "#{ROOT}/appserver/monit"
10
+ autoload :Nginx, "#{ROOT}/appserver/nginx"
11
+ autoload :Logrotate, "#{ROOT}/appserver/logrotate"
8
12
  end
@@ -1,110 +1,151 @@
1
+ require 'etc'
2
+
1
3
  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',
4
+ class App < Struct.new(:server_dir, :name, :branch, :ruby, :environment, :user, :group, :instances, :preload,
5
+ :env_whitelist, :env, :max_cpu_usage, :max_memory_usage, :usage_check_cycles, :http_check_timeout,
6
+ :domain, :hostname, :ssl_cert, :ssl_key, :public_dir)
7
+
8
+ SETTINGS_DEFAULTS = {
9
+ :branch => 'master',
10
+ :ruby => Utils.find_in_path('ruby') || '/usr/bin/ruby',
7
11
  :environment => 'production',
8
- :instances => 3,
12
+ :user => nil,
13
+ :group => nil,
14
+ :instances => Utils.number_of_cpus || 1,
15
+ :preload => false,
16
+ :env_whitelist => [],
17
+ :env => {},
9
18
  :max_cpu_usage => nil,
10
19
  :max_memory_usage => nil,
11
20
  :usage_check_cycles => 5,
12
21
  :http_check_timeout => 30,
13
- :hostname => `/bin/hostname -f`.chomp.gsub(/^[^.]+\./, ''),
22
+ :domain => Utils.system_domainname,
23
+ :hostname => nil,
24
+ :ssl_cert => nil,
25
+ :ssl_key => nil,
14
26
  :public_dir => 'public',
15
27
  }
16
28
 
17
- def self.unicorn_config
18
- File.expand_path('../unicorn.conf.rb', __FILE__)
29
+ SETTINGS_EXPAND = [ :ssl_cert, :ssl_key ]
30
+
31
+ ALWAYS_WHITELIST = ['PATH', 'PWD', 'GEM_HOME', 'GEM_PATH', 'RACK_ENV']
32
+
33
+ def initialize (server_dir, name, config)
34
+ self.server_dir, self.name = server_dir, name
35
+ # Apply configuration settings
36
+ config.apply!(self, name)
37
+ # Use the directory owner as the user to run instances under by default
38
+ self.user ||= exist? ? Etc.getpwuid(File.stat(path).uid).name : 'www-data'
39
+ # Use a subdomain if no hostname was given specifically for this app
40
+ self.hostname ||= "#{name.gsub(/[^a-z0-9_-]+/i, '_')}.#{domain}"
19
41
  end
20
42
 
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]
43
+ def path
44
+ File.join(server_dir.apps_path, name)
45
+ end
46
+
47
+ def exist?
48
+ File.directory?(path)
30
49
  end
31
50
 
32
- def dir
33
- File.join(server.dir, name)
51
+ def revision_file
52
+ File.join(path, 'REVISION')
53
+ end
54
+
55
+ def ssl?
56
+ ssl_cert && ssl_key
57
+ end
58
+
59
+ def public_path
60
+ File.expand_path(public_dir, path)
34
61
  end
35
62
 
36
63
  def rack_config
37
- File.join(dir, 'config.ru')
64
+ File.join(path, 'config.ru')
38
65
  end
39
66
 
40
67
  def rack?
41
68
  File.exist?(rack_config)
42
69
  end
43
70
 
71
+ def unicorn_config
72
+ File.expand_path('../unicorn.conf.rb', __FILE__)
73
+ end
74
+
75
+ def gem_file
76
+ File.join(path, 'Gemfile')
77
+ end
78
+
79
+ def bundle_path
80
+ File.join(path, '.bundle')
81
+ end
82
+
44
83
  def pid_file
45
- File.join(server.tmp_dir, "#{name}.pid")
84
+ File.join(server_dir.tmp_path, "#{name}.pid")
46
85
  end
47
86
 
48
87
  def socket
49
- File.join(server.tmp_dir, "#{name}.socket")
88
+ File.join(server_dir.tmp_path, "#{name}.socket")
50
89
  end
51
90
 
52
91
  def server_log
53
- File.join(server.log_dir, "#{name}.server.log")
92
+ File.join(server_dir.log_path, "#{name}.server.log")
54
93
  end
55
94
 
56
95
  def access_log
57
- File.join(server.log_dir, "#{name}.access.log")
96
+ File.join(server_dir.log_path, "#{name}.access.log")
58
97
  end
59
98
 
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})
99
+ def setup_env!
100
+ # Apply whitelist if set
101
+ if env_whitelist != '*' && env_whitelist != ['*']
102
+ ENV.reject! { |key, value| !env_whitelist.include?(key) && !ALWAYS_WHITELIST.include?(key) }
103
+ end
104
+ # Set environment variables
105
+ if env
106
+ ENV.update(env)
107
+ end
108
+ # Setup gem bundle if present
109
+ if File.exist?(gem_file) && File.directory?(bundle_path)
110
+ ENV.update({ 'GEM_HOME' => bundle_path, 'BUNDLE_PATH' => bundle_path })
111
+ # Remember load paths of required gems (which use autloading), before bundler takes away the load path
112
+ remember_paths = $LOAD_PATH.select { |path| path =~ %r(/(unicorn|rack|appserver)[^/]*/) }
113
+ # Load bundler and setup gem bundle
114
+ require 'bundler'
115
+ Bundler.setup
116
+ # Re-add remembered load paths
117
+ $LOAD_PATH.unshift *remember_paths
73
118
  end
74
119
  end
75
120
 
76
- def write_nginx_config (f)
77
- f.puts ""
78
- f.puts "# Application: #{name}"
121
+ def start_server_cmd
79
122
  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 "}"
123
+ "#{ruby} -S -- unicorn -E #{environment} -Dc #{unicorn_config} #{rack_config}"
101
124
  end
102
125
  end
103
126
 
104
- protected
127
+ def start_server?
128
+ !!start_server_cmd
129
+ end
130
+
131
+ def start_server
132
+ exec start_server_cmd if start_server?
133
+ end
134
+
135
+ def server_pid
136
+ File.readlines(pid_file)[0].to_i rescue nil
137
+ end
138
+
139
+ def stop_server
140
+ Process.kill(:TERM, server_pid)
141
+ end
142
+
143
+ def restart_server
144
+ Process.kill(:USR2, server_pid)
145
+ end
105
146
 
106
- def expand_path (path)
107
- File.expand_path(path, dir)
147
+ def reopen_server_log
148
+ Process.kill(:USR1, server_pid)
108
149
  end
109
150
  end
110
151
  end
@@ -0,0 +1,126 @@
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/name of the Monit configuration snippet that should be written
11
+ #monit_conf 'monitrc'
12
+
13
+ # Command to execute to tell Monit to reload the configuration. Used within
14
+ # the Monit snippet, so this command will be called as root
15
+ #monit_reload '/usr/sbin/monit reload'
16
+
17
+ # Path/name of the Nginx configuration snippet that should be written
18
+ #nginx_conf 'nginx.conf'
19
+
20
+ # Command to execute to tell Nginx to reload the configuration. Used within
21
+ # the Monit snippet, so this command will be called as root
22
+ #nginx_reload '/usr/sbin/nginx -s reload'
23
+
24
+ # Command to execute to tell Nginx to reopen the log files. Used within
25
+ # the Logrotate snippet, so this command will be called as root
26
+ #nginx_reopen '/usr/sbin/nginx -s reopen'
27
+
28
+ # Path/name of the logrotate configuration snippet that should be written
29
+ #logrotate_conf 'logrotate.conf'
30
+
31
+
32
+ #
33
+ # APPLICATION SETTINGS
34
+ # Can be either specified globally for all applications or application-
35
+ # specific in an "app" block (see examples at the bottom of this file).
36
+ # Paths are relative to the respective directory of the application
37
+ #
38
+
39
+ # Branch to check out when deploying an application. Defaults to 'master'
40
+ #branch 'master'
41
+
42
+ # Name/path of the command to call Ruby. By default, this points to the
43
+ # ruby command in your PATH, usually /usr/bin/ruby. Set this, if you want
44
+ # to use a manually installed ruby that is not in your PATH. If you're using
45
+ # RVM, you can use "/usr/local/bin/rvm XYZ ruby" here to easily use
46
+ # different Ruby versions for different applications. Make sure the
47
+ # targetted Ruby version also has the appserver gem installed!
48
+ #ruby '/usr/bin/ruby'
49
+
50
+ # Rack environment to run the application in. Defaults to 'production'
51
+ #environment 'production'
52
+
53
+ # User and group to run the instance server under. If user is left empty, the
54
+ # user who owns the application directory is used. If group is left empty, the
55
+ # primary group of the user is used. For security reasons, you might want to
56
+ # set this to a different user than the file owner. (Debian based systems
57
+ # typically use "www-data"). Using root here would be a real bad idea
58
+ #user 'www-data'
59
+ #group 'www-data'
60
+
61
+ # Number of instances (unicorn workers) for an application. This defaults to
62
+ # the number of CPUs detected, which should be fine for most cases (read the
63
+ # unicorn tuning tips before changing this)
64
+ #instances 2
65
+
66
+ # Use Unicorn's application preloading before forking instances. Defaults to
67
+ # false (preloading disabled)
68
+ #preload false
69
+
70
+ # By default, only PATH, PWD, GEM_HOME and GEM_PATH environment settings are
71
+ # preserved. If you need more environment variables to be preserved, add them
72
+ # here. If you set this to '*', all environment variables are preserved
73
+ #env_whitelist [ 'SOME_VAR', 'SOME_OTHER_VAR' ]
74
+
75
+ # In addition to the whitelisted environment variables, RACK_ENV and
76
+ # BUNDLE_PATH are set. If want to set more environment variables, specify
77
+ # them here
78
+ #env { 'KEY' => 'value' }
79
+
80
+ # Let Monit watch the CPU usage of instances and restart them if their
81
+ # CPU usage exceeds this value
82
+ #max_cpu_usage '80%'
83
+
84
+ # Let Monit watch the memory usage of instances and restart them if their
85
+ # memory usage exceeds this value
86
+ #max_memory_usage '100Mb'
87
+
88
+ # When doing CPU/memory usage checks, only restart an instance if it exceeds
89
+ # a resource for at least this number of Monit cycles
90
+ #usage_check_cycles 5
91
+
92
+ # Let Monit check periodically, if instances provide an answer to HTTP
93
+ # requests within the given timeout, or restart them if they don't. Set
94
+ # to 0 to disable
95
+ #http_check_timeout 30
96
+
97
+ # The domain will be used as the base domain for applications which you
98
+ # don't specifically set a hostname for. For these applications, a
99
+ # hostname of "<appname>.<domain>" will be set automatically. The hostname
100
+ # tells Nginx, which requests to route to the application and therefore
101
+ # makes it possible to run multiple domains on a single IP address. The
102
+ # domain setting defaults to the system's domainname
103
+ #domain 'example.com'
104
+
105
+ # If a SSL certificate and key are set, Nginx will be configured to accept
106
+ # HTTPS connections as well. The default is to only accept HTTP
107
+ #ssl_cert '/etc/ssl/certs/mydomain.pem'
108
+ #ssl_key '/etc/ssl/private/mydomain.key'
109
+
110
+ # Path where public static files should be served from by Nginx. Defaults to
111
+ # the public directory in the application
112
+ #public_dir 'public'
113
+
114
+
115
+ #
116
+ # APPLICATIONS
117
+ # All application default settings from above can be overridden for specific
118
+ # applications. You most probably want to set "hostname" to your liking here.
119
+ # Most other settings should do well with their defaults in most cases.
120
+ #
121
+
122
+ # A simple blog application named "myblog"
123
+ #app 'myblog' do
124
+ # hostname 'blog.example.com'
125
+ # instances 1
126
+ #end