Skip to content

Instantly share code, notes, and snippets.

@whiteinge
Created April 30, 2013 20:59
Show Gist options
  • Save whiteinge/5491907 to your computer and use it in GitHub Desktop.
Save whiteinge/5491907 to your computer and use it in GitHub Desktop.
A script to bootstrap a new Ubuntu machine for use as a PHP webserver. This gist exists to remind me / demonstrate a handful of techniques for organizing a large shell script.
#!/usr/bin/env bash
# Bootstrap a new Ubuntu machine for use as a PHP env for <company>.
# These functions are intended to be run largely in the order they appear
# below.
NAME=$(basename $0)
declare -A SITES
SITES[s1]="site1"
SITES[s2]="site2"
SITES[s3]="site3"
# These vars may be overridden by envionment variables of the same name but
# with a preceeding "S1_"
BASE_DIR=${S1_BASE_DIR:-"/home/somedev/src/site"}
DB_PASSWD=${S1_DB_PASSWD:-"somedev"}
function _error () {
local EXIT MSG
EXIT=$1 ; MSG=${2:-"${NAME}: Unknown Error"}
if [[ $EXIT -eq 0 ]] ; then
echo $MSG
else
echo $MSG 1>&2
exit $EXIT
fi
}
function _xmap () {
# Run a shell command once for every argument passed. The first argument
# should be a single-quoted string appropriate to pass to sh -c.
# For example:
# % _xmap 'echo @' foo bar baz
# echo foo
# echo bar
# echo baz
local cmd=$1
shift
echo -n ${@} | xargs -r -d" " -I@ sh -c "${cmd}"
}
function _check_site() {
# Verifies that the site passed in as an argument is valid
local SITE=$1
[[ "${SITE}" == *" "* ]] && _error 1 "One site at a time, please"
[[ ${!SITES[@]} == *${SITE}* ]] || _error 1 "Site not found: ${SITE}"
}
###
# Install required packages via apt
function pkgs_apt () {
_xmap 'echo "mysql-server-5.5 @ select '${DB_PASSWD}'" | debconf-set-selections' \
mysql-server/root_password{,_again}
aptitude -y install \
apache2 \
build-essential \
libapache2-mod-macro \
libnusoap-php \
memcached \
mysql-server-5.5 \
php5 \
php5-curl \
php5-dev \
php5-mcrypt \
php5-memcache \
php5-mysql \
php5-xdebug \
php-pear \
php-soap \
smarty \
subversion \
xmlstarlet \
|| _error 1 "Error installing packages from APT"
}
###
# Install required packages via pear
function pkgs_pear () {
pear install \
Config \
HTML_QuickForm \
MDB2 \
MDB2#mysql \
MDB2#mysqli \
XML_Parser \
XML_Util \
|| _error 1 "Error installing PEAR packages"
}
###
# Customize the PHP installation
function config_php () {
local limit before after phpopts
local phpini="/etc/php5/apache2/php.ini"
# Change default values in the php.ini file for development
# Define groups-of-three: search string, default value, new value
phpopts=( \
short_open_tag Off On \
allow_call_time_pass_reference Off On \
)
echo ${phpopts[@]} | xargs -n3 | while read limit before after ; do
sed -i'.bak' -re "/${limit}/ s/${before}/${after}/g" ${phpini}
# sed does not issue exit codes so in order to check if the above edit
# was successful, we need to search for the *after* pattern.
# We're using sed to do the search in order to reuse the limit pattern.
sed -nre "/${limit}/ s/${after}/&/gp" ${phpini} | grep -q . \
|| _error 1 "Edit failed for '${limit}' in ${phpini}"
done
# Also quiet the default error output
sudo sed -i.bak -re 's/^error_reporting = .*/error_reporting = E_ALL \& ~E_NOTICE \& ~E_DEPRECATED \& ~E_USER_DEPRECATED/g' ${phpini}
}
###
# Configure Apache and set up vhosts
# Takes a site name as an argument
function config_apache () {
local SITE=$1
local CODE_DIR=${S1_CODE_DIR:-"${BASE_DIR}/${SITES[${SITE}]}"}
local LOGS_DIR=${S1_LOGS_DIR:-"${CODE_DIR}/logs"}
[[ $# -eq 1 ]] || _error 1 "Requires a site argument"
_check_site "${SITE}"
for dir in \
"${LOGS_DIR}" \
"${CODE_DIR}/templates_c" \
"${CODE_DIR}/cached_files" \
"${CODE_DIR}/tmp" \
; do
mkdir -p ${dir}
chgrp www-data ${dir}
chmod g+w ${dir}
done
a2enmod \
expires \
macro \
mime \
mime_magic \
rewrite \
vhost_alias \
|| _error 1 "Error enabling Apache modules"
cat <<HEREDOC > /etc/apache2/sites-available/${SITE}
<VirtualHost *:80>
DocumentRoot ${CODE_DIR}/htdocs
ServerName ${SITE}.local.dev.example.com
ServerAdmin [email protected]
LogFormat COMBINED
ErrorLog ${LOGS_DIR}/error_log
TransferLog ${LOGS_DIR}/access_log
HostNameLookups off
<IfModule mod_php5.c>
php_value include_path ".:/usr/share/php:${CODE_DIR}/"
</IfModule>
<Directory "${CODE_DIR}/htdocs">
Options All -Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
ExpiresActive On
ExpiresDefault "access plus 300 seconds"
RewriteEngine On
</VirtualHost>
HEREDOC
cat <<HEREDOC > /var/www/index.html
<html><body><h1>It works!</h1>
<p>If you were expecting to see one of the development sites, try adding this
to your hosts file:</p>
<pre>
127.0.0.1 site1.local.dev.example.com
127.0.0.1 site2.local.dev.example.com
127.0.0.1 site3.local.dev.example.com
</pre>
<p>Then visit one of the URLs above and <strong>don't forget to add
:8080</strong> if your VM is configured to use ports.</p>
</body></html>
HEREDOC
a2ensite ${SITE} || _error 1 "Error enabling site: ${SITE}"
service apache2 reload
echo "Apache vhost for '$SITE' has been configured."
}
###
# Configure MySQL and permissions
# Takes a site name as an argument
function config_db () {
local SITE=$1
local CODE_DIR=${S1_CODE_DIR:-"${BASE_DIR}/${SITES[${SITE}]}"}
local APP_XML=${S1_APP_XML:-"${CODE_DIR}/application.xml"}
[[ $# -eq 1 ]] || _error 1 "Requires a site argument"
_check_site "${SITE}"
local dbuser dbpass
# Pull the username and password out of the xml file and into local vars
read dbuser dbpass <<<$(_xmap \
'xmlstarlet sel -t -v "//config/database/@" '${APP_XML}'' \
username \
password)
[[ -n ${dbuser} || -n ${dbpass} ]] \
|| _error 1 "Could not fetch user from XML file"
# Test the root user account before trying to use it
echo "\q" | mysql -uroot --password=${DB_PASSWD} mysql \
|| _error 1 "MySQL root passwd is incorrect"
# Test if the user account has been created yet
echo "\q" | mysql -u${dbuser} --password=${dbpass} db1 2>/dev/null
if [[ $? -ne 0 ]] ; then
echo "CREATE USER '${dbuser}'@'localhost' IDENTIFIED BY '${dbpass}';" \
| mysql --user=root --password=${DB_PASSWD} mysql \
|| _error 1 "Error creating user \"${dbuser}\" with password \"${dbpass}\""
echo "User account '${dbuser}' has been created."
fi
case "$SITE" in
db1 )
permssql="
GRANT DELETE ON \`db1\`.\`tablename\` TO '${dbuser}'@'localhost';
GRANT SELECT, INSERT, UPDATE, EXECUTE ON \`nc\`.* TO '${dbuser}'@'localhost';"
;;
db2 )
permssql="
GRANT DELETE ON \`db2\`.\`tablename\` TO '${dbuser}'@'localhost';
GRANT SELECT, INSERT, UPDATE, EXECUTE ON \`db2\`.* TO '${dbuser}'@'localhost' WITH GRANT OPTION;"
;;
db3 )
permssql="
GRANT INSERT ON \`db3\`.* TO '${dbuser}'@'localhost';
GRANT SELECT, INSERT, UPDATE, EXECUTE ON \`db3\`.* TO '${dbuser}'@'localhost';
GRANT DELETE ON \`db3\`.\`tablename\` TO '${dbuser}'@'localhost';" \
;;
esac
# Grant perms
echo ${permssql} \
| mysql --user=root --password=${DB_PASSWD} mysql \
|| _error 1 "Error setting db permissions"
echo "Database '$SITE' has been configured."
}
###
# Run all the setup functions
# Takes an optional site name as an argument
function config_all () {
pkgs_apt
pkgs_pear
config_php
for arg in $@ ; do
config_apache $arg
done
echo "Now run the (sitedev) db-setup script"
echo "then run this script again with config_all_db"
}
###
# Run all the database setup functions
# Takes an optional site name as an argument
function config_all_db () {
for arg in $@ ; do
config_db $arg
done
}
###
# The primary control point that executes other functions
function main () {
# Grab a list of all the "public" functions in this file for the help text
local ALL_FUNCS=$(awk '/^function [a-zA-Z]+/ {print $2}' $0)
local USAGE="Usage: ${NAME} <command> <site>\n
Available commands: ${ALL_FUNCS}\n
Available sites: ${!SITES[@]}\n"
if [[ $# -lt 1 ]] || [[ $1 == "--help" ]] || [[ $1 == "-h" ]] ; then
echo -e ${USAGE}
exit
fi
[[ "$(id -u)" == "0" ]] || _error 1 "This script must be run using sudo"
[[ ${ALL_FUNCS} == *${1}* ]] || _error 1 "Command not found: ${1}"
[[ -n "${2}" ]] && _check_site ${2}
# Run the command for the given site
${1} ${2:-"${!SITES[*]}"}
}
### run the program
main $*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment