Jason Codescode & rants of Jason Weatheredhttps://jasoncodes.com/https://jasoncodes.com/images/favicon.png2026-02-20T10:49:54+10:00Jason Weathered[email protected]Installing Exim on Mac OS X2013-12-31T00:00:00+10:002014-01-02T00:00:00+10:00https://jasoncodes.com/posts/exim-smarthost-mac<p>Why replace Postfix with Exim when Postfix comes pre-installed with Mac OS X?</p>
<p>For a long time I had been using <a href="/posts/mac-os-rails-server#email">Postfix on my Macs</a> to forward emails from cron jobs, etc. to my main email account. Since upgrading to Mavericks however, I found this to be less reliable than I would have liked. For various reasons, I send all outbound email via a smart host and Postfix has decided that sometimes it will ignore that setting and try direct delivery. The best part is that it seems to still try sending on the SMTP submission port (TCP 587) when doing this (rather than using TCP 25). Something’s broken and I’ve given up trying to fix it. I’ve decided to replace Postfix with something I know and trust: Exim.</p>
<p>My experience with Exim to date has been almost exclusively on Debian where the packagers have done a great job at making it easy to configure. <code>dpkg-reconfigure exim4-config</code> is pretty awesome. Luckily though, with a little bit of playing around and referencing the <a href="http://www.exim.org/exim-html-current/doc/html/spec_html/index.html">manual</a>, it’s not too hard to get Exim going on Mac OS X.</p>
<p>Here we go. :)</p>
<p><strong>Update 2014-01-02</strong>: Added section on <a href="#ipv6">Enabling IPv6 support</a></p>
<p><strong>Update 2014-01-02</strong>: Added section on <a href="#postfix-sendmail-compat">Postfix <code>sendmail</code> compatibility</a></p>
<h1 id="installation">Installing Exim</h1>
<h2 id="user_account">Creating a service user account</h2>
<p>It’s best to run Exim under a dedicated user account. You can create one though the Users & Groups preference pane, but that will leave you with an additional user account showing up in the user interface. Since you’ll never log into this account interactively, it’s better to create a new system account instead. Unfortunately, Mac OS X does not come with a simple command line tool to create user accounts and instead multiple calls to <code>dscl</code> are required. The good news is that I have wrapped this all up into a shell script which I’ve called <a href="https://github.com/jasoncodes/dotfiles/blob/master/bin/adduser"><code>adduser</code></a>.</p>
<p>You can install this utility in one of two ways: The first is to download the script file manually, place it somewhere in your path (e.g. <code>~/bin</code>) and then <code>chmod +x</code> it. The second, easier way is to use <a href="https://github.com/freshshell/fresh"><code>fresh</code></a>. <code>fresh</code> is a tool for managing your dotfiles and it works great for utility scripts. With fresh installed, you can simply run <code>fresh https://github.com/jasoncodes/dotfiles/blob/master/bin/adduser</code> and <code>adduser</code> will be installed.</p>
<h2 id="homebrew">Homebrew</h2>
<p><a href="http://brew.sh/">Homebrew</a> is a package manager for OS X. We could install Exim manually from source but Homebrew makes it so much easier. You probably want to install it now if you haven’t already.</p>
<h2 id="install">Installing Exim</h2>
<p>Create a user account for Exim, brew the formula, and set file permissions for the dedicated user account:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>adduser exim</code>
<code><span class="nv">USER</span><span class="o">=</span>ref:exim brew <span class="nb">install </span>exim</code>
<code><span class="nb">sudo chown </span>root /usr/local/etc/exim.conf</code>
<code><span class="nb">sudo cp</span> <span class="nt">-ai</span> /usr/local/etc/exim.conf<span class="o">{</span>,.org<span class="o">}</span></code>
<code><span class="nb">sudo chown </span>exim /usr/local/var/spool/exim</code>
<code><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /usr/local/var/spool/exim</code>
<code><span class="nb">sudo chown </span>exim:admin /usr/local/var/spool/exim</code>
<code><span class="nb">sudo chmod </span>750 /usr/local/var/spool/exim</code></pre>
<h3>404 Not Found {#404}</h3>
<p>Note: If you get a "Download failed" error when trying to <code>brew install</code> Exim, you can grab a copy of <code>exim-4.80.1.tar.gz</code> from somewhere else (to Google!) and drop it into <code>/Library/Caches/Homebrew</code>. Re-running <code>brew install</code> will then use this pre-cached copy. A great thing to note about Homebrew is that it will checksum the downloaded file to make sure it matches the original source file the formula creator used.</p>
<h2 id="configuration">Configuring Exim</h2>
<p>Open <code>/usr/local/etc/exim.conf</code> in your preferred text editor. Note that you’ll need to be able to write to this file as root. <code>sudo vim /usr/local/etc/exim.conf</code> is one way to do this but I prefer <a href="https://github.com/tpope/vim-eunuch"><code>vim-eunuch</code></a>’s <code>:SudoWrite</code>.</p>
<h3 id="hostname">Local Hostname</h3>
<p>For machines which are always on a single network with their hostname configured in DNS, the output of <code>hostname</code> should be both predicable and stable. For mobile machines which roam between networks (e.g. laptops), you’ll probably have better reliability if you tell Exim which hostname you’d like to use when referring to your local machine.</p>
<p>To set the hostname which Exim uses, search for <code>primary_hostname</code> in the configuration file, uncomment the line and set the value to the full hostname of your machine. e.g. <code>example.local</code>.</p>
<h3 id="interfaces">Block external access</h3>
<p>Exim by default denies relay attempts but it’s still good policy to not expose services when you don’t need to. To prevent Exim from listening on all network interfaces, add the following after the <code>primary_hostname</code> entry:</p>
<pre class="source"><code>local_interfaces = 127.0.0.1</code></pre>
<h3 id="postfix-sendmail-compat">Postfix <code>sendmail</code> compatibility</h3>
<p>Exim and Postfix have different default behaviours for sendmail’s <code>-t</code> option which used by default by Rails’ (ActionMailer) sendmail delivery method. This option extracts email addresses from the message headers. When additional email addresses are supplied on the command line, Postfix adds these to the extracted set. Exim’s default is to remove any addresses specified on the command line from the extracted set. Rails expects Postfix’s behaviour. Add the following to the main configuration section (after <code>local_interfaces</code> is fine):</p>
<pre class="source"><code>extract_addresses_remove_arguments = false</code></pre>
<p>Exim also adds a <code>Sender</code> header when using <code>sendmail</code> with a custom <code>From</code> address. One generally does not want this behaviour as the <code>Sender</code> header is often displayed in email clients. You can always tell what user account sent an email by examining the <code>Received</code> headers. Add the following to disable this behaviour:</p>
<pre class="source"><code>no_local_from_check</code></pre>
<h3 id="routers">Routers</h3>
<p>Search for <code>begin routers</code>. This section controls how mail is routed to its destination. We want to send all mail via a smarthost rather than using the default behaviour of delivering directly to destination mail servers via MX entries.</p>
<p>Comment out the existing <code>dnslookup</code> router entry and add a new entry below it to route via a smarthost:</p>
<pre class="source"><code>smart_route:</code>
<code> driver = manualroute</code>
<code> domains = !+local_domains</code>
<code> transport = smarthost</code>
<code> route_list = * smtp.example.net::587</code></pre>
<p>Replace <code>smtp.example.net</code> with your upstream SMTP smarthost server’s hostname.</p>
<h3 id="transports">Transports</h3>
<p>Search for <code>begin transports</code>. This section controls how mail is delivered once a destination is found by the router. Notice the "transport" setting in the router configuration. We want to force TLS (encryption) and use authentication (when required) for the target smarthost.</p>
<p>Add a new smarthost transport below the <code>remote_smtp</code> entry:</p>
<pre class="source"><code>smarthost:</code>
<code> driver = smtp</code>
<code> hosts_require_tls = *</code>
<code> hosts_require_auth = ${lookup{$host}nwildlsearch{/usr/local/etc/exim/passwd.client}{*}}</code></pre>
<h3 id="authenticators">Authentication</h3>
<p>Search for <code>begin authenticators</code>. This section controls where authentication credentials are retrieved from for both inbound (server) and outbound (client) connections. We're only authenticating as a client here so here's an entry to add which retrieves the username and password from our configuration file:</p>
<pre class="source"><code>plain:</code>
<code> driver = plaintext</code>
<code> public_name = PLAIN</code>
<code> client_send = "^${extract{1}{::}{${lookup{$host}lsearch*{/usr/local/etc/exim/passwd.client}{$value}fail}}}\</code>
<code> ^${extract{2}{::}{${lookup{$host}lsearch*{/usr/local/etc/exim/passwd.client}{$value}fail}}}"</code></pre>
<p>Next, we’ll create a secured password file to store the credentials for our smarthost.</p>
<pre class="source source-bash"><code><span class="nb">sudo mkdir</span> /usr/local/etc/exim</code>
<code><span class="nb">sudo touch</span> /usr/local/etc/exim/passwd.client</code>
<code><span class="nb">sudo chmod </span>600 /usr/local/etc/exim/passwd.client</code>
<code><span class="nb">sudo chown </span>exim /usr/local/etc/exim/passwd.client</code></pre>
<p>Add a line like the following to <code>/usr/local/etc/exim/passwd.client</code>, replacing the placeholders with your smarthost’s hostname, username, and password:</p>
<pre class="source"><code>smtp.example.net:username:password</code></pre>
<h3 id="forward">Forwarding local user accounts</h3>
<p>Create <code>.forward</code> files in the home directory of any local accounts you want to receive mail for. The file should contain a single line with the destination email address.</p>
<h3 id="ipv6">Enabling IPv6 support</h3>
<p>Exim’s IPv6 support is not enabled out of the box. If you’re interested in this, it’s fairly easy to get going.</p>
<p>We’ll first have to edit the Homebrew formula to compile Exim with IPv6 support enabled. Run <code>brew edit exim</code> and add <code>s << "HAVE_IPV6=yes\n"</code> to the end of the <code>inreplace 'Local/Makefile'</code> block. Run <code>brew uninstall exim</code> to remove the IPv4 version and then re-run <code>USER=ref:exim brew install exim</code> to install the IPv6 enabled version.</p>
<p>Secondly, we’ll add the IPv6 loopback address to the allowed list for relaying. Search for <code>relay_from_hosts</code> and change the value to <code><; 127.0.0.1 ; ::1</code>.</p>
<p>Finally, we’ll add the IPv6 loopback interface to the list of interfaces to listen to. Search for <code>local_interfaces</code> and change the value to <code><; 127.0.0.1 ; ::1</code>.</p>
<h3 id="check">Syntax check config file</h3>
<p>Run <code>sudo exim -bV</code> to check the syntax of the config file. Any major errors will be detected by this command. If all is good, you should see <code>Configuration file is /usr/local/etc/exim.conf</code> as the last line of output.</p>
<h2 id="port25">Running Exim on port 25</h2>
<p>If you have any other SMTP server running, you should disable it now. If you followed my previous <a href="">Postfix on OS X guide</a>, you can do this by running <code>sudo launchctl unload -w /Library/LaunchDaemons/org.postfix.master.plist</code>.</p>
<p>Create the following launchd daemon configuration file at <code>/Library/LaunchDaemons/exim.plist</code>:</p>
<pre class="source source-xml"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span></code>
<code><span class="cp"><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span></code>
<code><span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span></code>
<code><span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>Label<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>exim<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>UserName<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>root<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>ProgramArguments<span class="nt"></key></span></code>
<code> <span class="nt"><array></span></code>
<code> <span class="nt"><string></span>/usr/local/bin/exim<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>-bdf<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>-q30m<span class="nt"></string></span></code>
<code> <span class="nt"></array></span></code>
<code> <span class="nt"><key></span>RunAtLoad<span class="nt"></key></span></code>
<code> <span class="nt"><true</span> <span class="nt">/></span></code>
<code> <span class="nt"><key></span>KeepAlive<span class="nt"></key></span></code>
<code> <span class="nt"><true</span> <span class="nt">/></span></code>
<code><span class="nt"></dict></span></code>
<code><span class="nt"></plist></span></code></pre>
<p>Start the server now by running <code>sudo launchctl load -w /Library/LaunchDaemons/exim.plist</code>.</p>
<p>Run <code>nc -n 127.0.0.1 25 < /dev/null</code> and you should see a 220 banner message confirming the server is now running.</p>
<h2 id="sendmail">Replace Postfix <code>sendmail</code> with Exim</h2>
<p>UNIX services such as <code>cron</code> use sendmail rather than using SMTP to deliver mail. In order for these to work, we’ll need to swap out Postfix’s sendmail binary (<code>/usr/sbin/sendmail</code>) for Exim.</p>
<pre class="source source-bash"><code><span class="nb">sudo mv</span> <span class="nt">-i</span> /usr/sbin/sendmail<span class="o">{</span>,.org<span class="o">}</span></code>
<code><span class="nb">sudo ln</span> <span class="nt">-s</span> /usr/local/bin/exim /usr/sbin/sendmail</code>
<code><span class="nb">sudo chown </span>root:wheel /usr/sbin/sendmail</code>
<code><span class="nb">sudo chmod </span>u+s /usr/sbin/sendmail</code></pre>
<h2 id="logrotate">Log rotation</h2>
<p>The main log file for Exim is stored at <code>/usr/local/var/spool/exim/log/mainlog</code>. You can view this file if you wish to see detail on what Exim is doing.</p>
<p>Exim comes with a tool to perform log rotation. Let’s setup a launchd schedule to rotate the logs once a day. Create <code>/Library/LaunchDaemons/exim-logrotate.plist</code> with the following:</p>
<pre class="source source-xml"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span></code>
<code><span class="cp"><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span></code>
<code><span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span></code>
<code><span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>Label<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>exim-logrotate<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>UserName<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>root<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>ProgramArguments<span class="nt"></key></span></code>
<code> <span class="nt"><array></span></code>
<code> <span class="nt"><string></span>/usr/local/bin/exicyclog<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>-k<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>30<span class="nt"></string></span></code>
<code> <span class="nt"></array></span></code>
<code> <span class="nt"><key></span>RunAtLoad<span class="nt"></key></span></code>
<code> <span class="nt"><false/></span></code>
<code> <span class="nt"><key></span>StartCalendarInterval<span class="nt"></key></span></code>
<code> <span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>Hour<span class="nt"></key></span></code>
<code> <span class="nt"><integer></span>6<span class="nt"></integer></span></code>
<code> <span class="nt"><key></span>Minute<span class="nt"></key></span></code>
<code> <span class="nt"><integer></span>25<span class="nt"></integer></span></code>
<code> <span class="nt"></dict></span></code>
<code><span class="nt"></dict></span></code>
<code><span class="nt"></plist></span></code></pre>
<p>Register the log rotation job with launchctl by running <code>sudo launchctl load -w /Library/LaunchDaemons/exim-logrotate.plist</code>.</p>
<h1 id="testing">Testing</h1>
<p>You can test sendmail is working by sending a test message using <code>mail</code>. Assuming you setup a <code>.forward</code> file earlier for your user account, the following should send you an email:</p>
<pre class="source source-bash"><code><span class="nb">date</span> | mail <span class="nt">-s</span> Test <span class="nv">$USER</span></code></pre>
<p>If you receive this test email, you’re done! Yay!</p>
Nested resource URLs without controller names or numeric IDs in Rails 32011-04-04T00:00:00+10:002011-04-04T00:00:00+10:00https://jasoncodes.com/posts/rails-3-nested-resource-slugs<p>Let's say you want your app to have URLs like GitHub's: <code>https://github.com/mojombo/jekyll</code>. There are a number of advantages to URLs of this form over more conventional Rails resource URLs:</p>
<ul>
<li>
<p>Anyone with a vague familiarity with GitHub knows that the first path segment is the username and the second segment is the repository name. Without visiting a GitHub URL you can have a good idea of who owns the repository and what the name of the project is. This also makes remembering and typing URLs manually very easy.</p>
</li>
<li>
<p>Repositories are namespaced under accounts. As all repository URLs contain the account name, repository names need only be unique within a single account.</p>
</li>
<li>
<p>There are no controller or model names in the URL. A URL such as <code>https://github.com/accounts/mojombo/repositories/jekyll</code> would be overly verbose and would add no value.</p>
</li>
<li>
<p>Services like GitHub and Twitter allow you to rename accounts. Unfortunately, doing so breaks all existing external links. Out of the box Rails URLs contains numeric IDs which are immutable but aren't overly user friendly. By keeping track of history we can prevent links from breaking when slugs change. <a href="https://github.com/norman/friendly_id">FriendlyId</a> solves this problem perfectly.</p>
</li>
</ul>
<p>There are a couple of ways to implement URLs like GitHub's in Rails. One way is with a bunch of custom routes. This can easily result in a complex routes configuration file, especially if we nested further resources underneath repositories (such as issues or wiki pages). Care would also need to be taken for route helpers to have decent names.</p>
<p>A far nicer way to implement these kind of URLs is to define nested resources and hide the relevant controller names from the generated paths. This is the approach we will take here. Source code for this post's example app is <a href="https://github.com/jasoncodes/rails-3-nested-resource-slugs">available on GitHub</a>.</p>
<p>Note: This post is for Rails 3. For Rails 2.3, you can use <a href="https://github.com/caring/default_routing"><code>default_routing</code></a> in combination with <a href="https://github.com/norman/friendly_id">FriendlyId</a>. I have a <a href="https://github.com/jasoncodes/default_routing">fork of <code>default_routing</code></a> which adds support for Bundler.</p>
<h1>Project setup</h1>
<h2 id="rails-new">Generate new Rails app</h2>
<p>Create a new directory and setup our RVM gemset:</p>
<pre class="source source-bash"><code><span class="nb">mkdir </span>example</code>
<code><span class="nb">cd </span>example</code>
<code>git init</code>
<code><span class="nb">echo </span>rvm <span class="nt">--create</span> 1.9.2@example <span class="o">></span> .rvmrc</code>
<code><span class="nb">cd</span> <span class="nb">.</span> <span class="c"># trigger RVM to load the rvmrc file</span></code></pre>
<p>Generate a new Rails 3 app without Test::Unit or Prototype. We'll use RSpec for testing and we can add <a href="https://github.com/indirect/jquery-rails"><code>jquery-rails</code></a> later when we want to add client-side scripting.</p>
<pre class="source source-bash"><code>rails new <span class="nb">.</span> <span class="nt">--skip-test-unit</span> <span class="nt">--skip-prototype</span></code></pre>
<p>Add <a href="https://github.com/josevalim/inherited_resources">Inherited Resources</a> and <a href="https://github.com/rspec">RSpec</a> to the Gemfile. We'll be using these shortly.</p>
<pre class="source source-ruby"><code><span class="n">gem</span> <span class="s1">'inherited_resources'</span></code>
<code></code>
<code><span class="n">group</span> <span class="ss">:development</span><span class="p">,</span> <span class="ss">:test</span> <span class="k">do</span></code>
<code> <span class="n">gem</span> <span class="s1">'rspec-rails'</span></code>
<code><span class="k">end</span></code></pre>
<p>Run <code>bundle</code> to ensure all the required gems are installed.</p>
<h2 id="models">Create models</h2>
<p>Generate our model files and migrations. We'll be nesting projects underneath accounts and each model will have their own name.</p>
<pre class="source source-bash"><code>rails generate model account name:string</code>
<code>rails generate model project name:string account:references</code></pre>
<p>At this point we should add database constraints to the migration and validations to the model. For brevity we'll just add the <code>has_many</code> association for Account here:</p>
<pre class="source source-ruby"><code><span class="k">class</span> <span class="nc">Account</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span></code>
<code> <span class="n">has_many</span> <span class="ss">:projects</span></code>
<code><span class="k">end</span></code></pre>
<p>And then run the migrations with <code>rake db:migrate</code>.</p>
<h2 id="controllers">Controllers and initial routes</h2>
<p>Now that our test models are ready, let's add an initial set of routes to <code>config/routes.rb</code>:</p>
<pre class="source source-ruby"><code><span class="no">Example</span><span class="o">::</span><span class="no">Application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span></code>
<code> <span class="n">resources</span> <span class="ss">:accounts</span> <span class="k">do</span></code>
<code> <span class="n">resources</span> <span class="ss">:projects</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code></pre>
<p>Add a couple of controllers using Inherited Resources:</p>
<pre class="source source-ruby"><code><span class="k">class</span> <span class="nc">AccountsController</span> <span class="o"><</span> <span class="no">ApplicationController</span></code>
<code> <span class="n">inherit_resources</span></code>
<code><span class="k">end</span></code>
<code></code>
<code><span class="k">class</span> <span class="nc">ProjectsController</span> <span class="o"><</span> <span class="no">ApplicationController</span></code>
<code> <span class="n">inherit_resources</span></code>
<code> <span class="n">belongs_to</span> <span class="ss">:account</span></code>
<code><span class="k">end</span></code></pre>
<p>We now have URLs of the classic <code>https://example.com/accounts/3141/projects/59265</code> format. At this point I have also created specs for the routes which I have omitted here for brevity. You can find these in the <a href="https://github.com/jasoncodes/rails-3-nested-resource-slugs/commit/274a8f34c702afce8e22dcb532eb2c805da4df21">controllers commit</a> in the <a href="https://github.com/jasoncodes/rails-3-nested-resource-slugs">example app repo</a>.</p>
<h1 id="friendly_id">Replacing numeric IDs in URLs with slugs</h1>
<p>Revealing our surrogate primary keys in user visible URLs is not overly pretty. Usually there is a name field or other suitable text identifier which we could use. Name fields are rarely suitable for use directly in a URL as they typically contain unsafe characters and are often not unique nor immutable. Luckily for us, there's <a href="https://github.com/norman/friendly_id">FriendlyId</a> which normalises our name fields and ensures they are unique by adding a sequence number if required. With FriendlyId, we can easily turn URLs from <code>https://example.com/accounts/3141/projects/59265</code> into <code>https://example.com/accounts/foocorp/projects/widgets</code>.</p>
<p>Add <code>friendly_id</code> to the <code>Gemfile</code> and run <code>bundle</code>:</p>
<pre class="source source-ruby"><code><span class="n">gem</span> <span class="s1">'friendly_id'</span><span class="p">,</span> <span class="s1">'~> 3.2'</span></code></pre>
<p>Next, create the slugs table. This is where FriendlyId stores information on all current and previous slugs to allow existing URLs to continue to function even if we rename an account or project.</p>
<p>In a production app you should <a href="http://norman.github.com/friendly_id/file.Guide.html#redirecting_to_the_current_friendly_url">301 redirect any old slugs to the latest slug</a> by checking <code>resource.friendly_id_status.best?</code> in a <code>before_filter</code>. This will prevent search engines from seeing the same content at different URLs.</p>
<pre class="source source-bash"><code>rails generate friendly_id</code></pre>
<p>Add a cached slug column to each model table. This is done primarily for performance reasons. This allows FriendlyId to generate URLs without having to recalculate and verify the slug every time.</p>
<pre class="source source-bash"><code>rails generate migration add_cached_slug_to_accounts cached_slug:string</code>
<code>rails generate migration add_cached_slug_to_projects cached_slug:string</code></pre>
<p>The <code>cached_slug</code> fields will also be preferred for lookups and thus should be indexed together with any parent scope ID.
Ensure you add the relevant indexes to the migration.</p>
<pre class="source source-ruby"><code><span class="k">class</span> <span class="nc">AddCachedSlugToAccounts</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span></code>
<code> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">up</span></code>
<code> <span class="n">add_column</span> <span class="ss">:accounts</span><span class="p">,</span> <span class="ss">:cached_slug</span><span class="p">,</span> <span class="ss">:string</span></code>
<code class="hll"> <span class="n">add_index</span> <span class="ss">:accounts</span><span class="p">,</span> <span class="ss">:cached_slug</span><span class="p">,</span> <span class="ss">:unique</span> <span class="o">=></span> <span class="kp">true</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">down</span></code>
<code> <span class="n">remove_column</span> <span class="ss">:accounts</span><span class="p">,</span> <span class="ss">:cached_slug</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code>
<code></code>
<code><span class="k">class</span> <span class="nc">AddCachedSlugToProjects</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span></code>
<code> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">up</span></code>
<code> <span class="n">add_column</span> <span class="ss">:projects</span><span class="p">,</span> <span class="ss">:cached_slug</span><span class="p">,</span> <span class="ss">:string</span></code>
<code class="hll"> <span class="n">add_index</span> <span class="ss">:projects</span><span class="p">,</span> <span class="p">[</span><span class="ss">:account_id</span><span class="p">,</span> <span class="ss">:cached_slug</span><span class="p">],</span> <span class="ss">:unique</span> <span class="o">=></span> <span class="kp">true</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">down</span></code>
<code> <span class="n">remove_column</span> <span class="ss">:projects</span><span class="p">,</span> <span class="ss">:cached_slug</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code></pre>
<p>Add <code>has_friendly_id</code> to the models:</p>
<pre class="source source-ruby"><code><span class="k">class</span> <span class="nc">Account</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span></code>
<code> <span class="n">has_friendly_id</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:use_slug</span> <span class="o">=></span> <span class="kp">true</span></code>
<code><span class="k">end</span></code></pre>
<pre class="source source-ruby"><code><span class="k">class</span> <span class="nc">Project</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span></code>
<code> <span class="n">has_friendly_id</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:use_slug</span> <span class="o">=></span> <span class="kp">true</span><span class="p">,</span> <span class="ss">:scope</span> <span class="o">=></span> <span class="ss">:account_id</span></code>
<code><span class="k">end</span></code></pre>
<p>Run the migrations and generate slugs for any existing records:</p>
<pre class="source source-bash"><code>rake db:migrate</code>
<code>rake friendly_id:make_slugs <span class="nv">MODEL</span><span class="o">=</span>Account</code>
<code>rake friendly_id:make_slugs <span class="nv">MODEL</span><span class="o">=</span>Project</code></pre>
<h1 id="controller-names">Removing the controller names from URLs</h1>
<p>Now that we have URLs like <code>https://example.com/accounts/foocorp/projects/widgets</code>, we need to remove the the controller name segments from the path so we end up with URLs like <code>https://example.com/foocorp/widgets</code>.</p>
<h2 id="specs">Updating the specs</h2>
<p>First things first, let's update the routing specs to match the URLs we are after. i.e. <code>/foocorp/widgets</code> instead of <code>/accounts/foocorp/projects/widgets</code>.</p>
<p><code>spec/routing/accounts_routing_spec.rb</code>:</p>
<pre class="source source-ruby"><code><span class="nb">require</span> <span class="s1">'spec_helper'</span></code>
<code></code>
<code><span class="n">describe</span> <span class="no">AccountsController</span> <span class="k">do</span></code>
<code> <span class="n">describe</span> <span class="s2">"routing"</span> <span class="k">do</span></code>
<code> <span class="n">it</span> <span class="s1">'/ to Accounts#index'</span> <span class="k">do</span></code>
<code> <span class="n">path</span> <span class="o">=</span> <span class="n">accounts_path</span></code>
<code> <span class="n">path</span><span class="p">.</span><span class="nf">should</span> <span class="o">==</span> <span class="s1">'/'</span></code>
<code> <span class="p">{</span> <span class="ss">:get</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}.</span><span class="nf">should</span> <span class="n">route_to</span><span class="p">(</span></code>
<code> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'accounts'</span><span class="p">,</span></code>
<code> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'index'</span></code>
<code> <span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="n">it</span> <span class="s1">'/new to Account#new'</span> <span class="k">do</span></code>
<code> <span class="n">path</span> <span class="o">=</span> <span class="n">new_account_path</span></code>
<code> <span class="n">path</span><span class="p">.</span><span class="nf">should</span> <span class="o">==</span> <span class="s1">'/new'</span></code>
<code> <span class="p">{</span> <span class="ss">:get</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}.</span><span class="nf">should</span> <span class="n">route_to</span><span class="p">(</span></code>
<code> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'accounts'</span><span class="p">,</span></code>
<code> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'new'</span></code>
<code> <span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="n">it</span> <span class="s1">'/:account_id to Account#show'</span> <span class="k">do</span></code>
<code> <span class="n">path</span> <span class="o">=</span> <span class="n">account_path</span> <span class="s1">'foocorp'</span></code>
<code> <span class="n">path</span><span class="p">.</span><span class="nf">should</span> <span class="o">==</span> <span class="s1">'/foocorp'</span></code>
<code> <span class="p">{</span> <span class="ss">:get</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}.</span><span class="nf">should</span> <span class="n">route_to</span><span class="p">(</span></code>
<code> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'accounts'</span><span class="p">,</span></code>
<code> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'show'</span><span class="p">,</span></code>
<code> <span class="ss">:id</span> <span class="o">=></span> <span class="s1">'foocorp'</span></code>
<code> <span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="n">it</span> <span class="s1">'/:account_id/edit to Account#edit'</span> <span class="k">do</span></code>
<code> <span class="n">path</span> <span class="o">=</span> <span class="n">edit_account_path</span> <span class="s1">'foocorp'</span></code>
<code> <span class="n">path</span><span class="p">.</span><span class="nf">should</span> <span class="o">==</span> <span class="s1">'/foocorp/edit'</span></code>
<code> <span class="p">{</span> <span class="ss">:get</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}.</span><span class="nf">should</span> <span class="n">route_to</span><span class="p">(</span></code>
<code> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'accounts'</span><span class="p">,</span></code>
<code> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'edit'</span><span class="p">,</span></code>
<code> <span class="ss">:id</span> <span class="o">=></span> <span class="s1">'foocorp'</span></code>
<code> <span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code></pre>
<p><code>spec/routing/projects_routing_spec.rb</code>:</p>
<pre class="source source-ruby"><code><span class="nb">require</span> <span class="s1">'spec_helper'</span></code>
<code></code>
<code><span class="n">describe</span> <span class="no">ProjectsController</span> <span class="k">do</span></code>
<code> <span class="n">describe</span> <span class="s2">"routing"</span> <span class="k">do</span></code>
<code> <span class="n">it</span> <span class="s1">'/:account_id/new to Projects#new'</span> <span class="k">do</span></code>
<code> <span class="n">path</span> <span class="o">=</span> <span class="n">new_account_project_path</span><span class="p">(</span><span class="s1">'foocorp'</span><span class="p">)</span></code>
<code> <span class="n">path</span><span class="p">.</span><span class="nf">should</span> <span class="o">==</span> <span class="s1">'/foocorp/new'</span></code>
<code> <span class="p">{</span> <span class="ss">:get</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}.</span><span class="nf">should</span> <span class="n">route_to</span><span class="p">(</span></code>
<code> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'projects'</span><span class="p">,</span></code>
<code> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'new'</span><span class="p">,</span></code>
<code> <span class="ss">:account_id</span> <span class="o">=></span> <span class="s1">'foocorp'</span></code>
<code> <span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="n">it</span> <span class="s1">'/:account_id/:project_id to Projects#show'</span> <span class="k">do</span></code>
<code> <span class="n">path</span> <span class="o">=</span> <span class="n">account_project_path</span> <span class="s1">'foocorp'</span><span class="p">,</span> <span class="s1">'widgets'</span></code>
<code> <span class="n">path</span><span class="p">.</span><span class="nf">should</span> <span class="o">==</span> <span class="s1">'/foocorp/widgets'</span></code>
<code> <span class="p">{</span> <span class="ss">:get</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}.</span><span class="nf">should</span> <span class="n">route_to</span><span class="p">(</span></code>
<code> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'projects'</span><span class="p">,</span></code>
<code> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'show'</span><span class="p">,</span></code>
<code> <span class="ss">:account_id</span> <span class="o">=></span> <span class="s1">'foocorp'</span><span class="p">,</span></code>
<code> <span class="ss">:id</span> <span class="o">=></span> <span class="s1">'widgets'</span></code>
<code> <span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="n">it</span> <span class="s1">'/:account_id/:project_id/edit to Projects#edit'</span> <span class="k">do</span></code>
<code> <span class="n">path</span> <span class="o">=</span> <span class="n">edit_account_project_path</span> <span class="s1">'foocorp'</span><span class="p">,</span> <span class="s1">'widgets'</span></code>
<code> <span class="n">path</span><span class="p">.</span><span class="nf">should</span> <span class="o">==</span> <span class="s1">'/foocorp/widgets/edit'</span></code>
<code> <span class="p">{</span> <span class="ss">:get</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}.</span><span class="nf">should</span> <span class="n">route_to</span><span class="p">(</span></code>
<code> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'projects'</span><span class="p">,</span></code>
<code> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'edit'</span><span class="p">,</span></code>
<code> <span class="ss">:account_id</span> <span class="o">=></span> <span class="s1">'foocorp'</span><span class="p">,</span></code>
<code> <span class="ss">:id</span> <span class="o">=></span> <span class="s1">'widgets'</span></code>
<code> <span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code></pre>
<h2 id="routes">Updating the routes</h2>
<p>We can customise the controller names in routes by using the <code>:path</code> option on the <code>resources</code> block. By setting the path to an empty string, we can remove the controller name segment completely from the generated paths.</p>
<p>With no prefix on the nested resource routes, both the show page for accounts (<code>/accounts/foocorp</code>) and the index page for the nested projects (<code>/accounts/foocorp/projects</code>) will end up with the same URL (<code>/foocorp</code>). Since we can't have both of these at the same URL, we should only generate a route for one of these two actions to prevent confusion and possible bugs. In most cases I've found that disabling the nested index route is best as typically you'd want a normal show page for the parent resource. For example, the project listing makes up only part of the account's show page at <code>/foocorp</code>.</p>
<p>Here's the updated routes entries:</p>
<pre class="source source-ruby"><code><span class="no">Example</span><span class="o">::</span><span class="no">Application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span></code>
<code> <span class="n">resources</span> <span class="ss">:accounts</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=></span> <span class="s1">''</span> <span class="k">do</span></code>
<code> <span class="n">resources</span> <span class="ss">:projects</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=></span> <span class="s1">''</span><span class="p">,</span> <span class="ss">:except</span> <span class="o">=></span> <span class="p">[</span><span class="ss">:index</span><span class="p">]</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code></pre>
<h2 id="problem">A small problem</h2>
<p>Unfortunately this does not work quite to plan. If you run the specs, you'll see the member actions on the parent resource are not working. Viewing the output of <code>rake routes</code> confirms why:</p>
<pre class="source"><code>account_project GET /:account_id/:id(.:format) {:action=>"show", :controller=>"projects"}</code>
<code> edit_account GET /:id/edit(.:format) {:action=>"edit", :controller=>"accounts"}</code></pre>
<p>The nested show route is outputted first and catches all member actions on the parent. If you take a look the implementation of <code>resources</code> in <a href="https://github.com/rails/rails/blob/v3.0.5/actionpack/lib/action_dispatch/routing/mapper.rb#L1003"><code>action_dispatch/routing/mapper.rb</code></a>, you'll see that the child block is <code>yield</code>ed before any of the resources own routes are outputted.</p>
<h2 id="solution">An easy solution</h2>
<p>The workaround for this is to place any nested <code>resources</code> which have empty paths (<code>:path => ''</code>) within a second parent <code>resources</code> block. This second block uses <code>:only => []</code> to prevent it from generating any routes of its own. Any <code>member</code> or <code>collection</code> blocks for <code>resources :accounts</code> (as well as any <code>only</code>/<code>except</code> constraints) can be added as normal to the first <code>resources :accounts</code> entry.</p>
<pre class="source source-ruby"><code><span class="no">Example</span><span class="o">::</span><span class="no">Application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span></code>
<code> <span class="n">resources</span> <span class="ss">:accounts</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=></span> <span class="s1">''</span></code>
<code> <span class="n">resources</span> <span class="ss">:accounts</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=></span> <span class="s1">''</span><span class="p">,</span> <span class="ss">:only</span> <span class="o">=></span> <span class="p">[]</span> <span class="k">do</span></code>
<code> <span class="n">resources</span> <span class="ss">:projects</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=></span> <span class="s1">''</span><span class="p">,</span> <span class="ss">:except</span> <span class="o">=></span> <span class="p">[</span><span class="ss">:index</span><span class="p">]</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code></pre>
<p>Voilà, all of the specs are now passing.</p>
Upgrading Tomcat with Homebrew2011-02-14T00:00:00+10:002011-02-14T00:00:00+10:00https://jasoncodes.com/posts/homebrew-tomcat-upgrade<p>If you followed my guide on setting up <a href="/posts/mac-os-rails-server#tomcat">Tomcat on Mac OS with Homebrew</a>, at some point you may want to update Tomcat to the latest version.</p>
<p>You could stop remove the old version, install the new version, update the symlink then restart the service. You might even get away with leaving the restart to the end. I prefer to install the new version side by side and then flip the symlink just before restarting. This leaves a much smaller downtime window and makes rolling back really easy.</p>
<p>The first step is to update your local formula with the new version info by running the following:</p>
<pre class="source source-bash"><code>brew edit tomcat</code></pre>
<p>Here's the change I made to move from 7.0.6 to 7.0.8:</p>
<pre class="source source-diff"><code><span class="gh">diff --git a/Library/Formula/tomcat.rb b/Library/Formula/tomcat.rb</span></code>
<code>index 90b3443..bded504 100644</code>
<code><span class="gd">--- a/Library/Formula/tomcat.rb</span></code>
<code><span class="gi">+++ b/Library/Formula/tomcat.rb</span></code>
<code><span class="p">@@ -1,9 +1,9 @@</span></code>
<code> require 'formula'</code>
<code> </code>
<code> class Tomcat <Formula</code>
<code><span class="gd">- url 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.6/bin/apache-tomcat-7.0.6.tar.gz'</span></code>
<code><span class="gi">+ url 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.8/bin/apache-tomcat-7.0.8.tar.gz'</span></code>
<code> homepage 'http://tomcat.apache.org/'</code>
<code><span class="gd">- md5 '1c54578e2e695212ab3ed75170930df4'</span></code>
<code><span class="gi">+ md5 'b18b0f1d987f82038a7afeb2e3075511'</span></code>
<code> </code>
<code> skip_clean :all</code></pre>
<p>After the formula is updated you can install the new version with:</p>
<pre class="source source-bash"><code>brew <span class="nb">install </span>tomcat</code></pre>
<p>As per the <a href="/posts/mac-os-rails-server#tomcat">initial install</a>, Homebrew links the keg which results in a handful of generically named scripts being added to your path. We'll need to unlink it.</p>
<p>If you try to unlink with <code>brew unlink tomcat</code> you'll get <code>Error: tomcat has multiple installed versions</code>. It seems the <code>brew</code> command doesn't really like multiple versions of the same formula being installed at the same time and it presently doesn't expose a way to do this other than removing the old version first. We can however unlink it by calling the Homebrew API directly:</p>
<pre class="source source-bash"><code>ruby <span class="nt">-I</span>/usr/local/Library/Homebrew <span class="nt">-rglobal</span> <span class="nt">-rkeg</span> <span class="nt">-e</span> <span class="s1">'puts Keg.new("/usr/local/Cellar/tomcat/7.0.8").unlink'</span></code></pre>
<p>The next step is to switch out the <code>libexec</code> symlink:</p>
<pre class="source source-bash"><code><span class="nb">sudo</span> <span class="nt">-u</span> tomcat <span class="nb">ln</span> <span class="nt">-sf</span> /usr/local/Cellar/tomcat/7.0.8/libexec /usr/local/tomcat/</code></pre>
<p>And then restart the service:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>launchctl unload <span class="nt">-w</span> /Library/LaunchDaemons/org.apache.tomcat.plist</code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/org.apache.tomcat.plist</code></pre>
<p>If all goes well, remove the old version (via the API to workaround the <code>brew</code> limitation with multiple versions):</p>
<pre class="source source-bash"><code>ruby <span class="nt">-I</span>/usr/local/Library/Homebrew <span class="nt">-rglobal</span> <span class="nt">-rkeg</span> <span class="nt">-e</span> <span class="s1">'k = Keg.new("/usr/local/Cellar/tomcat/7.0.6"); puts k.unlink; k.uninstall'</span></code></pre>
Restarting Tomcat automatically on Mac OS with Monit2011-02-14T00:00:00+10:002011-02-14T00:00:00+10:00https://jasoncodes.com/posts/homebrew-tomcat-monit<p>When I setup <a href="/posts/mac-os-rails-server#tomcat">Tomcat on Mac OS with Homebrew</a> I also setup <a href="/posts/mac-os-rails-server#monit">Monit</a> to monitor if daemons fail. What I didn't do is tell Monit how to restart Tomcat should it fail.</p>
<p>Unfortunately it's often not just a simple case of bouncing Tomcat with <code>launchctl</code> when something goes wrong. This is especially the case with Tomcat as we're running it via a launch script. If <code>launchd</code> times out and kills the script process, any frozen Java process will stay running. That process will still be holding open port 8080 and will prevent a new instance from starting.</p>
<p>I came up with the following shell script to restart Tomcat fully, even if the Java process has frozen. The script asks <code>launchd</code> for what PID it is managing (the <code>catalina.sh</code> script) and then kills its direct children (the Java process). First we try with a normal <code>kill</code> and then fall back on a <code>kill -9</code> if it won't die. Once the existing process is dead, we can bounce the service with <code>launchctl</code>. Finally, we wait a few seconds to ensure the service has finished starting back up before we return control back to Monit.</p>
<p>Save the following as <code>/usr/local/sbin/tomcat-restart</code> and <code>chmod +x</code> it:</p>
<pre class="source source-bash"><code><span class="c">#!/bin/bash -e</span></code>
<code><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/usr/local/bin:/usr/bin:/bin:/usr/sbin</code>
<code></code>
<code><span class="nv">TEMPFILE</span><span class="o">=</span><span class="s2">"</span><span class="sb">`</span><span class="nb">mktemp</span> <span class="nt">-t</span> tomcat-restart.XXXXXX<span class="sb">`</span><span class="s2">"</span></code>
<code><span class="nb">trap</span> <span class="s1">'{ rm -f "$TEMPFILE"; }'</span> EXIT</code>
<code></code>
<code><span class="k">if </span>launchctl list <span class="nt">-x</span> org.apache.tomcat 2> <span class="nv">$TEMPFILE</span></code>
<code><span class="k">then</span></code>
<code> <span class="nb">ln</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">.plist"</span></code>
<code> <span class="nv">BASE_PID</span><span class="o">=</span><span class="si">$(</span>defaults <span class="nb">read</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">"</span> PID 2> /dev/null <span class="o">||</span> <span class="nb">true</span><span class="si">)</span></code>
<code> <span class="nb">rm</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">.plist"</span></code>
<code> </code>
<code> <span class="nv">CHILD_PID</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>ps <span class="nt">-axo</span> pid,ppid | <span class="nb">awk</span> <span class="s2">"{ if ( </span><span class="se">\$</span><span class="s2">2 == </span><span class="se">\"</span><span class="nv">$BASE_PID</span><span class="se">\"</span><span class="s2"> ) { print </span><span class="se">\$</span><span class="s2">1 }}"</span><span class="si">)</span><span class="s2">"</span></code>
<code> </code>
<code> <span class="k">if</span> <span class="o">!</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$CHILD_PID</span><span class="s2">"</span> <span class="o">]</span></code>
<code> <span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"Killing Tomcat softly..."</span></code>
<code> <span class="nb">kill</span> <span class="nv">$CHILD_PID</span></code>
<code> <span class="nb">sleep </span>2</code>
<code> <span class="k">if </span><span class="nb">kill</span> <span class="nt">-0</span> <span class="nv">$CHILD_PID</span> 2> /dev/null</code>
<code> <span class="k">then</span></code>
<code> <span class="nb">sleep </span>2</code>
<code> <span class="k">fi</span></code>
<code> if <span class="nb">kill</span> <span class="nt">-0</span> <span class="nv">$CHILD_PID</span> 2> /dev/null</code>
<code> <span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"It's not dead yet. Waiting a little longer..."</span></code>
<code> <span class="nb">sleep </span>5</code>
<code> <span class="k">fi</span></code>
<code> if <span class="nb">kill</span> <span class="nt">-0</span> <span class="nv">$CHILD_PID</span> 2> /dev/null</code>
<code> <span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"Nuking from orbit..."</span></code>
<code> <span class="nb">kill</span> <span class="nt">-9</span> <span class="nv">$CHILD_PID</span> <span class="nv">$BASE_PID</span></code>
<code> <span class="k">fi</span></code>
<code> fi</code>
<code> </code>
<code>fi</code>
<code></code>
<code><span class="nb">echo</span> <span class="s2">"Reversing the polarity..."</span></code>
<code><span class="nb">sudo </span>launchctl unload <span class="nt">-w</span> /Library/LaunchDaemons/org.apache.tomcat.plist <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"It's dead Jim."</span></code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/org.apache.tomcat.plist <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"I can't revive it."</span></code>
<code></code>
<code><span class="k">if</span> <span class="o">!</span> curl <span class="nt">--connect-timeout</span> 5 <span class="nt">--max-time</span> 5 <span class="nt">--silent</span> localhost:8080 <span class="o">></span> /dev/null</code>
<code><span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"Waiting for launch..."</span></code>
<code> <span class="nb">sleep </span>5</code>
<code><span class="k">fi</span></code>
<code>if <span class="o">!</span> curl <span class="nt">--connect-timeout</span> 5 <span class="nt">--max-time</span> 5 <span class="nt">--silent</span> localhost:8080 <span class="o">></span> /dev/null</code>
<code><span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"Not ready yet..."</span></code>
<code> <span class="nb">sleep </span>5</code>
<code><span class="k">fi</span></code>
<code>if <span class="o">!</span> curl <span class="nt">--connect-timeout</span> 5 <span class="nt">--max-time</span> 5 <span class="nt">--silent</span> localhost:8080 <span class="o">></span> /dev/null</code>
<code><span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"It's broken."</span></code>
<code> <span class="nb">exit </span>1</code>
<code><span class="k">fi</span></code>
<code></code>
<code><span class="nb">echo </span>Done.</code></pre>
<p>Then all we have to do is tell Monit to run this script when the service fails. My updated service entry for Tomcat in <code>/etc/monitrc</code> is as follows:</p>
<pre class="source"><code>check host tomcat with address 127.0.0.1</code>
<code> if failed port 8080</code>
<code> send "HEAD / HTTP/1.0\r\n\r\n"</code>
<code> expect "HTTP/1.1"</code>
<code> with timeout 5 seconds</code>
<code> then exec "/usr/local/sbin/tomcat-restart"</code></pre>
<p>Check the syntax is valid and then reload Monit:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>monit <span class="nt">-t</span></code>
<code><span class="nb">sudo </span>monit reload</code></pre>
CSRF vulnerability in Ruby on Rails 2.3.10 & 3.0.32011-02-10T00:00:00+10:002011-02-28T00:00:00+10:00https://jasoncodes.com/posts/rails-csrf-vulnerability<p>Yesterday Rails 3.0.4 and Rails 2.3.11 were released with patches for a few security issues. The two you're most likely to be affected by are <a href="http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2d95a3cc23e03665">CSRF Protection Bypass in Ruby on Rails (CVE-2011-0447)</a> and <a href="http://groups.google.com/group/rubyonrails-security/browse_thread/thread/b658902cf6bf4eed">Potential SQL Injection in Rails 3.0.x (CVE-2011-0448)</a>. I'll be looking at the CSRF issue here. The <a href="http://weblog.rubyonrails.org/">Riding Rails blog</a> has a <a href="http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails">post on the CSRF protection patch</a> which is worth reading.</p>
<h1 id="problem">The problem</h1>
<p>It has been discovered that browser plugins such as Flash and Java in some circumstances can bypass the same origin policy and send requests with a <code>X-Requested-With: XMLHttpRequest</code> header and POSTing with a different content type than normally used by forms (i.e. <code>application/javascript</code> instead of <code>application/x-www-form-urlencoded</code> or <code>multipart/form-data</code>). These methods are used by Rails to detect the difference between form POSTs which need the authenticity token field and AJAX requests (from jQuery or Prototype) which do not. This seemed like a good way to prevent cross-site request forgeries since these headers can't be set for a POST via a <code><form/></code> and the same origin policy prevents <code>XMLHttpRequest</code> requests from other sites.</p>
<p>For more detail on how this vulnerability works, see <a href="http://lists.webappsec.org/pipermail/websecurity_lists.webappsec.org/2011-February/007533.html">CSRF: Flash + 307 redirect = Game Over</a> on the Web Application Security Consortium mailing list.</p>
<h1 id="fix">The fix</h1>
<p>Rails 3.0.4 and Rails 2.3.11 have patches for this issue in commits <a href="https://github.com/rails/rails/commit/ae19e4141f27f80013c11e8b1da68e5c52c779ea" title="ae19e4141f27f80013c11e8b1da68e5c52c779ea">ae19e41</a> and <a href="https://github.com/rails/rails/commit/7e86f9b4d2b7dfa974c10ae7e6d8ef90f3d77f06" title="7e86f9b4d2b7dfa974c10ae7e6d8ef90f3d77f06">7e86f9b</a> respectively. With this patch, Rails now marks all non-GET requests which don't contain a valid authenticity token as unverified. This means AJAX POST requests will now need to pass send the authenticity token in a HTTP header.</p>
<p>The method for handling invalid requests (that is, POSTs without a valid authenticity token) has also changed with this patch. Instead of raising an <code>InvalidAuthenticityToken</code> exception, Rails now calls <code>handle_unverified_request</code> which by default clears your session data. The idea of this is that unverified requests cannot do any damage if they don't have access to any persistent state (like your user session).</p>
<p>A nice thing about this new setup is if you use HTTP authentication or an <code>X-API-Token</code> type header for API requests, they'll continue to function fine as these authentication systems don't use session data.</p>
<h1 id="authlogic">Authlogic</h1>
<p>Authlogic however stores authentication data in a cookie which by default is called <code>user_credentials</code>. This is separate from session data so we have to override <code>handle_unverified_request</code> in the <code>ApplicationController</code> to clear this ourselves.</p>
<p><strong>Update:</strong> If you run plugins which initialise <code>current_user</code> before <code>protect_from_forgery</code> runs (such as <code>paper_trail</code> which adds a <code>before_filter</code> to <code>ApplicationController::Base</code>), by the time <code>handle_unverified_request</code> is called Authlogic will have already logged in and it's too late to prevent authentication by just deleting the cookie. We need to clear the cached <code>@current_user</code> and <code>@current_user_session</code> variables to be sure.</p>
<pre class="source source-ruby"><code><span class="k">def</span> <span class="nf">handle_unverified_request</span></code>
<code> <span class="k">super</span></code>
<code> <span class="n">cookies</span><span class="p">.</span><span class="nf">delete</span> <span class="s1">'user_credentials'</span></code>
<code> <span class="vi">@current_user_session</span> <span class="o">=</span> <span class="vi">@current_user</span> <span class="o">=</span> <span class="kp">nil</span></code>
<code><span class="k">end</span></code></pre>
<p>Please do not blindly upgrade to 2.3.11/3.0.4 without carefully testing your authentication system with POST requests lacking an authentication token. Without adding the above code we would effectively lose CSRF protection as the app will still see the authentication and process the request where there's an invalid or missing authenticity token.</p>
<h1 id="devise">Devise</h1>
<p>If you use Devise's "remember me" functionally, you'll need to clear the persistent cookie. By default this is called <code>remember_user_token</code>. If you're making use of multiple Warden scopes, make sure you handle those as well.</p>
<pre class="source source-ruby"><code><span class="k">def</span> <span class="nf">handle_unverified_request</span></code>
<code> <span class="k">super</span></code>
<code> <span class="n">cookies</span><span class="p">.</span><span class="nf">delete</span> <span class="s1">'remember_user_token'</span></code>
<code> <span class="n">sign_out</span> <span class="ss">:user</span></code>
<code><span class="k">end</span></code></pre>
<p><strong>Update</strong>: <a href="http://blog.plataformatec.com.br/2011/02/devise-security-release-1-1-6/">Devise Security Release 1.1.6</a> patches this issue for Rails 3 by calling <code>sign_out_all_scopes</code> within <code>handle_unverified_request</code>. Devise 1.0.10 for Rails 2.3 has also been released which backports <code>sign_out_all_scopes</code> and calls it from <code>handle_unverified_request</code>. If you're using Devise, upgrade to these versions or later if possible.</p>
<h1 id="ajax">Ajax requests (jQuery)</h1>
<p>Since <code>X-Requested-With: XMLHttpRequest</code> is no longer enough to verify authenticity to Rails, we'll need to pass the authenticity token with each non-GET Ajax request. New to 2.3.11 is the <code>csrf_meta_tag</code> form helper which has been backported from Rails 3. If you don't already have <code>csrf_meta_tag</code> in your layout, add the following to the <code>%head</code> section of your (hopefully <a href="http://haml-lang.com/">Haml</a>) layouts:</p>
<pre class="source source-haml"><code><span class="nt">%head</span></code>
<code> <span class="p">=</span> <span class="n">csrf_meta_tag</span></code></pre>
<p>This adds a couple of meta attributes to every page which jQuery can look up to get the authenticity token. The second part of making Ajax requests work again is to set the <code>X-CSRF-Token</code> header on all Ajax requests with the authenticity token.</p>
<p><strong>Update</strong>: <a href="http://mislav.uniqpath.com/">Mislav Marohnić</a> <a href="http://jasoncodes.com/posts/rails-csrf-vulnerability#comment-155486572">points out</a> that <a href="https://github.com/rails/jquery-ujs/blob/a284dd7/src/rails.js#L9-15">Rails uses</a> jQuery 1.5's new <a href="http://api.jquery.com/extending-ajax/"><code>ajaxPrefilter</code></a> feature if available in preference to <code>beforeSend</code>. Using <code>ajaxPrefilter</code> instead of <code>beforeSend</code> is preferable as a <code>beforeSend</code> hook would be overridden if any Ajax call uses the <code>beforeSend</code> option itself. <code>ajaxPrefilter</code> allows multiple filters to be added and thus avoids this issue.</p>
<p>If you're using the latest <a href="https://github.com/rails/jquery-ujs">jquery-ujs</a> in your Rails 3 app, you shouldn't need any custom code at all for CSRF protection to work. If you're on Rails 2 (or for some reason stuck using Prototype for UJS), add the following to your main <code>application.js</code> file to send the CSRF token with jQuery Ajax requests:</p>
<pre class="source source-javascript"><code><span class="c1">// Make sure that every Ajax request sends the CSRF token</span></code>
<code><span class="kd">function</span> <span class="nf">CSRFProtection</span><span class="p">(</span><span class="nx">xhr</span><span class="p">)</span> <span class="p">{</span></code>
<code> <span class="kd">var</span> <span class="nx">token</span> <span class="o">=</span> <span class="nf">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">meta[name="csrf-token"]</span><span class="dl">'</span><span class="p">).</span><span class="nf">attr</span><span class="p">(</span><span class="dl">'</span><span class="s1">content</span><span class="dl">'</span><span class="p">);</span></code>
<code> <span class="nf">if </span><span class="p">(</span><span class="nx">token</span><span class="p">)</span> <span class="nx">xhr</span><span class="p">.</span><span class="nf">setRequestHeader</span><span class="p">(</span><span class="dl">'</span><span class="s1">X-CSRF-Token</span><span class="dl">'</span><span class="p">,</span> <span class="nx">token</span><span class="p">);</span></code>
<code><span class="p">}</span></code>
<code><span class="nf">if </span><span class="p">(</span><span class="dl">'</span><span class="s1">ajaxPrefilter</span><span class="dl">'</span> <span class="k">in</span> <span class="nx">$</span><span class="p">)</span> <span class="nx">$</span><span class="p">.</span><span class="nf">ajaxPrefilter</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">options</span><span class="p">,</span> <span class="nx">originalOptions</span><span class="p">,</span> <span class="nx">xhr</span><span class="p">)</span> <span class="p">{</span> <span class="nc">CSRFProtection</span><span class="p">(</span><span class="nx">xhr</span><span class="p">);</span> <span class="p">});</span></code>
<code><span class="k">else</span> <span class="nf">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nf">ajaxSend</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">xhr</span><span class="p">)</span> <span class="p">{</span> <span class="nc">CSRFProtection</span><span class="p">(</span><span class="nx">xhr</span><span class="p">);</span> <span class="p">});</span></code></pre>
<h1 id="dos">Avoiding authentication denial of service (DoS)</h1>
<p>One big thing I don't like about this fix is that it opens up an opportunity for a remote host to kill your login session. This can happen because any POST request with an invalid or missing authenticity token will kill your session data. Any other site with a <code><script/></code> block could POST a form to your site to log you out. It's almost as bad as making your logout link a GET instead of a POST.</p>
<p>Because of this I decided I'd rather have the previous behaviour of raising an <code>InvalidAuthenticityToken</code> exception to reject unverified requests completely:</p>
<pre class="source source-ruby"><code><span class="k">def</span> <span class="nf">handle_unverified_request</span></code>
<code> <span class="k">raise</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">InvalidAuthenticityToken</span></code>
<code><span class="k">end</span></code></pre>
<p>A side affect of this is that any POST requests outside of your web UI (such as to an API) will now fail as they aren't passing in the <code>X-CSRF-Token</code> header. We can get the best of both worlds by raising <code>InvalidAuthenticityToken</code> for web requests and clearing session for all other requests.</p>
<pre class="source source-ruby"><code><span class="k">def</span> <span class="nf">handle_unverified_request</span></code>
<code> <span class="n">content_mime_type</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="ss">:content_mime_type</span><span class="p">)</span> <span class="p">?</span> <span class="n">request</span><span class="p">.</span><span class="nf">content_mime_type</span> <span class="p">:</span> <span class="n">request</span><span class="p">.</span><span class="nf">content_type</span></code>
<code> <span class="k">if</span> <span class="n">content_mime_type</span> <span class="o">&&</span> <span class="n">content_mime_type</span><span class="p">.</span><span class="nf">verify_request?</span></code>
<code> <span class="k">raise</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">InvalidAuthenticityToken</span></code>
<code> <span class="k">else</span></code>
<code> <span class="k">super</span></code>
<code> <span class="n">cookies</span><span class="p">.</span><span class="nf">delete</span> <span class="s1">'user_credentials'</span></code>
<code> <span class="vi">@current_user_session</span> <span class="o">=</span> <span class="vi">@current_user</span> <span class="o">=</span> <span class="kp">nil</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code></pre>
Hosting Rails apps on a Mac OS X server2011-02-07T00:00:00+10:002012-01-04T00:00:00+10:00https://jasoncodes.com/posts/mac-os-rails-server<p>There are many guides for setting up Rails development environments on various platforms including Mac OS and Ubuntu. I thought I'd mix it up a little with my complete guide on setting up a production Mac OS server.</p>
<p><strong>Update 2011-02-12</strong>: Added a note to the <a href="#backups">backups</a> section on excluding large changing files (such as databases) from Time Machine backups.</p>
<p><strong>Update 2011-03-26</strong> All launchd configuration files for services should be placed in <code>LaunchDaemons</code> not <code>LaunchAgents</code> to run at startup as the correct user account. <code>LaunchAgents</code> are for interactive processes ran under as the logged in console user.</p>
<p><strong>Update 2012-01-04</strong> Added load average and file system monitoring to the example <a href="#monit">Monit</a> configuration.</p>
<h1>Contents</h1>
<ul>
<li><a href="#xcode">Xcode</a></li>
<li><a href="#homebrew">Homebrew</a></li>
<li><a href="#admin">Sysadmin tweaks</a></li>
<li><a href="#ruby">Ruby 1.9.2 via system-wide RVM</a></li>
<li><a href="#apache">Apache & Passenger</a></li>
<li><a href="#users">User accounts</a></li>
<li><a href="#email">Email</a></li>
<li><a href="#postgresql">PostgreSQL</a></li>
<li><a href="#memcached">Memcached</a></li>
<li><a href="#imagemagick">ImageMagick</a></li>
<li><a href="#tomcat">Tomcat</a></li>
<li><a href="#git">Git Hosting</a></li>
<li><a href="#ssh-alt">SSH on an alternate port</a></li>
<li><a href="#monit">Monit</a></li>
<li><a href="#backups">Backups</a></li>
<li><a href="#apps">Applications</a></li>
</ul>
<h1 id="xcode">Xcode</h1>
<p>Download and install <a href="http://developer.apple.com/technologies/xcode.html">Xcode</a> if you haven't already. It provides useful tools like a C compiler. Pretty much nothing is going to work without one.</p>
<h1 id="homebrew">Homebrew</h1>
<p><a href="http://mxcl.github.com/homebrew/">Homebrew</a> is an awesome package manager for Mac OS. It's superior to <a href="http://www.macports.org/">MacPorts</a> in many ways. If you're setting up a new machine, there's no decision to be made. If you're already running MacPorts it's still seriously worth switching over.</p>
<p>You can follow the <a href="https://github.com/mxcl/homebrew/wiki/installation">Homebrew installation instructions</a> on the Homebrew wiki or run the steps below to use my formulas:</p>
<pre class="source source-bash"><code>ruby <span class="nt">-e</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://gist.github.com/raw/323731/install_homebrew.rb<span class="si">)</span><span class="s2">"</span></code>
<code>brew <span class="nb">install </span>git</code>
<code>git clone http://github.com/jasoncodes/homebrew.git /tmp/homebrew <span class="c"># substitute your preferred fork</span></code>
<code><span class="nb">mv</span> /tmp/homebrew/.git /usr/local/</code>
<code><span class="nb">rm</span> <span class="nt">-rf</span> /tmp/homebrew</code>
<code><span class="nb">cd</span> /usr/local/</code>
<code>git remote add mxcl http://github.com/mxcl/homebrew.git</code>
<code>git fetch <span class="nt">--all</span></code></pre>
<h1 id="admin">Sysadmin tweaks</h1>
<h2 id="gnu">GNU command-line utilities</h2>
<p>Installing a few GNU utilities makes Mac OS's BSD userland feel more like home. You can skip this step if you're happy with the BSD variants and your apps don't need GNU flavour.</p>
<pre class="source source-bash"><code>brew <span class="nb">install </span>coreutils gnu-sed gawk findutils <span class="nt">--default-names</span></code></pre>
<p>Note: Installing these tools with <code>--default-names</code> will make the GNU variants the default and could possibly cause issues with any scripts that expect BSD versions. The GNU versions generally accept all the options as the BSD versions but there are a few differences. For example: BSD <code>sed</code> uses <code>-E</code> for extended mode and GNU <code>sed</code> uses <code>-r</code> and <code>--regexp-extended</code>. For compatibility with the BSD version of <code>sed</code> I use <code>/usr/bin/sed</code> in this guide.</p>
<h2 id="dotfiles">dot files</h2>
<p>I have customized my environment quite a bit and it can be frustrating to use a machine without my settings. As such, I like to install on every machine I use. Everything in this post should work on a bare config (and hopefully under your config as well). Here's what I run to setup my shell config:</p>
<pre class="source source-bash"><code>curl <span class="nt">-sL</span> http://github.com/jasoncodes/dotfiles/raw/master/install.sh | bash</code>
<code><span class="nb">exec </span>bash <span class="nt">-i</span> <span class="c"># reload the shell</span></code></pre>
<h2 id="htop">htop</h2>
<p><a href="http://htop.sourceforge.net/"><code>htop</code></a> is a <code>top</code> alternative which makes interactive use much easier. It primarily targets Linux but the basic functions work fine on Mac OS. It's far nicer to use than the version of <code>top</code> which comes with Mac OS.</p>
<pre class="source source-bash"><code>brew <span class="nb">install </span>htop</code></pre>
<p>You'll need to <code>sudo htop</code> when running htop in order to see all process information. I also like to enable the <code>Highlight program "basename"</code> setting.</p>
<h1 id="ruby">Ruby 1.9.2 via system-wide RVM</h1>
<h2 id="rvm">Install RVM</h2>
<pre class="source source-bash"><code><span class="nb">sudo </span>bash < <<span class="o">(</span>curl <span class="nt">-s</span> https://rvm.beginrescueend.com/install/rvm<span class="o">)</span></code>
<code><span class="nb">echo</span> <span class="nt">-e</span> <span class="s2">"[[ -s '/usr/local/rvm/scripts/rvm' ]] && source '/usr/local/rvm/scripts/rvm'</span><span class="se">\n</span><span class="s2">"</span> | <span class="nb">sudo </span>bash <span class="nt">-c</span> <span class="s1">'cat - /etc/bashrc > /etc/bashrc.new && mv /etc/bashrc{.new,}'</span> <span class="c"># add RVM to global shell config</span></code>
<code><span class="nb">source</span> <span class="s1">'/usr/local/rvm/scripts/rvm'</span> <span class="c"># load RVM in current session</span></code></pre>
<p>We prepend the RVM loader to <code>/etc/bashrc</code> so it runs on non-interactive shells such as cron. This in combination with <a href="http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production"><code>/bin/bash -l -c</code></a> (which is automatically provided by the <a href="https://github.com/javan/whenever">whenever</a> gem), we can have the RVM provided Ruby 1.9.2 available in cron jobs.</p>
<h2 id="rvm-ruby">Install Ruby 1.9.2 and set it as default</h2>
<pre class="source source-bash"><code><span class="nb">sudo </span>rvm pkg <span class="nb">install </span>readline</code>
<code>brew <span class="nb">install </span>libyaml</code>
<code><span class="nb">sudo </span>rvm <span class="nb">install </span>1.9.2 <span class="nt">--with-readline-dir</span><span class="o">=</span><span class="nv">$rvm_path</span>/usr <span class="nt">--with-libyaml-dir</span><span class="o">=</span>/usr/local</code>
<code><span class="nb">sudo </span>rvm <span class="nt">--default</span> 1.9.2</code>
<code>rvm default</code></pre>
<p><strong>Update 2011-04-29</strong>: These instructions originally installed <code>libyaml</code> for the Psych YAML parser on Ruby 1.9.2.
Unfortunately Ruby 1.9.2 up to and including p180 has an issue with Psych where by it fails with merge keys.
This can cause problems with certain versions of Bundler and RubyGems, as well as DRY database.yml files.
The issue is <a href="http://redmine.ruby-lang.org/issues/show/4300">fixed in Ruby HEAD</a>
and there's a now somewhat stale ticket open to <a href="http://redmine.ruby-lang.org/issues/show/4357">backport to 1.9.2</a>.
Hopefully we see a fix in the next Ruby 1.9.2 patch release.</p>
<p><strong>Update 2011-07-16</strong>: Ruby 1.9.2 p290 was released today and includes the Psych fix for YAML merge keys.
As a result, I have re-added <code>libyaml</code> to the Ruby installation instructions.
If you're upgrading you'll need to update RVM with <code>rvm get head && rvm reload</code> first.
To migrate a gemset over to the latest Ruby, run <code>rvm gemset copy 1.9.2-p{180,290}@example && rvm 1.9.2-p180 gemset delete example</code>.</p>
<h2 id="rvm-lockdown">Lockdown RVM installation</h2>
<p>By default RVM will give all users access to modify RVM rubies, gemsets, et al.
This is problem as the RVM install is system wide and users should not be able to mess with the environment of others.
Luckily, all we need to do is empty out the <code>rvm</code> group which will leave <code>root</code> as the only user allowed to administer RVM:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>dscl <span class="nb">.</span> delete /Groups/rvm GroupMembership</code></pre>
<h2 id="rvm-homebrew-permissions">Fix Homebrew permissions broken by installing RVM system-wide</h2>
<p>After installing RVM system-wide you may find <code>/usr/local/lib</code> and <code>/usr/local/bin</code> to be locked down. We can liberate them again without reinstalling Homebrew by coping the owner and permissions from another directory (such as <code>/usr/local/share/man</code>) which is unaffected by the installation of RVM.</p>
<pre class="source source-bash"><code><span class="nb">sudo chmod</span> <span class="nt">-R</span> <span class="nt">--reference</span><span class="o">=</span>/usr/local/lib /usr/local/bin /usr/local/share/man</code>
<code><span class="nb">sudo chown</span> <span class="nt">-R</span> <span class="nt">--reference</span><span class="o">=</span>/usr/local/lib /usr/local/bin /usr/local/share/man</code></pre>
<h2 id="bundler">Install Bundler</h2>
<p>We'll be using Bundler's deployment mode via Capistrano to install and manage gems. This keeps our system gems clean and isolates apps from each other.</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>gem <span class="nb">install </span>bundler</code></pre>
<h1 id="apache">Apache & Passenger</h1>
<p>Apache 2 comes standard with Mac OS 10.6. Sprinkle <a href="http://www.modrails.com/">Phusion Passenger</a> on top and we have an app server for Rack apps (including Rails).</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>gem <span class="nb">install </span>passenger</code>
<code>rvmsudo passenger-install-apache2-module</code></pre>
<p>Copy the 3 configuration lines emitted after installation into <code>/etc/apache2/other/passenger.conf</code>:</p>
<pre class="source source-apache"><code><span class="nc">LoadModule</span> passenger_module /usr/local/rvm/gems/ruby-1.9.2-p136/gems/passenger-3.0.2/ext/apache2/mod_passenger.so</code>
<code>PassengerRoot /usr/local/rvm/gems/ruby-1.9.2-p136/gems/passenger-3.0.2</code>
<code>PassengerRuby /usr/local/rvm/wrappers/ruby-1.9.2-p136/ruby</code></pre>
<p>There are a number of configuration options you can set in <code>passenger.conf</code> to control how it manages worker instances. You can read about these in the <a href="http://www.modrails.com/documentation/Users%20guide%20Apache.html">Passenger users guide</a>. Here's the settings I'm using:</p>
<pre class="source source-apache"><code>RailsSpawnMethod smart-lv2</code>
<code>RailsFrameworkSpawnerIdleTime 0</code>
<code>RailsAppSpawnerIdleTime 0</code>
<code>PassengerUseGlobalQueue <span class="ss">on</span></code>
<code>PassengerFriendlyErrorPages <span class="ss">off</span></code>
<code></code>
<code><span class="c"># recycle instances every so often to keep any leaks under control</span></code>
<code>PassengerMaxRequests 1000</code>
<code></code>
<code><span class="c"># default 6</span></code>
<code>PassengerMaxPoolSize 16</code>
<code></code>
<code><span class="c"># default 0</span></code>
<code>PassengerMaxInstancesPerApp 5</code>
<code></code>
<code><span class="c"># keep at least one instance around per app, let the others time out after 2 minutes</span></code>
<code>PassengerMinInstances 1</code>
<code>PassengerPoolIdleTime 120</code></pre>
<p>For the configuration changes to take affect you need to restart Apache by running the following:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>launchctl unload <span class="nt">-w</span> /System/Library/LaunchDaemons/org.apache.httpd.plist</code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /System/Library/LaunchDaemons/org.apache.httpd.plist</code></pre>
<p>Once restarted you should see Passenger in the server signature:</p>
<pre class="source source-bash"><code><span class="nv">$ </span>curl <span class="nt">-sI</span> localhost | <span class="nb">grep</span> ^Server</code>
<code>Server: Apache/2.2.15 <span class="o">(</span>Unix<span class="o">)</span> mod_ssl/2.2.15 OpenSSL/0.9.8l DAV/2 Phusion_Passenger/3.0.2</code></pre>
<h2 id="compression">HTTP Compression</h2>
<p><code>mod_deflate</code> is loaded by default but it's not configured to compress any responses automatically. Save the following as <code>/etc/apache2/other/deflate.conf</code> to enable HTTP compression for HTML, CSS, Javascript and fonts:</p>
<pre class="source source-apache"><code><span class="nc">AddOutputFilterByType</span> DEFLATE text/html text/plain text/xml font/ttf font/otf application/vnd.ms-fontobject text/css application/javascript application/atom+xml</code></pre>
<h2 id="apache-vhosts">Virtual Hosts</h2>
<p>It's useful to have a default virtual host to catch any hits that goto any undefined hostnames or direct IP requests.
Here's a basic vhost which will act as the default. The key thing to note here is the zeros in the filename which causes it to sort before the other vhost files and Apache's <code>Include</code> to load it first.</p>
<p><code>/etc/apache2/other/vhosts-000default.conf</code>:</p>
<pre class="source source-apache"><code><span class="nc">NameVirtualHost</span> *:80</code>
<code><span class="p"><</span><span class="nl">VirtualHost</span><span class="sr"> *:80</span><span class="p">></span></code>
<code> <span class="nc">ServerName</span> _default_</code>
<code> <span class="p"><</span><span class="nl">Directory</span><span class="sr"> /Library/WebServer/Documents</span><span class="p">></span></code>
<code> <span class="nc">AllowOverride</span> <span class="ss">All</span></code>
<code> <span class="p"></</span><span class="nl">Directory</span><span class="p">></span></code>
<code></<span class="nl">VirtualHost</span><span class="p">></span></code>
<code></code></pre>
<p>Let's use something a bit better than "It works!" as our default page.</p>
<p><code>/Library/WebServer/Documents/index.html</code>:</p>
<pre class="source source-html"><code><span class="cp"><!DOCTYPE html></span></code>
<code><span class="nt"><html></span></code>
<code> <span class="nt"><head></span></code>
<code> <span class="nt"><title></title></span></code>
<code> <span class="nt"><meta</span> <span class="na">http-equiv=</span><span class="s">"Content-Type"</span> <span class="na">content=</span><span class="s">"text/html; charset=utf-8"</span> <span class="nt">/></span></code>
<code> <span class="nt"></head></span></code>
<code> <span class="nt"><body></span></code>
<code> <span class="nt"><p></span>Move along, nothing to see here<span class="ni">&hellip;</span><span class="nt"></p></span></code>
<code> <span class="nt"></body></span></code>
<code><span class="nt"></html></span></code></pre>
<p>We don't want it to be cacheable just in case we screw up and end up serving it instead of a vhost.</p>
<p><code>/Library/WebServer/Documents/.htaccess</code>:</p>
<pre class="source source-apache"><code><span class="c"># Expire default page immediately</span></code>
<code><span class="nc">ExpiresActive</span> <span class="ss">On</span></code>
<code><span class="nc">ExpiresByType</span> text/html "access"</code></pre>
<p>And finally here's my template for all vhosts which we'll be using a bit later:</p>
<p><code>/etc/apache2/other/vhosts-example.conf.template</code>:</p>
<pre class="source source-apache"><code><span class="p"><</span><span class="nl">VirtualHost</span><span class="sr"> *:80</span><span class="p">></span></code>
<code></code>
<code> <span class="nc">ServerName</span> example.com</code>
<code> <span class="nc">ServerAlias</span> www.example.com</code>
<code></code>
<code> <span class="c"># no-www</span></code>
<code> <span class="nc">RewriteEngine</span> <span class="ss">On</span></code>
<code> <span class="nc">RewriteCond</span> %{HTTP_HOST} ^www\.(.*)$ [NC]</code>
<code> <span class="nc">RewriteRule</span> ^(.*)$ http://%1$1 [R=301,L]</code>
<code></code>
<code> <span class="nc">ServerAdmin</span> [email protected]</code>
<code> <span class="nc">DocumentRoot</span> /Users/example/apps/example/production/current/public/</code>
<code></code>
<code> <span class="nc">LogLevel</span> warn</code>
<code> <span class="nc">CustomLog</span> /var/log/apache2/example-production-access.log "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{Host}i\" %D"</code>
<code> <span class="nc">ErrorLog</span> /var/log/apache2/example-production-error.log</code>
<code></code>
<code> <span class="p"><</span><span class="nl">Directory</span><span class="sr"> /</span><span class="p">></span></code>
<code> <span class="nc">AllowOverride</span> <span class="ss">FileInfo</span> <span class="ss">Indexes</span> <span class="ss">Options</span></code>
<code> <span class="nc">Options</span> -Indexes <span class="ss">FollowSymLinks</span> -MultiViews</code>
<code> <span class="nc">Order</span> allow,deny</code>
<code> <span class="nc">Allow</span> <span class="ss">from</span> <span class="ss">all</span></code>
<code> <span class="p"></</span><span class="nl">Directory</span><span class="p">></span></code>
<code></code>
<code><span class="p"></</span><span class="nl">VirtualHost</span><span class="p">></span></code>
<code></code></pre>
<p>To check the configuration syntax and then reload Apache so new vhosts are available, run the following:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>apachectl configtest <span class="o">&&</span> <span class="nb">sudo </span>apachectl graceful</code></pre>
<h2 id="apache2ctl"><code>apache2ctl</code> errors</h2>
<p>If <code>apachectl</code> outputs an error like <code>/usr/sbin/apachectl: line 82: ulimit: open files: cannot modify limit: Invalid argument</code>, you've ran into a Mac OS 10.6.6 regression. You can patch <code>apachectl</code> by running the following:</p>
<pre class="source source-bash"><code><span class="nb">sudo</span> /usr/bin/sed <span class="nt">-E</span> <span class="nt">-i</span> bak <span class="s1">'s/^(ULIMIT_MAX_FILES=".*)`ulimit -H -n`(")$/\11024\2/'</span> /usr/sbin/apachectl</code></pre>
<h2 id="passenger-preference-pane">Passenger Preference Pane</h2>
<p>A quick note on <a href="http://www.fngtps.com/passenger-preference-pane">Passenger Preference Pane</a>: I don't recommend you install it.
It can be handy in development environments with Passenger for quickly spinning up a new vhost for an app. It does not however allow you to customise vhost settings like logging nor setup a default vhost.</p>
<p>It's a much better idea to create a template and then script the deployment of new applications in production environments. There's more to deploying an app than just creating a new vhost. We also need to create user accounts, databases, configure logging, etc.</p>
<h2 id="apache-logs">Log rotation</h2>
<p>Mac OS uses <code>newsyslog</code> to rotate the main log files such as <code>system.log</code> and <code>mail.log</code>. It does not however automatically rotate anything in <code>/var/log/apache2</code>.
We could point <code>newsyslog</code> at each log file we want to rotate but <code>logrotate</code> lets us use wildcards.</p>
<pre class="source source-bash"><code>brew <span class="nb">install </span>logrotate</code>
<code><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /etc/logrotate.d</code>
<code><span class="nb">sudo </span>bash <span class="nt">-c</span> <span class="s1">'cat > /etc/logrotate.conf'</span> <span class="o"><<</span><span class="no">EOF</span><span class="sh"></span></code>
<code>compresscmd <span class="si">$(</span>which <span class="nb">gzip</span><span class="si">)</span><span class="sh"></span></code>
<code>tabooext + template</code>
<code>include /etc/logrotate.d</code>
<code><span class="no">EOF</span></code>
<code></code></pre>
<p>Set your Apache log rotate settings. I prefer to rotate my logs weekly and keep 520 rotations (10 years). Disk space is cheap and old logs might be useful. Save the following config as <code>/etc/logrotate.d/apache.conf</code>:</p>
<pre class="source"><code>/var/log/apache2/access_log /var/log/apache2/error_log /var/log/apache2/*.log {</code>
<code> weekly</code>
<code> missingok</code>
<code> rotate 520</code>
<code> compress</code>
<code> delaycompress</code>
<code> notifempty</code>
<code> create 640 root wheel</code>
<code> sharedscripts</code>
<code> postrotate</code>
<code> apachectl graceful</code>
<code> endscript</code>
<code>}</code></pre>
<p>Since we're going to be reloading Apache after rotating the logs, we can use this opportunity to rotate <code>production.log</code> from our Rails apps. These logs are larger so I only keep 10 rotations. Save the following template as <code>/etc/logrotate.d/vhosts-example.conf.template</code>:</p>
<pre class="source"><code>/Users/example/apps/example/production/shared/log/production.log {</code>
<code> weekly</code>
<code> missingok</code>
<code> rotate 10</code>
<code> compress</code>
<code> delaycompress</code>
<code> notifempty</code>
<code> create 640 example staff</code>
<code> sharedscripts</code>
<code>}</code></pre>
<p>Setup <code>logrotate</code> to run automatically via <code>launchd</code>. Debian runs <code>logrotate</code> daily at 06:25 which sounds fine by me. See <a href="http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man5/launchd.plist.5.html">man 5 launchd.plist</a> for details on the <code>StartCalendarInterval</code> option. Save the following as <code>/Library/LaunchDaemons/logrotate.plist</code>:</p>
<pre class="source source-xml"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span></code>
<code><span class="cp"><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span></code>
<code><span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span></code>
<code><span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>Label<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>logrotate<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>ProgramArguments<span class="nt"></key></span></code>
<code> <span class="nt"><array></span></code>
<code> <span class="nt"><string></span>/usr/local/sbin/logrotate<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>/etc/logrotate.conf<span class="nt"></string></span></code>
<code> <span class="nt"></array></span></code>
<code> <span class="nt"><key></span>Disabled<span class="nt"></key></span></code>
<code> <span class="nt"><false/></span></code>
<code> <span class="nt"><key></span>RunAtLoad<span class="nt"></key></span></code>
<code> <span class="nt"><false/></span></code>
<code> <span class="nt"><key></span>StartCalendarInterval<span class="nt"></key></span></code>
<code> <span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>Hour<span class="nt"></key></span></code>
<code> <span class="nt"><integer></span>6<span class="nt"></integer></span></code>
<code> <span class="nt"><key></span>Minute<span class="nt"></key></span></code>
<code> <span class="nt"><integer></span>25<span class="nt"></integer></span></code>
<code> <span class="nt"></dict></span></code>
<code><span class="nt"></dict></span></code>
<code><span class="nt"></plist></span></code></pre>
<p>And finally run the following to force an initial test rotation and then activate the <code>launchd</code> schedule:</p>
<pre class="source source-bash"><code><span class="nb">sudo</span> /usr/local/sbin/logrotate <span class="nt">-f</span> /etc/logrotate.conf</code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/logrotate.plist</code></pre>
<h1 id="users">User accounts</h1>
<p>We want to isolate our services (PostgreSQL, Memcached, etc) and applications in their own user accounts.
Unfortunately Mac OS doesn't provide a nice and simple <code>adduser</code> like command but we can make our own. Save the following as <code>/usr/local/bin/adduser</code> and run <code>chmod +x /usr/local/bin/adduser</code> to make it executable:</p>
<pre class="source source-bash"><code><span class="c">#!/bin/bash -e</span></code>
<code><span class="nv">NEW_USERNAME</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span></code>
<code><span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> <span class="o">]</span></code>
<code><span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"Usage: </span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span><span class="si">)</span><span class="s2"> [username]"</span> <span class="o">></span>&2</code>
<code> <span class="nb">exit </span>1</code>
<code><span class="k">fi</span></code>
<code>if <span class="nb">id</span> <span class="s2">"</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> 2> /dev/null</code>
<code><span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span><span class="si">)</span><span class="s2">: User </span><span class="se">\"</span><span class="nv">$NEW_USERNAME</span><span class="se">\"</span><span class="s2"> already exists."</span> <span class="o">></span>&2</code>
<code> <span class="nb">exit </span>1</code>
<code><span class="k">fi</span></code>
<code></code>
<code><span class="nv">NEW_UID</span><span class="o">=</span><span class="k">$((</span> <span class="si">$(</span>dscl <span class="nb">.</span> <span class="nt">-list</span> /Users UniqueID | <span class="nb">awk</span> <span class="s1">'{print $2}'</span> | <span class="nb">sort</span> <span class="nt">-n</span> | <span class="nb">tail</span> <span class="nt">-1</span><span class="si">)</span> <span class="o">+</span> <span class="m">1</span> <span class="k">))</span></code>
<code></code>
<code><span class="k">if</span> <span class="o">!</span> <span class="o">[</span> <span class="nv">$NEW_UID</span> <span class="nt">-gt</span> 500 <span class="o">]</span></code>
<code><span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span><span class="si">)</span><span class="s2">: Could not determine new UID."</span> <span class="o">></span>&2</code>
<code> <span class="nb">exit </span>1</code>
<code><span class="k">fi</span></code>
<code></code>
<code>dscl <span class="nb">.</span> create <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span></code>
<code>dscl <span class="nb">.</span> create <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> UniqueID <span class="nv">$NEW_UID</span></code>
<code>dscl <span class="nb">.</span> create <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> PrimaryGroupID 20</code>
<code>dscl <span class="nb">.</span> delete <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> AuthenticationAuthority</code>
<code>dscl <span class="nb">.</span> create <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> Password <span class="s1">'*'</span></code>
<code>dscl <span class="nb">.</span> create <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> UserShell /bin/bash</code>
<code>dscl <span class="nb">.</span> create <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span> NFSHomeDirectory <span class="s2">"/Users/</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span></code>
<code>createhomedir <span class="nt">-c</span> <span class="nt">-u</span> <span class="s2">"</span><span class="nv">$NEW_USERNAME</span><span class="s2">"</span></code></pre>
<p>Now we can simply run <code>sudo adduser foo</code> to create a new account which will not show on the login screen.</p>
<p>Removing a user account later is much easier than creating one. Just run the following:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>dscl <span class="nb">.</span> delete /Users/foo</code>
<code><span class="nb">sudo rm</span> <span class="nt">-rf</span> /Users/foo</code></pre>
<h1 id="email">Email</h1>
<p>Sometimes cron jobs fail. Wouldn't it be nice to hear about it? It's fairly easy to setup <code>postfix</code> to send mail via an external server. You could use <a href="http://mail.google.com/support/bin/answer.py?hl=en&answer=13287">Gmail</a>, <a href="http://sendgrid.com/">SendGrid</a> or even your own mail server.</p>
<p>Configure <code>postfix</code> to forward mail to smtp.example.com with SSL and authentication. Replace both instances of <code>smtp.example.com</code> with your SMTP server's hostname and <code>username:password</code> with the username and password for your SMTP account.</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>bash <span class="nt">-c</span> <span class="s1">'umask 0077 > /dev/null && echo "smtp.example.com username:password" >> /etc/postfix/smtp_auth'</span></code>
<code><span class="nb">sudo </span>postmap <span class="nb">hash</span>:/etc/postfix/smtp_auth</code>
<code><span class="nb">sudo </span>postconf <span class="nt">-e</span> <span class="nv">relayhost</span><span class="o">=</span>smtp.example.com:submission <span class="nv">smtp_use_tls</span><span class="o">=</span><span class="nb">yes </span><span class="nv">smtp_sasl_auth_enable</span><span class="o">=</span><span class="nb">yes </span><span class="nv">smtp_sasl_password_maps</span><span class="o">=</span><span class="nb">hash</span>:/etc/postfix/smtp_auth <span class="nv">tls_random_source</span><span class="o">=</span>dev:/dev/urandom <span class="nv">smtp_sasl_security_options</span><span class="o">=</span>noanonymous</code></pre>
<p>Forward root's mail to an external account. Replace <code>[email protected]</code> with your email address.</p>
<pre class="source source-bash"><code><span class="nb">sudo cp</span> <span class="nt">-ai</span> /etc/postfix/aliases<span class="o">{</span>,.bak<span class="o">}</span> <span class="c"># backup the original aliases file</span></code>
<code><span class="nb">sudo</span> /usr/bin/sed <span class="nt">-i</span> <span class="s1">''</span> <span class="s1">'s/^#root.*/root: [email protected]/'</span> /etc/postfix/aliases</code>
<code><span class="nb">grep</span> ^root /etc/aliases <span class="c"># check the replacement worked</span></code>
<code><span class="nb">sudo </span>postalias /etc/aliases</code></pre>
<p>Set <code>postfix</code> to listen on <code>localhost</code> only. There's no need to give spam zombies the time of day.</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>postconf <span class="nt">-e</span> <span class="nv">inet_interfaces</span><span class="o">=</span>localhost</code></pre>
<p>By default <code>postfix</code> will only start when there's mail in the local queue (typically from calls to <code>sendmail</code>). We'll use our own <code>launchd</code> config which will run <code>postfix</code> all the time. Save the following as <code>/Library/LaunchDaemons/org.postfix.master.plist</code>:</p>
<pre class="source source-xml"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span></code>
<code><span class="cp"><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span></code>
<code><span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span></code>
<code><span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>Label<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>org.postfix.master<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>Program<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>/usr/libexec/postfix/master<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>ProgramArguments<span class="nt"></key></span></code>
<code> <span class="nt"><array></span></code>
<code> <span class="nt"><string></span>master<span class="nt"></string></span></code>
<code> <span class="nt"></array></span></code>
<code> <span class="nt"><key></span>AbandonProcessGroup<span class="nt"></key></span></code>
<code> <span class="nt"><true/></span></code>
<code> <span class="nt"><key></span>RunAtLoad<span class="nt"></key></span></code>
<code> <span class="nt"><true</span> <span class="nt">/></span></code>
<code> <span class="nt"><key></span>KeepAlive<span class="nt"></key></span></code>
<code> <span class="nt"><true</span> <span class="nt">/></span></code>
<code><span class="nt"></dict></span></code>
<code><span class="nt"></plist></span></code></pre>
<p>Start the <code>postfix</code> daemon and check port 25 is now open:</p>
<pre class="source source-bash"><code>nc <span class="nt">-4zv</span> localhost 25 <span class="c"># should error</span></code>
<code><span class="nb">sudo </span>launchctl unload <span class="nt">-w</span> /System/Library/LaunchDaemons/org.postfix.master.plist <span class="c"># stop built in on demand daemon</span></code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/org.postfix.master.plist <span class="c"># load our always running daemon</span></code>
<code>nc <span class="nt">-4zv</span> localhost 25 <span class="c"># should succeed</span></code></pre>
<p>Forward your email to root (which will forward to your designated email address):</p>
<pre class="source source-bash"><code><span class="nb">echo </span>root <span class="o">></span> ~/.forward</code></pre>
<p>Send yourself a test email:</p>
<pre class="source source-bash"><code><span class="nb">date</span> | mail <span class="nt">-s</span> <span class="s2">"Test from </span><span class="si">$(</span><span class="nb">hostname</span> <span class="nt">-s</span><span class="si">)</span><span class="s2">"</span> <span class="nv">$USER</span></code></pre>
<h1 id="postgresql">PostgreSQL</h1>
<pre class="source source-bash"><code>brew <span class="nb">install </span>postgresql</code></pre>
<p>The instructions included in the caveats blurb from Homebrew (which after you run <code>brew install postgresql</code>) are great for setting up a single user development install. However, we want to run a system-wide instance to be used by all our applications. Run the following instead to setup PostgreSQL with ident authentication under the <code>postgres</code> account.</p>
<pre class="source source-bash"><code><span class="c"># create user account</span></code>
<code><span class="nb">sudo </span>adduser postgres</code>
<code><span class="c"># initialize the database cluster</span></code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> postgres initdb <span class="nt">-A</span> ident /usr/local/var/postgres</code>
<code><span class="c"># set PostgreSQL to run at startup</span></code>
<code><span class="nb">sudo cp</span> /usr/local/Cellar/postgresql/9.0.1/org.postgresql.postgres.plist /Library/LaunchDaemons/</code>
<code><span class="nb">sudo </span>defaults write /Library/LaunchDaemons/org.postgresql.postgres UserName postgres</code>
<code><span class="nb">sudo </span>plutil <span class="nt">-convert</span> xml1 /Library/LaunchDaemons/org.postgresql.postgres.plist</code>
<code><span class="nb">sudo chmod </span>644 /Library/LaunchDaemons/org.postgresql.postgres.plist</code>
<code><span class="c"># start the server</span></code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/org.postgresql.postgres.plist</code></pre>
<p>Add yourself as a superuser on the cluster so you can manage it without <code>sudo -u postgres</code>:</p>
<pre class="source source-bash"><code><span class="nb">sudo</span> <span class="nt">-u</span> postgres createuser <span class="nt">-s</span> <span class="nv">$USER</span></code>
<code>createdb</code></pre>
<p>The default PostgreSQL memory settings are very conservative. On a production machine you'll want to adjust these to suit your workload. Run <code>sudo cp -ai /usr/local/var/postgres/postgresql.conf{,.org}</code> before editing the config so you have a pristine copy of the config file to reference later.</p>
<p>Below are my current settings for my server which will give you an idea of what settings to play with. See <a href="http://www.postgresql.org/docs/9.0/static/runtime-config-resource.html">Resource Consumption</a> in the PostgreSQL docs for what each setting means. I recommend you start small and nothing beats trial, error and lots of testing. Don't forget to benchmark with production queries against production data.</p>
<pre class="source source-bash"><code>shared_buffers <span class="o">=</span> 256MB</code>
<code>work_mem <span class="o">=</span> 64MB</code>
<code>maintenance_work_mem <span class="o">=</span> 128MB</code>
<code>effective_cache_size <span class="o">=</span> 512MB</code>
<code>max_connections <span class="o">=</span> 50</code></pre>
<p>If you're adjusting the <code>shared_buffers</code> setting you will probably run into the following error (in <code>/var/log/messages</code>):</p>
<pre class="source"><code>FATAL: could not create shared memory segment: Invalid argument</code>
<code>DETAIL: Failed system call was shmget(key=5432001, size=276275200, 03600).</code>
<code>HINT: This error usually means that PostgreSQL's request for a shared memory segment exceeded your kernel's SHMMAX parameter. You can either reduce the request size or reconfigure the kernel with larger SHMMAX. To reduce the request size (currently 276275200 bytes), reduce PostgreSQL's shared_buffers parameter (currently 32768) and/or its max_connections parameter (currently 24).</code>
<code>If the request size is already small, it's possible that it is less than your kernel's SHMMIN parameter, in which case raising the request size or reconfiguring SHMMIN is called for.</code>
<code>The PostgreSQL documentation contains more information about shared memory configuration.</code></pre>
<p>The fix is easy. First make sure the quoted request size sounds sane (263 MB in the above error) and then update <code>SHMMAX</code> to be at least as large. I generally to round up to the next power of 2. <code>SHMALL</code> should generally be set to <code>ceil(SHMMAX/PAGE_SIZE)</code> where <code>PAGE_SIZE</code> is 4096 bytes. I'm setting <code>SHMMAX</code> to 512 MB:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>sysctl <span class="nt">-w</span> kern.sysv.shmmax<span class="o">=</span><span class="k">$((</span><span class="m">1048576</span> <span class="o">*</span> <span class="m">512</span><span class="k">))</span></code>
<code><span class="nb">sudo </span>sysctl <span class="nt">-w</span> kern.sysv.shmall<span class="o">=</span><span class="k">$((</span><span class="m">1048576</span> <span class="o">/</span> <span class="m">4096</span> <span class="o">*</span> <span class="m">512</span><span class="k">))</span></code>
<code>sysctl <span class="nt">-a</span> | egrep <span class="s1">'^kern.sysv.shm(max|all)'</span> | /usr/bin/sed <span class="s1">'s/: /=/'</span> | <span class="nb">sudo tee</span> <span class="nt">-a</span> /etc/sysctl.conf</code></pre>
<p>To restart the server run the following:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>launchctl unload <span class="nt">-w</span> /Library/LaunchDaemons/org.postgresql.postgres.plist</code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/org.postgresql.postgres.plist</code></pre>
<p>Finally type <code>psql</code> and you should drop straight into a PostgreSQL prompt.</p>
<h2 id="postgresql-permissions">Granting permissions</h2>
<p>Sometimes you may want to give full privileges on a database to a non-superuser account which is not the database owner.
For example: you may want to share a database between two apps or let developers play around with data on a staging instance.</p>
<p>To grant full access to a database to a non-superuser you can use the following in a superuser <code>psql</code> prompt:</p>
<pre class="source source-bash"><code>GRANT ALL ON DATABASE <span class="nv">$database</span> TO <span class="nv">$user</span><span class="p">;</span></code>
<code><span class="se">\c</span> <span class="nv">$database</span></code>
<code>ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO <span class="nv">$user</span><span class="p">;</span></code>
<code>GRANT ALL ON ALL TABLES IN SCHEMA public TO <span class="nv">$user</span><span class="p">;</span></code></pre>
<h1 id="memcached">Memcached</h1>
<pre class="source source-bash"><code><span class="c"># install memcached</span></code>
<code>brew <span class="nb">install </span>memcached</code>
<code><span class="c"># configure to run at startup</span></code>
<code><span class="nb">sudo </span>adduser memcache</code>
<code><span class="nb">sudo cp</span> /usr/local/Cellar/memcached/1.4.5/com.danga.memcached.plist /Library/LaunchDaemons/</code>
<code><span class="nb">sudo </span>defaults write /Library/LaunchDaemons/com.danga.memcached UserName memcache</code>
<code><span class="nb">sudo </span>plutil <span class="nt">-convert</span> xml1 /Library/LaunchDaemons/com.danga.memcached.plist</code>
<code><span class="nb">sudo chmod </span>644 /Library/LaunchDaemons/com.danga.memcached.plist</code>
<code><span class="c"># start the service</span></code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/com.danga.memcached.plist</code></pre>
<p>Memcached defaults to a maximum cache size of 64 MB. You can increase this if needed with by adding <code>-m</code>, <code>128</code> to <code>ProgramArguments</code> in the launchd plist and restarting the service.</p>
<p>Running <code>echo stats | nc localhost 11211</code> should give you memcached stats.</p>
<h2 id="memcached-security">Sharing the cache with multiple applications & security issues</h2>
<p>If you're configuring multiple applications to use it, make sure you namespace your keys with something like:</p>
<pre class="source source-ruby"><code><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:mem_cache_store</span><span class="p">,</span> <span class="p">{</span> <span class="ss">:namespace</span> <span class="o">=></span> <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">database_configuration</span><span class="p">[</span><span class="no">Rails</span><span class="p">.</span><span class="nf">env</span><span class="p">][</span><span class="s1">'database'</span><span class="p">]</span> <span class="p">}</span></code></pre>
<p>If you have untrusted users/applications, you'll probably want to setup multiple instances with <a href="http://code.google.com/p/memcached/wiki/SASLHowto">SASL authentication</a>.</p>
<h1 id="imagemagick">ImageMagick</h1>
<p>If your applications resize images with RMagick, you're going to need the ImageMagick libraries. Installing with <code>--disable-openmp</code> fixes some random crashing issues I was having.</p>
<pre class="source source-bash"><code>brew <span class="nb">install </span>imagemagick <span class="nt">--disable-openmp</span></code></pre>
<h1 id="tomcat">Tomcat</h1>
<p>To run <a href="http://lucene.apache.org/solr/">Solr</a> one needs a servlet container. <a href="http://tomcat.apache.org/">Tomcat</a> is a safe bet here. I recommend the excellent <a href="http://outoftime.github.com/sunspot/">Sunspot</a> library for using Solr in Rails.</p>
<h2 id="tomcat-installing">Installing</h2>
<p>Install Tomcat via Homebrew and then unlink it. Tomcat comes with a number of scripts which have generic names (<code>startup.sh</code>, <code>version.sh</code>, etc). We don't want those in our <code>PATH</code>.</p>
<pre class="source source-bash"><code>brew <span class="nb">install </span>tomcat</code>
<code>brew <span class="nb">unlink </span>tomcat</code></pre>
<p>Setup Tomcat to run in its own path (<code>/usr/local/tomcat</code>) under its own username (<code>tomcat</code>):</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>adduser tomcat</code>
<code><span class="nb">sudo mkdir</span> /usr/local/tomcat</code>
<code><span class="nb">sudo chown </span>tomcat /usr/local/tomcat</code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> tomcat <span class="nb">ln</span> <span class="nt">-s</span> <span class="si">$(</span>brew <span class="nt">--prefix</span> tomcat<span class="si">)</span>/libexec /usr/local/tomcat/</code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> tomcat <span class="nb">ln</span> <span class="nt">-s</span> libexec/<span class="o">{</span>bin,lib<span class="o">}</span> /usr/local/tomcat</code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> tomcat <span class="nb">mkdir</span> /usr/local/tomcat/<span class="o">{</span>logs,temp,webapps,work<span class="o">}</span></code>
<code><span class="nb">sudo </span>rsync <span class="nt">--archive</span> <span class="nt">--no-perms</span> <span class="nt">--chmod</span><span class="o">=</span><span class="s1">'ugo=rwX'</span> /usr/local/tomcat/<span class="o">{</span>libexec/conf/,conf<span class="o">}</span></code>
<code><span class="nb">sudo </span>find /usr/local/tomcat/conf <span class="nt">-exec</span> <span class="nb">chown </span>tomcat:staff <span class="o">{}</span> <span class="se">\;</span></code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> tomcat bash <span class="nt">-c</span> <span class="s1">'echo "org.apache.solr.level = WARNING" >> /usr/local/tomcat/conf/logging.properties'</span></code></pre>
<h2 id="tomcat-connectors">Configure connectors</h2>
<p>I recommend editing <code>/usr/local/tomcat/conf/server.xml</code> and replacing all <code><Connector /></code> entries with a single <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/http.html">HTTP connector</a> for <code>localhost:8080</code>:</p>
<pre class="source source-xml"><code><span class="nt"><Connector</span> <span class="na">address=</span><span class="s">"127.0.0.1"</span> <span class="na">port=</span><span class="s">"8080"</span> <span class="na">protocol=</span><span class="s">"HTTP/1.1"</span> <span class="na">connectionTimeout=</span><span class="s">"20000"</span> <span class="nt">/></span></code></pre>
<h2 id="tomcat-startup">Run at startup</h2>
<p>Save the following as <code>/Library/LaunchDaemons/org.apache.tomcat.plist</code> to have launchd start Tomcat automatically:</p>
<p>Note: Adjust <code>-Xmx2048M</code> to control how much memory Tomcat can use.</p>
<pre class="source source-xml"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span></code>
<code><span class="cp"><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span></code>
<code><span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span></code>
<code><span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>Label<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>org.apache.tomcat<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>ProgramArguments<span class="nt"></key></span></code>
<code> <span class="nt"><array></span></code>
<code> <span class="nt"><string></span>/usr/local/tomcat/bin/catalina.sh<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>run<span class="nt"></string></span></code>
<code> <span class="nt"></array></span></code>
<code> <span class="nt"><key></span>UserName<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>tomcat<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>EnvironmentVariables<span class="nt"></key></span></code>
<code> <span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>CATALINA_HOME<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>/usr/local/tomcat<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>JAVA_OPTS<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>-Djava.awt.headless=true -Xmx2048M<span class="nt"></string></span></code>
<code> <span class="nt"></dict></span></code>
<code> <span class="nt"><key></span>Disabled<span class="nt"></key></span></code>
<code> <span class="nt"><false/></span></code>
<code> <span class="nt"><key></span>RunAtLoad<span class="nt"></key></span></code>
<code> <span class="nt"><true/></span></code>
<code> <span class="nt"><key></span>HopefullyExitsFirst<span class="nt"></key></span></code>
<code> <span class="nt"><true/></span></code>
<code> <span class="nt"><key></span>ExitTimeOut<span class="nt"></key></span></code>
<code> <span class="nt"><integer></span>60<span class="nt"></integer></span></code>
<code><span class="nt"></dict></span></code>
<code><span class="nt"></plist></span></code></pre>
<p>Finally, start Tomcat with the following:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/org.apache.tomcat.plist</code></pre>
<p>To upgrade Tomcat to a newer version in the future, see my <a href="/posts/homebrew-tomcat-upgrade">Upgrading Tomcat with Homebrew</a> post.</p>
<h1 id="git">Git Hosting</h1>
<p>Run the following as your admin user on the server to install <a href="http://github.com/sitaramc/gitolite">gitolite</a>:</p>
<pre class="source source-bash"><code><span class="c"># create git user account</span></code>
<code><span class="nb">sudo </span>adduser git</code>
<code><span class="c"># copy our admin public key over to the git account</span></code>
<code><span class="nb">sudo cp</span> ~/.ssh/id_rsa.pub ~git/<span class="nv">$USER</span>.pub</code>
<code><span class="nb">sudo chown </span>git ~git/<span class="nv">$USER</span>.pub</code>
<code><span class="c"># switch to git user and configure shell</span></code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> git <span class="nt">-i</span></code>
<code>curl <span class="nt">-sL</span> http://github.com/jasoncodes/dotfiles/raw/master/install.sh | bash</code>
<code><span class="nb">exec </span>bash <span class="nt">-i</span> <span class="c"># reload the shell</span></code>
<code><span class="c"># clone gitolite source code</span></code>
<code>git clone git://github.com/sitaramc/gitolite gitolite-source</code>
<code><span class="c"># install gitolite</span></code>
<code><span class="nb">cd </span>gitolite-source</code>
<code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/bin ~/share/gitolite/conf ~/share/gitolite/hooks</code>
<code>src/gl-system-install ~/bin ~/share/gitolite/conf ~/share/gitolite/hooks</code>
<code><span class="nb">cd</span></code>
<code><span class="c"># configure gitolite with ourselves as the admin</span></code>
<code>gl-setup <span class="nv">$SUDO_USER</span>.pub</code>
<code><span class="c"># cleanup</span></code>
<code><span class="nb">rm</span> <span class="nv">$SUDO_USER</span>.pub</code>
<code><span class="nb">exit</span></code></pre>
<p>From your workstation you can then clone the config repo by running:</p>
<pre class="source source-bash"><code>git clone git@<span class="nv">$SERVER</span>:gitolite-admin.git <span class="nv">$SERVER</span><span class="nt">-gitolite-admin</span></code></pre>
<p>The <a href="http://sitaramc.github.com/gitolite/doc/">documentation</a> should contain everything you need. If you're new you'll want to read <a href="http://sitaramc.github.com/gitolite/doc/gitolite.conf.html">gitolite.conf</a> for permission config and <a href="http://sitaramc.github.com/gitolite/doc/migrate.html">migrate</a> if you're moving from Gitosis.</p>
<h1 id="ssh-alt">SSH on an alternate port</h1>
<p>I want SSH to be available on both IPv4 and IPv6 on an alternate secondary port. I could use <code>ipfw add 01000 fwd 127.0.0.1,22 tcp from any to me 4242</code> for IPv4 but <code>ip6fw</code> doesn't support forwarding. We can however just have <code>launchd</code> listen for SSH connections on an alternate port by running the following:</p>
<p>Note: Replace 4242 with your desired alternate port number.</p>
<pre class="source source-bash"><code><span class="nb">sudo cp</span> /System/Library/LaunchDaemons/ssh.plist /Library/LaunchDaemons/ssh-alt.plist</code>
<code><span class="nb">sudo </span>defaults write /Library/LaunchDaemons/ssh-alt Label com.openssh.sshd-alt</code>
<code><span class="nb">sudo </span>defaults write /Library/LaunchDaemons/ssh-alt Sockets <span class="s1">'{ Listeners = { SockServiceName = 4242; }; }'</span></code>
<code><span class="nb">sudo </span>plutil <span class="nt">-convert</span> xml1 /Library/LaunchDaemons/ssh-alt.plist</code>
<code><span class="nb">sudo chmod </span>644 /Library/LaunchDaemons/ssh-alt.plist</code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/ssh-alt.plist</code></pre>
<p>Now with this port opened on my firewall I can use the following in my <code>~/.ssh/config</code> to easily connect to my server with <code>ssh server</code>:</p>
<pre class="source source-ssh"><code><span class="k">Host</span> server</code>
<code> <span class="k">HostName</span> server.example.com</code>
<code> <span class="k">Port</span> <span class="m">4242</span></code></pre>
<h1 id="monit">Monit</h1>
<p><a href="http://mmonit.com/monit/">Monit</a> is a great tool that lets you monitor processes and make sure they're still serving requests. <code>launchd</code> handles restarting of failed services for us automatically but processes could still hang. This is where <code>monit</code> comes into the picture.</p>
<p>Check out the <a href="http://mmonit.com/monit/documentation/monit.html">documentation</a> for what can be monitored. Resources you can monitor include load average, system memory usage, disk space, process memory usage and connectivity.</p>
<p>First thing is to install <code>monit</code>. You'll need at least 5.2.3 as earlier versions are prone to crash on Mac OS. If you're using <a href="#homebrew">my Homebrew fork</a>, you're all good to go.</p>
<pre class="source source-bash"><code>brew <span class="nb">install </span>monit</code></pre>
<p>Create <code>/etc/monitrc</code>:</p>
<pre class="source"><code>set daemon 30 with start delay 60</code>
<code>set mail-format { from: [email protected] }</code>
<code>set alert [email protected]</code>
<code>set mailserver localhost</code>
<code>set httpd port 2812 allow localhost</code>
<code></code>
<code>check system server</code>
<code> if loadavg (1min) > 8 then alert</code>
<code> if loadavg (5min) > 6 then alert</code>
<code></code>
<code>check filesystem rootfs with path /</code>
<code> if space usage > 90 % then alert</code>
<code></code>
<code>check process apache2 with pidfile /var/run/httpd.pid</code>
<code> if failed URL http://localhost:80/ with timeout 5 seconds then alert</code>
<code></code>
<code>check host postgresql with address 127.0.0.1</code>
<code> if failed port 5432 with timeout 5 seconds then alert</code>
<code></code>
<code>check host tomcat with address 127.0.0.1</code>
<code> if failed port 8080</code>
<code> send "HEAD / HTTP/1.0\r\n\r\n"</code>
<code> expect "HTTP/1.1"</code>
<code> with timeout 5 seconds</code>
<code> then alert</code>
<code></code>
<code>check host memcached with address 127.0.0.1</code>
<code> if failed port 11211</code>
<code> send "stats\n"</code>
<code> expect "STAT pid"</code>
<code> with timeout 5 seconds</code>
<code> then alert</code></pre>
<p>Save the following as <code>/Library/LaunchDaemons/monit.plist</code>:</p>
<pre class="source source-xml"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span></code>
<code><span class="cp"><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span></code>
<code><span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span></code>
<code><span class="nt"><dict></span></code>
<code> <span class="nt"><key></span>UserName<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>root<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>Label<span class="nt"></key></span></code>
<code> <span class="nt"><string></span>monit<span class="nt"></string></span></code>
<code> <span class="nt"><key></span>OnDemand<span class="nt"></key></span></code>
<code> <span class="nt"><false/></span></code>
<code> <span class="nt"><key></span>RunAtLoad<span class="nt"></key></span></code>
<code> <span class="nt"><true/></span></code>
<code> <span class="nt"><key></span>ProgramArguments<span class="nt"></key></span></code>
<code> <span class="nt"><array></span></code>
<code> <span class="nt"><string></span>/usr/local/bin/monit<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>-c<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>/etc/monitrc<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>-I<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>-l<span class="nt"></string></span></code>
<code> <span class="nt"><string></span>/var/log/monit.log<span class="nt"></string></span></code>
<code> <span class="nt"></array></span></code>
<code><span class="nt"></dict></span></code>
<code><span class="nt"></plist></span></code></pre>
<p>Secure the config file, check the syntax, and then start <code>monit</code>:</p>
<pre class="source source-bash"><code><span class="nb">sudo chmod </span>600 /etc/monitrc</code>
<code><span class="nb">sudo </span>monit <span class="nt">-t</span> /etc/monitrc</code>
<code><span class="nb">sudo </span>launchctl load <span class="nt">-w</span> /Library/LaunchDaemons/monit.plist</code></pre>
<p>If you <code>kill -STOP</code> or otherwise break a service and you should get an email letting you know. You can view the current status with <code>sudo monit status</code>.</p>
<h2>Restarting failed services</h2>
<p>A great feature of Monit is that it can run tasks for you when something bad happens. In the case of Tomcat, I have created a script which kills Tomcat and then relaunches it. See my <a href="/posts/homebrew-tomcat-monit">Restarting Tomcat automatically on Mac OS with Monit</a> post for details.</p>
<h1 id="backups">Backups</h1>
<p>My current local backup solution consists of both Time Machine backups to a Time Capsule and a weekly startup disk image with <a href="http://www.bombich.com/">Carbon Copy Cloner</a>.
A problem with both of these solutions though is that they can't quiesce database writes to allow atomic snapshots. Hopefully Apple's working on their own ZFS/brtfs alternative for Mac OS 10.7 Lion which will allow cheap copy-on-write snapshots.</p>
<p>Until Time Machine can take atomic snapshots and efficiently backup large changing files, we need to make database dumps which can be picked up by our backup system. This applies to both PostgreSQL databases and and other large and changing files such as our Solr indexes.</p>
<p><strong>Update</strong>: To prevent Time Machine from backing up these files you can mark them as excluded either in the Time Machine preference pane or with <code>xattr</code>:</p>
<pre class="source source-bash"><code><span class="nb">sudo </span>xattr <span class="nt">-w</span> com.apple.metadata:com_apple_backup_excludeItem com.apple.backupd /usr/local/var/postgres <span class="c"># [...]</span></code></pre>
<p>If you don't exclude these directories from Time Machine, you'll find that your backup increments will be large and you'll quickly lose your history as Time Machine starts pruning old backups to make room. The worst bit is copying those large files every hour will put a noticeable resource strain on your machine.</p>
<p>If you want to audit what Time Machine is backing up (highly recommended if you suspect your backups are larger than they should be), I've found the easiest way is TimeTracker from <a href="http://www.charlessoft.com/">CharlesSoft</a> (the makers of the handy Pacifist tool). I found I had to run it under <code>sudo</code> with the Time Machine backup mounted to avoid errors.</p>
<p>I have a backup script which I run daily. It archives PostgreSQL databases and Solr indexes locally and then <code>rsync</code>s them along with my Git repos to an offsite VPS. Since we're archiving locally, we might as well send these same backups offsite.
If your databases are large you might want to look into <a href="http://stackoverflow.com/questions/2094963/postgresql-improving-pg-dump-pg-restore-performance">continuous archiving</a> to create incremental backups. Be sure to perform full <a href="http://www.postgresql.org/docs/9.0/static/continuous-archiving.html#BACKUP-BASE-BACKUP">base backups</a> regularly with any incremental setup (I recommend weekly).</p>
<p>The script makes use of my <a href="https://github.com/jasoncodes/scripts/blob/master/lib_exclusive_lock.sh"><code>lib_exclusive_lock</code></a> functions to prevent concurrent executions. With sufficiently large databases and a slow enough upstream this becomes a problem.</p>
<p>PostgreSQL databases are detected by querying the <a href="http://www.postgresql.org/docs/9.0/static/catalog-pg-database.html"><code>pg_database</code></a> catalog. Solr instances and their paths are detected by looking for the <code>solr/home</code> environment variable in the Tomcat contexts. You'll need to <code>brew install xmlstarlet</code> for this to work.</p>
<p>Note: unlike the rest of this guide, this backup script assumes GNU tools are installed as the default.</p>
<p>To use the script, save it as <code>~root/bin/backup</code> and add a crontab entry for root (<code>sudo crontab -e</code>) like <code>15 0 * * * ~/bin/backup</code>. Adjust the configuration variables at the top to suit.</p>
<pre class="source source-bash"><code><span class="c">#!/bin/bash -e</span></code>
<code><span class="nb">set</span> <span class="nt">-o</span> pipefail</code>
<code><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin</code>
<code></code>
<code><span class="nv">BWLIMIT</span><span class="o">=</span>30</code>
<code><span class="nv">ARCHIVE_COUNT</span><span class="o">=</span>5</code>
<code><span class="nv">REMOTE_HOST</span><span class="o">=</span>[email protected]</code>
<code></code>
<code><span class="c"># grab lock</span></code>
<code><span class="nb">cd</span> <span class="s2">"</span><span class="sb">`</span><span class="nb">dirname</span> <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span><span class="sb">`</span><span class="s2">"</span></code>
<code><span class="nb">source</span> <span class="s2">"lib_exclusive_lock.sh"</span></code>
<code>exclusive_lock_require</code>
<code></code>
<code><span class="c"># prepare backup directory</span></code>
<code><span class="nb">umask </span>0077</code>
<code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/backups</code>
<code><span class="nb">cd</span> ~/backups</code>
<code></code>
<code><span class="c"># compact git repos</span></code>
<code>find ~git/repositories <span class="nt">-maxdepth</span> 1 <span class="nt">-type</span> d <span class="nt">-name</span> <span class="s1">'*.git'</span> | <span class="k">while </span><span class="nb">read </span>REPO</code>
<code><span class="k">do</span></code>
<code> <span class="o">(</span></code>
<code> <span class="nb">cd</span> <span class="nv">$REPO</span></code>
<code> git gc <span class="nt">--auto</span> <span class="nt">--quiet</span></code>
<code> <span class="o">)</span></code>
<code><span class="k">done</span></code>
<code></code>
<code><span class="c"># backup PostgreSQL databases</span></code>
<code><span class="nb">mkdir</span> <span class="nt">-p</span> postgres</code>
<code>su - postgres <span class="nt">-lc</span> <span class="s2">"psql -q -A -t"</span> <span class="o"><<</span><span class="no">SQL</span><span class="sh"> |</span></code>
<code>SELECT datname</code>
<code>FROM pg_database</code>
<code>WHERE datname NOT LIKE 'template%';</code>
<code><span class="no">SQL</span></code>
<code><span class="k">while </span><span class="nb">read </span>DB_NAME</code>
<code><span class="k">do</span></code>
<code> <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"postgres/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span></code>
<code> <span class="nv">FILENAME</span><span class="o">=</span><span class="s2">"postgres/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">_</span><span class="sb">`</span><span class="nb">date</span> +%Y%m%d<span class="sb">`</span><span class="s2">.sql.bz2"</span></code>
<code> <span class="nb">nice </span>su - postgres <span class="nt">-lc</span> <span class="s2">"pg_dump --format=p </span><span class="nv">$DB_NAME</span><span class="s2">"</span> | <span class="nb">nice </span>bzip2 <span class="o">></span> <span class="s2">"</span><span class="k">${</span><span class="nv">FILENAME</span><span class="k">}</span><span class="s2">.new"</span></code>
<code> <span class="nb">mv</span> <span class="s2">"</span><span class="k">${</span><span class="nv">FILENAME</span><span class="k">}</span><span class="s2">"</span><span class="o">{</span>.new,<span class="o">}</span></code>
<code> find <span class="s2">"postgres/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-maxdepth</span> 1 <span class="nt">-type</span> f <span class="nt">-name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">_*.sql.*"</span> | <span class="nb">sort</span> <span class="nt">-r</span> | <span class="nb">tail</span> <span class="nt">-n</span> +<span class="k">$((</span>ARCHIVE_COUNT <span class="o">+</span> <span class="m">1</span><span class="k">))</span> | xargs <span class="nt">-r</span> <span class="nb">rm</span></code>
<code><span class="k">done</span></code>
<code></code>
<code><span class="c"># backup Solr indexes</span></code>
<code>xml sel <span class="nt">-t</span> <span class="nt">-v</span> <span class="s2">"Context/@path "</span> <span class="nt">-o</span> <span class="s2">" "</span> <span class="nt">-v</span> <span class="s2">"Context/Environment[@name='solr/home']/@value"</span> /usr/local/tomcat/conf/Catalina/localhost/<span class="k">*</span>.xml | <span class="k">while </span><span class="nb">read </span>CONTEXT_PATH SOLR_HOME</code>
<code><span class="k">do</span></code>
<code> if <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$CONTEXT_PATH</span><span class="s2">"</span> <span class="nt">-a</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$SOLR_HOME</span><span class="s2">"</span> <span class="o">]</span></code>
<code> <span class="k">then</span></code>
<code> <span class="nv">DB_NAME</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$CONTEXT_PATH</span><span class="s2">"</span> | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s1">'s/-production-solr$//'</span><span class="si">)</span><span class="s2">"</span></code>
<code> <span class="nb">mkdir</span> <span class="nt">-p</span> ~/<span class="s2">"backups/solr/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span></code>
<code> <span class="nv">FILENAME</span><span class="o">=</span>~/<span class="s2">"backups/solr/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">_</span><span class="si">$(</span><span class="nb">date</span> +%Y%m%d<span class="si">)</span><span class="s2">.tar.bz2"</span></code>
<code> <span class="nb">rm</span> <span class="nt">-rf</span> <span class="s2">"</span><span class="nv">$SOLR_HOME</span><span class="s2">"</span>.bak</code>
<code> <span class="nb">cp</span> <span class="nt">-lr</span> <span class="s2">"</span><span class="nv">$SOLR_HOME</span><span class="s2">"</span><span class="o">{</span>,.bak<span class="o">}</span></code>
<code> <span class="o">(</span><span class="nb">cd</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SOLR_HOME</span><span class="k">}</span><span class="s2">.bak"</span> <span class="o">&&</span> <span class="nb">nice tar </span>c .<span class="o">)</span> | <span class="nb">nice </span>bzip2 <span class="o">></span> <span class="s2">"</span><span class="k">${</span><span class="nv">FILENAME</span><span class="k">}</span><span class="s2">.new"</span></code>
<code> <span class="nb">rm</span> <span class="nt">-rf</span> <span class="s2">"</span><span class="nv">$SOLR_HOME</span><span class="s2">"</span>.bak</code>
<code> <span class="nb">mv</span> <span class="s2">"</span><span class="k">${</span><span class="nv">FILENAME</span><span class="k">}</span><span class="s2">"</span><span class="o">{</span>.new,<span class="o">}</span></code>
<code> find <span class="s2">"solr/</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-maxdepth</span> 1 <span class="nt">-type</span> f <span class="nt">-name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">_*.tar.bz2"</span> | <span class="nb">sort</span> <span class="nt">-r</span> | <span class="nb">tail</span> <span class="nt">-n</span> +<span class="k">$((</span>ARCHIVE_COUNT <span class="o">+</span> <span class="m">1</span><span class="k">))</span> | xargs <span class="nt">-r</span> <span class="nb">rm</span></code>
<code> <span class="k">fi</span></code>
<code>done</code>
<code></code>
<code><span class="c"># rsync with a retry. sometimes there's intermittent connectivity issues.</span></code>
<code><span class="k">function </span>do_rsync<span class="o">()</span></code>
<code><span class="o">{</span></code>
<code> <span class="k">if</span> <span class="o">!</span> rsync <span class="nt">--archive</span> <span class="nt">--delete-after</span> <span class="nt">--partial-dir</span><span class="o">=</span>.partial <span class="nt">--bwlimit</span> <span class="nv">$BWLIMIT</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span></code>
<code> <span class="k">then</span></code>
<code> <span class="nb">echo </span>rsync failed: <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span></code>
<code> <span class="nb">sleep </span>5m</code>
<code> <span class="nb">echo </span>retrying...</code>
<code> rsync <span class="nt">--archive</span> <span class="nt">--delete-after</span> <span class="nt">--partial-dir</span><span class="o">=</span>.partial <span class="nt">--bwlimit</span> <span class="nv">$BWLIMIT</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span></code>
<code> <span class="nb">echo </span>retried.</code>
<code> <span class="k">fi</span></code>
<code><span class="o">}</span></code>
<code></code>
<code><span class="c"># copy backups offsite</span></code>
<code></code>
<code>do_rsync ~git/repositories/ <span class="nv">$REMOTE_HOST</span>:~/backups/git/</code>
<code></code>
<code>find ~/backups/postgres/<span class="k">*</span> <span class="nt">-maxdepth</span> 0 <span class="nt">-type</span> d | <span class="k">while </span><span class="nb">read </span>DIR</code>
<code><span class="k">do</span></code>
<code> do_rsync <span class="s2">"</span><span class="nv">$DIR</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$REMOTE_HOST</span><span class="s2">:~/backups/postgres/"</span></code>
<code><span class="k">done</span></code>
<code></code>
<code>find ~/backups/solr/<span class="k">*</span> <span class="nt">-maxdepth</span> 0 <span class="nt">-type</span> d | <span class="k">while </span><span class="nb">read </span>DIR</code>
<code><span class="k">do</span></code>
<code> do_rsync <span class="s2">"</span><span class="nv">$DIR</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$REMOTE_HOST</span><span class="s2">:~/backups/solr/"</span></code>
<code><span class="k">done</span></code></pre>
<h1 id="apps">Applications</h1>
<h2 id="capistrano">Automating application deployment with Capistrano</h2>
<p>The <a href="https://github.com/capistrano/capistrano/wiki">Capistrano Wiki</a> covers the basics on how to setup Capistrano. There are a few gotchas to watch out for however.</p>
<p>The documentation for <a href="http://rvm.beginrescueend.com/integration/capistrano/">RVM</a> and <a href="http://gembundler.com/deploying.html">Bundler</a> cover setting up Capistrano support pretty well. Other than requiring the right files, the only other thing you should need to do is disable <code>sudo</code> with <code>set :use_sudo, false</code>.</p>
<p>For the remote cache to work (<code>set :deploy_via, :remote_cache</code>), you'll need to enable SSH agent forwarding with <code>ssh_options[:forward_agent] = true</code>. This allows the app user on the server to temporarily use your SSH key to authenticate to your Git repository when deploying.</p>
<p>Here's a complete Capistrano recipe (<code>config/deploy.rb</code>):</p>
<pre class="source source-ruby"><code><span class="nb">require</span> <span class="s1">'bundler/capistrano'</span></code>
<code></code>
<code><span class="vg">$:</span><span class="p">.</span><span class="nf">unshift</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="s1">'./lib'</span><span class="p">,</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'rvm_path'</span><span class="p">]))</span></code>
<code><span class="nb">require</span> <span class="s1">'rvm/capistrano'</span></code>
<code></code>
<code><span class="n">set</span> <span class="ss">:application</span><span class="p">,</span> <span class="s1">'example'</span> <span class="c1"># change me</span></code>
<code><span class="n">set</span> <span class="ss">:server_name</span><span class="p">,</span> <span class="s1">'server'</span> <span class="c1"># change me</span></code>
<code><span class="n">set</span> <span class="ss">:user</span><span class="p">,</span> <span class="s1">'example'</span> <span class="c1"># change me</span></code>
<code><span class="n">set</span><span class="p">(</span><span class="ss">:deploy_to</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"~/apps/</span><span class="si">#{</span><span class="n">application</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">stage</span><span class="si">}</span><span class="s2">"</span> <span class="p">}</span></code>
<code><span class="n">set</span> <span class="ss">:keep_releases</span><span class="p">,</span> <span class="mi">5</span></code>
<code><span class="n">set</span><span class="p">(</span><span class="ss">:releases</span><span class="p">)</span> <span class="p">{</span> <span class="n">capture</span><span class="p">(</span><span class="s2">"ls -x </span><span class="si">#{</span><span class="n">releases_path</span><span class="si">}</span><span class="s2">"</span><span class="p">).</span><span class="nf">split</span><span class="p">.</span><span class="nf">sort</span> <span class="p">}</span></code>
<code></code>
<code><span class="n">set</span> <span class="ss">:scm</span><span class="p">,</span> <span class="ss">:git</span></code>
<code><span class="n">set</span> <span class="ss">:repository</span><span class="p">,</span> <span class="s2">"git@</span><span class="si">#{</span><span class="n">server_name</span><span class="si">}</span><span class="s2">:</span><span class="si">#{</span><span class="n">application</span><span class="si">}</span><span class="s2">.git"</span></code>
<code><span class="n">set</span> <span class="ss">:branch</span><span class="p">,</span> <span class="s2">"master"</span></code>
<code><span class="n">set</span> <span class="ss">:deploy_via</span><span class="p">,</span> <span class="ss">:remote_cache</span></code>
<code></code>
<code><span class="n">ssh_options</span><span class="p">[</span><span class="ss">:forward_agent</span><span class="p">]</span> <span class="o">=</span> <span class="kp">true</span></code>
<code><span class="n">set</span> <span class="ss">:use_sudo</span><span class="p">,</span> <span class="kp">false</span></code>
<code></code>
<code><span class="n">role</span> <span class="ss">:web</span><span class="p">,</span> <span class="n">server_name</span></code>
<code><span class="n">role</span> <span class="ss">:app</span><span class="p">,</span> <span class="n">server_name</span></code>
<code><span class="n">role</span> <span class="ss">:db</span><span class="p">,</span> <span class="n">server_name</span><span class="p">,</span> <span class="ss">:primary</span> <span class="o">=></span> <span class="kp">true</span></code>
<code></code>
<code><span class="n">namespace</span> <span class="ss">:deploy</span> <span class="k">do</span></code>
<code> <span class="n">task</span> <span class="ss">:start</span> <span class="k">do</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="n">task</span> <span class="ss">:stop</span> <span class="k">do</span></code>
<code> <span class="k">end</span></code>
<code></code>
<code> <span class="n">task</span> <span class="ss">:restart</span> <span class="k">do</span></code>
<code> <span class="n">run</span> <span class="s2">"touch </span><span class="si">#{</span><span class="n">current_path</span><span class="si">}</span><span class="s2">/tmp/restart.txt"</span></code>
<code> <span class="k">end</span></code>
<code><span class="k">end</span></code>
<code></code>
<code><span class="n">before</span> <span class="s2">"deploy:symlink"</span><span class="p">,</span> <span class="s2">"deploy:migrate"</span></code>
<code><span class="n">after</span> <span class="s2">"deploy:update"</span><span class="p">,</span> <span class="s2">"deploy:cleanup"</span></code></pre>
<h2 id="app-user">Setting up the application environment</h2>
<p>With your application configured to deploy via Capistrano, you can prepare the new application environment (user account, database, vhost) with the following script. Save a copy as <code>/usr/local/bin/createapp</code> and <code>chmod +x</code> it:</p>
<pre class="source source-bash"><code><span class="c">#!/bin/bash -e</span></code>
<code><span class="nv">APPNAME</span><span class="o">=</span><span class="k">${</span><span class="nv">1</span>:?Specify<span class="p"> application name</span><span class="k">}</span></code>
<code><span class="nv">DOMAIN</span><span class="o">=</span><span class="k">${</span><span class="nv">2</span><span class="k">:-${</span><span class="nv">APPNAME</span><span class="k">}</span><span class="p">.com</span><span class="k">}</span></code>
<code></code>
<code><span class="c"># create user account</span></code>
<code><span class="nb">sudo </span>adduser <span class="nv">$APPNAME</span></code>
<code><span class="c"># copy over admin public key</span></code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> <span class="nv">$APPNAME</span> <span class="nt">-i</span> bash <span class="nt">-c</span> <span class="s1">'umask 0077 > /dev/null && mkdir -p ~/.ssh/ && cat >> ~/.ssh/authorized_keys'</span> < ~/.ssh/authorized_keys</code>
<code><span class="c"># seed SSH known hosts with git server details</span></code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> <span class="nv">$APPNAME</span> <span class="nt">-i</span> bash <span class="nt">-c</span> <span class="s1">'umask 0077 > /dev/null && mkdir -p ~/.ssh/ && echo "$(hostname -s) $(cat /etc/ssh_host_rsa_key.pub)" >> ~/.ssh/known_hosts'</span></code>
<code><span class="c"># install dotfiles</span></code>
<code><span class="nb">sudo</span> <span class="nt">-u</span> <span class="nv">$APPNAME</span> <span class="nt">-i</span> bash < <<span class="o">(</span> curl <span class="nt">-sL</span> http://github.com/jasoncodes/dotfiles/raw/master/install.sh <span class="o">)</span></code>
<code><span class="c"># forward mail to root</span></code>
<code><span class="nb">echo </span>root | <span class="nb">sudo</span> <span class="nt">-u</span> <span class="nv">$APPNAME</span> <span class="nt">-i</span> bash <span class="nt">-c</span> <span class="s1">'cat > ~/.forward'</span></code>
<code></code>
<code><span class="c"># create PostgreSQL database</span></code>
<code>createuser <span class="nt">-SDR</span> <span class="nv">$APPNAME</span></code>
<code>createdb <span class="nt">-O</span> <span class="nv">$APPNAME</span> <span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span>_production</code>
<code></code>
<code><span class="c"># setup vhost</span></code>
<code><span class="nb">sudo cp</span> /etc/apache2/other/vhosts-example.conf.template /etc/apache2/other/vhosts-<span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span><span class="nt">-production</span>.conf</code>
<code><span class="nb">sudo</span> /usr/bin/sed <span class="nt">-i</span> <span class="s1">''</span> <span class="nt">-e</span> s/example<span class="se">\.</span>com/<span class="k">${</span><span class="nv">DOMAIN</span><span class="k">}</span>/g <span class="nt">-e</span> s/example/<span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span>/g /etc/apache2/other/vhosts-<span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span><span class="nt">-production</span>.conf</code>
<code><span class="nb">sudo</span> <span class="nt">-e</span> /etc/apache2/other/vhosts-<span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span><span class="nt">-production</span>.conf <span class="c"># check config, make any custom tweaks</span></code>
<code><span class="nb">sudo </span>apachectl configtest <span class="o">&&</span> <span class="nb">sudo </span>apachectl graceful</code>
<code></code>
<code><span class="c"># setup log rotation</span></code>
<code><span class="nb">sudo cp</span> /etc/logrotate.d/vhosts-example.conf.template /etc/logrotate.d/vhosts-<span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span><span class="nt">-production</span>.conf</code>
<code><span class="nb">sudo</span> /usr/bin/sed <span class="nt">-i</span> <span class="s1">''</span> s/example/<span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span>/g /etc/logrotate.d/vhosts-<span class="k">${</span><span class="nv">APPNAME</span><span class="k">}</span><span class="nt">-production</span>.conf</code></pre>
<h2 id="deploying">Deploying your application</h2>
<p>From within the app directory on your workstation you can then run the following:</p>
<pre class="source source-bash"><code><span class="c"># prepare application directories for deployment</span></code>
<code>cap deploy:setup</code>
<code><span class="c"># deploy the application</span></code>
<code>cap deploy</code></pre>
Clean Google Analytics tracking data from your URLs2011-01-09T00:00:00+10:002013-01-07T00:00:00+10:00https://jasoncodes.com/posts/clean-google-analytics-tracking-urls<p>FeedBurner has a feature which integrates with Google Analytics to tell you how many of your hits come via feed readers. This is rather useful because feed readers typically don't pass any useful referrer information (especially desktop clients). This tracking can be enabled by ticking the "<a href="http://www.google.com/support/feedburner/bin/answer.py?hl=en&answer=165769">Track clicks as a traffic source in Google Analytics</a>" option in your FeedBurner account.</p>
<p>However, this comes with a disadvantage to those of us who care about clean URLs. I'm sure I'm not the only one who dislikes seeing otherwise clean URLs polluted with Google Analytics tracking data:</p>
<pre class="source"><code>http://example.com/posts/foo-bar?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+example</code></pre>
<p>You may even add <code>utm_source=twitter</code> to short URLs when you post to Twitter in addition to tracking your feeds. Either way, a big problem with this is that these URLs are often shared around and not everybody takes the time to remove all the cruft and share the clean canonical URL.</p>
<p>Luckily Google Analytics <a href="http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html">asynchronous tracking</a> provides a nice little API that lets us <a href="http://code.google.com/apis/analytics/docs/tracking/asyncUsageGuide.html#PushingFunctions">queue a function</a> to be ran after the tracking request has been sent. This combined with the <a href="https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history#The_replaceState%28%29.c2.a0method"><code>history.replaceState</code></a> method in HTML5 lets us remove the the tracking data from the URL without reloading the page or breaking the browser's history. Another win for modern browsers.</p>
<p>The additional code required to make this happen is one extra statement to be added to your Google Analytics tracking JavaScript:</p>
<pre class="source source-javascript"><code><span class="kd">var</span> <span class="nx">_gaq</span> <span class="o">=</span> <span class="nx">_gaq</span> <span class="o">||</span> <span class="p">[];</span></code>
<code><span class="nx">_gaq</span><span class="p">.</span><span class="nf">push</span><span class="p">([</span><span class="dl">'</span><span class="s1">_setAccount</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">UA-XXXXX-X</span><span class="dl">'</span><span class="p">]);</span></code>
<code><span class="nx">_gaq</span><span class="p">.</span><span class="nf">push</span><span class="p">([</span><span class="dl">'</span><span class="s1">_trackPageview</span><span class="dl">'</span><span class="p">]);</span></code>
<code class="hll"><span class="nx">_gaq</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span></code>
<code class="hll"> <span class="kd">var</span> <span class="nx">newPath</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">[</span><span class="sr">?&</span><span class="se">]</span><span class="sr">utm_</span><span class="se">[^</span><span class="sr">?&</span><span class="se">]</span><span class="sr">+/g</span><span class="p">,</span> <span class="dl">""</span><span class="p">).</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/^&/</span><span class="p">,</span> <span class="dl">"</span><span class="s2">?</span><span class="dl">"</span><span class="p">)</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">hash</span><span class="p">;</span></code>
<code class="hll"> <span class="nf">if </span><span class="p">(</span><span class="nx">history</span><span class="p">.</span><span class="nx">replaceState</span><span class="p">)</span> <span class="nx">history</span><span class="p">.</span><span class="nf">replaceState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="nx">newPath</span><span class="p">);</span></code>
<code class="hll"><span class="p">});</span></code>
<code></code>
<code><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span></code>
<code> <span class="kd">var</span> <span class="nx">ga</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">script</span><span class="dl">'</span><span class="p">);</span> <span class="nx">ga</span><span class="p">.</span><span class="nx">type</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">text/javascript</span><span class="dl">'</span><span class="p">;</span> <span class="nx">ga</span><span class="p">.</span><span class="k">async</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span></code>
<code> <span class="nx">ga</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="p">(</span><span class="dl">'</span><span class="s1">https:</span><span class="dl">'</span> <span class="o">==</span> <span class="nb">document</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">protocol</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">https://ssl</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">http://www</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.google-analytics.com/ga.js</span><span class="dl">'</span><span class="p">;</span></code>
<code> <span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementsByTagName</span><span class="p">(</span><span class="dl">'</span><span class="s1">script</span><span class="dl">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span> <span class="nx">s</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nf">insertBefore</span><span class="p">(</span><span class="nx">ga</span><span class="p">,</span> <span class="nx">s</span><span class="p">);</span></code>
<code><span class="p">})();</span></code></pre>
<p><strong>Update 2011-01-26:</strong> I originally called <code>replaceState</code> with just <code>location.pathname</code> but that resulted in the removal of anchors within a page (e.g. links to comments). The code has been updated to <code>location.pathname + location.hash</code> to keep anchors. e.g. <code>/foo/bar?utm_medium=example#comment-42</code> will now be replaced with <code>/foo/bar#comment-42</code>.</p>
<p><strong>Update 2013-01-07:</strong> Thanks <a href="http://henrik.nyh.se/">Henrik Nyh</a> for a <a href="#comment-758637324">patch</a> to preserve parameters other than those used by Google Analytics.</p>
Ruby 1.9.2 encoding issues with Rails 2.3.102010-12-30T00:00:00+10:002010-12-30T00:00:00+10:00https://jasoncodes.com/posts/ruby19-rails2-encodings<p>While switching my app over from Ruby Enterprise Edition 1.8.7 to Ruby 1.9.2p0, I ran into a few issues with content encodings. The vast majority of these issues could be solved by placing <code># -*- coding: utf-8 -*-</code> at the top of the source file. A couple of the problems I ran into ended up being a little more complex.</p>
<h2>UTF-8 form parameters</h2>
<p>The first problem was the <code>params</code> hash was not arriving to my controllers encoding as UTF-8 but instead as ASCII-8BIT. This is not a problem if all your inputs are ASCII-7 but that wasn't so for me. With content such as <code>café</code> I was getting an exception later on during the request that had become all too familiar to me: <code>incompatible character encodings: ASCII-8BIT and UTF-8</code>.</p>
<p>Rails 3 solves this very nicely by doing a number of things including interpreting params as UTF-8 and adding <a href="http://railssnowman.info/">workarounds for Internet Explorer</a>. I'll leave the workarounds and <code>accept-charset="UTF-8"</code> form attributes as an exercise for the reader.</p>
<p>We can force Rails to interpret all string parameters as UTF-8 with <a href="https://rails.lighthouseapp.com/projects/8994/tickets/4336-ruby19-submitted-string-form-parameters-with-non-ascii-characters-cause-encoding-errors#ticket-4336-3">a small patch</a>. Save the following as <code>config/initializers/utf8_params.rb</code>:</p>
<pre class="source source-ruby"><code><span class="k">raise</span> <span class="s2">"Check if this is still needed on "</span> <span class="o">+</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">version</span> <span class="k">unless</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">version</span> <span class="o">==</span> <span class="s1">'2.3.10'</span></code>
<code></code>
<code><span class="k">class</span> <span class="nc">ActionController::Base</span></code>
<code></code>
<code> <span class="k">def</span> <span class="nf">force_utf8_params</span></code>
<code> <span class="n">traverse</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="k">do</span> <span class="o">|</span><span class="n">object</span><span class="p">,</span> <span class="n">block</span><span class="o">|</span></code>
<code> <span class="k">if</span> <span class="n">object</span><span class="p">.</span><span class="nf">kind_of?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span></code>
<code> <span class="n">object</span><span class="p">.</span><span class="nf">each_value</span> <span class="p">{</span> <span class="o">|</span><span class="n">o</span><span class="o">|</span> <span class="n">traverse</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="n">block</span><span class="p">)</span> <span class="p">}</span></code>
<code> <span class="k">elsif</span> <span class="n">object</span><span class="p">.</span><span class="nf">kind_of?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></code>
<code> <span class="n">object</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">o</span><span class="o">|</span> <span class="n">traverse</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="n">block</span><span class="p">)</span> <span class="p">}</span></code>
<code> <span class="k">else</span></code>
<code> <span class="n">block</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">object</span><span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code> <span class="n">object</span></code>
<code> <span class="k">end</span></code>
<code> <span class="n">force_encoding</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="k">do</span> <span class="o">|</span><span class="n">o</span><span class="o">|</span></code>
<code> <span class="n">o</span><span class="p">.</span><span class="nf">force_encoding</span><span class="p">(</span><span class="no">Encoding</span><span class="o">::</span><span class="no">UTF_8</span><span class="p">)</span> <span class="k">if</span> <span class="n">o</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="ss">:force_encoding</span><span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code> <span class="n">traverse</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">force_encoding</span><span class="p">)</span></code>
<code> <span class="k">end</span></code>
<code> <span class="n">before_filter</span> <span class="ss">:force_utf8_params</span></code>
<code> </code>
<code><span class="k">end</span></code></pre>
<h2>ERB templates</h2>
<p>I was having trouble getting the old <code># -*- coding: utf-8 -*-</code> working in ERB and I was wondering if I'd have to start looking into other options. I'd get an exception any time I outputted a string containing UTF-8 (from params or the model). Luckily I came across the following code in <a href="https://github.com/rails/rails/blob/v2.3.10/actionpack/lib/action_view/template_handlers/erb.rb#L14"><code>action_view/template_handlers/erb.rb</code></a>:</p>
<pre class="source source-ruby"><code><span class="n">magic</span> <span class="o">=</span> <span class="vg">$1</span> <span class="k">if</span> <span class="n">template</span><span class="p">.</span><span class="nf">source</span> <span class="o">=~</span> <span class="sr">/\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/</span></code>
<code><span class="n">erb</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">magic</span><span class="si">}</span><span class="s2"><% __in_erb_template=true %></span><span class="si">#{</span><span class="n">template</span><span class="p">.</span><span class="nf">source</span><span class="si">}</span><span class="s2">"</span></code></pre>
<p>It looks like Rails does support specifying the encoding of ERB templates after all. All you need to do is to add the following to the top of your template:</p>
<pre class="source"><code><%# coding: utf-8 %></code></pre>
<p>The key part I was missing is to have no whitespace between the <code><%</code> and <code>#</code>. Additionally, if this is in a plain text template for ActionMailer, you'll want to avoid any newlines between this block and your main content otherwise you'll have a blank line at the top of the email.</p>
<h2>Environment variables</h2>
<p>I ran into a couple of encoding errors that I was having trouble reproducing locally but were highly reproducible on production (running on Apache with Passenger). It even ran fine when I booted up a <code>RAILS_ENV=production script/server</code> on the production host. This had me stumped for a few minutes until I thought to look at my environment variables. A <code>set | grep UTF</code> revealed the following:</p>
<pre class="source source-bash"><code><span class="nv">LANG</span><span class="o">=</span>en_AU.UTF-8</code>
<code><span class="nv">LC_CTYPE</span><span class="o">=</span>en_US.UTF-8</code></pre>
<p>The bugs reproduced fine once I <code>unset</code> these in my local terminal session. Ruby 1.9 can use these environment variables to set the default encoding to something other than US-ASCII. Handy once you know about it. You may want to try running your tests with these unset to see if you're missing any encoding issues.</p>
<p>If you want Ruby to default to UTF-8 when loading files (i.e <code>File.read</code>) when these environment variables are not set, add the following to an initializer:</p>
<pre class="source source-ruby"><code><span class="no">Encoding</span><span class="p">.</span><span class="nf">default_external</span> <span class="o">=</span> <span class="s1">'UTF-8'</span></code></pre>
Using Sass on Heroku with Hassle2010-12-12T00:00:00+10:002011-01-08T00:00:00+10:00https://jasoncodes.com/posts/sass-heroku-hassle<p>A little while ago <a href="http://developingego.com/">Lucas Willett</a> and I hacked together <a href="http://til.developingego.com/post/1266966478/compass-rails-3-and-heroku-without-a-hassle">Compass, Rails 3 and Heroku without a Hassle</a>. This is a combination of three components:</p>
<ol>
<li>
<p>Render compiled CSS to <code>tmp/stylesheets/</code> instead of <code>public/stylesheets/</code> as the file system is <a href="http://docs.heroku.com/constraints#read-only-filesystem">read only</a> on Heroku.</p>
</li>
<li>
<p>Use <code>Rack::Static</code> to mount <code>tmp/stylesheets</code> on <code>/stylesheets</code> so they're accessible via their original URL.</p>
</li>
<li>
<p>Monkey patch <a href="http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html"><code>ActionView::Helpers::AssetTagHelper</code></a> to check <code>tmp/stylesheets/</code> for stylesheets in addition to the default of <code>public/stylesheets/</code>. This is to ensure cache busting continues to function and you don't unintentionally serve old stylesheets to your users.</p>
</li>
</ol>
<p>The whole reason this workaround came about was that we had some trouble in getting Hassle to work. The problem was around initialisation which wasn't hard to solve in retrospect but I had the hair-brained idea to write the quick workaround above. It worked. The cache busting was an added bonus (of which I would have needed anyway).</p>
<p>One problem though is <code>Rack::Static</code> does not fall-through if it doesn't find a file. As a result, this solution does not work if you have existing files in <code>public/stylesheets/</code> you want to serve alongside your Sass stylesheets. Luckily for us this wasn't a problem.</p>
<p>However now I am wanting to use this same setup on an existing project which has a mix of both CSS files and Sass stylesheets. The time for the quick hack is over. I have <a href="https://github.com/jasoncodes/hassle">forked Hassle</a> and added a couple of features from our workaround:</p>
<ol>
<li>
<p><a href="https://github.com/jasoncodes/hassle/commit/b2ce7d03b01795a4da5bdbd1447b9c8fe8d82347">Always run Hassle even when not on Heroku.</a>
I didn't want to have <code>public/</code> polluted with generated files so I set Hassle to run all the time. This keeps <code>public/stylesheets/</code> clean in development mode.</p>
</li>
<li>
<p><a href="https://github.com/jasoncodes/hassle/commit/74f9a95ae6273bdc200a46c8bd503fa7704f98a7">Fix cache busting on Hassle stylesheets.</a>
The monkey patch to ensure Sass stylesheets correctly refresh when you redeploy.</p>
</li>
</ol>
<p><strong>Update:</strong> I have also made a couple of <a href="https://github.com/jasoncodes/hassle/compare/ee74b86...a61495d">tweaks to the HTTP response headers</a> to improve the cacheability of the generated stylesheets. By adding a <code>Last-Modified</code> header to the response, browsers can use a <code>If-Modified-Since</code> header in their request to get a much smaller 304 response if nothing has changed. Ideally browsers will just use the existing <code>max-age</code>, but this doesn't happen often enough.</p>
<p>If you'd like to use my fork, add the following to your <code>Gemfile</code>:</p>
<pre class="source"><code>gem 'hassle', :git => 'git://github.com/jasoncodes/hassle.git'</code></pre>
<p>We use Hassle in combination with <a href="http://compass-style.org/">Compass</a> and it's working great.</p>
A free SSL certificate for your web server2010-12-11T00:00:00+10:002017-11-21T00:00:00+10:00https://jasoncodes.com/posts/startssl-free-ssl<p><strong>Update 2017-11-21:</strong> StartCom will soon be <a href="https://groups.google.com/d/msg/mozilla.dev.security.policy/LM1SpKHJ-oc/4nBsP1xJAQAJ">no longer be issuing certificates</a>. I recommend you look into <a href="https://letsencrypt.org">Let's Encrypt</a> with the <a href="https://github.com/lukas2511/dehydrated">Dehydrated</a> client.</p>
<h1>No more self-signed certificates</h1>
<p>Typically for a low volume site where verified identity is not import one would use a self-signed certificate for SSL. Unfortunately these triggers security warnings in browsers and require you to recognise/remember checksums to prevent man-in-the-middle attacks when accessing your own servers over HTTPS.</p>
<p><a href="https://startssl.com/">StartSSL</a> offers free domain verified SSL server certificates which work pretty much everywhere. The only real downside is they have a 1 year expiry. This means you need to create a new one every year rather than say a 10 year self-signed certificate which will probably last the life of your server.</p>
<p>These free certificates are also chained which means configuration can be a little tricky as there are a few traps you can fall into. If you don't setup the chain correctly on your server you can run into compatibility issues with clients that may not be immediately obvious. These include either forgetting to setup the chain or creating the links in the wrong order. With either of these errors I myself could still access the site fine.</p>
<p>And that's where this post comes in. I need to setup a couple of new certificates and this time I'm making notes on on the steps I'm taking, referencing my existing setup as I go. At worst I'll save some time next year when I have to renew these certificates. Hopefully you'll find this useful as well.</p>
<h1>Setting it all up</h1>
<p>I run Debian stable on my servers. At the time of writing this is Debian Lenny with Apache 2.2.9. Substitute <code>example.com</code> for your domain name where applicable.</p>
<h2>Authenticating with StartSSL</h2>
<p>Note: As of the time of writing, Chrome has some issues with SSL client certificates which will cause you problems. I recommend using Safari (or Firefox if that's your thing).</p>
<p>If this is your first time using <a href="https://startssl.com/">StartSSL</a>, you'll need to create an account. Click on Control Panel and then on Sign-up. Fill out all the details and you'll get an SSL client certificate which you use to authenticate with the website.</p>
<p>The client certificate expires after a year so you'll have to create a new one when it comes time to renew your server certificate. StartSSL will send you an email when both are coming up for renewal. To create a new client certificate, first reverify your email address under Validations Wizard: Email Address Validation and then create a new certificate under Certificates Wizard</p>
<h2>Requesting a server certificate</h2>
<p>Validations Wizard: Domain Name Validation</p>
<p>Certificates Wizard: Web Server SSL/TLS Certificate</p>
<pre class="source"><code>openssl req -new -newkey rsa:4096 -days 365 -nodes -keyout example.com.key -out example.com.csr</code></pre>
<p>Pick the CSR option when prompted and upload the contents of <code>example.com.csr</code>. You will also be prompted for a hostname underneath your domain. I run a <a href="http://no-www.org/">no-www</a> shop so I used my server's hostname (<code>host.example.com</code>). If you want to run <code>www.example.com</code>, enter <code>www</code> here.</p>
<p>As of this point the <code>.csr</code> file is no longer required and can be removed. Alternatively you could generate a CSR with a longer expiry and reuse it next year.</p>
<p>And now we wait for certificate to be issued. This usually happens within the half hour. When you receive the certificate signing confirmation email, download the following certificates:</p>
<ol>
<li>Toolbox > Retrieve Certificate: You will see your newly created certificate. Save it as <code>example.com.crt</code>.</li>
<li>Toolbox > StartCom CA Certificates: Download "StartCom Root CA (PEM encoded)" (<a href="https://www.startssl.com/certs/ca.pem">ca.pem</a>)</li>
<li>Toolbox > StartCom CA Certificates: Download "Class 1 Intermediate Server CA" (<a href="https://www.startssl.com/certs/class1/sha2/pem/sub.class1.server.sha2.ca.pem">sub.class1.server.sha2.ca.pem</a>)</li>
</ol>
<p>Copy the <code>.crt</code>, <code>.key</code> and <code>.pem</code> files to <code>/etc/apache2/ssl</code> on your server.</p>
<h2>Configuring Apache</h2>
<p>Run the following commands as root:</p>
<pre class="source"><code>cd /etc/apache2/ssl</code>
<code>mv ca.pem startssl.ca.crt</code>
<code>mv sub.class1.server.sha2.ca.pem startssl.sub.class1.server.sha2.ca.crt</code>
<code>cat startssl.sub.class1.server.sha2.ca.crt startssl.ca.crt > startssl.chain.class1.server.crt</code>
<code>cat example.com.{key,crt} startssl.chain.class1.server.crt > example.com.pem</code>
<code>ln -sf example.com.pem apache.pem</code>
<code>chown root:ssl *.crt *.key *.pem</code>
<code>chmod 640 *.key *.pem</code></pre>
<p>Edit <code>/etc/apache2/sites-available/ssl</code> and add the following within the <code><VirtualHost></code> block:</p>
<pre class="source source-apache"><code><span class="nc">SSLEngine</span> <span class="ss">On</span></code>
<code><span class="nc">SSLCertificateFile</span> /etc/apache2/ssl/example.com.crt</code>
<code><span class="nc">SSLCertificateKeyFile</span> /etc/apache2/ssl/example.com.key</code>
<code><span class="nc">SSLCertificateChainFile</span> /etc/apache2/ssl/startssl.chain.class1.server.crt</code></pre>
<p>At this point you'll want to configure the rest of Apache for SSL if you haven't already.</p>
<p>Check that your Apache config parses as valid:</p>
<pre class="source"><code>apache2ctl -t</code></pre>
<p>And then restart Apache with the new config:</p>
<pre class="source"><code>/etc/init.d/apache2 reload</code></pre>
<h2>Verifying everything worked</h2>
<p>Run the following after restarting Apache to check the certificate chain:</p>
<pre class="source"><code>echo HEAD / | openssl s_client -connect localhost:443 -quiet > /dev/null</code></pre>
<p>You should see something like:</p>
<pre class="source"><code>depth=2 /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority</code>
<code>verify error:num=19:self signed certificate in certificate chain</code>
<code>verify return:0</code></pre>
<p>A depth of 2 and a return value of 0 is good. If the certificate chain is wrong, you'll probably see something like:</p>
<pre class="source"><code>depth=0 /description=12345-ABCDEF123456/C=XX/O=Persona Not Validated/OU=StartCom Free Certificate Member/CN=host.example.com/[email protected]</code>
<code>verify error:num=20:unable to get local issuer certificate</code>
<code>verify return:1</code>
<code>depth=0 /description=12345-ABCDEF123456/C=XX/O=Persona Not Validated/OU=StartCom Free Certificate Member/CN=host.example.com/[email protected]</code>
<code>verify error:num=27:certificate not trusted</code>
<code>verify return:1</code>
<code>depth=0 /description=12345-ABCDEF123456/C=XX/O=Persona Not Validated/OU=StartCom Free Certificate Member/CN=host.example.com/[email protected]</code>
<code>verify error:num=21:unable to verify the first certificate</code>
<code>verify return:1</code></pre>
<h2>Hosting email services over SSL</h2>
<p>I have one host setup to send and receive email using Exim 4 for SMTP and Courier for IMAP. To have these services use your new SSL certificate, point them to your existing certificate files:</p>
<pre class="source"><code>ln -sf /etc/apache2/ssl/example.com.pem /etc/courier/imapd.pem</code>
<code>ln -sf /etc/apache2/ssl/example.com.pem /etc/courier/pop3d.pem</code>
<code></code>
<code>ln -sf /etc/apache2/ssl/example.com.pem /etc/exim4/exim.pem</code>
<code>ln -sf /etc/apache2/ssl/example.com.crt /etc/exim4/exim.crt</code>
<code>ln -sf /etc/apache2/ssl/example.com.key /etc/exim4/exim.key</code></pre>
<p>Outlook and Outlook Express are both fairly broken but fortunately I found some workarounds. You'll want to set the following config option for Exim (in <code>/etc/exim4/conf.d/main/03_exim4-config_tlsoptions</code> if you are using split configuration on Debian):</p>
<pre class="source source-bash"><code><span class="c"># Outlook Express really sucks</span></code>
<code><span class="c"># See <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=482012></span></code>
<code><span class="nv">MAIN_TLS_TRY_VERIFY_HOSTS</span><span class="o">=</span><span class="s1">''</span></code>
<code></code>
<code>MAIN_TLS_CERTKEY <span class="o">=</span> CONFDIR/exim.pem</code></pre>
<p>Outlook does not work properly with the altname in the certificate. You may have to use the servers hostname (<code>host.example.com</code>) rather than the domain name (<code>example.com</code>).</p>
<tt>gup</tt>: A friendlier <tt>git pull --rebase</tt>2010-11-09T00:00:00+10:002011-09-16T00:00:00+10:00https://jasoncodes.com/posts/gup-git-rebase<p>By now most <code>git</code> users would have heard about <a href="http://www.gitready.com/intermediate/2009/01/31/intro-to-rebase.html">rebasing your local commits</a> on top of the remote branch HEAD before you <code>git push</code> them rather than merging to <a href="http://www.viget.com/extend/only-you-can-prevent-git-merge-commits/">prevent the proliferation of useless same branch merge commits</a> like "Merge remote branch 'origin/topic' into topic".</p>
<p>If one is using <code>git pull</code>, the rebasing can be accomplished by using <code>git pull --rebase</code> instead. This essentially changes <code>git pull</code> from doing <code>git fetch && git merge $TRACKING_BRANCH</code> to <code>git fetch && git rebase $TRACKING_BRANCH</code>.</p>
<p>There's still the inconvenience of having to stash any uncommitted changes before a rebase. If you don't, you'll get messages like "refusing to pull with rebase: your working tree is not up-to-date". This results in a fetch, stash, rebase, pop dance which gets tiring. I think we can do better.</p>
<p><strong>Update 2011-01-11:</strong> Another thing to watch out for when using <code>git pull --rebase</code> is merge commits. You cannot preserve merges when rebasing using <code>git pull</code> as it does not let you pass in the <code>--preserve-merges</code> option. This means you could end up losing valuable merge commits. Glen Maddern has a great post on <a href="http://notes.envato.com/developers/rebasing-merge-commits-in-git/">Rebasing Merge Commits in Git</a> over on the <a href="http://notes.envato.com/">Envato Notes</a> blog which covers this in more detail. The good news is that my <code>gup</code> script already handles rebasing merge commits by passing the <code>-p</code> (<code>--preserve-merges</code>) option to <code>git rebase</code>.</p>
<p><strong>Update 2011-09-16:</strong> My <code>gup</code> function has had a number of tweaks since I first posted. I removed the quiet flag from <code>git stash pop</code> as late versions of <code>git</code> seem to silence the error when pop fails. The other significant change is that <code>gup</code> will now explicitly fast-forward if it can rather than rebasing. The rest of the changes are minor (e.g. refactoring for style).</p>
<p><strong>Update 2011-09-16:</strong> I now prefer <a href="https://github.com/aanand/git-up"><code>git-up</code></a> when available as it has nicer output and it also has an option to show if one needs to <code>bundle</code>. I still use the <code>gup</code> function as it's handy on foreign systems where I don't have my normal Ruby setup and I like the command name better :).</p>
<pre class="source source-bash"><code><span class="k">function </span>gup</code>
<code><span class="o">{</span></code>
<code> <span class="c"># subshell for `set -e` and `trap`</span></code>
<code> <span class="o">(</span></code>
<code> <span class="nb">set</span> <span class="nt">-e</span> <span class="c"># fail immediately if there's a problem</span></code>
<code></code>
<code> <span class="c"># use `git-up` if installed</span></code>
<code> <span class="k">if </span><span class="nb">type </span>git-up <span class="o">></span> /dev/null 2>&1</code>
<code> <span class="k">then</span></code>
<code> <span class="nb">exec </span>git-up</code>
<code> <span class="k">fi</span></code>
<code></code>
<code> <span class="c"># fetch upstream changes</span></code>
<code> git fetch</code>
<code></code>
<code> <span class="nv">BRANCH</span><span class="o">=</span><span class="si">$(</span>git symbolic-ref <span class="nt">-q</span> HEAD<span class="si">)</span></code>
<code> <span class="nv">BRANCH</span><span class="o">=</span><span class="k">${</span><span class="nv">BRANCH</span><span class="p">##refs/heads/</span><span class="k">}</span></code>
<code> <span class="nv">BRANCH</span><span class="o">=</span><span class="k">${</span><span class="nv">BRANCH</span><span class="k">:-</span><span class="nv">HEAD</span><span class="k">}</span></code>
<code></code>
<code> <span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="si">$(</span>git config branch.<span class="nv">$BRANCH</span>.remote<span class="si">)</span><span class="s2">"</span> <span class="nt">-o</span> <span class="nt">-z</span> <span class="s2">"</span><span class="si">$(</span>git config branch.<span class="nv">$BRANCH</span>.merge<span class="si">)</span><span class="s2">"</span> <span class="o">]</span></code>
<code> <span class="k">then</span></code>
<code> <span class="nb">echo</span> <span class="s2">"</span><span class="se">\"</span><span class="nv">$BRANCH</span><span class="se">\"</span><span class="s2"> is not a tracking branch."</span> <span class="o">></span>&2</code>
<code> <span class="nb">exit </span>1</code>
<code> <span class="k">fi</span></code>
<code></code>
<code> <span class="c"># create a temp file for capturing command output</span></code>
<code> <span class="nv">TEMPFILE</span><span class="o">=</span><span class="s2">"</span><span class="sb">`</span><span class="nb">mktemp</span> <span class="nt">-t</span> gup.XXXXXX<span class="sb">`</span><span class="s2">"</span></code>
<code> <span class="nb">trap</span> <span class="s1">'{ rm -f "$TEMPFILE"; }'</span> EXIT</code>
<code></code>
<code> <span class="c"># if we're behind upstream, we need to update</span></code>
<code> <span class="k">if </span>git status | <span class="nb">grep</span> <span class="s2">"# Your branch"</span> <span class="o">></span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">"</span></code>
<code> <span class="k">then</span></code>
<code></code>
<code> <span class="c"># extract tracking branch from message</span></code>
<code> <span class="nv">UPSTREAM</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">"</span> | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s2">"'"</span> <span class="nt">-f</span> 2<span class="si">)</span></code>
<code> <span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$UPSTREAM</span><span class="s2">"</span> <span class="o">]</span></code>
<code> <span class="k">then</span></code>
<code> <span class="nb">echo </span>Could not detect upstream branch <span class="o">></span>&2</code>
<code> <span class="nb">exit </span>1</code>
<code> <span class="k">fi</span></code>
<code></code>
<code> <span class="c"># can we fast-forward?</span></code>
<code> <span class="nv">CAN_FF</span><span class="o">=</span>1</code>
<code> <span class="nb">grep</span> <span class="nt">-q</span> <span class="s2">"can be fast-forwarded"</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">"</span> <span class="o">||</span> <span class="nv">CAN_FF</span><span class="o">=</span>0</code>
<code></code>
<code> <span class="c"># stash any uncommitted changes</span></code>
<code> git stash | <span class="nb">tee</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">"</span></code>
<code> <span class="o">[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PIPESTATUS</span><span class="p">[0]</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-eq</span> 0 <span class="o">]</span> <span class="o">||</span> <span class="nb">exit </span>1</code>
<code></code>
<code> <span class="c"># take note if anything was stashed</span></code>
<code> <span class="nv">HAVE_STASH</span><span class="o">=</span>0</code>
<code> <span class="nb">grep</span> <span class="nt">-q</span> <span class="s2">"No local changes"</span> <span class="s2">"</span><span class="nv">$TEMPFILE</span><span class="s2">"</span> <span class="o">||</span> <span class="nv">HAVE_STASH</span><span class="o">=</span>1</code>
<code></code>
<code> <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$CAN_FF</span><span class="s2">"</span> <span class="nt">-ne</span> 0 <span class="o">]</span></code>
<code> <span class="k">then</span></code>
<code> <span class="c"># if nothing has changed locally, just fast foward.</span></code>
<code> git merge <span class="nt">--ff</span> <span class="s2">"</span><span class="nv">$UPSTREAM</span><span class="s2">"</span></code>
<code> <span class="k">else</span></code>
<code> <span class="c"># rebase our changes on top of upstream, but keep any merges</span></code>
<code> git rebase <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$UPSTREAM</span><span class="s2">"</span></code>
<code> <span class="k">fi</span></code>
<code></code>
<code> <span class="c"># restore any stashed changes</span></code>
<code> <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$HAVE_STASH</span><span class="s2">"</span> <span class="nt">-ne</span> 0 <span class="o">]</span></code>
<code> <span class="k">then</span></code>
<code> git stash pop</code>
<code> <span class="k">fi</span></code>
<code></code>
<code> fi</code>
<code></code>
<code> <span class="o">)</span></code>
<code><span class="o">}</span></code></pre>
<p>Throw the following into your shell's startup script. I keep the script in my <a href="https://github.com/jasoncodes/dotfiles">dotfiles</a> as it's much easier to bring it along to new machines. Alternatively you could remove the function wrapper and save it as a standalone script in <code>~/bin</code>.</p>
<p>Once setup you can pull changes for the current tracking branch, rebase any unpushed commits on top of any new ones from upstream, all while preserving anything you have uncommitted (via <code>git stash</code>) with a single command: <code>gup</code>.</p>