WordPress Installer

WordPress Installer

This is my biggest bash script yet! It will fully install WordPress from a shell command and add a number of security measures to boot.

It allows you to enter the database connection details, downloads the latest version in your language, file and directory permissions are sorted, it handles the random keys,  all the basic WordPress administrator information (if you have WP Cli installed) and a few other things.

  • Install and update WordPress in your own language to web root or a sub directory
  • Handles file and directory permissions
  • Removes licence.txt and readme.html
  • Checks for a working database connection
  • Adds a random database prefix
  • Generates random username and password for administrators
  • Set the maximum upload and post limits
  • Creates the .htaccess to allow for URL rewriting, adds browser caching and some basic security measures
  • Uninstall functionality
  • Besides the setup part, this will install WordPress without requiring WP-Cli
#!/bin/sh

#################################################################################
#                                                                               #
#  WORDPRESS INSTALLER                                                          #
#                                                                               #
#  Coded by Noah Hearle, Design Extreme                                         #
#  http://designextreme.com                                                     #
#                                                                               #
#  Created: 2015/08/01                                                          #
#  Modified: 2015/08/06                                                         #
#                                                                               #
#  Post your comments at:                                                       #
#  https://blog.nahoo.co.uk/wordpress-installer/                             #
#                                                                               #
#  Usage: sh ./wordpress_install.sh <username>[/<subdirectory>]                 #
#       [<database-name> <database-username>] <database-password> [<language>]  #
#                                                                               #
#################################################################################


## SETTINGS ##

upload_max_filesize='64M'
language='en_GB'
wordpress_download_url='https://en-gb.wordpress.org/wordpress-latest-en_GB.zip'
wp_cli=$(type -p "wp")
email_regex="^[a-z0-9!#\$%&'*+/=?^_\`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\$"
database_name_suffix='_db'
database_username_suffix='_user'


## ARGUMENTS ##

username=$1
sub_directory=''
uninstall=false
salt=''
random_password=''
database_prefix=''
database_host=localhost
directory=''
wordpress_url=''

function validate_url() {
    if [[ `wget -S --spider $1 2>&1 | grep -Ei '200 OK'` ]]; then
        echo 'true';
    else
        echo 'false';
    fi
}

function generate_random_string() {
    local l=$1
    local letters=$2
    if [ ! -n "$letters" ]; then
        letters='A-Za-z0-9_\[\]()#@-'
    fi
    [ "$l" == "" ] && l=16
    tr -dc $letters < /dev/urandom | head -c ${l} | xargs
}

if [ ! -n "$username" ] || [ "$username" == "" ]; then
    echo -e "\e[1;31mError:\e[0m Please enter at least a username."
    exit;
fi

if [[ $username =~ ([/]home[0-9a-z]*[/])?([0-9a-z_-]+)[/]?(public_html|www)?[/]([0-9a-z_-]+) ]]; then
    username=${BASH_REMATCH[2]}
    sub_directory=${BASH_REMATCH[4]}
elif [[ $username =~ ([/]home.*[/])([0-9a-z_-]+) ]]; then
    username=${BASH_REMATCH[2]}
fi

if [ $username != 'help' ] && [ -n $2 ] && [ "$2" == 'uninstall' ]; then
    uninstall=true
elif [ $username != 'help' ] && [ -n "$3" ] && [ "$3" != "" ] && [ -n "$4" ] && [ "$4" != "" ]; then
    database_name=$2
    database_user=$3
    database_pass=$4
    mysql_connection=$(php -r "\$conn = @mysqli_connect('$database_host', '$database_user', '$database_pass'); if (\$conn && @mysqli_select_db(\$conn, '$database_name')) { echo 1; } else { echo 0; }")

    if [[ $mysql_connection == 0 ]]; then
        echo -e "\e[1;31mError:\e[0m Unable to connect to the database using MySQL: $database_name."
        exit;
    fi

    if [ -n "$5" ] && [ "$5" != "" ]; then
        language=$5
    else
        echo 'Please enter the installation language:';
        read -i $language -e language
    fi
elif [ $username != 'help' ] && [ ! -n "$4" ] || [ "$4" == "" ]; then
    echo 'Please enter the database name:';
    read -i "${username}${database_name_suffix}" -e database_name
    echo 'Please enter the database username:';
    read -i "${username}${database_username_suffix}" -e database_user
    echo 'Please enter the database password:';
    read database_pass
    mysql_connection=$(php -r "\$conn = @mysqli_connect('$database_host', '$database_user', '$database_pass'); if (\$conn && @mysqli_select_db(\$conn, '$database_name')) { echo 1; } else { echo 0; }")

    if [[ $mysql_connection == 0 ]]; then
        echo -e "\e[1;31mError:\e[0m Unable to connect to the database using MySQL: $database_name."
        exit;
    fi

    if [ -n "$3" ] && [ "$3" != "" ]; then
        language=$3
    else
        echo 'Please enter the installation language:';
        read -i $language -e language
    fi
else
    echo -e '\e[1;44mUsage:\e[0m sh ./wordpress_install.sh <username>[/<subdirectory>] [<database-name> <database-username>] <database-password> [<language>]';
    exit;
fi

if [ ! -d /home/$username/public_html/ ]; then
    echo -e "\e[1;31mError:\e[0m Unable to locate username: $username."
    exit;
fi

if [ -n "$sub_directory" ] && [ "$sub_directory" != "" ]; then
    if [ ! -d /home/$username/public_html/$sub_directory/ ]; then
        echo -e "\e[1;31mError:\e[0m Unable to locate sub directory: $sub_directory."
        exit;
    fi
    directory="/home/$username/public_html/$sub_directory/"
else
    directory="/home/$username/public_html/"
fi

cd $directory;

if [ -f wp-config.php ] && [ $uninstall == false ]; then
    echo -e '\e[1mInstall Failed\e[0m'
    if [ -n "$sub_directory" ] && [ "$sub_directory" != "" ]; then
        echo -e "\e[1;31mError:\e[0m WordPress installation exists for username: $username, sub directory: $sub_directory."
        exit;
    fi
    echo -e "\e[1;31mError:\e[0m WordPress installation exists for username: $username."
    exit;
elif [ ! -f wp-config.php ] && [ $uninstall == true ]; then
    echo -e '\e[1mUninstall Failed\e[0m';
    if [ -n "$sub_directory" ] && [ "$sub_directory" != "" ]; then
        echo -e "\e[1;31mError:\e[0m Cannot find an existing WordPress installation for username: $username, sub directory: $sub_directory."
        exit;
    fi
    echo -e "\e[1;31mError:\e[0m Cannot find an existing WordPress installation for username: $username."
    exit;
fi

if [ $uninstall == true ]; then
    rm -Rf wp-*;
    rm -f xmlrpc.php;
    rm -f license.txt;
    rm -f readme.html;
    rm -f index.php;

    touch .htaccess;
    touch index.php;
    chown $username.$username .htaccess;
    chown $username.$username index.php;

    cat >.htaccess <<EOL
<IfModule mod_rewrite.c>
  RewriteEngine On

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^.*$ /index.php [L]
</IfModule>
EOL

    cat >index.php <<EOL
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
</body>
</html>
EOL
    echo -e '\e[1mUninstall Successful\e[0m'
    if [ -n "$sub_directory" ] && [ "$sub_directory" != "" ]; then
        echo -e "\e[92mSuccess:\e[0m Removed files for the existing WordPress installation for username: $username, sub directory: $sub_directory."
        exit;
    fi
    echo -e "\e[92mSuccess:\e[0m Removed files for the existing WordPress installation for username: $username."
    exit;
fi

if [[ `validate_url "$wordpress_download_url" >/dev/null` == false ]]; then
    echo -e '\e[1mInstall Failed\e[0m'
    echo -e "\e[1;31mError:\e[0m Cannot find the WordPress installation file at: $wordpress_download_url."
    exit;
fi

database_prefix="wp$(generate_random_string 4 a-z)_"
wget --progress=bar:force "$wordpress_download_url" -O wordpress-latest.zip;

if [ ! -f "${directory}wordpress-latest.zip" ]; then
    echo -e "\e[1;31mError:\e[0m Unable to locate installation file: ${directory}wordpress-latest.zip."
    exit;
fi

unzip -q wordpress-latest.zip;

if [ ! -d "${directory}wordpress/" ]; then
    echo -e "\e[1;31mError:\e[0m Unable to locate extracted files: ${directory}wordpress/."
    exit;
fi

rm -f wordpress-latest.zip;
chown -R $username.$username wordpress/;
mv -f wordpress/* ./;
rm -Rf wordpress/;
mkdir wp-content/cache/;
mkdir wp-content/uploads/;
mkdir wp-content/w3tc-config/;
chown -R nobody.nobody wp-content/cache/;
chown -R nobody.nobody wp-content/uploads/;
chown -R nobody.nobody wp-content/w3tc-config/;
rm -f license.txt;
rm -f readme.html;

if [ $wp_cli ]; then
    echo 'Please enter the URL of the WordPress website:';
    read -i 'http://' -e wordpress_url
    wordpress_url=$(sed -e 's/[\\]/\\&/g' <<< $wordpress_url)
    database_name=$(sed -e 's/[\\]/\\&/g' <<< $database_name)
    database_user=$(sed -e 's/[\\]/\\&/g' <<< $database_user)
    database_pass=$(sed -e 's/[\\]/\\&/g' <<< $database_pass)
    rm -f wp-config-sample.php;
    wp --allow-root core config --path="$directory" --url="$wordpress_url" --dbname="$database_name" --dbuser="$database_user" --dbpass="$database_pass" --locale="$language" --dbprefix="$database_prefix"
    chown $username.$username wp-config.php;
else
    salt=$(perl -i -p0e 's/(;)[\s\r\n ]*(define)/\1\n\2/smg' <<< $(sed -e 's/[^0-9a-z@\s\n ,.:;_-]/\\&/gi' <<< $(curl -L --silent "https://api.wordpress.org/secret-key/1.1/salt/")));
    database_name=$(sed -e 's/[]\/\\&%$*.^|[]/\\&/g' <<< $database_name)
    database_user=$(sed -e 's/[]\/\\&%$*.^|[]/\\&/g' <<< $database_user)
    database_pass=$(sed -e 's/[]\/\\&%$*.^|[]/\\&/g' <<< $database_pass)
    mv -f wp-config-sample.php wp-config.php;
    sed -i "s/database_name_here/$database_name/" wp-config.php;
    sed -i "s/username_here/$database_user/" wp-config.php;
    sed -i "s/password_here/$database_pass/" wp-config.php;
fi

sed -i "s/\(\$table_prefix\s*=\s*'\)[a-z_-]+\(';\)/\1$database_prefix\2/" wp-config.php;

if [ -n "$salt" ]; then
    perl -0pi -e "s/define[(].AUTH_KEY.+\n.+\n.+\n.+\n.+\n.+\n.+\n.+NONCE_SALT.+/$salt/" wp-config.php;
fi

if [ -n $language ] && [[ ! -n $(grep -Eq "define\('WPLANG'" wp-config.php) ]]; then
    perl -0pi -e "s/([\/* \t]+That.s all.+)/\n\n\/* WordPress Localized Language *\/\ndefine('WPLANG', '$language');\n\n$&/" wp-config.php
fi

if [[ ! -n $(grep -Eq "define\('AUTO_UPDATE'" wp-config.php) ]]; then
    perl -0pi -e "s/([\/* \t]+That.s all.+)/\n\n\/* Server-wide automatic update service *\/\ndefine('AUTO_UPDATE', TRUE);\n\n$&/" wp-config.php
fi

if [ ! -f .htaccess ]; then
    touch .htaccess;
fi

cat >.htaccess <<EOL
ErrorDocument 401 default

# BEGIN WordPress
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /

  RewriteRule ^index\.php$ - [L]

  RewriteCond %{REQUEST_URI} ^/wp-content/uploads/.*$
  RewriteRule ^wp-content/uploads/.*\.(php|inc|rb|py|s?html?|js|vb|cgi|pl)$ - [F,L,NC]

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.php [L]
</IfModule>
# END WordPress

# BEGIN W3TC Browser Cache
<IfModule mod_deflate.c>
  <IfModule mod_headers.c>
    Header append Vary User-Agent env=!dont-vary
  </IfModule>
  AddOutputFilterByType DEFLATE text/css text/x-component application/x-javascript application/javascript text/javascript text/x-js text/html text/richtext image/svg+$
  <IfModule mod_mime.c>
    # DEFLATE by extension
    AddOutputFilter DEFLATE js css htm html xml
  </IfModule>
</IfModule>
# END W3TC Browser Cache

<Files wp-config.php>
  Order allow,deny
  Deny from all
</Files>

php_value upload_max_filesize $upload_max_filesize
php_value post_max_size $upload_max_filesize
EOL

if [ $wp_cli ]; then
    random_user=User_$(generate_random_string 5 A-Za-z0-9)
    random_password=$(generate_random_string 10)

    if [ ! -n $wordpress_url ] || [ $wordpress_url == '' ]; then
        echo 'Please enter the URL of the WordPress website:';
        read -i 'http://' -e wordpress_url
    fi

    echo 'Website title:';
    read wordpress_title
    echo 'Username for the administrator:';
    read -i $random_user -e admin_user
    echo 'Password for the administrator:';
    read -i $random_password -e admin_password
    echo 'Email address for the administrator:';
    read admin_email

    wordpress_url=$(sed -e 's/[\\]/\\&/g' <<< $wordpress_url)
    wordpress_title=$(sed -e 's/[\\]/\\&/g' <<< $wordpress_title)
    admin_user=$(sed -e 's/[\\]/\\&/g' <<< $admin_user)
    admin_password=$(sed -e 's/[\\]/\\&/g' <<< $admin_password)

    if [[ $admin_email =~ $email_regex ]]; then
        admin_email=$(sed -e 's/[\\]/\\&/g' <<< $admin_email)
    else
        echo 'Please enter a valid email address for the administrator:';
        read admin_email
        if [[ $admin_email =~ $email_regex ]]; then
            admin_email=$(sed -e 's/[\\]/\\&/g' <<< $admin_email)
        else
            echo -e "\e[1;31mError:\e[0m Invalid email address: $admin_email. Skipping setup."
            admin_email=''
        fi
    fi

    if [ -n $admin_email ] && [ $admin_email != '' ]; then
        wp --allow-root --quiet --path="$directory" --url="$wordpress_url" --title="$wordpress_title" --admin_user="$admin_user" --admin_password="$admin_password" --admin_email="$admin_email" core install;
        wp --allow-root --quiet --path="$directory" theme update --all;
        wp --allow-root --quiet --path="$directory" plugin update --all;

        echo -e '\e[1mInstall and Setup Successful\e[0m';
        if [ -n "$sub_directory" ] && [ "$sub_directory" != "" ]; then
            echo -e "\e[92mSuccess:\e[0m Installed and setup WordPress for username: $username, sub directory: $sub_directory."
            exit;
        fi
        
        echo -e "\e[92mSuccess:\e[0m Installed and setup WordPress for username: $username."
        exit;
    fi
fi

echo -e '\e[1mInstall Successful\e[0m';
if [ -n "$sub_directory" ] && [ "$sub_directory" != "" ]; then
    echo -e "\e[92mSuccess:\e[0m Installed WordPress for username: $username, sub directory: $sub_directory."
    exit;
fi

echo -e "\e[92mSuccess:\e[0m Installed WordPress for username: $username."
exit;

It is simple to use, to install a new WordPress website run:

sh ./wordpress_install.sh <username>[/<subdirectory>] [[<database-name> <database-username>] <database-password> [<language>]]

The arguments in square brackets are optional, you just need the username as a minimum. To uninstall (remove all files and reset the website) an existing installation use:

sh ./wordpress_install.sh <username>[/<subdirectory>] uninstall

Please have a look at my automatic WordPress update script if you like to set up regular updates using WP-Cli. In a future version of this script, I’ll make the arguments easier to set and include all the inputs needed for the WP-Cli setup.

If you’re adding this to your server, I’d recommend taking the opportunity to add a few of your favourite plugins as follows:

cp -Rf /another/blog/wp-content/plugins/plugin-name ${directory}wp-content/plugins/;
unzip -q /source/directory/another-plugin.zip -d ${directory}wp-content/plugins/;
chown -R $username.$username ${directory}wp-content/plugins/;
wp --allow-root --path="$directory" plugin activate plugin-name;
wp --allow-root --path="$directory" plugin activate another-plugin;

Insert this after the wp core install line and edit this to use your plugin names and locations.

Please send me your suggestions and I’ll update the script if you have some good ideas!


Update on 5th November 2018: Added a check for a working MySQL connection before attempting to install WordPress. Previously, a bad database connection broke the installation process.

Last updated on

Leave a Reply

Your email address will not be published. Required fields are marked *