diff --git a/.gitignore b/.gitignore index 7c417c2..c7df4af 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ Gemfile.lock *.un~ /cookbooks .chef +/.kitchen diff --git a/.kitchen.yml b/.kitchen.yml new file mode 100644 index 0000000..9385751 --- /dev/null +++ b/.kitchen.yml @@ -0,0 +1,22 @@ +--- +driver: + name: vagrant + customize: + memory: 768 + network: + - ['forwarded_port', {guest: 80, host: 8080, auto_correct: true}] + +provisioner: + name: chef_zero + +platforms: + - name: centos-7.3 + - name: ubuntu-12.04 + +suites: + - name: default + run_list: + - recipe[mongodb] + - recipe[chef_nginx] + - recipe[errbit::default] + - recipe[errbit::bootstrap] diff --git a/Berksfile b/Berksfile index c4bb297..03bfe35 100644 --- a/Berksfile +++ b/Berksfile @@ -1,3 +1,2 @@ -site :opscode - +source 'https://api.berkshelf.com' metadata diff --git a/README.md b/README.md index 386758a..9c00ac8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Description This cookbook is designed to be able to run [Errbit](http://github.com/errbit/errbit). -Its github is at [chef-errbit](https://github.com/millisami/chef-errbit) +Its github is at [chef-errbit](https://github.com/klamontagne/chef-errbit) # Requirements @@ -9,12 +9,14 @@ Developed using chef 10.14.0 and it should work with higher versions. But not te The following Opscode cookbooks are dependencies: -* mongodb * git -* unicorn * apt * nginx +You also need a MongoDB installation, such as with the [mongodb cookbook](https://github.com/edelight/chef-mongodb). + +If you have other installations of rbenv on the node, you need to edit the node's user_installs as described in [chef-rbenv's documentation](https://github.com/fnichol/chef-rbenv#-rbenv-installed-for-a-specific-user-with-rubies). + # Usage Just to install the Errbit app, include the following in your wrapper cookbook's recipe diff --git a/Vagrantfile b/Vagrantfile index 9fc76ed..be824e8 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -46,8 +46,7 @@ Vagrant.configure("2") do |config| } chef.run_list = [ - 'recipe[errbit::install_ruby]', - 'recipe[nginx]', + 'recipe[chef_nginx]', 'recipe[errbit::default]', 'recipe[errbit::bootstrap]' ] diff --git a/attributes/default.rb b/attributes/default.rb index ec1a4b0..30a7b3c 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -4,45 +4,81 @@ # # Copyright (C) 2013 Millisami # -# All rights reserved - Do Not Redistribute +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # default['errbit']['name'] = "errbit" -default['errbit']['user'] = "deployer" -default['errbit']['password'] = "$1$qqO27xay$dtmwY9NMmJiSa47xhUZm0." #errbit +default['errbit']['user'] = "errbit" default['errbit']['group'] = node['errbit']['user'] -default['errbit']['deploy_to'] = "/home/#{default['errbit']['user']}/#{node['errbit']['name']}" +default['errbit']['deploy_to'] = "/home/#{node['errbit']['user']}/#{node['errbit']['name']}" default['errbit']['repo_url'] = "git://github.com/errbit/errbit.git" default['errbit']['revision'] = "master" -default['errbit']['environment'] = "production" - -# errbit config.yml -default['errbit']['config']['host'] = "errbit.example.com" -default['errbit']['config']['enforce_ssl'] = false -default['errbit']['config']['email_from'] = "errbit@example.com" -default['errbit']['config']['per_app_email_at_notices'] = false -default['errbit']['config']['email_at_notices'] = [1, 10, 100] -default['errbit']['config']['confirm_resolve_err'] = true -default['errbit']['config']['user_has_username'] = false -default['errbit']['config']['allow_comments_with_issue_tracker'] = true -default['errbit']['config']['use_gravatar'] = true -default['errbit']['config']['gravatar_default'] = "identicon" -# errbit github integration -default['errbit']['config']['github_authentication'] = false -default['errbit']['config']['github_client_id'] = "github_client_id" -default['errbit']['config']['github_secret'] = "github_secret" -default['errbit']['config']['github_access_scope'] = ['repo'] +# Local ruby to install via rbenv +default['errbit']['install_ruby'] = '2.2.4' -# mongodb creds -default['errbit']['db']['host'] = "localhost" -default['errbit']['db']['port'] = "27017" -default['errbit']['db']['database'] = "errbit" -default['errbit']['db']['username'] = "" -default['errbit']['db']['password'] = "" - -# app server (Optional: More info in README) -default['errbit']['server'] = "unicorn" # or use others like puma +# dotenv file variables +# +## Errbit +default['errbit']['config']['errbit_host'] = 'errbit.example.com' # Don't default to FQDN, it might fail e-mail validation. +default['errbit']['config']['errbit_protocol'] = 'http' +default['errbit']['config']['errbit_port'] = 80 +default['errbit']['config']['errbit_enforce_ssl'] = false +default['errbit']['config']['errbit_confirm_err_actions'] = true +default['errbit']['config']['errbit_user_has_username'] = true +default['errbit']['config']['errbit_use_gravatar'] = true +default['errbit']['config']['errbit_gravatar_default'] = 'identicon' +default['errbit']['config']['errbit_email_from'] = 'errbit@example.com' +default['errbit']['config']['errbit_email_at_notices'] = [1, 10, 100] +default['errbit']['config']['errbit_per_app_email_at_notices'] = false +default['errbit']['config']['errbit_notify_at_notices'] = [0] +default['errbit']['config']['errbit_per_app_notify_at_notices'] = false +# +## GitHub +default['errbit']['config']['github_url'] = 'https://github.com' +default['errbit']['config']['github_authentication'] = false +default['errbit']['config']['github_client_id'] = nil +default['errbit']['config']['github_secret'] = nil +default['errbit']['config']['github_org_id'] = nil +default['errbit']['config']['github_access_scope'] = ['repo'] +# +## SMTP +default['errbit']['config']['smtp_server'] = nil +default['errbit']['config']['smtp_port'] = nil +default['errbit']['config']['smtp_authentication'] = nil +default['errbit']['config']['smtp_username'] = nil +default['errbit']['config']['smtp_password'] = nil +default['errbit']['config']['smtp_domain'] = nil +# +## Sendmail +default['errbit']['config']['sendmail_location'] = nil +default['errbit']['config']['sendmail_arguments'] = nil +# +## Misc +default['errbit']['config']['devise_modules'] = %w( database_authenticatable recoverable rememberable trackable validatable omniauthable ) +default['errbit']['config']['email_delivery_method'] = 'smtp' +default['errbit']['config']['mongo_url'] = 'mongodb://localhost/errbit' +default['errbit']['config']['rails_env'] = 'production' +default['errbit']['config']['secret_key_base'] = nil +default['errbit']['config']['serve_static_assets'] = true -default['errbit']['secret_token'] = 'b9e131c733a2672c79af5699f26e0bc5fba23a40ec51d76c9271c00097f35aa4c0993e1150f08048f0b66bd141cbcb58ab28814e35eb281c3cb2374aac160203' +# The cookbook currently only supports Puma on systemd and Unicorn on SysVinit. +default['errbit']['server']['name'] = node['init_package'] == 'systemd' ? 'puma' : 'unicorn' +default['errbit']['server']['backlog'] = 100 +default['errbit']['server']['port'] = node['errbit']['config']['errbit_port'] +default['errbit']['server']['preload_app'] = false +default['errbit']['server']['tcp_nodelay'] = true +default['errbit']['server']['tcp_nopush'] = true +default['errbit']['server']['timeout'] = 60 +default['errbit']['server']['workers'] = 2 diff --git a/chefignore b/chefignore index a6de142..07d2d20 100644 --- a/chefignore +++ b/chefignore @@ -94,3 +94,8 @@ Vagrantfile # Travis # ########## .travis.yml + +# Kitchen # +########### +.kitchen/* +.kitchen.yml diff --git a/metadata.json b/metadata.json deleted file mode 100644 index 36c927d..0000000 --- a/metadata.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "errbit", - "description": "Installs/Configures errbit", - "long_description": "# Description\n\nThis cookbook is designed to be able to run [Errbit](http://github.com/errbit/errbit).\nIts github is at [chef-errbit](https://github.com/millisami/chef-errbit)\n\n# Requirements\n\nDeveloped using chef 10.14.0 and it should work with higher versions. But not tested against chef 11 family.\n\nThe following Opscode cookbooks are dependencies:\n\n* mongodb\n* git\n* unicorn\n* apt\n* nginx\n\n# Usage\n\nJust to install the Errbit app, include the following in your wrapper cookbook's recipe\n\n include_recipe \"errbit\"\n\nOr include it in your run_list\n\n 'recpie[errbit]'\n\n\nLicense and Author\n==================\n\nAuthor:: [Sachin Sagar Rai](http://nepalonrails.com) millisami@gmail.com\n\nCopyright 2013\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n", - "maintainer": "Sachin Sagar Rai", - "maintainer_email": "millisami@gmail.com", - "license": "All rights reserved", - "platforms": { - "ubuntu": ">= 0.0.0" - }, - "dependencies": { - "mongodb": ">= 0.0.0", - "git": ">= 0.0.0", - "unicorn": ">= 0.0.0", - "apt": ">= 0.0.0", - "nginx": ">= 0.0.0", - "build-essential": ">= 0.0.0" - }, - "recommendations": { - }, - "suggestions": { - }, - "conflicting": { - }, - "providing": { - }, - "replacing": { - }, - "attributes": { - }, - "groupings": { - }, - "recipes": { - }, - "version": "0.4.0" -} \ No newline at end of file diff --git a/metadata.rb b/metadata.rb index e5b65b8..cba74ce 100644 --- a/metadata.rb +++ b/metadata.rb @@ -1,16 +1,19 @@ name "errbit" maintainer "Sachin Sagar Rai" maintainer_email "millisami@gmail.com" -license "All rights reserved" +license "Apache 2.0" description "Installs/Configures errbit" long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) version "0.4.0" -depends "mongodb" depends "git" -depends "unicorn" +depends "ruby_rbenv", ">= 2.0" depends "apt" -depends "nginx" +depends "chef_nginx" depends "build-essential" +depends "selinux_policy" +recommends "mongodb" + +supports "centos" supports "ubuntu" diff --git a/recipes/bootstrap.rb b/recipes/bootstrap.rb index 55de2bc..9625806 100644 --- a/recipes/bootstrap.rb +++ b/recipes/bootstrap.rb @@ -1,14 +1,40 @@ +# +# Author:: Sachin Sagar Rai +# Cookbook Name:: errbit +# Recipe:: setup +# +# Copyright (C) 2013 Millisami +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +home_dir = "/home/#{node['errbit']['user']}" +current_dir = "#{node['errbit']['deploy_to']}/current" -Chef::Log.info "-" * 70 -Chef::Log.info "Checking to bootstrap the admin user" -execute "check whether to bootstrap admin user" do - command "bundle exec rake db:seed -t" - cwd "#{node['errbit']['deploy_to']}/current" - environment ({'RAILS_ENV' => 'production'}) - # not_if "bundle exec rails runner 'p User.where(admin: true).first'" - notifies :create, "ruby_block[remove_bootstrap]", :immediately - # notifies :restart, "service[unicorn_#{app['id']}]" +rbenv_script 'rake db:seed' do + code 'bundle exec rake db:seed RAILS_ENV=' + node['errbit']['config']['rails_env'] + cwd current_dir + user node['errbit']['user'] + rbenv_version node['errbit']['install_ruby'] + + not_if "#{home_dir}/.rbenv/bin/rbenv exec bundle exec rails runner 'exit User.where(:admin => true).exists?'", { + :cwd => current_dir, + :user => node['errbit']['user'], + :group => node['errbit']['group'], + :environment => { 'HOME' => home_dir } + } + + notifies :run, "ruby_block[remove_bootstrap]", :immediately end ruby_block "remove_bootstrap" do diff --git a/recipes/default.rb b/recipes/default.rb index 53a8fd5..4afd112 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -19,6 +19,10 @@ # include_recipe 'errbit::setup' -server = node['errbit']['server'] +include_recipe 'errbit::' + node['errbit']['server']['name'] -include_recipe "errbit::#{server}" +# Hack to work around systemd creating the socket with the wrong +# context and selinux_policy cookbook only running restorecon once. +execute "restorecon -R #{node['errbit']['deploy_to']}" do + only_if 'which restorecon' +end diff --git a/recipes/install_ruby.rb b/recipes/install_ruby.rb deleted file mode 100644 index cfb7575..0000000 --- a/recipes/install_ruby.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Helper recipe to install ruby while cooking -# This is added in the run_list in the Vagrantfile while cooking this cookbook - -package "python-software-properties" - -execute "apt-add-repository -y ppa:brightbox/ruby-ng" do - not_if "test -t /etc/apt/sources.list.d/brightbox-ruby-ng-#{node['lsb']['codename']}.list" -end - -execute "remove tmp list" do - command "cd /etc/apt/sources.list.d && rm ruby-packaged-source.list && rm ruby-packaged-source.list.save" - only_if "test -t /etc/apt/sources.list.d/ruby-packaged-source.list" -end - -execute "apt-get -y update" do - not_if "test -f /usr/bin/ruby" -end - -execute "apt-get -y install ruby1.9.3" do - not_if "test -f /usr/bin/ruby" -end diff --git a/recipes/puma.rb b/recipes/puma.rb index f128c55..c2ac7a7 100644 --- a/recipes/puma.rb +++ b/recipes/puma.rb @@ -1,15 +1,50 @@ # +# Author:: Sachin Sagar Rai # Cookbook Name:: errbit -# Recipe:: default +# Recipe:: puma # # Copyright (C) 2013 Millisami # -# All rights reserved - Do Not Redistribute +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +template "#{node['errbit']['deploy_to']}/shared/config/puma.rb" do + source 'puma.rb.erb' + owner node['errbit']['user'] + group node['errbit']['group'] + mode 0644 +end + +['socket', 'service'].each do |unit| + template "/lib/systemd/system/puma-#{node['errbit']['name']}.#{unit}" do + source "puma.#{unit}.erb" + owner 'root' + group 'root' + mode 0644 + end +end + +service "puma-#{node['errbit']['name']}" do + supports [:reload, :restart, :status] + action :nothing + + subscribes :reload, "template[/lib/systemd/system/puma-#{node['errbit']['name']}.service]" + subscribes :reload, "template[#{node['errbit']['deploy_to']}/shared/config/puma.rb]" + subscribes :reload, "file[#{node['errbit']['deploy_to']}/shared/config/env]" + subscribes :reload, "deploy_revision[#{node['errbit']['deploy_to']}]" +end -execute "puma app server" do - cwd "#{node['errbit']['deploy_to']}/current" - command "bundle exec puma -D" - # creates "/tmp/something" - action :run +service "puma-#{node['errbit']['name']}.socket" do + supports [:restart, :status] + action [:enable, :start] end diff --git a/recipes/setup.rb b/recipes/setup.rb index 86d74fb..1ea4e56 100644 --- a/recipes/setup.rb +++ b/recipes/setup.rb @@ -18,64 +18,61 @@ # limitations under the License. # -include_recipe "mongodb::10gen_repo" - -node.set['build_essential']['compiletime'] = true include_recipe "build-essential" - include_recipe "git" -gem_package "bundler" +include_recipe "chef_nginx" + +extend SELinuxPolicy::Helpers +include_recipe 'selinux_policy::install' if use_selinux + +home_dir = "/home/#{node['errbit']['user']}" +rails_env = node['errbit']['config']['rails_env'] group node['errbit']['group'] + user node['errbit']['user'] do action :create - comment "Deployer user" + comment "Errbit user" gid node['errbit']['group'] shell "/bin/bash" - home "/home/#{node['errbit']['user']}" - password node['errbit']['password'] + home home_dir supports :manage_home => true - system true -end - -# Exporting the SECRET_TOKEN env var -secret_token = rand(8**256).to_s(36).ljust(8,'a')[0..150] -# execute "set SECRET_TOKEN var" do -# command "echo 'export SECRET_TOKEN=#{secret_token}' >> ~/.bash_profile" -# not_if "grep SECRET_TOKEN ~/.bash_profile" -# end -file "/etc/profile.d/errbit_env.sh" do - mode "0644" - action :create_if_missing - content "export SECRET_TOKEN=#{secret_token}\nexport RAILS_ENV=production\nexport RACK_ENV=production\n" -end - -# execute "set RAILS_ENV var" do -# command "echo 'export RAILS_ENV=production' >> ~/.bash_profile" -# not_if "grep RAILS_ENV ~/.bash_profile" -# end - -# execute "set RACK_ENV var" do -# command "echo 'export RACK_ENV=production' >> ~/.bash_profile" -# not_if "grep RACK_ENV ~/.bash_profile" -# end - -execute "update sources list" do - command "apt-get update" - action :nothing -end.run_action(:run) - -%w(libxml2-dev libxslt1-dev libcurl4-gnutls-dev).each do |pkg| - r = package pkg do - action :nothing - end - r.run_action(:install) + system false +end + +# Ensure nginx can read within this directory +directory home_dir do + mode 0701 +end + +rbenv_user_install node['errbit']['user'] + +rbenv_plugin 'ruby-build' do + git_url 'https://github.com/rbenv/ruby-build.git' + user node['errbit']['user'] +end + +# Install appropriate Ruby with rbenv +rbenv_ruby node['errbit']['install_ruby'] do + action :install + user node['errbit']['user'] +end + +# Set as the rbenv default ruby +rbenv_global node['errbit']['install_ruby'] do + user node['errbit']['user'] +end + +# Install required Ruby Gems(via rbenv) +rbenv_gem "bundler" do + action :install + user node['errbit']['user'] + rbenv_version node['errbit']['install_ruby'] end directory node['errbit']['deploy_to'] do owner node['errbit']['user'] group node['errbit']['group'] - mode 00755 action :create recursive true end @@ -86,7 +83,7 @@ mode 00755 end -%w{ log pids system tmp vendor_bundle scripts config sockets }.each do |dir| +%w( config log pids sockets ).each do |dir| directory "#{node['errbit']['deploy_to']}/shared/#{dir}" do owner node['errbit']['user'] group node['errbit']['group'] @@ -95,85 +92,96 @@ end end -# errbit config.yml -template "#{node['errbit']['deploy_to']}/shared/config/config.yml" do - source "config.yml.erb" - owner node['errbit']['user'] - group node['errbit']['group'] - mode 00644 - variables(params: { - host: node['errbit']['config']['host'], - enforce_ssl: node['errbit']['config']['enforce_ssl'], - email_from: node['errbit']['config']['email_from'], - per_app_email_at_notices: node['errbit']['config']['per_app_email_at_notices'], - email_at_notices: node['errbit']['config']['email_at_notices'], - confirm_resolve_err: node['errbit']['config']['confirm_resolve_err'], - user_has_username: node['errbit']['config']['user_has_username'], - allow_comments_with_issue_tracker: node['errbit']['config']['allow_comments_with_issue_tracker'], - use_gravatar: node['errbit']['config']['use_gravatar'], - gravatar_default: node['errbit']['config']['gravatar_default'], - github_authentication: node['errbit']['config']['github_authentication'], - github_client_id: node['errbit']['config']['github_client_id'], - github_secret: node['errbit']['config']['github_secret'], - github_access_scope: node['errbit']['config']['github_access_scope'] - }) -end - -template "#{node['errbit']['deploy_to']}/shared/config/mongoid.yml" do - source "mongoid.yml.erb" +require 'securerandom' +node.normal_unless['errbit']['config']['secret_key_base'] = SecureRandom.urlsafe_base64(96) + +file "#{node['errbit']['deploy_to']}/shared/config/env" do + content node['errbit']['config'].map { |key, value| + case value + when nil + when Array + "export #{key.upcase}=\"[#{value.join ','}]\"" + else + "export #{key.upcase}=#{value.inspect}" + end + }.compact.join("\n") + "\n" + owner node['errbit']['user'] group node['errbit']['group'] - mode 00644 - variables( params: { - environment: node['errbit']['environment'], - host: node['errbit']['db']['host'], - port: node['errbit']['db']['port'], - database: node['errbit']['db']['database'] - # username: node['errbit']['db']['username'], - # password: node['errbit']['db']['password'] - }) + mode 0644 end deploy_revision node['errbit']['deploy_to'] do repo node['errbit']['repo_url'] revision node['errbit']['revision'] + shallow_clone true + user node['errbit']['user'] group node['errbit']['group'] - enable_submodules false - migrate false + + environment( + 'HOME' => home_dir, + 'RAILS_ENV' => rails_env + ) + + migration_command "#{home_dir}/.rbenv/bin/rbenv exec bundle exec rake db:migrate" + migrate true + + symlink_before_migrate('config/env' => '.env') + symlinks('log' => 'log', 'pids' => 'tmp/pids', 'sockets' => 'tmp/sockets') + before_migrate do - link "#{release_path}/vendor/bundle" do - to "#{node['errbit']['deploy_to']}/shared/vendor_bundle" + template "#{release_path}/UserGemfile" do + source "UserGemfile.erb" + owner node['errbit']['user'] + group node['errbit']['group'] + mode 0644 end - common_groups = %w{development test cucumber staging production} - execute "bundle install --deployment --without #{(common_groups - ([node['errbit']['environment']])).join(' ')}" do - ignore_failure true + + common_groups = %w{development test production heroku} - [rails_env] + + rbenv_script 'bundle install' do + code "bundle install --system --without '#{common_groups.join ' '}'" cwd release_path + user node['errbit']['user'] + rbenv_version node['errbit']['install_ruby'] end - end - symlink_before_migrate nil - symlinks( - "config/config.yml" => "config/config.yml", - "config/mongoid.yml" => "config/mongoid.yml" - ) - environment 'RAILS_ENV' => node['errbit']['environment'], 'SECRET_TOKEN' => node['errbit']['secret_token'] - shallow_clone true - action :deploy #:deploy or :rollback or :force_deploy - - before_restart do + selinux_policy_fcontext "#{release_path}/(app/assets|public)(/.*)?" do + secontext 'httpd_sys_content_t' + end Chef::Log.info "*" * 20 + "COMPILING ASSETS" + "*" * 20 - execute "asset_precompile" do + + rbenv_script 'rake assets:precompile' do + code 'bundle exec rake assets:precompile RAILS_ENV=' + rails_env cwd release_path user node['errbit']['user'] - group node['errbit']['group'] - command "bundle exec rake assets:precompile --trace" - environment ({'RAILS_ENV' => node['errbit']['environment']}) + rbenv_version node['errbit']['install_ruby'] end end - # git_ssh_wrapper "wrap-ssh4git.sh" - scm_provider Chef::Provider::Git +end + +selinux_policy_fcontext "#{node['errbit']['deploy_to']}/current" do + secontext 'httpd_sys_content_t' +end + +selinux_policy_fcontext "#{node['errbit']['deploy_to']}/shared/sockets/[^/]*\.sock" do + secontext 'httpd_var_run_t' +end + +selinux_policy_module 'nginx-errbit-socket' do + content <<-EOF + module nginx-errbit-socket 0.1; + + require { + type httpd_t; + type init_t; + class unix_stream_socket connectto; + } + + allow httpd_t init_t:unix_stream_socket connectto; + EOF end template "#{node['nginx']['dir']}/sites-available/#{node['errbit']['name']}" do @@ -187,4 +195,3 @@ nginx_site node['errbit']['name'] do enable true end - diff --git a/recipes/unicorn.rb b/recipes/unicorn.rb index 0e0562e..638db5e 100644 --- a/recipes/unicorn.rb +++ b/recipes/unicorn.rb @@ -18,46 +18,35 @@ # limitations under the License. # -include_recipe 'unicorn' - -node.default[:unicorn][:worker_timeout] = 60 -node.default[:unicorn][:worker_processes] = 2 #[node[:cpu][:total].to_i * 4, 8].min -node.default[:unicorn][:preload_app] = false -node.default[:unicorn][:tcp_nodelay] = true -node.default[:unicorn][:backlog] = 100 -node.default[:unicorn][:tcp_nopush] = true -node.default[:unicorn][:tries] = 3 -# node.default[:unicorn][:delay] = 100 - Chef::Log.info "-" * 70 Chef::Log.info "Unicorn Config" -template "#{node['errbit']['deploy_to']}/shared/config/unicorn.conf" do - source "unicorn.conf.erb" +template "#{node['errbit']['deploy_to']}/shared/config/unicorn.rb" do + source "unicorn.rb.erb" owner node['errbit']['user'] group node['errbit']['group'] mode 00644 end template "/etc/init.d/unicorn_#{node['errbit']['name']}" do - source "unicorn.service.erb" + source "unicorn.init.erb" owner "root" group "root" mode 00755 + variables( + :user => node['errbit']['user'], + :deploy_to => node['errbit']['deploy_to'], + :env => node['errbit']['config']['rails_env'] + ) end service "unicorn_#{node['errbit']['name']}" do provider Chef::Provider::Service::Init::Debian - start_command "/etc/init.d/unicorn_#{node['errbit']['name']} start" - stop_command "/etc/init.d/unicorn_#{node['errbit']['name']} stop" - restart_command "/etc/init.d/unicorn_#{node['errbit']['name']} restart" - status_command "/etc/init.d/unicorn_#{node['errbit']['name']} status" - supports :start => true, :stop => true, :restart => true, :status => true - action :nothing -end + supports [:restart, :status] + action :enable - -# Restarting the unicorn -service "unicorn_#{node['errbit']['name']}" do - action :restart + subscribes :restart, "template[/etc/init.d/unicorn_#{node['errbit']['name']}]" + subscribes :restart, "template[#{node['errbit']['deploy_to']}/shared/config/env]" + subscribes :restart, "template[#{node['errbit']['deploy_to']}/shared/config/unicorn.rb]" + subscribes :restart, "deploy_revision[#{node['errbit']['deploy_to']}]" end diff --git a/templates/default/UserGemfile.erb b/templates/default/UserGemfile.erb new file mode 100644 index 0000000..1feefaf --- /dev/null +++ b/templates/default/UserGemfile.erb @@ -0,0 +1 @@ +gem "<%= node['errbit']['server']['name'] %>" diff --git a/templates/default/config.yml.erb b/templates/default/config.yml.erb deleted file mode 100644 index dddb947..0000000 --- a/templates/default/config.yml.erb +++ /dev/null @@ -1,83 +0,0 @@ -# Generated by chef: <%= node['fqdn'] %> -# Errbit Config -# ============= -# - -# The host of your errbit server -host: <%= @params[:host] %> - -# Enforce SSL connections -enforce_ssl: <%= @params[:enforce_ssl] %> - -# The email address which email notifications -# will be sent from. -email_from: <%= @params[:email_from] %> - -# If you turn on this option, email_at_notices can be -# configured on a per app basis, at the App edit page -per_app_email_at_notices: <%= @params[:per_app_email_at_notices] %> - -# Configure when emails are sent for an error. -# [1,3,7] = 1st, 3rd, and 7th occurence triggers -# an email notification. -email_at_notices: <%= @params[:email_at_notices] %> - -# Configure whether or not the user should be prompted before resolving an error. -confirm_resolve_err: <%= @params[:confirm_resolve_err] %> - -# Add an optional 'username' field to Users. -# Helpful when you need to plug in a custom authentication strategy, such as LDAP. -user_has_username: <%= @params[:user_has_username] %> - -# Allow comments while an issue tracker is configured. -# This is useful if the err is not critical enough to create a ticket, -# but you want to leave a short comment. -allow_comments_with_issue_tracker: <%= @params[:allow_comments_with_issue_tracker] %> - -# Enable Gravatar. -use_gravatar: <%= @params[:use_gravatar] %> -# Default Gravatar image, can be: mm, identicon, monsterid, wavatar, retro. -gravatar_default: <%= @params[:gravatar_default] %> - -# Setup your deploy options for capistrano. -deployment: - hosts: - web: errbit.example.com - app: errbit.example.com - db: errbit.example.com - repository: http://github.com/errbit/errbit.git - user: deploy - deploy_to: /var/www/apps/errbit - # setup path to unicorn pids folder (or deploy_to/shared/pids will be used) - # pids: /var/www/apps/errbit/shared/pids - -# GitHub OAuth configuration -# If you want to allow authentication via GitHub, you will need to register -# your app at: https://github.com/settings/applications -# If you hosted Errbit at errbit.example.com, you would fill in: -# -# URL: http://errbit.example.com/ -# Callback URL: http://errbit.example.com/users/auth/github -# -# After you have registered your app, copy your Client ID and Secret key below. -github_authentication: <%= @params[:github_authentication] %> -github_client_id: <%= @params[:github_client_id] %> -github_secret: <%= @params[:github_secret] %> -# GitHub Permissions to request from user -# ['repo'] - Allow creating issues for public and private repos. -# ['public_repo'] - Only allow creating issues for public repos. -# [] - No permission to create issues on any repos. -github_access_scope: <%= @params[:github_access_scope] %> - -# Configure SMTP settings. If you are running Errbit on Heroku, -# sendgrid will be configured by default. -# ------------------------------------------------------------------------ -#smtp_settings: -# :address: ADDRESS -# :domain: DOMAIN -# :port: "25" -# :authentication: :plain, :login, :cram_md5 -# :enable_starttls_auto: true -# :user_name: USERNAME -# :password: PASSWORD - diff --git a/templates/default/mongoid.yml.erb b/templates/default/mongoid.yml.erb deleted file mode 100644 index 28b661d..0000000 --- a/templates/default/mongoid.yml.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= @params[:environment] %>: - host: localhost - database: <%= @params[:database] %> diff --git a/templates/default/nginx.conf.erb b/templates/default/nginx.conf.erb index 65c6196..6820fc9 100644 --- a/templates/default/nginx.conf.erb +++ b/templates/default/nginx.conf.erb @@ -1,28 +1,21 @@ - upstream <%= node['errbit']['name'] %> { - server unix:<%= node['errbit']['deploy_to'] %>/shared/sockets/unicorn.sock fail_timeout=0; + server unix:<%= node['errbit']['deploy_to'] %>/shared/sockets/<%= node['errbit']['server']['name'] %>.sock fail_timeout=0; } -# Rewrite www to non-www -# server{ -# server_name www.railsapp1.com; -# return 301 $scheme://domain.com$request_uri; -# } - server { - listen 80 deferred; - <% if @server_names %> - server_name <%= @server_names.join(" ") %>; - <% else %> - server_name <%= node['ipaddress'] %> - <% end %> + listen <%= node['errbit']['server']['port'] %> deferred; + server_name <%= node['errbit']['config']['errbit_host'] %>; + client_max_body_size 4G; keepalive_timeout 5; + tcp_nodelay <%= node['errbit']['server']['tcp_nodelay'] ? 'on' : 'off' %>; + tcp_nopush <%= node['errbit']['server']['tcp_nopush'] ? 'on' : 'off' %>; + root <%= node['errbit']['deploy_to'] %>/current/public; - access_log <%= node['errbit']['deploy_to'] %>/shared/log/access.log combined; - error_log <%= node['errbit']['deploy_to'] %>/shared/log/error.log; + access_log <%= node['nginx']['log_dir'] %>/<%= node['errbit']['name'] %>.access.log combined; + error_log <%= node['nginx']['log_dir'] %>/<%= node['errbit']['name'] %>.error.log; location ^~ /assets/ { gzip_static on; @@ -30,23 +23,9 @@ server { add_header Cache-Control public; } -# location / { -# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -# proxy_set_header Host $http_host; -# proxy_redirect off; -# -# if (-f $request_filename) { -# break; -# } -# -# if (!-f $request_filename) { -# proxy_pass http://<%= node['errbit']['name'] %>; -# break; -# } -# } + try_files $uri/index.html $uri @<%= node['errbit']['name'] %>; - try_files $uri/index.html $uri @unicorn; - location @unicorn { + location @<%= node['errbit']['name'] %> { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; diff --git a/templates/default/puma.rb.erb b/templates/default/puma.rb.erb new file mode 100644 index 0000000..79ecd49 --- /dev/null +++ b/templates/default/puma.rb.erb @@ -0,0 +1,20 @@ +tag "<%= node['errbit']['name'] %>" + +# Is Errbit thread-safe? Probably but we default to MRI so threading +# is of little benefit. This should be made configurable later. +workers <%= node['errbit']['server']['workers'] %> +threads 1, 1 + +# Ensure we're always in the "current" directory. +directory "<%= node['errbit']['deploy_to']%>/current" + +# Socket might be created by systemd. +bind "unix://<%= node['errbit']['deploy_to']%>/shared/sockets/puma.sock<%= '?backlog=' + node['errbit']['server']['backlog'] unless node['init_package'] == 'systemd' %>" + +state_path "<%= node['errbit']['deploy_to']%>/shared/pids/puma.state" +stdout_redirect "<%= node['errbit']['deploy_to']%>/shared/log/server.stdout.log", "<%= node['errbit']['deploy_to']%>/shared/log/server.stderr.log", true + +environment "<%= node['errbit']['config']['rails_env'] %>" +<%=- "preload_app!" if node['errbit']['server']['preload_app'] -%> +prune_bundler +worker_timeout <%= node['errbit']['server']['timeout'] %> diff --git a/templates/default/puma.service.erb b/templates/default/puma.service.erb new file mode 100644 index 0000000..5a53cae --- /dev/null +++ b/templates/default/puma.service.erb @@ -0,0 +1,14 @@ +[Unit] +Description=<%= node['errbit']['name'] %> Puma daemon +Requires=puma-errbit.socket + +[Service] +User=<%= node['errbit']['user'] %> +Group=<%= node['errbit']['group'] %> +SyslogIdentifier=puma-<%= node['errbit']['name'] %> +WorkingDirectory=<%= node['errbit']['deploy_to'] %>/current +ExecStart=/home/<%= node['errbit']['user'] %>/.rbenv/bin/rbenv exec puma --config <%= node['errbit']['deploy_to'] %>/shared/config/puma.rb +ExecReload=/bin/kill -USR2 $MAINPID + +[Install] +WantedBy=multi-user.target diff --git a/templates/default/puma.socket.erb b/templates/default/puma.socket.erb new file mode 100644 index 0000000..fb0122a --- /dev/null +++ b/templates/default/puma.socket.erb @@ -0,0 +1,12 @@ +[Unit] +Description=<%= node['errbit']['name'] %> Puma socket + +[Socket] +SocketUser=<%= node['errbit']['user'] %> +SocketGroup=<%= node['nginx']['group'] %> +SocketMode=0660 +Backlog=<%= node['errbit']['server']['backlog'] %> +ListenStream=<%= node['errbit']['deploy_to'] %>/shared/sockets/puma.sock + +[Install] +WantedBy=sockets.target diff --git a/templates/default/unicorn.init.erb b/templates/default/unicorn.init.erb new file mode 100644 index 0000000..1b62eb1 --- /dev/null +++ b/templates/default/unicorn.init.erb @@ -0,0 +1,126 @@ +#! /bin/bash + +### BEGIN INIT INFO +# Provides: errbit +# Required-Start: $local_fs $remote_fs $network $syslog +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Errbit +# Description: Errbit open source error catcher +### END INIT INFO + + +APP_ROOT="<%= @deploy_to %>" +APP_USER="<%= @user %>" +DAEMON_OPTS="-D -c $APP_ROOT/shared/config/unicorn.rb" +PID_PATH="$APP_ROOT/shared/tmp/pids" +SOCKET_PATH="$APP_ROOT/shared/tmp/sockets" +WEB_SERVER_PID="$APP_ROOT/shared/pids/unicorn.pid" + +NAME="errbit" +DESC="Errbit service" + +check_pid(){ + if [ -f $WEB_SERVER_PID ]; then + PID=`cat $WEB_SERVER_PID` + STATUS=`ps aux | grep $PID | grep -v grep | wc -l` + else + STATUS=0 + PID=0 + fi +} + +execute() { + sudo -u $APP_USER -H bash -l -c "cd \"$APP_ROOT/current\" ; $1" +} + +start() { + cd $APP_ROOT + check_pid + if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then + # Program is running, exit with error code 1. + echo "Error! $DESC $NAME is currently running!" + exit 1 + else + if [ `whoami` = root ]; then + execute "rm -f $SOCKET_PATH/errbit.socket" + execute "RAILS_ENV=<%= @env %> bundle exec unicorn $DAEMON_OPTS" + echo "$DESC started" + fi + fi +} + +stop() { + cd $APP_ROOT + check_pid + if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then + ## Program is running, stop it. + kill -QUIT `cat $WEB_SERVER_PID` + rm "$WEB_SERVER_PID" >> /dev/null + echo "$DESC stopped" + else + ## Program is not running, exit with error. + echo "Error! $DESC not started!" + exit 1 + fi +} + +restart() { + cd $APP_ROOT + check_pid + if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then + echo "Restarting $DESC..." + stop + sleep 2 + start + echo "$DESC restarted." + else + echo "$NAME not running" + start + fi +} + +status() { + cd $APP_ROOT + check_pid + if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then + echo "$DESC / Unicorn with PID $PID is running." + else + echo "$DESC is not running." + exit 1 + fi +} + +## Check to see if we are running as root first. +## Found at http://www.cyberciti.biz/tips/shell-root-user-check-script.html +if [ "$(id -u)" != "0" ]; then + echo "This script must be run as root" + exit 1 +fi + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + reload|force-reload) + echo -n "Reloading $NAME configuration: " + kill -HUP `cat $PID` + echo "done." + ;; + status) + status + ;; + *) + echo "Usage: sudo service $NAME {start|stop|restart|reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/templates/default/unicorn.conf.erb b/templates/default/unicorn.rb.erb similarity index 91% rename from templates/default/unicorn.conf.erb rename to templates/default/unicorn.rb.erb index 8c13964..1750b87 100644 --- a/templates/default/unicorn.conf.erb +++ b/templates/default/unicorn.rb.erb @@ -1,4 +1,4 @@ -worker_processes <%= node[:unicorn][:worker_processes] %> +worker_processes <%= node['errbit']['server']['workers'] %> user "<%= node['errbit']['user'] %>" @@ -7,10 +7,9 @@ working_directory "<%= node['errbit']['deploy_to']%>/current" # listen on both a Unix domain socket and a TCP port, # use a shorter backlog for quicker failover when busy -listen "<%= node['errbit']['deploy_to']%>/shared/sockets/unicorn.sock", :backlog => <%= node[:unicorn][:backlog] %> -listen 8080, :tcp_nopush => true +listen "<%= node['errbit']['deploy_to']%>/shared/sockets/unicorn.sock", :backlog => <%= node['errbit']['server']['backlog'] %> -timeout <%= node[:unicorn][:worker_timeout] %> +timeout <%= node['errbit']['server']['timeout'] %> pid "<%= node['errbit']['deploy_to']%>/shared/pids/unicorn.pid" @@ -19,7 +18,7 @@ stdout_path "<%= node['errbit']['deploy_to']%>/shared/log/unicorn.stdout.log" # combine REE with "preload_app true" for memory savings # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow -preload_app <%= node[:unicorn][:preload_app] %> +preload_app <%= node['errbit']['server']['preload_app'] %> GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=) # ensure Unicorn doesn't use a stale Gemfile when restarting diff --git a/templates/default/unicorn.service.erb b/templates/default/unicorn.service.erb deleted file mode 100644 index eaad0d0..0000000 --- a/templates/default/unicorn.service.erb +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env ruby - -require 'digest/md5' - -ROOT_PATH="<%= node['errbit']['deploy_to'] %>" -APP_NAME="<%= node['errbit']['name'] %>" -PID_PATH="<%= node['errbit']['deploy_to'] %>/shared/pids/unicorn.pid" - -def run_and_print_command(command) - puts command - system(command) || exit(1) -end - -def run_and_ignore_exitcode_and_print_command(command) - puts command - system(command) -end - -def unicorn_running? - if File.exists?(PID_PATH) && (pid = File.read(PID_PATH).chomp) && system("ps aux | grep #{pid} | grep -v grep > /dev/null") - pid - else - false - end -end - -def different_gemfile? - if File.exists?("#{ROOT_PATH}/current/Gemfile") - dir = Dir["#{ROOT_PATH}/releases/*"] - previous_release_path = dir.sort[dir.size-2] - if !previous_release_path.nil? && File.exists?("#{previous_release_path}/Gemfile") - return Digest::MD5.hexdigest(File.read(ROOT_PATH + "/current/Gemfile")) != Digest::MD5.hexdigest(File.read("#{previous_release_path}/Gemfile")) - end - end - false -end - -def start_unicorn - puts "Gemfile detected - running Unicorn with bundle exec" - run_and_ignore_exitcode_and_print_command "cd #{ROOT_PATH}/current && bundle exec unicorn -E production -c #{ROOT_PATH}/shared/config/unicorn.conf -D" -end - -def stop_unicorn - if unicorn_running? - if run_and_ignore_exitcode_and_print_command "kill -QUIT `cat #{PID_PATH}`" - `rm #{PID_PATH}` - end - else - puts "You can't stop unicorn, because it's not running" - end -end - -def restart_unicorn - if unicorn_running? - run_and_ignore_exitcode_and_print_command "kill -USR2 `cat #{PID_PATH}`" - else - start_unicorn - end -end - -def clean_restart - if different_gemfile? - puts "Found a previous version with a different Gemfile: Doing a stop & start" - stop_unicorn if unicorn_running? - start_unicorn - else - puts "No previous version with a different Gemfile found. Assuming a quick restart without re-loading gems is save" - restart_unicorn - end -end - -def status_unicorn - if pid = unicorn_running? - puts "Unicorn #{APP_NAME} running with PID #{pid}" - return true - else - puts "Unicorn #{APP_NAME} not running" - return false - end -end - -case ARGV[0] -when "start" - puts "Starting Unicorn #{APP_NAME}" - start_unicorn -when "stop" - puts "Stopping Unicorn #{APP_NAME}" - stop_unicorn -when "status" - status_unicorn -when "restart" - restart_unicorn -when "clean-restart" - clean_restart -else - puts "Usage: {start|stop|status|restart|clean-restart}" - exit 1 -end - -exit 0