Visualizing Web Design Evolution Using Git Posted on 2017-09-05 - Comment

My website here, as of the time of writing this, is still based on a design I made back in 2010, and is rendered using my static site generator that I haven't touched in nearly as long. The site's served its purpose pretty well, but it's kind of a mess; It's unreadable on mobile devices, the CSS causes some weird inconsistencies, and the static site generator is no where near my current standards. So since this is a personal project I have the liberty of throwing it all in the trash and starting over (and learning new things along the way!).

Since my weakest area is front-end (design, Javascript, CSS that doesn't look like it was written by a crazy person, etc.), I decided to jump in there, doing a couple of experiments. I ended up spending the better part of a weekend fiddling with HTML, playing with a couple of CSS frameworks to see what I liked, and incessantly bugging my friend Brian for help. Eventually I got something that I thought looked pretty good and got the 'final' version checked into git.

So you want to know the cool part about git? If you use it right, you have a bunch of commits containing the full history of what you're building! And with a bit of magic you can come up with something like this:

Progress so far

(click on image to see the full size version)

Neat, huh? So how the heck did I manage to pull this off? With some shell scripting wizardry!

Since all of my design is in a single HTML file, index.html, it's easy to comb through the history with the git log command. And to get the commit hashes to iterate over them, just add in some grep, awk, and tac to reverse-sort them (from oldest to newest).

git log -- index.html | grep commit | grep -v initial | awk '{print $2}' | tac

Okay, cool, so now we can flip through the history of our index.html, now how do we make an animated GIF of it? Well, an animation is just a set of images, so we need to figure out how to turn our HTML into an image a bunch of times. This is where wkhtmltopdf comes in handy! The name's kind of a mouthful, but it's a tool that uses WebKit to render HTML and output that to a PDF (or an image). It's super simple to use! Just give it a URL or file name, and then a file to output to, and it does the rest.

wkhtmltoimage --width 1920 --height 1080 index.html index${NUM}.png

Alright, now we've got a bunch of images, how do we string those together into a GIF? For things like this, I always turn to ImageMagick's convert tool, which is the swiss-army-knife of image manipulation. It turns out that if you pass it a bunch of still images and a filename that ends in .gif, it just knows to make a GIF! Incredible! Since we want it to slowly go through the changes so you can play spot-the-difference, we add in a -delay 100 to the command to tell it to wait 100 tens of milliseconds between each frame.

convert -delay 100 index*.png progress.gif

Add in some hackery to remove duplicates (because the rendered page may not change if you change the HTML) and to add a pause of the last frame, and this is what I came up with:

North Bay Area Bike Tour Log Posted on 2017-06-25 - Comment

In April of this year, I attempted to bike the Katy Trail, but I forgot how unpredictable weather could be in Missouri that time of year and got rained out. While visiting with my parents waiting for my flight back to California, I decided to do a short bike tour in the San Francisco Bay Area where at the very least I wouldn't have to deal with rain.

Set out on this trip on July 21 and returned to my home in Berkeley on July 24. This is the log I kept while on the road, transcribed with minimal editing from the notebook I kept with me.

In addition to my notes, I also took a bunch of pictures which you can find on Flickr.

The Plan

Day 1

This is abridged since I'm writing this after the fact

Rolled out @ 08:00, hit up Suzette Crepe Cafe and then Safeway for food. Got some tortillas, bananas, and Nutella. That combo makes for a killer snack!

Caught the 10:15 ferry from Oakland to SF. More crowded than expected, probably because it's summer and kids are out of school. Was on the Gemini which was a fast ride (faster that weekends).

SF was uneventful, normal tourists on their way to the bridge. The west path was closed, so it was more of a shitshow than usual getting across (with not only biking tourists, but pedestrians too). Plus it was foggy and freezing. It felt like a 20 degrees warmer after the bridge. The east path meant I came down Alexander Drive which saved the climb up the hill before Sausilito.

Got lunch at Sausilito Gourmet Deli. Had a really nice backyard seating area. Ended up taking El Camino Alto up as a challenge, wasn't too bad and dumped me right into Corte Madera.

Took (bicycle) Route 20 along the creek to Fairfax, was really nice.

Stopped on a park bench at Fairfax to rest in the mid-afternoon.

Fucking hot.

And that fucking climb out of Fairfax! Holy crap!! It was tough in the heat. While I was stopped just before the crest, a mountain biker stopped to make sure I was good. Really lifted my spirits. He mentioned a shady way parallel to Sir Francis Drake Blvd. I should have followed his advice!

Got to Sam P. Taylor alright after that.

At camp, I met:

Day 2

Departed Sam P Taylor, got the anxious feeling of venturing somehwere new (since I had been to Sam P Taylor before and knew my way there). Took the bike path to the end, then a serious climb up Sir Francis Drake with zero shoulder and a 55 mph speed limit. Yikes! The descent into Olema was gorgeous though!

Highway 1 was smooth going, shady, and cool. Nice change. Scenery of Tomales Bay was nice, got breakfast at Bovine Bakery in Pt. Reyes Station. Giant scone!

Biking right along the coast was really nice. Found a place on the side of the road that was shady. Took a quick rest there, almost fell asleep!

Got to Tomales, got some early lunch (lamb sandwich with feta, onions, pepperocini, and horseradish sauce, yum!). As usual, had a giant climb after the town. Bunch of ups and downs that I'll probably have to re-visit tomorrow.

Headwind from Tomales. :( I'd rather have a headwind than that oppressive heat! No shoulder on Highway 1 in many places, even on tough climbs.

Gentle coast down toward the coast, which gave me a second wind! Grass got greener, could smell the ocean breeze, it was lovely.

Decent climbs into Bodega Bay, again no shoulder, but at least the speed limit was 25 mph.

The guys I met at Sam P Taylor warned me about the sand at Bodega Dunes State Park, they weren't kidding. Nice big hiker/biker site. Lots of people wrote stuff on the food locker there, mostly names, dates, and their route (and some words of wisdom, like "count the smiles, not the miles"). Got dinner at Spud Point Crab Co. Crab roll and clam chowder. Good stuff.

Beach was a short ride away. Nice but kinda hot. Water was feezing as expected. Chilled there for about an hour before coming back to camp.

No other cyclists. At least I still have Caliban's War to finish reading.

Late, like 8 pm or so a couple showed up who were from Quebec City. They were going from Portland to SF, doing about 70 miles a day! They're getting married in the fall. That's about the extent of the conversation I was able to get out of them, they just hung out in their tent as soon as they got that set up.

Day 3

Writing this at Bothe-Napa State Park. Hot but fun day. About 2500 feet of climbing, but nothing demoralizing. Maybe it gets easier past day 2!

Woke up to it raining on my tent! It was misting and droplets were falling from the trees. Cold morning, but after about a mile had to take my jacket off.

Took Highway 1 (back the way I came) to Bodega Highway. While on Bodega Highway a truck passed me kicking up all kinds of crud and I got something stuck in my eye. Had to stop and flush it out with water, I'm sure I looked like a goofball. Tip: wear sunglasses, even when it's foggy.

Got second breakfast at a bakery (Wild Flour Bread) in Freehold. Popular place. Met some poeple on a supported bike tour coming from Tahoe.

As usual, had a big climb out of the town I stopped in. Nothing stressful though, despite the 6-8% grade according to the map. Took windy back roads after the climb. Much less traffic and amazing views. Lots of vineyards and giant homes up there.

Rode down into Sebastopol and jumped on the Joe Rodota Trail into Santa Rosa. That was super-chill, but turned into a hobo highway in Santa Rosa city limits.

Ate at Franchettis' which Jason Wilson from Dropbox recommended. Was great, but I was out of place as the smelly cyclist, hah!

Went north out of town and took the Mark West Springs, Porter Creek, Petrified Forest route. I didn't pay to see the petrified forest but made a water stop.

Honestly, the climbing wasn't all that bad. The descent was crazy though. Gorgeous and fast. I should have turned Strava on, I must have exceeded my record speed and hit 40 mph. On a touring bike no less! I had a stupid grin on my face the entire way down.

Once in Calistoga, I went to go see Old Faithful. Not that Old Faithful, just the California knock-off. Still neat, but I wouldn't go again.

Got a carnitas super burrito and a six pack of 21st Amendment's Hell or High Watermelon from a taqueria/bodega combo. Dug into that right before writing this. Life is good.

Day 4

I slept like a baby. Either I'm getting used to camping, or it was the beer.

According to Google Maps:


The elevation profile shows a nice gentle descent through the Napa Valley. Gonna be a fun day albiet maybe long.

Camp breakfast was oatmeal with peanut butter mixed in. How did I not think of this earlier?

Jumped north to catch Silverado Trail Road. It's a cyclist's paradise: little traffic, rolling hills, shade, decent "bike lane" (shoulder).

Got a pastry and cup of coffee at Napa Valley Coffee Roasting Company in St. Helena. Damn good coffee.

Decent headwind, but still downhill. Awesome view of vineyeards. So many cyclists that I'm getting tired of waving at all of them.

Got into Napa at 11:00 on the dot. Nice and cool compared to Marin County! Hung out for a bit and got a couple of slices at Velo Pizzeria, which has the most legit NY style I've had in California.

Riding south of Napa is all along California Highway 29, with some side roads. The shoulder is nice and wide, but it's still stressful having cars fly by at 55 mph.

American Canyon feels like American Dream-land. Reminds me of pictures of neighborhoods from the 1950s.

Vallejo has some decent riding on trails or bike lanes near Napa River. But then it's back on CA-29. Took some side roads before the bridge, but with some annoying hills.

The Carquinez Bridge Trail is a nice, chill ride. San Pablo Ave up from Crockett is the opposite, but no traffic.

This part of the Bay Area is so weird, vastly different towns right next to each other.

Starts getting sketchier the closer you get to Richmond.

Starting to get angry at hills.

Google Maps is an asshole. Why does it think that Sarah Drive in Pinole is an acceptible bike route? It's like a 10+% grande. Should have followed the Krebs map through Richmond.

San Pablo Dam Road sucks. Horrible, narrow shoulder, I think they consider it a bike lane. I almost flipped catching a wheel in a grate.

Stupid hills.

I could hear the BART trains as I approached the Ohlone Greenway. Oddly soothing after travelling 200 miles. Once I hit the greenway, I played some punk from my phone and pounded out the last few miles home.

Building My Own Home Router - IPv6 Tunnel Posted on 2015-08-16 - Comment

Continuing on my adventure of running my own self-built router at home, I decided to get IPv6 running on my home network. As of writing this blog post, my ISP doesn't do native IPv6 yet so I decided to go with Hurricane Electric's IPv6 Tunnel Broker service, which provides you with an IPv6-in-IPv4 tunnel.

Creating the tunnel

The first step is going to HE's Tunnel Broker website and creating a regular tunnel. Set your IPv4 endpoint to your router's public IP address and be sure to pick a tunnel server close to you.

Once the tunnel's been created, you'll want to grab the following information:

If you run multiple subnets, you can create a /48 block, but for my uses I just need a single subnet (/64 block), so that's what I'll be covering.

Updating the firewall

Before we even fire up the tunnel, we want to make sure it'll be secure when it comes up. This is a little different from my first post which covered IPv4 since we won't be using a NAT, but instead directly routing packets.

The goals of these firewall rules will be to:

Since we are doing regular routing, all rules on the INPUT chain will manage traffic directed to the router itself and all rules on the FORWARD chain will manage routed traffic (between the local network and the internet).

Here's what my /etc/iptables/rules.v6 file looks like with all these rules applied. Note that the default policy on INPUT and FORWARD are DROP.

Updating the interfaces file

Once you have the firewall rules in place, it's time to update the /etc/network/interfaces file for the tunnel. There are two additions that we need to make: An IPv6 address for your internal network's interface and a virtual interface for the tunnel.

This is where you'll use the details you got from the Tunnel Broker website. Everything will mostly be directly used, however you need to choose an address from the Routed /64 block for your router's internal interface. The first one in the block is convenient, so if your block is 2001:470:6661:7274::/64 then your router's address will be 2001:470:6661:7274::1 (2001:470:6661:7274::0 is technically the first address, but using 1 is less confusing since it's similar to IPv4 addressing).

Here's what my /etc/network/interfaces files looks like after those changes, the IPv6 additions at the end. Be sure to replace the variables in the file with the values you got from the Tunnel Broker website.

Once you make these changes you'll be able to run these commands to start/restart your interfaces to fire up the tunnel:

Note that if you're SSH'd into your server, you should run the first command in screen because you're going to lose connectivity for a few seconds. Once your tunnel's up, you should be able to ping6 and get a response.

Setting up Router Advertisements

Getting the router talking IPv6 is only the first half. Now we need to have the devices on our local network pick up IPv6 addresses using a mechanism called Router Advertisement. Fortunately there's a Linux package called radvd which is incredibly easy to set up.

Here's what a basic /etc/radvd.conf will look like. Again, be sure to replace $ROUTED_64 with the block you were assigned via the Tunnel Broker website.

Yeah, that's it. Start the radvd service and everything should get an IPv6 address. From a machine that's not your router, you can ping to verify that connectivity's working.

Now that's all done, you have a router that talks IPv6 with the world and you can feel a little bit better about the whole IPv4 exhaustion issue.

Building My Own Home Router, Part 2 - 802.11 Posted on 2014-08-22 - Comment

In my last post I talked about getting my home router up and forwarding packets from nothing and getting my computers connected via Ethernet. The next step is to get 802.11 (WiFi) working.

Hardware Caveats

In my last post, I mentioned that I got the Intel 7260-ac card, which I've had some problems with. Intel decided to code into the EEPROM that the card can only use channels that make it compliant with every country's laws, and the firmware and Linux driver dutifully read this information and comply. This means that the card can only work in AP mode on channels 1-11, and will NOT in the 5GHz band. This means that you're stuck to the noisy 2.4 GHz band and can't even use 802.11ac (since it requires 5GHz).

I've seen some various blog and forum posts where the OpenWRT people have gotten around this on cards with atheros chipsets since it's just a check in the driver. However, in the small amount of kernel driver hacking I've done, I've been unsuccessful.

Long story short, watch which card you pick up and make sure people have had luck making it do what you want to, preferrably without having to patch kernel drivers.

Network Changes

Since you're turning your router into a wireless access point, you have two options to connect clients to your network: split them off into their own network segment in a different subnet, or bridge the wireless interface in with your inside network and let wireless users mingle with your wired users. I chose the latter, since it was simpler.

The basic idea is that you create a bridge device (br0) and bridge in your eth1 and wlan0 interfaces. My updated config shows the changes you need to make to /etc/network/interfaces:

Note that br0 has pretty much taken the place of eth1 in the config. Also, we don't bridge in wlan0 since our access point daemon will take care of that.

Along with this change in /etc/network/interfaces, don't forget to also change your dnsmasq settings so that it listens on br0 instead of eth1.

Install the bridge-utils package if you haven't already and restart networking. Congrats, your router is now a one port network switch!

Firewall Changes

Since our inside interface is now br0, we have to tweak our firewall rules a bit.

HostAPd Config

Thanks to hostapd, getting your wireless card running in AP mode is a cinch! It's just a package install away in most cases, and the configuration isn't too terrible. Below is my config, annotated to make it easier to understand.

The things to watch out for are the settings that are ORs of bits, like auth_algs and wpa. When setting up your own AP, it's a good idea to check out the example config to see what each setting does and what the defaults are.

My config doesn't include any 5 GHz settings, so you'll have to figure those out on your own if you're lucky enough to have a card that supports it. If I get mine working, I'll make another post with those settings.

Once you're done with configuration, fire up hostapd with service hostpad start. If everything was successful, you should see wlan0 bridged in (use the brctl show command to check) and the network should be joinable by one of your wireless devices. If you don't see that, you'll want to check /var/log/syslog to see what hostapd is complaining about.

And there you have it, a router with wireless! Next up is IPv6 support, so stay tuned for part 3.

Building My Own Home Router, Part 1 Posted on 2014-08-19 - Comment

This is the first of a series of blog posts on building my own home router from scratch using Debian. My hopes are that by sharing my experiences, it can help others in this endeavor.

I've been kicking around the idea of building my own router for a while now, mostly due to the fact that my trusty WRT54GL is grealy limted by what it can do with its measly 4 MB of flash and weak CPU. After months of casually searching and trying (unsuccessfully) to re-purpose some old hardware, I finally found what I've been looking for: a cheap-ish, low-power, rackmount server with more than one NIC.

The Hardware

I can't believe that I didn't think to check the various Mini-ITX resellers for something like this, because this is almost exactly what I've always been looking for. I got a 2-NIC board since I'm cheap and already have a gigabit switch, but you can easily find boards with more ports if you don't mind shelling out the extra cash.

Once the equipment got to my apartment, I slapped in some old laptop RAM and a spare 2.5" drive and got Debian installed.

802.3 and IPv4

The first order of business was to replicate the core functionality of my old router: IPv4 routing and Ethernet connectivity. The plan was to use eth0 as my public interface (plugged into my cable modem) and eth1 as my internal interface. Before I even plugged in anything, I wrote a basic /etc/network/interfaces file.

Note the hwaddress ether line there. Since my ISP (whose name shall not be spoken (not Voldemort, but just as evil)) locks me to a single MAC address, my new router had to spoof my old router's MAC address, which was spoofed from my laptop that I originally set the connection up with. If you seemingly can't get a DHCP lease on your public interface, this is likely the problem.

Now that I had my interfaces configured and rarin' to go, I had to make sure that my ip{,6}tables rules were in order before plugging in.

The above rules are my output from iptables-save and ip6tables-save, and are fully compatible with the respective restore programs. Debian even has a nice package called iptables-persistent which will load these rules on boot if you stash them as rules.v4 and rules.v6 in /etc/iptables!

To better understand these rules, it helps to have the Netfilter packet flow diagram in front of you. There are some simple goals with these:

IPv4 Filters

IPv6 Filters

Of course, before I could have a fully-functional internet connection, I had to get DNS set up. And I guess a DHCP server would be nice to have before my leases all expire and everything drops its IP.

Luckily, there's a software package which is geared towards these very tasks: dnsmasq! Getting it running was as easy as running apt-get install dnsmasq and service dnsmasq start, which was enough to get DNS working. To get DHCP working, I created two config files in /etc/dnsmasq.d/:

With all of that, I had a functioning IPv4 router and could do important things again, like idle on IRC and browse Reddit.

This is just the beginning though! You should go check out part 2 of this series where I get 802.11 working.

Syncing Minecraft Saves with Git Posted on 2011-11-25 - Comment

I've been playing Minecraft for a while and after doing some travelling, I've ran into the issue where I'd like to syncronize my Minecraft saves across computers.

I already use git for software version control, so why not shoehorn Minecraft into it? Not only would I get easy syncronization, I would also get version control so if I seriously mung something up, I can revert back to a previous save! Here's how I did it.

First, I had to make sure git was installed on all of my machines. Luckily on Linux git is usually provided in the package repository (git-core), but since my desktop also runs Windows (for gaming), I use msysgit. For example, on Debian/Ubuntu all you need to do is:

sudo apt-get install git-core

Once git was installed, I decided to go with a centralized approach since I want one 'official' spot where I can push and pull my Mincraft saves to. I already have a server from the wonderful folks at Linode, so I just initialized a bare (centralized) repository on there:

cd /path/to/repos/minecraft
git init --bare

Then, since I already have Minecraft installed on my desktop with quite a few saves, I had to clone the central repository, add my saves, commit, and then push back to the central repository.

cd /home/nick/.minecraft/
git clone [email protected]:/path/to/repos/minecraft temp
mv temp/.git ./

Since you can't clone a repository into a non-empty folder, I had to clone it to a temporary folder and then copy the .git folder from there into my .minecraft folder. Now that my local repository was setup, I added the files I wanted to syncronize.

git add saves screenshots stats texturepacks options.txt servers.dat
git commit -m 'Initial commit'

Once I had the files commited, all I needed to do was push them up to the central repository on my server.

git push

And now I have my Minecraft files in a central spot! Now every time I'm done playing a bit, all I have to do to sync my files up is:

git commit -a -m 'Played a bit'
git push

Now, on other machines, all I need to do is clone once, git pull before playing, and then commit and push when I'm done playing!

Easy peasy. Of course, you can do more fancy things with git since it's a full-blown version control system. If you feel inlined to play with those features, go read some documentation.

Kegerator Posted on 2011-02-20 - Comment

After getting my tax refund and being sick and tired of bottling my homebrew beer, I've built myself a kegerator!


Details are over at my project page, or if you'd rather just gawk at some pictures, check out the Flickr set!

New Website (Again!) Posted on 2010-10-22 - Comment

Yes, it's that time of year. Time to change my website design yet again!

I got tired of dealing with Wordpress and wrote my own static page generator in Python, dubbed Posty. I know, I'm kind of re-inventing the wheel here and there are some really nice solutions to this problem, but why not just make something for making's sake?

Plus, I just feel cool using Markdown and YAML to update my website.

Verifying Google Voice Posted on 2010-06-23 - Comment

I've been struggling getting my work phone number verified with Google Voice. The way that number verification works is that Google Voice calls you and asks you to enter the two-digit code that's displayed on the website. But for some reason when I get a call on my outside line and I press a number button, it doesn't send that DTMF tone. Instead it tries to place another call.

My solution? I found a DTMF tone generator online and I played the code DTMF tones through my computer speakers.

It's nice to see that these old phone tricks still work.