aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCaleb Land <caleb.land@gmail.com>2013-04-30 19:06:56 -0400
committerCaleb Land <caleb.land@gmail.com>2013-04-30 19:06:56 -0400
commit034e6d2eed9acf04456c1771b925191b3dcd2f24 (patch)
tree20bc460294c93fc16c4d1e6be7f116fda55eec05
parent3a0c7374d7998fe7d1bd8cdc3d90338748aa4d88 (diff)
downloadfreebsd-puma-034e6d2eed9acf04456c1771b925191b3dcd2f24.tar.gz
freebsd-puma-034e6d2eed9acf04456c1771b925191b3dcd2f24.tar.bz2
freebsd-puma-034e6d2eed9acf04456c1771b925191b3dcd2f24.zip
add preliminary documentation in the README, add the script
-rw-r--r--README.md184
-rw-r--r--unicorn303
2 files changed, 487 insertions, 0 deletions
diff --git a/README.md b/README.md
index aa36238..3cda65c 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,187 @@ freebsd-unicorn
===============
A robust init script for running unicorn on FreeBSD
+
+This rc script works with either RVM and RBENV installed in your deploy user's directory, or with a globally installed ruby. It correctly handle's Unicorn+Bundler [idiosyncrasies][unicorn-sandboxing].
+
+Simply place the `unicorn` script in your `/usr/local/etc/rc.d` directory, modify it if necessary, and configure your application via variables in `/etc/rc.conf`
+
+This has been tested on **FreeBSD 9.0 and 9.1**
+
+Making sure unicorn starts after your database launches
+-------------------------------------------------------
+
+The only thing you might need to configure in the rc script is to change the `REQUIRES` line to specify your database (I use PostreSQL so that's what's in the repo)
+
+For example, if you were using MySQL, you would change
+
+ # REQUIRE: LOGIN postgresql
+
+to
+
+ # REQUIRE: LOGIN mysql-server
+
+You might need to add other services to this list if your Rails application requires them.
+
+### Quick Setup
+
+To get up and running quickly, adjust the `REQUIRES` line like above, and add edit your `/etc/rc.conf`:
+
+For Capistrano or Capistrano-like directory layouts:
+
+ unicorn_enable="YES"
+
+ # this is the path to where your application is deployed in Capistrano,
+ # the parent directory of the `current` directory
+ unicorn_directory="/u/application"
+
+
+For Non-Capistrano-like layouts:
+
+ unicorn_enable="YES"
+ unicorn_command="/u/application/bin/unicorn_rails"
+ unicorn_command_args="/u/application/config.ru"
+ unicorn_pidfile="/u/application/tmp/pids/unicorn.pid"
+ unicorn_old_pidfile="/u/application/tmp/pids/unicorn.oldbin"
+ unicorn_listen="/u/application/tmp/sockets/unicorn.sock"
+ unicorn_config="/u/application/config/unicorn.rb"
+ unicorn_chdir="/u/application"
+ unicorn_user="deploy"
+
+ # Uncomment this if using a different RAILS_ENV/RACK_ENV than production
+ #unicorn_env="production"
+
+
+Edit `/etc/rc.conf` to meet your needs
+-------------------------------------------
+
+### Using a Capistrano directory layout
+
+The rc script does as much as possible to help you out. If you are using capistrano, or a capistrano-like directory structure, then you can just specify the directory of your application (the parent directory of `current`):
+
+ unicorn_enable="YES"
+ unicorn_directory="/u/application"
+
+This infers all sorts of information about your app (you can always run `/usr/local/etc/rc.d/unicorn show` to see what your configuration is. **Note** the variable names listed here are without the leading `unicorn_` prefix that you would need to specify in `/etc/rc.conf`):
+
+ #
+ # Unicorn Configuration for application
+ #
+
+ command: /u/application/current/bin/unicorn_rails
+ command_args: /u/application/current/config.ru
+ pidfile: /u/application/shared/pids/unicorn.pid
+ old_pidfile: /u/application/shared/pids/unicorn.pid.oldbin
+ listen:
+ config: /u/application/current/config/unicorn.conf.rb
+ init_config:
+ bundle_gemfile: /u/application/current/Gemfile
+ chdir: /u/application/current
+ user: deploy
+ nice:
+ env: production
+ flags: -E production -c /u/application/current/config/unicorn.conf.rb -D
+
+ start_command:
+
+ su -l deploy -c "export BUNDLE_GEMFILE=/u/application/current/Gemfile && cd /u/application && /u/application/current/bin/unicorn_rails -E production -c /u/application/current/config/unicorn.conf.rb -D /u/application/current/config.ru"
+
+Let's look at these settings one by one:
+
+`command`: By default, it uses the `current/bin/unicorn_rails` [bundler binstub][binstub] located in your project to ensure your gems are loaded. `command` comes from FreeBSD's `rc.subr` init system functions.
+
+`command_args`: This is the standard FreeBSD's `rc.subr` variable that holds the arguments to the above `command`
+
+`pidfile`: This is also part of FreeBSD's `rc.subr` system. This is where the built in functions will look for the pid of the process. By default, this rc script looks in the `shared/pids/unicorn.pid` file.
+
+`old_pidfile`: This is the pidfile used by unicorn to perform zero-downtime upgrades. [Procedure to replace a running unicorn executable][unicorn-0-downtime]. This rc script uses Unicorn's default convention of appending `.oldbin` to the end of the `pidfile`
+
+`listen`: This is the port or socket for Unicorn to listen on. This rc script assumes that you will specify it in your project's Unicorn config file (see the next variable).
+
+e.g.
+
+ listen "#{app}/shared/sockets/unicorn.sock", :backlog => 64
+
+`config`: This is the path to Unicorn's config file where unicorn will find it's settings. By default this rc script looks for a file called `current/conf/unicorn.conf.rb`
+
+`init_config`: This is a shell script file that is included in the environment before Unicorn is executed. In this file you can include `export VAR=value` statements to pass environment variables into your rails app. By default, this init script looks for a file called `current/.env` and uses that. If that file doesn't exist, this rc script will skip this functionality (as seen in the above example).
+
+This could be used in conjunction with [dotenv][dotenv] in development since dotenv accepts lines beginning with `export`
+
+
+`bundle_gemfile`: This is the path to the `Gemfile` of your project. This rc script sets the `BUNDLE_GEMFILE` environment variable to this value. By default it looks to `current/Gemfile`. This is required so that Unicorn uses the most current `Gemfile` (rather than the one in the specific deployment directory) when an upgrade is performed. See [Unicorn Sandboxing][unicorn-sandboxing]. This is a safeguard, you should really put this in your unicorn.conf.rb:
+
+ before_exec do |server|
+ ENV["BUNDLE_GEMFILE"] = "/path/to/app/current/Gemfile"
+ end
+
+`chdir`: This is the directory we `cd` into before running Unicorn. By default it's the currently deployed version of your application "`current/`"
+
+`user`: This is the user that Unicorn will be run as. By default we do like [Passenger][passenger] and use the owner of the `unicorn_directory`.
+
+`nice`: The `nice` level to run unicorn at. Usually you'll leave this alone.
+
+`flags`: This is a variable defined by FreeBSD's `/etc/rc.subr` init system, and contains the flags passed to the command (Unicorn in this case) when run. This variable is built up from the variables above, but you could manually specify `unicorn_flags` in your `/etc/rc.conf` to override them.
+
+`start_command`: Here you can see the full command that will be run when you start this service. It's a beaut' isn't it?
+
+You can override any of these parameter in your `/etc/rc.conf` by simply specifying the variables like you see below (you can pick and choose which to override).
+
+
+### Using a custom directory layout
+
+Using your own layout is easy, you can just leave the `unicorn_directory` variable out of your `/etc/rc.conf` and specify all of the above variables manually. Here's a list of those variables for your convenience:
+
+ unicorn_command: The path to the unicorn command
+ unicorn_command_args: The non-flag arguments passed to the above command. This is usually the path to the Rack config.ru file
+ unicorn_pidfile: The path where Unicorn will put its pid file
+ unicorn_old_pidfile: The path where Unicorn will put its `old` pid file (usually the same as the sbove with `.oldbin` appended)
+ unicorn_listen: The path to the socket or port to listen on. You can leave this blank to specify where to listen in the Unicorn config
+ unicorn_config: The path to the Unicorn config file
+ unicorn_chdir: The path where this script will `cd` to before starting unicorn
+ unicorn_user: The user to run Unicorn as
+ unicorn_nice: The `nice` level to run Unicorn as. Leave blank to run un-niced
+ unicorn_env: The RAILS_ENV (or RACK_ENV) to run your application as. (default: production)
+ unicorn_flags: The flags passed in to Unicorn when starting (not counting the unicorn_command_args specified above). Override this for complete control of how to start Unicorn.
+
+
+### Deploying multiple applications
+
+This is all find and dandy, but some of you might have multiple applications running on the same server (even if it's just a staging and production version of your app).
+
+This rc script can work with multiple applications. It works similarly to how postgresql's rc script works on FreeBSD.
+
+You simply specify your profiles in your `/etc/rc.conf` with the `unicorn_profiles` variable. This is a space separated list of application names.
+
+Then you can customize each application by specifying variables in this form:
+
+ unicorn_<application-name>_variable=VALUE
+
+Here's a simple example (I can leave the _env variable out of the production declaration since it's the default value)
+
+ unicorn_enable="YES"
+ unicorn_profiles="application_staging application_production"
+
+ unicorn_application_staging_enable="YES"
+ unicorn_application_staging_directory="/u/application_staging"
+ unicorn_application_staging_env="staging"
+
+ unicorn_application_production_enable="YES"
+ unicorn_application_production_directory="/u/application_production"
+
+You can use the simplified Capistrano `directory`-based configuration like above, or you can specify all of the variable's separately, for a fully custom setup
+
+### Customizing the script
+
+If you want to customize the default, calculated values you want to look in the `_setup_directory()` function. This is what is called when you specify that you want to use a Capistrano-like directory layout by specifying `unicorn_directory` or `unicorn_<application-name>_directory` in your `/etc/rc.conf`.
+
+If you use a different deployment strategy than Capistrano, you could adjust the default values to work with your system.
+
+
+
+
+passenger: https://www.phusionpassenger.com
+unicorn-sandboxing: http://unicorn.bogomips.org/Sandbox.html
+dotenv: https://github.com/bkeepers/dotenv
+unicorn-0-downtime: http://unicorn.bogomips.org/SIGNALS.html
+binstub: https://github.com/sstephenson/rbenv/wiki/Understanding-binstubs \ No newline at end of file
diff --git a/unicorn b/unicorn
new file mode 100644
index 0000000..0817fbd
--- /dev/null
+++ b/unicorn
@@ -0,0 +1,303 @@
+#!/bin/sh
+
+# PROVIDE: unicorn
+# REQUIRE: LOGIN postgresql
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="unicorn"
+rcvar=`set_rcvar`
+procname="ruby"
+
+extra_commands="upgrade show reopenlogs"
+start_cmd="unicorn_start_command"
+upgrade_cmd="unicorn_upgrade_command"
+show_cmd="unicorn_show_command"
+reopenlogs_cmd="unicorn_reopenlogs_command"
+
+load_rc_config "${name}"
+
+: ${unicorn_enable:="NO"}
+: ${unicorn_flags:="-D"}
+: ${unicorn_env:="production"}
+: ${unicorn_init_config:=""}
+: ${unicorn_bundle_gemfile:=""}
+: ${unicorn_profiles:=""}
+: ${unicorn_upgrade_timeout:="60"}
+: ${unicorn_rc_script:="/usr/local/etc/rc.d/unicorn"}
+
+#
+# Convenience function to read a profile's environment variable, or fall back to a default
+#
+# e.x. _read_profile_var my_app pidfile '/u/my_app/shard/pids/unicorn.pid'
+#
+_read_profile_var()
+{
+ profile=$1
+ var=$2
+ default=$3
+ default2=$4
+ eval value="\${unicorn_${profile}_${var}:-${default}}"
+ eval value="\${value:-${default2}}"
+ echo "${value}"
+}
+
+#
+# Takes a directory and sets up some default environement variables based on a capistrano layout
+#
+_setup_directory()
+{
+ local directory
+ directory=$1
+
+ directory_command="${directory}/current/bin/unicorn_rails"
+ directory_pidfile="${directory}/shared/pids/unicorn.pid"
+ directory_old_pidfile="${directory_pidfile}.oldbin"
+ directory_config="${directory}/current/config/unicorn.conf.rb"
+ directory_rackup="${directory}/current/config.ru"
+ directory_init_config="${directory}/current/.env"
+ directory_bundle_gemfile="${directory}/current/Gemfile"
+ directory_chdir="${directory}/current"
+
+ # only use the directory_init_config if it exists
+ if [ ! -f "${directory_init_config}" ]; then
+ unset directory_init_config
+ fi
+
+ # only use the bundle_gemfile if it exists
+ if [ ! -f "${directory_bundle_gemfile}" ]; then
+ unset directory_bundle_gemfile
+ fi
+
+ if [ -f "${directory_config}" ]; then
+ directory_user=`stat -f "%Su" "${directory_config}"` # default to the owner of the config file
+ fi
+}
+
+#
+# If we have a profile, set up the environment for that profile
+#
+if [ -n "$2" ]; then
+ profile="$2"
+
+ # set the rcvar for this specific profile
+ rcvar=`set_rcvar "${name}_${profile}"`
+ # if the user provides a directory, we can infer some default configuration
+ directory=`_read_profile_var "${profile}" "directory"`
+
+ if [ -n "${directory}" ]; then
+ _setup_directory "${directory}"
+ fi
+
+ unicorn_rackup=` _read_profile_var "${profile}" "rackup" "${directory_rackup}"`
+ unicorn_old_pidfile=` _read_profile_var "${profile}" "old_pidfile" "${directory_old_pidfile}"`
+ unicorn_config=` _read_profile_var "${profile}" "config" "${directory_config}"`
+ unicorn_init_config=` _read_profile_var "${profile}" "init_config" "${directory_init_config}"`
+ unicorn_bundle_gemfile=`_read_profile_var "${profile}" "bundle_gemfile" "${directory_bundle_gemfile}"`
+ unicorn_chdir=` _read_profile_var "${profile}" "chdir" "${directory_chdir}"`
+ unicorn_listen=` _read_profile_var "${profile}" "listen" "${unicorn_listen}"`
+ unicorn_user=` _read_profile_var "${profile}" "user" "${unicorn_user}" "${directory_user}"`
+ unicorn_nice=` _read_profile_var "${profile}" "nice" "${unicorn_nice}"`
+ unicorn_env=` _read_profile_var "${profile}" "env" "${unicorn_env}"`
+ unicorn_flags=` _read_profile_var "${profile}" "flags" "${unicorn_flags}"`
+
+ command=`_read_profile_var "${profile}" "command" "${unicorn_command}" "${directory_command}"`
+ pidfile=`_read_profile_var "${profile}" "pidfile" "${directory_pidfile}"`
+else
+ if [ "x${unicorn_profiles}" != "x" -a "x$1" != "x" ]; then
+ # If we weren't started with a profile, run the command on all available profiles
+ for profile in ${unicorn_profiles}; do
+ # By default set the profile rcvar to no to suppress warnings by checkyesno
+ profile_rcvar=`set_rcvar "${name}_${profile}"`
+ eval "${profile_rcvar}=\${${profile_rcvar}:-'NO'}"
+
+ if checkyesno ${profile_rcvar}; then
+ echo "Running ${1} on ${profile}"
+ ${unicorn_rc_script} $1 $profile
+ else
+ echo "Skipping ${profile}"
+
+ # Unset the variable and then checkyesno again to print the warning
+ eval "unset ${profile_rcvar}"
+ checkyesno ${profile_rcvar}
+ fi
+ echo
+ done
+ exit 0
+ else
+ # look for a profile-less configuration
+
+ # if the user provides a directory, we can infer some default configuration
+ directory=${unicorn_directory:-}
+
+ if [ -n "${directory}" ]; then
+ _setup_directory "${directory}"
+ fi
+
+ unicorn_rackup=${unicorn_rackup:-$directory_rackup}
+ unicorn_old_pidfile=${unicorn_old_pidfile:-$directory_old_pidfile}
+ unicorn_chdir=${unicorn_chdir:-$directory_chdir}
+ unicorn_user=${unicorn_user:-$directory_user}
+ unicorn_config=${unicorn_config:-$directory_config}
+ unicorn_init_config=${unicorn_init_config:-$directory_init_config}
+ unicorn_bundle_gemfile=${unicorn_bundle_gemfile:-$directory_bundle_gemfile}
+
+ command=${unicorn_command:-$directory_command}
+ pidfile=${unicorn_pidfile:-$directory_pidfile}
+ fi
+fi
+
+# add the directory as a required directory, if it's specified
+required_dirs="${directory:-}"
+
+# if we have a config file or rackup file specified, make sure it exists
+required_files="${unicorn_config:-} ${unicorn_rackup:-}"
+
+#
+# Build up the flags based on the environment variables
+#
+[ -n "${unicorn_listen}" ] && unicorn_flags="-l ${unicorn_listen} ${unicorn_flags}"
+[ -n "${unicorn_config}" ] && unicorn_flags="-c ${unicorn_config} ${unicorn_flags}"
+[ -n "${unicorn_env}" ] && unicorn_flags="-E ${unicorn_env} ${unicorn_flags}"
+
+# Add our rackup file to the unicorn command_args
+[ -n "${unicorn_rackup:-}" ] && command_args="${unicorn_rackup:-}"
+
+# This function builds the command to start unicorn. This is split out so we can
+# print it from the "show" command
+_unicorn_start_command()
+{
+ local shell_command
+ shell_command="${unicorn_bundle_gemfile:+export BUNDLE_GEMFILE=$unicorn_bundle_gemfile && }"\
+"${unicorn_init_config:+. $unicorn_init_config && }"\
+"${unicorn_chdir:+cd $unicorn_chdir && }"\
+"${unicorn_nice:+nice -n $unicorn_nice }"\
+"${command} ${rc_flags} ${command_args}"
+
+ if [ -n "${unicorn_user}" ]; then
+ echo "su -l ${unicorn_user} -c \"${shell_command}\""
+ else
+ echo "sh -c \"${shell_command}\""
+ fi
+}
+
+#
+# The start command
+#
+unicorn_start_command()
+{
+ # ensure unicorn isn't already running
+ if [ -z "$rc_fast" -a -n "$rc_pid" ]; then
+ echo 1>&2 "${name} already running? (pid=$rc_pid)."
+ return 1
+ fi
+
+ # ensure that the command exists and is executable
+ if [ ! -x ${_chroot}${_chroot:+"/"}${command} ]; then
+ warn "run_rc_command: cannot run $command"
+ return 1
+ fi
+
+ check_startmsgs && echo "Starting ${name}: ${profile}."
+
+ eval "$(_unicorn_start_command)"
+ _return=$?
+
+ if [ $_return -ne 0 ] && [ -z "${rc_force}" ]; then
+ return 1
+ fi
+
+ return 0
+}
+
+unicorn_upgrade_command()
+{
+ if [ -n "${rc_pid}" ]; then
+ # Tell the master to spawn a new version of itself
+ if kill -USR2 "${rc_pid}"; then
+ n=$unicorn_upgrade_timeout
+
+ # Wait until the unicorn_old_pidfile and pidfile both exist and are non-empty
+ echo -n >&2 "Waiting for the new master to spawn"
+ while [ \( ! -s "${unicorn_old_pidfile}" -o ! -s "${pidfile}" \) -a $n -ge 0 ]; do
+ printf >&2 '.' && sleep 1 && n=$(( $n-1 ))
+ done
+ echo
+
+ # if the pidfile or unicorn_old_pidfile still don't exist, quit
+ if [ ! -s "${pidfile}" -o ! -s "${unicorn_old_pidfile}" ]; then
+ [ ! -s "${pidfile}" ] && echo >&2 "${pidfile} doesn't exist after ${unicorn_upgrade_timeout} seconds"
+ [ ! -s "${punicorn_old_pidfile}" ] && echo >&2 "${unicorn_old_pidfile} doesn't exist after ${unicorn_upgrade_timeout} seconds"
+ return 1
+ fi
+
+ # make sure the new master can receive signals, and then QUIT the old one
+ echo >&2 "Killing the old master"
+ kill -0 `check_pidfile "${pidfile}" "${procname}"` && kill -QUIT `check_pidfile "${unicorn_old_pidfile}" "${procname}"`
+
+ # Wait for the old master to die
+ echo -n >&2 "Waiting for the old master to cleanup"
+ while [ -s "${unicorn_old_pidfile}" -a $n -ge 0 ]; do
+ printf >&2 '.' && sleep 1 && n=$(( $n-1 ))
+ done
+ echo
+
+ # If the old master is still around, print that fact and exit with an error
+ if [ -s "${unicorn_old_pidfile}" -a $n -lt 0 ]; then
+ echo >&2 "${unicorn_old_pidfile} still exists after ${unicorn_upgrade_timeout} seconds"
+ return 1
+ fi
+
+ # everything worked, return success
+ return 0
+ fi
+
+ # If there isn't a pid in rc_pid, just run the start command
+ echo >&2 "Couldn't upgrade, running `start` instead"
+ $0 start "${2}"
+ fi
+}
+
+#
+# Prints the configuration for the given profile
+#
+unicorn_show_command()
+{
+ if [ -n "${profile}" ]; then
+ banner="Unicorn Configuration for ${profile}"
+ else
+ banner="Unicorn Configuration"
+ fi
+
+ echo "
+#
+# ${banner}
+#
+
+command: ${command}
+command_args: ${command_args}
+pidfile: ${pidfile}
+old_pidfile: ${unicorn_old_pidfile}
+listen: ${unicorn_listen}
+config: ${unicorn_config}
+init_config: ${unicorn_init_config}
+bundle_gemfile: ${unicorn_bundle_gemfile}
+chdir: ${unicorn_chdir}
+user: ${unicorn_user}
+nice: ${unicorn_nice}
+env: ${unicorn_env}
+flags: ${unicorn_flags}
+
+start_command:
+
+$(_unicorn_start_command)
+"
+}
+
+unicorn_reopenlogs_command()
+{
+ [ -n "${rc_pid}" ] && kill -USR1 $rc_pid
+}
+
+run_rc_command "${1}"