Injecting a custom mu-plugin for FTP deployments on WP Engine

Sometimes FTP is your only option

In a world where we have such amazing tools like WP-CLI it’s hard to imagine why you’d want to attempt scripting over FTP. WP Engine doesn’t allow SSH access and FTP was really the only option if I wanted to automate things with their service.

One time use mu-plugins

It all starts with a mu-plugin. A mu-plugin or must use plugin is a great way to force some php code to run on a website. All you really need to do is upload a php file to the wp-content/mu-plugins folder and on the next web request WordPress will auto-activate and run the code.

Must use plugins are special and can not be deactivated through normal means. They appear under a section called Must-Use on the plugins page.

Screen Shot 2016-09-05 at 7.52.34 AM.png

Deploying code with bash script

All of these actions can be automated with a bash script. The following will push a mu-plugin to a web server, run the code, then delete the file. I’ll call it deploy.sh.

#!/bin/sh

#
#   Deploys one time use mu-plugins on remote server via FTP
#   Script/deploy.sh
#

### FTP Information
domain=example.com
username=example
password='randompassword123'
ipAddress='example.wpengine.com'
protocol='sftp'
port='2222'

### Configs
mupluginpath=/home/Tmp
muplugin=anchor_load_configs.php
lftpprep='set sftp:auto-confirm yes;set net:max-retries 2;set ftp:ssl-allow no'

### Uploads plugin to mu-plugins
sudo lftp -e "$lftpprep;put -O /wp-content/mu-plugins/ $mupluginpath/$muplugin; exit" -u $username,$password -p $port $protocol://$ipAddress

### Trigger website to run mu-plugins
wget --no-cache --spider $ipAddress/wp-login.php

### Removes plugin
sudo lftp -e "$lftpprep;rm /wp-content/mu-plugins/$muplugin; exit" -u $username,$password -p $port $protocol://$ipAddress

If you can code it in PHP you can do it over FTP

By wrapping custom code into a mu-plugin you can accomplish anything possible in PHP with a FTP deployment script. The following is a static example of a mu-plugin which will do the following.

  • Conditionally adds a new admin account if not already created
  • Send an email with link to set password
  • Conditionally remove default WP Engine admin account if new admin account created and account corrosponding to the install name is found
  • Conditionally remove site title if WP Engine’s defaults are detected
  • Conditionally remove site description if WP Engine’s defaults are detected
  • Sets timezone to New York

This one is called anchor_load_configs.php .

<?php

### Patch fix email notifications in WordPress 4.6
add_filter( 'wp_mail_from', function( $email ) {
    $site = get_site_url();
    $parse = parse_url($site);
    $email = "wordpress@". $parse['host'];
    return $email;
});

function add_admin_acct(){

    ### Required in order to use wp_delete_user function
    require_once(ABSPATH.'wp-admin/includes/user.php' );

    global $wpdb;
    $tablename = $wpdb->prefix . "users";

    ### Check for default WP Engine site name and description
    if (get_option( "blogname" ) == "Austin Ginder Blog") {
        ## Update default site title
        update_option( "blogname", "installname.wpengine.com" );
    }

    if (get_option( "blogdescription" ) == "Your SUPER-powered WP Engine Blog") {
        ## Update default site description
        update_option( "blogdescription", "" );
    }

    if (get_option( "timezone_string" ) == "") {
        ## Update default time zone
        update_option( "timezone_string", "America/New_York" );
    }

    ### Generating primary admin account

    if ( !username_exists( "anchorhost" ) && !email_exists( "support@anchor.host" ) ) {

        $userdata = array(
            'user_login'    =>  'anchorhost',
            'user_email'    =>  'support@anchor.host',
            'display_name'  =>  'anchorhost',
            'first_name'    =>  'Anchor', 
            'last_name'     =>  'Hosting', 
            'user_nicename' =>  'anchorhost',
            'nickname'      =>  'anchorhost',
            'role'          =>  'administrator'
        );
        $user_id = wp_insert_user( $userdata );

    }

    ### Sending email for primary admin account

    if ($user_id) {
        wp_new_user_notification( $user_id, null, 'user' );

        ### Check for default WP Engine account
        $user = get_user_by( 'login', 'installname' );

        ### If found remove and reassign pages/post to new admin
        if($user) {
            wp_delete_user( $user->ID, $user_id );
        }
    }

    $user_id = null;

}
add_action('init','add_admin_acct');

?>