#!/bin/bash ## Download Drupal, CiviCRM, dependencies, and useful development tools. ## Setup basic symlinks. ## ## Maybe, when drush or composer is more mature, we can eliminate this. ## Determine the absolute path of the directory with the file ## absdirname function absdirname() { pushd $(dirname $0) >> /dev/null pwd popd >> /dev/null } BINDIR=$(absdirname "$0") PRJDIR=$(dirname "$BINDIR") TMPDIR="$PRJDIR/app/tmp" LOCKFILE="$TMPDIR/civi-download-tools.lock" LOCKTIMEOUT=90 CIVIXVER=15.04.1 CIVIXURL="http://downloads.sourceforge.net/project/civix/civix-${CIVIXVER}.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fcivix%2Ffiles%2F&use_mirror=master" CVURL="https://download.civicrm.org/cv/cv.phar-83e03d3" HUBTAG="v1.12.4" HUBURL="https://github.com/github/hub" DRUSH8URL=http://files.drush.org/drush.phar IS_QUIET= IS_FORCE= IS_FULL= ################################################## ## Parse arguments while [ -n "$1" ] ; do OPTION="$1" shift case "$OPTION" in -q|--quiet) ## Don't display advisory comments ## Only display messages if we're actually making changes IS_QUIET=1 ;; -f|--force) ## (Re)-download everything IS_FORCE=1 ;; --full) ## Heuristically identify/download system packages. IS_FULL=1 ;; --dir) set -e [ ! -d "$1" ] && mkdir "$1" pushd "$1" >> /dev/null PRJDIR=$(pwd) popd >> /dev/null BINDIR="$PRJDIR/bin" TMPDIR="$PRJDIR/app/tmp" LOCKFILE="$TMPDIR/civi-download-tools.lock" set +e shift ;; *) echo "Unrecognized option: $OPTION" echo "Usage: $0 [-q|--quiet] [-f|--force] [--full] [--dir ]" ;; esac done ############################################################################### ## usage: download_url function download_url() { #php -r "echo file_get_contents('$1');" > $2 if which wget >> /dev/null ; then wget -O "$2" "$1" elif which curl >> /dev/null ; then curl -L -o "$2" "$1" else echo "error: failed to locate curl or wget" fi } ############################################################################### ## usage: echo_comment function echo_comment() { if [ -z "$IS_QUIET" ]; then echo "$@" fi } ############################################################################### ## Ensure that a command is on the PATH. If missing, then give ## advice on possible resolutions and exit. ## usage: check_command [] function check_command() { local cmd="$1" local requirement="$2" local msg="$3" [ -z "$msg" ] && msg="Failed to locate command \"$cmd\". Please install it (and set the PATH appropriately)." cmdpath=$(which $cmd) if [ -z "$cmdpath" ]; then echo "$msg" show_command "$cmd" "It is possible that you have already installed \"$cmd\" in a non-standard location. If so, please update the PATH appropriately. Possible matches were found in:" if [ "$requirement" = "required" ]; then exit 3 fi fi } ############################################################################### ## Show a list of possible locations where the command can be found ## usage: show_command [] function show_command() { local cmd="$1" local msg="$2" local is_first=1 for altdir in \ /Applications/MAMP/Library/bin \ /Applications/MAMP/bin/php/php*/bin \ /{usr,opt}{,/local}/bin \ /{usr,opt}{,/local}/*/bin \ /{usr,opt}{,/local}/lib/*/bin do if [ -f "$altdir/$cmd" ]; then if [ -n "$is_first" ]; then echo $msg is_first= fi echo " * $altdir" fi done } ############################################################################### ## Debian.org's NodeJS package uses a non-standard name for the node binary. ## If necessary, setup an alias for the standard name. function nodejs_debian_workaround() { if which nodejs >> /dev/null ; then if ! which node >> /dev/null ; then echo "[[NodeJS binary appears to be misnamed. Creating 'node' alias.]]" ln -s "$(which nodejs)" "$BINDIR/node" fi fi } ############################################################################### ## Check if a PHP extension is enabled ## usage: check_php_ext [] ## ## Note: There's not much harm in calling check_php_ext for more requirements, ## but bear in mind that this only handles requirements for buildkit CLI. ## For civicrm-core runtime, the app should have its own checks. function check_php_ext() { local ext="$1" local requirement="$2" local msg="$3" if [ -z "$msg" -a "$requirement" = "required" ]; then msg="ERROR: Failed to find required PHP extension \"$ext\"." elif [ -z "$msg" -a "$requirement" = "recommended" ]; then msg="WARNING: Failed to find recommended PHP extension \"$ext\"." fi if php -r 'exit((int)in_array("'$ext'", get_loaded_extensions()));' ; then echo "$msg" if [ "$requirement" = "required" ]; then echo "" if [ `uname` = "Darwin" ]; then echo "TIP: In OS X, it is common to install an alternative PHP bundle, such as MAMP or XAMPP, which provides more extensions by default." show_command php "TIP: You may wish to configure a PATH to choose a different version of PHP. The following versions were found automatically:" fi if [ `uname` = "Linux" ]; then echo "TIP: In some systems, the PHP version used in CLI and web are different. Extensions should be active in both." fi exit 4 fi fi } ############################################################################### ## Prompt user for confirmation ## (In automated scripts or blank response, use default) ## ## usage: cvutil_confirm ## example: cvutil_confirm "Are you sure? [Y/n] " y y function cvutil_confirm() { local msg="$1" local i_default="$2" local s_default="$3" if tty -s ; then echo -n "$msg" read _cvutil_confirm if [ "x$_cvutil_confirm" == "x" ]; then _cvutil_confirm="$i_default" fi else echo "${msg}${s_default}" _cvutil_confirm="$s_default" fi case "$_cvutil_confirm" in y|Y|1) return 0 ;; *) return 1 ;; esac } ############################################################################### ## Determine the function to handle system package installation function get_system_installer() { if [ -n "$DISTRIB_CODENAME" ]; then true elif which lsb_release >/dev/null; then # Debian doesn't ship with /etc/lsb-release. See # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=444678 DISTRIB_CODENAME=$(lsb_release -cs) elif [ -f "/etc/lsb-release" ]; then source /etc/lsb-release fi case "$DISTRIB_CODENAME" in precise|trusty|jessie) echo "do_system_$DISTRIB_CODENAME" ;; *) echo do_system_unknown ;; esac } ############################################################################### function do_system_precise() { set -e PACKAGES="acl git wget unzip mysql-server mysql-client php5-cli php5-imap php5-ldap php5-curl php5-mysql php5-intl php5-gd php5-mcrypt php-apc apache2 libapache2-mod-php5 nodejs ruby rake" echo "Detected \"Ubuntu Precise 12.04\"." echo "" echo "Recommended packages: $PACKAGES" echo "" if cvutil_confirm "Run automated installation? [Y/n] " y y; then curl -sL https://deb.nodesource.com/setup_0.12 | sudo -E bash - sudo apt-get -y install $PACKAGES sudo a2enmod rewrite sudo apache2ctl restart else echo "Aborted" 1>&2 exit 1 fi set +e } ############################################################################### function do_system_trusty() { set -e PACKAGES="acl git wget unzip mysql-server mysql-client php5-cli php5-imap php5-ldap php5-curl php5-mysql php5-intl php5-gd php5-mcrypt php-apc apache2 libapache2-mod-php5 nodejs-legacy npm ruby rake" echo "Detected \"Ubuntu Trusty 14.04\"." echo "" echo "Recommended packages: $PACKAGES" echo "" if cvutil_confirm "Run automated installation? [Y/n] " y y; then sudo apt-get update sudo apt-get -y install $PACKAGES sudo php5enmod mcrypt sudo php5enmod imap sudo a2enmod rewrite sudo apache2ctl restart else echo "Aborted" 1>&2 exit 1 fi set +e } ############################################################################### function do_system_jessie() { set -e PACKAGES="acl git wget unzip mysql-server mysql-client php5-cli php5-imap php5-ldap php5-curl php5-mysql php5-intl php5-gd php5-mcrypt php-apc apache2 libapache2-mod-php5 nodejs-legacy npm ruby rake" echo "Detected \"Debian Jessie\"." echo "" echo "Recommended packages: $PACKAGES" echo "" if cvutil_confirm "Run automated installation? [Y/n] " y y; then sudo apt-get update sudo apt-get -y install $PACKAGES sudo php5enmod mcrypt sudo php5enmod imap sudo a2enmod rewrite sudo apache2ctl restart else echo "Aborted" 1>&2 exit 1 fi set +e } ############################################################################### function do_system_unknown() { echo "ERROR: Could not identify the required system packages." echo "" echo "If you want to add support for a new system, update 'get_system_installer()'" echo "and add a new 'do_system_*()' function." echo "" echo "If you are running Debian, install lsb-release package." exit 1 } ################################################## ## Perform system installation (if requested) if [ -n "$IS_FULL" ]; then SYSTEM_FUNC=$(get_system_installer) if [[ $SYSTEM_FUNC =~ ^[a-zA-Z0-9_]+$ ]]; then $SYSTEM_FUNC else echo "ERROR: Malformed system function: $SYSTEM_FUNC" exit 2 fi if [ ! -f "$PRJDIR/composer.json" ]; then set -e echo "[[ Clone civicrm-buildkit to $PRJDIR ]]" git clone "https://github.com/civicrm/civicrm-buildkit.git" "$PRJDIR" set +e fi fi ################################################## ## Validation check_command php required check_command mysql required check_command mysqldump required check_command git required check_command tar required check_command bzip2 required check_command gzip required check_php_ext Phar required check_php_ext SimpleXML required check_php_ext SPL required check_php_ext curl required check_php_ext date required check_php_ext json required check_php_ext libxml required check_php_ext mcrypt required check_php_ext pcre required check_php_ext pdo_mysql required check_php_ext xml required nodejs_debian_workaround if [ ! -d "$TMPDIR" ]; then mkdir -p "$TMPDIR" fi ################################################## ## Only allow one concurrent process if php $BINDIR/pidlockfile.php "$LOCKFILE" $$ 5 ; then ## we acquired lock quickly; no need to bug user with output true else OLDPID=$(cat "$LOCKFILE") echo "[[civi-download-tools: Already locked by PID $OLDPID; waiting up $LOCKTIMEOUT seconds]]" if php $BINDIR/pidlockfile.php "$LOCKFILE" $$ $LOCKTIMEOUT ; then echo "[[civi-download-tools: Lock acquired]]" else exit 1 fi fi ################################################## ## Begin execution set -e pushd $PRJDIR >> /dev/null ## Download "composer" if [ -z "$IS_FORCE" -a -f "$PRJDIR/bin/composer" ]; then echo_comment "[[Composer binary ($PRJDIR/bin/composer) already exists. Skipping.]]" else echo "[[Download composer]]" download_url "https://getcomposer.org/installer" "$TMPDIR/composer-installer" php "$TMPDIR/composer-installer" -- --install-dir="$PRJDIR/bin" mv "$PRJDIR/bin/composer.phar" "$PRJDIR/bin/composer" fi ## Download dependencies (via composer) COMPOSER_MD5=$(cat composer.json composer.lock | php -r 'echo md5(file_get_contents("php://stdin"));') touch "$TMPDIR/composer-data.md5" if [ -z "$IS_FORCE" -a "$(cat $TMPDIR/composer-data.md5)" == "$COMPOSER_MD5" ]; then echo_comment "[[composer dependencies already installed. Skipping.]]" else "$PRJDIR/bin/composer" install cat composer.json composer.lock | php -r 'echo md5(file_get_contents("php://stdin"));' > "$TMPDIR/composer-data.md5" fi ## Download dependencies (via npm) if which npm > /dev/null ; then PACKAGE_MD5=$(cat package.json | php -r 'echo md5(file_get_contents("php://stdin"));') touch "$TMPDIR/package-data.md5" if [ -z "$IS_FORCE" -a "$(cat $TMPDIR/package-data.md5)" == "$PACKAGE_MD5" -a -d "$PRJDIR/node_modules" ]; then echo_comment "[[npm dependencies already installed. Skipping.]]" else npm install cat package.json | php -r 'echo md5(file_get_contents("php://stdin"));' > "$TMPDIR/package-data.md5" fi for f in node_modules/bower/bin/bower node_modules/karma/bin/karma node_modules/jshint/bin/jshint node_modules/karma-phantomjs-launcher/node_modules/phantomjs/bin/phantomjs ; do pushd "$PRJDIR/bin" >> /dev/null toolname=$(basename $f) if [ ! -e "$toolname" ]; then ln -s ../$f $toolname fi popd >> /dev/null done fi [ ! -d "$PRJDIR/extern" ] && mkdir "$PRJDIR/extern" ## Download "civix" ## FIXME: Update civix so that it can be installed via composer as a dependency mkdir -p "$PRJDIR/extern" && touch "$PRJDIR/extern/civix.txt" if [ -z "$IS_FORCE" -a -e "$PRJDIR/bin/civix" -a -d "extern/civix" -a "$(cat $PRJDIR/extern/civix.txt)" == "$CIVIXURL" ]; then echo_comment "[[civix binary ($PRJDIR/bin/civix) already exists. Skipping.]]" else echo "[[Install civix]]" ## Cleanup [ -e app/tmp/civix ] && rm -rf app/tmp/civix [ -e extern/civix ] && rm -rf extern/civix [ -e "$TMPDIR/civix.tar.bz2" ] && rm -rf "$TMPDIR/civix.tar.bz2" mkdir -p extern/civix ## Download download_url "$CIVIXURL" "$TMPDIR/civix.tar.bz2" tar xj --strip-components 1 -C extern/civix -f "$TMPDIR/civix.tar.bz2" ## Setup a relative symlink pushd bin >> /dev/null [ -e civix ] && rm -f civix ln -s ../extern/civix/civix civix popd >> /dev/null ## Mark as downloaded echo "$CIVIXURL" > "$PRJDIR/extern/civix.txt" fi ## Download "drush8" mkdir -p "$PRJDIR/extern" && touch "$PRJDIR/extern/drush8.txt" if [ -z "$IS_FORCE" -a -e "$PRJDIR/bin/drush8" -a "$(cat $PRJDIR/extern/drush8.txt)" == "$DRUSH8URL" ]; then echo_comment "[[drush8 binary ($PRJDIR/bin/drush8) already exists. Skipping.]]" else echo "[[Install drush8]]" download_url "$DRUSH8URL" "bin/drush8" chmod +x bin/drush8 echo "$DRUSH8URL" > "$PRJDIR/extern/drush8.txt" fi ## Download "cv" mkdir -p "$PRJDIR/extern" && touch "$PRJDIR/extern/cv.txt" if [ -z "$IS_FORCE" -a -e "$PRJDIR/bin/cv" -a "$(cat $PRJDIR/extern/cv.txt)" == "$CVURL" ]; then echo_comment "[[cv binary ($PRJDIR/bin/cv) already exists. Skipping.]]" else echo "[[Install cv]]" download_url "$CVURL" "bin/cv" chmod +x bin/cv echo "$CVURL" > "$PRJDIR/extern/cv.txt" fi ## Download "hub" touch "$PRJDIR/extern/hub.txt" if [ -z "$IS_FORCE" -a -e "$PRJDIR/extern/hub/bin/hub" -a -e "$PRJDIR/bin/hub" -a "$(cat $PRJDIR/extern/hub.txt)" == "$HUBURL $HUBTAG" ]; then echo_comment "[[hub ($PRJDIR/extern/hub) already exists. Skipping.]]" elif [ -z "`which ruby`" -o -z "`which rake`" ]; then echo_comment "[[hub requires Ruby and Rake (which are unavailable). Skipping.]]" else echo "[[Install hub]]" ## Cleanup [ -e app/tmp/hub ] && rm -rf app/tmp/hub [ -e extern/hub ] && rm -rf extern/hub mkdir -p app/tmp extern/hub ## Download git clone "$HUBURL" app/tmp/hub pushd app/tmp/hub git checkout $HUBTAG popd ## Build pushd app/tmp/hub >> /dev/null PREFIX="$PRJDIR/extern/hub" rake install popd >> /dev/null ## Setup a relative symlink pushd bin >> /dev/null [ -f hub ] && rm -f hub ln -s ../extern/hub/bin/hub hub popd >> /dev/null ## Mark as downloaded echo "$HUBURL $HUBTAG" > "$PRJDIR/extern/hub.txt" fi popd >> /dev/null set +e ################################################## ## Recommendations ## ## Note: Non-fatal recommendations come at the end so that they appear as ## the last output (which is most likely to be read). check_php_ext bcmath recommended check_php_ext gd recommended check_php_ext gettext recommended check_php_ext hash recommended check_php_ext imap recommended check_php_ext intl recommended check_php_ext mbstring recommended check_php_ext mysql recommended check_php_ext openssl recommended check_php_ext session recommended check_php_ext zip recommended check_command node recommended "WARNING: Failed to locate command \"node\". NodeJS (http://nodejs.org/) is required for development of CiviCRM v4.6+." check_command npm recommended "WARNING: Failed to locate command \"npm\". NodeJS (http://nodejs.org/) is required for development of CiviCRM v4.6+." ################################################## ## Cleanup rm -f "$LOCKFILE"