Laziness is the engine of progress or my version of creating an environment for web development based on VirtualBox



One way or another, all web developers need some kind of server to develop their web applications. Someone uses Denver , someone OpenServer , more advanced take a virtual server (VPS), and even more advanced use Vagrant , and someone just lazy. Under the cat, I’ll tell you how to deploy a web development application using VirtualBox, bash and some crutches. For those who are lazy and do not want to look under the cat: one bash script is described that mounts the shared folders in the guest OS and a half- daemon that runs the first script after starting before stopping the system and implements the daemon interface.

The CentOS 6.5 Linux distribution was chosen as the guest operating system, and Apache 2.2.15 as the web server.

I’ll make a reservation right away: I won’t describe the installation and configuration of the LAMP server because there are a lot of mana on the Internet.

The very first version of the script looked something like this:

#!/bin/sh
mount -t vboxsf $1 $2


Gradually, it grew into the following script:
Script one - workhorse - /root/scripts/vbox-sf.sh
#!/bin/sh
# Author:	Dmitry Vapelnik
# Email:	dvapelnik@gmail.com
# Местонахождение лог-файла
logfile='/var/log/vbox-sf.log'
# Место, куда мы будем маунтить папки
mountPrefix='/var/www/html/';
# Хостнейм-суффикс
hn='.'`hostname`
# Ищем все папки, которые были подмаунчены VirtualBox
sharedFolders=`df | egrep "\/media\/sf_\$2[^ ]*" -o | sed -e 's/\/media\/sf_//'`
#================ LOG ==========================================================#
function log {
    echo [`date +"%F %T"`] $1 $2 >> $logfile
}
#================ MOUNTING =====================================================#
function mountFn {
    echo "Mounting....";
    for f in $sharedFolders; do
	mountPath=$mountPrefix$f$hn
	if cat /proc/mounts | grep vbox | grep $mountPath &> /dev/null; then
	    echo Already mounted. Continue..;
	else
	    rm -rf $mountPath 2> /dev/null;
	    mkdir -p $mountPath;
	    chown apache:apache $mountPath;
	    if mount -t vboxsf $f $mountPath -o umask=0022,uid=apache,gid=apache; then
		echo Mounted $f
		log mounted $mountPath
		# Папки для сохранения результирующих файлов профилиования и 
		# трейсов для XDebug
		mkdir -p /var/www/html/$f$hn/xd_profile_$f$hn
		mkdir -p /var/www/html/$f$hn/xd_trace_$f$hn
		if [ -f $mountPath'/httpd.conf' ]; then
		    # Формирование конфига httpd из заранее подготовленного шаблона
		    cat $mountPath'/httpd.conf' | sed -e "s/<%domain%>/$f$hn/g" > /etc/httpd/conf/sf/$f$hn.conf
		    if [ -f $mountPath'/aftermount.sh' ]; then
			# Запуск заранее подготовленного скрипта, который будет
			# запускаться сразу после монтирования папки
			bash $mountPath'/aftermount.sh' $mountPath;
		    fi
		fi
	    fi
	fi
    done;
    # Перезапускаем веб-сервер
    service httpd restart
}
#================ UNMOUNTING ===================================================#
function umountFn {
    echo "Unmounting..."
    # Останавливаем веб-сервер
    service httpd stop
    for f in $sharedFolders; do
	mountPath=$mountPrefix$f$hn
	# Удаляем логи веб-сервера
	find $mountPath -type f -name httpd_"$f""$hn"_*.log -exec rm -f {} \;
	# Удаляем папки профилирования и трейсов
	rm -rf $mountPath/xd_profile_"$f""$hn"
	rm -rf $mountPath/xd_trace_"$f""$hn"
	# Выполняем заранее подготовленный стенарий, который должен
	# быть выполненым перед демонтированием папки
	if [ -f $mountPath'/beforeumount.sh' ]; then
	    bash $mountPath'/beforeumount.sh' $mountPath;
	fi
	# Демонтируем папку
	umount $mountPath
	# Подчищаем за собой
	if [[ $? -eq 0  ]]; then
	    rm -rf $mountPath 2> /dev/null
	    rm -f /etc/httpd/conf/sf/$f$hn.conf 2> /dev/null
	    echo "Unmounted and removed $f"
	    log umounted $mountPath
	else
	    echo "Not unmounted"
	fi
    done;
    # Запускаем веб-сервер
    service httpd start
}
#================ STATUS =======================================================#
function statusFn {
    com=0
    for f in $sharedFolders; do
	mountPath=$mountPrefix$f$hn
	if df | grep $mountPath &> /dev/null; then
	    com=`expr $com + 1`;
	    if [ $com -eq 1 ]; then
		echo List of mounted resources:
	    fi
	    df | grep $mountPath | egrep -o '\/.+$'
	fi
    done
    if [ $com -eq 0  ]; then
	echo No shared storage mounted
    fi
}
#===============================================================================#
if [ "$1" == "mount"  ]; then
    mountFn;
    exit 0
elif [ "$1" == "umount" ]; then
    umountFn;
    exit 0
elif [ "$1" == "status" ]; then
    statusFn;
    exit 0
else
    cat << EOF
    No arguments supplied
    -----------------------------------------------------------------------------
    Usage:
    -----------------------------------------------------------------------------
    Using with single argument one of:
	mount	: for mounting all shared folders under /var/www/html directory
	umount	: for unmounting all shared folders under /var/www/html directory
	status	: for checking mount status
    -----------------------------------------------------------------------------
    Using with two argument for mounting or unmounting single folder
    Example:
	vbox-sf mount foo	: will mount shared folder /media/sf_foo into /var/www/html/foo.domain.com
	vbox-sf umount foo	: will umount shared folder /media/sf_foo from /var/www/html/foo.domain.com
EOF
    exit 1
fi
exit 0


What is needed and how does it work


  1. It is necessary to install a web server on the guest machine (I chose apache).
  2. To correctly mount shared folders on the guest OS, you must install Guest Additions .
  3. The folder that we mount should be called short, but unique - this name will be a subdomain of our server’s domain.
  4. It is necessary to put the virtual host config template for the web server in the root of the shared folder.
    Approximate virtual host config for web server
    
    	DocumentRoot /var/www/html/<%domain%>
    	ServerName <%domain%>
    	ServerAlias www.<%domain%>
    	DirectoryIndex index.php
    	>
    		AllowOverride All
    		php_admin_value open_basedir /var/www/html/<%domain%>:/tmp:/usr/share:/var/lib
    	
    	CustomLog	/var/www/html/<%domain%>/httpd_<%domain%>_access.log combined
    	ErrorLog	/var/www/html/<%domain%>/httpd_<%domain%>_error.log
    	php_admin_value xdebug.profiler_output_dir /var/www/html/<%domain%>/xd_profile_<%domain%>
    	php_admin_value xdebug.trace_output_dir /var/www/html/<%domain%>/xd_trace_<%domain%>
    

    The config itself can change, but the main points need to be saved: <% domain%> - a mask that is replaced with a domain for a web application, log files and folders for profiling and tracing (if necessary). Everything else to taste, depending on what is needed in the application.


  5. Folders must be shared with the auto-mount and preferably with the right to write to it - we can upload something through this application and therefore the files must be saved. And someone can cache something in files. The right to write does not hurt. Auto-mount is important - the script selects the necessary folders from the list of already mounted folders in / media / sf_ * - that is why the folders must be mounted with auto-mount. It should look something like the screenshot.
  6. On the guest machine, SELinux must be disabled. With SELinux enabled, the web server does not see folders mapped in / var / www / html / - they are mounted in the vbox context, not httpd. I have not yet found how to fix it and therefore propped up with a crutch - disabled SELinux.
  7. The hostname on the guest machine should be in the form of a domain. I have it natty.nat and as a result, all the web applications that I download to this guest machine will look like this: [name of the folder to be shared]. [Hostname]. e.g. test.natty.nat . Personally, I was comfortable hanging on such domains.
  8. You need to create a folder / etc / httpd / conf / sf ,
    # mkdir -p /etc/httpd/conf/sf
    in which configs of virtual hosts of a web server will be stored. At the same time, at the end of the /etc/httpd/conf/httpd.conf file, it is necessary to include all the configs that we will store in the above folder:
    include "conf/sf/*.conf"


If everything is fine, then now we can share the folder with our project on our virtual machine, run it and, by executing the following command, get a ready place for development:

# /path/to/vbox-sf.sh mount


So we mount all the shared folders. If we specify the name of our folder as the second argument, then only it can be mounted or unmounted:

# /path/to/vbox-sf.sh mount test

# /path/to/vbox-sf.sh umount test


Demon


But now it would be nice to automate this business somehow. I chose the path of the daemon and I believe that it will be more correct than changing the /etc/rc.# files. The following script was written for /etc/init.d
Second script - daemon - /etc/init.d/vboxsf
#!/bin/bash
#
# Author:	Dmitry Vapelnik
# Email:	dvapelnik@gmail.com
### BEGIN INIT INFO
# Required-Start: 	httpd mysqld vboxadd-service vboxadd
# Required-Stop:	httpd mysqld vboxadd-service vboxadd
# Default-Start:	3
# Default-Stop:		0 6
# Short-Description:	Mounting VirtualBox shared folders
# Description:		This file should be used to mount and umount 
#			VirtualBox shared folders
### END INIT INFO
# Get function from functions library
. /etc/init.d/functions
prog="VBoxSF"
lockfile='/var/lock/subsys/vboxsf'
# Start the service vbox-sf
start() {
#	initlog -c "echo -n Starting $prog server: "
	/root/bin/vbox-sf mount &> /dev/null && touch $lockfile
	success $"$prog: mounted"
	echo
}
# Restart the service vbox-sf
stop() {
#	initlog -c "echo -n Umounting $prog: "
	/root/bin/vbox-sf umount &> /dev/null && rm -f $lockfile
	success $"$prog: umounted"
	echo
}
status() {
	/root/bin/vbox-sf status
}
### main logic ###
case "$1" in
	start)
		start
		;;
	stop)
		stop
		;;
	status)
		status
		;;
	restart|reload|condrestart)
		stop
		start
		;;
	*)
		echo $"Usage: $0 {start|stop|restart|status}"
		exit 1
esac
exit 0


This script must be put in the directory /etc/init.d and name the file vboxsf . In fact, the file name is uncritical here. Just when we add a new daemon using chkconfig , we will need to specify the name of this file. Next, I will use vboxsf .

So, we added the file. Now we need to link our script to / root / bin :

# mkdir -p /root/bin
# ln -s /root/scripts/vbox-sf.sh /root/bin/vbox-sf


Add to chkconfig :

# chkconfig --add vboxsf


Check if everything has been added:

# chkconfig | grep vboxsf


If all is well, we should be shown at what levels our script will run.



As a result, now we can simply use the following commands:

# service vboxsf start
# service vboxsf stop
# service vboxsf restart
# service vboxsf status


If not, then look what we did wrong. In principle, at this point, everything should work: mounted at startup and dismantled before shutting down.

And now yummy


Remember the scripts that run after mounting and before unmounting a folder? So, for more convenient work, I also deploy the database from the dump to the MySQL server and before the demount occurs, merge the database back into the dump. Thus, we have an actual base dump after we turn off the machine and have an actual base after turning on the machine.

These scripts are:
Third script - restoring a database from a dump - aftermount.sh
#!/bin/sh
# Author:	Dmitry Vapelnik
# Email:	dvapelnik@gmail.com
if [ $# -eq 0  ]; then
    echo 'No arguments supplied';
    echo 'Exit';
    exit 0;
fi
######################################################
dbAdminUser='ourDbAdminLogin'
dbAdminPass='ourDbAdminPassword'
dbName='ourDbName'
dbUser='ourDbUser'
dbPass='ourDbPassword'
dbHost='localhost'
dbDump=$1'/db.sql'
######################################################
queryCreateUser="CREATE USER '$dbUser'@'$dbHost' IDENTIFIED BY '$dbPass';
CREATE DATABASE IF NOT EXISTS \`$dbName\`;
GRANT ALL PRIVILEGES ON \`$dbName\`.* TO '$dbUser'@'$dbHost';
FLUSH PRIVILEGES;"
echo Creating new user...
if mysql -u$dbAdminUser -p$dbAdminPass -e "$queryCreateUser"; then
    echo User added
    echo Using MySQL dump
    if mysql -u$dbAdminUser -p$dbAdminPass $dbName < $dbDump; then
	echo MySQL dump loaded into $dbName
    else
	echo MySQL dump not loaded
    fi
else
    echo Error: user not added exit
fi


Important! Achtung! dbAdminUser and dbAdminPass is the login and password of the database administrator, who can create / delete the user, create the database and fill in the dump.
ourDbName, ourDbUser and ourDbPassword - the name of the database and the username and password of the MySQL user that is used in our mounted web application being developed.

What this script does:
  1. Creates a user;
  2. Creates a database;
  3. Pour the contents of our dump into this database


What is necessary:

  1. So that the DBA can add the user, create the DB and fill it with our dump (I use the MySQL root user and some short password - no one will break my server on VirtualBox);
  2. A dump of our database should be at the root of the shared folder in the db.sql file . We can name it differently and put it in another place, but then it will be necessary to correct the script too - this is also not a problem if desired;
  3. To maintain cleanliness, neither the created user, nor the database should be before the deployment of our entire business.


The second script simply saves the contents of our database to a file, cleans up the user and removes the database.
The fourth script - saving the database to the dump - beforeumount.sh
#!/bin/sh
# Author:	Dmitry Vapelnik
# Email:	dvapelnik@gmail.com
if [ $# -eq 0  ]; then
    echo 'No arguments supplied';
    echo 'Exit';
    exit 0;
fi
######################################################
dbAdminUser='dbAdminUser'
dbAdminPass='dbAdminPass'
dbName='ourDbName'
dbUser='ourDbUser'
dbHost='localhost'
dbDump=$1'/db.sql'
######################################################
queryRemove="DROP DATABASE \`$dbName\`;
DROP USER '$dbUser'@'$dbHost';
FLUSH PRIVILEGES;"
echo Dumping MySQL DB into file..
if mysqldump -u$dbAdminUser -p$dbAdminPass $dbName > $dbDump; then
    echo DB dumped into file
    echo Removing user and database
    if mysql -u$dbAdminUser -p$dbAdminPass -e "$queryRemove"; then
	echo User and DB was removed
    else
	echo Error on removing
    fi
fi

Settings as in the previous script. At this point, everything should be transparent.

After all these manipulations, we get the following: we have a virtual server running under VirtualBox; after we turn it on, it automatically mounts our folders, creates a user, a database in MySQL, uploads our pre-prepared dump to it - you can work - the logs are actually on our machine, profiling files and traces are also on our machine ; upon completion of work, turn off the server: logs are deleted, profiling files and traces are deleted along with the folder, the contents of the database are dumped back to our file, folders are dismantled and the machine turns off. In fact, our server plays the role of a kind of surrogate or donor - nothing is finally stored on it and it doesn’t roll around, but it only reproduces what we throw it.

A nice and sometimes useful bonus: we can configure several servers with different software versions and at the same time check how our web application works on each version of php, MySQL, Apache. Logs, traces and profiling files will lie in separate files depending on the domain name, so as not to interfere with everything in a heap.

If necessary, you can hang other sweets and amenities, but this is to the taste and needs of each developer separately. All that is needed is shoved into scripts that are executed after mounting and before dismounting.

Yes, another important detail. In order not to register the whole zoo of subdomains that can be used for development, it is necessary to install it on the server machine (on the machine on which we are developing and where VirtualBox is installed, I have this laptop) Dnsmasq- This is a lightweight DNS, DHCP, TFTP (BOOTP, PXE) server. It is tuned utterly nasty simply. My cleaned config looks like this (yes, I have three machines: 5.3, 5.4, 5.5):

$ cat /etc/dnsmasq.conf | egrep -v "(^#.*|^$)"
listen-address=127.0.0.1
address=/natty.nat/192.168.191.160
address=/ketty.nat/192.168.191.161
address=/betsy.nat/192.168.191.162


Thus, all subdomains of these domains will be directed to the specified IP addresses.

Still, someone might find the piece /etc/php.ini useful for XDebug useful .
Piece from /etc/php.ini
; этот путь может быть другим - смотрим куда установится XDebug
zend_extension="xdebug.so"
xdebug.cli_color=1
xdebug.remote_enable=true
xdebug.remote_host="192.168.191.1"
xdebug.remote_port=9000
xdebug.remote_handler="dbgp"
xdebug.remote_connect_back=1
; Profiler
xdebug.profiler_enable = 0
xdebug.profiler_enable_trigger = 1
xdebug.profiler_append = 0
; Trace options
xdebug.collect_includes = 1
xdebug.collect_params = 4
xdebug.collect_vars = 0
xdebug.dump.REQUEST = *
xdebug.dump.SESSION = *
;xdebug.dump.SERVER = REMOTE_ADDR,REQUEST_METHOD
xdebug.dump.SERVER = *
xdebug.dump_globals = 1
xdebug.dump_once = 1
xdebug.dump_undefined = 0
xdebug.show_mem_delta = Off
;xdebug.file_link_format = ''
xdebug.manual_url = http://www.php.net
xdebug.show_exception_trace = 1
xdebug.show_local_vars = 1
xdebug.show_mem_delta = 1
; Traces
xdebug.auto_trace = 0
xdebug.collect_assignments = 1
xdebug.collect_return = 1
xdebug.trace_enable_trigger = 1
; 0 for parsing
; 1 human readable
xdebug.trace_format = 1
xdebug.trace_options = 0
xdebug.trace_output_name = trace.%c


If anyone does not know, there is a convenient extension for working with XDebug - The easiest Xdebug , which allows you to enable / disable breakpoint stopping, profiling and tracing.

I created everything on the network 192.168.191.0/24 and then, my IPs from this subnet. If you have a different subnet - change as you like.

Also popular now: