Basic Drupal Installation StackScript

by linode
314 deployments · 131 still active · last rev. 11 months ago

Installs Drupal 8 or Drupal 7 with your typical LAMP stack. This would be Apache, PHP, MySQL on top of Ubuntu or Debian.

- We recommend a minimum disk image size of 1500MB.

Compatible with: Debian 8, Ubuntu 14.04 LTS
						#!/usr/bin/python
#
# Basic Drupal Installation StackScript
#   StackScript written by Ricardo N Feliciano <rfeliciano@linode.com>
#   Version 1.2.4.0
#
#   Notes:
#
#       - Deploy your distro with at least 1500MB of space.
#
## Standard Deployment Variables
#
# <UDF name="linux_user" label="Linux Username" example="Username to log into your Linode with. We will not be using 'root'." />
# <UDF name="linux_user_password" label="Linux User's Password" example="The password to log into the Linode with." />
# <UDF name="linux_user_pubkey" default="" label="Linux User's public SSH key" example="Public SSH key for passwordless logins" />
# <UDF name="db_root_password" label="Database root password" />
# <UDF name="completion_email_address" label="My email address" example="We'll send an email with further instructions to this address when installation is completed. Check your spam folder if neccessary." />
#
## Drupal Configuration
#
# <UDF name="drupal_version" label="Drupal Version" oneof="Latest Drupal 8,Latest Drupal 7" />
# <UDF name="drupal_db_name" label="Drupal Database Name" />
# <UDF name="drupal_db_user" label="Drupal Database User" />
# <UDF name="drupal_db_password" label="Drupal Database Password" />
#
### Imports
#
import fcntl
import logging
import os
import platform
import socket
import struct
import subprocess
import sys
import time

# Sanity check
if( os.path.exists( "/var/log/stackscript.log" )):
	sys.exit( 1 ) # This script has already ran, exit.


### Initiate logging
#
# Logs the StackScript specified output. Default logging level is `INFO` however you can change it
# to `DEBUG` for much more in-depth log output.
logging.basicConfig( filename="/var/log/stackscript.log", level=logging.INFO )
logging.info( "Logging has started." )


####################################################################################################
#### Variables
###
######
distro              = {}
linuxUser           = os.environ[ 'LINUX_USER' ]
linuxUserPasswd     = os.environ[ 'LINUX_USER_PASSWORD' ]
linuxUserPubkey     = os.environ[ 'LINUX_USER_PUBKEY' ]
dbRootPasswd        = os.environ[ 'DB_ROOT_PASSWORD' ]
emailAddress        = os.environ[ 'COMPLETION_EMAIL_ADDRESS' ]

# Drupal Configuration
drupalVersion       = os.environ[ 'DRUPAL_VERSION' ]
drupalDBName        = os.environ[ 'DRUPAL_DB_NAME' ]
drupalDBUser        = os.environ[ 'DRUPAL_DB_USER' ]
drupalDBPasswd      = os.environ[ 'DRUPAL_DB_PASSWORD' ]


####################################################################################################
#### Functions
###
######
####
## Installs the greatest web server in the world. Optionally restarts the service.
#
#   @restart - boolean - whether or not Apache should be restarted upon completion.
##
def apacheInstall( restart = True ):

    if( distro[ 'group' ] == 1 ):

        logging.debug( "Started: Installing Apache on a Debian/Ubuntu system." )
        os.system( "apt-get -y install apache2" )
        time.sleep( 1 ) # There's been hanging issues, sleeping seems to help
        os.system( "a2enmod alias" )
        os.system( "a2enmod rewrite" )
        os.system( "a2enmod status" )
        os.system( "a2dissite 000-default" ) # Ubuntu 13.10 and after
        if( distro[ 'name' ].upper() == "DEBIAN" and distro['version'] >= 8 ):
            os.system( "systemctl restart apache2.service" ) if restart else None
        else:
            os.system( "service apache2 restart" ) if restart else None
        time.sleep( 1 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed: Installing Apache on a Debian/Ubuntu system." )
    elif( distro[ 'group' ] == 2 ):

        logging.debug( "Started: Installing Apache on a CentOS/Fedora system." )
        os.system( "yum -y install httpd" )
        time.sleep( 1 ) # There's been hanging issues, sleeping seems to help
        os.system( "echo \"LoadModule alias_module modules/mod_alias.so\" >> /etc/httpd/conf.httpd.conf" )
        os.system( "echo \"LoadModule rewrite_module modules/mod_rewrite.so\" >> /etc/httpd/conf.httpd.conf" )
        os.system( "echo \"LoadModule status_module modules/mod_status.so\" >> /etc/httpd/conf.httpd.conf" )
        os.system( "systemctl enable httpd.service" ) # Start Apache on boot
        os.system( "systemctl restart httpd.service" ) if restart else None
        time.sleep( 1 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed: Installing Apache on a CentOS/Fedora system." )
    else:

        logging.error( "This distro is not supported." )


####
## Detects what distro we're running. Uses Python's `platform` modules however mades the info easier
#   for our script to read.
##
def detectDistro( distro ):

    logging.debug( "Started: Detecting Distribution Information." )
    distroInfo = platform.linux_distribution()
    distro[ 'name' ] = distroInfo[ 0 ]
    distro[ 'version' ] = distroInfo[ 1 ]
    distro[ 'codename' ] = distroInfo[ 2 ]

    if( distroInfo[ 0 ].upper() == "UBUNTU" or distroInfo[ 0 ].upper() == "DEBIAN" ):

        distro[ 'group' ] = 1
    elif( distroInfo[ 0 ].upper() == "CENTOS LINUX" or distroInfo[ 0 ].upper() == "FEDORA" ):

        distro[ 'group' ] = 2
    else:

        logging.warn( "Unable to determine a supported distribution. Will assume a Debian/Ubuntu based distro." )
        distro[ 'group' ] = 1

    logging.debug( "Completed: Detecting Distribution Information." )


####
## Installs a Database Management System. On Debian and Ubuntu this would be MySQL, on CentOS and
#   Fedora this would be MariaDB.
#
#   @rootPassword - the root password to set for the database.
##
def dbmsInstall( rootPassword ):

    if( distro[ 'group' ] == 1 ):

        logging.debug( "Started: Installing MySQL on a Debian/Ubuntu system." )
        os.system( 'echo "mysql-server mysql-server/root_password password ' + rootPassword + '" | debconf-set-selections' )
        os.system( 'echo "mysql-server mysql-server/root_password_again password ' + rootPassword + '" | debconf-set-selections' )
        time.sleep( 5 ) # There's been hanging issues, sleeping seems to help
        os.system( "apt-get -y install mysql-server" )
        logging.debug( "Completed: Installing MySQL on a Debian/Ubuntu system." )
    elif( distro[ 'group' ] == 2 ):

        logging.debug( "Started: Installing MariaDB on a CentOS/Fedora system." )
        time.sleep( 3 ) # There's been hanging issues, sleeping seems to help
        os.system( "yum -y install mariadb-server" )
        time.sleep( 2 ) # There's been hanging issues, sleeping seems to help
        os.system( "systemctl start mariadb.service" ) # Start MariaDB on boot
        os.system( "systemctl enable mariadb.service" ) # Start MariaDB on boot
        os.system( "mysqladmin -u root password " + rootPassword )
        logging.debug( "Completed: Installing MariaDB on a CentOS/Fedora system." )
    else:

        logging.error( "This distro is not supported." )


####
## Installs Drupal.
#
#   @version - string - the version of Drupal to install. Currently 7 or 8.
#   @virtualHost - string - the hostname that will be used for the site.
#   @rootPath - string - where the files should go. The first half of the DocumentRoot.
##
def drupalInstall( version, virtualHost, rootPath = '/srv/www' ):

    logging.debug( "Started: Installing Drupal." )
    vhostRoot = rootPath + "/" + virtualHost
    logging.debug( "Creating vhost document root and related directories." )
    os.system( "mkdir -p " + vhostRoot + "/logs" )
    os.system( "mkdir -p " + vhostRoot + "/backups" )

    if( version == "Latest Drupal 7" ):

		logging.debug( "Downloading the latest Drupal 7 release and saving it to " + vhostRoot )
		#os.system( "wget -P " + vhostRoot + " http://ftp.drupal.org/files/projects/$(wget -O- https://www.drupal.org/node/3060/release | egrep -o 'drupal-7\.[0-9\.]+.tar.gz' | sort -V  | tail -1)" )
		os.system( "wget -P " + vhostRoot + " http://ftp.drupal.org/files/projects/drupal-7.41.tar.gz" )
    elif( version == "Latest Drupal 8" ):

		logging.debug( "Downloading the latest Drupal 8 release and saving it to " + vhostRoot )
		#os.system( "wget -P " + vhostRoot + " http://ftp.drupal.org/files/projects/$(wget -O- https://www.drupal.org/node/3060/release | egrep -o 'drupal-8\.[0-9\.-]+.tar.gz' | sort -V  | tail -1)" )
		os.system( "wget -P " + vhostRoot + " http://ftp.drupal.org/files/projects/drupal-8.0.1.tar.gz" )
    else:

        logging.error( "Invalid Drupal version. Quiting." )

    logging.debug( "Untarring." )
    if( distro[ 'name' ].upper() == "FEDORA" ): # Fedora doesn't have Tar for some reason
        os.system( "yum -y install tar" )
    os.system( "tar xfz " + vhostRoot + "/drupal-*.tar.gz -C " + vhostRoot )
    logging.debug( "Deleting the tarball." )
    os.system( "rm -r " + vhostRoot + "/drupal-*.tar.gz" )
    logging.debug( "Renaming the Drupal directory as `public_html`." )
    os.system( "mv " + vhostRoot + "/drupal-* " + vhostRoot + "/public_html" )
    # Create a new settings.php file.
    os.system( "cp " + vhostRoot + "/public_html/sites/default/default.settings.php " + vhostRoot + "/public_html/sites/default/settings.php" )
    os.system( "cp " + vhostRoot + "/public_html/sites/default/default.services.yml " + vhostRoot + "/public_html/sites/default/services.yml" )
    os.system( "chown -R " + linuxUser + ":" + linuxUser + " /home/" + linuxUser )
    os.system( "chmod 750 /home/" + linuxUser )
    os.system( "chmod -R 770 " + vhostRoot + "/public_html" )

    if( distro[ 'group' ] == 1 ):

        os.system( "adduser www-data " + linuxUser )
    elif( distro[ 'group' ] == 2 ):

        os.system( "usermod -a -G " + linuxUser + " apache" )

    # Create dbms database and user for Drupal
    os.system( "echo \"CREATE DATABASE " + drupalDBName + "\" | mysql -u root -p" + dbRootPasswd )
    logging.debug( "Created database." )
    os.system( "echo \"CREATE USER '" + drupalDBUser + "'@'localhost' IDENTIFIED BY '" + drupalDBPasswd + "';\" | mysql -u root -p" + dbRootPasswd )
    logging.debug( "Created database user." )
    os.system( "echo \"GRANT ALL PRIVILEGES ON " + drupalDBName + ".* TO '" + drupalDBUser + "'@'localhost';\" | mysql -u root -p" + dbRootPasswd )
    logging.debug( "Granted user access to the database." )
    os.system( "echo \"FLUSH PRIVILEGES;\" | mysql -u root -p" + dbRootPasswd )
    logging.debug( "Flushed privileges." )
    drupalDBConfig = """\$databases[ '\\''default'\\'' \]\[ '\\''default'\\'' ] = array(\\n\\t'\\''database'\\'' => '\\''""" + drupalDBName + """'\\'',\\n\\t'\\''username'\\'' => '\\''""" + drupalDBUser + """'\\'',\\n\\t'\\''password'\\'' => '\\''""" + drupalDBPasswd + """'\\'',\\n\\t'\\''host'\\'' => '\\''localhost'\\'',\\n\\t'\\''driver'\\'' => '\\''mysql'\\'',\\n\\t'\\''prefix'\\'' => '\\'''\\''\\n);"""
    if( version == "Latest Drupal 7" ):
        os.system( "sed -i 's/$databases = array();/" + drupalDBConfig + "/' " + vhostRoot + "/public_html/sites/default/settings.php" )
    generateVHOST( virtualHost, vhostRoot )
    logging.debug( "Completed: Installing Drupal." )


####
## Creates an Apache vhost file.
#
#   @servername - string - ServerName field for Apache.
#   @vhostRoot - string - Main directory for site. Parent of its public_html directory.
#   @restart - boolean - Restart Apache if needed. Defaults to True.
##
def generateVHOST( serverName, vhostRoot, restart = True ):

    logging.debug( "Started: Generating VHOST file." )

    if( distro[ 'name' ].upper() == "DEBIAN" and distro['version'] < 8 ):
        apache24Line = ""
    else:
        apache24Line = "\n\t\tRequire all granted"

    # server admin address should change if a notify email is included in this StackScript
    vhostFile = """<VirtualHost *:80>\n\tServerAdmin root@localhost\n\tServerName """ + serverName + """\n\n\tDirectoryIndex index.html index.php\n\tDocumentRoot """ + vhostRoot + """/public_html\n\n\tLogLevel warn\n\tErrorLog """ + vhostRoot + """/logs/error.log\n\tCustomLog """ + vhostRoot + """/logs/access.log combined\n\n\t<Directory "/home/""" + linuxUser + """/""" + serverName + """/public_html">\n\t\tOptions +Indexes\n\t\tAllowOverride All\n\t\tOrder allow,deny\n\t\tAllow from all""" + apache24Line + """\n\t</Directory>\n</VirtualHost>"""
    logging.debug( "Created variable with file contents." )

    if( distro[ 'group' ] == 1 ):

        logging.debug( "Creating the vhost file on a Debian/Ubuntu system." )
        fo = open( "/etc/apache2/sites-available/" + serverName + ".conf", "w" )
        fo.write( vhostFile )
        fo.close()
        os.system( "a2ensite " + serverName + ".conf" )
        logging.debug( "Enabled vhost on a Debian/Ubuntu system." )
        if( distro[ 'name' ].upper() == "DEBIAN" and distro['version'] >= 8 ):
            os.system( "systemctl restart apache2.service" ) if restart else None
        else:
            os.system( "service apache2 restart" ) if restart else None
    elif( distro[ 'group' ] == 2 ):

        logging.debug( "Creating the vhost file on a CentOS/Fedora system." )
        fo = open( "/etc/httpd/conf.d/vhost.conf", "w" )
        fo.write( vhostFile )
        fo.close()
        logging.debug( "Enabled vhost on a CentOS/Fedora system." )
        os.system( "systemctl restart httpd.service" ) if restart else None
    else:

        logging.error( "This distro is not supported." )

    logging.debug( "Completed: Generating VHOST file." )


####
## Returns the IPv4 attached to the interface.
#
#   @ifname - string - Interface name to use. i.e. eth0
##
def getInterfaceAddress( ifname ):

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa( fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15])
    )[20:24])


####
## Returns the reverse DNS value from the given IP address.
#
#   @IP - string - IP address to use. If not provided, assumes server's own FQDN.
##
def getReverseDNS( IP = None ):

    logging.debug( "Started: getReverseDNS." )

    if( IP == None ):

        logging.debug( "getReverseDNS ran with an IP address" )
        #return socket.getfqdn() # Assuming this host
        IP = getInterfaceAddress( "eth0" ) # get rDNS for eth0 IP

    logging.debug( "IP choosen was: " + IP )
    logging.debug( "Completed: getReverseDNS." )
    return socket.gethostbyaddr( IP )[ 0 ]


####
## Initializes the network. Mostly a cheap way to get default DHCP values from Linode to load.
##
def initNetwork():

    if( distro[ 'group' ] == 1 ):

        logging.debug( "Started: Initializing the network on a Debian/Ubuntu system." )
        os.system( "ifdown eth0 && ifup eth0" ) # This pulls in the default Linode rDNS for us
        logging.debug( "Completed: Initializing the network on a Debian/Ubuntu system." )
    elif( distro[ 'group' ] == 2 ):

        logging.debug( "Started: Initializing the network on a CentOS/Fedora system." )
        os.system( "hostnamectl set-hostname " + getReverseDNS() ) # This pulls in the default Linode rDNS for us
        os.system( "systemctl restart network.service" )
        logging.debug( "Completed: Initializing the network on a CentOS/Fedora system." )
    else:

        logging.error( "This distro is not supported." )


####
## Installs PHP (duh). We'll just being using regular PHP, meaning via Apache.
#
#   @IP - string - IP address to use. If not provided, assumes server's own FQDN.
##
def phpInstall():

    if( distro[ 'group' ] == 1 ):

        logging.debug( "Started: Installing PHP on a Debian/Ubuntu system." )
        os.system( "apt-get -y install php5 php5-mysql php5-gd php5-mcrypt php5-cli" )
        time.sleep( 5 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed: Installing PHP on a Debian/Ubuntu system." )
    elif( distro[ 'group' ] == 2 ):

        logging.debug( "Started: Installing PHP on a CentOS/Fedora system." )
        os.system( "yum -y install php php-mysql php-gd php-mcrypt php-cli php-mbstring php-xml" )
        time.sleep( 5 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed: Installing PHP on a CentOS/Fedora system." )
    else:

        logging.error( "This distro is not supported." )

####
## Installs Postfix, only listens on localhost. This is so this StackScript as well as Drupal can
#   send emails as needed.
#
#   @restart - boolean - If True, postfix will be restart upon completion. Defaults to True.
##
def postfixInstall( restart = True ):

    if( distro[ 'group' ] == 1 ):

        logging.debug( "Started: Installing postfix on a Debian/Ubuntu system" )
        os.system( "echo \"postfix postfix/main_mailer_type select Internet Site\" | debconf-set-selections" )
        os.system( "echo \"postfix postfix/mailname string localhost\" | debconf-set-selections" )
        os.system( "echo \"postfix postfix/destinations string localhost.localdomain, localhost\" | debconf-set-selections" )
        os.system( "apt-get -y install postfix" )
        time.sleep( 5 ) # There's been hanging issues, sleeping seems to help
        os.system( "/usr/sbin/postconf -e \"inet_interfaces = loopback-only\"" )
        if( distro[ 'name' ].upper() == "DEBIAN" and distro['version'] >= 8 ):
            os.system( "postfix reload" )
        else:
            os.system( "service postfix restart" ) if restart else None
        time.sleep( 3 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed: Installing postfix on a Debian/Ubuntu system" )
    elif( distro[ 'group' ] == 2 ):

        logging.debug( "Started: Installing postfix on a CentOS/Fedora system" )
        os.system( "yum -yq install postfix" )
        time.sleep( 3 ) # There's been hanging issues, sleeping seems to help
        os.system( "/usr/sbin/postconf -e \"inet_interfaces = loopback-only\"" )
        os.system( "systemctl restart postfix.service" ) if restart else None
        time.sleep( 3 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed:Installing postfix on a CentOS/Fedora system" )
    else:

        logging.error( "This distro is not supported." )


####
## Updates the system's repositories.
##
def systemUpdate():

    if( distro[ 'group' ] == 1 ):

        logging.debug( "Started: Updating a Debian/Ubuntu system." )
        os.system( "apt-get update" )
        os.system( "apt-get -y upgrade" )
        time.sleep( 5 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed: Updating a Debian/Ubuntu system." )
    elif( distro[ 'group' ] == 2 ):

        logging.debug( "Started: Updating a CentOS/Fedora system." )
        os.system( "yum -yq upgrade" )
        time.sleep( 5 ) # There's been hanging issues, sleeping seems to help
        logging.debug( "Completed: Updating a CentOS/Fedora system." )
    else:

        logging.error( "This distro is not supported." )


####################################################################################################
#### Main logic
###
######

# Detect Distro
detectDistro( distro )
logging.info( "Detected " + distro[ 'name' ] + " " + distro[ 'version' ] + "." )

# Initialize Network
#initNetwork()
#time.sleep( 9 ) # Let the network catch up
#logging.info( "Network initialized." )

# Update packages
systemUpdate()
logging.info( "Updated repositories." )

postfixInstall()
hostname = getReverseDNS()

# Configure a regular user account
if( distro[ 'group' ] == 1 ):

    os.system( "useradd -m " + linuxUser + " -s /bin/bash" )
    logging.debug( "Created new Linux user with a home directory." )
else:

    os.system( "useradd -m " + linuxUser + " -s /bin/bash" )
    logging.debug( "Created new Linux user with a home directory." )

os.system( "echo '" + linuxUser + ":" + linuxUserPasswd + "' | chpasswd" )
logging.debug( "Set the user's password." )
logging.info( "The user `" + linuxUser + "` has been created successfully." )

# Install SSH key
if( linuxUserPubkey != "" ):

    os.system( "mkdir /root/.ssh" )
    os.system( "echo '" + linuxUserPubkey + "' > /root/.ssh/authorized_keys" )
    os.system( "mkdir /home/" + linuxUser + "/.ssh" )
    os.system( "echo '" + linuxUserPubkey + "' > /home/" + linuxUser + "/.ssh/authorized_keys" )
    logging.info( "Added the SSH key to both root and " + linuxUser )

# Install Apache
apacheInstall( False )
logging.info( "Installed Apache." )

# Install a Relational Database Management System (RDBMS)
dbmsInstall( dbRootPasswd )
logging.info( "Installed a DBMS." )

# Install PHP
phpInstall()
logging.info( "Installed PHP." )

# Install Drupal
drupalInstall( drupalVersion, hostname, "/home/" + linuxUser )
logging.info( "Installed Drupal." )

# Restart everything
if( distro[ 'group' ] == 1 ):

    if( distro[ 'name' ].upper() == "DEBIAN" and distro['version'] >= 8 ):
        os.system( "systemctl restart apache2.service" )
        os.system( "systemctl restart mysql.service" )
    else:
        os.system( "service apache2 restart" )
        os.system( "service mysql restart" )

    logging.debug( "Restarting services from a Debian/Ubuntu system." )
elif( distro[ 'group' ] == 2 ):

    os.system( "systemctl restart httpd.service" )
    os.system( "systemctl restart mariadb.service" )
    logging.debug( "Restarting services from a CentOS/Fedora system." )

else:
    logging.eror( "This distro is not supported." )

# Build email
if( distro[ 'group' ] == 1 ):

    os.system( "apt-get -y install mailutils" )
elif( distro[ 'group' ] == 2 ):

    os.system( "yum -y install mailx" )
logging.info( "Installed a simple mail command." )
completionEmail = """Linode User,

Your Drupal installation has completed. You can set up your site here:

 - http://""" + hostname + """/install.php.

Enjoy using Drupal!

-The Linode Team"""
os.system( "echo \"" + completionEmail + "\" | mail -s \"Basic Drupal Install StackScript Completed\" " + emailAddress )

# Completion
logging.info( "Restarted neccessary services." )
logging.info( "Sent completion email to " + emailAddress )
logging.info( "The StackScript has been completed." )