Server Maintenance

Standard

Tonight, I decided it was time to give my web server some lovin’. I’ve been happily using Linode for 1.7 years (613 days to be exact) and have enjoyed 100% uptime in that time. Holy crap.

613 days uptimeNot long ago, Linode announced that they were giving everybody 8 cores. Wow, that’s awesome!.. but unfortunately means I’d have to restart the web server to take advantage of the awesomeness. Bye-bye uptime. I’ve been thinking about this for a couple weeks. At first, I didn’t think I’d restart the server until I absolutely had to, but there are some really cool things I want to do like install node.js (and passenger + gitlab).

Anyway, today I decided it was time to not only upgrade the web server’s hardware, but also the software. Might as well do it all at the same time, right?

Backing Up the Server

Obviously before attempting an upgrade such as this, I wanted to be sure I had a solid backup.

I’m slightly embarrassed to say this… actually, really embarrassed… but I don’t take regular backups of anything on my web server.

I host a couple of freebies and I take backups of those every couple of months, but that’s not nearly enough! I’m going to be adding Linode’s backup service for $5/mo. As they say when describing the backup features, I can “set it and forget it” — and I plan to.

I decided the easiest thing to do would be to take a dump of my databases and an archive of my www directory.

Backing up Databases

cd /tmp
mysqldump -uroot -p --all-databases > dump.sql

Easy.

Backing up Files

tar zcf www.tar.gz  /var/www/

This is where I ran into issues. This ran for about 10 minutes and during so, I thought, “I didn’t realize I had that much content in my web folder!” When it finally finished, the tarball was 4.95gb… WTF?!

Remember how I said I don’t take regular backups of anything on my web server? I was obviously wrong because my blog’s backup plugin was running a daily, and there were 250 backups. Oops.

I used the web interface to delete the backups so that it would delete the associated database records as well. I listed the backups, clicked the “Check all” checkbox, hit delete, aaaaannnnd…. Boom, HTTP 414 error. Request URI too Long… Huh? I figured I must have too many backups displaying per page, so I lowered it from 30 to 10. Same thing. Lowered it to five. Same thing. Then I noticed that my scrollbar was much longer than it should’ve been for 10 backups.

Turns out my backup plugin’s pagination was broken and all 250 backups were being listed, each time. I didn’t have the time or want to debug someone else’s “award-winning” plugin so I wrote some jQuery that when entered in the console, would select some of the checkboxes and click the delete button. Close enough to automated.

jQuery('input[name*=backupfiles]').slice(0, 25).each(function() {jQuery(this).prop('checked', true)});jQuery('select[name=action]').find('option[value=delete]').prop('selected', true);jQuery('#doaction').trigger('click');

After I got those all deleted, I took another backup and it was 1.32gb. Much more aligned to my expectations.

Taking the Server Offline

I really hesitated for a good minute before typing “reboot.” 100% uptime is insane… but so is having 8 cores without having to pay any extra…

Rebooting Linode Server

It took about 45 seconds for everything to come back online. Not bad. Bye-bye awesome uptime, but heeelllloooooooooo insane computing power!

Updating the Software

I wasn’t nearly as worried about this part of the process. I love APT.

apt-get update
apt-get upgrade

Notable upgrades include various networking utilities, MySQL, nginx, perl, PHP (and 10+ dependencies) and Redis. Updated without a hitch. (Although I did need to update some config files.)

What to Do Now!

Now that the upgrade is completed, I plan on tearing through the web server and essentially starting the config from scratch. I want to get a node.js + nginx setup going, install Passenger because I’m becoming increasingly interested in Python and Ruby and switch MySQL to MariaDB. Probably some other things too, but not at this point.

WordPress Drop-down Menus with a Custom Walker

Standard

One feature of WordPress that I’ve never quite been happy with is the wp_nav_menu function. It has a lot of customizable options but one thing I think could be improved is its support for multiple levels (drop-downs.)

While working on a website for a client, we needed to add an arrow next to top-level nav that had children. We needed to:

  1. add a “dropdown” class to the top-level nav, and
  2. add <b class=”caret”></b> inside the link (yes, that’s bootstrap-inspired)

wp_nav_menu doesn’t support either. After some brainstorming, I had three possible ideas:

  1. Buffer the wp_nav_menu output (aka set the “echo” flag to false) and use regex to parse the HTML and add what we need
  2. Make use of a PHP class like DOMDocument to traverse the HTML and add what we need
  3. Write a custom walker and add what we need before we ever call wp_nav_menu

I’ve been looking for an excuse to get into further WordPress development so I decided to use a custom walker.

Modifying the Walker

I couldn’t find muchuseful information pertaining to my need so I dug into the WordPress core and found two files in /wp-includes, class-wp-walker.php and nav-menu-template.php.

Through some trial and error I found that the display_element function in class-wp-walker.php would allow me to append the class I wanted. Here’s what that code looked like (placed in my theme’s functions.php):

class Dropdown_Walker_Nav_Menu extends Walker_Nav_Menu {
	function display_element($element, &$children_elements, $max_depth, $depth=0, $args, &$output) {
		$id_field = $this->db_fields['id'];
		if(!empty($children_elements[$element->$id_field])) {
			$element->classes[] = 'dropdown';
		}
		Walker_Nav_Menu::display_element($element, $children_elements, $max_depth, $depth, $args, $output);

	}
}

That solved the first part of my problem, but the HTML modification issue had yet to be tackled. While experimenting, I came across the start_el function that looked like I could append some HTML. I copied the entire start_el function to functions.php and added the following just before the closing </a> tag:

if(in_array('dropdown', $classes)) $item_output .= ' <b class="caret"></b>';

It’s possible to search for the “dropdown” class because display_element is called before start_el.

Anyway, this worked like a charm but was uber bulky – I love simplicity. I finally had my “D’OH” moment when I realized that it’s possible to append my HTML directly in the display_element function. I was looking at start_el and noticed it calling $item->title. $item is the same as $element in display_functions, so I appended

$element->title .= ' <b class="caret"></b>';

to my custom display_element function and BINGO!

Final Code

Here’s the final code (placed in the theme’s functions.php file):

class Dropdown_Walker_Nav_Menu extends Walker_Nav_Menu {
	function display_element($element, &$children_elements, $max_depth, $depth=0, $args, &$output) {
		$id_field = $this->db_fields['id'];
		if(!empty($children_elements[$element->$id_field])) {
			$element->classes[] = 'dropdown';

			$element->title .= ' <b class="caret"></b>';
		}
		Walker_Nav_Menu::display_element($element, $children_elements, $max_depth, $depth, $args, $output);

	}
}