Isolated Nginx & PHP installation script

nginx php bash debian

This script install a chrooted nginx and php environment on a specific folder of the system. Is has been developped and tested on Debian Squeeze 64 bits. It requires that the nginx, php5-cgi, spawn-fcgi packages are installed. In addition, if you want mysql support, install php5-mysql before launching the script.

If you need help, want to report bug or submit improvements, please post a comment at the bottom of the page.

#! /bin/bash
set -e

#### 
# Isolated Nginx & PHP Installation script
# Julien Vehent - 20110418
# http://wiki.linuxwall.info
# Distributed under GPL V2 licence
####


# The main container. change it to match your configuration.
chroot_container=/var/www/


# this function copies shared libraries from a binary to the chroot
# stolen from nixcraft and then improved a bit ;)
libs_to_chroot(){
        local d="$1"            # JAIL ROOT
        local pFILE="$2"        # copy bin file libs
        local files=""
        local _cp="/bin/cp"

        # get rid of blanks and (0x00007fff0117f000)
        files="$(ldd $pFILE |  awk '{ print $3 }' | sed -e '/^$/d' -e '/(*)$/d')"

        for i in $files
        do
            dcc="${i%/*}" # get dirname only
            [ ! -d ${d}${dcc} ] && mkdir -p ${d}${dcc} # create missing dirs
            [ ! -e ${d}${dcc}${i} ] && ${_cp} -f $i ${d}${dcc} # copy missing only

            # recursively get shared libraries of shared library
            libs_to_chroot "${d}" "${i}"
        done

        # Works with 32 and 64 bit ld-linux
        sldl="$(ldd $pFILE | grep 'ld-linux' | awk '{ print $1}')"
        sldlsubdir="${sldl%/*}"
        #[ ! -f ${d}${sldl} ] && ${_cp} -f ${sldl} ${d}${sldlsubdir}
        if [ ! -f ${d}${sldl} ];
        then
                ${_cp} -f ${sldl} ${d}${sldlsubdir}
        fi
}

install_env() {
	local target="$1"

	mkdir -p $chroot_container$target/{etc/init.d,dev,var/log/nginx,var/run,var/www,var/tmp,var/lib/nginx,usr/sbin,usr/bin,usr/share,bin,sbin,tmp,lib/lsb,lib64,usr/lib}
	chmod 1777 $chroot_container$target/var/tmp/
	mknod -m 0666 $chroot_container$target/dev/null c 1 3
	mknod -m 0666 $chroot_container$target/dev/random c 1 8
	mknod -m 0666 $chroot_container$target/dev/urandom c 1 9
	mknod -m 0666 $chroot_container$target/dev/zero c 1 5

	# create a user and a group that have the same name as target
	groupadd $target
	useradd -d $chroot_container$target/var/www -g $target -M -N -s /bin/false $target

	# copy startup program
	#cp -f /sbin/start-stop-daemon $chroot_container$target/sbin/
	#cp -f /lib/lsb/init-functions $chroot_container$target/lib/lsb/

    # copy ln to recreate symbolic links inside the chroot
    cp -f /bin/ln $chroot_container$target/bin/
}

install_nginx(){
	local target="$1"
	local nginx_bin=`which nginx`
	cp -f $nginx_bin $chroot_container$target$nginx_bin
	cp -fr /etc/nginx $chroot_container$target/etc/
	#copy shared libs to target
	libs_to_chroot "${chroot_container}${target}" "${nginx_bin}"

	cp -f /lib/libnss_compat.so.2 $chroot_container$target/lib/
	cp -f /lib/libnsl.so.1 $chroot_container$target/lib/
	cp -f /lib/libnss_nis.so.2 $chroot_container$target/lib/
	cp -f /lib/libnss_files.so.2 $chroot_container$target/lib/

	cp -fr /etc/{nsswitch.conf,services,hosts*,localtime,protocols,ld.so.cache,ld.so.conf,ld.so.conf.d,host.conf} $chroot_container$target/etc/

	#we just give the chroot the user it needs, that is root and its own
	head -n 1 /etc/passwd > $chroot_container$target/etc/passwd
	tail -n 1 /etc/passwd >> $chroot_container$target/etc/passwd

	head -n 1 /etc/group > $chroot_container$target/etc/group
	tail -n 1 /etc/group >> $chroot_container$target/etc/group
	
	

	# change user ownership
	chown -R $target:$target $chroot_container$target/var/www/

	
	# install basic configuration
	echo "user $target $target;
worker_processes  1;
error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    access_log    /var/log/nginx/access.log;
    sendfile        on;
    keepalive_timeout  65;
    tcp_nodelay        on;
    gzip  on;
    gzip_disable \"MSIE [1-6]\.(?!.*SV1)\";
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
" > $chroot_container$target/etc/nginx/nginx.conf

	# install basic virtual host
	echo "Nginx can be configured to accept requests from 127.0.0.1 only (coming from a proxy or load balancer)"
	echo -n "Do you want to allow 127.0.0.1 only and deny all (can be changed in site config) ? y/n > "
	read response
	
	if [ $response == "y" ]
	then
		echo "server {
    listen  127.0.0.1:$nginxport;"> $chroot_container$target/etc/nginx/sites-available/default
	else
		echo "server {
    listen  $nginxport;"> $chroot_container$target/etc/nginx/sites-available/default
	fi

        echo "
    server_name  $fqdn;
    access_log  /var/log/nginx/$fqdn.access.log;
    root   /var/www;
    index  index.cgi index.php index.html index.htm;">>$chroot_container$target/etc/nginx/sites-available/default

	
    if [ $phpsupport -eq 1 ]
    then
        echo "
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass   127.0.0.1:$phpport;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  \$document_root\$fastcgi_script_name;
    }">> $chroot_container$target/etc/nginx/sites-available/default
    fi

    if [ $fcgiwrapsupport -eq 1 ]
    then
        echo "
    location ~ \.cgi {
        include fastcgi_params;
        fastcgi_pass    unix:/var/run/fcgiwrap.sock;
        fastcgi_index   index.cgi;
        fastcgi_param  SCRIPT_FILENAME  \$document_root\$fastcgi_script_name;
    }">>$chroot_container$target/etc/nginx/sites-available/default
    fi

	echo "
}" >> $chroot_container$target/etc/nginx/sites-available/default

	# test the setup
	chroot $chroot_container$target/ $nginx_bin -t

	echo "nginx setup finished."
}



install_php(){

    aptitude install php5-cgi spawn-fcgi

    local spawnfcgibin=`which spawn-fcgi`
	local php_bin=`which php5-cgi`

    if [ ! -x $chroot_container$target$spawnfcgibin ]
    then
        cp -f $spawnfcgibin $chroot_container$target$spawnfcgibin
        libs_to_chroot "${chroot_container}${target}" "$spawnfcgibin"
    fi

	cp -f $php_bin $chroot_container$target$php_bin
	cp -fr /etc/php5 $chroot_container$target/etc/
	cp -fr /usr/lib/php5 $chroot_container$target/usr/lib/
	cp -fr /usr/share/zoneinfo $chroot_container$target/usr/share/

	libs_to_chroot "${chroot_container}${target}" "${php_bin}"

	echo "php setup finished."
}

install_mysql(){

	aptitude update 1>/dev/null
	aptitude install php5-mysql

	cp -fr /etc/php5 $chroot_container$target/etc/
	cp -fr /usr/lib/php5 $chroot_container$target/usr/lib/

	local mysql_lib=`find /usr/lib/php5/ -iname mysql.so`
	libs_to_chroot "${chroot_container}${target}" "${mysql_lib}"

	find /lib -iname libgcc_s.so* -exec cp -f {} $chroot_container$target/lib/ \;

	echo "php5-mysql setup finished."
}

install_fcgiwrap(){
	aptitude update 1>/dev/null
	aptitude install fcgiwrap spawn-fcgi

    fcgiwrap_bin=`which fcgiwrap`
	cp -f $fcgiwrap_bin $chroot_container$target$fcgiwrap_bin
	libs_to_chroot "${chroot_container}${target}" "${fcgiwrap_bin}"
    
    spawnfcgibin=`which spawn-fcgi`
    if [ ! -x $chroot_container$target$spawnfcgibin ]
    then
        cp -f $spawnfcgibin $chroot_container$target$spawnfcgibin
        libs_to_chroot "${chroot_container}${target}" "$spawnfcgibin"
    fi
}

install_perl(){
	local perl_bin=`which perl`
    #copy perl binary
	cp -f $perl_bin $chroot_container$target$perl_bin

    echo "copying all included perl modules from @INC, be patient (~5/10min)"
    for INC in $(perl -e 'foreach(@INC){print "$_\n";}')
    do
        if [ "$INC" != "." ]
        then
            if [ -h "$INC" ]
            then
                DESTLINK=$(readlink -f $INC)
                mkdir -p $chroot_container$target$DESTLINK
                cp -fr $DESTLINK/* $chroot_container$target$DESTLINK/
                chroot $chroot_container$target ln -s $DESTLINK $INC
            else
                if [ -d "$INC" ]
                then
                    mkdir -p $chroot_container$target$INC
                    cp -fr $INC $chroot_container$target$INC
                    for perl_lib in $(find $INC -iname '*.so' -exec ls {} \;)
                    do
                        libs_to_chroot "${chroot_container}${target}" "${perl_lib}"
                    done
                fi
            fi
        fi
    done

    #copy dynamic libs
	libs_to_chroot "${chroot_container}${target}" "${perl_bin}"

	echo "perl setup finished."
}

install_mojolicious(){
    # install chroot env, then move there and issue mojolicious install
    cp $(which bash) ${chroot_container}${target}$(which bash)
    libs_to_chroot "${chroot_container}${target}" $(which bash)

    cp $(which env) ${chroot_container}${target}$(which env)
    libs_to_chroot "${chroot_container}${target}" $(which env)

    cp $(which curl) ${chroot_container}${target}$(which curl)
    libs_to_chroot "${chroot_container}${target}" $(which curl)

    curl -s -L cpanmin.us | perl - Mojolicious --local-lib=/tmp-${target}-mojolicious/
    cp -r /tmp/${target}-mojolicious/lib/perl5/* ${chroot_container}${target}/usr/lib/perl5/
    cp -r /tmp/${target}-mojolicious/bin/*  ${chroot_container}${target}/usr/bin/
    rm -rf /tmp/${target}-mojolicious/

    echo "mojolicious setup finished."
}


create_supervised_config(){

	aptitude update 1>/dev/null
	aptitude install daemontools daemontools-run

    mkdir -p /etc/sv/spawn-fcgi-$target
    echo "#! /bin/bash
CHROOTBIN=/usr/sbin/chroot
CHROOTPATH=$chroot_container$target
SPAWNBIN=`which spawn-fcgi`
PHP5BIN=`which php5-cgi`
PHP5PORT=$phpport
PHP5ADDR=127.0.0.1
USER=$target
exec \$CHROOTBIN \$CHROOTPATH \$SPAWNBIN -n -a \$PHP5ADDR -p \$PHP5PORT -u \$USER -g \$USER -C 3 \$PHP5BIN
" > /etc/sv/spawn-fcgi-$target/run

	chmod +x /etc/sv/spawn-fcgi-$target/run
	update-service --add /etc/sv/spawn-fcgi-$target spawn-fcgi-$target
}

create_control(){
	echo "#! /bin/sh
#
### BEGIN INIT INFO
# Provides:          control-$target
# Required-Start:    $syslog
# Required-Stop:     $syslog
# Should-Start:      $local_fs
# Should-Stop:       $local_fs
# Default-Start:     2
# Default-Stop:      0 1 6
# Short-Description: starts/stops $target
# Description:       starts and stops $target
#                    a webserver chroot for $fqdn
#
### END INIT INFO

CHROOTBIN=/usr/sbin/chroot
CHROOTPATH=$chroot_container$target
NGINXDAEMON=`which nginx`
NAME=nginx

PHP5SUPPORT=$phpsupport
SPAWNBIN=`which spawn-fcgi`
PHP5BIN=`which php5-cgi`
PHP5PORT=$phpport
PHP5ADDR=127.0.0.1
PHPSUPERVISE=$phpsupervise

FCGIWRAPSUPPORT=$fcgiwrapsupport
FCGIWRAPSOCK=/var/run/fcgiwrap.sock
FCGIWRAPBIN=`which fcgiwrap`

USER=$target

DESC='chroot webserver (nginx/php) for $fqdn'
set -e
. /lib/lsb/init-functions
test_nginx_config() {
  if \$CHROOTBIN \$CHROOTPATH -t \$NGINXDAEMON_OPTS >/dev/null 2>&1
  then
    return 0
  else
    \$CHROOTBIN \$CHROOTPATH \$NGINXDAEMON -t \$NGINXDAEMON_OPTS
    return \$?
  fi
}
case \"\$1\" in
  start)
        echo \"Starting \$DESC ---\"

	# launching nginx
        test_nginx_config
        start-stop-daemon --chroot \$CHROOTPATH --start --pidfile /var/run/nginx.pid \
                --exec \$NGINXDAEMON -- \$NGINXDAEMON_OPTS || true
        echo -n \"nginx started with pid \"; cat \$CHROOTPATH/var/run/nginx.pid ; echo

        if [ \$PHP5SUPPORT -eq 1 ]
        then
            if [ \$PHPSUPERVISE -eq 1 ]
            then
                svc -u /etc/service/spawn-fcgi-$target
                echo \"spawn-fcgi and php5 started under supervise\"
            else
                start-stop-daemon --chroot \$CHROOTPATH --start \
                    --exec \$SPAWNBIN -- -a \$PHP5ADDR -p \$PHP5PORT \
                    -u \$USER -g \$USER -P /var/run/php5-cgi.pid \$PHP5BIN || true

            	echo -n \"php5-cgi started with pid \"; cat \$CHROOTPATH/var/run/php5-cgi.pid ; echo
            fi
        fi

        if [ \$FCGIWRAPSUPPORT -eq 1 ]
        then
            start-stop-daemon --chroot \$CHROOTPATH --start \
                    --exec \$SPAWNBIN -- -s \$FCGIWRAPSOCK \
                    -u \$USER -g \$USER -P /var/run/fcgiwrap.pid \$FCGIWRAPBIN || true

        	echo -n \"fcgiwrap started with pid \"; cat \$CHROOTPATH/var/run/fcgiwrap.pid ; echo
        fi

        ;;
  stop)
        echo \"Stopping \$DESC ---\"
	if [ -e \$CHROOTPATH/var/run/nginx.pid ]
	then
		kill \`cat \$CHROOTPATH/var/run/nginx.pid\`
		if [ \$? -ne 0 ]
		then
			echo  -n \"couldn't kill nginx - found running pid \" ; \`cat \$CHROOTPATH/var/run/nginx.pid\`;echo
		else
                	echo \"stopping nginx\"
		fi
        else
                echo \"nginx is not running, no pid file in /var/run/\"
        fi

        if [ \$PHP5SUPPORT -eq 1 ]
        then
            if [ \$PHPSUPERVISE -eq 1 ]
            then
                svc -d /etc/service/spawn-fcgi-$target
                echo \"spawn-fcgi and php5 stopped under supervise\"
            else
                if [ -e \$CHROOTPATH/var/run/php5-cgi.pid ]
                then
                    ps -p \`cat \$CHROOTPATH/var/run/php5-cgi.pid\` 2>&1 1>/dev/null
                    if [ $? -ne 0 ]
                    then
                        echo \"php5-cgi process not running\"
                    else
                        kill \`cat \$CHROOTPATH/var/run/php5-cgi.pid\`
                		if [ \$? -ne 0 ]
                		then
		    	            echo  -n \"couldn't kill php5-cgi - pid=\";\`cat \$CHROOTPATH/var/run/php5-cgi.pid\`;echo
                		else
                        	echo \"php5-cgi has been stopped\"
            		    fi
                    fi
                else
                    echo \"no php5-cgi pid file in /var/run/\"
                fi
            fi
        fi

        if [ \$FCGIWRAPSUPPORT -eq 1 ]
        then
            if [ -e \$CHROOTPATH/var/run/fcgiwrap.pid ]
            then
                ps -p \`cat \$CHROOTPATH/var/run/fcgiwrap.pid\` 2>&1 1>/dev/null
                if [ $? -ne 0 ]
                then
                    echo \"fcgiwrap process not running\"
                else
                    kill \`cat \$CHROOTPATH/var/run/fcgiwrap.pid\`
                	if [ $? -ne 0 ]
                	then
		    	        echo  -n \"couldn't kill fcgiwrap - pid=\";\`cat \$CHROOTPATH/var/run/fcgiwrap.pid\`;echo
                	else
                       	echo \"fcgiwrap has been stopped\"
            		fi
                fi
            else
                echo \"no fcgiwrap.pid file in /var/run/\"
            fi
        fi
            
        ;;
  restart)
        echo -n \"Restarting \$DESC: \"
	\$0 stop
        sleep 1
	\$0 start
        ;;
  configtest)
        echo -n \"Testing \$DESC configuration: \"
        if test_nginx_config
        then
          echo \"\$NAME.\"
        else
          exit \$?
        fi
        ;;
  status)
        status_of_proc -p \$CHROOTPATH/var/run/nginx.pid \"\$NGINXDAEMON\" nginx && exit 0 || exit $?
    " >> /etc/init.d/control-$target

if [ $phpsupport -eq 1 ]
then
    echo "
        status_of_proc -p \$CHROOTPATH/var/run/php5-cgi.pid \"\$PHP5BIN\" php5-cgi && exit 0 || exit $?
    " >> /etc/init.d/control-$target
fi

    echo "
        ;;
  *)
        echo \"Usage: \$0 {start|stop|restart|status|configtest}\" >&2
        exit 1
        ;;
esac
exit 0

" >> /etc/init.d/control-$target

	if [ -e /etc/init.d/control-$target ]
	then
		chmod +x /etc/init.d/control-$target
	else
		echo "couldn't create control file /etc/init.d/control-$target"
		exit 1
	fi
}

usage(){
	echo "Chroot installation script"
	echo "Syntax: $0 <site name>"
    echo "example: $0 chroot-example"
    echo "this will initiate the creation of $chroot_container/chroot-example"
	exit 1
}

[ $# -ne 1 ] && usage
[ $UID -ne 0 ] && echo "You need to be root to launch this program" && usage

target=$1
[ -d $chroot_container$target ] && echo "Target already exist !" && exit 1

echo -n "This is going to install a chroot environment in $chroot_container$target. Do you want to continue ? y/n > "
read response
if [ $response != "y" ]
then
	exit 1
fi

# create the environment
install_env "${target}"

# get some information from the administrator before we start
port_available=$(netstat -taupen|grep :50|grep LISTEN|awk '{print $4}'|cut -d : -f 2|sort|tail -n 1)
port_available=$(( $port_available + 1 ))

echo -n "Nginx listening port ($port_available seems available) ? > "
read nginxport

echo -n "What will the FQDN of the server be ? (example.com) ? > "
read fqdn

echo -n "Do you want chrooted php support ? y/n > "
read response
if [ $response == "y" ]
then
    phpsupport=1
	echo -n "Which port should php cgi listen on ? > "
	read phpport


	echo -n "Would you like to install php5-mysql support in the chroot ? y/n > "
	read response
	if [ $response == "y" ]
	then
		install_mysql "${target}"
	fi
else
    phpsupport=0
fi


if [ $phpsupport -eq 1 ]
then
	install_php "${target}"

	echo -n "Would you like to add to supervise php using daemon-tools 'supervise' ? y/n > "
	read response
	if [ $response == "y" ]
	then
        phpsupervise=1
		create_supervised_config "${target}"
	else
        phpsupervise=0
	fi
fi


echo -n "Do you want chrooted fcgiwrap support ? y/n > "
read response
if [ $response == "y" ]
then
    fcgiwrapsupport=1
	install_fcgiwrap "${target}"
else
    fcgiwrapsupport=0
fi

echo -n "Do you want perl support ? y/n > "
read response
if [ $response == "y" ]
then
	install_perl "${target}"
    echo -n "Also want Mojolicious ? y/n > "
    read response
    if [ $response == "y" ]
    then
        install_mojolicious "${target}"
    fi
fi

# call installation functions
install_nginx "${target}"
create_control "${target}"

echo -n "Would you like to add this server to startup runlevel 2 ? y/n > "
read response
if [ $response == "y" ]
then
	SAVEPWD=`pwd`
	cd /etc/rc2.d
	ln -s ../init.d/control-$target S17control-$target
	cd /etc/rc6.d
	ln -s ../init.d/control-$target K01control-$target
	cd $SAVEPWD
fi

echo -n "Would you like to create a logrotate rule for this server ? y/n > "
read response
if [ $response == "y" ]
then
	echo "$chroot_container$target/var/log/nginx/*.log {
        daily
        missingok
        rotate 7
        compress
        delaycompress
        notifempty
        create 640 root adm
        sharedscripts
        postrotate
                [ ! -f $chroot_container$target/var/run/nginx.pid ] || kill -USR1 \`cat $chroot_container$target/var/run/nginx.pid\`
        endscript
}" > /etc/logrotate.d/$target
fi

echo "The installation of the chroot environment is finished."
echo "To control the environement, use the script located at /etc/init.d/control-$target (outside the chroot)"

exit 0