Skip to content

Instantly share code, notes, and snippets.

@phlbnks
Created June 8, 2017 08:52
Show Gist options
  • Select an option

  • Save phlbnks/94773378dce06336d05f35a88d88d9dd to your computer and use it in GitHub Desktop.

Select an option

Save phlbnks/94773378dce06336d05f35a88d88d9dd to your computer and use it in GitHub Desktop.

Revisions

  1. phlbnks created this gist Jun 8, 2017.
    339 changes: 339 additions & 0 deletions wp_cli.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,339 @@
    #!/bin/bash -e
    #
    # Description:
    # This will deploy WordPress in the current directory.
    # Without modification it:
    # - will configure basic security:
    # - remove initial user created
    # - deploy 6G firewall in .htaccess
    # - attempt to prevent user enumeration in .htaccess
    # - protect sensitive files and disallow executables in /wp-uploads
    # - presumes a htpasswd file called wp-login is located in /etc/apache2/
    # - disallows theme/plugin editor in wp-admin
    # - only allows core/plugin/theme updates in wp-admin when using a cookie set using &key=xxx
    # - presumes it is a staging environment so sets 'discourage search engines'
    # - deletes akismet and hello_dolly plugins
    # - deletes the default page and post
    # - creates a blank page called Home and sets it as the frontpage
    # - sets permalinks to /%postaname%/
    # - optionally it:
    # - bootstraps an _S theme
    # - installs some plugins - advanced-custom-fields autoptimize better-wp-security breadcrumb-navxt broken-link-checker cms-tree-page-view custom-post-type-ui ewww-image-optimizer google-analytics-dashboard-for-wp google-captcha google-sitemap-generator gravity-forms-custom-post-types plugincheck query-monitor redirection re generate-thumbnails simple-local-avatars theme-check wp-fail2ban wp-pagenavi wp-super-cache
    # - finally it will delete itself after it finishes.
    clear
    echo "============================================"
    echo "WordPress Install Script"
    echo "============================================"
    echo "Do you need to setup new MySQL database? (y/n)"
    read -e setupmysql
    if [ "$setupmysql" == y ] ; then
    echo "MySQL Admin User: "
    read -e mysqluser
    echo "MySQL Admin Password: "
    read -s mysqlpass
    echo "MySQL Host (Enter for default 'localhost'): "
    read -e mysqlhost
    mysqlhost=${mysqlhost:-localhost}
    fi
    echo "WP Database Name: "
    read -e dbname
    echo "WP Database User: "
    read -e dbuser
    echo "WP Database Password: "
    read -s dbpass
    echo "WP Database Table Prefix [numbers, letters, and underscores only] (Enter for default 'wp_'): "
    read -e dbtable
    dbtable=${dbtable:-wp_}
    echo "Key for updating in wp-admin: "
    read -e hardenkey
    echo "Last chance - sure you want to run the install? (y/n)"
    read -e run
    if [ "$run" == y ] ; then
    if [ "$setupmysql" == y ] ; then
    echo "============================================"
    echo "Setting up the database."
    echo "============================================"
    #login to MySQL, add database, add user and grant permissions
    dbsetup="create database $dbname;GRANT ALL PRIVILEGES ON $dbname.* TO $dbuser@$mysqlhost IDENTIFIED BY '$dbpass';FLUSH PRIVILEGES;"
    mysql -u $mysqluser -p$mysqlpass -e "$dbsetup"
    if [ $? != "0" ]; then
    echo "============================================"
    echo "[Error]: Database creation failed. Aborting."
    echo "============================================"
    exit 1
    fi
    fi
    # configure wp cli to allow htaccess modification / re-write flushing.
    cat > wp-cli.local.yml <<'EOL'
    apache_modules:
    - mod_rewrite
    EOL
    echo "============================================"
    echo "Downloading WordPress for you."
    echo "============================================"
    #download wordpress
    wp core download --locale=en_GB
    echo "+++ Configuring..."
    wp core config --dbname=$dbname --dbprefix=$dbtable --dbuser=$dbuser --dbpass=$dbpass --extra-php <<PHP
    /** WP_DEBUG setup **/
    define( 'WP_DEBUG', false );
    if ( WP_DEBUG ) {
    define( 'WP_DEBUG_LOG', true );
    define( 'SCRIPT_DEBUG', true );
    define( 'WP_DEBUG_DISPLAY', false );
    @ini_set( 'display_errors', 0 );
    }
    /** Disallow theme and plugin editor in admin. Updates only with query var **/
    define( 'DISALLOW_FILE_EDIT', true );
    if ( $_REQUEST['key'] == "$hardenkey" ) {
    setcookie( 'updatebypass', 1 );
    } elseif ( ! $_COOKIE['updatebypass'] ) {
    define( 'DISALLOW_FILE_MODS', true );
    }
    PHP

    #create uploads folder and set permissions
    mkdir -p wp-content/uploads
    chmod 775 wp-content/uploads

    #remove readme.html
    rm readme.html
    #create root .htaccess with some useful starters
    cat > .htaccess <<'EOL'
    # Protect this file
    <Files ~ "^\.ht">
    Order allow,deny
    Deny from all
    </Files>
    # Prevent directory listing
    Options -Indexes
    ## BEGIN 6G Firewall from https://perishablepress.com/6g/
    # 6G:[QUERY STRINGS]
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{QUERY_STRING} (eval\() [NC,OR]
    RewriteCond %{QUERY_STRING} (127\.0\.0\.1) [NC,OR]
    RewriteCond %{QUERY_STRING} ([a-z0-9]{2000}) [NC,OR]
    RewriteCond %{QUERY_STRING} (javascript:)(.*)(;) [NC,OR]
    RewriteCond %{QUERY_STRING} (base64_encode)(.*)(\() [NC,OR]
    RewriteCond %{QUERY_STRING} (GLOBALS|REQUEST)(=|\[|%) [NC,OR]
    RewriteCond %{QUERY_STRING} (<|%3C)(.*)script(.*)(>|%3) [NC,OR]
    RewriteCond %{QUERY_STRING} (\\|\.\.\.|\.\./|~|`|<|>|\|) [NC,OR]
    RewriteCond %{QUERY_STRING} (boot\.ini|etc/passwd|self/environ) [NC,OR]
    RewriteCond %{QUERY_STRING} (thumbs?(_editor|open)?|tim(thumb)?)\.php [NC,OR]
    RewriteCond %{QUERY_STRING} (\'|\")(.*)(drop|insert|md5|select|union) [NC]
    RewriteRule .* - [F]
    </IfModule>
    # 6G:[REQUEST METHOD]
    <IfModule mod_rewrite.c>
    RewriteCond %{REQUEST_METHOD} ^(connect|debug|delete|move|put|trace|track) [NC]
    RewriteRule .* - [F]
    </IfModule>
    # 6G:[REFERRERS]
    <IfModule mod_rewrite.c>
    RewriteCond %{HTTP_REFERER} ([a-z0-9]{2000}) [NC,OR]
    RewriteCond %{HTTP_REFERER} (semalt.com|todaperfeita) [NC]
    RewriteRule .* - [F]
    </IfModule>
    # 6G:[REQUEST STRINGS]
    <IfModule mod_alias.c>
    RedirectMatch 403 (?i)([a-z0-9]{2000})
    RedirectMatch 403 (?i)(https?|ftp|php):/
    RedirectMatch 403 (?i)(base64_encode)(.*)(\()
    RedirectMatch 403 (?i)(=\\\'|=\\%27|/\\\'/?)\.
    RedirectMatch 403 (?i)/(\$(\&)?|\*|\"|\.|,|&|&amp;?)/?$
    RedirectMatch 403 (?i)(\{0\}|\(/\(|\.\.\.|\+\+\+|\\\"\\\")
    RedirectMatch 403 (?i)(~|`|<|>|:|;|,|%|\\|\s|\{|\}|\[|\]|\|)
    RedirectMatch 403 (?i)/(=|\$&|_mm|cgi-|etc/passwd|muieblack)
    RedirectMatch 403 (?i)(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd|eval\(|self/environ)
    RedirectMatch 403 (?i)\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp|tar|rar|rdf)$
    RedirectMatch 403 (?i)/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen|timthumb|webshell)\.php
    </IfModule>
    # 6G:[USER AGENTS]
    <IfModule mod_setenvif.c>
    SetEnvIfNoCase User-Agent ([a-z0-9]{2000}) bad_bot
    SetEnvIfNoCase User-Agent (archive.org|binlar|casper|checkpriv|choppy|clshttp|cmsworld|diavol|dotbot|extract|feedfinder|flicky|g00g1e|harvest|heritrix|httrack|kmccrew|loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python|seekerspider|siclab|skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy|youda|zmeu|zune) bad_bot
    <limit GET POST PUT>
    Order Allow,Deny
    Allow from All
    Deny from env=bad_bot
    </limit>
    </IfModule>
    # 6G:[BAD IPS]
    <Limit GET HEAD OPTIONS POST PUT>
    Order Allow,Deny
    Allow from All
    # uncomment/edit/repeat next line to block IPs
    # Deny from 123.456.789
    </Limit>
    ## END 6G Firewall
    ## BEGIN htauth basic authentication
    # STAGING
    Require all denied
    AuthType Basic
    AuthUserFile /etc/apache2/wp-login
    AuthName "Please Authenticate"
    Require valid-user
    # LIVE - prevent wp-login brute force attacks from causing load
    #<FilesMatch "^(wp-login|xmlrpc)\.php$">
    # AuthType Basic
    # AuthUserFile /etc/apache2/wp-login
    # AuthName "Please Authenticate"
    # Require valid-user
    #</FilesMatch>
    # Exclude the file upload and WP CRON scripts from authentication
    <FilesMatch "(async-upload\.php|wp-cron\.php)$">
    Satisfy Any
    Order allow,deny
    Allow from all
    Deny from none
    </FilesMatch>
    ## END htauth
    ## BEGIN WP file protection
    <Files wp-config.php>
    order allow,deny
    deny from all
    </Files>
    # WP includes directories
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^wp-admin/includes/ - [F,L]
    RewriteRule !^wp-includes/ - [S=3]
    # note - comment out next line on multisite
    RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
    RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
    RewriteRule ^wp-includes/theme-compat/ - [F,L]
    </IfModule>
    ## END WP file protection
    # Prevent author enumeration
    RewriteCond %{REQUEST_URI} !^/wp-admin [NC]
    RewriteCond %{QUERY_STRING} author=\d
    RewriteRule ^ /? [L,R=301]
    EOL

    #create .htaccess to protect uploads directory
    cat > wp-content/uploads/.htaccess <<'EOL'
    # Protect this file
    <Files .htaccess>
    Order Deny,Allow
    Deny from All
    </Files>
    # whitelist file extensions to prevent executables being
    # accessed if they get uploaded
    order deny,allow
    deny from all
    <Files ~ "(?i)\.(docx?|xlsx?|pptx?|txt|pdf|xml|css|jpe?g|png|gif)$">
    allow from all
    </Files>
    EOL

    echo "Setup WordPress to a 'standard install'? (y/n)"
    read -e setupwp
    if [ "$setupwp" == y ] ; then
    echo "========================="
    echo "Configuring WordPress."
    echo "========================="
    # useful ref: https://indigotree.co.uk/automated-wordpress-installation-with-bash-wp-cli/

    echo "Site URL (no trailing slash): "
    read -e siteurl
    echo "Site name: "
    read -e sitename
    wp core install --url="${siteurl}/" --title="$sitename" --admin_user=admin --admin_password=admin [email protected] --skip-email
    echo "Is this a sub-directory install? (y/n)"
    read -e issubdir
    if [ "$issubdir" == y ] ; then
    read -e subdir
    wp option update "${siteurl}/$issubdir"
    fi

    echo "+++ Setting up users..."
    echo "Admin user name: "
    read -e adminname
    echo "Admin user email: "
    read -e adminemail
    wp user create $adminname $adminemail --role=administrator --send-email
    echo "+++ Deleting dummy admin user..."
    wp user delete admin

    echo "+++ Discouraging search engines..."
    wp option update blog_public 0
    echo "+++ Setting permalinks to /%postaname%/..."
    wp rewrite structure '/%postname%/' --hard
    wp rewrite flush --hard

    echo "+++ Sample page and post deleted when users cleaned; now create empty 'Home' page..."
    #echo "+++ Deleting sample page and post; create empty 'Home' page..."
    #wp post delete $(wp post list --post_type=page,post --field=ID --format=ids)
    wp post create --post_type=page --post_title=Home --post_status=publish
    echo "+++ Set frontpage setting to show a page..."
    wp option update show_on_front 'page'
    echo "+++ Set 'Home' to be the frontpage..."
    wp option update page_on_front $(wp post list --post_type=page --pagename=home --field=ID --format=ids)

    echo "Install _s theme? (y/n)"
    read -e underscores
    if [ "$underscores" == y ] ; then
    echo "Theme slug: "
    read -e themeslug
    echo "Theme name: "
    read -e themename
    echo "Author: "
    read -s themeauthor
    echo "Author URI: "
    read -s themeuri
    wp scaffold _s $themeslug --theme_name="$themename" --author="$author" --author_uri="$themeuri" --activate --sassify
    fi

    echo "Install default plugins? (y/n)"
    read -e plugins
    if [ "$plugins" == y ] ; then
    echo "+++ Deleting Akismet and Hello Dolly..."
    wp plugin delete akismet hello
    echo "+++ Installing standard plugins..."
    wp plugin install advanced-custom-fields autoptimize better-wp-security breadcrumb-navxt broken-link-checker cms-tree-page-view custom-post-type-ui ewww-image-optimizer google-analytics-dashboard-for-wp google-captcha google-sitemap-generator gravity-forms-custom-post-types plugincheck query-monitor redirection regenerate-thumbnails simple-local-avatars theme-check wp-fail2ban wp-pagenavi wp-super-cache

    fi
    fi

    echo "Cleaning..."
    #remove bash script if it exists in this dir
    [[ -f "wp_cli.sh" ]] && rm "wp_cli.sh"
    echo "========================="
    echo "[Success]: Installation is complete."
    echo "========================="
    else
    exit
    fi