LAMPInstall - Noah.org

LAMPInstall

From Noah.org

Jump to: navigation, search


Contents

LAMP Install

This script installs almost everything you need for a LAMP server. The server install directory can be set anywhere you like. The entire installation is also relocatable and can coexist with other applications and libraries. All libraries such as zlib and openssl are installed separately from the system library files, so you don't have to worry about ".so Hell".

Components:
"zlib","libpng","tiff","jpegsrc","freetype","gd","libxml2","openssl","httpd","subversion","php-5","mediawiki"

Automatic configuration enhancements

The INSTALL script will automatically configure some components with some useful defaults.

  • php.ini
    • php.ini-recommended is copied to php.ini
  • httpd.conf
    • SSL is turned on
    • a snake-oil self-signed certificate is installed (~/conf/extra/httpd-ssl.conf)
    • ~/conf/extra/httpd-default.conf is enabled.
    • PHP5 handler is turned on
    • mod_deflate is turned on for text/html text/plain text/xml
    • combined log format is enabled
    • index.php is turned on as a Directory Index

INSTALL

#!/usr/bin/env python
 
"""This builds an Apache2 web server. The server directory structure follows a
virtual server structure, so you can install the entire web server and all of
its libraries under any directory on your server. All libraries and external
tools are kept separate from the rest of the file system.
 
Most of the options are NOT set through the command line. Instead, you edit
this script. The parts that you care about are:
 
    USERS: This sets the user:group that the given component will use.
    VIRTUAL_SERVER_ROOT: This sets the root path where everything gets installed
    COMPONENTS: This sets the list of components that you want built.
 
I am not a Makefile.
 
2006 Noah Spurrier
no copyright
no license
no warranty
no refunds
public domain
 
TODO: Check for missing external applications such as: libtool, gcc, autoconf,
lex or flex, mysql includes (/usr/include/mysql/.) msgfmt/gettext (Ubuntu
require 'gettext' package be installed before building wget. This only matters
for wget dependency).
 
TODO: Add apache2/bin to LD_LIBRARY_PATH
 
Libraries have been installed in:
   /var/www/usr/local/apache2/lib
 
If you ever happen to want to link against installed libraries in a given
directory, LIBDIR, you must either use libtool, and specify the full pathname
of the library, or use the `-LLIBDIR' flag during linking and do at least one
of the following:
 
   * add LIBDIR to the `LD_LIBRARY_PATH' environment variable
     during execution
 
   * add LIBDIR to the `LD_RUN_PATH' environment variable
     during linking
 
   * use the `-Wl,--rpath -Wl,LIBDIR' linker flag
 
See any operating system documentation about shared libraries for more
information, such as the ld(1) and ld.so(8) manual pages.
 
"""
 
USERS = {'httpd':{'user':'www','group':'www'},
 'mysql':{'user':'mysql','group':'mysql'}}
# Note: Make sure that EVERY directory in the path for
# VIRTUAL_SERVER_ROOT is readable and executable by the httpd user.
# Otherwise you will get a 403 error.
VIRTUAL_SERVER_ROOT = '/var/www'
#VIRTUAL_SERVER_ROOT = os.getcwd() + '/WWW'
COMPONENTS=["zlib","libpng","tiff","jpegsrc","freetype","gd","openssl","libxml2","httpd","curl","subversion","php-5","mediawiki","webalizer"]
##########################################################################
GLOBAL_LOGFILE = None
 
import sys, os, shutil, re, time, tarfile, platform, traceback
import pexpect
from pyed import pyed
import getopt
 
def exit_with_usage ():
    print globals()['__doc__']
    os._exit(1)
 
def parse_args (options='', long_options=[]):
    try:
        optlist, args = getopt.getopt(sys.argv[1:], options+'h?', long_options+['help','h','?'])
    except Exception, e:
        print str(e)
        exit_with_usage()
    options = dict(optlist)
    if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
        exit_with_usage()
    return (options, args)
 
def main ():
    (options, args) = parse_args('v')
 
    # if args<=0:
    #     exit_with_usage()
    if '-v' in options:
        verbose_flag = True
    else:
        verbose_flag = False
 
    if platform.dist()[0]=='debian' or platform.dist()[0]=='ubuntu':
        install_prerequisites_ubuntu ()
    else:
        print "THIS SCRIPT ONLY KNOWS HOW TO INSTALL PREREQUISITES FOR UBUNTU AND DEBIAN DISTROS."
        print "ALL OTHERS ARE ON THEIR OWN. SEE THE FUNCTION install_prerequisites_ubuntu()"
        print "IN THIS SCRIPT FOR HINTS ON WHAT YOU MAY NEED TO INSTALL BY HAND."
        print "THIS SCRIPT WILL NOW CONTINUE AND HOPE FOR THE BEST."
 
    global VIRTUAL_SERVER_ROOT
    global COMPONENTS
    env={}
    env['prefix_path'] = VIRTUAL_SERVER_ROOT
    env['platform'] = platform.system().lower()
    # For some reason Apache2 MUST be installed under ./usr/local/apache2.
    env['prefix_path_httpd'] = env['prefix_path'] + '/usr/local/apache2'
    # Some build systems will not create these and expect them to already exist:
    mkdir(env['prefix_path'])
    mkdir(env['prefix_path']+'/etc')
    mkdir(env['prefix_path']+'/bin')
    mkdir(env['prefix_path']+'/include')
    mkdir(env['prefix_path']+'/lib')
    mkdir(env['prefix_path']+'/man/man1')
    cp ('WWW.ENV', env['prefix_path']+'/WWW.ENV')
    cp ('APACHECTL', env['prefix_path']+'/APACHECTL')
    chmod (env['prefix_path']+'/APACHECTL', 0774)
    cp ('PHP', env['prefix_path']+'/PHP')
    chmod (env['prefix_path']+'/PHP', 0774)
    document = pyed()
    document.read (env['prefix_path']+'/WWW.ENV')
    if document.find_first('^\s*PREFIX_PATH\s*=.*') is not None:
        document.cur_line = 'PREFIX_PATH=%(prefix_path)s' % env
        document.write(env['prefix_path']+'/WWW.ENV')
    document.read(env['prefix_path']+'/APACHECTL')
    if document.find_first('^{SOURCE_WWW.ENV}') is not None:
        document.cur_line = '. %(prefix_path)s/WWW.ENV' % env
        document.write(env['prefix_path']+'/APACHECTL')
    document.read(env['prefix_path']+'/PHP')
    if document.find_first('^{SOURCE_WWW.ENV}') is not None:
        document.cur_line = '. %(prefix_path)s/WWW.ENV' % env
        document.write(env['prefix_path']+'/PHP')
    if 'x86_64' in uname('-m'):
        print "BUILDING FOR 64-BIT LINUX"
        env['x86_64'] = '1'
    
    #
    # THIS ENVIRONMENT SHOULD MATCH WHAT IS DEFINED IN THE 'WWW.ENV' FILE.
    #
    PATH = env['prefix_path']+'/bin:'+ env['prefix_path']+'/usr/local/apache2/bin:'+env['prefix_path']+'/php/bin:'+import_env('PATH')
    SSL_BASE = env['prefix_path'] + '/ssl'
    CPPFLAGS= '-I' + env['prefix_path']+'/include'
    LD_LIBRARY_PATH = env['prefix_path']+'/lib:' + SSL_BASE+'/lib:' + import_env('LD_LIBRARY_PATH')
    # libpng needs this (can't do it with ./configure):
    LDFLAGS = '-L' + env['prefix_path']+'/lib'
    #INCLUDES = env['prefix_path']+'/include' # Using both INCLUDES and CPPFLAGS can confuse some ./configures
    if 'x86_64' in env:
        LDFLAGS = LDFLAGS + ' -L/usr/lib64 -L/usr/lib64/mysql' # This is necessary for 64-bit Linux (PHP5 build bug).
    export_env('LDFLAGS', LDFLAGS)
    export_env('PATH', PATH)
    export_env('SSL_BASE', SSL_BASE)
    export_env('CPPFLAGS', CPPFLAGS)
    export_env('LD_LIBRARY_PATH', LD_LIBRARY_PATH)
    #export_env('LDFLAGS', LDFLAGS)
    #export_env('INCLUDES', INCLUDES)
    env.update(os.environ)
    
    print "OPEN TARBALLS"
    components_build_info = process_tarballs(COMPONENTS)
 
    # normalize paths and contstruct class names from their component names
    build_root_path = pwd()
    for k,v in components_build_info.items():
        v['build_path'] = build_root_path + '/build/' + v['build_path']
        v['tar_path'] = build_root_path + '/' + v['tar_path']
        v['build_class'] = 'build_' + k.replace('-','')
 
    print "BUILD COMPONENTS"
    for c in COMPONENTS:
        print
        time.asctime()
        print "building", c
        env.update(components_build_info[c])
        b = eval (components_build_info[c]['build_class']+'(env)')
        b.build()
        print
 
    u = USERS['httpd']
    print "Setting ownership of all files under %s" % ( env['prefix_path'] )
    print "to user %s, group %s" % (u['user'], u['group'])
    chown (env['prefix_path'], u['user'], u['group'], recursive=True)
 
    print "------------------------------------------------------------------"
    print "Done!"
    print "------------------------------------------------------------------"
    print "Apache is installed under " + env['prefix_path_httpd']
    print "To start Apache:"
    print "    " + env['prefix_path_httpd'] + "/bin/apachectl start"
    print "This will also start HTTPS, but with a snakeoil self-signed cert."
    print "You should get a CA signed Cert for production use."
    print
    print "To use other Apache and PHP utilities you must first"
    print "source this environment file:"
    print "    . " + env['prefix_path'] + "/WWW.ENV"
    print "This will set the LD_LIBRARY_PATH and the PATH."
    return
 
def clean(path_list):
    """This is kind of like "make clean".
    FIXME: THIS DOES NOT WORK YET.
    """
    for f in path_list:
        rm (f)
 
def print_run_output(d):
    """This is a callback for run() to print dots every few seconds.
    """
    sys.stdout.write (".")
    sys.stdout.flush()
 
def run(cmd):
    """This is a wrapper around pexpect.run(). It adds logging to a global logfile.
    """
    global GLOBAL_LOGFILE
    print >>GLOBAL_LOGFILE, "RUNNING COMMAND"
    print >>GLOBAL_LOGFILE, cmd
    (result, exitstatus) = pexpect.run(cmd, events={pexpect.TIMEOUT:print_run_output}, timeout=5, withexitstatus=True, logfile=GLOBAL_LOGFILE)
    if exitstatus != 0:
        print >>GLOBAL_LOGFILE, "FOR THE FOLLOWING COMMAND RETURNED EXIT STATUS %s:" % (str(exitstatus))
        print >>GLOBAL_LOGFILE, "    " + str(cmd)
        print "FOR THE FOLLOWING COMMAND RETURNED EXIT STATUS %s:" % (str(exitstatus))
        print "    " + str(cmd)
    return (result, exitstatus)
 
def export_env (env_name, variable=None):
    """This puts the given variable name and value into the os.environment.
    This is kind of like "export" in a bash script.
    """
    if variable == None:
        del os.environ[env_name]
        return
    os.environ[env_name] = variable
def import_env (env_name):
    if not env_name in os.environ:
        return ''
    return os.environ[env_name]
def cd (path):
    os.chdir (path)
def uname (args='-a'):
    (result, exitstatus) = run ('uname %s' % args)
    return result
def cp (path_from, path_to):
    if path_from[-1] == '/':
        path_from = path_from[0:-1]
    if os.path.isdir(path_from):
        shutil.copytree(path_from, path_to)
    else:
        shutil.copy2(path_from, path_to)
def chown (path, user, group=None, recursive=False):
    opts = ''
    if recursive:
        opts = '-R'
    if group is not None:
        run ('chown -h %s %s:%s %s' % (opts, user, group, path))
    else:
        run ('chown -h %s %s %s' % (opts, user, path))
def chmod (path, mode):
    os.chmod (path, mode)
def ls (path='.'):
    return os.listdir(path)
def ln (target, link_name):
    run('ln -sf %s %s' % (target, link_name))
def pwd ():
    return os.getcwd()
def rm (path):
    shutil.rmtree(path)
def exit (status=0):
    sys.exit(status)
def mkdir (path):
    """This is like 'mkdir -p'.
    """
    try:
        os.makedirs(path)
    except:
        return None
 
class builder (object):
    """This is the common component builder base class.
        This does a standard "configure && make && make install" style of install.
        TODO: Does not actually do the &&. Should check exit status
        TODO: before each step.
    """
    def __init__ (self, env):
        global GLOBAL_LOGFILE
        self.prefix_path = env['prefix_path']
        self.build_path = env['build_path']
        self.env = env
        GLOBAL_LOGFILE = open('./log/'+self.__class__.__name__+'.log', 'wb')
    def build (self):
        # setup # build # express environment # tear down
        home = pwd()
        # by this point the build path has been automatically opened from the tarball.
        cd (self.build_path)
        # the run function automatically logs output to the ./log directory.
        # FIXME I should not run make install if configure or make fails...
        run ('./configure --prefix=%(prefix_path)s' % self.env)
        run ('make')
        run ('make install')
        cd (home)
 
def install_prerequisites_ubuntu ():
    """This installs packages necessary for operation under Ubuntu.
    This should fail harmlessly on non-ubuntu systems.
    TODO: Add a check for non-ubuntu systems.
    """
    global GLOBAL_LOGFILE
    GLOBAL_LOGFILE = open('./log/apt-get.log', 'wb')
    run ('apt-get -q -y install gettext') # For wget
    run ('apt-get -q -y install libxml2-dev libxml2-utils')
    run ('apt-get -q -y install build-essential autoconf sed flex bison')
    run ('apt-get -q -y install mysql-server libmysqlclient15-dev')
    
class build_zlib (builder):
    def build (self):
        # setup # build # express environment # tear down
        home = pwd()
        cd (self.build_path)
        run ('./configure --shared --prefix=%(prefix_path)s' % self.env)
        run ('make')
        run ('make install')
        cd (home)
class build_tiff (builder):
    pass
class build_libpng (builder):
    def build (self):
        home = pwd()
        cd (self.build_path)
        #run ('./autogen.sh') # Do not do this on Linux!
        run ('./configure --prefix=%(prefix_path)s' % self.env)
        run ('make')
        run ('make install')
        cd (home)
class build_jpegsrc (builder):
    def build (self):
        home = pwd()
        cd (self.build_path)
        if 'x86_64' in self.env:
            cp ('/usr/share/libtool/config.sub', '.') # This is necessary for 64-bit Linux.
            cp ('/usr/share/libtool/config.guess', '.')
        run ('./configure --prefix=%(prefix_path)s --enable-shared --enable-static'%self.env)
        run ('make')
        run ('make install')
        cd (home)
class build_freetype (builder):
    pass
class build_gd (builder):
    pass
class build_libxml2 (builder):
    def build (self):
        home = pwd()
        cd (self.build_path)
        configure = """./configure --prefix=%(prefix_path)s \
            --with-zlib=%(prefix_path)s""" % self.env
        run (configure)    
        run ('make')
        run ('make install')
        cd (home)
class build_wget (builder):
    pass
class build_openssl (builder):
    def build (self):
        home = pwd()
        cd (self.build_path)
        cmd = './config -fPIC zlib shared no-threads no-asm -L'+ self.env['prefix_path']+'/lib' + ' --openssldir=%(SSL_BASE)s' % self.env
        run (cmd)
        run ('make')
        run ('make install')
        cd (home)
class build_mediawiki(builder): 
    def build (self):
        home = pwd()
        u = USERS['httpd']
        cp (self.build_path, self.env['prefix_path_httpd'] + '/htdocs/wiki')
        chown (self.env['prefix_path_httpd'] + '/htdocs/wiki', u['user'], u['group'], recursive=True)
        chmod (self.env['prefix_path_httpd'] + '/htdocs/wiki', 0774)
        cd (home)
class build_mysql (builder):
    def build (self):
        create_user_and_group('mysql')
        u = USERS['mysql']
        home = pwd()
        cd (self.build_path)
        run ('./configure --prefix=%(prefix_path)s/mysql'%self.env)
        run ('make')
        run ('make install')
        cp ('support-files/my-medium.cnf','%(prefix_path)s/etc/my.cnf'%self.env)
        cd ('%(prefix_path)s/mysql/bin'%self.env)
        #run ('./bin/mysql_install_db --user=mysql --basedir=%(prefix_path)s/mysql'%self.env)
        run ('./mysql_install_db --user=mysql --basedir=%(prefix_path)s/mysql'%self.env)
        cd ('%(prefix_path)s/mysql'%self.env)
        run ('chown -R root .')
        run ('chown -R %s var' % (u['user']))
        run ('chgrp -R %s .' % (u['group']))
        cd (home)
class build_webalizer (builder):
    def build (self):
        home = pwd()
        cd (self.build_path)
        configure = """./configure --prefix=%(prefix_path_httpd)s \
            --with-gd=%(prefix_path)s/include --with-gdlib=%(prefix_path)s/lib"""
        run (configure)
        run ('make')
        run ('make install')
        cd (home)
 
class build_httpd (builder):
    """This will install Apache2 with SSL.
        It will create and activate a self-signed snakeoil certificate.
        It will update the User and Group that Apache2 runs as.
    """
    def build (self):
        create_user_and_group('httpd')
        u = USERS['httpd']
        home = pwd()
        cd (self.build_path)
        configure = """./configure --prefix=%(prefix_path_httpd)s \
            --enable-so \
            --enable-mime-magic \
            --enable-rewrite \
            --enable-vhost-alias \
            --enable-headers \
            --with-z=%(prefix_path)s \
            --enable-deflate \
            --enable-dav \
            --enable-dav-fs \
            --enable-ssl --with-ssl=%(SSL_BASE)s""" % self.env
        run (configure)
        run ('make')
        run ('make install')
        cd (home)
        # make a snakeoil self-signed cert (X.509 Certificate)
        cd (self.env['prefix_path_httpd'] + '/conf')
        run ('%(prefix_path)s/ssl/bin/openssl req -x509 -batch -newkey rsa:1024 -nodes -keyout server.key -out server.crt' % self.env)
        # enable SSL in the httpd.conf
        # update User and Group in httpd.conf
 
        # edit Apache's envvars so that apachectl will automatically source environment.
        conf_filename = self.env['prefix_path_httpd'] + '/bin/envvars'
        conf = pyed ()
        conf.read (conf_filename)
        conf.find_last()
        conf.append('. ' + self.env['prefix_path']+'/WWW.ENV')
        conf.write(conf_filename)
 
        conf_filename = self.env['prefix_path_httpd'] + '/conf/httpd.conf'
        conf = pyed ()
        conf.read (conf_filename)
        # Turn on indexes
        conf.find_first(r"<Directory />")
        if conf.find_next(r"\w*Options .*"):
            conf.append("    Options +ExecCGI")
            conf.append("    Options +FollowSymLinks")
            conf.append("    Options +Indexes")
            conf.append("    IndexOptions FancyIndexing")
            conf.append("    IndexOptions NameWidth=*")
            conf.append("    IndexOptions SuppressDescription")
            conf.append("    IndexOptions FoldersFirst")
            conf.append("    #IndexOptions ScanHTMLTitles")
        # Allow .htaccess files to work
        conf.find_first(r"<Directory />")
        if conf.find_next(r"\w*AllowOverride None"):
            conf.cur_line = "    AllowOverride All"
        # turn on SSL
        if conf.find_first('#Include conf/extra/httpd-ssl.conf') is not None:
            conf.cur_line = 'Include conf/extra/httpd-ssl.conf'
        # turn on default settings
        if conf.find_first('#Include conf/extra/httpd-default.conf')