<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Aaron Lerch</title>
 <link href="http://aaronlerch.github.io/atom.xml" rel="self"/>
 <link href="http://aaronlerch.github.io"/>
 <updated>2014-12-25T07:47:22+00:00</updated>
 <id>http://aaronlerch.github.io</id>
 <author>
   <name>Aaron Lerch</name>
   <email>aaronlerch@gmail.com</email>
 </author>

 
 <entry>
   <title>Casual Minecrafting</title>
   <link href="http://aaronlerch.github.io/blog/casual-minecrafting"/>
   <updated>2014-12-24T13:05:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/casual-minecrafting</id>
   <content type="html">&lt;p&gt;When it comes to gaming, I’m what you would call “&lt;a href=&quot;http://xkcd.com/606/&quot;&gt;cutting edge&lt;/a&gt;”. So naturally my kids and I have only recently started playing &lt;a href=&quot;https://minecraft.net/&quot;&gt;Minecraft&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I saw &lt;a href=&quot;https://twitter.com/qrush&quot;&gt;qrush&lt;/a&gt; post something on twitter about running a custom server for weekends-only survival mode play, but what was really cool was the google maps-style map they had for it. Check it out at &lt;a href=&quot;http://pickaxe.club/&quot;&gt;pickaxe.club&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My kids are young enough I don’t yet want them playing on a public server, so the idea of a weekends-only-ish survival mode server for friends and family was appealing, and the map interface was just too cool to pass up. I know you can easily create a Minecraft Realms server, which has better server management integration and a hard to beat price point of $10/month, but man, &lt;em&gt;that map&lt;/em&gt;. And, let’s be honest here, the fun is usually in the process and not the result (for me).&lt;/p&gt;

&lt;p&gt;So I bring you:&lt;/p&gt;

&lt;h2 id=&quot;how-to-set-up-your-own-vanilla-minecraft-server-with-some-cool-add-ons-in-a-bunch-of-moderately-easy-steps&quot;&gt;How to set up your own vanilla Minecraft server with some cool add-ons in a bunch of moderately-easy steps&lt;/h2&gt;

&lt;p&gt;I tried to be reasonably thorough in the steps to follow, not assuming too much about any previous experience you might have, so skim and skip as you see fit.&lt;/p&gt;

&lt;p&gt;I also originally set up my server using &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;Digital Ocean&lt;/a&gt; but I’m going to walk you through it using &lt;a href=&quot;http://aws.amazon.com/&quot;&gt;Amazon Web Services&lt;/a&gt; because it’s cheaper, and frankly it’s also just easier to handle a “sometimes-on” server in AWS.&lt;/p&gt;

&lt;p&gt;If you don’t care about the hosting of a server but just want to know how to set up and configure the maps, &lt;a href=&quot;#maps&quot;&gt;skip ahead&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;

&lt;p&gt;I assume you’ve got an AWS account and are at least somewhat familiar with how AWS works. It’s pretty easy, so it doesn’t take a ton of knowledge, but I will skip some of the generic AWS-isms to save time.&lt;/p&gt;

&lt;p&gt;I also assume you’ve got a domain name of some sort for which you can control the DNS.&lt;/p&gt;

&lt;p&gt;Let’s get started with some basic security setup.&lt;/p&gt;

&lt;h3 id=&quot;security&quot;&gt;Security&lt;/h3&gt;

&lt;p&gt;In AWS this is done through IAM roles (access) and EC2 Security Groups (networking).&lt;/p&gt;

&lt;p&gt;Go to the EC2 area of the AWS console (no command line stuff here, sorry!) and under the “Network &amp;amp; Security” section choose “Security Groups”. Click the “Create Security Group” button and set it up:&lt;/p&gt;

&lt;p&gt;Name: minecraft&lt;br /&gt;
Description: Minecraft Server&lt;br /&gt;
VPC: [select your VPC in the list – you likely only have one]&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;This Security Group is basically configuring a firewall. We’ll need to allow SSH traffic, the minecraft server port, and I like to enable HTTP access as well for testing the map generation. (More on that later.) So add the following rules:&lt;/p&gt;

&lt;p&gt;Type: SSH&lt;br /&gt;
Protocol: TCP&lt;br /&gt;
Port Range: 22&lt;br /&gt;
Source: Anywhere (let’s live dangerously!)&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Type: HTTP&lt;br /&gt;
Protocol: TCP&lt;br /&gt;
Port Range: 80&lt;br /&gt;
Source: Anywhere&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Type: Custom TCP Rule&lt;br /&gt;
Protocol: TCP&lt;br /&gt;
Port Range: 25565&lt;br /&gt;
Source: Anywhere&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and create the group.&lt;/p&gt;

&lt;h3 id=&quot;ip-addresses&quot;&gt;IP Addresses&lt;/h3&gt;

&lt;p&gt;We’ll want a consistent DNS name to access this server, and if you are hosting your DNS using Amazon’s Route 53 service, you have more options available to you. Most of us probably aren’t, though, so we need an Elastic IP so that it stays constant.&lt;/p&gt;

&lt;p&gt;Quick aside: in AWS, when you stop and start an instance, it gets a new IP address assigned to it each time. So if we stop our Minecraft server on Monday morning, and start it back up on Friday evening, it’ll have a new IP address unless we leverage an Elastic IP.&lt;/p&gt;

&lt;p&gt;Under “Network &amp;amp; Security” in the EC2 console, choose “Elastic IPs”. If you don’t have any available, click the “Allocate New Address” button. I believe you get 5 with no questions asked. If you need a new one, just specify that it’ll be used in &lt;code&gt;VPC&lt;/code&gt; (and not &lt;code&gt;EC2&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We can’t use it until we have our server instance up and running, so let’s do that now.&lt;/p&gt;

&lt;h3 id=&quot;server-instance&quot;&gt;Server Instance&lt;/h3&gt;

&lt;p&gt;Launch a new Ubuntu EC2 instance. I use the “Quick Start” AMI because, well, because I don’t need anything special so “starting quickly” sounds good. The general recommendations online for running a Minecraft server are to have at least 2GB of memory, so we’ll start there. For my server, I’ll be hosting about 10 people max, so it’s pretty light-weight. I don’t want to spend a lot on this, so I’m using a &lt;code&gt;t2.small&lt;/code&gt; instance size which gives me one CPU and 2GB of memory. This currently runs at $0.026 per hour. A &lt;code&gt;t2.medium&lt;/code&gt; doubles your specs, and the price.&lt;/p&gt;

&lt;p&gt;Make sure you specify the &lt;code&gt;minecraft&lt;/code&gt; security group when creating it, and make sure to leave “Shutdown behavior” set to the default of &lt;code&gt;stop&lt;/code&gt;. You want to keep this thing around.&lt;/p&gt;

&lt;p&gt;After your instance has been launched, head back to “Elastic IPs” and associate your EIP with the newly launched instance.&lt;/p&gt;

&lt;h3 id=&quot;dns&quot;&gt;DNS&lt;/h3&gt;

&lt;p&gt;Set up an A record to point to your new instance. This is how people will connect to your server when they want to play, and setting it up now will make it easier to SSH in as you move forward also.&lt;/p&gt;

&lt;p&gt;For example, you could set up something like &lt;code&gt;minecraft.example.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;quick-review&quot;&gt;Quick Review&lt;/h3&gt;

&lt;p&gt;So we haven’t even touched Minecraft yet, but now we have a server, with a dedicated IP address, and a tad bit more locked down network layer for access to the box. It’s time to hop on and set up Minecraft.&lt;/p&gt;

&lt;h3 id=&quot;java&quot;&gt;Java&lt;/h3&gt;

&lt;p&gt;Minecraft requires Java. Java doesn’t ship on any OS by default, because how else would they ensure that you get asked “Do you not not not not not want to install the Ask Jeeves Yahoo Plus Plus Toolbar Extension, Spyware Edition?” [DEFAULT ANSWER IS YES, MEANING NO].&lt;/p&gt;

&lt;p&gt;OpenJDK is really easy to install on Ubuntu, since the packages are available in the preconfigured repos. Some guides online say that you shouldn’t use it to run a server due to “issues” but for right now, we’re going to use it for ease. If you run into issues with it, &lt;a href=&quot;https://twitter.com/aaronlerch&quot;&gt;tweet at me&lt;/a&gt; and I’ll update this post. &lt;a href=&quot;http://minecraft.gamepedia.com/Tutorials/Setting_up_a_server#Ubuntu&quot;&gt;(Here are some additional helpful hints for installing Java on Ubuntu.)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install the OpenJDK JRE:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo apt-get update
sudo apt-get install openjdk-7-jre-headless
java -version&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You should see something like &lt;code&gt;java version &quot;1.7.0_65&quot;&lt;/code&gt; as a result. Good enough.&lt;/p&gt;

&lt;h3 id=&quot;msm-or-vanilla&quot;&gt;MSM or Vanilla&lt;/h3&gt;

&lt;p&gt;You can just download and run a Minecraft server pretty easily. However we want the server to automatically start when the server boots, and we’d like it to stop cleanly when the server is going down, etc. The less I have to SSH into the server for, the better.&lt;/p&gt;

&lt;p&gt;None of this is rocket science, but I found the &lt;a href=&quot;http://msmhq.com/&quot;&gt;Minecraft Server Manager&lt;/a&gt; to be pretty convenient at automating this, and some basic level of security (separate user account, etc.) Let’s be clear: MSM does a &lt;em&gt;ton&lt;/em&gt; of stuff, almost all of which we won’t need. Just look at the &lt;a href=&quot;http://msmhq.com/&quot;&gt;bullet list on their homepage&lt;/a&gt; if you want to get a feel for it. The parts I want are the automatic setup and organization, so for this guide we’ll go with MSM.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://msmhq.com/docs/installation.html&quot;&gt;installation docs&lt;/a&gt; are pretty good, especially if you’re the kind to trust the “wget this URL and execute it” setup helpers.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Don’t curl or wget scripts from the web to bash.&lt;/p&gt;

  &lt;p&gt;- me&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Normally I think it’s a terrible idea to just execute random stuff you’ve downloaded. However, in this case, I’m strongly favoring convenience over security given the nature of what I’m setting up, so let’s just go for it. Accept the defaults except for the last question (which has a trick default answer if you aren’t paying attention):&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;wget -q http://git.io/Sxpr9g -O /tmp/msm &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; bash /tmp/msm&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we’ve got MSM installed and ready to be set up. There’s just one teeny problem, MSM stopped pointing to the latest version of Minecraft a while back. But you can get around it pretty easily. The short version of the commands below is that MSM is also a Minecraft version manager, which lets you manage and run different versions of Minecraft for different servers you might be hosting. Yadda yadda yadda, we just want the latest version of Minecraft for our server, which is pretty easy. We have to invoke a magic hack the MSM folks put in and then tell MSM to download the latest version:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;msm jargroup changeurl minecraft minecraft
msm jargroup getlatest minecraft&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The last step is to create the server – just pick whatever name you want.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;msm server create myserver&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here’s the biggest and stupidest gotcha of all of this. You need to accept the EULA before you can run your server, but MSM doesn’t handle that very elegantly… or at all. The server will not give any indication it failed to start, but running &lt;code&gt;msm myserver status&lt;/code&gt; will indicate that it’s still not running. The fix is simple but inane. Switch to the &lt;code&gt;minecraft&lt;/code&gt; user and update the &lt;code&gt;eula.txt&lt;/code&gt; file in the server’s directory.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo su - minecraft
&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;eula&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &amp;gt; /opt/msm/servers/myserver/eula.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, start your server.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;msm myserver start&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boom. Now’s a great time to test it out, after one more step: make yourself the operator on your server, assuming that’s something you want.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;msm myserver op add your-username&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&quot;test-it-out&quot;&gt;Test It Out&lt;/h3&gt;

&lt;p&gt;Fire up Minecraft and connect to your new server to make sure everything is working.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://i.imgur.com/GsmkD5f.png&quot; alt=&quot;Minecraft Screenshot&quot; style=&quot;width: 600px;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;maps&quot;&gt;Maps&lt;/h1&gt;

&lt;p&gt;Now that we’ve got a self-hosted server up and running, what about those sweet maps?&lt;/p&gt;

&lt;p&gt;Those are borne from the power of &lt;a href=&quot;http://overviewer.org/&quot;&gt;The Minecraft Overviewer&lt;/a&gt;, a magic and customizable python script that knows how to render maps and bits of info into a Google Maps-style interface. You can make it do a TON of things, but I’ll walk you through a general setup with just a few minor customizations that I use to produce &lt;a href=&quot;http://minecraft.aaronlerch.com&quot;&gt;minecraft.aaronlerch.com&lt;/a&gt;. Check out &lt;a href=&quot;http://docs.overviewer.org/en/latest/&quot;&gt;the documentation&lt;/a&gt; for all the details of what overviewer can do.&lt;/p&gt;

&lt;p&gt;What we’re going for is this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;configuration to instruct overviewer to render two map layers (Daytime and Caves) and two sets of points of interest (Signs and Players)&lt;/li&gt;
  &lt;li&gt;local web server (&lt;a href=&quot;http://nginx.org/&quot;&gt;nginx&lt;/a&gt;) for easy testing and local hosting&lt;/li&gt;
  &lt;li&gt;Amazon S3-hosted static website&lt;/li&gt;
  &lt;li&gt;cron job to render maps and sync to AWS S3&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;First, get your prerequisite utilities in place, nginx for a web server and the AWS command line tools.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo apt-get install nginx
sudo apt-get install awscli&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&quot;aws-s3-and-security&quot;&gt;AWS S3 and Security&lt;/h3&gt;

&lt;p&gt;Always with the security, I know. Hosting a website in S3 is the cheapest option you’ll ever find. And the AWS command line tools support for synchronizing a directory into an S3 bucket is hard to beat too. Let’s get S3 set up and ready.&lt;/p&gt;

&lt;p&gt;Create a new S3 bucket and configure it for static website hosting. I won’t cover how to do that here, but &lt;a href=&quot;http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html&quot;&gt;Amazon has some documentation that helps&lt;/a&gt;. Just make sure the Index Document for your S3 website is set to &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the purposes of the rest of this, I’m going to assume the name of the bucket you created is &lt;code&gt;map.example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the server we’ll need to have valid credentials to access the S3 bucket and upload files to it. We could leverage EC2 IAM Roles for permissions, but for now let’s just create a single-purpose IAM user and configure things using this user’s credentials.&lt;/p&gt;

&lt;p&gt;Create a new IAM user named &lt;code&gt;minecraft&lt;/code&gt; and make sure “Generate an access key for each user” is checked, we need API access. Capture the Access Key ID and Secret Access Key, those are important.&lt;/p&gt;

&lt;p&gt;Edit the user in IAM and under Permissions click “Attach User Policy”. Select “Custom Policy”, give it a unique name (doesn’t matter what), and use the following document, making sure to use the correct bucket name.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&amp;quot;Version&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;2012-10-17&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&amp;quot;Statement&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Sid&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Stmt1418101359000&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:GetObject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:GetObjectAcl&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:GetObjectVersion&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:ListAllMyBuckets&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:ListBucket&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:PutObject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:PutObjectAcl&amp;quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;arn:aws:s3:::map.example.com&amp;quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Sid&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Stmt1418101444000&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:GetObject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:GetObjectAcl&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:GetObjectVersion&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:ListAllMyBuckets&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:ListBucket&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:PutObject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;s3:PutObjectAcl&amp;quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;arn:aws:s3:::map.example.com/*&amp;quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This security policy is perhaps a teeny bit overly permissive, but I’ve found there are few things I dislike more than trying to guess precisely which permissions are needed for various AWS “things”. Mostly I’m going for “this user can’t cost me tons of money if compromised”.&lt;/p&gt;

&lt;p&gt;Okay, S3 is ready to go – let’s get generate something to put there.&lt;/p&gt;

&lt;h3 id=&quot;overviewer-configuration&quot;&gt;Overviewer Configuration&lt;/h3&gt;

&lt;p&gt;There’s a little bit to setting up and configuring overviewer, unfortunately, so stay with me here.&lt;/p&gt;

&lt;p&gt;First up, install Overviewer &lt;a href=&quot;http://docs.overviewer.org/en/latest/installing/#debian-ubuntu&quot;&gt;following their Ubuntu install directions&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Add the custom repo to the list&lt;/span&gt;
sudo &lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;deb http://overviewer.org/debian ./ &amp;gt;&amp;gt; /etc/apt/sources.list

&lt;span class=&quot;c&quot;&gt;# Get the key for the signed repo&lt;/span&gt;
wget -O - http://overviewer.org/debian/overviewer.gpg.asc &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; sudo apt-key add -

&lt;span class=&quot;c&quot;&gt;# Update the repo sources and install overviewer&lt;/span&gt;
sudo apt-get update
sudo apt-get install minecraft-overviewer&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, create the Overviewer configuration.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Switch to the minecraft user&lt;/span&gt;
sudo su - minecraft

&lt;span class=&quot;c&quot;&gt;# Create a directory to hold the overviewer config&lt;/span&gt;
mkdir /opt/msm/.overviewer

&lt;span class=&quot;c&quot;&gt;# Start with (and customize!) an example overviewer.conf file, or&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# create one using the content in the next code block&lt;/span&gt;
wget -O /opt/msm/.overviewer/overviewer.conf https://gist.githubusercontent.com/aaronlerch/15369228265cda5bbd3d/raw/114f0e7419af9e798f38cab6cbf0bd1ae6df262b/overviewer.conf&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;worlds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;myserver&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;/opt/msm/servers/myserver/world&amp;quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;playerIcons&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Player&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;icon&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;http://overviewer.org/avatar/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;EntityId&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;Last known location for &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;EntityId&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;signFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Sign&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;Text1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;Text2&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;Text3&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;Text4&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;renders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;normalday&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;world&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;myserver&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;Daytime&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;rendermode&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smooth_lighting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;dimension&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;overworld&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;markers&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;All signs&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;signFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;All players&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;playerIcons&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;renders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;caves&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;world&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;myserver&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;Caves&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;rendermode&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cave&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;dimension&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;overworld&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;quot;markers&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;All players&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;playerIcons&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Uncomment the line below if you want to customize the default index.html&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# customwebassets = &amp;quot;/opt/msm/.overviewer/customassets&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;outputdir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;/usr/share/nginx/html/map&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In a nutshell, this is configuring the primary server world to include two render layers, each with two sets of markers, and to put the results into a subdirectory of the default nginx html directory. Speaking of which, we need to create this directory and give the &lt;code&gt;minecraft&lt;/code&gt; user permissions to it.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo mkdir -p /usr/share/nginx/html/map
sudo chown minecraft:minecraft /usr/share/nginx/html/map&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The only thing left to configure Overviewer is to download the map textures, since Overviewer doesn’t ship with them, it relies on Minecraft itself for them. The easiest way to do this, if not a bit inefficient, is to install the minecraft client locally. &lt;a href=&quot;http://docs.overviewer.org/en/latest/running/#installing-the-textures&quot;&gt;Overviewer has helpful instructions because this changes a bit over time.&lt;/a&gt; Using the current latest version, 1.8.1, the following works:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo su - minecraft
&lt;span class=&quot;nv&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1.8.1
wget https://s3.amazonaws.com/Minecraft.Download/versions/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;.jar -P ~/.minecraft/versions/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that’s it! (That was a lot.)&lt;/p&gt;

&lt;p&gt;Give it a try, it’ll take a while to run but not horribly long on a brand new world. Overviewer runs in 2 passes if you are also generating points of interest. Future runs will only render differences from the previous render, so speeds should be faster.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;overviewer.py --config&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/opt/msm/.overviewer/overviewer.conf --genpoi
overviewer.py --config&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/opt/msm/.overviewer/overviewer.conf&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After it runs, because we opened port 80 in the AWS Security Group, we can hit the server directly to see the results: &lt;a href=&quot;http://minecraft.example.com/map&quot;&gt;http://minecraft.example.com/map&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Success! (Hopefully.)&lt;/p&gt;

&lt;h3 id=&quot;upload-to-s3&quot;&gt;Upload to S3&lt;/h3&gt;

&lt;p&gt;If you are running a server 24/7, you can probably just host the maps from the server itself. But if you, like me, are hosting this casually, it’s more fun if the maps are always accessible regardless of the server’s availability.&lt;/p&gt;

&lt;p&gt;Configure the AWS CLI to use the Access Key ID and Secret Access Key for the IAM user you created above by running &lt;code&gt;aws configure&lt;/code&gt;. Leave the default region name and default output format empty.&lt;/p&gt;

&lt;p&gt;Test the upload/sync is successful with this command (don’t forget to use your bucket name)&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;aws s3 sync /usr/share/nginx/html/map/ s3://map.example.com/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After it completes successfully, you should be able to visit http://map.example.com/ and see your map!&lt;/p&gt;

&lt;h3 id=&quot;cron&quot;&gt;cron&lt;/h3&gt;

&lt;p&gt;The last step is to set up a cron job to automate the map generation and S3 upload process. I have a simple helper script called &lt;code&gt;update-map.sh&lt;/code&gt; that can give you a starting point.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo su - minecraft
wget -O /opt/msm/update-map.sh https://gist.githubusercontent.com/aaronlerch/15369228265cda5bbd3d/raw/1915441bac0c8556e9c15d5d829ff946b1764e4b/update-map.sh
chmod &lt;span class=&quot;m&quot;&gt;775&lt;/span&gt; /opt/msm/update-map.sh
&lt;span class=&quot;c&quot;&gt;# Don&amp;#39;t forget to update the script to use your bucket name&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then configure cron to run this every 20 minutes, or however frequently you want the map updated.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo su - minecraft
crontab -e&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;*/20 * * * * /opt/msm/update-map.sh &amp;gt;/dev/null 2&amp;gt;&lt;span class=&quot;p&quot;&gt;&amp;amp;&lt;/span&gt;1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;

&lt;p&gt;And there you have it, you’ve got an available minecraft server with automatically updating independently-hosted maps! And, you made it all the way through this mini-guide, gold star for you.&lt;/p&gt;

&lt;p&gt;There’s plenty of room for customization, and I made some assumptions about the level of sophistication in hosting your Minecraft server and generated maps. Hopefully it’s a good starting point, if nothing else. Above all else, have fun!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Spelunking</title>
   <link href="http://aaronlerch.github.io/blog/spelunking"/>
   <updated>2013-11-20T14:57:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/spelunking</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Writing software is 10% inspiration, 90% grepping through source from
github trying to find how the broken internals of a third party
dependency work.&lt;/p&gt;

  &lt;p&gt;- me&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It seems like a joke, like something that just &lt;em&gt;couldn’t possibly be
true&lt;/em&gt;. But in today’s world of increasingly
componentized software units, we find ourselves spending a
disproportionate amount of time
figuring out how and why some 3rd party library works, or doesn’t work.&lt;/p&gt;

&lt;h3 id=&quot;can-you-imagine-what-a-closed-source-compiled-world-would-be-like&quot;&gt;Can you imagine what a closed-source compiled world would be like?&lt;/h3&gt;

&lt;p&gt;*shudder*&lt;br /&gt;
Thank goodness for OSS.&lt;/p&gt;

&lt;p&gt;Today’s “OSS cave” was &lt;a href=&quot;https://github.com/ddollar/foreman/&quot;&gt;foreman&lt;/a&gt;.
Foreman is great, but we are using it to export
&lt;a href=&quot;http://upstart.ubuntu.com/&quot;&gt;upstart&lt;/a&gt; configuration files and the config
files it created
result in orphaned processes after a new deploy.&lt;/p&gt;

&lt;p&gt;As it turns out this is a long-standing known issue:
&lt;a href=&quot;https://github.com/ddollar/foreman/issues/97&quot;&gt;#97&lt;/a&gt;. In the issue,
&lt;a href=&quot;https://github.com/jarthod&quot;&gt;jarthod&lt;/a&gt; has a
&lt;a href=&quot;https://github.com/ddollar/foreman/issues/97#issuecomment-19608866&quot;&gt;resolution&lt;/a&gt;
which involves using a customized template to generate a working 
upstart configuration file. What’s non-obvious is how to incorporate
this custom template.&lt;/p&gt;

&lt;h3 id=&quot;customizing-foreman-upstart-templates&quot;&gt;Customizing foreman upstart templates&lt;/h3&gt;

&lt;p&gt;The foreman upstart
&lt;a href=&quot;https://github.com/ddollar/foreman/blob/master/lib/foreman/export/upstart.rb&quot;&gt;exporter&lt;/a&gt;
uses three templates: &lt;code&gt;upstart/master.conf.erb&lt;/code&gt;,
&lt;code&gt;upstart/process_master.conf.erb&lt;/code&gt;, and &lt;code&gt;upstart/process.conf.erb&lt;/code&gt;.
jarthod offered a replacement for &lt;code&gt;process.conf.erb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After looking through the command line options and the source for the
upstart exporter, the answer was fairly easy.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create &lt;code&gt;process.conf.erb&lt;/code&gt; in any folder you like&lt;/li&gt;
  &lt;li&gt;Invoke foreman with the &lt;code&gt;-t&lt;/code&gt; option to specify a custom template
directory: &lt;code&gt;foreman export upstart /etc/init -t path/to/templates/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/ddollar/foreman/wiki/Custom-exporters&quot;&gt;documentation
says&lt;/a&gt; that the foreman exporter &lt;a href=&quot;https://github.com/ddollar/foreman/blob/master/lib/foreman/export/base.rb&quot;&gt;base
class&lt;/a&gt;
uses the method &lt;code&gt;export_template&lt;/code&gt; which looks for templates in 3
locations: the &lt;code&gt;-t&lt;/code&gt; command-line provided option,
&lt;code&gt;~/.foreman/templates&lt;/code&gt;, and finally the templates shipped with the gem
itself.&lt;/p&gt;

&lt;p&gt;The trick is that if you look at the source for the default upstart
exporter, you might be tempted to put the templates in an &lt;code&gt;upstart/&lt;/code&gt;
subdirectory, but templates from the &lt;code&gt;-t&lt;/code&gt; command-line option are looked
for just in the root of that directory.&lt;/p&gt;

&lt;h2 id=&quot;heres-the-frustrating-part&quot;&gt;Here’s the frustrating part&lt;/h2&gt;

&lt;p&gt;After writing all that down, it seems STUPIDLY SIMPLE. Because it is. But yet it took
longer than I wish to find and/or figure all that out. Ugh.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Sinatra, Bundler, and the global namespace</title>
   <link href="http://aaronlerch.github.io/blog/sinatra-bundler-and-the-global-namespace"/>
   <updated>2013-10-26T23:04:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/sinatra-bundler-and-the-global-namespace</id>
   <content type="html">&lt;p&gt;Either my google-fu is waning or I ran into an issue most people
haven’t so I want to quickly document it.&lt;/p&gt;

&lt;h2 id=&quot;the-scenario&quot;&gt;The scenario&lt;/h2&gt;

&lt;p&gt;A Rails 4 app mounting a modular-style Sinatra app to service a
particular route, kind of like this.&lt;/p&gt;

&lt;p&gt;routes.rb&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mount&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MySubApp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;subapp&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;my_sub_app.rb&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;sinatra/base&amp;#39;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MySubApp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Sinatra&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# stuff&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;

&lt;p&gt;The Gemfile looks like what you’d expect if you just added gems
‘normally’.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;https://rubygems.org&amp;#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Bundle edge Rails instead: gem &amp;#39;rails&amp;#39;, github: &amp;#39;rails/rails&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;rails&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;4.0.0&amp;#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# yadda yadda yadda&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;sinatra&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;~&amp;gt; 1.4.3&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;sinatra-contrib&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;~&amp;gt; 1.4.1&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The problem comes because Bundler is really helpful and requires the
gems for you automatically, using the gem name by default. Unfortunately
this means that Bundler will end up running &lt;code&gt;require &#39;sinatra&#39;&lt;/code&gt; (and
&lt;code&gt;require &#39;sinatra-contrib&#39;&lt;/code&gt;) and when
used this way Sinatra adds its DSL to the global namespace.&lt;/p&gt;

&lt;p&gt;That means that when you run rake, for example, you could see odd error
messages like “rake aborted! undefined method ‘task’ for Sinatra::Application:Class”&lt;/p&gt;

&lt;p&gt;In my case, sinatra-contrib was intercepting rake’s DSL and obviously
failing.&lt;/p&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The solution&lt;/h2&gt;

&lt;p&gt;The solution is to fix the Gemfile to instruct Bundler how to correctly
require the sinatra gems.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;sinatra&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;~&amp;gt; 1.4.3&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;sinatra/base&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;sinatra-contrib&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;~&amp;gt; 1.4.1&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This change results in Sinatra being required correctly for modular app use,
and turns off automatic requiring for sinatra-contrib so you can
require just the bits you need at the place you need them, since
sinatra-contrib is just a set of helpful modules.&lt;/p&gt;

&lt;p&gt;It’s not complex or difficult, but the error message (and backtrace) was
a little non-obvious.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Preventing Rack::Session on a Per-Request Basis</title>
   <link href="http://aaronlerch.github.io/blog/preventing-rack-session-on-a-per-request-basis"/>
   <updated>2012-01-14T22:55:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/preventing-rack-session-on-a-per-request-basis</id>
   <content type="html">&lt;p&gt;This is admittedly a rare need, but the other day I found myself needing
to conditionally enable and disable sessions in Sinatra on a per-request
basis. Sinatra uses the Rack::Session middleware to manage sessions. I
searched around for a while but couldn’t find out how to do it. There
are options for the session that can be set per-request, like this:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session_options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:renew&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The
&lt;a href=&quot;http://rack.rubyforge.org/doc/Rack/Session/Abstract/ID.html&quot;&gt;documentation&lt;/a&gt; shows a :defer option that seems to do what I want,
but the terminology didn’t make it abundantly clear whether it did or
not.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;:defer will not set a cookie in the response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Actually that does sound like what I want, sorta, but mentally I was
thinking “turn off” or “disabling” and not “deferring the setting of a
cookie.”&lt;/p&gt;

&lt;p&gt;With most ruby projects, or for that matter, any interpreted language
that isn’t compiled , I’ve found that I end up spending a decent amount
of time reading the code from my dependencies. Some unscientific polling
of ruby devs I know showed that to be a pretty standard practice.&lt;/p&gt;

&lt;p&gt;Doing a little digging through the Rack::Session middleware showed an
option that didn’t appear in the documentation:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;:skip will not a set a cookie in the response nor update the session state&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that matches the mental model of what I was looking for, and the
behavior as well. Turns out it’s not in the docs because it was added
&lt;a href=&quot;https://github.com/rack/rack/pull/277&quot;&gt;recently&lt;/a&gt; from a Rails core team member to support the new asset pipeline
in Rails 3.1, and the docs haven’t been updated yet.&lt;/p&gt;

&lt;p&gt;So if, on a per-request basis, you want to disable using sessions
entirely, simply do:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session_options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:skip&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;use_session?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(where “use_session?” is your method that figures out whether or not to
use sessions)&lt;/p&gt;

&lt;p&gt;What’s the diff between :defer and :skip, you ask? Well the answer is in
the pull request description&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This will not send a cookie back nor change the session state.&lt;/p&gt;

  &lt;p&gt;The :defer option did not send the cookie back but did change the
session state in the backend.&lt;/p&gt;

  &lt;p&gt;This is useful for assets requests that still go through the rack stack
but do not want to cause any change in the session (for example
accidentally expiring flash messages).&lt;/p&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>App Extensibility, Follow Up</title>
   <link href="http://aaronlerch.github.io/blog/app-extensibility"/>
   <updated>2011-10-09T22:35:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/app-extensibility</id>
   <content type="html">&lt;p&gt;My &lt;a href=&quot;/blog/app-extensibility-in-ruby&quot;&gt;last
post&lt;/a&gt;
about introducing an extensibility point
got some good feedback. I realized two things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Following the pattern of rails generators was &lt;em&gt;huge&lt;/em&gt; overkill&lt;/li&gt;
  &lt;li&gt;I wasn’t thinking correctly about the problem to begin with&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rails generators need to support a lot of things. For example, they
need to process command line arguments, consume or produce templates,
and update
existing files in a non-destructive way. The potential complexity in
generators is handled by the complexity in the extensility model
implemented in rails. Arbitrarily duplicating this complexity (magic
class names,
inheritance, etc) is just wrong. Which, of course, is why I
said:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;OK, I’m doing something wrong, I just know it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I was going to continue down this road (I’m not, see below) I would
take a cue from something like &lt;a href=&quot;http://www.sinatrarb.com/&quot;&gt;Sinatra&lt;/a&gt; and
take advantage
of global methods to reduce/remove requirements on the configuration
code.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# do something with &amp;#39;host&amp;#39; to configure your app&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But that’s all irrelevent. Jeff Lindsay set me straight in a comment on my previous post.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The hooks that most SCM use, like post-commit or post-receive,
are based on running shell scripts. This is my favorite approach
so far because it’s not language specific.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DUH. I love those things that I look back and think “of course.”&lt;/p&gt;

&lt;p&gt;I was thinking about this as a ruby problem, because localtunnel’s
client is written in ruby. But it’s not a ruby problem. Localtunnel is a
utility. I can write hooks for Git in anything I want, it doesn’t matter
what language Git is written in. The same is true for localtunnel.&lt;/p&gt;

&lt;p&gt;Taking this approach &lt;em&gt;greatly&lt;/em&gt; simplifies the solution. And the simpler
solution is always always always the better solution. In this
case, we can add a quick check followed by a system call to enable users
to provide a shell hook to configure whatever system(s) they want:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;SHELL_HOOK_FILE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;./.localtunnel_callback&amp;quot;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exists?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SHELL_HOOK_FILE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SHELL_HOOK_FILE&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exists?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SHELL_HOOK_FILE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;success?&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;   An error occurred executing the callback hook &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SHELL_HOOK_FILE&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;   (Make sure it is executable)&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Bam. Localtunnel now provides an extensibility point that can be
implemented in any language. Well, it will when/if my &lt;a href=&quot;https://github.com/progrium/localtunnel/pull/27&quot;&gt;pull
request&lt;/a&gt; is
accepted. :)&lt;/p&gt;

&lt;p&gt;Thanks, Jeff!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>App Extensibility in Ruby</title>
   <link href="http://aaronlerch.github.io/blog/app-extensibility-in-ruby"/>
   <updated>2011-09-08T18:13:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/app-extensibility-in-ruby</id>
   <content type="html">&lt;p&gt;I ran into an interesting problem recently. There is a utility called &lt;a href=&quot;http://progrium.com/localtunnel/&quot;&gt;localtunnel&lt;/a&gt; which connects a public URL to a local port. Extremely useful when it comes to developing an app that leverages services that expose web hooks.&lt;/p&gt;

&lt;p&gt;The problem is that the public URL is only valid while the script is running. Which means every time you start it, you potentially need to go update the web hook URL in whatever service you’re using. For example, with &lt;a href=&quot;http://www.twilio.com/&quot;&gt;Twilio&lt;/a&gt; you configure an app that is available at a particular URL. With GitHub you can add &lt;a href=&quot;http://help.github.com/post-receive-hooks/&quot;&gt;post-receive hooks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What needs to happen is that localtunnel needs to give you a chance to run some custom code after it registers and connects up ports, but before it sits and waits.&lt;/p&gt;

&lt;p&gt;It needs an extension point.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/snay2/localtunnel/&quot;&gt;One fork&lt;/a&gt; seeks to do this by having localtunnel call a web hook. It works, even if it feels a bit ironic. ;) But that involves maintaining another service simply to configure your first service, which is less than ideal. I took a different approach and I’d love to get feedback here, since I’m still so new to this.&lt;/p&gt;

&lt;p&gt;Most stuff on the web about extending ruby has to do with class design, or monkeypatching, or something else that assumes all the code is already loaded and executing. In this case, there’s an existing app (not framework) and I want to have it load &lt;em&gt;my&lt;/em&gt; code dynamically at runtime. In my code, I want to configure whatever service I need to.&lt;/p&gt;

&lt;p&gt;Being a n00b, I’m not the most widely read yet, so I looked at an example of something that I already knew exposed an extension point in a similar way: &lt;a href=&quot;http://guides.rubyonrails.org/generators.html&quot;&gt;rails generators&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/progrium/localtunnel/pull/23&quot;&gt;You can see my code here&lt;/a&gt; and the &lt;a href=&quot;https://github.com/aaronlerch/localtunnel/&quot;&gt;updated readme
here&lt;/a&gt;, but here’s how it works.&lt;/p&gt;

&lt;h2 id=&quot;specify-the-config-to-run&quot;&gt;Specify the config to run&lt;/h2&gt;

&lt;p&gt;This part was easy, I just extended the existing command line processing to add a “-c NAME” argument specifying which configuration you want to run. For example:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;localtunnel -c twilio 9292&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;will create a public URL connecting to local port 9292, and will look
for an auto configuration implementation named “twilio” and execute it.&lt;/p&gt;

&lt;h2 id=&quot;discovery-via-magic-names-i-mean-convention&quot;&gt;Discovery via magic names, I mean, convention&lt;/h2&gt;

&lt;p&gt;This is a nice, but sometimes frustrating thing about Rails. There’s
lots of stuff that magically happens if you organize or name your code a
certain way.&lt;/p&gt;

&lt;p&gt;In this case, to make it easy to specify via the command line, I opted
for a similar “magic name” approach. Name your file
foo_auto_config.rb and when you specify “-c foo” I’ll look for your
file under a ‘localtunnel’ subdir. This is a way to try and avoid
loading a lot of code unnecessarily.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;ss&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aaronlerch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;master&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;autoconfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;including_current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$LOAD_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dup&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;including_current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;including_current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;localtunnel&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_auto_config.rb&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;   [Warning] Could not load autoconfig &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;. Error: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backtrace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This has a few issues with it. Because this is running as a script, if
you’re using bundler, the $LOAD_PATH won’t include all the gems unless
Bundler.require has been called. So looking in the load path for auto
configuration files is probably pointless. Not that gems would include a
custom auto configuration anyway. I should probably remove this.
(Thoughts?) Secondly, I manually added
the local directory because ruby 1.9.2 &lt;a href=&quot;http://stackoverflow.com/questions/2900370/why-does-ruby-1-9-2-remove-from-load-path-and-whats-the-alternative&quot;&gt;took it out of $LOAD_PATH by
default&lt;/a&gt;
due to security reasons.
And if you consider the use-case, this is typically executed from within
~/MyApp and not randomly throughout the directory structure. Again,
similar to rails generators, you run it from the root of your
application directory.&lt;/p&gt;

&lt;h2 id=&quot;discovery-via-class-name--base-class&quot;&gt;Discovery via class name + base class&lt;/h2&gt;

&lt;p&gt;Now that we’ve figured out which files to load, we still don’t know what
code to call. We need to be able to pass in parameters such as the new
URL that is reserved, so we probably want a method to call and not just
loading a file.&lt;/p&gt;

&lt;p&gt;Ruby gives us a feature that allows you to know when a new base class is
created: the
&lt;a href=&quot;http://www.ruby-doc.org/core/classes/Class.html#M000177&quot;&gt;inherited&lt;/a&gt;
method.
It gets called when a new subclass is created.&lt;/p&gt;

&lt;p&gt;I created a base class, LocalTunnel::AutoConfig::Base, which keeps track
of all subclasses:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;ss&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aaronlerch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;master&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;autoconfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inherited&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/Base$/&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;LocalTunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AutoConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subclasses&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When someone specifies “-c foo” on the command line, I look for any
subclass of LocalTunnel::AutoConfig::Base that is named FooAutoConfig
and has a method called “configure”. If I find that, then that’s the
auto configuration code that will be called.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;ss&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aaronlerch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;master&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;autoconfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;names&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subclasses&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;autoconfig_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;downcase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nil?&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;configurator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configurator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;respond_to?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:configure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;configurator&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then I can call that method on an instance of the matching class, and
pass it the new URL.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;ss&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aaronlerch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;master&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@autoconfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nil?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;configurator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LocalTunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AutoConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@autoconfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configurator&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;configurator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;   [Warning] Unable to find an automatic configuration plugin for &amp;#39;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@autoconfig&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;#39;&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;do-your-custom-configuration&quot;&gt;Do your custom configuration&lt;/h2&gt;

&lt;p&gt;The configuration code can then run and do whatever it wants. Usually
it’s
specific per app. Here’s an example of me using this to configure
Twilio.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;rubygems&amp;#39;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;localtunnel/autoconfig&amp;#39;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;twilio-ruby&amp;#39;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;uri&amp;#39;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TwilioAutoConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LocalTunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AutoConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;TWILIO_ACCOUNT_SID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# my account Sid&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;TWILIO_AUTH_TOKEN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;TWILIO_AUTH_TOKEN&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;TWILIO_APP_SID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;TWILIO_APP_SID&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# set up a client to talk to the Twilio REST API&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#     client = Twilio::REST::Client.new(TWILIO_ACCOUNT_SID,&lt;/span&gt;
          &lt;span class=&quot;no&quot;&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;applications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TWILIO_APP_SID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Grab the current voice_url and status_callback and swap out the&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;voice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;voice_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;voice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;status_callback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;status_callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:voice_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;voice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:status_callback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;status_callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;   Configured twilio app &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;friendly_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; for new host&lt;/span&gt;
&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;ok-im-doing-something-wrong-i-just-know-it&quot;&gt;OK, I’m doing something wrong, I just know it&lt;/h2&gt;

&lt;p&gt;I am sure I either overcomplicated this, or just plain did it wrong.
Some thoughts I have (in hindsight now) are that perhaps I could’ve
exposed a global method for the autoconfig code to call. Then, instead
of a base class and magic class name, I’d just load the specified file
(magic name is still nice) and in that file I could call “host” to get
the new host name.&lt;/p&gt;

&lt;p&gt;Leave a comment, create a gist, &lt;a href=&quot;https://github.com/aaronlerch/localtunnel/&quot;&gt;fork my
fork&lt;/a&gt;,
or &lt;a href=&quot;http://twitter.com/aaronlerch/&quot;&gt;tweet at me&lt;/a&gt;, but somehow let me
know how I could
make this better.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>n00b</title>
   <link href="http://aaronlerch.github.io/blog/n00b"/>
   <updated>2011-08-25T18:17:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/n00b</id>
   <content type="html">&lt;p&gt;It’s been a long time since I’ve spent significant time to learn an
entirely new technology. And I’ve had a blast digging into ruby, rails,
sinatra, padrino, javascript (okay, I really mean jQuery), and tons of
other related frameworks. In spite of some of the excellent, and
not-so-excellent, documentation out there, it can be an incredibly
frustrating experience. Especially when trying to learn something new –
like something &lt;em&gt;really new&lt;/em&gt; – not just new to me. Expect to see posts
here related to what I’m learning, that make as few assumptions as
possible about what you already know.&lt;/p&gt;

&lt;p&gt;For example, I recently worked on getting a test environment set up for
my app that involves sinatra, mongoid, and twilio APIs. It was an
interesting experience. I’m sure seasoned ruby devs wouldn’t have had
the same issues, but I’m a firm believer in “you’re not alone” so I’ll
be posting about the process to hopefully help someone else in my shoes.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>New Content, New Blog</title>
   <link href="http://aaronlerch.github.io/blog/new-content"/>
   <updated>2011-08-22T16:23:00+00:00</updated>
   <id>http://aaronlerch.github.io/blog/new-content</id>
   <content type="html">&lt;p&gt;For the past 6 months to a year, I’ve been getting more and more into
web development on the side (I used to be a web developer, a long time
ago.) I’ve been focusing exclusively on ruby-based frameworks. It serves
two purposes, I get to learn a new language, and there are plenty of
options out there for me to explore.&lt;/p&gt;

&lt;p&gt;In that spirit, instead of changing the focus of my &lt;a href=&quot;http://www.aaronlerch.com/blog/&quot;&gt;existing
blog&lt;/a&gt;, I’m
going to take a cue from &lt;a href=&quot;http://benscheirman.com/blog/2011/08/17/a-fresh-start/&quot;&gt;Ben
Scheirman&lt;/a&gt; and
start a new blog and just start updating this one more often. Which,
given the number of things going on in my life right now, will not be
often at all. :)&lt;/p&gt;

&lt;p&gt;If you haven’t checked out &lt;a href=&quot;http://octopress.org/&quot;&gt;octopress&lt;/a&gt; you should it’s pretty sweet! I love the idea of a file-based blog engine using git.&lt;/p&gt;
</content>
 </entry>
 

</feed>
