appserver 0.0.1 → 0.0.2

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