diff --git a/.config/newsboat/my_urls b/.config/newsboat/my_urls index a0af4ef9..1725546a 100644 --- a/.config/newsboat/my_urls +++ b/.config/newsboat/my_urls @@ -50,3 +50,4 @@ file://./rss/stackoverflowpodcast.xml file://./rss/linuxjournal.xml file://./rss/linux_lads.rss file://./rss/ishadeed.xml +file://./rss/bigdinosaur_blog.rss diff --git a/.config/newsboat/rss/bigdinosaur_blog.rss b/.config/newsboat/rss/bigdinosaur_blog.rss new file mode 100644 index 00000000..2c0bb1e4 --- /dev/null +++ b/.config/newsboat/rss/bigdinosaur_blog.rss @@ -0,0 +1,1910 @@ + + + + BigDino Blog + + https://blog.bigdinosaur.org + Tales of hacking and stomping on things, by Lee Hutchinson + Wed, 31 Aug 2022 00:40:26 +0000 + en-US + + hourly + + 1 + https://wordpress.org/?v=6.2 + + + https://blog.bigdinosaur.org/wp-content/uploads/2021/02/cropped-bigdino200-32x32.png + BigDino Blog + https://blog.bigdinosaur.org + 32 + 32 + + + Configuring Wordpress & OpenLiteSpeed + https://blog.bigdinosaur.org/configuring-wordpress-openlitespeed/ + + + Sat, 23 Jul 2022 16:01:19 +0000 + + + + + https://blog.bigdinosaur.org/?p=602 + + Read more]]> + +
A dinosaur who has stolen the OLS logo and looks very proud of himself
+ + +

I described in a previous post the 2022 changes in the BigDino web stack, with the biggest change being that we’re now using OpenLiteSpeed (“OLS”) as our web server application. There are a whole buttload of “how to install Wordpress on OLS” posts on the web and I ended up reading most of them during the process of switching the server over, but most of them are outdated in some way, and none of them included everything I ended up needing to do to get everything working.

+ + + +

Partially to document my configuration for the world but mainly so that I won’t ever have to look all this up from scratch again, here’s how I configured OpenLiteSpeed to serve up Wordpress quickly, safely, and securely. (I hope.)

+ + + + + + + +

The big switch: from text files to GUI

+ + + +

In spite of how excited I was to switch over to OpenLiteSpeed, there was one aspect that I was positively dreading: giving up the convenience and quickness of directly editing configuration text files and instead switching over to using a GUI as the primary means of making server config changes. OLS does allow you to edit its config files directly, but I wanted to make a good-faith effort to actually do things the “right” way—or at least the way the developers intended for most people to use their application.

+ + +
+
Screenshot of the OLS web admin console virtual host dashboard
OpenLiteSpeed’s web administration console.
+ + +

The GUI is for the most part easy to use, though coming from Nginx some of the terminology required some googling in order to fully understand. I think I got everything figured out, but there was a bit of a learning curve to map the things I knew how to do in Nginx to the OLS way of doing things. (Much of the OLS way of doing things is based around the Apache way of doing things, including the way OLS handles rewrites and .htaccess files. More on that in a bit.)

+ + + +

Securing that GUI

+ + + +

Still, having come of age in a certain era of computing, a world-accessible configuration GUI feels like a fast way to get pwn3d. Several OLS setup guides recommended putting additional security in front of it, but I settled on simply firewalling the admin console via an AWS security group. The admin console listens on TCP port 7080, so I created one ruleset with that port open and listening for inbound connections from my current IP address, and one ruleset with that port closed.

+ + +
+
Screenshot of the AWS admin console showing network security group configuration
AWS EC2 network security group configuration. This is the ruleset with the OLS admin console available—note the rule labeled “IPv4 OLS console inbound allow” that exposes TCP port 7080 and listens for traffic coming from my IP address.
+ + +

When I need to actively make changes, I switch the EC2 instance I’m running all of this on to the “admin port open” security group. When I’m done, I switch it to the “admin port closed” group. It’s maybe a little paranoid, but it makes me feel a lot more secure than the alternative of trying to secure the port with extra authentication or even just leaving it open. (And please don’t do that. Seriously. Why take the risk?)

+ + + +

WP virtual host configuration

+ + + +

Like most web server applications, OLS uses the concept of “virtual hosts” so that you can run multiple sites on your server, each with its own bits of configuration. This is especially useful when you’re hosting full applications (like Wordpress!) that might need a bit of extra love out of the box to work properly and securely—rather than throwing all your applications into a pile and trying to keep everything balanced with your global server configuration, every time you need to set up a new site you simply create a new virtual host (or “vhost”). Each site lives on its own vhost, and each site can have its own configuration.

+ + + +

Adding a vhost gets you this screen here, where you’ve got a number of required parameters to fill in. Odds are you know what most of these do, and odds are you can accept the default values for most of them:

+ + + + + + + +

The image at left is the “add a vhost” screen waiting for input, and the image at right is what it looks like after some basic details are entered. At the very least, your vhost needs a name, a path for the files to actually live in, and a configuration file location. If you intend to run php applications, or anything else that requires an additional external program or interpreter, you need to set “Enable Scripts/ExtApps” to “yes.” I also toggled “Restrained” to “yes,” which is intended to prevent files outside of the web root from being accessed by anything running on the vhost.

+ + + +

(There are also lots of things I’m not using that other folks might find useful, like per-vhost throttle settings. I’m not going to talk about those settings, but they are easily google-able. Same deal for all the rest of the pages—I’m only going to talk about the bits I’m actually using.)

+ + + +

That takes care of the “Basic” tab. Moving on to the “General” tab:

+ + +
+
Screenshot of the "General" tab on the OLS web console.
OLS webadmin vhost configuration “General” tab.
+ + +

The first setting to specify here is the “document root,” which differs from the web root defined on the Basic page in that the web root could notionally contain log and config files for your site, while the document root contains the actual files that you’re serving out. I have mine set to a subdirectory underneath the web root.

+ + + +

Then, a very important bit: you’ll need to define a name, along with any aliases, so the server knows what hostname to listen for to direct traffic to this vhost. My blog uses blog.bigdinosaur.org as its fully-qualified domain name, so I don’t need any aliases; if you’re serving your WP site from example.com without a subdomain, you might want to add www.example.com to your alias list so that the server is listening for that name. Otherwise, visitors who typed in example.com would see your site, but visitors who type www.example.com won’t.

+ + + +

This tab lets you enable compression (which has almost no downsides and which you should almost certainly do), and it also lets you set up what your default index file is and whether directory indexing is on or not. Wordpress needs index.php in the list of index files, so I made sure to add it.

+ + + +

This tab is also where you specify custom error pages, if you’d like. I need to do a bit more investigation about how to do this with Wordpress, as it’s not quite working. (I suspect WP is handling the errors itself at the application level rather than letting the web server do it, so fixing this might require some plugin work.)

+ + +
+
Screenshot of the "Rewrite" tab on the OLS web console.
The “Rewrite” tab.
+ + +

Next is the “Rewrite” tab, where you can—surprise—control vhost-level rewrites. There are lots of places to set rewrites up with OLS, including good ol’ .htaccess files, but this is the proper place to put rewrites that need to function for all requests across the whole vhost. This is where I’m putting my HTTP-to-HTTPS rewrite rule, so that visitors to the site that come by way of HTTP will get redirected to HTTPS.

+ + + +

(This is somewhat of a superfluous redirect because BigDinosaur.org, like all responsible web sites, uses HTTP Strict Transport Security. Your browser should be more or less incapable of letting you type http://blog.bigdinosaur.org—it’ll automatically redirect you to the HTTPS version of the site because the site is on the big HSTS preload lists. If you’re hosting a site and not using HSTS, you should start, yesterday.)

+ + + +

A very important setting you’ll want to make sure is enabled is the “Auto Load from .htaccess” one, which ensures that your .htaccess files are utilized whenever OLS comes across one.

+ + + +

Next is the “Context” tab, and this one took me a bit to grok.

+ + +
+
Screenshot of the "Context" tab on the OLS web console.
The confusing “Context” tab.
+ + +

The “Context” tab lets you define things that happen in certain contexts. Which sounds confusing. Here’s a better example: if you want to define some rules that only apply to image files, or that only apply to your upload directory, this is the place to do those things. First you define the context under which your settings apply, and then you set some settings. The way this tab seems to be most often used is to send customized headers, using the global “/” context (meaning “all requests”). I’m also using it to set specific rewrite rules on specific locations, and to change the cache expiry of some stuff. Let’s jump in.

+ + + + + + + +

The three contexts I constructed and am using for Wordpress. From left to right, we have the global (“/“) context, a context for images and other files I want a long cache expiry on, and the uploads directory context.

+ + + +

First, the global context, denoted by a single forward slash. I’m using this context to set all my custom headers, including Content Security Policy (see the “Header Operations” field in the first image). Further, this is also where I’m emplacing some specific rewrite rules to help prevent certain security issues—mainly restricting client access to certain locations and files.

+ + + +

I have reproduced the header operations and rewrite rule code blocks below for copying and pasting. However, don’t just copy and paste what I’ve got and assume it will work for you. The CSP header in particular requires a lot of work to get right (there are sites and tools that can help, but it’s largely a manual process of trial and error, especially if you use Jetpack).

+ + + +
Header operations:
+
+set X-Are-Dinosaurs-Awesome HELL YES
+set Server clever girl
+set Referrer-Policy strict-origin-when-cross-origin
+set Strict-Transport-Security max-age=31536000;includeSubDomains;preload;
+set X-Content-Type-Options nosniff
+set X-XSS-Protection 1; mode=block
+set X-Frame-Options SAMEORIGIN
+set Expect-CT Expect-CT: max-age=0; report-uri='https://bigdino.report-uri.io/r/default/ct/reportOnly'
+set Content-Security-Policy default-src https:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' https: data:; object-src 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; font-src 'self' data: https://fonts.gstatic.com; upgrade-insecure-requests; frame-ancestors https://blog.bigdinosaur.org
+
+--
+
+Rewrite rules:
+
+RewriteRule ^wp-admin/includes/ - [F,L]
+RewriteRule !^wp-includes/ - [S=3]
+RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
+RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
+RewriteRule ^wp-includes/theme-compat/ - [F,L]
+
+# BEGIN Block Sensitive Files
+RewriteCond %{REQUEST_URI} error_log|wp-config-sample.php|readme.html|readme.txt|license.txt|wp-config.php|php.ini [NC]
+RewriteRule .* - [F,L]
+# END Block Sensitive Files
+
+ + + +

And then, the last image shows the rewrite rule I have set on the /wp-content/uploads directory (note URI and location). This is a simple Apache-style rewrite that blocks accessing files in uploads that might be put there to do bad things and/or execute code.

+ + + +

Then there’s the SSL tab, and that’s the last tab I had to mess with:

+ + + +
Screenshot of the "SSL" tab on the OLS web console.
The SSL tab.
+ + + +

OLS lets you configure a default server-wide SSL configuration, and you can override things at the vhost level as required with the “SSL” tab in your vhost config. I’ve got my SSL certificate and key (LetsEncrypt via acme.sh), my preferred cipher suite (adapted from Mozilla’s recommended intermediate TLS 1.2-compatible config), my preferred key exchange options and the location of my DH param file, and whatever other SSL options you want. For me, I wanted to make sure all the ALPN options are enabled to let modern browsers use as many speedy page-loading tricks as possible.

+ + +
+
Screenshot of the "Cache" page in the Litespeed Cache Wordpress plugin.
+ + +

Configuring Wordpress itself

+ + + +

Turning away from OLS, the last thing to look at is the stuff you have to do to Wordpress itself, and that, fortunately, isn’t a huge amount. The main thing is to get WP up and running and then install the LiteSpeed Cache WP plugin so that OLS can do its thing.

+ + + +

At right are the OLS cache settings I’ve found work best for me with Wordpress. One particular deviation from standard that I’ve had to make is changing the “Cache Login Page” setting to “off,” because having the login page served from cache was causing some downright weird behavior with Duo, my preferred two-factor authentication solution.

+ + + +

In general, it’s advisable to keep as many of these turned on as you can without actually screwing anything up. If you have a lot of logged-in visitors to your site, “Cache Logged-in Users” can be a critical setting for you; if you don’t, best to leave it off. I also am not really certain I’d ever want to cache REST API responses, but the option is there if you think you need it.

+ + + +

There’s one more thing in the LS Cache plugin I had to screw with that I want to point out—the Object Cache.

+ + +
+
Screenshot of the "Object Cache Settings" page in the Litespeed Cache Wordpress plugin.
The Object Cache settings page in the Litespeed Cache Wordpress plugin.
+ + +

The object cache hangs on to database query results, greatly decreasing the load on your WP database. Using it requires you (or your web host) to set up an external application on your server for cache storage—either Memcached, Redis, or LiteSpeed’s modified version of Memcached, Litespeed Memcached (along with the appropriate php extension). Once the cache application is up and running and its extension is installed, the “Status” area of the page should show a green “Enabled.”

+ + + +

You’ll then need to enter the appropriate host and port details into the fields so that Wordpress can begin using the database. (You can also use Unix sockets, but I haven’t bothered yet. I should probably switch that on soon, though.)

+ + + +

Now, here’s the tricky part. If you’re only hosting a single Wordpress blog, then it doesn’t much matter whether you pick Memecached or Redis. (Litespeed Memcached’s main distinguishing feature is that it’s built to work distributed, so that’s not really a useful feature for small WP installs.) However, if you’re going to host more than one Wordpress site, Redis is the choice you want to make. The reason is because Redis can use multiple databases, while Memcached has a single store—so you can put one WP site on database 0, and the second WP site on database 1. Or, if you really, really love Memcached, you can just spin up multiple Memcached instances locally, each on different ports or sockets, and keep track of things that way.

+ + + +

And that’s it! That’s all it took to make Wordpress work right on OLS. There are probably some more small config tweaks scattered around that I did and that I’ve forgotten about, but this ought to cover everything important. Enjoy!

+]]>
+ + + +
+ + Fixing my Valve Index’s extreme tilt + https://blog.bigdinosaur.org/fixing-my-valve-indexs-extreme-tilt/ + + + Mon, 27 Jun 2022 14:36:20 +0000 + + + + https://blog.bigdinosaur.org/?p=589 + + Read more]]> + Over the past month or so, my Valve Index has been registering an annoying amount of tilt—when I put it on and start up SteamVR, the floor in VR-land is about five degrees out-of-whack with the actual floor. No amount of room setup re-running or view re-centering would touch the issue. The only fix I could find was to break tracking on the headset by covering it with a towel, then variously aiming the headset straight up or straight down and restoring tracking. Sometimes, this would straighten out the horizon, but often not.

+ + + +

You’d think there’d be an easy way to fix the headset’s tilt, too—like, why isn’t there a SteamVR option to adjust the angle of the horizon? Why is this such a massive issue?

+ + + +

Turns out there is a way to address the problem with SteamVR—but it requires screwing around with some jank-ass command line tools. Because of course it does.

+ + + + + + + +

To make things quick, the fix that worked for me is the one outlined in this here reddit post. You’ll need to register with Valve as a developer in order to get access to the SteamVR tracking HDK, and then you’ll need to use one of the utilities in the HDK to gather some sensor samples from your Index’s inertial measurement unit (IMU). The utility has you rotate the Index to six different orientations, sampling at each, and then distills those measurements into a set of numbers indicating your accelerometer scale, your accelerometer bias, and your gyro bias.

+ + + +

You take those numbers and stuff them into a JSON file, and then you upload that JSON file to your headset with another CLI utility. And then you reboot your headset.

+ + + +

And then—well, hopefully, there is no “and then,” because that’s what fixed the issue for me. I can once again fly my spaceship without feeling like I’m sliding sideways across the deck.

+ + + +

Any chance we can get a built-in option to adjust this without having to bust out the command line, Valve?

+ + + +

Update, two days later: still broken 🙁

+ + + +

Sadly, the fix doesn’t seem to have done the trick. The tilt is back, as bad as before. I fear I’m going to have to RMA the headset. This is already my second Index—maybe the third time will be the charm.

+ + + +

Assuming I can successfully navigate the dark despairing caverns of Steam support. Wish me luck.

+]]>
+ + + +
+ + Farewell to the old stack, welcome AWS and OpenLiteSpeed + https://blog.bigdinosaur.org/farewell-to-the-old-stack-welcome-aws-and-openlitespeed/ + + + Thu, 09 Jun 2022 11:43:46 +0000 + + + + + + + https://blog.bigdinosaur.org/?p=547 + + Read more]]> + Nginx and I have a long history. BigDinosaur.org first went online some time in 2010 with a little Apache-powered homepage, and it didn’t take long for me to switch over to Nginx—probably to be contrary, more than anything else, because Nginx was a fascinating underdog that was steadily winning web server market share with its speed and flexibility. I liked it. I blogged about it. A lot. I thought I’d found a piece of software I could live with forever.

+ + + + + + + +

And don’t get me wrong—Nginx is great. But in the decade since 2010, my web hosting ambitions have grown and I’ve incurred a lot of technical debt. Nginx gave way to Nginx and Varnish, and then after the HTTPS revolution happened, Nginx and Varnish and HAProxy. For a time, things were good—Varnish is maybe a little heavy-duty for my needs, but I appreciated the crazy stuff it let me do with very fast redirects and screwing around with cookies. It made hosting Wordpress a little nutty, but my hosting strategies were working out well where I was applying them under real load over Space City Weather, which weathered a peak load of 1.5 million pageviews in a single day during Hurricane Laura’s near-miss of the Houston area in 2020.

+ + +
+
Traffic on Space City Weather during Hurricane Laura. The BigDino web stack (plus a hefty helping of Cloudflare magic) carried the day.
+ + +

But as bulletproof as the Haproxy-Varnish-Nginx stack was, as the years wore on and things evolved, it grew to be kind of a pain in the butt to maintain—especially when mixed with Cloudflare on a few of the sites I maintain. Troubleshooting issues while dealing with both a caching CDN (Cloudflare) and a local cache layer (Varnish) sometimes caused me to pull my hair out. And after a decade on the same hosting stack, I was growing curious about some of the newer options out there. Was there something I could use to host my stuff that might give around the same level of performance, but without the complexity? Could I ditch my triple application stack sandwich for something simpler?

+ + + + + + + +

Looking to OpenLiteSpeed

+ + + +

OpenLiteSpeed is the libre version of LiteSpeed Enterprise, a fancy web server with a whole bucketload of features—including native HTTP/3 support. It’s got a reputation for being very quick, especially at WordPress hosting, and part of that quickness comes from its built-in cache functionality. There are lots of benchmarks claiming that OLS’ native cache is more performant than either Varnish or Nginx’s FastCGI cache, and there’s a Wordpress plugin that automates a lot of the cache management tasks.

+ + + +

Those things were enough to get me interested, and I’ll be the first to admit that I make tech decisions mostly based on whims, so I started thinking about making the jump. Moving my personal sites would be easy—BigDinosaur.org, Fangs, and the Chronicles of George are all static sites, so they’d be easy. This blog and Dwight’s blog would provide good practice with configuring Wordpress and PHP on OpenLiteSpeed.

+ + + +

The two bits I was worried about were the BigDino/CoG Discourse forums, and Eric Berger’s Space City Weather—the former because Discourse can be a cantankerous beast, and the latter because SCW is an important site that hundreds of thousands of people in the Houston area rely on every day for weather information (especially during hurricane season). I could screw up migrating my own stuff, but SCW had to be done properly and with minimal downtime.

+ + + +

Cloud time

+ + + +

On top of that, I was looking to save myself some hosting money. Since late 2017, I’ve been paying for a dedicated server at Liquid Web for my own use, and while it’s been incredibly freeing to have as many web resources as I could ever need, it was also costing about $2,000 a year. Looking at my actual requirements in terms of RAM, CPU, storage, and bandwidth, it became pretty clear that in spite of Liquid Web’s extremely reasonable dedicated server pricing, keeping the box was like lighting money on fire.

+ + + +

AWS beckoned. I was already using an AWS EC2 t3a.medium instance to run the BigDinosaur.org e-mail server; I’d picked AWS for that because, perhaps surprisingly, the cost proved pretty reasonable. You can prepay for what Amazon calls a “reserved instance,” and a three-year prepayment for the t3a.medium-sized mail server was only $350 or so. I’d need to step up to a t3a.large instance to get 8GB of RAM—definitely necessary in a cache-heavy configuration like I wanted to run—but the pricing stayed pretty reasonable.

+ + +
+
BigDino EC2 console, with mail and web servers.
+ + +

I think EC2 gets somewhat of a bad rap among non-business hosters, in terms of both complexity and costs, but I quite like it. It is overly complex, but you can ignore most of it and just focus on the parts that you need. With reserved instance pricing being so downright reasonable, it seemed like the time to make the switch from physical to cloud hosting.

+ + + +

Moving day

+ + + +

I ended up doing the migrations spread over a full weekend. The biggest issue for me was getting my brain out of Nginx mode and forcing it into Apache mode, since OpenLiteSpeed uses Apache-style .htaccess files and Apache-style rewrites. Nginx ignores .htaccess and uses config files with a vastly different syntax and options, so it took quite a bit of googling for examples before I figured out all of the steps necessary to recreate equivalent hosting setups for each of the sites.

+ + + +

An aspect of OLS that I initially intensely disliked is its web configuration console. You can configure OLS by manually editing config files, but the preferred way to do OLS admin tasks is through the GUI. This is a pretty substantial change.

+ + + + + + + +

I’m still not a hundred percent on board with the GUI configuration, and I wish there was an easier way to clone an existing vhost as a template, but it’s also a testament to OLS’ functionality that I was able to get almost everything migrated over in a weekend.

+ + + +

Space City Weather took a little longer, but it too is now running on OLS. The only thing that didn’t quite make the jump was the BigDino Discourse instance, which I shoved onto a spare EC2 reserved instance and fronted with good ol’ Nginx as a reverse proxy, because that’s pretty easy and at that point I was tired of difficult things.

+ + + +

And so here we are, with our new OLS-based stack. It’s been a few days and nothing’s caught on fire yet, so that’s pretty good. In a lot of ways I’m going to miss HAProxy and Varnish and Nginx; in the ten-plus years I’ve been using that stack, I’ve gotten very familiar with wandering through those applications’ config files. I’ve lived with those programs and found out some neat tricks. Letting go of that makes me feel surprisingly wistful—it’s a good stack, though the complexity of managing new things was just getting out of hand.

+ + + +

Hopefully OLS will keep me busy for another ten years.

+]]>
+ + + +
+ + Fixing Wordpress annoyances: welcome box, lowercase “p,” please, & syntax highlighting + https://blog.bigdinosaur.org/fixing-wordpress-annoyances/ + + + Sat, 13 Feb 2021 23:44:34 +0000 + + + + https://blog.bigdinosaur.org/?p=370 + + Read more]]> +
+ + + +

So, hey, it turns out that while the grass is indeed a little bit greener over here on this side of the blogging fence, Wordpress still does a couple of things I don’t like and that need correcting. This gives me an opportunity to do that most overdone of blog posts—the “here’s how to add $THING to Wordpress!” post.

+ + + +

There are two problems we’re going to fix: the first is Automattic’s decision to make “Wordpress” (without camel-case) always render as “WordPress” (with camel-case) no matter how the author writes the word. We’re going to undo this unwanted trademark enforcement via a must-use plugin.

+ + + +

The second problem we’re going to fix is the block editor’s “Welcome Guide” popover, and its insistence at re-appearing even after being dismissed. The damn thing comes back every time I create a new post in a new browser window—I assume because for whatever reason its dismissal is recorded not in an actual site preference, but in a cookie or something. So we’re going to banish it permanently via another must-use plugin.

+ + + +

And then, since we’re already going to make a couple of must-use plugins, we’ll make one more—this one will add PrismJS-based syntax highlighting to Wordpress. Which I will be typing with a lowercase “p.”

+ + + + + + + +

Plugins, not functions.php modifications

+ + + +

We’re going to be adding our three bits of functionality as plugins—more specifically, as special plugins called “must-use plugins.” A must-use plugin is automatically loaded by Wordpress before normal plugins and cannot be disabled; must-use plugins are also displayed on their own page, rather than cluttering up the main plugin list. There are some downsides to must-use plugins, which you can read about on the must-use plugin documentation page, but none of those downsides apply to what we’re going to be doing.

+ + + +

Ah, but, you might ask, why use plugins at all? Can’t we just append whatever we want into our theme’s functions.php file? Well, we could—but there are some very good reasons not to. I’ll quote the good folks over at wpmudev:

+ + + +

At the core of WordPress lies a simple principle: design and functionality should (whenever possible) be clearly separated.

That is why we have themes and plugins; ostensibly, themes are solely responsible for design and plugins are solely responsible for functionality. One should be able to switch themes without affecting functionality, and one should be able to deactivate plugins without affecting the design.



If you understand what functions.php is for (functionality strictly related to a particular theme), you should be able to figure out what it isn’t for.


Alex Stine &  Martin Aranovitch
+ + + +

Since we’re going to be altering core functionality—specifically, neutering the Wordpress camel-case override and killing the block editor welcome box—plugins are the way to go. And while syntax highlighting is arguably something that should indeed vary from theme to theme, we’re already going to be making two plugins, so doing a third one is a matter of convenience.

+ + + +

I’ll say “Wordpress” if I want & you can’t stop me

+ + + +

The first thing to do is to get Automattic’s branding decisions out of my blog. Wordpress began automatically applying camel-case to its name with this WP core change all the way back in 2010. Fortunately, unwinding this unwanted feature just requires a bit of php.

+ + + +

I’m going to use Tom Lany’s “Remove WordPress to Wordpress filter” plugin, except instead of installing it as a normal plugin via the WP interface, I’m going to install it as a must-use plugin via the command line.

+ + + +

Must-use plugins live in a different directory from regular plugins—instead of wp-content/plugins, they go in wp-content/mu-plugins. If you don’t have a mu-plugins directory, now’s the time to create it.

+ + + +

Then, create a php file inside of mu-plugins. I called my php file lowercasepress.php, but you can call yours whatever you’d like.

+ + + +

Inside it, paste the contents of the php file inside Tom Lany’s plugin package:

+ + + +
<?php
+/*
+Plugin Name: Remove Wordpress to WordPress filter
+Plugin URI: https://tomlany.net/code/
+Description: This turns off the default filter that changes all instances of "Wordpress" to "WordPress".
+Version: 1.3
+Author: Tom Lany
+Author URI: https://tomlany.net/
+License: GPLv2 or later
+*/
+
+/* For more information about this plugin, see the readme.txt file included with the download package. */
+
+foreach ( array( 'the_content', 'the_title', 'wp_title', 'comment_text' ) as $filter ) {
+	$priority = has_filter( $filter, 'capital_P_dangit' );
+	if ( $priority !== FALSE ) {
+		remove_filter( $filter, 'capital_P_dangit', $priority );
+	}
+}
+
+?>
+ + + +

Save the file, and either reload Wordpress to have it take effect immediately, or continue on with our second plugin.

+ + + +

Begone forever, block editor welcome box

+ + + +

The “Welcome to the block editor!” popover was informative the very first time I saw it, but its insistence on appearing every time I logged in from a different browser or in incognito mode quickly turned it into a nuisance. Wordpress enthusiast Andrew Duthie felt the same way, since he wrote an excellent simple plugin to prevent the popover from ever showing up ever again.

+ + + +

We’re going to do the exact same thing with Andrew’s plugin as we did with Tom’s: stick its php file directly into our wp-content/mu-plugins directory. Here’s the code:

+ + + +
<?php
+
+/**
+ * Plugin Name: Disable NUX
+ * Plugin URI:  https://github.com/aduth/wp-disable-nux/
+ * Description: Disables "New User Experience" guides.
+ * Version:     1.1.0
+ * Author:      Andrew Duthie
+ * Author URI:  https://andrewduthie.com
+ * License:     GPLv2 or later
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+function dn_enqueue_scripts() {
+    wp_add_inline_script(
+        'wp-edit-post',
+        'wp.data.select( "core/edit-post" ).isFeatureActive( "welcomeGuide" ) && wp.data.dispatch( "core/edit-post" ).toggleFeature( "welcomeGuide" );'
+    );
+}
+add_action( 'admin_enqueue_scripts', 'dn_enqueue_scripts' );
+ + + +

Save the file and reload Wordpress to have it start working.

+ + + +

Syntax highlighting with PrismJS

+ + + +

Syntax highlighting makes me happy, so let’s get that set up. There are a few different syntax highlighter choices and a whole mess of different installation options, but we’re going to stick PrismJS (because I’m comfy with it and have used it before).

+ + + +

There’s an argument to be made that because syntax highlighting is probably something you want to vary with your theme, that any code to enable syntax highlighting should be added via editing a child theme’s functions.php. But, hey, we’re already making mu-plugins. Let’s make one more.

+ + + +
The PrismJS download page
+ + + +

The first thing to do is to grab a copy of PrismJS with the color scheme you want and the languages that you think you’ll need highlighted. You can start that process directly on the PrismJS homepage, and it’ll take you to a long list of different lexers. Pick what you want, and then download both the resulting Javascript and CSS files and place them on your server somewhere that Wordpress can see. (I created a prism directory immediately off of the Wordpress webroot, but you can put them wherever you feel is appropriate if that doesn’t work for you.)

+ + + +

Once the two files are in place, it’s time to actually create the plugin. I’ve cribbed from Jake Pfohl’s excellent (and much more comprehensive) post over at Start Blogging 101—using his method, PrismJS highlighting will be applied to any post with the tag “code” applied to it. This will save some bandwidth by not loading any of the highlighting CSS or JS for posts that don’t have any code blocks in them. Just make sure to remember to tag your code posts with “code” so they do get highlighted!

+ + + +

If you’re still following along with this implementation, create a file named prism-syntax-highlighting.php and paste the following inside of it:

+ + + +
<?php
+
+/**
+* Plugin Name: Quick Prism.JS plugin
+* Description: Adds Prism.JS-based syntax highlighting to any post with the "code" tag. Adapted from this <a href="https://startblogging101.com/how-to-add-prism-js-syntax-highlighting-wordpress/">Start Blogging 101 post</a>.
+* Author: Jake Pfohl
+* Version: 0.1
+*/
+
+/* Your code goes below here. */
+function add_prism() {
+
+    if ( is_single() && has_tag( 'code' ) ) {
+        
+        // Register prism.css file
+        wp_register_style(
+            'prismCSS', // handle name for the style 
+            '/prism/prism.css' // location of the prism.css file
+        );
+
+        // Register prism.js file
+        wp_register_script(
+            'prismJS', // handle name for the script 
+            '/prism/prism.js' // location of the prism.js file
+        );
+
+        // Enqueue the registered style and script files
+        wp_enqueue_style('prismCSS');
+        wp_enqueue_script('prismJS');
+
+    }
+}
+add_action('wp_enqueue_scripts', 'add_prism');
+
+/* Your code goes above here. */
+
+?>
+ + + +

Once you’re actually creating posts with code blocks in them, you specify the highlighting language by selecting the code block, going to its “Advanced” settings, and filling in the desired language in the “Additional CSS class(es)” box, prefaced by “lang-“. (The block above has lang-php specified.) For a complete list of language identifiers used by PrismJS, see this page.

+ + + +

And that’s it! Refresh your Wordpress dashboard and check the plug-ins menu. You should see your normal plugins listed first, and then a new page showing the must-use plugins separately:

+ + + + + + + +

And keeping things organized like this makes me happy.

+ + + +

Discuss this post on the BigDinosaur forums

+]]>
+ + + +
+ + Ah, Wordpress, we meet again + https://blog.bigdinosaur.org/ah-wordpress-we-meet-again/ + + + Thu, 11 Feb 2021 02:00:26 +0000 + + + https://blog.bigdinosaur.org/?p=353 + + Read more]]> + +
+ + +

For all my bitching about Wordpress as my personal blogging platform, I don’t think I ever actually ended up using it. I went from Grey Matter to Octopress to Ghost to a pure static site, and I thought I’d stay that way. I thought we were done with change.

+ + + +

And yet, here we are. On Wordpress.

+ + + +

How the hell did we get here?

+ + + + + + + +

I stayed away from Wordpress because I use Wordpress at work. I know its flaws and its grossness. I know the evil dark php heart lurking within. And I know how complex it is—Wordpress is more or less the Microsoft Word of blogging platforms. Odds are you’re never going to use a tenth of what Microsoft Word has to offer—odds are you just need to write a report or a letter or something. It always felt like overkill.

+ + + +

Other blogging platforms were so neat and cool. Octopress was all shiny and Ruby and fast and rewardingly complex—but ultimately it was just a theme and some automation on top of Jekyll, and it didn’t do what I really wanted.

+ + + +

Ghost, I thought, would be the coolest thing—it was made of node.js and therefore self-hosting it automatically carried a lot of geek cred, but Ghost never quite became what I wanted. There were always a couple of things that it just didn’t quite do. Variable length excerpts with formatting was a huge one. Non-hacky image layout was another. And then, when some neat new features I was interested in began to finally appear, the theme I was using stopped working with the new versions of Ghost that had the features I wanted.

+ + + +

And through it all, Wordpress was like, “Hey, I can do all this stuff already.”

+ + + +

Then along came the block editor—the editor formerly known as Gutenberg—and I knew it was time.

+ + + + + + + +

It’s clean. It’s fast. I dig how blocks work. Creating reusable blocks is fast and easy. The text entry experience is very WSYWIG and adding images and pullquotes and other cool stuff is super easy.

+ + + +

The other thing that brought me back to Wordpress was Generatepress, a lightweight theme that can be customized in more or less infinite ways. A reader over on Space City Weather suggested it, and I had such a positive experience adapting it for use over there that I decided to put it on my personal blog. It made recreating my old comfortable Ghost theme super-easy.

+ + + +

Wordpress gets a bad rap—it gets called out (incorrectly) for being super insecure, or slow, or overly complicated. It can be all of those things—if you run wild with plugins, and if your hosting situation is sub-par, or if you over-complicate things. But Wordpress is not inherently insecure (no more than any other application, anyway), or inherently slow, or inherently complicated.

+ + + +

In fact, after a couple of decades of blogging on other platforms, it feels just right.

+ + + +

Discuss this post on the BigDinosaur forums

+]]>
+ + + +
+ + An updated look at the BigDino web stack + https://blog.bigdinosaur.org/an-updated-look-at-the-bigdino-web-stack/ + + + Sun, 06 Jan 2019 13:00:00 +0000 + + + + + + https://wpblog.bigdinosaur.org/?p=6 + + Read more]]> +
+ + + +

It’s been some time since I’ve done a good ol’ infrastructure post, and the Bigdinosaur.org web stack has evolved a bit over the course of 2018. We’re still using HAProxy, Varnish, and Nginx, but the way these applications connect and how they communicate is very different from my 2017-era config. Let’s dive in!

+ + + + + + + +

The front line: HAProxy

+ + + +

HAProxy is a layer 7-aware reverse proxy and load balancer. It sits at the very top of the web stack and it’s the thing you as a visitor first interact with. I’m using HAProxy primarily for SSL termination for all of the sites hosted on the BigDino web server—in other words, whether you’re connecting to Fangsthe Chronicles of George, this blog, or whatever else I host, you’re talking to HAProxy first.

+ + + +

The HAProxy configuration I’m using is as follows:

+ + + +
global
+	log /dev/log	local0
+	log /dev/log	local1 notice
+	chroot /var/lib/haproxy
+	stats timeout 30s
+	user haproxy
+	group haproxy
+	daemon
+	nbthread 4
+	tune.ssl.cachesize 1000000
+
+	ca-base [redacted]
+	crt-base [redacted]
+
+	ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
+	ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
+	ssl-dh-param-file [redacted]/dhparam.pem
+
+defaults
+	log	global
+	mode    http
+	option  tcpka
+	option  dontlognull
+	option  httplog
+	option  tcp-smart-connect
+	option  splice-auto
+	timeout connect 5000
+	timeout client  50000
+	timeout server  50000
+	errorfile 400 /etc/haproxy/errors/400.http
+	errorfile 403 /etc/haproxy/errors/403.http
+	errorfile 408 /etc/haproxy/errors/408.http
+	errorfile 500 /etc/haproxy/errors/500.http
+	errorfile 502 /etc/haproxy/errors/502.http
+	errorfile 503 /etc/haproxy/errors/503.http
+	errorfile 504 /etc/haproxy/errors/504.http
+
+frontend unifiedfront
+	bind *:80,:::80 v6only tfo
+	## HTTP to HTTPS redirect
+	acl plain ssl_fc,not
+	http-request redirect scheme https if plain
+	bind *:443,:::443 v6only tfo ssl crt [several certificates redacted] ecdhe secp384r1 alpn h2,http/1.1
+	default_backend tovarnish
+
+backend tovarnish
+	## Set real IP if cloudflare
+	acl cloudy src 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 104.16.0.0/12 108.162.192.0/18 131.0.72.0/22 141.101.64.0/18 162.158.0.0/15 172.64.0.0/13 173.245.48.0/20 188.114.96.0/20 190.93.240.0/20 197.234.240.0/22 198.41.128.0/17 2400:cb00::/32 2405:b500::/32 2606:4700::/32 2803:f800::/32 2c0f:f248::/32 2a06:98c0::/29	
+	http-request set-header X-Client-IP %[req.hdr(CF-Connecting-IP)] if cloudy
+	http-request set-header X-Client-IP %[src] if !cloudy
+	http-request add-header X-Forwarded-Proto https
+	server varnish /dev/varnish-listen.sock check
+ + + +

(If you’re curious about any of the specific config options, you can look them up in the HAProxy documentation.)

+ + + +

Beyond SSL termination, HAProxy is also listening on TCP port 80 for regular HTTP requests, and redirecting them to HTTPS bef0re they get down to the web server.

+ + + +

As of version 1.9, HAProxy support downstream communication via unix domain sockets, and one of my goals with the latest configuration was to go UDS end-to-end for the entire stack. This presents some configuration challenges, since going UDS means that you have to be particularly observant about keeping track of client IP addresses.

+ + + +

Along those lines, observant readers might notice that the backend configuration section has an extra ACL and does some header voodoo. I want to specifically capture the client’s real IP address so that the lower layers of the stack can see it (and, where appropriate, log it). Rather than trusting a provided X-Forwarded-For header, I’m setting my own.

+ + + +

Because BigDino uses Cloudflare as a CDN for a couple of sites, I’m extracting the contents of the Cloudflare-provided CF-Connecting-IP header on traffic that originates from Cloudflare’s IP addresses; for traffic originating everywhere else, I’m setting the IP address to what HAProxy sees as the client’s source. The reason for doing this will be explained more thoroughly in the next section, but the quick explanation is that Varnish has changed its X-Forwarded-For behavior and I’m working around that change.

+ + + +

But why have a distinct layer for SSL termination? Why not let this happen at the web server layer like most howtos recommend?

+ + + +

Ahhh, that is indeed a good question. The answer is that by sticking an SSL terminator at the very top of the stack, I can also use a caching layer.

+ + + +

Gimme the cache: Varnish

+ + + +

HAProxy sends traffic down the stack to Varnish, a fast caching reverse proxy. Varnish holds objects in RAM and serves them up much faster than the actual web servers and applications running at lower layers in the stack, and makes a huge overall contribution to site performance.

+ + + +

But Varnish can’t (easily) cache encrypted traffic, because encrypted traffic looks like random garbage. Traditionally, if you wanted to use cache, you had to either forego encryption or get creative; I chose to get creative and simply break the web stack into layers so that I could have SSL for everything and also cache for everything.

+ + + +

Varnish’s configuration looks like this:

+ + + +
# Combined VCL for all hosts on this server
+###########################################
+
+# We're using unix sockets, so we need to declare that we're using VCL 4.1
+vcl 4.1;
+
+# Backend definition
+backend default {
+	.path = "/var/run/nginx-default.sock";
+	.connect_timeout = 600s;
+	.first_byte_timeout = 600s;
+	.between_bytes_timeout = 600s;
+	.max_connections = 800;
+}
+
+# HTTP/2 backend
+backend h2 {
+	.path = "/var/run/nginx-h2.sock";
+	.connect_timeout = 600s;
+	.first_byte_timeout = 600s;
+	.between_bytes_timeout = 600s;
+	.max_connections = 800;
+}
+
+# Import Varnish Standard Module so I can serve custom error pages
+import std;
+
+sub vcl_recv {
+
+	# Because we're using unix domain sockets for basically everything, we  
+	# need to work around Varnish's default behavior of appending "client.ip" 
+	# (which will always be 0.0.0.0 with UDS) to X-Forwarded-For. Since 
+	# HAProxy is sending us the real IP in X-Client-IP and it's vetted against
+	# Cloudflare's IP list, we can just toss whatever's in X-Forwaded-For
+	# and re-set it from the known-good X-Client-IP. Whew.
+  
+	unset req.http.X-Forwarded-For;
+	set req.http.X-Forwarded-For = req.http.X-Client-IP;
+
+	if (req.method == "POST") {
+		return (pass);
+	}
+
+	if (req.http.upgrade ~ "(?i)websocket") {
+		return (pipe);
+	}
+
+	# Send HTTP/2 requests to the proper backend
+	if (req.http.protocol ~ "HTTP/2") {
+		set req.backend_hint = h2;
+	}
+	else {
+		set req.backend_hint = default;
+	}
+
+	# No PHP for Bigdino, Fangs, and CoG
+	if (req.http.host ~"(chroniclesofgeorge.com|bigdinosaur.org|fangs.ink)") {
+		if (req.url ~ "\.php(\?.*)?$") {
+			return (synth(700));
+		}
+	}
+
+	# Cache only static assets in Discourse assets dir & pass everything else
+	if (req.http.host ~"discourse.bigdinosaur.org") {
+		if (!(req.url ~ "(^/uploads/|^/assets/|^/user_avatar/)" )) {							  
+			return (pass);
+		}
+	}
+
+	# Ignore traffic to Ghost blog amin stuff
+	if (req.http.host ~"blog.bigdinosaur.org") {
+		if (req.url ~ "^/(api|signout)") {
+			return (pass);
+		}
+		elseif (req.url ~ "^/ghost" && (req.url !~ "^/ghost/(img|css|fonts)")) {
+			return (pass);
+		}
+	}
+
+	# Remove cookies from things that should be static, if any are set
+	if (req.url ~ "\.(png|gif|jpg|swf|css|js|ico|css|js|woff|ttf|eot|svg)(\?.*|)$") {
+		unset req.http.Cookie;
+		return (hash);
+	}
+	if (req.url ~ "^/images") {
+		unset req.http.cookie;
+		return (hash);
+	}
+
+	# Remove Google Analytics and Piwik cookies so pages can be cached
+	if (req.http.Cookie) {
+		set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js)=[^;]*", "");
+		set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_pk_(ses|id)[\.a-z0-9]*)=[^;]*", "");
+	}
+	if (req.http.Cookie == "") {
+		unset req.http.Cookie;
+	}
+}
+
+sub vcl_pass {
+	set req.http.connection = "close";
+}
+
+sub vcl_pipe {
+	if (req.http.upgrade) {
+		set bereq.http.upgrade = req.http.upgrade;
+	}
+}
+
+sub vcl_backend_response {
+	set beresp.http.x-url = bereq.url;
+	set beresp.http.X-Host = bereq.http.host;
+
+	# Strip cookies before static items are inserted into cache.
+	if (bereq.url ~ "\.(png|gif|jpg|swf|css|js|ico|html|htm|woff|eof|ttf|svg)$") {
+		unset beresp.http.set-cookie;
+	}
+	if (bereq.http.host ~ "www.chroniclesofgeorge.com") {
+		set beresp.ttl = 1008h;
+	}
+	else {
+		if (beresp.ttl < 24h) {
+			if (beresp.http.Cache-Control ~ "(private|no-cache|no-store)") {
+				set beresp.ttl = 60s;
+			}
+			else {
+				set beresp.ttl = 24h;
+			}
+		}
+	}
+}
+
+sub vcl_deliver {
+
+	# Display hit/miss info
+	if (obj.hits > 0) {
+		set resp.http.X-Cache = "HIT";
+	}
+	else {
+		set resp.http.X-Cache = "MISS";
+	}
+	# Remove the Varnish header
+	unset resp.http.X-Varnish;
+	unset resp.http.Via;
+	unset resp.http.X-Powered-By;
+	unset resp.http.Server;
+
+	# HTTP headers for all sites
+	set resp.http.X-Are-Dinosaurs-Awesome = "HELL YES";
+	set resp.http.Server = "on fire";
+	set resp.http.X-Hack = "don't hack me bro";
+	set resp.http.Referrer-Policy = "strict-origin-when-cross-origin";
+	set resp.http.Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload;";
+	set resp.http.X-Content-Type-Options = "nosniff";
+	set resp.http.X-XSS-Protection = "1; mode=block";
+	set resp.http.X-Frame-Options = "DENY";
+	set resp.http.Expect-CT = {"Expect-CT: max-age=0; report-uri="https://bigdino.report-uri.io/r/default/ct/reportOnly""};
+
+	# Site-specific HTTP headers
+	if (req.http.host ~ "fangs.ink" ) {
+		set resp.http.Content-Security-Policy = "default-src https:; img-src 'self' https: data:; object-src 'none'; script-src 'self' https://analytics.bigdinosaur.net https://ajax.googleapis.com 'unsafe-inline'; font-src 'self'; upgrade-insecure-requests; frame-ancestors 'none'";
+	}
+
+	if (req.http.host ~ "www.bigdinosaur.org" ) {
+		set resp.http.Content-Security-Policy = "default-src https:; img-src 'self' https: data:; object-src 'none'; script-src 'self' https://analytics.bigdinosaur.net https://ajax.googleapis.com 'unsafe-inline'; font-src 'self'; upgrade-insecure-requests; frame-ancestors 'none'";
+	}
+
+	if (req.http.host ~ "blog.bigdinosaur.org" ) {
+		set resp.http.Content-Security-Policy = "default-src https:; style-src 'self' 'unsafe-inline' https://maxcdn.bootstrapcdn.com/; img-src 'self' https: data:; object-src 'none'; script-src 'self' https://analytics.bigdinosaur.net https://code.jquery.com/ 'unsafe-inline' 'unsafe-eval' ; font-src 'self' https://maxcdn.bootstrapcdn.com/; upgrade-insecure-requests; frame-ancestors 'none'";
+	}
+
+	if (req.http.host ~ "www.chroniclesofgeorge.com" ) {
+		set resp.http.Content-Security-Policy = "default-src https:; style-src 'self'; img-src 'self' https: data:; object-src 'none'; script-src 'self' https://analytics.bigdinosaur.net https://ajax.googleapis.com 'unsafe-inline'; font-src 'self'; upgrade-insecure-requests; frame-ancestors 'none'";
+	}
+
+	# Remove custom error header
+	unset resp.http.MyError;
+	return (deliver);
+}
+
+sub vcl_synth {
+
+	if (resp.status == 700) {
+		set resp.status = 404;
+		set resp.reason = "Not Found";
+		synthetic ( {"Ain't no PHP on this server so fuck off with that"});
+		return(deliver);
+	}
+
+	if (resp.status == 405) {
+		set resp.http.Content-Type = "text/html; charset=utf-8";
+		set resp.http.MyError = std.fileread("/var/www/error/varnisherr.html");
+		synthetic(resp.http.MyError);
+		return(deliver);
+	}
+}
+ + + +

Before we break that down, though, I’ve also modified Varnish’s startup options so that it listens on a unix socket rather than a TCP port. Since I’m on Ubuntu 16.04 server, that means setting a systemctl override, which you can do with the following command:

+ + + +
$ sudo systemctl edit varnish.service
+ + + +

This brings up an empty nano edit window. I’ve added the following three lines, which unset the Varnish service’s ExecStart parameter and replace it with a new line specifying a listen socket and enabling HTTP/2:

+ + + +
[Service]
+ExecStart=
+ExecStart=/usr/sbin/varnishd -a /var/lib/haproxy/dev/varnish-listen.sock,group=haproxy,mode=660 -T localhost:6082 -f /etc/varnish/default.vcl -S [redacted] -s malloc,1G -p feature=+http2
+ + + +

The one wrinkle here is that HAProxy starts itself in a chroot jail, and in order to use UDS, I have to have Varnish create its listen socket inside of HAProxy’s chroot jail and also make sure the socket is created with appropriate permissions. (Figuring out this specific issue took me a lot longer than it should have.)

+ + + +

Now, looking at the Varnish VCL, you’ll notice it starts off with two back-ends—this is how I’m supporting HTTP/2 down through the stack. The first backend is for non-HTTP/2 traffic and the second is for HTTP/2. Varnish makes the call on what traffic to send where with this bit of code in my vcl_recv sub:

+ + + +
# Send HTTP/2 requests to the proper backend
+	if (req.http.protocol ~ "HTTP/2") {
+		set req.backend_hint = h2;
+	}
+	else {
+		set req.backend_hint = default;
+	} 
+ + + +

The rest of the config is mostly unremarkable. I’ve elected to use Varnish rather than HAProxy to set global and site-specific HTTP headers, since it’s just a hell of a lot easier to do it at the Varnish level. For the Expect-CT certificate transparency header, I’m leaning on Scott Helme’s invaluable Report URI service.

+ + + +

Since I’m also (finally) not running PHP applications on the BigDino server (with the exception of Matomo, which has its own analytics-specific domain, I’m also capturing any requests for PHP files and serving up a fast Varnish-synthesized 404 message. This shifts the load of answering skr1pt k1dd13s’ incessant PHP bot requests into the cache layer, where there’s effectively no penalty to synthesizing up a fast error message.

+ + + +

Nginx under all

+ + + +

Below Varnish sits the actual web server: Nginx. The Nginx main configuration is mostly unremarkable:

+ + + +
user www-data;
+worker_processes auto;
+pid /run/nginx.pid;
+include /etc/nginx/modules-enabled/*.conf;
+
+events {
+	worker_connections 1024;
+	use epoll;
+	multi_accept on;
+}
+
+http {
+	# Basic Settings
+	sendfile on;
+	tcp_nopush on;
+	tcp_nodelay on;
+	types_hash_max_size 2048;
+	server_tokens off;
+	port_in_redirect off;
+	server_name_in_redirect off;
+
+	include /etc/nginx/mime.types;
+	default_type application/octet-stream;
+
+	## Legacy SSL settings
+	ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
+	ssl_prefer_server_ciphers on;
+	ssl_session_cache shared:SSL:10m;
+	ssl_buffer_size 4k;
+
+	# Logging Settings
+	log_format show_hosts '[$time_local] $http_x_forwarded_for - $server_name: $request $status Referrer: "$http_referer" UA: "$http_user_agent"';
+	access_log /var/log/nginx/access.log show_hosts;
+	error_log /var/log/nginx/error.log error;
+
+	# Gzip Settings
+	gzip on;
+	gzip_disable "msie6";
+	gzip_min_length 1100;
+	gzip_vary on;
+	gzip_proxied any;
+	gzip_buffers 16 8k;
+	gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/rss+xml text/javascript image/  svg+xml application/x-font-ttf font/opentype application/vnd.ms-fontobject application/javascript; 
+
+	# Security stuff for size limits and buffer overflows
+	client_max_body_size  0; 
+	client_body_timeout   10;
+	client_header_timeout 10;
+	keepalive_timeout     10 10;
+	send_timeout          10;
+
+	# Websocket compatibility
+	map $http_upgrade $connection_upgrade {
+		default Upgrade;
+		''      close;
+	}
+	map $http_x_forwarded_proto $thescheme {
+		default $scheme;
+		https https;
+	}
+
+	# GeoIP stuff
+	geoip_country /usr/local/share/GeoIP/GeoIP.dat;
+	geoip_city /usr/local/share/GeoIP/GeoLiteCity.dat;
+
+	# Virtual Host Configs
+	include /etc/nginx/conf.d/*.conf;
+	include /etc/nginx/sites-enabled/*;
+
+	server_names_hash_bucket_size 128;
+}
+ + + +

The only thing I have in the conf.d directory is a variable definition for php-fpm to make vhost PHP configurations a little easier to read:

+ + + +
upstream php7-fpm-sock {
+	server unix:/var/run/php/php7.3-fpm.sock;
+}
+ + + +

There are a bunch of vhosts running on the server, all of which share similar configurations. The vhost file for this blog, for example, looks like this:

+ + + +

There are a bunch of vhosts running on the server, all of which share similar configurations. The vhost file for this blog, for example, looks like this:

+ + + +
server {
+	server_name  blog.bigdinosaur.org;
+	listen unix:/var/run/nginx-default.sock;
+	listen unix:/var/run/nginx-h2.sock http2;
+	root /var/www-10ghost/;
+	index index.js index.html;
+	autoindex off; 
+
+	location = /.well-known/security.txt { allow all; }
+	location = /.well-known/security.txt.sig { allow all; }
+
+	location / {
+		proxy_pass http://localhost:2368/;
+		proxy_set_header Host $host;
+		proxy_set_header Upgrade $http_upgrade;
+		proxy_set_header Connection "Upgrade";
+	}
+}
+ + + +

The important bits here are the two listen sockets; the second one, with HTTP/2 enabled, is the one that gets used for pretty much everything. Nginx fortunately doesn’t need to be in charge of SSL/TLS in order for it to speak HTTP/2 to clients, which is how this blog shows up as fully HTTP/2.

+ + + +

Annoyingly, Ghost’s support for UDS is a little fiddly, so I still have to use a TCP port to proxy blog requests. I hope at some point in the future that the Ghost devs will implement proper UDS support, but I understand it’s not a huge priority.

+ + + +

And that’s the BigDino web stack, as of the beginning of 2019!

+ + + +

Discuss this post on the BigDinosaur forums

+]]>
+ + + +
+ + Pronunciation of names and places in C.J. Cherryh’s “Foreigner” books + https://blog.bigdinosaur.org/pronunciation-of-names-and-places-in-c-j-cherryhs-foreigner-books/ + + + Mon, 09 Jul 2018 12:00:00 +0000 + + https://wpblog.bigdinosaur.org/?p=15 + + Read more]]> +
+ + + +

Author C. J. Cherryh is one of the last great living masters of science fiction, easily on a par with ClarkeHerbert, or Wolfe. Her strength is in building worlds populated with believable humans and non-humans, and then writing those characters in such a way that the reader ends up deeply empathizing with them—even the most alien of aliens.

+ + + +

Her best-known works are the long-running Alliance-Union novels, which taken together describe a war-filled future history epic of the expansion of humankind off of Earth and into the rest of the galaxy. However, for the past couple of decades Cherryh has been focusing on a different series altogether: the Foreigner books.

+ + + +

The series (starting with Foreigner) tells the tale of a lost human colony ship forced to take permanent refuge at far-off world populated by heretofore undiscovered aliens: the three-meter tall black-skinned atevi. Atevi don’t experience the same emotions as humans and have an innate perception of numbers that’s described as roughly analogous to the human perception of color. Humanity and atevi are similar enough that they quickly establish cordial relations, and different enough that war is inevitable.

+ + + +

But I’m not going to do a whole series recap—we’d be here forever, since the series at this point consists of 19 books with at least two more to come. Instead, I want to focus on a very touchy subject, and one about which readers of the books will no doubt have very spiky feelings: pronunciation of names and places.

+ + + +

N.B. Folks who haven’t read at least one Foreigner book should probably bail on this entry, because this post probably isn’t going to be super-interesting unless you’ve already got some Ragi words bouncing around in your head.

+ + + + + + + +

Look who’s talking

+ + + +

Cherryh includes a pronunciation guide and limited glossary in the end of some of the books, but the primary sources of canonical pronunciation are the narrators of the two different sets of audiobooks.

+ + + +

The one most familiar to readers (well, listeners) will likely be Daniel Thomas May, who narrates the Audible versions of the audiobooks. May is outstanding with his characterizations and performance, and he’s also extraordinarily consistent in his pronunciations of a whole mess of alien words—but I’m not convinced he’s correct in many of his pronunciation choices.

+ + + +

The narrator who gets it right, I believe, is Kimberly Schraf. Schraf narrates the National Library Service talking book versions of the first ten titles in the series. However, the only legitimate way to listen to Schraf’s versions is to be sight-impaired, as NLS talking books are only available to sight-impaired individuals. (You can also get them by, ahem, sailing the high seas, as it were, but you’re on your own for that.)

+ + + +

Specific examples: names and accents

+ + + +

May’s Banichi is buh-nee'-che, while Schraf’s is bah'-nih-chee. Schraf’s pronunciation mirrors the phonetic spelling given in the Foreigner glossary and also follows Cherry’s rules for which syllable to accent depending on vowel length. Schraf is objectively correct here, since Cherryh’s glossary is by definition canonical.

+ + + +

(The differences in pronunciation on this particular one might arise from the fact that May treats the middle syllable in Banichi’s name the same as the middle syllable in Algini’s name—both long e sounds. On one hand, this seems like a valid alternate pronunciation, like “dayta” and “dahta,” perhaps. On the other hand, how do you pronounce the name of the nine foot tall assassin? It’s kind of like the joke with the 800 lb gorilla—”any way he wants”…)

+ + + +

May’s Illisidi is ih-lee'-sih-dee, while Schraf’s is ill-uh-see'-tee. Again, at least as near as I can parse the rules, Schraf is “correct” (though correctness appears mutable, based on the glossary’s many caveats). And, again, the difference here potentially comes from the choice of vowel pronunciation—long e for May, short e for Schraf.

+ + + +

May’s Cajeiri is kah-zher-ee', while Schraf almost over-humanizes it to kuh-jer'-ee (cuh-Jerry!). Though May’s choice gives the name a more alien gravitas, Schraf’s much better choice follows from the canonical “Jeri-Ji” diminutive. (And here I must guess at the spelling of “Jeri-Ji,” because the only books I have with him in them are of the audio variety!) However, while I believe Schraf’s pronunciation is closer to the truth, she also makes the j sound overly J-like, defying the glossary’s notes that j is more like a midway between ch and zh (Schraf does this with Jagoas well, giving it almost a hard j instead of May’s soft zh).

+ + + +

The word paidhi is one of the most important words in the entire series, and May and Schraf differ fundamentally in their approaches to it. May gives it a spectacular flub, rendering it pie-dee', with a long i and long e and a heavy accent on the second syllable. This ignores explicit guidance given in the glossary on how to pronounce the word with a palatal h. Schraf, on the other hand, follows the glossary religiously with pite'-hee—still two long vowels, but with the accent in the right place and with almost a hiss on the last syllable.

+ + + +

There are many others, including vastly different takes on aishidi’tat (May: aye-shade'-ee-taht , Schraf: eye'-shu-tee dad), mecheiti (May: meh-cheh-tee', Schraf meh-chay'-tee), and Bu’Javid (May: boo-zha-veed', Schraf: boo-jay'-vit), but the thing that continually resurfaces in May v. Schraff is May’s insistence on stressing the accent on the final syllables of many words, especially if they end in an i sound, or if they trail a -ma or -ji suffix behind. May’s Atevi is ah-teh-vee', while Schraf’s take is the dictionary-strict ah'-teh-veeCenedi is a doozy—May plays fast and loose with sen-eh-dee', presumably choosing to interpret the opening c as soft in order to avoid saying the name as “Kennedy.” Shraf on the other hand goes all-in with ken'-eh-tee, basically just saying “Kennedy” with a t instead of a d.

+ + + +

I’d guess that May’s insistence on stressing trailing -i sounds is a stylistic choice in order to add some recognizable “foreignness” to Ragi words. It works very well and he’s extraordinarily consistent in his adherence to the practice, but in many cases he’s elected to say a word in a demonstrably incorrect way in order to stick with it.

+ + + +

D, T, or something in between?

+ + + +

Which brings me to the second huge difference, and what might be Schraf’s only real misstep: she clings so religiously to the note in the glossary about making d indistinguishable from t that to the listener it often sounds as if she’s purposefully swapping d for t and vice versa.

+ + + +

After listening to so many Schraf books, that D/T thing was the hardest adjustment to make when I got to May instead. It impacts so many important words—words that after hearing so often one way, I have a difficult time spelling correctly. To me, Tabini should always sound like it’s almost got a D in front, but May gives it a hard TDamiri in my head should sound more like Schraf’s take, “Tamiri.” Tano, Illisidi, Tatiseigi, Bindanda, on and on and on—Schraf renders them “Dano,” “Illisiti,” “Dadiseigi,” and “Bintanta.”

+ + + +

The human island

+ + + +

The third is puzzling and difficult to figure out: is “Mospheira” a human word, or a Ragi one? Because May speaks it as I did when I first picked up the print version of Foreigner so many years ago, and the way that Schraf pronounces it in the audiobook version of Foreignermos-fay'-rah, with the “ph” treated as the human phoneme. But starting in Invader, Schraf switches to a very strict Ragi reading of the pronunciation (again, going by Cherryh’s glossary notes as ultimately canonical)—she says mos-pay'-rah, treating the “ph” as a separate “p” and “h” (and also treating the ‘h’ as palatal, exactly per the dictionary, so it comes out almost with a hiss). This continues to the name of the spoken human language—May says mos-fay-ee', cramming a whole third syllable in there, while Schraf goes with mos'-pay, two syllables and palatal.

+ + + +

A grab bag of other words

+ + + +

Without opining any further, here are some additional words and their differing pronunciations between the two narrators. I’ll indicate which one I believe is correct—though, spoiler alert, I’m going to pick Schraf for most things.

+ + + +

Aiji
May: aye-zhee'
Schraf: aye'-jee (preferred, other than the j)

+ + + +

Ajuri
May: ah-zhur-ee'
Schraf: ah-jur'-ee (preferred, other than the j)

+ + + +

Antaro
May: on'-tar-oh
Schraf: on-dar'-oh' (believed correct)

+ + + +

‌‌Baji-Naji
May: bah-zhee' nah-zhee' (j correct)
Schraf: bah'-jee nah'-jee (accents correct)

+ + + +

Bindanda‌
May: bin-dahn'-dah
Schraf: bin-tahn'-tah

Hasdrawad

May: has'-drah-wad
Schraf: has'-trah-wat

+ + + +

Jegari
May: zheh-gar-ee'
Schraf: jeh-gar'-ee (preferred, other than the j)

+ + + +

‌Lucasi
May: loo-cah-see'
Schraf: loo'-cah-see (believed correct)

+ + + +

Machigi‌
May: mah-chig-ee'
Schraf: mah'-chig-ee (believed correct)

+ + + +

Machimi
May: mah-shee-mee'
Schraf: mah-chee'-mee (believed correct)

+ + + +

Man’chi
May: man-chee'
Schraf: man'-chee (believed correct)

+ + + +

Nadi
May: nah-dee'
Schraf: nah'-dee (This one’s tricky, because per the glossary the accent changes, but Schraf’s choice is generally more correct.)

+ + + +

Najida
May: nah-zhee-dah'
Schraf: nah-jee'-tah (preferred, other than the j)

+ + + +

(Madam) Saidin
May: sy-deen' (long i in first syllable)
Schraf: sy'-teen (long i in first syllable—more correct than May, I believe)

+ + + +

Shejidan
May: shay-zhee-dahn'
Schraf: shay'-jee-tahn (more correct, I think)

+ + + +

‌‌Tabini
May: tuh-bee'-nee
Schraf: duh-bee'-nee

+ + + +

‌‌‌‌Tano
May: tah'-no
Schraf: dah'-no

+ + + +

Tatiseigi
May: tah-teh-say'-gee
Schraf: dah-deh-say'-gee

+ + + +

Tashrid
May: tash-reed'
Schraf: dash'-rit (believed correct)

+ + + +

Vejico
May: vay-zhee-ko'
Schraf: veh-jee'-ko (preferred, other than the j)

+ + + +

Discuss this post on the BigDinosaur forums

+]]>
+ + + +
+ + Farewell to HPKP, hello to DNS-01 and ECDSA + https://blog.bigdinosaur.org/farewell-to-hpkp-hello-to-dns-01-and-ecdsa/ + + + Sun, 18 Feb 2018 13:00:00 +0000 + + + + + + https://wpblog.bigdinosaur.org/?p=18 + + Read more]]> +
+ + + +

A few months back I switched on HTTP public key pinning, a security scheme designed to make it more difficult for attackers to do nefarious things with the BigDino web server. HPKP is difficult to implement and comes with a long list of configuration pitfalls—and, as of today, I’m done with it.

+ + + +

Sixty-one days ago I stopped sending out HPKP headers, which I’d set to a sixty day duration. This morning, I ripped out the scaffolding of Certbot scripts and cron jobs that kept LetsEncrypt running properly with HPKP, and replaced them all with the blissful simplicity of Neilpang’s acme.sh coupled with DNS-based validation.

+ + + + + + + +

HPKP is dead

+ + + +

The primary reason I wanted to drop HPKP is because I’m terribly interested in taking advantage of LetsEncrypt’s almost-here wildcard certificates, and LE will only allow wildcard certs with DNS validation. In trying to figure out DNS validation, I quickly realized that it would further complicate my already over-complicated LetsEncrypt setup, and that I should probably be looking at ways to simplify things, rather than complexificating them.

+ + + +

My decision was made a lot easier by the fact that as of May 2018, Chrome will no longer support HPKP, leaving Firefox as the only important browser with HPKP support. About 50% of visitors to all of this server’s web sites are running Chrome, and putting time and effort into supporting something that Chrome doesn’t do anymore is a waste of time.

+ + + +

HPKP is like a loaded gun—if you don’t take care with your implementation, you could wind up killing your site. If you don’t take care with your maintenance, you could end up killing your site. And, if you put too much confidence in its ability to protect you from malicious actors and not enough time on an overall defense-in-depth strategy…you could end up killing your site.

+ + + +

So goodbye, HPKP. I won’t miss how much you complicated my life.

+ + + +

Acme DNS-01 validation: it’s so good

+ + + +

At the same time, I’ve dropped Certbot and HTTP-01 validation both from my LetsEncrypt setup. Anyone dealing with LE on a server with more than the most basic configuration knows that the default LE client (good old Certbot) can be a bitch to work with once you go off the beaten path. Fortunately, there are excellent Certbot alternatives out there, and my favorite by a mile is acme.sh, which you can grab via Github.

+ + + +

In addition to coming with its own built-in automation, the acme.sh script features outstanding support for DNS-01 validation, which allows LetsEncrypt to check your ownership of a domain name by programatically setting and then checking a TXT record in your DNS zone file. The script supports a wide variety of DNS providers, including Cloudflare, which is my current preferred DNS solution.

+ + + +

After running the script’s installation process and getting it set up, issuing my first certificate and automating its renewal requires only a single command:

+ + + +
$ acme.sh --issue --keylength ec-384 --dns dns_cf \
+-d bigdinosaur.org -d www.bigdinosaur.org --post-hook \
+/usr/local/bin/acme-bigdino-post.sh
+ + + +

SANs are supplied with multiple instances of the -d parameter—here, for example, I’m issuing the certificate for both bigdinosaur.org and www.bigdinosaur.org. I’m also using a 384-bit elliptical curve public/private key pair, which I’ll talk about in a moment.

+ + + +

The --post-hook script concatenates the issued certificate with its key (which I need to do because I use HAProxy for SSL termination and HAProxy prefers its certificates and keys bundled together) and then reloads HAProxy so it picks up the new certificate. The script is very short:

+ + + +
#!/bin/bash
+
+cat /home/me/.acme.sh/bigdinosaur.org_ecc/\
+{fullchain.cer,bigdinosaur.org.key} \
+> /etc/haproxy/ssl-dns/bigdinosaur.org.pem
+
+systemctl reload haproxy
+ + + +

Once you’ve successfully issued a certificate with acme.sh, the script handles renewal all on its own. It’s pretty awesome and I’m a fan.

+ + + +

Bend it like Koblitz & Miller

+ + + +

Finally, I’ve switched 100% over to elliptical curve-based keys. It seemed to be a good time to make the switch away from RSA, since I was already screwing around with stuff under the hood anyway.

+ + + +

Elliptical curve cryptography—”ECC,” or also potentially “ECDSA” for “elliptical curve digital signature algorithm”—differs from RSA in that the math is simultaneously simpler (primarily because it offers equivalent security with much smaller numbers) and (at least for now) harder to break. It uses iterative math involving the addition of points on a specific kind of curve (the eponymous “elliptical curve”), which tends to look something like this:

+ + + +
+ + + +

Rather than bloat this entry with the how and why, I’ll simply embed Computerphile’s excellent (and short!) explainer video below:

+ + + +
+ +
+ + + +

The tl;dw is that ECC depends on math that’s easy to do one way (find the point that results from performing N iterations of this function) and damn near impossible to do the other way (given a specific point, find how many iterations of this function were performed to get there).

+ + + +

Currently, LetsEncrypt allows users to generate certificates using ECDSA keypairs, but its certificate authority still signs those certificates with an RSA key. This will be changing midway through this year (at least according to LE’s current timeline) when LE rolls out ECDSA root and intermediate certificate authorities.

+ + + +

The future is here, and it looks curvey.

+ + + +

Discuss this post on the BigDinosaur forums

+]]>
+ + + +
+ + Grieving over the death of StartSSL + https://blog.bigdinosaur.org/grieving-over-the-death-of-startssl/ + + + Sun, 03 Dec 2017 13:00:00 +0000 + + + + + https://wpblog.bigdinosaur.org/?p=26 + + Read more]]> +
+ + + +

What was once the web’s best source for free SSL/TLS certificates and affordable-by-normal-humans wildcard certs is dead, killed by shitty unethical behavior by a shitty company called WoSign. So thanks, WoSign—thanks for wrecking StartCom and their StartSSL service. You destroyed something wonderful and useful to millions of people. Hope it was worth it, dicks.

+ + + +

If there’s an upside to this mess, it’s that Let’s Encrypt has mostly made StartSSL redundant. Where StartSSL was once the only place to go if you wanted free certificates, LE now fills that gap—very successfully, too. And LE will begin offering free wildcard certificates starting in 2018, so that’s another need fulfilled.

+ + + +

But man, I am going to miss the hell out of StartCom and StartSSL.

+ + + + + + + +

The one who was

+ + + +

I’ve been a happy paying StartCom customer since 2010, when I set up the first version of my dumb homepage. An article on Ars Technica by Glenn Fleishman on how to get free certificates piqued my interest, and StartCom proved to be everything the article promised—fast, friendly, and extremely affordable if you wanted to move off the free option and into class 2 validation-dependent certificates (like wildcards). They also offered a code signing certificate for a ludicrously cheap rate.

+ + + +

Things were great for years. I unequivocally recommended StartCom to anyone and everyone because there were basically no downsides. A single $60 identity validation got you unlimited wildcard certs for as many domains as you wanted.

+ + + +

There’s a line in Fight Club, when Jack is talking to Tyler, that really sort of captures this situation very well:

+ + + +
+ +
+ + + +

For folks unable or unwilling to watch the scene, here’s the quote:

+ + + +

When you buy furniture, you tell yourself, “That’s it—that’s the last sofa I’m ever gonna need. Whatever else happens, I’ve got that sofa problem handled.”

+ + + +

StartSSL was that good—it felt like I had my SSL/TLS problem definitively handled with an endgame solution, and I’d never need to deal with shopping for certs or paying gross and usurious rates for the things.

+ + + +

Enter WoSign.

+ + + +

Fucking it up for the people in the streets

+ + + +

Through a complicated set of hidden backroom dealings, Startcom was purchased (apparently in secret) by shady Chinese certificate authority WoSign. According to a detailed investigation by Mozilla, WoSign had been backdating the issue dates of some SSL/TLS certificates in order to skirt around the then-looming deadline for SHA-1 certificate deprecation.

+ + + +

In response to these shenanigans, Mozilla announced it was responding with the almost-nuclear option: Mozilla browsers would cease trusting WoSign (and StartSSL) SSL/TLS certificates issued after a certain date (the full nuclear option would have been to simply stop trusting all WoSign and StartCom certificates). GoogleMicrosoft, and Apple quickly followed.

+ + + +

Mozilla further outlined a proposed remediation plan they would accept from WoSign/StartCom; failure to follow the plan and demonstrate improvement would lead to a removal of WoSign and StartCom from the trusted root certificate authority store of every major browser on every major operating system. Perhaps unsurprisingly, WoSign was unable to demonstrate sufficient remediation and reform, and the deadlines were set. The full nuclear option had been deployed.

+ + + +

You can’t always get what you want

+ + + +

The effect has been total. StartCom struggled to come up with some alternatives, but the company’s death was inevitable—what good is a root certificate authority that no one can trust?

+ + + +

The following email showed up in my inbox this morning, marking the end of an era.

+ + + +
+ + + +

The last service I had still using my old StartSSL wildcard certificate was BigDinosaur.org’s email, and immediately prior to penning this blog post I cut that over to Let’s Encrypt, too.

+ + + +

It’s depressing, watching so wonderful a service being brought down by a few greedy assholes. Thanks, assholes.

+ + + +

But don’t judge StartCom by the actions of the rambling zombie corpse the company became after it was bought by WoSign. Recent bad behavior doesn’t erase the good of so many years of excellence, even if it does mean that the company has to take a bullet to the head.

+ + + +

Farewell, StartCom and StartSSL. You were well-loved, and you will be well-missed. The Internet is less without you.

+ + + +

Discuss this post on the BigDinosaur forums

+]]>
+ + + +
+ + Ghost hits 1.0: Reflections on three years of Ghost blogging + https://blog.bigdinosaur.org/ghost-hits-1-0-reflections-on-three-years-of-ghost-blogging/ + + + Sat, 29 Jul 2017 12:00:00 +0000 + + + + + https://wpblog.bigdinosaur.org/?p=30 + + Read more]]> +
+ + + +

In September 2013, after years of light blogging with Jekyll via Octopress, I switched to a very sexy-looking new blogging platform called Ghost. Ghost was written with Node.JS and promised to be fast and efficient, with a minimalist writing interface and a slick looking dashboard.

+ + + +

I wrote up the platform in a quick article for Ars shortly after the public beta became available. The new and shiny had won me over, and I ditched Octopress and converted everything to Ghost.

+ + + +

But development took far longer than anyone anticipated. Essential blog features like post scheduling lagged; others, like customizable excerpts or the ability to center images without resorting to manual HTML and CSS entry still haven’t shown up. And that slick dashboard? Canceled.

+ + + + + + + +
The Ghost dashboard that was promised but never happened.
+ + + +

Life with pre-1.0 Ghost

+ + + +

Coming from a static site generator made even the early Ghost betas feel luxurious—there were actual settings that weren’t in a config file! But after the honeymoon period wore off, I found myself looking enviably at WordPress (which I’m in 10+ hours a day because we use it at Ars). Ghost was nice, but decidedly barebones—and the total lack of image formatting options made it hugely problematic for me to try to shift my wife’s blog over onto Ghost, since she wants WYSIWYG image layout tools.

+ + + +

Then there was Ghost’s update procedure—an involved procedure requiring you to download a tarball and then manually copy files from it into your live site’s working directories. Ghost is also completely stupid when it comes to working intelligently with any kind of caching layer—creating or updating posts requires screwing around with Varnish bans (and Cloudflare purges, if you’re using Cloudflare).

+ + + +

But moving blogging software again would be a pain in the ass, and in spite of the annoyances, there was some cachet with having your blog on self-hosted Ghost. It stuck out in a way that self-hosted WordPress didn’t, and it was sometimes a point of pride to be different. The much-ballyhooed dashboard’s failure to launch didn’t bother me overmuch as I still had Piwik to fall back on, and for almost three years I made do with Ghost. If I’d been blogging more, perhaps I would have switched over to WordPress, but I wasn’t, and I didn’t.

+ + + +

Life with post-1.0 Ghost

+ + + +

Ghost soft-launched 1.0 last Friday, holding their release post until almost a full week had passed. From a user’s perspective things are still pretty similar—the editor has a lot of visual improvements and there’s now a night mode, which I like very much:

+ + + +
Writing this post in Ghost
+ + + +

But the backend is drastically changed. Gone is the clunky manual process for updating and the need to screw around with npm. In its place is a lovely command line tool called Ghost-CLI that handles all of that for you. Updating Ghost now requires just a single command:

+ + + +
$ sudo ghost update
+✔ Checking for latest Ghost version
+✔ Downloading and updating Ghost to v1.1.0
+Running sudo command: systemctl stop ghost-bigdinosaur-org
+✔ Stopping Ghost
+✔ Linking latest Ghost and recording versions
+Running sudo command: -E -u ghost /usr/local/lib/node_modules/ghost-cli/node_modules/.bin/knex-migrator-migrate --init --mgpath /var/www-ghost/current
+[2017-07-29 10:42:28] INFO Finished database migration! 
+✔ Running database migrations
+✔ Validating config
+Running sudo command: systemctl start ghost-bigdinosaur-org
+✔ Restarting Ghost
+ + + +

The majority of the changes appear minor unless you read the development blog; at that point, it becomes obvious how much of a change Ghost has undergone.

+ + + +

Living with Ghost now for the past week has been a very different experience, since the application now behaves as a proper systemd citizen on Ubuntu server and can update itself. From this admin’s perspective, Ghost is finally the blogging app that I wanted it to be three years ago.

+ + + +

But is it better than WordPress or Jekyll?

+ + + +

That’s impossible to say, since “better” means something different to everyone. But I’ll list Ghost 1.0’s downsides first:

+ + + +
  • Ghost still remains blissfully unaware of—and unable to interact with—any caching layer, either on- or off-box
  • Ghost has no two-factor authentication option
  • Ghost has no image layout tools at all—your image is going to show up left-aligned unless you manually define some classes in CSS and manually enter some HTML into the edit window for each picture.
  • Ghost still lacks any kind of support for many basic necessities required for professional writing, including:
    • Image captioning (though you can DIY this with more custom CSS and HTML)
    • Pullquotes (again, DIY-able with custom CSS and HTML)
    • Variable-length excerpts
    • Pagination & multi-page posts
+ + + +

Additionally—at least for now—Ghost has no usable plugin system and no plugin library. You can download a bunch of similar-looking themes all day long, but there’s nothing like WordPress’s extensive plugin library; extending Ghost’s functionality requires coding stuff yourself.

+ + + +

The lack of plugins isn’t necessarily a bad thing, since that same enormous library of plugins is most of what gives WordPress a reputation for being about as secure as a broken sieve. Ghost doens’t have that problem—not yet, at least.

+ + + +

Where Ghost wins, though, is in simplicity and focus. It’s not as cut-down as Jekyll and it will never be as fast, since Jekyll blogs are purely static assets with no dynamic generation, but Jekyll also has no real interface—you do your blogging with an external composer of your choice. Ghost’s 1.0 editor is just a joy to work with—it’s almost as sexy as Dillinger, and Dillinger is mighty fine.

+ + + +

Comparing Ghost to WordPress is like comparing Sublime or Notepad++ to Microsoft Word—or perhaps like comparing vim to emacs. WordPress is dripping with features and has extensibility out the wazoo—but unless you’re a huge business using WP as a CMS, you’ll never use the majority of WP’s features and you’ll never have to touch its extensibility beyond maybe installing a 2FA plugin. Ghost, on the other hand, has (most of) what normal writers and code-bloggers need (well, with the exception of syntax highlighting, but that’s easily added).

+ + + +

Recall also that WordPress has moved a large number of vital functions into its pervasive Jetpack plugin; if you’re self-hosting WordPress and want basics like being able to edit your posts in Markdown, you’ll have to install and connect Jetpack, which ties your site in to WordPress.com and opts you in to data collection and other WP-provided services you may not want. Ghost’s ties back to its creators are somewhat more minimal.

+ + + +

For now, I’m staying

+ + + +

I like Ghost. I’m used to Ghost. I work with WordPress every day for work, and I’m also self-hosting a WordPress-based site on this web server, so I’m very familiar with what WordPress offers at all levels, from the casual author’s perspective all the way to the system administrator’s perspective. WordPress is perfectly fine, but I prefer Ghost for my own use—even if I wish it had some more writer-friendly features. If I were blogging more regularly and with more contributors, I’d likely consider switching to WordPress, but I’m not, so I’m not.

+ + + +

And more to the point, I’m already here. Migrations are a pain in the ass.

+ + + +

Discuss this post on the BigDinosaur forums

+]]>
+ + + +
+
+
diff --git a/gzipping_fonts_with_nginx.html b/gzipping_fonts_with_nginx.html new file mode 100644 index 00000000..124aa72f --- /dev/null +++ b/gzipping_fonts_with_nginx.html @@ -0,0 +1,232 @@ + + + + + Gzipping @font-face with Nginx – BigDino Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+
+

Gzipping @font-face with Nginx

+
+ +
+ +

This is an old post. It may contain broken links and outdated information.

+ + + +
+ + + +

In a previous post, I discussed how to alter Octopress’s configuration to serve web fonts via the @font-face CSS method. This works great and will get your Octopress site working with locally-served web fonts, but there’s some optimization that can be done on the web server side; specifically, three of the four types of web font files are compressable and benfit from being gzipped by Nginx (or whatever web server you’re using) as they’re served out to your site’s readers.

+ + + +

We use Nginx here at the Bigdinosaur.org compound, and so this post will focus only on how to enable gzipping web font files with Nginx. Instructions for doing this with other web servers are easily locatable via the googles.

+ + + + + + + +

But first, mime types

+ + + +

You should already have gzip compression enabled in your nginx configuration—it can provide a significant speed boost to your web site for a negligible amount of CPU usage. Nginx uses a file’s mime type declaration to decide whether or not to apply compression to that file, and so we must first ensure that the four types of web font files have mime types configured. Without a properly configured mime type, the web server won’t really know what kind of file it’s serving; in most cases, this isn’t a problem, since modern web browsers are very good at guessing what type of data they’re receiving, but in this particular case we need our mime types explicitly defined.

+ + + +

The file to edit is /etc/nginx/mime.types. Add the following lines:

+ + + +
application/vnd.ms-fontobject eot;
+application/x-font-ttf		  ttf;
+font/opentype			  ott;
+font/x-woff			  woff;
+ + + +

You might already have an existing declaration for eot as application/octet-stream; if so, change it to application/vnd.ms-fontobject as indicated above. Also, svg isn’t listed here because it should already have a mime type defined, so we won’t worry about that one.

+ + + +

Nobody mess with the gzip!

+ + + +

OK, misleading section heading for the sake of humor, because we’re going to mess with the gzip. You’ll want to alter the gzip_types line in /etc/nginx/nginx.conf so that the mime types we’ve just added are included in the list of compressible files. Here’s how mine reads:

+ + + +
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype;
+ + + +

The woff format isn’t included here because it’s already compressed, so adding it would gain us nothing.

+ + + +

To verify that your web fonts are being served compressed, you can take a peek at your site’s headers as pages load, or you can go hit up Google’s Page Speed Online tool, which will tell you whether or not the web font files it receives as it benchmarks your site are compressed. It also has a bunch of other handy tips and tricks for decreasing page load time, so it’s a good site to check out anyway.

+ + + +

For an idea of exactly how many bytes you’re saving from gzipping web fonts, check out this article on phpied.com. It usually results in a reduction of 40-50%, which makes this an excellent, low-effort tweak that you definitely should do.

+ + + +

Discuss this post on the BigDinosaur forums

+
+ + +
+
+
+
+ + +
+
+ + + + + + + + + + + + \ No newline at end of file