<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Blooming Cacti</title><link>https://bloomingcacti.com/</link><description>Bring life to a barren, technological wasteland</description><lastBuildDate>Thu, 15 Nov 2018 01:59:43 -0600</lastBuildDate><item><title>Hugo Bound?</title><link>https://bloomingcacti.com/blog/2018/hugo-bound</link><description>&lt;p&gt;I've been thinking about switching my blogs from being generated with &lt;a href="https://getpelican.com/"&gt;Pelican&lt;/a&gt; to being generated with &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;. Pelican takes nearly 30 seconds to rebuild my &lt;a href="https://minorthoughts.com/"&gt;main blog&lt;/a&gt;, while Hugo promises to work much more quickly.&lt;/p&gt;
&lt;p&gt;Pelican is written in &lt;a href="https://python.org/"&gt;Python&lt;/a&gt; and needs to be installed in a &lt;a href="http://virtualenv.org/"&gt;virtualenv&lt;/a&gt;, with a whole passel of Python modules. Hugo is written in &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; and compiled to a single, static executable. That one file can be dropped anywhere on a server and run without any further dependencies. That kind of simplicity appeals to me, over my current setup which has Pelican running in a &lt;a href="https://docker.io/"&gt;Docker&lt;/a&gt; container.&lt;/p&gt;
&lt;p&gt;My current setup is more baroque than just running Pelican in a container. All of my blog posts are stored in &lt;a href="https://git-scm.com"&gt;Git&lt;/a&gt;. To publish something, I commit the new post to Git and push the update to the repository on &lt;a href="https://bitbucket.org"&gt;BitBucket&lt;/a&gt;. BitBucket then pings my server with a notification that something was changed. My server pulls the updates from the Git repository, uses the Dockerized install of Pelican to rebuild the site, and then pushes the updated HTML to the web server. There's a lot of moving parts there. And most of the time I don't even remember how they work or where they're all installed.&lt;/p&gt;
&lt;p&gt;Before I can possibly move to Hugo, I need to document how my current setup works, where everything is installed, and how the pieces communicate with each other. I also need to document which features of Pelican I'm actually using (and which plugins), so that I can be sure that Hugo has equivalent functionality.&lt;/p&gt;
&lt;p&gt;(I hope &lt;a href="https://en.m.wikipedia.org/wiki/Betteridge's_law_of_headlines"&gt;Betteridge's Law&lt;/a&gt; doesn't apply to this blog post.)&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Thu, 15 Nov 2018 01:59:43 -0600</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2018-11-14:/blog/2018/hugo-bound</guid><category>Python</category><category>Hugo</category></item><item><title>Picking a Development Language</title><link>https://bloomingcacti.com/blog/2016/picking-a-development-language</link><description>&lt;p&gt;&lt;em&gt;Note: I wrote this post 2 years ago, in a Houston hotel room. I recently realized that I never actually posted it to the blog.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’m thinking about ditching PHP and using Python for web development. @marcoarment &lt;a href="https://twitter.com/marcoarment/status/438143806043856897"&gt;linked&lt;/a&gt; to &lt;a href="http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/"&gt;PHP: a fractal of bad design&lt;/a&gt;. It’s a comprehensive rant about everything that’s wrong with PHP.&lt;/p&gt;
&lt;p&gt;Summary:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;PHP is full of surprises: mysql_real_escape_string, E_ALL&lt;/li&gt;
&lt;li&gt;PHP is inconsistent: strpos, str_rot13&lt;/li&gt;
&lt;li&gt;PHP requires boilerplate: error-checking around C API calls, ===&lt;/li&gt;
&lt;li&gt;PHP is flaky: ==, foreach ($foo as &amp;amp;$bar)&lt;/li&gt;
&lt;li&gt;PHP is opaque: no stack traces by default or for fatals, complex error reporting&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve seen most of these myself. I’ve been ignoring on the theory that PHP runs everywhere and it’s the language I know best. But, realistically, I’m going to be running my stuff on a VPS from now on. So the “runs anywhere” logic is moot. I also don’t know it all &lt;em&gt;that&lt;/em&gt; well. If I’m going to be doing more programming, it makes sense to invest my time in a language that will be better long term instead of one that’s horrible.&lt;/p&gt;
&lt;p&gt;I’ve resisted committing to a modern language like Python or Ruby. I like that I can create one-off PHP scripts in a single new file, drop them on the server, and have them executed easily. I don’t like having to create a separate Python / Ruby &lt;em&gt;application&lt;/em&gt; for each idea that I have.&lt;/p&gt;
&lt;p&gt;Today, I had an epiphany: create one application for all of my one-off ideas. Implement each idea in a separate method (or module) and use application URLs to route between them. If I have a new idea, I only need to create a new method and then redeploy the application. In some ways, that will be simpler than creating new one-off scripts all over the place. For one thing, all of my scripts will be collected in one place, making maintenance easier.&lt;/p&gt;
&lt;p&gt;Why Python? It’s the language of choice for Editorial, my favorite scriptable iOS editor. I can run Python scripts on iOS, via Pythonista. The highly recommended Sublime Text editor uses Python as its scripting engine. Sublime Text runs on Mac, Windows, and Linux. Python looks to be my best choice for a universal scripting / development language across all of my text editors and platforms.&lt;/p&gt;
&lt;p&gt;The author of the rant recommended three Python projects for web development.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://flask.pocoo.org/"&gt;Flask&lt;/a&gt; for the web stuff&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.pylonsproject.org/"&gt;Pyramid&lt;/a&gt; - medium level&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, which is a complex monstrosity that works well for building sites like Django's&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2016-03-11:/blog/2016/picking-a-development-language</guid><category>Python</category></item><item><title>Linode: Final Migration Steps</title><link>https://bloomingcacti.com/blog/2012/linode-final-migration-steps</link><description>&lt;p&gt;This post is a bit of a grab bag, of the final things that I did to set up my new Linode server.&lt;/p&gt;
&lt;h3 id="videohub-migration"&gt;Videohub Migration&lt;/h3&gt;
&lt;p&gt;I had created a very small, personal video hosting application using PHP and &lt;a href="http://symfony.com"&gt;symfony&lt;/a&gt;. It was pretty easy to move but I’d forgotten some of the details of how it worked, so I had to figure out a few extra steps to get it working.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I rsynced the project over to Linode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I symlinked the videohub2/web directory to the public_html directory that I’d created for the application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I edited the symfony file, to change the PHP path to &lt;code&gt;/usr/bin/php&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I cleaned the symfony cache, to remove cached references to the old server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./symfony cc
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I set the timezone, at the top of &lt;code&gt;web/index.php&lt;/code&gt;, to stop PHP from complaining about a missing default time zone.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;date_default_timezone_set(&amp;#39;America/Chicago&amp;#39;);
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then I got a fun error from PHP, preventing the app from running at all.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;PHP Fatal error:  session_start(): Failed to initialize storage module: files (path: )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I figured out that PHP didn’t have anywhere to save session files. I solved this by adding a new config line to my PHP-FPM pool definition and creating a new directory. (This happened before I finalized my PHP application creation script.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;php_admin_value[session.save_path] = /srv/www/videohub/tmp
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, I hit my last error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Unable to open PDO connection [wrapped: could not find driver]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I’d semi-forgotten that the app was built with a SQLite database and that PHP needed SQLite extensions, to access the database. I solved the error by installing the PHP extension for SQLite.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;apt-get install sqlite3 php5-sqlite
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After I restarted PHP (to pick up the new extensions), the app worked beautifully.&lt;/p&gt;
&lt;h3 id="sending-mail"&gt;Sending Mail&lt;/h3&gt;
&lt;p&gt;I don’t want to run a full mail server on my Linode, but I did want the ability to send out emails, so that I could get notifications from my cron jobs, in the event that anything went wrong.&lt;/p&gt;
&lt;p&gt;After a quick search of the Linode Library, I found a guide to &lt;a href="http://library.linode.com/email/postfix/gateway-debian-6-squeeze"&gt;creating a gateway with Postfix on Debian 6 (Squeeze)&lt;/a&gt;. I followed the steps in that guide (up until the “create mail directories” step) and ended up with a perfectly functional outgoing mail server.&lt;/p&gt;
&lt;h3 id="logrotate"&gt;Logrotate&lt;/h3&gt;
&lt;p&gt;I needed to ensure that none of my (many) nginx and PHP log files grew too large. Debian comes with logrotate preinstalled. Adding my log files to logrotate’s configuration was pretty easy. Out of the box, logrotate loads its configurations from any files that are in &lt;code&gt;/etc/logrotate.d&lt;/code&gt;. I created a new file there, called &lt;code&gt;www&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I rotates files weekly, as long as they’re at least 1 megabyte. The rotated logs are created under the &lt;code&gt;www-data&lt;/code&gt; user / group and compressed with bzip2. As the logs are rotated, I’m keeping only the last 12 logs (about 3 months worth). Each rotated file is named with the date it was rotated.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/srv/www/*/log/*.log {
        size 1M
        copytruncate
        create 0660 www-data www-data
        rotate 12
        compress
        dateext
        weekly
        missingok
        compresscmd /bin/bzip2
        compressext .bz2
}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It works well.&lt;/p&gt;
&lt;h3 id="offsite-backups"&gt;Offsite Backups&lt;/h3&gt;
&lt;p&gt;Finally, I setup a script to use rsync to copy both the &lt;code&gt;/srv/www&lt;/code&gt; and database backup folders over to &lt;a href="http://www.strongspace.com/"&gt;Strongspace&lt;/a&gt;, nightly. In the event of disaster, I’ll never lose more than a day’s worth of changes to the sites.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;RSYNC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/bin/rsync&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-rltvz --delete&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;df@df.strongspace.com&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;MYDATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;`date +%d | sed &amp;#39;s/0*//&amp;#39;`&amp;quot;&lt;/span&gt;

&lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/backup/&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;DEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/strongspace/df/home/JoesFiles/mark_copy&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;BACKUPDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/df/Backups/Joe/mark_copy/Backup-&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MYDATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;EXCLUDE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/root/excludes.txt&amp;quot;&lt;/span&gt;
&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RSYNC&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPTIONS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; --exclude-from &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXCLUDE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/www/&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;DEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/strongspace/df/home/JoesFiles/mark_copy&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;BACKUPDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/df/Backups/Joe/mark_copy/Backup-&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MYDATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;EXCLUDE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/root/excludes.txt&amp;quot;&lt;/span&gt;
&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RSYNC&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPTIONS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; --exclude-from &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXCLUDE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;And, with that, I’m done with my migration to Linode. It was fun but now I’m ready to get back to actually enhancing my sites rather than just moving my sites.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-09-05:/blog/2012/linode-final-migration-steps</guid></item><item><title>Migrating Wordpress Multisite</title><link>https://bloomingcacti.com/blog/2012/migrating-wordpress-multisite</link><description>&lt;p&gt;Using my &lt;a href="http://desertflood.com/blog/2012/implementing-a-locked-down-php-site/"&gt;new PHP setup method&lt;/a&gt;, I migrated a bunch of PHP applications over to my Linode. But I saved the hardest challenge for last: migrating my Wordpress multisite install. There are 5 active sites inside of that install and about 3 inactive sites, as well as a decent amount of uploaded media. I wanted to figure out all of the other wrinkles first, before I tackled the most important PHP application. As if all of that wasn’t enough, I decided that I wanted to move the Wordpress installation from its previous domain (desertflood.com) to its current domain (wordflood.net).&lt;/p&gt;
&lt;p&gt;After reading through multiple sites about &lt;a href="http://codex.wordpress.org/Moving_WordPress"&gt;Wordpress migrations&lt;/a&gt;, I decided to take the migration in two steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Move the site using the existing domain and using my /etc/hosts file to access the existing domain via the new IP address.&lt;/li&gt;
&lt;li&gt;Change the domain on the new Wordpress install.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="moving-to-the-new-server"&gt;Moving to the New Server&lt;/h3&gt;
&lt;p&gt;After all of the worry, the actual move turned out to be fairly straightforward. I was already using &lt;a href="http://sourceforge.net/projects/automysqlbackup/"&gt;automysqlbackup&lt;/a&gt; to create nightly backups of my MySQL databases and rsync to create offsite backups of my files. Thanks to that, and shell access on both servers, it was fairly trivial to copy both the Wordpress files and database to the new server.&lt;/p&gt;
&lt;p&gt;I had already created a home directory and user account for the new Wordpress install, using my setup script. After transferring my files from the old server to the new server and copying the database backup from the old server to the new server, I was able to load the backup into my new database, using the &lt;code&gt;mysql&lt;/code&gt; command line tool.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rsync -rav jmartin@litho.joyent.us:domains/desertflood.com/web/public/ /srv/www/wordpress/public_html
scp jmartin@litho.joyent.us:/users/home/jmartin/backups/db/latest/jmartin_mjm_wp3_week.34.2012-08-25_01h03m.sql.bz2 /home/wordpress
bzip2 -d /home/wordpress/jmartin_mjm_wp3_week.34.2012-08-25_01h03m.sql.bz2
cat /home/wordpress/jmartin_mjm_wp3_week.34.2012-08-25_01h03m.sql | mysql -u wordpress -p wordpress
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I was already using nginx on my Joyent shared account. That being the nginx setup easy as well.  I copied the nginx configuration file from the old server, to merge it into the configuration file for the new server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scp jmartin@litho.joyent.us:etc/nginx/sites/desertflood.conf .
ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then, on my laptop, I pointed my domains to the new server, using /etc/hosts.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;66.175.215.121 desertflood.com www.desertflood.com thosemartins.desertflood.com thosemartins.us minorthoughts.desertflood.com minorthoughts.com www.minorthoughts.com www.thosemartins.us
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After I edited my wp-config.php file to point to the new database, I was able to access Wordpress on the new server.&lt;/p&gt;
&lt;h3 id="moving-to-the-new-domain"&gt;Moving to the New Domain&lt;/h3&gt;
&lt;p&gt;I was worried about this too but it turned out to be almost as easy as moving the actual Wordpress installation. There were just three steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Edit the wp-config.php file to change the &lt;code&gt;DOMAIN_CURRENT_SITE&lt;/code&gt; parameter to the new domain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update the MySQL database. The MySQL database had references to the old domain scattered all throughout it. My natural inclination would be to just do a global search and replace on the database backup, before loading it into MySQL. However, Wordpress has a lot of serialized data in the database. You can’t just change values there, without breaking those columns.&lt;/p&gt;
&lt;p&gt;Fortunately, the internet has already solved that problem for me. Interconnect IT &lt;a href="http://interconnectit.com/719/migrating-a-wordpresswpmubuddypress-website/"&gt;mentions it&lt;/a&gt; in their guide to migrating a Wordpress/WPMU/BuddyPress website. They developed a tool for &lt;a href="http://interconnectit.com/124/search-and-replace-for-wordpress-databases/"&gt;safely searching and replacing&lt;/a&gt; in the database, using PHP. I temporarily installed the “Safe Search and Replace” script and used it to change all instances of desertflood.com to wordflood.net in the database.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, in the ‘Domain Mapping’ section of Wordpress’s Network Admin, I changed the Server IP Address to the IP of the new server.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And, just like that, Wordpress was live on the new domain.&lt;/p&gt;
&lt;h3 id="wp-super-cache-multisite-nginx"&gt;WP Super Cache / MultiSite / nginx&lt;/h3&gt;
&lt;p&gt;One final note. On Monday, I decided to activate the &lt;a href="http://wordpress.org/extend/plugins/wp-super-cache/"&gt;WP Super Cache&lt;/a&gt; plugin, to add that extra bit of oomph to the site’s loading speed. I’ve used it before but it’s been a while. When I turned it on, I ran into a &lt;a href="http://wordpress.org/support/topic/plugin-wp-super-cache-warning-wpsupercache-11-and-nginx-broken-on-multisite"&gt;nasty bug&lt;/a&gt; that shows up when using WPSC 1.1 with nginx. The plugin assumes that the environment variable SERVER_NAME is configured, but nginx doesn’t set that environment variable.&lt;/p&gt;
&lt;p&gt;The end result is that Wordpress caches whichever site the administrator last visited. Then nginx serves out the content for that site to visitors for every other Wordpress site. In effect, the administrator’s last visited site gets cloned across all of the Wordpress sites. &lt;/p&gt;
&lt;p&gt;Fortuantely, donncha posted a &lt;a href="http://wordpress.org/support/topic/plugin-wp-super-cache-warning-wpsupercache-11-and-nginx-broken-on-multisite#post-2913202"&gt;development version of WPSC&lt;/a&gt; that fixes the SERVER_NAME problem. Once I installed it, my caching ills went away.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-09-04:/blog/2012/migrating-wordpress-multisite</guid></item><item><title>Implementing a Locked Down PHP Site</title><link>https://bloomingcacti.com/blog/2012/implementing-a-locked-down-php-site</link><description>&lt;p&gt;In my &lt;a href="http://desertflood.com/blog/2012/locking-down-php/"&gt;last post&lt;/a&gt; I talked about a plan for securely setting up PHP. After making the plan, I had two goals: test it to see if it would work and automate the setup.&lt;/p&gt;
&lt;h3 id="testing-the-setup"&gt;Testing the Setup&lt;/h3&gt;
&lt;p&gt;I started with a simple site: a small Dokuwiki installation. It was an easy site to test because it didn’t require a database connection.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run nginx as a separate user account, with read access to the web root.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Done. nginx is already setup to run as the &lt;code&gt;www-data&lt;/code&gt; user and the web files are not group writeable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a new user, &lt;code&gt;dokuwiki&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I set up the user account with a user private group as well as membership to the &lt;code&gt;www-data&lt;/code&gt; group. This will let me log in as the dokuwiki user to edit the dokuwiki site (and only the dokuwiki site). I also created a directory for the dokuwiki site, changed the user ownership to the dokuwiki user and the group ownership to the &lt;code&gt;www-data&lt;/code&gt; group. Finally, I made the whole thing user writeable and group readable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;adduser dokuwiki
usermod -a -G www-data dokuwiki
mkdir /srv/www/dokuwiki
chown -R dokuwiki:www-data /srv/www/dokuwiki
chmod -R 2755 /srv/www/dokuwiki
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set up a separate PHP instance for each application and use the &lt;code&gt;open_basedir&lt;/code&gt; directive to lock PHP down to the specific application’s folder.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I setup my VPS &lt;a href="http://www.dotdeb.org/instructions/"&gt;to use&lt;/a&gt; the Dotdeb repository for Debian and installed the php5-fpm package. I copied the default configuration, to make one specific for Dokuwiki, and edited it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;apt-get install php5 php-pear php5-mysql php5-suhosin php5-cli php5-cgi php5-fpm
sudo cp /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/dokuwiki.conf 
sudo vim /etc/php5/fpm/pool.d/dokuwiki.conf
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I made the following changes to my dokuwiki.conf file. This sets up a PHP “pool” just for Dokuwiki. The pool runs as the dokuwiki user, so that PHP will have read-write access to the dokuwiki folders.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;; pool name
[dokuwiki]

user = dokuwiki
group = www-data
listen = /var/run/php5-fpm/dokuwiki.sock

pm.max_children = 2    ; this is an infrequently used site
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 1
pm.max_requests = 500
request_terminate_timeout = 30s

chdir = /srv/wwww/dokuwiki/public_html/
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /srv/wwww/dokuwiki/log/fpm-php.err.log
php_admin_value[open_basedir] = /srv/wwww/dokuwiki/
php_admin_value[session.save_path] = /srv/www/dokuwiki/tmp
security.limit_extensions = .php .php3 .php4 .php5
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;chdir&lt;/code&gt; directive ensures that PHP starts from the dokuwiki web directory. The &lt;code&gt;php_admin_flag&lt;/code&gt; and &lt;code&gt;php_admin_value&lt;/code&gt; directives allow me to set PHP options directly, without needing to create a separate php.ini file for each application. With this setup, PHP will log errors to an application specific log file, limit applications to reading and writing files in the application’s directory, and limit PHP to executing files with various PHP extensions.&lt;/p&gt;
&lt;p&gt;I can’t be sure until someone actually hacks me, of course, but I think this is pretty locked down. Attackers can’t use PHP to read / write other files on the server. And, because I’m using a specific user account for each application, exploiting security holes to gain shell access only gains you shell access for this particular account and application. I may not be able to prevent an individual application from being hacked, but I can limit the damage to that one application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set up nginx to serve the site&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After I saved the configuration file, I started up php5-fpm, to launch the persistently running PHP processes: &lt;code&gt;/etc/init.d/php5-fpm start&lt;/code&gt;. Next I created an nginx sites file, to enable nginx to serve the application: &lt;code&gt;/etc/nginx/sites-enabled/dokuwiki&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;location ~ \.php$ {
    include /etc/nginx/fastcgi_params;
    fastcgi_pass /var/run/php5-fpm/dokuwiki.sock
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME /srv/www/dokuwiki/public_html$fastcgi_script_name;
}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice that nginx and and PHP-FPM are communicating through Unix sockets, using a unique socket for each PHP application. I tested the configuration and it worked perfectly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="automating-the-setup"&gt;Automating the Setup&lt;/h3&gt;
&lt;p&gt;I created a shell script to automate each of these steps for me. It needs to be run by the root user. The first parameter is the username that you want the script to create and the second is the name of the application that you’re setting up. The only real rule is that the app name can’t have an '@' symbol in it. I saved this script as &lt;code&gt;phpapp.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First it will create the new user account (and user private group) and add the user account to the &lt;code&gt;www-data&lt;/code&gt; group. It will create all of the necessary application folders under /srv/www and set the ownership and permissions appropriately. It will also create a PHP-FPM configuration file (using the default file as a base) and an nginx sites file, setting up the correct Unix socket in each.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c1"&gt;# params&lt;/span&gt;
&lt;span class="c1"&gt;# $1 = user&lt;/span&gt;
&lt;span class="c1"&gt;# $2 = app name&lt;/span&gt;
&lt;span class="c1"&gt;# $3 = domain&lt;/span&gt;

&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;
&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/www/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/php5/fpm/pool.d/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.conf&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;NGINXCONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/nginx/sites-available/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

adduser &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
usermod -a -G www-data &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
mkdir -p &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/public_html&amp;quot;&lt;/span&gt;
mkdir -p &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp&amp;quot;&lt;/span&gt;
mkdir -p &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/log&amp;quot;&lt;/span&gt;
chown -R &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:www-data &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
chmod -R &lt;span class="m"&gt;2755&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

cp /etc/php5/fpm/pool.d/www.old &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s/\[www\]/\[&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\]/&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s@listen = 127.0.0.1:9000@listen = /var/run/php5-fpm/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sock@&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s@user = www-data@user = &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s/;listen.allowed_clients/listen.allowed_clients/&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s@chdir = /@chdir = &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/public_html/@&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s1"&gt;&amp;#39;s/;security.limit_extensions/security.limit_extensions/&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s/;php_admin_flag\[log_errors\] = on/php_admin_flag\[log_errors\] = on/&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s@;php_admin_value\[error_log\] = /var/log/fpm-php.www.log@php_admin_value\[error_log\] = &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/log/fpm-php.err.log@&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;php_admin_value[open_basedir] = &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;php_admin_value[session.save_path] = &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp&amp;quot;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

cp /etc/nginx/sites-available/template &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NGINXCONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s@/srv/www/app/@/srv/www/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/@&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NGINXCONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s@/var/run/php5-fpm/app.sock@/var/run/php5-fpm/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sock@&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NGINXCONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
sed -i &lt;span class="s2"&gt;&amp;quot;s/server_name template.com/server_name &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NGINXCONF&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;After running this script, you just need to restart PHP-FPM and reload nginx and your application will be good to go, safely sandboxed in its own directory and account.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-09-03:/blog/2012/implementing-a-locked-down-php-site</guid></item><item><title>Locking Down PHP</title><link>https://bloomingcacti.com/blog/2012/locking-down-php</link><description>&lt;p&gt;The most important sites that I need to migrate from Joyent to Linode are PHP sites, primarily my Wordpress multisite network. I’ve been dragging my feet on it though, because I want to be paranoid about security and I couldn’t figure out &lt;em&gt;how&lt;/em&gt; to be properly paranoid with PHP.&lt;/p&gt;
&lt;p&gt;After all, the web server needs to have read access to all of the files. The PHP interpreter needs to have read access to all of the files. And, for several sites, the PHP interpreter needs to have write access to both files and folders. But if the PHP interpreter has write access to the files then one poorly written script can give an attacker write access to those files as well. The last thing I want is for a bug in one PHP application to cause destruction across &lt;em&gt;all&lt;/em&gt; of my PHP applications.&lt;/p&gt;
&lt;p&gt;Today, I think I finally figured out how I want my PHP setup to work. I was searching the Linode forums for inspiration and I found &lt;a href="http://forum.linode.com/viewtopic.php?f=10&amp;amp;t;=8466&amp;amp;hilit;=php+security#p48425"&gt;this post&lt;/a&gt; from hybinet. Extrapolating from that post, there are several things that I want to do.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Setup nginx to run under its own user account, as a member of a group that has read access (but not write access) to the entire web hierarchy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a separate user account for each PHP application. Make that application’s files readable and writeable by that user and readable by the nginx group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setup a separate PHP instance for each application. Have PHP run as that user, with read and write access to that user’s files and that user’s files &lt;em&gt;only&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the &lt;code&gt;open_basedir&lt;/code&gt; directive to lock PHP down to the specific application’s folder. Don’t let it exit that file hierachy for any reason.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this setup, if an application gets hacked, the attacker will have access to one specific user account and one specific, limited, set of files. A successful attack against one application won’t automatically result in all applications being attacked.&lt;/p&gt;
&lt;p&gt;The big question I had was how to manageably set up a separate PHP instance for each application and manageably limit PHP to a specific application root. That’s why I was ecstatic when I learned about PHP-FPM (FastCGI Process Manager). It’s a new (as of PHP 5.3.3 and PHP 5.4) FastCGI implementation. Among its features is the ability to start workers with different uid/gid/chroot/environments and the ability to start workers with different php.ini files.&lt;/p&gt;
&lt;p&gt;I can easily configure the PHP-FPM configuration file with entries for each application, limiting the user account and application roots. Then I can configure nginx to direct PHP scripts for each application to that application’s specific pool of PHP-FPM workers. The result will be a centrally managed PHP setup that can have limited security for each application.&lt;/p&gt;
&lt;p&gt;The theory sounds fantastic. Now I just have to work on actually setting it up for a test PHP application.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-08-21:/blog/2012/locking-down-php</guid></item><item><title>Your Password is Weak</title><link>https://bloomingcacti.com/blog/2012/your-password-is-weak</link><description>&lt;p&gt;When I set up a new account for my parents, they're always annoyed by my password choice: long, nonsense strings of letters, numbers, and punctuation characters. There's a reason for that though: if you can think up a password, someone else can crack it with hardly any effort at all.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://arstechnica.com/security/2012/08/passwords-under-assault/"&gt;Ars Technica goes into the gory details&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The average Web user maintains 25 separate accounts but uses just 6.5 passwords to protect them, according to a &lt;a href="https://research.microsoft.com/pubs/74164/www2007.pdf"&gt;landmark study (PDF)&lt;/a&gt; from 2007. ... such password reuse, combined with the frequent use of e-mail addresses as user names, means that once hackers have plucked login credentials from one site, they often have the means to compromise dozens of other accounts, too.&lt;/p&gt;
&lt;p&gt;Newer hardware and modern techniques have also helped to contribute to the rise in password cracking. Now used increasingly for computing, graphics processors allow password-cracking programs to work thousands of times faster than they did just a decade ago on similarly priced PCs that used traditional CPUs alone. A PC running a single AMD Radeon HD7970 GPU, for instance, can try on average an astounding 8.2 billion password combinations each second, depending on the algorithm used to scramble them. Only a decade ago, such speeds were possible only when using pricey supercomputers.&lt;/p&gt;
&lt;p&gt;At any given time, Redman is likely to be running thousands of cryptographically hashed passwords though a PC containing four of Nvidia's GeForce GTX 480 graphics cards. It's an "older machine," he conceded, but it still gives him the ability to cycle through as many as 6.2 billion combinations every second.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What can you do to protect yourself? Use long, randomly generated passwords. And never, ever reuse a password.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Even powerful computation engines have trouble cracking longer passwords using brute force. Assuming such an attack checks for all combinations of all 95 letters, numbers, and symbols available on a standard English-language keyboard, it takes a matter of hours for a desktop computer with an Intel Core i7 980x processor to brute-force crack any five character password. Increasing the password length by just one character requires about a day; bumping the length by one more character, though, dramatically increases the cracking time to more than 10 days. Rob Graham, the Errata Security CEO who calculated the requirements, refers to this limitation as the "exponential wall of brute-force cracking."&lt;/p&gt;
&lt;p&gt;So what can the average person do to pick a passcode that won't be toppled in a matter of hours? Per Thorsheim, a security advisor who specializes in passwords for a large company headquartered in Norway, said the most important attribute of any passcode is that it be unique to each site.&lt;/p&gt;
&lt;p&gt;"For most sites, you have no idea how they store your password," he explained. "If they get breached, you get breached. If your password at that site is unique, you have much less to worry about."&lt;/p&gt;
&lt;p&gt;It's also important that a password not already be a part of the corpus of the hundreds of millions of codes already compiled in crackers' word lists, that it be randomly generated by a computer, and that it have a minimum of nine characters to make brute-force cracks infeasible. Since it's not uncommon for people to have dozens of accounts these days, the easiest way to put this advice into practice is to use program such as &lt;a href="https://agilebits.com/onepassword"&gt;1Password&lt;/a&gt; or &lt;a href="http://passwordsafe.sourceforge.net/"&gt;PasswordSafe&lt;/a&gt;. Both apps allow users to create long, randomly generated passwords and to store them securely in a cryptographically protected file that's unlocked with a single master password.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you need help generating a strong master password, you could try my online &lt;a href="http://diceware.wordflood.net/"&gt;Diceware pass phrase generator&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-08-21:/blog/2012/your-password-is-weak</guid></item><item><title>Moving Lessn</title><link>https://bloomingcacti.com/blog/2012/moving-lessn</link><description>&lt;p&gt;The last site that I’m going to move this weekend is a small: my personal URL shortener, using &lt;a href="http://shauninman.com/archive/2009/08/17/less_n"&gt;Lessn&lt;/a&gt;. It’s a good small test, because it uses PHP and it will let me make sure that I can get PHP working, without having to mess with a large, complex site.&lt;/p&gt;
&lt;p&gt;I’m not going to detail all of the setup. For the most part, I’m taking the basic nginx / PHP setup stragith from Linode’s &lt;a href="http://library.linode.com/web-servers/nginx/php-fastcgi/debian-6-squeeze"&gt;Nginx and PHP-FastCGI on Debian 6&lt;/a&gt; guide.&lt;/p&gt;
&lt;p&gt;Here are the MySQL steps, to create the Lessn database and a secure user for it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mysqladmin create lessn

CREATE USER &amp;#39;lessn&amp;#39;@&amp;#39;localhost&amp;#39; IDENTIFIED BY &amp;#39;xxxxx&amp;#39;;
GRANT ALL ON lessn.* TO &amp;#39;lessn&amp;#39;@&amp;#39;localhost&amp;#39;;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Finally, I could load the database backup, from Litho.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scp jmartin@litho.joyent.us:/users/home/jmartin/backups/db/latest/jmartin_lessn_2012-08-19_01h03m.Sunday.sql.bz2 .
bzip2 -d jmartin_lessn_2012-08-19_01h03m.Sunday.sql.bz2
cat jmartin_lessn_2012-08-19_01h03m.Sunday.sql | mysql -u lessn -p lessn
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I also need to copy the site files over, from Litho, and configure them for their new home.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rsync -ravn jmartin@litho.joyent.us:/users/home/jmartin/domains/jmdf.im/web/public /srv/www/jmdf.im/public_html
chown -R www-data:www-data /srv/www/jmdf.im
vim /srv/www/jmdf.im/public_html/-/config.php
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The final change I had to make was to enable URL rewriting, for lessn, in /etc/nginx/sites-available/jmdf.im.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;location / {
    index index.php index.html index.htm;
    # if the requested file exists, return it immediately
    if (-e $request_filename) {
            break;
    }

    # all other requests go to Wordpress
    if (!-e $request_filename) {
            rewrite ^(.*)$ /index.php?token=$1 last;
    }
}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After I reloaded nginx with the new configuration, everything worked perfectly.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-08-19:/blog/2012/moving-lessn</guid></item><item><title>Migrating "Timetrack" from Joyent to Linode</title><link>https://bloomingcacti.com/blog/2012/migrating-timetrack-from-joyent-to-linode</link><description>&lt;p&gt;I decided to start my migration by migrating my Rails application, timetrack. This migration would have been a lot easier except that this particular application is using 2-year old tools and still needs a lot of tweaking.&lt;/p&gt;
&lt;p&gt;The main dependencies are MySQL and Ruby 1.8.7.&lt;/p&gt;
&lt;h3 id="user-account-and-ruby"&gt;User Account and Ruby&lt;/h3&gt;
&lt;p&gt;I started by creating a new user account, just for the timetrack application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;useradd timetrack
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I installed rvm, then tried to install ruby 1.8.7, from the ‘timetrack’ user account.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rvm install 1.8.7
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The rvm script gave me a list of Debian dependencies to install first.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After that, ruby 1.8.7 installed cleanly. Although I did get a warning that just compiling it on modern Linux required two extra patches and that I should strongly consider installing ruby 1.9, for better security.&lt;/p&gt;
&lt;h3 id="install-mysql"&gt;Install MySQL&lt;/h3&gt;
&lt;p&gt;Installing MySQL was easy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;apt-get install mysql-server libmysqlclient-dev
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I tuned it, using the parameters from &lt;a href="http://library.linode.com/hosting-website#sph_optimizing-mysql-for-a-linode-512"&gt;Linode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I created a new database, specifically for this application, then created a new database specific MySQL user account.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mysqladmin create timetrack

CREATE USER &amp;#39;timetrack&amp;#39;@&amp;#39;localhost&amp;#39; IDENTIFIED BY &amp;#39;xxxx&amp;#39;;
GRANT ALL ON timetrack.* TO &amp;#39;timetrack&amp;#39;@&amp;#39;localhost&amp;#39;;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Finally, I could load the database backup, from Litho.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mysql -u timetrack -p timetrack &amp;lt; jmartin_desertflood_timetrack_2012-08-19_01h03m.Sunday.sql
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="deploy-the-application"&gt;Deploy the Application&lt;/h3&gt;
&lt;p&gt;Capistrano is quite powerful and should have made this the easiest part of the setup. But I never did build a really good deployment recipe, so it was only a partial help. I started by updating the config/deploy.rb file, to point to my new server and user account. Then I had to setup my 'timetrack' user account, to be able to access GitHub. Without it, I was getting errors on the &lt;code&gt;cap deploy&lt;/code&gt; step.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new SSH key&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh-keygen
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the contents of '.ssh/id_rsa.pub' to my Github keys. Then do an initial checkout, to make sure that SSH is okay with Github’s server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git clone git@github.com:jmartindf/timetrack.git
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setup the required directories.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir -p /home/timetrack/apps/timetrack/releases
mkdir shared/pids
mkdir shared/log
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy the app&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cap deploy
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update local configurations&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;vim config/database.yml
vim config/unicorn.conf.rb
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install required gem dependencies&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bundle install
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start the app, to see if it works.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;unicorn -E production -D -o 127.0.0.1 -c /home/timetrack/apps/timetrack/current/config/unicorn.conf.rb
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And, it works. Amazing.&lt;/p&gt;
&lt;h3 id="start-at-boot"&gt;Start at boot&lt;/h3&gt;
&lt;p&gt;It’s all well and good to start it manually, but I really need it to start at boot. After some Googling, I found an &lt;a href="https://gist.github.com/1416971"&gt;init.d script for unicorn&lt;/a&gt;, that I could use as a basis for my own init.d script.&lt;/p&gt;
&lt;p&gt;After tweaking it a bit, this is what it looks like. The key points are that it uses rvm and bundler, to get the dependencies right. (As mentioned before, I’m using some old gems and an old version of ruby.) It also uses the chdir and chuid flags, to &lt;code&gt;start-stop-daemon&lt;/code&gt;, to get everything running with the right permissions in the right folder. I’ve tested it and it does all work.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#! /bin/sh&lt;/span&gt;

&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/sbin:/usr/sbin:/bin:/usr/bin
&lt;span class="nv"&gt;DESC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Timetrack server&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;timetrack
&lt;span class="nv"&gt;RVM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/timetrack/.rvm/bin/rvm
&lt;span class="nv"&gt;RVM_EXEC_ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.8.7 exec&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/timetrack/apps/timetrack/current
&lt;span class="nv"&gt;DAEMON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$RVM&lt;/span&gt;
&lt;span class="nv"&gt;DAEMON_ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$RVM_EXEC_ARGS&lt;/span&gt;&lt;span class="s2"&gt; bundle exec unicorn -c &lt;/span&gt;&lt;span class="nv"&gt;$RAILS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/config/unicorn.conf.rb -E production -D&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;PIDFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$RAILS_DIR&lt;/span&gt;/tmp/pids/timetrack.pid
&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;timetrack
&lt;span class="nv"&gt;SCRIPTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/init.d/&lt;span class="nv"&gt;$NAME&lt;/span&gt;

&lt;span class="c1"&gt;# Exit if the package is not installed&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; -x &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$DAEMON&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# Read configuration variable file if it is present&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; -r /etc/default/&lt;span class="nv"&gt;$NAME&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; . /etc/default/&lt;span class="nv"&gt;$NAME&lt;/span&gt;

&lt;span class="c1"&gt;# Load the VERBOSE setting and other rcS variables&lt;/span&gt;
. /lib/init/vars.sh

&lt;span class="c1"&gt;# Define LSB log_* functions.&lt;/span&gt;
&lt;span class="c1"&gt;# Depend on lsb-base (&amp;gt;= 3.0-6) to ensure that this file is present.&lt;/span&gt;
. /lib/lsb/init-functions

&lt;span class="c1"&gt;# unicorn needs some environment vars&lt;/span&gt;
. /etc/environment

&lt;span class="c1"&gt;# Create /var/run dir if missing&lt;/span&gt;
&lt;span class="c1"&gt;# if [ ! -d /var/run/unicorn ]; then&lt;/span&gt;
&lt;span class="c1"&gt;#   mkdir -m 0755 /var/run/unicorn&lt;/span&gt;
&lt;span class="c1"&gt;#   chown $USER /var/run/unicorn&lt;/span&gt;
&lt;span class="c1"&gt;# fi&lt;/span&gt;

symlink_resque&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    ln -sfT &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$RVM&lt;/span&gt; &lt;span class="nv"&gt;$RVM_EXEC_ARGS&lt;/span&gt; which resque &lt;span class="p"&gt;|&lt;/span&gt; tail -1&lt;span class="sb"&gt;`&lt;/span&gt;/server/public &lt;span class="nv"&gt;$RAILS_DIR&lt;/span&gt;/public/resque
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Function that starts the daemon/service&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
do_start&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Return&lt;/span&gt;
    &lt;span class="c1"&gt;#   0 if daemon has been started&lt;/span&gt;
    &lt;span class="c1"&gt;#   1 if daemon was already running&lt;/span&gt;
    &lt;span class="c1"&gt;#   2 if daemon could not be started&lt;/span&gt;
    symlink_resque
    start-stop-daemon --start --quiet --pidfile &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt; --chuid &lt;span class="nv"&gt;$USER&lt;/span&gt; --exec &lt;span class="nv"&gt;$DAEMON&lt;/span&gt; --test &lt;span class="nv"&gt;$RAILS_DIR&lt;/span&gt; --chdir &lt;span class="nv"&gt;$RAILS_DIR&lt;/span&gt; &amp;gt; /dev/null &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    start-stop-daemon --start --quiet --pidfile &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt; --chuid &lt;span class="nv"&gt;$USER&lt;/span&gt; --exec &lt;span class="nv"&gt;$DAEMON&lt;/span&gt; --chdir &lt;span class="nv"&gt;$RAILS_DIR&lt;/span&gt; -- &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nv"&gt;$DAEMON_ARGS&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="c1"&gt;# Add code here, if necessary, that waits for the process to be ready&lt;/span&gt;
    &lt;span class="c1"&gt;# to handle requests from services started subsequently which depend&lt;/span&gt;
    &lt;span class="c1"&gt;# on this one.  As a last resort, sleep for some time.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Function that stops the daemon/service&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
do_stop&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Return&lt;/span&gt;
    &lt;span class="c1"&gt;#   0 if daemon has been stopped&lt;/span&gt;
    &lt;span class="c1"&gt;#   1 if daemon was already stopped&lt;/span&gt;
    &lt;span class="c1"&gt;#   2 if daemon could not be stopped&lt;/span&gt;
    &lt;span class="c1"&gt;#   other if a failure occurred&lt;/span&gt;
    start-stop-daemon --stop --quiet --retry&lt;span class="o"&gt;=&lt;/span&gt;TERM/30/KILL/5 --pidfile &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt;
    &lt;span class="nv"&gt;RETVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$RETVAL&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="c1"&gt;# Ensure the pidfile is cleared&lt;/span&gt;
    rm -f &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$RETVAL&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Function that sends a SIGHUP to the daemon/service&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
do_reload&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    symlink_resque
    start-stop-daemon --stop --signal &lt;span class="m"&gt;1&lt;/span&gt; --quiet --pidfile &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Function that sends a SIGUSR2 to the daemon/service&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
do_graceful_reload&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    symlink_resque
    start-stop-daemon --stop --signal USR2 --quiet --pidfile &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; in
  start&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VERBOSE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; no &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_daemon_msg &lt;span class="s2"&gt;&amp;quot;Starting &lt;/span&gt;&lt;span class="nv"&gt;$DESC&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    do_start
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; in
        &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VERBOSE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; no &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_end_msg &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VERBOSE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; no &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_end_msg &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  stop&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VERBOSE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; no &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_daemon_msg &lt;span class="s2"&gt;&amp;quot;Stopping &lt;/span&gt;&lt;span class="nv"&gt;$DESC&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    do_stop
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; in
        &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VERBOSE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; no &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_end_msg &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VERBOSE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; no &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_end_msg &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  status&lt;span class="o"&gt;)&lt;/span&gt;
       status_of_proc &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$DAEMON&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
       &lt;span class="p"&gt;;;&lt;/span&gt;
  reload&lt;span class="o"&gt;)&lt;/span&gt;
    log_daemon_msg &lt;span class="s2"&gt;&amp;quot;Reloading &lt;/span&gt;&lt;span class="nv"&gt;$DESC&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    do_reload
    log_end_msg &lt;span class="nv"&gt;$?&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  graceful-reload&lt;span class="o"&gt;)&lt;/span&gt;
    log_daemon_msg &lt;span class="s2"&gt;&amp;quot;Gracefully reloading &lt;/span&gt;&lt;span class="nv"&gt;$DESC&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    do_graceful_reload
    log_end_msg &lt;span class="nv"&gt;$?&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  restart&lt;span class="o"&gt;)&lt;/span&gt;
    log_daemon_msg &lt;span class="s2"&gt;&amp;quot;Restarting &lt;/span&gt;&lt;span class="nv"&gt;$DESC&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    do_stop
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; in
      &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        do_start
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; in
            &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; log_end_msg &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
            &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; log_end_msg &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="c1"&gt;# Old process is still running&lt;/span&gt;
            *&lt;span class="o"&gt;)&lt;/span&gt; log_end_msg &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="c1"&gt;# Failed to start&lt;/span&gt;
        &lt;span class="k"&gt;esac&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
      *&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Failed to stop&lt;/span&gt;
        log_end_msg &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  *&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Usage: &lt;/span&gt;&lt;span class="nv"&gt;$SCRIPTNAME&lt;/span&gt;&lt;span class="s2"&gt; {start|stop|status|restart|graceful-reload|reload}&amp;quot;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

:
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;I activated the &lt;code&gt;timetrack&lt;/code&gt; init.d script, for the default run levels.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;update-rc.d timetrack defaults
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="nginx"&gt;Nginx&lt;/h3&gt;
&lt;p&gt;My app is running, but it’s not on a port that the outside world can access. That’s where nginx comes in. I like nginx as a lightweight web server and in this case, I can easily use it to proxy traffic back and forth between my app. I just needed to create a very simple configuration file, for nginix.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;vim /etc/nginx/sites-available/timetrack.desertflood.com
cd /etc/nginx/sites-enabled
ln -s /etc/nginx/sites-available/timetrack.desertflood.com
/etc/init.d/nginx start
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here’s what the file looks like.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;upstream&lt;/span&gt; &lt;span class="nt"&gt;timetrack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;server&lt;/span&gt; &lt;span class="err"&gt;127.0.0.1:10100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;listen&lt;/span&gt;       &lt;span class="err"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;server_name&lt;/span&gt;  &lt;span class="err"&gt;timetrack2.desertflood.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;location&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;proxy_set_header&lt;/span&gt;  &lt;span class="err"&gt;X-Real-IP&lt;/span&gt;        &lt;span class="err"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;proxy_set_header&lt;/span&gt;  &lt;span class="err"&gt;X-Forwarded-For&lt;/span&gt;  &lt;span class="err"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;proxy_set_header&lt;/span&gt;  &lt;span class="err"&gt;Host&lt;/span&gt;             &lt;span class="err"&gt;$http_host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;proxy_redirect&lt;/span&gt;    &lt;span class="err"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;proxy_pass&lt;/span&gt;        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;timetrack&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And, just like that, I’ve migrated my first site from Joyent to Linode.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-08-19:/blog/2012/migrating-timetrack-from-joyent-to-linode</guid></item><item><title>Moving to Linode</title><link>https://bloomingcacti.com/blog/2012/moving-to-linode</link><description>&lt;p&gt;After Joyent’s decision to terminate my lifetime hosting account I had two options: I could stay at Joyent, for 5 years, using a SmartMachine (their Virtual Private Server). Or I could take a refund of my original purchases ($600) and go elsewhere. I decided to go elsewhere.&lt;/p&gt;
&lt;p&gt;Right now, I don’t want to be at Joyent. The way that Jason decided to terminate the lifetime hosting, the short time frame he gave us to migrate, and the original promotional offer (just 1 year on a SmartMachine) just rubbed me the wrong way. I had a lot of trust and affection banked for Joyent. But, at least right now, the trust is gone and the affection is greatly diminished.&lt;/p&gt;
&lt;p&gt;As I thought about my options, I thought back to all of the negative aspects of hosting with Joyent. I thought back over the number of products that were promised and then failed to materialize. TextDrive was going to have a truly awesome control panel, but it never came. Then there was going to be a next generation statistics system, but that too never came. The Joyent Connector was going to be the future of group webmail, until it rotted on the vine. Shared Accelerators were the future until suddenly they were the past and they stopped selling shared hosting entirely. Accelerators became SmartMachines, and so on.&lt;/p&gt;
&lt;p&gt;Joyent’s documentation could be a challenge too. Sometimes things were well documented on Wikis. Sometimes they weren’t. You took your chances. The community was great for figuring things out but, lately, the community just hasn’t been there around Joyent’s SmartMachines. The target customer either knows what they’re doing or can afford to pay for support. Those learning on their own are a bit left out. Then too, Joyent’s communication has always been ad-hoc. We joyeurs found out about many changes, on the forum. There was always a risk that you might miss changes entirely if you stopped participating in the forum.&lt;/p&gt;
&lt;p&gt;All in all, I felt that it was time for me to move on. As I thought about my options, I grew increasingly attracted to &lt;a href="http://www.linode.com"&gt;Linode&lt;/a&gt;. I’ve been on a shared, managed hosting server for the past 7 years. But my real dream has always been to have my own server, out on the internet. I’ve always wanted to have a box that I can fully control and configure, to my own design. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.linode.com"&gt;Linode&lt;/a&gt; gives me that. For just $20 a month, I can have a Virtual Private Server of my very own. I can reboot it, wipe it, re-image it with different operating systems, move it between data centers, clone it, and more. I can install any server software I want and leave out anything that I don’t want. And Linode has &lt;a href="http://library.linode.com"&gt;great documentation&lt;/a&gt; too. Some if it is more basic than I need and some of it is more advanced than I need. That makes for a great foundation for learning and tinkering.&lt;/p&gt;
&lt;p&gt;I’m taking a refund of my $600 from Joyent and I’ll be spending it at Linode, over the next couple of years. I can’t wait to get started.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-08-19:/blog/2012/moving-to-linode</guid></item><item><title>The End of An Era</title><link>https://bloomingcacti.com/blog/2012/the-end-of-an-era</link><description>&lt;p&gt;For the past 7 years, I've hosted all of my websites at &lt;a href="http://www.joyent.com/"&gt;Joyent&lt;/a&gt;. Over the next 7 weeks, that will change. It wasn't my choice, but this is my story.&lt;/p&gt;
&lt;h3 id="the-marriage"&gt;The Marriage&lt;/h3&gt;
&lt;p&gt;In 2005 I was a college senior. At that point, I'd already had the wordflood.net domain name for at least 5 years and I'd been using various web hosts that entire time. As I'd learned more about the internet and hosting, I'd switched providers frequently, to take advantage of what I was learning.&lt;/p&gt;
&lt;p&gt;In March 2005, I found TextDrive. TextDrive was founded by Dean Allen, first and foremost a writer, as a web host for people who cared about writing, who cared about good design, and who didn't want to deal with corporate headaches. The first 200 users provided the funding for the initial servers and the entire setup was community driven and funded. I quickly switched my hosting to TextDrive and felt at home.&lt;/p&gt;
&lt;p&gt;TextDrive had a great community of users. The founders (Dean Allen and Jason Hoffman) were the first two users and first two financial backers. They were very active in the forums and in almost every way were just two more members of the community. The community learned from each other, laughed together, and encouraged each other. Like almost every community, we had a host of inside jokes and our own cultural references. I loved it.&lt;/p&gt;
&lt;p&gt;TextDrive offered a "VCIII" lifetime hosting plan, in September 2005. I would have to pay $399 for it, but it would guarantee me an account on their servers for as long as TextDrive existed. Given the existing community and the active participation by the founders, I wanted in.&lt;/p&gt;
&lt;p&gt;I had graduated from college in May 2005 and gotten married in July 2005. For me, $399 was a lot of money. My wife wasn't really enthusiastic about the purchase. I convinced her by pointing out that it was a lifetime deal. It would get the current hosting amount off of our monthly budget and it would give me a "free" place to tinker, experiment, and learn for a long, long time.&lt;/p&gt;
&lt;p&gt;In November 2005, a company called Joyent purchased TextDrive. The TextDrive customers were extremely nervous about this acquistion. We had a great company, a great community, and a great host. Would Joyent ruin it? Would they kick us out? Were our lifetime hosting accounts over now that TextDrive no longer officially existed?&lt;/p&gt;
&lt;p&gt;David Young, Joyent's CEO, acted quickly to demonstrate good faith. He not only promised to honor the existing lifetime accounts, he doubled down by offering the &lt;a href="http://web.archive.org/web/20060203030930/http://www.textdrive.com/mixedgrill"&gt;Mixed Grill&lt;/a&gt; lifetime package, for $499. It included TextDrive hosting, Strongspace for file storage, and Joyent Connector for small group email. And, "how long is it good for?", we asked. "As long as we exist", David told us.&lt;/p&gt;
&lt;p&gt;My wife and I had recently been talking about the need for online file storage, for backups. Strongspace would be perfect for that. And Joyent's Connector product looked like a good idea for email too. When Dean Allen decided that existing VC customers could &lt;a href="http://discuss.joyent.com/viewtopic.php?id=8345"&gt;upgrade&lt;/a&gt; to the Mixed Grill lifetime package for just $199, we were in. We scraped up the money from our budget and sent it in.&lt;/p&gt;
&lt;p&gt;Since then, I've had nearly seven years to enjoy my hosting package and the Joyent community. Over time, most of the discussion migrated from the TextDrive forums to Twitter. But we all (mostly) stayed in contact, linked by our appreciation for Joyent and the early community we'd shared.&lt;/p&gt;
&lt;p&gt;I learned a lot too. TextDrive gave us command prompt access to the servers. We had the freedom to install, configure, and manage our own software. I learned Ruby on Rails and hosted my own app on my account. I compiled my own copy of nginx and used it for my own PHP web apps. I installed multiple versions of Wordpress and learned how to create and manage a Wordpress multi-site server. I ran my own websites and those of my family on the server. It was a great time to tinker and learn.&lt;/p&gt;
&lt;p&gt;Along the way, Strongspace was sold to ExpanDrive, but Joyent ensured that our lifetime accounts would stay intact and endure. The Joyent Connector was discontinued and the hosting servers were migrated from FreeBSD servers to shiny, new Solaris servers. Joyent grew, expanded, and moved into enterprise hosting. Joyent discontinued Shared Hosting for new customers but continued to honor their agreements with their lifetime customers.&lt;/p&gt;
&lt;h3 id="the-break-up"&gt;The Break Up&lt;/h3&gt;
&lt;p&gt;Thursday morning, Joyent gave their loyal, lifetime customers a nasty surprise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dear Joseph,&lt;/p&gt;
&lt;p&gt;We've been analyzing customer usage of Joyent’s systems and noticed that you are one of the few customers that are still on our early products and have not migrated to our new platform, the Joyent Cloud. &lt;/p&gt;
&lt;p&gt;For many business reasons, including infrastructure performance, service quality and manageability, these early products are nearing their End of Life. We plan to sunset these services on October 31, 2012 and we'd like to walk you through a few options.&lt;/p&gt;
&lt;p&gt;We understand this might be an inconvenience for you, but we have a plan and options to make this transition as easy as possible.  We’ve been developing more functionality on our new cloud infrastructure, the Joyent Cloud, for our customers who care about performance, resiliency and security.  Now’s the time to take advantage of all the new capabilities you don’t have today. Everyone that’s moved to our new cloud infrastructure has been pleased with the results.  &lt;/p&gt;
&lt;p&gt;We appreciate and value you as one of Joyent's lifetime Shared Hosting customers. As this service is one of our earliest offerings, and has now run its course, your lifetime service will end on October 31, 2012. However, we believe that you will enjoy the new functionalities of the Joyent Cloud. To show you our appreciation, as one of Joyent's lifetime Shared Hosting customers, we'd like to offer you a free 512MB SmartMachine on the Joyent Cloud for one year. Use this promotional code to redeem the offer.&lt;/p&gt;
&lt;p&gt;Promotional Code: xxxxxxxxxxxx  &lt;/p&gt;
&lt;p&gt;Please review the Terms and Conditions for the Joyent Cloud One Year Free 512 MB Machine Promotion by visiting this &lt;a href="http://www.joyent.com/migration/migration-faq.php#terms"&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To find out more about the Joyent Cloud and your options, please follow this &lt;a href="http://www.joyent.com/migration"&gt;link&lt;/a&gt; to our migration center for additional details.&lt;/p&gt;
&lt;p&gt;Sincerely,&lt;/p&gt;
&lt;p&gt;Jason Hoffman&lt;br&gt;
Founder and CTO&lt;br&gt;
Joyent&lt;br&gt;
jason@joyent.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was surprising to get this message at all. It was shocking to get it from Jason. He was one of us, he'd been there since the beginning, and he'd helped so many of us. More than a few customers had been promoted to TextDrive or Joyent employees. More than a few people had visited San Francisco and had beers or tacos with Jason. It was a personal relationship that we trusted. And, suddenly, it was over. Our lifetime accounts were no longer good for the lifetime of Joyent.&lt;/p&gt;
&lt;p&gt;We would have been willing to migrate from our existing servers to new servers, using Joyent's latest infrastructure and products. We realize that servers get old and architectures change. We'd already migrated once before and we were willing to do it as many times as needed, over the years. But that offer wasn't extended to us. Instead, we were invited to "upgrade" to a paid, monthly account using Joyent's latest products. That offer seemed insulting, to say the least.&lt;/p&gt;
&lt;p&gt;It felt like a kick in the gut. It felt like a sudden breach with an old friend. We were thrown out, with a token promotional code and a mere 10 weeks to migrate all of our sites to a new server. The news spread quickly, the &lt;a href="http://discuss.joyent.com/viewtopic.php?id=33682"&gt;outrage&lt;/a&gt; spread faster, and by the end of Thursday, Jason was negotiating with his customers, over the best resolution for everyone involved.&lt;/p&gt;
&lt;p&gt;Jason has now offered everyone their choice of either 5 years free hosting on a Joyent SmartMachine or a full refund of their original purchase. I haven't decided which option I'll take, but I'll need to decide soon and then start migrating my own sites.&lt;/p&gt;
&lt;p&gt;It's the end of an era for me and I'm saddened by that.&lt;/p&gt;
&lt;h3 id="other-reactions"&gt;Other Reactions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://plus.google.com/u/0/112783391065033208484/posts/16WyAvu37wE"&gt;Ken McKinney - Google+ - Joyent ending "Lifetime" hosting accounts. Some years ago…&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://tracks.ranea.org/post/29561736816/textdrive-r-i-p"&gt;Coyote Tracks - TextDrive, R.I.P.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.rawlinson.us/2012/08/lifetime-doesnt-really-mean-lifetime.html"&gt;In the Trenches: Lifetime doesn't really mean Lifetime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=4391669"&gt;Joyent ending "lifetime" hosting accounts | Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2012-08-18:/blog/2012/the-end-of-an-era</guid></item><item><title>I'm Looking For a New Email Host</title><link>https://bloomingcacti.com/blog/2011/im-looking-for-a-new-email-host</link><description>&lt;p&gt;I’ve been increasingly thinking of ditching my GMail account. I have a couple of reasons for wanting to do that but they mainly boil down to one thing: I don’t like how comfortable Google is with delivering products that don’t pay attention to details.&lt;/p&gt;
&lt;p&gt;For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can’t &lt;a href="http://www.google.com/support/forum/p/Google+Apps/thread?tid=5da2a2e6553f59a9&amp;amp;hl;=en"&gt;rename a domain&lt;/a&gt; in Google Apps. You have to delete the domain and start over with the new name.&lt;/li&gt;
&lt;li&gt;You can’t &lt;a href="https://www.google.com/support/forum/p/Google+Apps/thread?tid=0534ebc47dad128c&amp;amp;hl;=en"&gt;rename an admin user&lt;/a&gt; in Google Apps. You have to create a new user account and start over. (If the user was the initial user in the domain, it won’t even work to demote the admin user and then rename him.)&lt;/li&gt;
&lt;li&gt;GMail likes to add a “Sender” header anytime you send a message from an alternate email address. You &lt;a href="http://gmailblog.blogspot.com/2009/07/send-mail-from-another-address-without.html"&gt;can prevent this&lt;/a&gt; (&lt;a href="http://solidstateraam.com/removing-the-gmail-on-behalf-of-sender-header/"&gt;setup instructions&lt;/a&gt;) but it’s a bit clunky and requires you to have an external SMTP server.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://tidbits.com/article/10253"&gt;GMail IMAP is wonky&lt;/a&gt;, making me reluctant to use my GMail account with a desktop email client.&lt;/li&gt;
&lt;li&gt;You can’t use a &lt;a href="http://www.google.com/support/forum/p/Google%20Apps/thread?tid=08f56168a00dc731&amp;amp;hl;=en"&gt;Google Apps account with Google Plus&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;On a more general note, Google Plus is &lt;a href="http://www.zdnet.com/blog/violetblue/google-plus-too-much-unnecessary-drama/652"&gt;overly dramatic&lt;/a&gt; in its requirement that people to use real names and not pseudonyms. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these things add up to give me a general feeling of using a half-baked product from a company that doesn’t sweat the details. And I don’t like it.&lt;/p&gt;
&lt;p&gt;There are a lot of things that I do like about GMail.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I can host multiple personal domains.&lt;/li&gt;
&lt;li&gt;I can have e-mail addresses setup at my domain (for family members) that either deliver mail to a GMail account or else deliver mail to an outside account (so that I can provide a “forwarding address”).&lt;/li&gt;
&lt;li&gt;I get generous email quotas.&lt;/li&gt;
&lt;li&gt;The service is free.&lt;/li&gt;
&lt;li&gt;I get mobile push email.&lt;/li&gt;
&lt;li&gt;Google provides a very good, nearly instantaneous, email search.&lt;/li&gt;
&lt;li&gt;There is a great conversation view, in the web interface.&lt;/li&gt;
&lt;li&gt;I really like the entire Inbox / everything else paradigm that Gmail created and I like being able to one-click Archive my messages.&lt;/li&gt;
&lt;li&gt;I can create unlimited message filters to automatically file messages and mark them as read or starred, even when my computer isn’t running.&lt;/li&gt;
&lt;li&gt;Robust spam filtering.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But, given that I no longer trust Google to provide a high quality product, I’m looking to move to a new email provider. I’m willing to pay for my service. Here’s the baseline feature set I’m looking for.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Let me host email for multiple domains.&lt;/li&gt;
&lt;li&gt;Let me create multiple aliases either for a mailbox at the host or to forward to an outside mailbox.&lt;/li&gt;
&lt;li&gt;Robust spam filtering.&lt;/li&gt;
&lt;li&gt;Generous email quotas.&lt;/li&gt;
&lt;li&gt;Server side message filters, to sort and file my email.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s the features that I’d like to see.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A good, web-based, email search.&lt;/li&gt;
&lt;li&gt;A web-based conversation view.&lt;/li&gt;
&lt;li&gt;Push email, for mobile clients.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'll be looking around to see who can sell me that, at a price that's reasonable for just 2-3 mailboxes.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2011-11-03:/blog/2011/im-looking-for-a-new-email-host</guid></item><item><title>My Kindle Wishlist</title><link>https://bloomingcacti.com/blog/2011/my-kindle-wishlist</link><description>&lt;p&gt;Amazon is all set to announce an update to their Kindle product list, tomorrow morning. The rumor mill is pretty certain that this will involve the debut of some kind of a tablet device and not an update to their existing eInk Kindles.&lt;/p&gt;
&lt;p&gt;I’m really only interested in the eInk Kindles. I like the book-like readability of the screens and I’m really not interested in reading from an LCD screen for a long period of time. I’ve owned every Kindle so far (each iteration of the 9” model) and I’m pretty pleased with how the hardware has developed. In general, in day to day use, I don’t have any complaints about my Kindle. But I do have a small wish list, for the upcoming 4th generation eInk Kindle.&lt;/p&gt;
&lt;p&gt;I’ll probably upgrade even if Amazon doesn’t implement these features but I’d sure love if it they did implement some (or all) of them. &lt;/p&gt;
&lt;h2 id="easy-hardware-transitions"&gt;Easy Hardware Transitions&lt;/h2&gt;
&lt;p&gt;Apple has a very easy process for moving from an old iPod touch to a new iPod touch. Plug the new iPod touch into your computer, go into iTunes and choose the “Restore from Backup” option. That will move over all of your applications, settings, music, books, movies, TV shows, etc. In a few minutes (or a few hours, if you have a lot of media loaded) your new iPod touch will be setup identically to your old iPod touch.&lt;/p&gt;
&lt;p&gt;Amazon has nothing remotely comparable to this, to make it easy to upgrade from an old Kindle to a new Kindle. There are two things going on here.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DRM books&lt;/strong&gt;. If you’ve purchased books from Amazon, the Digital Rights Management locks those books to your specific piece of hardware. You can copy the digital files from the old Kindle to the new Kindle, but you won’t be able to open or read the books once they’re on the new Kindle. &lt;/p&gt;
&lt;p&gt;The only way to get the books onto the new Kindle is to download them, one at a time, from Amazon. For digital pack rats, this is a colossal pain. The only saving grace is that the Kindle will remember which Collection the book was in and what your bookmark was, so you won't have to reorganize it once it is downloaded.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-DRM books (personal files)&lt;/strong&gt;. These books aren't locked to your specific piece of hardware, so you can copy them from one Kindle to another. However, the Kindle doesn’t remember anything about them, so you lose both your current bookmarks and your organization. After copying the books over, you can read them but you have to spend an hour or so putting everything back into the proper Collections.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="unique-screensavers"&gt;Unique Screensavers&lt;/h2&gt;
&lt;p&gt;My wife and I each have a Kindle. They look physically identical and, in the beginning, it was easy to pick up the wrong Kindle when I was ready to read. Then I discovered the &lt;a href="http://wiki.mobileread.com/wiki/Kindle_Screen_Saver_Hack_for_all_2.x_and_3.x_Kindles"&gt;Kindle screensaver hack&lt;/a&gt; and we each loaded a single, custom, screensaver that differentiates our Kindles. Mine reflects my book personality.&lt;/p&gt;
&lt;p&gt;This is something that shouldn’t require a hack to do. I’d like to see Amazon create an easy tool, even a web based one, to pick a photo and convert it into personalized Kindle screensaver.&lt;/p&gt;
&lt;p&gt;I’ve heard that the Kobo eReader will default to showing the cover of whichever book you’re currently reading. I think this is a great idea too and it would really give each Kindle its own personality.&lt;/p&gt;
&lt;h2 id="better-magazine-management"&gt;Better Magazine Management&lt;/h2&gt;
&lt;p&gt;I like reading magazines on my Kindle. I have a lot of back issues of &lt;a href="http://baens-universe.com/"&gt;Jim Baen’s Universe&lt;/a&gt; &lt;a href="http://clarkesworldmagazine.com/"&gt;Clarkesworld&lt;/a&gt;, &lt;a href="http://www.intergalacticmedicineshow.com/"&gt;Orson Scott Card’s Intergalactic Medicine Show&lt;/a&gt;, and &lt;a href="http://www.instapaper.com/"&gt;Instapaper&lt;/a&gt;. Unfortunately, magazines live in their own little world and can’t be added to any of your existing Collections. That’s a real pity, since I have a “Currently Reading” Collection that I use to organize everything that I’m, well, currently reading and it’s impossible to add my current (or past) magazine issues to that Collection.&lt;/p&gt;
&lt;p&gt;Here’s an idea I’d love to see implemented: associate a magazine subscription to a Collection. Any new issues for that magazine should automatically be added to that Collection, ready for reading. But I should also be free to move back issues to a different Collection.&lt;/p&gt;
&lt;h2 id="fast-book-switching"&gt;Fast Book Switching&lt;/h2&gt;
&lt;p&gt;This is another area where Amazon should take a cue from Apple. iOS allows you to double-tap the home button to get to a list of recently used applications. That allows you to quickly switch from one application to another, without having to go all the way out to the home screen.&lt;/p&gt;
&lt;p&gt;I’m generally reading 4-6 books at any one time and they’re not always next to each other on the home screen. Right now, to switch between books, I have to press the “Home” button, look for the book on a specific page of the home screen or navigate to a specific collection, and then open the book.&lt;/p&gt;
&lt;p&gt;It’d be nice if the Kindle supported some form of fast switching (a pop-up menu? a list at the bottom of the window) that would allow me to quickly toggle between various recently read books or magazines. The current system isn’t bad, exactly, but I think it could be improved upon.&lt;/p&gt;
&lt;h2 id="easier-collection-management"&gt;Easier Collection Management&lt;/h2&gt;
&lt;p&gt;This is yet another area where Amazon could take a page from Apple’s playbook. I really like using iTunes to manage my iPod touch. (iTunes itself could use some work but that’s a topic for another post.) iTunes makes it easy to create playlists, organize media, load and unload applications, etc.&lt;/p&gt;
&lt;p&gt;The Kindle has nothing comparable. The only way to manage your books, magazines, and documents is to do the management on the Kindle itself. It works but it’s exceedingly clunky and slow. It’s hard to manage multiple items at once and the screen refresh delays makes the experience an exercise in patience.&lt;/p&gt;
&lt;p&gt;Amazon should provide some way to organize your content using a desktop or web application that can push your changes out to the Kindle itself.&lt;/p&gt;
&lt;p&gt;Another option, maybe a better one, is to provide something for existing applications (perhaps &lt;a href="http://calibre-ebook.com/"&gt;Calibre&lt;/a&gt;) to hook into. Calibre is already a suberb application for organizing eBooks and its limited only by the fact that it can’t manage Kindle Collections. If Amazon provided a way for it to manage Collections, Calibre could easily become the iTunes of the Kindle ecosystem.&lt;/p&gt;
&lt;h2 id="more-customized-books"&gt;More Customized Books&lt;/h2&gt;
&lt;p&gt;Kindle books are too uniform. Amazon should provide a way for publishers to customize font faces, margins, line spacing, etc so that publishers can provide books with a unique look and feel. One of the joys of physical books is the &lt;em&gt;look&lt;/em&gt; that each book can have. With the current Kindles, that joy is sadly missing.&lt;/p&gt;
&lt;p&gt;Additionally, the Kindle needs to support more formatting features. For example, the Kindle doesn’t provide any way for a book to support block quoting. In many non-fiction books, it’s very difficult to separate out the block-quoted content from the author’s commentary. I think that needs to change in order for eBooks to reach their full potential.&lt;/p&gt;
&lt;h2 id="epub-support"&gt;ePub Support&lt;/h2&gt;
&lt;p&gt;Finally, I’d really like to see Amazon support the ePub file format. It’s the de-facto standard for every other eReader on the market. Right now, I have to keep all of my books in both ePub and MOBI/Kindle formats, to maintain compatibility with all readers. I don’t necessarily expect Amazon to distribute their own books as ePub files but it’d be nice to be able to load my other non-Amazon books as ePub files, without needing to convert them first.&lt;/p&gt;
&lt;h2 id="whispersync-plus"&gt;WhisperSync Plus&lt;/h2&gt;
&lt;p&gt;I buy a lot of my eBooks directly from the publishers: &lt;a href="http://webscription.net/"&gt;Baen Books&lt;/a&gt; and &lt;a href="http://www.pyrsf.com/"&gt;Pyr&lt;/a&gt;, among others. I can load those books onto my Kindle and my iPod touch (running the Kindle app) but I can’t synchronize my last-read position between the devices. That only works for books that were purchased through the Amazon Kindle store.&lt;/p&gt;
&lt;p&gt;I realize that WhisperSync is a big advantage for Amazon’s store and that they’re not likely to give it up just for my convenience. But what if Amazon created a "WhisperSync Plus" service, available as an annual subscription ($25 a year? $50 a year? part of an Amazon Prime account?) that would allow members to use WhisperSync for personal documents? I would gladly pay for that service and I'd like to think that other people would too.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2011-09-27:/blog/2011/my-kindle-wishlist</guid></item><item><title>Updating VideoHub: Design Needs</title><link>https://bloomingcacti.com/blog/2011/updating-videohub-design-needs</link><description>&lt;p&gt;I generate my video files from iMovie and dump them into a folder. I generally generate both a high and a low quality video. I need a way to painlessly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Detect the video quality of the files&lt;/li&gt;
&lt;li&gt;Generate a stub post for the file, with appropriate height / width values, and links to both the high and low quality files.&lt;/li&gt;
&lt;li&gt;Generate separate RSS feeds for the high and low quality videos.&lt;/li&gt;
&lt;li&gt;Generate the VideoJS embed code for the video, to include in the post.&lt;/li&gt;
&lt;li&gt;Geneate a VideoJS embed file, suitable for embedding in an iFrame, so that the videos can be embedded on &lt;a href="http://thosemartins.us/"&gt;&lt;em&gt;Those&lt;/em&gt; Martins&lt;/a&gt; or other sites.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I think I can do all of that with &lt;a href="https://github.com/mojombo/jekyll/wiki"&gt;Jekyll&lt;/a&gt; but I need to learn it and figure out which pieces do what.&lt;/p&gt;
&lt;p&gt;I think that each individual post should have some &lt;a href="https://github.com/mojombo/jekyll/wiki/YAML-Front-Matter"&gt;YML Front Matter&lt;/a&gt; to detail the metadata for each video. It may look something like this:&lt;/p&gt;
&lt;p&gt;Video Data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Height&lt;/li&gt;
&lt;li&gt;Width&lt;/li&gt;
&lt;li&gt;High Quality File&lt;/li&gt;
&lt;li&gt;Low Quality File&lt;/li&gt;
&lt;li&gt;Preview Image&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From that data I need to generate several files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An embed file, including the HTML/JS code necessary to show the video in a browser. It should be suitable both the actual video posts as well as being used from an iframe, to embed the video on a different site.&lt;/li&gt;
&lt;li&gt;The actual video post file&lt;/li&gt;
&lt;li&gt;The low quality RSS feed&lt;/li&gt;
&lt;li&gt;The high quality RSS feed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I can then edit the post, to add a description / narrative for the video. After that, I generate the static site and let rsync take care of pushing it to the server.&lt;/p&gt;
&lt;h3 id="the-video-tag-and-embed"&gt;The Video Tag and Embed&lt;/h3&gt;
&lt;p&gt;Through Liquid Extensions, Jekyll has support for including fragments into a larger page. So, the embed files could be generated into _includes/&lt;em&gt;video&lt;/em&gt;.html. Then, the post file could include it and the file for the actual embed page could also include it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;{% include video.html %}
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="getting-the-video-data"&gt;Getting the Video Data&lt;/h3&gt;
&lt;p&gt;I’ve been looking at Jekyll plugins but I’m not sure that they’re right for scanning my video directory, looking for new videos, getting the video’s metadata and then generating the embed fragments. I might be able to make it work but it also seems more natural to just write a standalone program that will do all of that work and then let Jekyll handle the actual site generation.&lt;/p&gt;
&lt;h3 id="generating-the-rss-feeds"&gt;Generating the RSS Feeds&lt;/h3&gt;
&lt;p&gt;It doesn’t look like Jekyll has a built-in method for generating RSS feeds. I found an example &lt;a href="http://recursive-design.com/blog/2010/09/14/integrating-jekyll-with-feedburner/"&gt;RSS template&lt;/a&gt;, courtesy of &lt;a href="http://recursive-design.com/"&gt;recursive&lt;/a&gt;. I’m thinking that I should just create two RSS templates: one to pull the low quality data from the posts and one to pull the high quality data from the posts.&lt;/p&gt;
&lt;h3 id="other-things"&gt;Other Things&lt;/h3&gt;
&lt;p&gt;To make it easy to move the video files around, I need to have a global host / path prefix, for where the videos live. I should be able to define that in a site-wide configuration file, so that all of my URLs generate automatically.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2011-07-17:/blog/2011/updating-videohub-design-needs</guid><category>Videohub</category></item><item><title>Updating VideoHub: Introduction</title><link>https://bloomingcacti.com/blog/2011/updating-videohub-introduction</link><description>&lt;p&gt;I have an aversion to depending on third party websites to serve my content. I don't mind using them (how else would I actually have a site on the internet, if I didn't have a web host) but I don't like being absolutely dependent on them.&lt;/p&gt;
&lt;p&gt;So, &lt;a href="http://www.youtube.com"&gt;YouTube&lt;/a&gt; has always presented me with a bit of a conundrum. It's very convenient but if I were to use it extensively and it were to change or go down, all of my hosted videos would disappear.&lt;/p&gt;
&lt;p&gt;Several years, I created a site I called &lt;a href="http://videohub.wordflood.net/"&gt;VideoHub&lt;/a&gt; to get around this problem. It was designed to do two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Show a new post for each family movie I created, allowing me to post them and allowing others to view them. It is, of course, RSS enabled so that you can subscribe to new videos.&lt;/li&gt;
&lt;li&gt;Generate a podcast/vodcast compatible RSS feed, so that you could subscribe to the videos in iTunes and auto download them. For geographically remote family members, this is an absolute necessity.&lt;/li&gt;
&lt;li&gt;Serve both a high quality and low quality vodcast feed. For geographically remote family members with poor internet connections, this is also an absolute necessity.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The site still works well for all of these goals. But it’s gotten a bit long in the tooth and needs both maintenance and enhancement. It needs maintenance because it doesn’t work well in a world of iPads and iPhones and is still heavily dependent on flash. It needs enhancement because it’s still too much of a pain to add new videos to the site.&lt;/p&gt;
&lt;p&gt;I’m leaning towards rewriting the site in &lt;a href="https://github.com/mojombo/jekyll/wiki"&gt;Jekyll&lt;/a&gt;. Because Jekyll creates static HTML pages, I can speed up the site eliminate a PHP dependency. I can code the site’s logic in Ruby, without having to actually run Ruby on my server.&lt;/p&gt;
&lt;p&gt;I think can code scripts that will do much of the work of generating a new Jekyll post and then upload everything to the server. That will make creating a new post a much more fire and forget experience.&lt;/p&gt;
&lt;p&gt;I’ll also switch to using &lt;a href="http://videojs.com/"&gt;VideoJS&lt;/a&gt;, or something like it, to embed the video files. This will give me direct MP4 playback for Safari, Chrome, and IE9+. I have no intention of creating, uploading, and hosting OGM versions of my videos, which creates a problem for users of Firefox and Opera. VideoJS will give me a Flash fallback for these browsers.&lt;/p&gt;
&lt;p&gt;Now, I just need to find the time to make it happen.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2011-07-17:/blog/2011/updating-videohub-introduction</guid><category>Videohub</category></item><item><title>We're Moving...</title><link>https://bloomingcacti.com/blog/2011/were-moving</link><description>&lt;p&gt;We are moving... to a new site.&lt;/p&gt;
&lt;p&gt;If you like reading about our family life (and why wouldn't you?), you'll need to update your bookmarks. We're moving to &lt;a href="http://thosemartins.us"&gt;&lt;em&gt;Those&lt;/em&gt; Martins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All new family posts will be at &lt;a href="http://thosemartins.us"&gt;&lt;em&gt;Those&lt;/em&gt; Martins&lt;/a&gt;. &lt;a href="http://wordflood.net"&gt;wordflood.net&lt;/a&gt; will stick around but I'll be changing the content entirely, to be technology focused and not family focused.&lt;/p&gt;
&lt;p&gt;So change your bookmarks (or feed readers) to point to &lt;a href="http://thosemartins.us"&gt;thosemartins.us&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Joe Martin</dc:creator><pubDate>Tue, 06 Jun 2017 16:39:34 -0500</pubDate><guid isPermaLink="false">tag:bloomingcacti.com,2011-07-17:/blog/2011/were-moving</guid></item></channel></rss>