Created
April 30, 2013 20:59
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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