Variable SSL certificate directives in Nginx

When I left WSU, we had hundreds of server blocks defined for hundreds of domains that pointed to a single WordPress multisite installation. I wrote this up previously, but each of these blocks was effectively the same:

server {
    include common-listen.conf;

    server_name ohlookadomain.com;
    root /var/www/wordpress;

    ssl_certificate /etc/nginx/ssl/ohlookadomain.com.cer;
    ssl_certificate_key /etc/nginx/ssl/ohlookadomain.com/key;

    include common-ssl-config.conf;
    include common-location-block.conf;
}

The only reason different server blocks were required was the directives for ssl_certificate and ssl_certificate_key. As we were getting prepped to launch our first sites, I wrote about my disappointment that variables were not supported.

Fast-forward to Tuesday. I no longer support hundreds of unique domains, but I still think it’s cool to see that Nginx 1.15.9 added support for variables to the to the ssl_certificate and ssl_certificate_key directives.

I upgraded this evening on my personal server and then did a brief test with a multi-tenant WordPress configuration that I manage and everything worked exactly as expected.

This is the server block that I used: (It now works with 4 domains)

server {
    listen 443 ssl http2;
    server_name _;
    root         /var/www/wordpress;

    ssl_certificate.    /etc/letsencrypt/live/$server_name/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$server_name/privkey.pem;

    include /etc/nginx/ssl-common.conf;
    include /etc/nginx/wp-shared-location-common.conf;
    include /etc/nginx/php-location-common.conf;
}

I haven’t really explored the consequences of using _ as the server name, but with this configuration—just one server block—I can support as many different domains as I want as long as they have matching Let’s Encrypt generated certs.

Pretty cool!

Things I’m learning while playing with Gutenberg (part I)

I’ve been having fun working heavily with Gutenberg over the last several weeks. As I ran into a couple things, I started tracking them in a draft post so I could work through and publish them later.

Here’s the first set. 🙂

How to access className from a block’s save() function

The classname attribute is available to a block’s edit() function by default, but it is not available to the block’s save() function. I learned from this GitHub issue that registering className as one of the block’s attributes will provide it to the save() function.

registerBlockType( 'my/block', {

	attributes: {
		className: {
			type: 'string',
			default: '',
		},
	},

	edit( props ) {
		// className via props
		const { attributes, className } = props;
		const { customData } = attributes;

		return ( // ... );
	},

	save( { attributes } ) {
		// className via attributes 
		const { className, customData } = attributes;

		return ( // ... );
	},
} );

Be careful importing Lodash modules

If you want to use something from Lodash, like find():

// Import the module like this
import find from 'lodash/find';

// Not like this...
import { find } from 'lodash';

The latter brings in the global _ from Lodash, which overrides the global _ provided by Underscore and causes trouble.

A this.activateMode is not a function error that prevented media blocks from being used led me to this issue, which helped me.

Beware of non-changing arrays

I ran into this a couple different ways recently and I’m still working on understanding it completely, but things like Array().push and Array().pop modify the reference value of an existing array and may not trigger the state change you’re expecting.

let apples = [];
let oranges = apples;

oranges; // []

apples.push( 1 );
oranges; // [ 1 ]

oranges === apples; // true
oranges === apples.concat( 1 ); //false

I picked up on better approaches for this via this issue when a block’s attributes weren’t saving as expected.

Remove an administrator from 58 networks at once with WP-CLI

Ok, so this is super specific and probably won’t come in handy for you. But it’s another example of how quickly you can perform a task using WP-CLI.

At WSU, we currently have 58 networks configured in WordPress multisite.

  • Global administrators are users that have been set under the site_admins option on network 1.
  • Network administrators are users that have been set under the site_admins option on any other network.

Depending on the page view, these are used to make up what WordPress validates as is_super_admin(). See WordPress core ticket #37616 for an eye into how this will get a lot better soon.

One of our excellent interns has graduated and we needed to remove him from ~25 different networks. Because I rushed the original UI on adding network administrators, there’s no great way to remove them.

Enter WP-CLI:

wp site option list --field=site_id --search=site_admins | xargs -n1 -I % wp eval 'if ( $sa = get_network_option( %, "site_admins" ) ) { if ( $tk = array_search( "old.username", $sa ) ) { unset( $sa[ $tk ] ); update_network_option( %, "site_admins", $sa ); } }'

WP-CLI isn’t really multi-network aware out of the box, so the wp site option list command will output all options for all networks. We can use this to our advantage by having only site_id output for only the site_admins option on each network.

We then pipe this to xargs and use wp eval to run arbitrary PHP code. This code retrieves the current site_admins data for the given network, removes the old user from the array, and then updates the network option with the new value.

This is a prime candidate for packing into some code and turning it into an actual command—wp network admin remove or something—but it does the trick for now!

And if we haven’t written a better UI or WP-CLI command by the time somebody else needs to be removed, then referring to this post and using the command will take 10 seconds instead of the 5 minutes it  took to make it in the first place. 🙂

Update: Daniel pointed out wp super-admin, which I hadn’t noticed before. This will take a --url argument, so if you know a URL on the network, then you can pass that and it will work. There’s no great way (that I know of yet) to generate list of URLs that covers all networks, but I started an issue on the WP-CLI repo to start figuring that out.