diff --git a/README.md b/README.md index 583acb4b4e..c8a2a72cce 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,10 @@ If you start puma with `-S some/path` then you can pass that same path to the `p will cause the server to perform a restart. `pumactl` is a simple CLI frontend to the control/status app described above. +## Managing multiple Pumas / init.d script + +If you want an easy way to manage multiple scripts at once check "tools/jungle" for an init.d script. + ## License Puma is copyright 2011 Evan Phoenix and contributors. It is licensed under the BSD license. See the include LICENSE file for details. diff --git a/tools/jungle/README.md b/tools/jungle/README.md new file mode 100644 index 0000000000..5a2508396c --- /dev/null +++ b/tools/jungle/README.md @@ -0,0 +1,52 @@ +# Puma daemon service + +Init script to manage multiple Puma servers on the same box using start-stop-daemon. + +## Installation + + # Copy the init script to services directory + sudo cp puma /etc/init.d + sudo chmod +x /etc/init.d/puma + + # Make it start at boot time. + sudo update-rc.d -f puma defaults + + # Copy the Puma runner to an accessible location + sudo cp run-puma /usr/local/bin + sudo chmod +x /usr/local/bin/run-puma + + # Create an empty configuration file + sudo touch /etc/puma.conf + +## Managing the jungle + +Puma apps are held in /etc/puma.conf by default. It's mainly a CSV file and every line represents one app. Here's the syntax: + + app-path,user,config-file-path,log-file-path + +You can add an instance by editing the file or running the following command: + + sudo /etc/init.d/puma add /path/to/app user /path/to/app/config/puma.rb /path/to/app/config/log/puma.log + +The config and log paths are optional parameters and default to: + +* config: /path/to/app/*config/puma.rb* +* log: /path/to/app/*config/puma.log* + +To remove an app, simply delete the line from the config file or run: + + sudo /etc/init.d/puma remove /path/to/app + +The command will make sure the Puma instance stops before removing it from the jungle. + +## Assumptions + +* The script expects a temporary folder named /path/to/app/*tmp/puma* to exist. Create it if it's not there by default. +The pid and state files should live there and must be called: *tmp/puma/pid* and *tmp/puma/state*. +You can change those if you want but you'll have to adapt the script for it to work. + +* Here's what a minimal app's config file should have: + + pidfile "/path/to/app/tmp/puma/pid" + state_path "/path/to/app/tmp/puma/state" + activate_control_app diff --git a/tools/jungle/puma b/tools/jungle/puma new file mode 100755 index 0000000000..8bcee586ec --- /dev/null +++ b/tools/jungle/puma @@ -0,0 +1,332 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: puma +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Example initscript +# Description: This file should be used to construct scripts to be +# placed in /etc/init.d. +### END INIT INFO + +# Author: DarĂ­o Javier Cravero +# +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/usr/local/bin:/usr/local/sbin/:/sbin:/usr/sbin:/bin:/usr/bin +DESC="Puma rack web server" +NAME=puma +DAEMON=$NAME +SCRIPTNAME=/etc/init.d/$NAME +CONFIG=/etc/puma.conf +JUNGLE=`cat $CONFIG` +RUNPUMA=/usr/local/bin/run-puma + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# +# Function that starts the jungle +# +do_start() { + log_daemon_msg "=> Running the jungle..." + for i in $JUNGLE; do + dir=`echo $i | cut -d , -f 1` + user=`echo $i | cut -d , -f 2` + config_file=`echo $i | cut -d , -f 3` + if [ "$config_file" = "" ]; then + config_file="$dir/config/puma.rb" + fi + log_file=`echo $i | cut -d , -f 4` + if [ "$log_file" = "" ]; then + log_file="$dir/log/puma.log" + fi + do_start_one $dir $user $config_file $log_file + done +} + +do_start_one() { + PIDFILE=$1/tmp/puma/pid + if [ -e $PIDFILE ]; then + PID=`cat $PIDFILE` + # If the puma isn't running, run it, otherwise restart it. + if [ "`ps -A -o pid= | grep -c $PID`" -eq 0 ]; then + do_start_one_do $1 $2 $3 $4 + else + do_restart_one $1 + fi + else + do_start_one_do $1 $2 $3 $4 + fi +} + +do_start_one_do() { + log_daemon_msg "--> Woke up puma $1" + log_daemon_msg "user $2" + log_daemon_msg "log to $4" + start-stop-daemon --verbose --start --chdir $1 --chuid $2 --background --exec $RUNPUMA -- $1 $3 $4 +} + +# +# Function that stops the jungle +# +do_stop() { + log_daemon_msg "=> Putting all the beasts to bed..." + for i in $JUNGLE; do + dir=`echo $i | cut -d , -f 1` + do_stop_one $dir + done +} +# +# Function that stops the daemon/service +# +do_stop_one() { + log_daemon_msg "--> Stopping $1" + PIDFILE=$1/tmp/puma/pid + STATEFILE=$1/tmp/puma/state + if [ -e $PIDFILE ]; then + PID=`cat $PIDFILE` + if [ "`ps -A -o pid= | grep -c $PID`" -eq 0 ]; then + log_daemon_msg "---> Puma $1 isn't running." + else + log_daemon_msg "---> About to kill PID `cat $PIDFILE`" + pumactl --state $STATEFILE stop + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE $STATEFILE + fi + else + log_daemon_msg "---> No puma here..." + fi + return 0 +} + +# +# Function that restarts the jungle +# +do_restart() { + for i in $JUNGLE; do + dir=`echo $i | cut -d , -f 1` + do_restart_one $dir + done +} + +# +# Function that sends a SIGUSR2 to the daemon/service +# +do_restart_one() { + PIDFILE=$1/tmp/puma/pid + i=`grep $1 $CONFIG` + dir=`echo $i | cut -d , -f 1` + + if [ -e $PIDFILE ]; then + log_daemon_msg "--> About to restart puma $1" + pumactl --state $dir/tmp/puma/state restart + # kill -s USR2 `cat $PIDFILE` + # TODO Check if process exist + else + log_daemon_msg "--> Your puma was never playing... Let's get it out there first" + user=`echo $i | cut -d , -f 2` + config_file=`echo $i | cut -d , -f 3` + if [ "$config_file" = "" ]; then + config_file="$dir/config/puma.rb" + fi + log_file=`echo $i | cut -d , -f 4` + if [ "$log_file" = "" ]; then + log_file="$dir/log/puma.log" + fi + do_start_one $dir $user $config_file $log_file + fi + return 0 +} + +# +# Function that statuss the jungle +# +do_status() { + for i in $JUNGLE; do + dir=`echo $i | cut -d , -f 1` + do_status_one $dir + done +} + +# +# Function that sends a SIGUSR2 to the daemon/service +# +do_status_one() { + PIDFILE=$1/tmp/puma/pid + i=`grep $1 $CONFIG` + dir=`echo $i | cut -d , -f 1` + + if [ -e $PIDFILE ]; then + log_daemon_msg "--> About to status puma $1" + pumactl --state $dir/tmp/puma/state stats + # kill -s USR2 `cat $PIDFILE` + # TODO Check if process exist + else + log_daemon_msg "--> $1 isn't there :(..." + fi + return 0 +} + +do_add() { + str="" + # App's directory + if [ -d "$1" ]; then + if [ "`grep -c "^$1" $CONFIG`" -eq 0 ]; then + str=$1 + else + echo "The app is already being managed. Remove it if you want to update its config." + exit 1 + fi + else + echo "The directory $1 doesn't exist." + exit 1 + fi + # User to run it as + if [ "`grep -c "^$2:" /etc/passwd`" -eq 0 ]; then + echo "The user $2 doesn't exist." + exit 1 + else + str="$str,$2" + fi + # Config file + if [ "$3" != "" ]; then + if [ -e $3 ]; then + str="$str,$3" + else + echo "The config file $3 doesn't exist." + exit 1 + fi + fi + # Log file + if [ "$4" != "" ]; then + str="$str,$4" + fi + + # Add it to the jungle + echo $str >> $CONFIG + log_daemon_msg "Added a Puma to the jungle: $str. You still have to start it though." +} + +do_remove() { + if [ "`grep -c "^$1" $CONFIG`" -eq 0 ]; then + echo "There's no app $1 to remove." + else + # Stop it first. + do_stop_one $1 + # Remove it from the config. + sed -i "\\:^$1:d" $CONFIG + log_daemon_msg "Removed a Puma from the jungle: $1." + fi +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + if [ "$#" -eq 1 ]; then + do_start + else + i=`grep $2 $CONFIG` + dir=`echo $i | cut -d , -f 1` + user=`echo $i | cut -d , -f 2` + config_file=`echo $i | cut -d , -f 3` + if [ "$config_file" = "" ]; then + config_file="$dir/config/puma.rb" + fi + log_file=`echo $i | cut -d , -f 4` + if [ "$log_file" = "" ]; then + log_file="$dir/log/puma.log" + fi + do_start_one $dir $user $config_file $log_file + fi + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + if [ "$#" -eq 1 ]; then + do_stop + else + i=`grep $2 $CONFIG` + dir=`echo $i | cut -d , -f 1` + do_stop_one $dir + fi + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + # TODO Implement. + log_daemon_msg "Status $DESC" "$NAME" + if [ "$#" -eq 1 ]; then + do_status + else + i=`grep $2 $CONFIG` + dir=`echo $i | cut -d , -f 1` + do_status_one $dir + fi + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + restart) + log_daemon_msg "Restarting $DESC" "$NAME" + if [ "$#" -eq 1 ]; then + do_restart + else + i=`grep $2 $CONFIG` + dir=`echo $i | cut -d , -f 1` + do_restart_one $dir + fi + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + add) + if [ "$#" -lt 3 ]; then + echo "Please, specifiy the app's directory and the user that will run it at least." + echo " Usage: $SCRIPTNAME add /path/to/app user /path/to/app/config/puma.rb /path/to/app/config/log/puma.log" + echo " config and log are optionals." + exit 1 + else + do_add $2 $3 $4 $5 + fi + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + remove) + if [ "$#" -lt 2 ]; then + echo "Please, specifiy the app's directory to remove." + exit 1 + else + do_remove $2 + fi + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + *) + echo "Usage:" >&2 + echo " Run the jungle: $SCRIPTNAME {start|stop|status|restart}" >&2 + echo " Add a Puma: $SCRIPTNAME add /path/to/app user /path/to/app/config/puma.rb /path/to/app/config/log/puma.log" + echo " config and log are optionals." + echo " Remove a Puma: $SCRIPTNAME remove /path/to/app" + echo " On a Puma: $SCRIPTNAME {start|stop|status|restart} PUMA-NAME" >&2 + exit 3 + ;; +esac +: diff --git a/tools/jungle/run-puma b/tools/jungle/run-puma new file mode 100755 index 0000000000..059d177e9b --- /dev/null +++ b/tools/jungle/run-puma @@ -0,0 +1,3 @@ +#!/bin/bash +app=$1; config=$2; log=$3; +cd $app && exec bundle exec puma -C $config 2>&1 >> $log