<?xml version="1.0" encoding="UTF-8"?>
<!--Generated by Site-Server v@build.version@ (http://www.squarespace.com) on Wed, 10 Jun 2026 19:46:28 GMT
--><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://www.rssboard.org/media-rss" version="2.0"><channel><title>John Roepke - Boston Area Web and Open Source Software Developer</title><link>https://john.sh/</link><lastBuildDate>Mon, 15 May 2023 01:41:03 +0000</lastBuildDate><language>en-US</language><generator>Site-Server v@build.version@ (http://www.squarespace.com)</generator><description><![CDATA[]]></description><item><title>Backing up Android with BitTorrent Sync and TitaniumBackup</title><category>Android</category><dc:creator>John Roepke</dc:creator><pubDate>Tue, 23 Jul 2013 03:43:03 +0000</pubDate><link>https://john.sh/blog/2013/7/22/backing-up-android-with-bittorrent-sync-and-titaniumbackup</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:51eddefde4b0be2cd1f8abb8</guid><description><![CDATA[<img data-preserve-html-node="true" src="https://i.john.sh/mqddwu/transitions.png" alt="">

<p>With BitTorrent Sync being available on Android, this means you can simply and securely make a backup of your Android device to your home computer using Titanium Backup and BitTorrent Sync from anywhere. The beauty of this method is that there's no middleman you need to trust with your data as it's encrypted from end-to-end.</p>

<p>To follow this guide, you'll need to install <a href="https://play.google.com/store/apps/details?id=com.keramidas.TitaniumBackup">Titanium Backup</a> and <a href="https://play.google.com/store/apps/details?id=com.bittorrent.sync">BitTorrent Sync</a> on your Android device and <a href="http://labs.bittorrent.com/experiments/sync.html">BitTorrent Sync</a> on your computer.</p>

<h2 id="photobackup">Photo Backup</h2>

<p>BitTorrent Sync makes photo backup a trivial process. Simply launch the BitTorrent Sync app and select the BACKUP tab. Tap the add folder button and select the DCIM folder from your sdcard. Make note of the secret key that is generated.</p>

<p><a data-preserve-html-node="true" href="https://i.john.sh/mqdd2z/BitTorrent%20Sync%20-%20Backup%20Tab.png"><img data-preserve-html-node="true" src="https://i.john.sh/mqdd2z/BitTorrent%20Sync%20-%20Backup%20Tab.png" alt="" width="150px"></a> <a data-preserve-html-node="true" href="https://i.john.sh/mqdd35/BitTorrent%20Sync%20-%20Select%20Folder.png"><img data-preserve-html-node="true" src="https://i.john.sh/mqdd35/BitTorrent%20Sync%20-%20Select%20Folder.png" alt="" width="150px"></a> <a data-preserve-html-node="true" href="https://i.john.sh/mqdd32/BitTorrent%20Sync%20-%20Folder%20Key.png"><img data-preserve-html-node="true" src="https://i.john.sh/mqdd32/BitTorrent%20Sync%20-%20Folder%20Key.png" alt="" width="150px"></a> <a data-preserve-html-node="true" href="https://i.john.sh/mqdd2v/BitTorrent%20Sync%20-%20Backup%20Pictures.png"><img data-preserve-html-node="true" src="https://i.john.sh/mqdd2v/BitTorrent%20Sync%20-%20Backup%20Pictures.png" alt="" width="150px"></a></p>

<p>Now, from the BitTorrent Sync client on your computer, add a new sync folder using the secret key generated on your phone and a location for the backup on your computer. Once complete, your devices should connect and being transferring data.</p>

<p>Note that, by default, BitTorrent Sync will not backup over cellular data, but that can be changed in the settings if you have the data available to do so.</p>

<h2 id="appbackupwithtitaniumbackup">App Backup with Titanium Backup</h2>

<p>Note that Titanium Backup requires root to run.</p>

<ol>
<li><p><strong>Setup Scheduled Backups in Titanium Backup</strong> <a data-preserve-html-node="true" href="https://i.john.sh/mqdd3c/Titanium%20Backup%20-%20Scheduled%20Backup.png"><img data-preserve-html-node="true" src="https://i.john.sh/mqdd3c/Titanium%20Backup%20-%20Scheduled%20Backup.png" alt="" width="100px"></a></p>

<p>In order to keep an updated backup, you need to enable scheduled backups in Titanium Backup. There are two profiles that come pre-setup with Titanium; one that backups new apps, and one that checks for changed data in previously backed up apps. The defaults are reasonable, but I like to update them to run more frequently. I set the modified data check to run daily and the new app check twice a week (Wed and Sun).</p>

<p>Check to enable both scheduled backups.</p></li>
<li><p><strong>Setup BitTorrent Sync on Your Phone</strong> <a data-preserve-html-node="true" href="https://i.john.sh/mqdd39/Titanium%20Backup%20-%20Overview.png"><img data-preserve-html-node="true" src="https://i.john.sh/mqdd39/Titanium%20Backup%20-%20Overview.png" alt="" width="100px"></a></p>

<p>Just like with photos, you'll want to add a new folder to the BACKUP tab in BitTorrent Sync. The directory you want to add can be found in the Overview tab of Titanium Backup. The entry you're looking for is "Backup directory". It should be something like "/storage/emulated/0/TitaniumBackup". Make note of the secret key generated on the phone.</p></li>
<li><p><strong>Setup BitTorrent Sync on Your Computer</strong></p>

<p>Again, from the BitTorrent Client on your PC, add a new folder using the secret key from your phone. The devices will connect and you're on your way to having a backup of all the apps on your Android phone.</p></li>
</ol>

<h2 id="notes">Notes</h2>

<ul>
<li>In order to have a continual backup, your computer will need to be on all the time. If that's a problem, you might benefit from a <a href="http://blog.bittorrent.com/2013/05/23/how-i-created-my-own-personal-cloud-using-bittorrent-sync-owncloud-and-raspberry-pi/">Raspberry Pi + BitTorrent Sync</a> setup.</li>
<li>By default BitTorrent Sync only backups over Wifi, this is probably what you want, but it can be changed if you have the data available on your cell plan.</li>
</ul>]]></description></item><item><title>Secure File Syncing</title><category>Life</category><dc:creator>John Roepke</dc:creator><pubDate>Sun, 21 Jul 2013 20:10:54 +0000</pubDate><link>https://john.sh/blog/2013/7/20/secure-file-syncing</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:51ea1698e4b0de966bea51f9</guid><description><![CDATA[<p>This is a follow up to my previous post <a href="http://john.sh/blog/2013/4/26/single-point-of-failure">Single Point of Failure</a>, where I detailed my switch to self-hosted/distributed alternatives to various Google services so I wasn't completely reliant on one provider. At the time I didn't cover file syncing, since Dropbox was doing a fine job for me. However, for a couple of reasons, one being that the Dropbox Linux client is significantly slowing down my system, I've decided to move to a different tool. Conveniently, with the recent <a href="http://www.theverge.com/2013/7/17/4517480/nsa-spying-prism-surveillance-cheat-sheet">NSA Prism revelations</a>, secure distributed alternatives to tools like Dropbox have been enjoying a lot of exposure, so it's been easy to find good alternatives.</p>

<p>The two tools that I'm now using are:</p>

<ol>
<li><a href="https://git-annex.branchable.com">Git Annex</a> + <a href="https://git-annex.branchable.com/assistant/">Git Annex Assistant</a></li>
<li><a href="http://labs.bittorrent.com/experiments/sync.html">BitTorrent Sync</a></li>
</ol>

<p>Both are really promising, but for different reasons.</p>

<h2 id="gitannex">Git Annex</h2>

<p>Git Annex offers the ability to distribute files across many locations and uses Git to keep track of the location(s) any given file is available from. It also gives you the ability to recall a file from a remote location to your current device. This is really powerful for managing a large amount of data when you don't have room for it all on one machine.</p>

<p>The assistant functionality automates the syncing between repositories. It has a nice webapp which provides a GUI for managing remotes and sync strategies. These remotes can be additional git repositories, encrypted rsync destinations, local drives, EC2, and <a href="https://git-annex.branchable.com/special_remotes/">more</a>. The assistant is still under active development, so it's not 100% stable or feature-complete, but it's definitely usable and the underlying functionality is pretty solid.</p>

<p>At the time of writing (July 20th) there's a crowd-funding campaign going on to support another year of full time development on git annex. If you want to support a good open source project, <a href="https://campaign.joeyh.name/">head ond over and contribute a few bucks (or bitcoins.)</a></p>

<h2 id="bittorrentsync">BitTorrent Sync</h2>

<p>BitTorrent Sync is the second tool I'm using for file syncing. It's much less complex then Git Annex. It's all about keeping folders in sync across multiple systems. The setup is pretty simple:</p>

<ol>
<li>Add a folder to the client and generate a unique secret key.</li>
<li>From any other computer, choose a folder and give it the secret key.</li>
<li>Let the computers auto-discover each other, or specify an address.</li>
</ol>

<p>BitTorrent Sync has clients for all the major desktop operating systems and an Android app.</p>

<h2 id="whattouse">What to use?</h2>

<p>For the time being, I'm going to use both. I'm trying out Git Annex as a central store for anything I care about. Using it, I can setup a backup with the encrypted rsync remote as well as directly sync when I need to my different computers on an as-needed basis. This allows me to track all my old projects and miscellany in a single place.</p>

<p>BitTorrent Sync is going to handle more transient syncing needs. This includes things like my workspace and desktop wallpaper collection since they're ever changing and could be rebuilt from scratch if really necessary.</p>

<h2 id="webaccess">Web Access</h2>

<p>In addition to the two syncing services listed above, I also have <a href="http://ajaxplorer.info/">ajaxplorer</a> setup on a remote VM and pointed at a BitTorrent Sync folder setup on the VM. This allows me to make files available when I'm on the road, at work or don't have access to a trusted machine with the client installed.</p>

<h3 id="furtherreading">Further Reading</h3>

<ul>
<li><a href="http://blog.bittorrent.com/2013/05/23/how-i-created-my-own-personal-cloud-using-bittorrent-sync-owncloud-and-raspberry-pi/">BitTorrent Sync + Raspberry Pi</a></li>
<li><a href="https://prism-break.org/#cloud-storage">Free/Open Source alternatives to popular syncing software</a></li>
</ul>]]></description></item><item><title>Single Point of Failure</title><category>Life</category><dc:creator>John Roepke</dc:creator><pubDate>Thu, 02 May 2013 20:28:32 +0000</pubDate><link>https://john.sh/blog/2013/4/26/single-point-of-failure</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:517b2b73e4b065cfbf5cfb2a</guid><description><![CDATA[<h2 id="whyinolongerusegoogleforeverything">Why I no longer use Google for everything</h2>

<p>Periodically a story makes the top of Hacker News about someone who's just had their Google account disabled. Every time I read those stories, I think through what would happen if my account got disabled, and as you might imagine, it wouldn't go well for me, since I don't have any good friends who are Google employees who could bail me out.</p>

<p>I don't have any problem with what Google's doing, and I still intend to use some of their services, but it has become a massive single point of failure for me, and I don't have any recourse if something happens, since Google has no support.</p>

<p>I've decided to fix that by switching to separate providers for the services I depend on. Here's the breakdown of what I'm switching and what I'm keeping.</p>

<h3 id="switching">Switching</h3>

<p><strong>Email</strong></p>

<p>This is the most important service I'm switching, since it's what I use and depend on the most. I've tried a number of alternatives, and I've decided to go with <a href="https://www.fastmail.fm">Fastmail</a>, since they support custom domains, IMAP, SMTP, and have a nice web interface. It's also not expensive for 10GB of space at $40/year (or $32/year if you prepay 5 years). Fortunately, I've been using my own domain with Google Apps, so this switch will be completely seamless for everyone I communicate with.</p>

<p><strong>Email Clients</strong></p>

<p>As a side effect of dropping Gmail, I also needed to switch mail clients on my Android and iOS devices. The key feature I need in a mail client is identites, since I send mail from my custom domain, not the Fastmail account username. On iOS, this is simple, since the native mail client supports sending from a different email address.</p>

<p>Android however, does not support this in the native Mail application, so I had to find a good alternative. The one I ended up with is <a href="https://play.google.com/store/apps/details?id=com.kaitenmail">Kaiten Mail</a>, which is excellent and well worth the $5. It has support for Identities, archiving mail and a plethora of customization options. My only gripe with it is the icon, which looks a lot like a trash can.</p>

<p><strong>Contacts and Calendar</strong></p>

<p>While Fastmail does provide contacts, they don't offer CardDAV syncing, which makes them unavailable on Android and iOS. My solution is to use a separate service, a self-hosted instance of  <a href="http://kolab.org/">Kolab</a> for both Contacts and Calendar. This works with iOS and Android, since it has support for ActiveSync. It also has a very solid web interface for managing both.</p>

<p>I've also been experimenting with <a href="http://owncloud.org/">ownCloud</a>'s Calendar and Contacts, but I'm unimpressed with their web interface and I probably won't keep it. The main reason I'm trying it is the CardDAV and CalDAV support, since those protocols work well across all platforms, desktop and mobile. Kolab has <a href="http://kolab.org/news/2013/03/19/roadmap-kolab-3.1-irony-included">announnced support</a> for both in the next version, so I think it's still the better choice.</p>

<p><em>CalDAV/CardDAV Support (Mobile)</em></p>

<p>This is mostly for my own reference once Kolab has support for CardDAV and CalDAV - iOS has native support, Android does not, but there are good sync adapters available from Google Play for both - <a href="https://play.google.com/store/apps/details?id=org.dmfs.carddav.Sync">CardDAV-Sync</a> ($2) and <a href="https://play.google.com/store/apps/details?id=org.dmfs.caldav.lib">CalDAV-Sync</a> ($2) which both support 2-way syncing.</p>

<p><strong>Reader</strong></p>

<p>I've been largely off of Reader for a while, since it's been obvious Google didn't care about it. The only place I still use it is on iOS, since it's the only service my favorite RSS reader (<a href="https://itunes.apple.com/us/app/byline/id284946773?mt=8">Byline</a>) syncs with. Other than iOS, I've moved all my feeds over to both NewsBlur and Feedly, and have been alternating between the two. I'm waiting for the dust to settle after Reader shuts down to decide which I'll stick with.</p>

<p><strong>Music</strong></p>

<p>I'm setting up ownCloud to replace this as well. I'm using ownCloud's file sync to move Music up to a server and then for playback I'll use a separate tool (ownCloud has a built in music player, but it doesn't have many features for filtering albums or artists.) My intention is to setup, <a href="https://github.com/ampache">Ampache</a>, <a href="http://subsonic.org/">Subsonic</a>, <a href="https://github.com/jinzora">Jinzora</a> or something else on the server to use for playback and streaming. </p>

<p>This is an area where I might end up back with Google, since nothing I've tried has been very good and I also have everything in Amazon's CloudPlayer, so it's not really a single point of failure for me.</p>

<h3 id="keeping">Keeping</h3>

<p><strong>Voice</strong></p>

<p>I haven't found a good alternative to Google Voice; <a href="https://www.sendhub.com">SendHub</a> is close, but it's missing a number of key features like SMS to email and at $25/month for 500 minutes it's a little expensive on top of a cell phone bill. My current plan is to start experimenting with the <a href="http://www.twilio.com/">Twilio</a> API to see if I can duplicate the most important features of Google Voice myself.</p>

<p><strong>Docs / Drive</strong></p>

<p>I don't use Docs enough to merit finding an alternative and I already use Dropbox for syncing.</p>

<p><strong>Search</strong></p>

<p>Google still has the best search engine. It's also not strongly tied to my account, so I don't have to worry about losing data.</p>

<h3 id="summary">Summary</h3>

<p>I have a few final thoughts on this process:</p>

<ol>
<li><p>Google's great on the wallet</p>

<p>Google's offerings are free, which is really hard to beat if you're trying to be economical. However, that comes at the cost of no support. Now that I'm out of college and have an income, but less time, I'm willing to pay for support and the assurance a service isn't going away.</p></li>
<li><p>Google's/Microsoft's/Yahoo's/etc... free offerings suffocate markets</p>

<p>Looking around for good alternatives to products Google or Microsoft offer for free is really hard. There's no really good open solutions for online calendaring, or, until the recently announed death of Reader, RSS readers. Nobody wants to compete with Google.</p></li>
<li><p>Rent a dedicated server</p>

<p>Nothing gives you more flexibility than having a big beefy dedicated server to run VMs on. When I want to test a new tool, all I have to do is clone a template VM and install it. If I like it, I keep the VM, if not, I delete or archive it and move on.</p></li>
</ol>

<p>That's my experience switching from Google. If you have suggestions for good alternatives I'd love to hear about them. Feel free to post a comment or tweet <a href="http://twitter.com/justjohn">@justjohn</a>.</p>]]></description></item><item><title>Working with a VirtualBox VM and a VPN</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Tue, 02 Apr 2013 00:52:26 +0000</pubDate><link>https://john.sh/blog/2013/4/1/working-with-a-virtualbox-vm-and-a-vpn</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:515a2695e4b07a98c912e41c</guid><description><![CDATA[<p>At both my current and previous job, we've used VM's as development platforms. The biggest issue surrounding these has always been getting VPN access into the VM and the host environment while preserving the connection between the VM and host, especially when the VPN client is only availble for the host OS (Windows).</p>

<p>The solution to this problem when using VirtualBox is to setup two network adapters.</p>

<h3 id="1natadapter">1. NAT Adapter</h3>

<p>The NAT adapter allows the VM to share the host OS's network connection, including any VPN's it's connected to. The downside of NAT networking is that you can't directly access the VM from the host box using the assigned IP. </p>

<h3 id="2hostonlyadapter">2. Host-Only Adapter</h3>

<p>That previous problem can be solved by creating a second host-only adapter. This second adapter creates a separate network with the VM and host as members. This gives you an IP address for the VM you can access from the host for testing services on the VM or mounting a Samba/NFS/SFTP share to the host.</p>]]></description></item><item><title>Recovering from a Bad Mercurial Subrepo State</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Mon, 11 Feb 2013 01:04:45 +0000</pubDate><link>https://john.sh/blog/2013/2/10/recovering-from-a-bad-mercurial-subrepo-state.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac84</guid><description><![CDATA[Earlier today I was checking out a Mercurial repository I setup a while 
back as a blank template for projects. Unfortunately, my last commit to it 
was a merge that broke the subrepo state. It also suffered from a typo in 
the .hgsub file. In short, the repository was hosed because I couldn't 
update to any of the previous revisions. I kept getting errors like:

cloning subrepo vendor/_src/twig.js from git://github.com:justjohn/twig.js.git
fatal: unable to connect to github.com:
github.com: Servname not supported for ai_socktype

abort: git clone error 128 in vendor/_src/twig.js (in subrepo vendor/_src/twig.js)

and

abort: invalid subrepository revision specifier in .hgsubstate line 5

It took me a lot of searching to find a solution to these problems, so I'm 
documenting it here in the hope that someone else finds it useful.]]></description><content:encoded><![CDATA[<p>Earlier today I was checking out a Mercurial repository I setup a while back as a blank template for projects. Unfortunately, my last commit to it was a merge that broke the subrepo state. It also suffered from a typo in the .hgsub file. In short, the repository was hosed because I couldn't update to any of the previous revisions. I kept getting errors like:</p>

<pre><code>cloning subrepo vendor/_src/twig.js from git://github.com:justjohn/twig.js.git
fatal: unable to connect to github.com:
github.com: Servname not supported for ai_socktype

abort: git clone error 128 in vendor/_src/twig.js (in subrepo vendor/_src/twig.js)
</code></pre>

<p>and</p>

<pre><code>abort: invalid subrepository revision specifier in .hgsubstate line 5
</code></pre>

<p>It took me a lot of searching to find a <a href="http://mercurial.selenic.com/wiki/ManualCheckout">solution to these problems</a>, so I'm documenting it here in the hope that someone else finds it useful.</p>

<p>Here are the steps I took to get the repository back into a usable state:</p>

<p>Clone the repository, but don't update to the latest revision (-U flag)</p>

<pre><code>hg clone -U ssh://hg@bitbucket.org/justjohn/template
</code></pre>

<p>Revert to a (mostly) good revision excluding the subrepo directories (all were in vendor/_src, you might need multiple "-X" arguments). I was still seeing some errors after running this related to subrepos, but it recovered all the non-subrepo files in the repository.</p>

<pre><code>hg revert -a -r 17 -X vendor/_src
</code></pre>

<p>Now, force set the parent revision for your working directory to the latest in the repository:</p>

<pre><code>hg debugrebuildstate -r 19
</code></pre>

<p>Now, remove references to hgsub and hgsubstate and commit:</p>

<pre><code>hg rm .hgsub
hg rm .hgsubstate

hg commit -m "Remove broken subrepo files"
</code></pre>

<p>You'll still be unable to update to the broken revisions prior to this, but you can at least clone the repository successfully now.</p>]]></content:encoded></item><item><title>Using Your Browser and HTML5 "content editable" as an Editor</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Sun, 10 Feb 2013 17:17:59 +0000</pubDate><link>https://john.sh/blog/2013/2/10/using-your-browser-and-html5-content-editable-as-an-editor.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac83</guid><description><![CDATA[HTML5 has the concept of content editable which allows any DOM element to 
become a user-editable canvas. All you need to do is add the 
contenteditable="true" attribute to an element:

<div id="content" contenteditable="true">
</div>

The beauty of this feature is that the content is part of the HTML DOM and 
can be edited with the chrome inspector, so you have all the power of the 
inspector to drag and drop nodes, change the element styles from the style 
panel, etc...

This gives you a fantastic editor to work in with a very minimal amount of 
code required.]]></description><content:encoded><![CDATA[<p>HTML5 has the concept of <a href="https://developer.mozilla.org/en-US/docs/HTML/Content_Editable">content editable</a> which allows any DOM element to become a user-editable canvas. All you need to do is add the contenteditable="true" attribute to an element:</p>
<p>The beauty of this feature is that the content is part of the HTML DOM and can be edited with the chrome inspector, so you have  all the power of the inspector to drag and drop nodes, change the element styles from the style panel, etc...</p><p>This gives you a fantastic editor to work in with a very minimal amount of code required.</p>


































































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png" data-image-dimensions="1319x739" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" data-sqsp-image-classic-block-image src="https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=1000w" width="1319" height="739" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5031462f24ac2660dce9d64d/1360933726506-90WI70Y0BZ0TB8MLTCNF/content-editable-chrome.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
      
        </figure>
      

    
  


  





  <h2>Persisting Locally</h2><p>Obviously, an editor that can't save isn't very useful, but we can make use of another HTML5 technology, localStorage, to keep the content saved. The basic form of this looks like:</p><pre>&lt;div id="content" contenteditable="true"&gt;
&lt;/div&gt;

&lt;script&gt;
    var key = "draft",
        contentEl = document.getElementById("content");

    contentEl.innerHTML = localStorage[key] || '';
    contentEl.onkeyup=function() {
        localStorage[key] = contentEl.innerHTML;
    };
&lt;/script&gt;
</pre><p>This saves the content to <a href="https://developer.mozilla.org/en-US/docs/DOM/Storage#localStorage">localStorage</a> whenever you type a key, so it persists with your browser. </p><p>See the following jsFiddle for a working example:</p>
























  
  
    
    
      
        
        
        
        
          <iframe allowfullscreen="allowfullscreen" src="http://jsfiddle.net/justjohn/grBUg/embedded/" frameborder="0" id="yui_3_7_3_1_1361120362706_196808"></iframe>
        
        
        
      
    
  




  <p>If all you want is a scratch pad that saves locally, you can stop here. If you want to persist more permanently, keep reading.</p><h2>Saving Remotely</h2><p>The next step is to save the contents external to the editor, which is possible by posting the contents to an API endpoint when Ctrl-S is pressed. But first, we need an API to save documents to. This very basic API (in PHP) saves the posted document to a local <a href="http://redis.io/">redis</a> store:</p><h3>save.php</h3><pre>&lt;?php
 // composer provided autoloader
require("../vendor/autoload.php");
header("Content-Type: application/json");

$prefix = "doc:";

$redis = new Predis\Client();

$key = $_POST["key"];
$content = $_POST["content"];

$redis-&gt;set($prefix.$key, $content);

print(json_encode(array(
    "key" =&gt; $key
)));

?&gt;

</pre><h3>get.php</h3><pre>&lt;?php
// composer provided autoloader
require("../vendor/autoload.php");
header("Content-Type: application/json");

$prefix = "doc:";

$redis = new Predis\Client();

$key = $_GET["key"];
$content = $redis-&gt;get($prefix.$key);

if ($content != null) {
    print(json_encode(array(
        "key" =&gt; $key,
        "content" =&gt; $content
    )));
} else {
    print(json_encode(array("error" =&gt; "404")));
}
?&gt;
</pre><p>Once the API is in place, you need to catch Ctrl-S and post the content from the content editable div to save.php (for brevity, I'm going to use <a href="http://zeptojs.com/">Zepto</a>/<a href="http://jquery.com/">jQuery</a> syntax for ajax calls):</p><pre>function save(key, content) {
    $.post('save.php', {content: content, key: key}, function(response) {
        // keep a copy of the last saved version is localStorage
        // so we can check for changes.
        localStorage[key + ":saved"] = content;
    }, 'json');
}

$("body").keydown(function(event) {
    // Only Ctrl-S and Command-S (19)
    if (!( String.fromCharCode(event.which).toLowerCase() == 's' &amp;&amp; event.ctrlKey) &amp;&amp; !(event.which == 19)) 
        return true;

    save(key, contentEl.innerHTML);

    event.preventDefault();
    return false;
});
</pre><p>This means you have a draft version in localStorage until you explicitly save it. Then it posts to the remote endpoint. In the examples here, the key we post to is hard coded to 'draft'.</p><p>The only step left is to add a function for loading saved content from the remote API and getting the key from the URL:</p><pre>function load(key, fn) {
    $.getJSON('get.php?key='+key, function(data) {
        var content = data.content;
        if (content) fn(content);
    });
}

// get key from url hash and load content
// otherwise, use default key of "content"

var key,
    hash = window.location.hash.replace("#", "");

if (hash) {
    key = hash; 
    load(hash, function(content) {
        localStorage[hash] = content;
        contentEl.innerHTML = content;
    });
} else {
    key = "draft";
}
</pre><p>Here we've replaced our previous static key of "draft" with the content of the url hash, defaulting it to "draft" if it isn't set so we can have different documents at different URLs. Once pulled together, you get a nice, easy to setup editor in the web.</p><p>I've also packaged it together as a site you can setup locally along with some additional features such as document status and conflicts notices. It's available on Github as <a href="https://github.com/justjohn/editor5">editor5</a>.</p><p>It's pretty basic at this point, but I'll be adding features to it over time.</p>]]></content:encoded></item><item><title>How to Host Your Own Personal (Mini) Cloud</title><category>Development</category><category>Features</category><dc:creator>John Roepke</dc:creator><pubDate>Thu, 31 Jan 2013 04:50:22 +0000</pubDate><link>https://john.sh/blog/2013/1/30/how-to-host-your-own-personal-mini-cloud.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac82</guid><description><![CDATA[Need someplace to host your projects and find Amazon EC2 or the Rackspace 
cloud too expensive or under-powered? I have a solution for you that 
provides more flexibility and more power for about $100/month.

Now, I’m not suggesting that cloud providers are bad or overpriced (read 
the delusions that companies have about the cloud for the reasons why not.) 
The DIY route is going to cost you lots of time and research, but if you 
have the time for setup and maintenance, hosting your own mini-cloud will 
save you money and you’ll gain a better understanding of what’s going on 
behind the curtains when you use a cloud service provider.

I call this a mini-cloud since we're only configuring one host server, and 
we're doing VM configuration manually. A true cloud platform should provide 
automated tools for managing VMs (perhaps a topic for a future post.)

My tools of choice for this endeavor are a dedicated server from OVH, I 
chose to go with their SP2 tier (32GB RAM, 2TB HD, 4 core Intel Xeon E3 
1245v2)

Read on to learn about configuring a dedicated server as a VM host!]]></description><content:encoded><![CDATA[<p>Need someplace to host your projects and find Amazon EC2 or the Rackspace cloud
too expensive or under-powered? I have a solution for you that provides more
flexibility and more power for about $100/month.</p>
<aside data-preserve-html-node="true" class="note">
Now, I’m not suggesting that cloud providers are bad or overpriced (read
<a href="http://gigaom.com/2013/01/26/the-delusions-that-companies-have-about-the-cloud/">the delusions that companies have about the cloud</a>
for the reasons why not.) The DIY route is going to cost you lots of time and research,
but if you have the time for setup and maintenance, hosting your own mini-cloud
will save you money and you’ll gain a better understanding of what’s going on
behind the curtains when you use a cloud service provider.

I call this a mini-cloud since we're only configuring one host server, and
we're doing VM configuration manually. A true cloud platform should provide
automated tools for managing VMs (perhaps a topic for a future post.)
</aside>

<p>My tools of choice for this endeavor are a dedicated server from OVH, I chose
to go with their SP2 tier (32GB RAM, 2TB HD, 4 core Intel Xeon E3 1245v2)</p>
<p><img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/OVH%20SP2.png" width="99%"></p>
<p>The most important feature to look for in the server you rent is
“Virtualization Support” since you’re going to be running a bunch of virtual
machines on the server. The cost for the server I selected is $90/month, and
an additional cost of $20/month for extended services if you want more then
1 IP address, which is useful, but not strictly necessary (covered below).</p>
<h1 id="infrastructure">Infrastructure</h1>
<p>Because I’m familiar with it and it will remain supported until 2017, I use
Ubuntu Server 12.04 LTS as the base operating system and the following
instructions are geared towards it.</p>
<aside data-preserve-html-node="true" class="note">
A note on security: The following guide does not really address securing your
server. For that, I suggest reading <a href="http://joshrendek.com/2013/01/securing-ubuntu/">Securing Ubuntu</a>
(mainly the sections on ssh, iptables and Fail2Ban.) By default, this setup
leaves a lot of services exposed to the outside world that really shouldn’t be.
I suggest you restrict all external access to the host machine with a firewall
except SSH (and HTTP if you use a web GUI to administer the VMs). Everything
else should be tunneled through SSH.
</aside>

<p>We’ll be using KVM and libvirt to run our Virtual Machines, so run the
following to get it installed</p>
<pre><code>$ sudo apt-get install kvm libvirt-bin libvirt-doc sasl2-bin</code></pre><p>In order to access libvirtd over tcp, you need to make a few config file modifications:</p>
<p>Add a “-l” (a lowercase L)</p>
<pre><code>/etc/default/libvirt-bin

libvirtd_opts="-d -l"</code></pre><p>Enable TCP and disable TLS</p>
<pre><code>/etc/libvirt/libvirtd.conf

# uncomment the following two lines
listen_tls = 0
listen_tcp = 1</code></pre><p>Restart libvirtd to apply the changes</p>
<pre><code>$ sudo service libvirt-bin restart</code></pre><p>Now we need to add a user for accessing libvirtd, that can be accomplished with:</p>
<pre><code>$ sudo saslpasswd2 -a libvirt john</code></pre><h1 id="networking">Networking</h1>
<p>In order to connect to the VMs we’ll be creating, there’s some network
configuration that needs to be done. The first step is to install these
packages:</p>
<pre><code>$ sudo apt-get install bridge-utils</code></pre><p>If you’re using a restrictive iptables setup, you need to allow connections
from the NAT bridge interface that KVM uses and the loopback adapter, you can
do that with the following iptables rules:</p>
<pre><code>$ sudo iptables -A INPUT -i virbr0 -j ACCEPT
$ sudo iptables -A INPUT -i lo -j ACCEPT</code></pre><p>To persist these rules I use the setup from the Securing Ubuntu article linked
above. You can find a copy of my iptables rules in this <a href="https://gist.github.com/4650817">Gist</a>.</p>
<p>At this point you have a choice to make. Do you want external IP addresses for
some of your VMs? If you do, you’ll need to follow the instructions in the
following section to setup network bridging. The alternative is to use iptables
to forward specific ports from the server to individual VMs, or use a reverse
proxy like nginx or apache to direct web traffic to VMs based on hostname.</p>
<h3 id="network-bridging">Network Bridging</h3>
<p>If you plan on assigning external IP addresses to your VMs you need to setup a
network bridge for your ethernet adapter:</p>
<p>Add network bridge</p>
<pre><code>/etc/network/interfaces

auto eth0
iface eth0 inet manual

auto br0
iface br0 inet static
    address 12.34.56.78
    netmask 255.255.255.0
    network 12.34.56.0
    broadcast 12.34.56.255
    gateway 12.34.56.254
    bridge_ports eth0
    bridge_stp off
    bridge_fd 0
    bridge_maxwait 0</code></pre><p>In order for the VMs to communicate freely with the outside world, the following
configuration disables iptables from interfering with bridged traffic.</p>
<p>Add the following lines to the end of the file</p>
<pre><code>/etc/sysctl.conf

   net.bridge.bridge-nf-call-ip6tables = 0
   net.bridge.bridge-nf-call-iptables = 0
  net.bridge.bridge-nf-call-arptables = 0</code></pre><p>Reload the kernel with the new sysctl parameters</p>
<pre><code>$ sudo sysctl -p /etc/sysctl.conf
$ sudo service networking restart</code></pre><p>At this point, it’s also a good idea to reboot the server to ensure all the
networking changes get applied. Once that’s complete you can check for the
bridged adapter by running:</p>
<pre><code>$ brctl show
bridge name     bridge id               STP enabled     interfaces
virbr0          8000.000000000000       yes
br0             8000.000e0cb30550       no              eth0</code></pre><h2 id="creating-and-managing-vms">Creating and Managing VMs</h2>
<p>Once you have the infrastructure and network setup, you can access to the
server from an administration tool for libvirt. I use virt-manager, which
you can install on your Ubuntu Desktop with:</p>
<pre><code>$ sudo apt-get install virt-manager</code></pre><p>Another promising tool to consider is Web Virtual Manager, which, when set up
on the host machine provides a web interface for creating and managing virtual
machines. I found it to be quite buggy, but useful for starting and stopping
existing VMs when I don’t have access to my desktop and virt-manager.</p>
<p>To add a connection to your server with the following settings (using your
server IP/hostname and your unix username)</p>

<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/connect.png" width="45%">


<p>If you've set up public key authentication from your desktop to your server,
this is all that’s required to connect.</p>
<p>The first step, once you’re connected is to setup the storage pool for your
VMs. You can do that by right clicking on the connection and selecting
“Details” For simplicity, I just use a directory storage pool, which stores
disk image files in the directory you specify.</p>
<aside data-preserve-html-node="true" class="note">
A note on storage space: Make sure to check the partition configuration of the
machine you’re running on. By default, OVH dedicated servers are partitioned
with the bulk of your storage space mounted in /home, so a good place to keep
your VMs is /home/vm rather than the default in /var/lib/libvirt.
</aside>

<pre><code>$ sudo mkdir /home/vm</code></pre>
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-store-1.png" width="45%">
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-store-2.png" width="45%">


<p>Once you have your storage space set up, you’re almost ready to create a
virtual machine. The only thing between you and your new VM is getting the
install cd ISO for your OS of choice. I choose to store them in a second
storage pool called ‘iso’</p>
<p>From the Virtual Machine Manager, click “New VM” and follow the wizard choosing
how much memory, CPU and hard drive space to allocate the new VM:</p>

<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-vm-1.png" width="45%">
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-vm-2.png" width="45%">
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-vm-3.png" width="45%">
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-vm-4.png" width="45%">



<p>The last step varies based on if you want an external IP for your VM. If you
do, you may have to use a MAC address provided by your hosting provider.
With OVH, you generate one from their control panel’s “IPs and Reverses”
section, check with your host for their specific requirements. You’ll also need
to specify the network bridge created before.</p>
<p>If you don’t need a specific MAC, you can use the one generated by virt-manager
or set your own, I find this command from the Ubuntu Wiki page on KVM
Networking helpful for generating a random QEMU vendor prefixed addresses:</p>
<pre><code>$ MACADDR="52:54:00:$(dd if=/dev/urandom bs=512 count=1 2&gt;/dev/null | md5sum | sed 's/^\(..\)\(..\)\(..\).*$/\1:\2:\3/')"; echo $MACADDR</code></pre>
With External IP
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-vm-5.png" width="100%">


Internal NAT IP
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/add-vm-5.2.png" width="100%">

&nbsp;

<p>When you finish, the VM will start and virt-manager will give you a graphical
console via VNC connection to set up your VM.</p>
<img data-preserve-html-node="true" src="https://johnroepke.squarespace.com/storage/posts/private-cloud/vm-finished.png" width="90%">


<p>Congratulations! You now have the basis for your own private cloud. What you do
with it is entirely up to you now.</p>
<p>Want a next step? I suggest trying to setup something like <a href="https://github.com/cloudfoundry/vcap">CloudFoundry</a>
on at least one VM, just to give yourself another taste of what the cloud feels like.</p>]]></content:encoded></item><item><title>Transit Widget on Google Play</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Sat, 03 Nov 2012 20:09:58 +0000</pubDate><link>https://john.sh/blog/2012/11/3/transit-widget-on-google-play.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac81</guid><description><![CDATA[<p><a href="http://play.google.com/store/apps/details?id=com.transitwidget"><img src="https://github.com/justjohn/transitwidget/raw/master/artwork/transit.png" width="128" height="128" /><br /><img alt="Android app on Google Play" src="http://developer.android.com/images/brand/en_generic_rgb_wo_45.png" /></a></p>

<p>I never quite got around to posting here, but TransitWidget, an application started at a Google Technology User Group hackathon I participated in a while back is now available for free on Google Play. It supports all bus services that <a href="http://www.nextbus.com/predictor/agencySelector.jsp">NextBus supports</a>, including the MBTA, MTA and SF Muni. If you like the idea of having a widget on your home or lock screen that displays the time till the next bus, you might like it.</p>]]></description></item><item><title>Weather in your Console</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Fri, 26 Oct 2012 04:32:06 +0000</pubDate><link>https://john.sh/blog/2012/10/26/weather-in-your-console.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac80</guid><description><![CDATA[<p><a href="javascript:Y.Squarespace.Utils.lightboxAsset('511e3348e4b08a2d8e26ae01');"><img src="/static/5031462f24ac2660dce9d64d/511e3347e4b08a2d8e26ac57/511e3348e4b08a2d8e26ae02/1351227126877/weatherBaner.png/1000w" alt=""/></a></p>

<p>I've been experimenting with writing command line tools in <a href="http://nodejs.org/">node.js</a> recently and it turns out to be a fantastic platform for doing so. It's really easy to get information from an API and format it for a console. As a result, I present <code>console-weather</code>, a simple tool for printing the current weather conditions based on a geolocation of your IP address.</p>

<p>You can install it via <a href="http://nodejs.org/download/">npm</a> with</p>

<pre><code>npm install -g console-weather
</code></pre>

<p>You'll need to get an API key from the <a href="http://api.wunderground.com/weather/api/">Weather Underground API</a> and store it in your <code>WEATHER_API</code> environment variable.</p>

<pre><code>Usage: weather [OPTION]...
Print the current weather conditions and forecast.

Options:
    -c       Output temperature in celsius.
    -f       Print 4-day forecast.
    --color  Colorize the output.
</code></pre>

<p>I'll be posting more on the topic of command line node in the future; so if you're interested, stay tuned.</p>

<p><a href="https://github.com/justjohn/console-weather">Github</a> :: <a href="https://npmjs.org/package/console-weather">NPM</a></p>]]></description></item><item><title>FlipClock Updates</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Tue, 25 Sep 2012 03:47:28 +0000</pubDate><link>https://john.sh/blog/2012/9/24/flipclock-updates.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac7f</guid><description><![CDATA[<p><span class="full-image-float-right ssNonEditable"><span><a href="https://chrome.google.com/webstore/detail/copjokjinhlflggeifkidlmodfepbpgl?utm_source=john-sh"><img src="/static/5031462f24ac2660dce9d64d/511e3347e4b08a2d8e26ac57/511e3348e4b08a2d8e26add8/1328916767017/promo_small_tile.png/1000w" alt="" /></a></span></span>There's a updated version of <a href="https://chrome.google.com/webstore/detail/copjokjinhlflggeifkidlmodfepbpgl?utm_source=john-sh">FlipClock</a> available today on the Chrome Web Store with a couple new features.</p>
<p>Changes:&nbsp;</p>
<ul>
<li>Option to switch between 12 and 24hr modes.</li>
<li>Options to enable or disable seconds</li>
<li>flipclock.us now works better on mobile devices with smaller screens and in porttrait orientation.</li>
</ul>
<p>&nbsp;You can get the updated verions on the <a href="https://chrome.google.com/webstore/detail/copjokjinhlflggeifkidlmodfepbpgl?utm_source=john-sh">Chrome Web Store</a>, or at <a href="http://flipclock.us/">http://flipclock.us/</a>&nbsp;</p>]]></description></item><item><title>MyGeode on the Android Market and Startup Weekend Boston</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Mon, 05 Mar 2012 10:15:27 +0000</pubDate><link>https://john.sh/blog/2012/3/5/mygeode-on-the-android-market-and-startup-weekend-boston.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac7d</guid><description><![CDATA[<span class="thumbnail-image-float-right ssNonEditable"><a href="https://market.android.com/details?id=com.mygeode"><br /><img src="/static/5031462f24ac2660dce9d64d/511e3347e4b08a2d8e26ac57/511e3348e4b08a2d8e26add3/1330942589337/3825654-16955892-thumbnail.jpg/1000w" alt="" /><br /><img src="/static/5031462f24ac2660dce9d64d/511e3347e4b08a2d8e26ac57/511e3348e4b08a2d8e26add4/1330942729037/android_market_logo.png/1000w" alt="" /></a></span><a href="http://mygeode.com/">MyGeode</a>, a location-based todo list app I had the pleasure of developing is now available on the&nbsp;<a href="https://market.android.com/details?id=com.mygeode">Android Market</a> for the reasonable price of $0.99.</p>
<p>MyGeode was a project I pitched last week at <a href="http://boston.startupweekend.org/">Startup Weekend Boston</a> and developed largely over the weekend with the help of <span>Amir Banihashem (design), <span>Ryan Newton (website), Judith da Silva (business) and Mike Gioia (business). I've spent the last week finishing up the app and getting it ready for publishing.</span></span></p>
<p><span><span>I think the app has potential and I plan to continue developing it, it's based around the idea that tasks should have locations associated with them and using that location can help make you more productive. Imagine if your task list could alert you when you're near the grocery store, or when you get to work, it could know that, and remind you of your high priority tasks for the day. If that sounds attractive to you, take a look at the app and let me know what you think.</span></span></p>
<p>The version I released today is a solid todo list with the core location features: notifications when you near a task, a map view that dispalys all your tasks on a map and a task view that gives you all the information you need to know about a task. I'm excited about it and I've got a great list of features I plan on adding to the app, including routing capability to help you plan the most efficient route to complete your tasks.</p>
<p>I feel it's important to give a plug to <a href="http://startupweekend.org/">Startup Weekend</a>, since this is the second time I've gone and it's been an amazing experience both times. This time I have an app in the market a week after the event and I'm still actively working on the project from last year's event. If you're at all interested in startups or building a software product, you owe it to yourself to check out an event like <a href="http://startupweekend.org/">Startup Weekend</a>. It's 54 hours of insane fun and a great opportunity to connect with the local startup scene. If you do go, make sure to take advantage of the mentors available; it's very helpful to talk to people who've done it before and get their feedback during the early stages of developing your idea.</p>
<p>To conclude, you should <a href="https://market.android.com/details?id=com.mygeode">buy MyGeode from the Android Market</a> and let me know what you think!</p>]]></description></item><item><title>Modularized JavaScript with Backbone.js and CommonJS Modules/2.0</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Sat, 11 Feb 2012 03:52:13 +0000</pubDate><link>https://john.sh/blog/2012/2/10/modularized-javascript-with-backbonejs-and-commonjs-modules2.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac5c</guid><description><![CDATA[As part of my project to create a port of the excellent PHP Twig templating 
language to JavaScript I've been putting together some demos of how to use 
it with some of the more popular JavaScript frameworks available. Parallel 
to that I've been to several different conferences (Wakanday and Rich Web 
Experience) recently with talks about modularized JavaScript and JavaScript 
MVC frameworks. So I decided to put together one of the demos using what 
I've learned from the different talks.

You can find the completed demo in the my twig.js github project. The 
following are my thoughts from the experience and some instructions for 
setting up a project using Backbone.js and BravoJS.]]></description><content:encoded><![CDATA[<p>As part of my project to create a <a href="https://github.com/justjohn/twig.js">port</a> of the excellent PHP <a href="http://twig.sensiolabs.org/">Twig templating language</a> to JavaScript I've been putting together some demos of how to use it with some of the more popular JavaScript frameworks available. Parallel to that I've been to several different conferences (<a href="http://wakanday.org/">Wakanday</a> and <a href="http://therichwebexperience.com/conference/fort_lauderdale/2011/11/home">Rich Web Experience</a>) recently with talks about modularized JavaScript and JavaScript MVC frameworks. So I decided to put together one of the demos using what I've learned from the different talks. </p>

<p>You can find the completed demo in the my <a href="https://github.com/justjohn/twig.js/tree/master/demos/twitter_backbone">twig.js github project</a>. The following are my thoughts from the experience and some instructions for setting up a project using Backbone.js and BravoJS. </p>

<h3>You have to do some work to use Backbone as a CommonJS Modules/2.0 Module</h3>

<p>Currently, backbone and underscore have Modules/1.1 style exports for use in node.js (or other Modules/1.1 environments). This makes it easy to adapt them to support Modules/2.0 by simply wrapping the js files with a <code>modules.declare</code> call.  This would be very easy to automate with a build script for your application.</p>



<h3>MVC on the client is not MVC on the server</h3>

<p>It's worth making the point that you don't have to do MVC on the front-end the same way as you do it no the backend. Because you don't have to serve different content types (JSON, HTML) from the same MVC you can take more liberties with your design. Also, because of the way Backbone lets you bind to events from the view, it makes it very convenient to write your control logic in the Backbone.View and handle the view rendering with a template.</p>

<h3>A good templating engine makes your life much easier</h3>

<p>Because you do a lot of the controller-type logic in the Backbone Views, you need a good templating engine for rendering the views. This keeps the HTML out of the JavaScript and makes it much easier to maintain. What I found (using <a href="https://github.com/justjohn/twig.js">twig.js</a>) is that very granular templates work best. For example, in my twitter demo, the TweetView binds to the change event of the backing model and re-renders the template anytime the model changes. This keeps the UI updated everywhere a TweetView is used.</p>

<p>Here's an example of the TweetView Backbone.View and template:</p>

<p><strong>tweetView.js</strong></p>



<p><strong>tweet.twig</strong></p>



<p>This pattern is by far the best I've used for maintaining a dynamic web application and I'll probably take this tack with future projects of mine. I'm a proponent of well modularized code and this fits the bill nicely. It avoids the pitfalls of relying on global definitions of objects in JavaScript and makes your code much more portable. I anticipate being able to share a lot of code (modules) in future applications between a backend running on Node.js and the frontend web browser; which will be a big improvment over my current stack of Java or PHP on the backend and JavaScript on the frontend.  I'll be continuing to experiment with this pattern and I'll likely have more to say on the subject over time as I try it on larger projects.</p>]]></content:encoded></item><item><title>FlipClock Available on the Chrome Web Store</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Thu, 02 Feb 2012 10:43:14 +0000</pubDate><link>https://john.sh/blog/2012/2/2/flipclock-available-on-the-chrome-web-store.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac59</guid><description><![CDATA[<a href="https://chrome.google.com/webstore/detail/copjokjinhlflggeifkidlmodfepbpgl"><span class="full-image-block ssNonEditable"><span><img src="/static/5031462f24ac2660dce9d64d/511e3347e4b08a2d8e26ac57/511e3348e4b08a2d8e26adff/1328916767017/promo_small_tile.png/1000w" alt="" /></span></span></a>
I just published my <a href="http://flipclock.us">FlipClock webapp</a> to the <a href="https://chrome.google.com/webstore/detail/copjokjinhlflggeifkidlmodfepbpgl">Chrome Web Store</a>.<br /><br />It was remarkably easy to package it up. I have to hand it to Google, they've made it pretty easy to publish a JavaScript based webapp to their store. The process basically entailed adding a manifest.json to the project and packaging the directory into a ZIP file. If you're interested, you can check out the source on <a href="https://github.com/justjohn/flipclock">Github</a> or <a href="https://bitbucket.org/justjohn/flipclock">bitbucket</a>.<br /><br />In short, you should install it and enjoy watching the time flip away...]]></description></item><item><title>Cross-domain AJAX, Express.js and Access-Control-Allow-Origin</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Thu, 30 Jun 2011 11:23:56 +0000</pubDate><link>https://john.sh/blog/2011/6/30/cross-domain-ajax-expressjs-and-access-control-allow-origin.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac6a</guid><description><![CDATA[<p>I have a tip for anyone working with <a href="http://nodejs.org/">node.js</a> frameworks, in my case it's the <a href="http://expressjs.com/">Express</a> framework, but this applies to any framework that binds to URL patterns.<br /><br />I've been working out some JavaScript on <a href="http://jsfiddle.net">jsFiddle</a> that involves AJAX requests to an endpoint I have running on a node.js server external to jsFiddle. Part of this requires that the Access-Control-Allow-Origin header be setup to allow access from external domains. You can do this in Express by adding the header to your response object with:</p>
<p><code>res.header("Access-Control-Allow-Origin", "*");</code></p>
<p>I had a route that looked something like this:</p>
<pre>app.get('/posts', function(req, res){<br />    res.header("Access-Control-Allow-Origin", "*");<br />    res.header("Access-Control-Allow-Headers", "X-Requested-With"); <br />    res.send(<br />        { posts : ... }<br />    );<br />});</pre>
<p><br />But it wasn't working in Chrome, I kept seeing the error "XMLHttpRequest cannot load http://... Origin <a title="http://fiddle.jshell.net" href="http://fiddle.jshell.net/">http://fiddle.jshell.net</a> is not allowed by Access-Control-Allow-Origin." even though I was setting the correct header.<br /><br />Turns out that there was a OPTIONS request being sent before the GET request that didn't match my route and it was failing with the previous error. Problem is, Chrome didn't show the request in the Inspector's Network tab so it took me a long time to figure out what was happening.<br /><br />The fix is a pretty simple one; you need to make sure your route in Express matches the OPTIONS request as well as the GET request. The easiest way to do that is to bind with app.all(...) to match on all requests. So the updated and working code is:</p>
<pre>app.<strong>all</strong>('/posts', function(req, res){<br />    res.header("Access-Control-Allow-Origin", "*");<br />    res.header("Access-Control-Allow-Headers", "X-Requested-With");<br />    res.send(<br />        { posts : ... }<br />    );<br />});</pre>
<p><br />It's also important to note that I needed to set the <strong>Access-Control-Allow-Headers</strong> header to allow "X-Requested-With". Without it, it generated a similar error: "XMLHttpRequest cannot load http://... Request header field X-Requested-With is not allowed by Access-Control-Allow-Headers."</p>]]></description></item><item><title>Startup Weekend Boston and Developing PocketRoster</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Sun, 12 Jun 2011 01:24:42 +0000</pubDate><link>https://john.sh/blog/2011/6/11/startup-weekend-boston-and-developing-pocketroster.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac76</guid><description><![CDATA[<p>Last weekend, <a href="http://boston.startupweekend.org/">Startup Weekend Boston</a> took over a whole floor of the <a href="http://microsoftcambridge.com/Default.aspx">Microsoft NERD Center</a> in Cambridge and for 54 hours, entrepreneurship, development and lots of caffeine were the rule. This is my experience over the weekend and how the <a href="http://pocketroster.me/">PocketRoster</a> app was developed.<br /><br />The weekend started on Friday evening with around 50 ideas presented in rapid fire succession, with 60 seconds being given to each idea. There were a lot of ideas, but after a round of discussion and voting on each idea only about half remained. Among them was the idea for an mobile organization directory app presented by <a href="http://www.linkedin.com/profile/view?id=211905">Howie Hecht</a>. That idea piqued my interest and I joined his team. Unfortunately, no one else shared my interest and we ended the evening with only two members on our team. After a quick meeting we decided to go ahead with the idea and got a basic plan for the app hashed out before leaving for the night.<br /><br />Saturday started at 9am in the morning with and we spent the day working out the fundamentals of the application and strategies for making a profitable business out of it. From a development point of view, two days is a pretty short time for developing a working mobile site, but by paring down the required features to the bare minimum and by using a number of relatively new frameworks and technologies we were able to get a good looking and functional mobile app completed.<br /><br />The first of the frameworks I used was <a href="http://silex-project.org/">Silex</a>, a new PHP 5.3 micro-framework based on components from <a href="http://symfony.com/">Symfony2</a>, a popular php framework. The way Silex works is by binding closures (functions) to url paths like:</p>
<pre>$app-&gt;get('/user/{username}', function($username) {<br />    return "User profile for $username";<br />});</pre>
<p><br />Combined with a powerful templating engine like <a href="http://www.twig-project.org/">twig</a> that supports template inheritance, you can rapidly bind variations of a template to different url patterns. Using a base template with a standard page structure you can move all the common code for analytics, ads, etc... into it and you only have to write the page specific elements in each template. For storing data I ended up using Mongo for it's flexibility and because I could reuse components I've previously written for my <a href="https://github.com/justjohn/foundry-core">foundry-core</a> library.<br /><br />For the front end I chose JQuery Mobile and was impressed by it's ease of use. It's pretty quick to get up and running with very little code required. They also have a decent amount of documentation on the basics which helped immensely when putting together the structure of the app but was lacking when it came to troubleshooting issues that popped up during development. My overall impression is definitely favorable, but I don't think it's quite good enough to replace native apps. There is a lot of loading time involved between pages that really slows down the experience. I think I could probably get around a lot of it by pre-loading templates and with some caching of content, but the effort required is probably better spent writing native apps. In it's current form it's a great tool for prototyping, but not yet a viable long term solution.<br /><br />Unfortunately, we didn't win the contest on Sunday, that honor went to <a href="http://boston.startupweekend.org/2011/06/08/demos/">CaseReportal</a>, a medical reporting/collaboration tool. But we do plan on pursuing our project since it has great applications and we were able to confirm the demand for such a product. If you want to learn more about it you can sign up for an account at <a href="http://pocketroster.me">http://pocketroster.me</a> and try it out on your mobile device. If you think you'd like to use our product for your team or organization or if you have feedback for us you can contact us at <a href="mailto:feedback@pocketroster.me">feedback@pocketroster.me</a>.</p>
&nbsp;]]></description></item><item><title>A Workspace That Works with Everything</title><category>Development</category><category>Life</category><dc:creator>John Roepke</dc:creator><pubDate>Mon, 18 Apr 2011 04:00:03 +0000</pubDate><link>https://john.sh/blog/2011/4/18/a-workspace-that-works-with-everything.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac78</guid><description><![CDATA[<p class="">One of the problems I've always had with the setup of my computer is<br>that each operating system has one thing I want that it does best, and I<br>would have to either compromise in one or reboot to the other.  On Linux,<br>it's the development environment - I've always been most productive in an<br>environment with a usable terminal and minimal UI. On Windows it's the<br>media software available (and the games); I can't live without Winamp (and<br>sometimes iTunes) for their device syncing capabilities and excellent<br>playback capabilities.<br><br>With the last round of upgrades to my primary desktop (a quad-core i7<br>and 12GB of memory) I've finally reached the point where I can have what<br>I've always wanted: a reliable way to run a complete development<br>environment and simultaneously use the Media software of my choice on<br>the same machine.<br><br>My system currently runs the following:</p><ul data-rte-list="default"><li><p class="">Windows 7 Ultimate as the host OS</p></li><li><p class=""><a href="http://www.virtualbox.org/">VirtualBox 4</a> running the following 3 VMs (all Ubuntu 10.10)</p></li><li><p class="">A file server running <a href="http://www.greyhole.net/">Greyhole</a> and Samba for redundant storage accessible from everywhere</p></li><li><p class="">A network core VM for locally hosting network services such as DNS and NIS. (oh, and a <a href="http://www.minecraft.net/">Minecraft</a> server)</p></li><li><p class="">A development VM with the appropriate toolset for PHP development (<a href="http://netbeans.org/">Netbeans 7</a> and <a href="http://www.mozilla.com/en-US/firefox/">Firefox</a>).</p></li></ul><p class=""><br>The features that make this all work:</p><ol data-rte-list="default"><li><p class="">VirtualBox supports multiple monitors, so my development VM can span<br> both my monitors, or only one if I need some windows-only tool on the other.</p></li><li><p class="">Windows is the host OS, so it has all the access to the hardware it needs<br> for device syncing with my Android phone and my iPod Touch. It's also a<br> plus when I want to do some gaming; I can close the development VM to<br> free up resources and since VirtualBox saves state it takes mere moments<br> to bring it back up when I need it (typically under 30 seconds.)</p></li><li><p class="">Ubuntu has excellent support for running in a VirtualBox container. I haven't<br> had any issues with the VirtualBox guest additions and drivers.</p></li></ol><p class=""><br>In short, if you have a powerful enough machine, you can get the benefits of both<br>Windows and Linux simultaneously without having to give up anything due to<br>VirtualBox's great support within Ubuntu.</p>]]></description></item><item><title>PHP Frameworks</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Wed, 23 Feb 2011 02:23:29 +0000</pubDate><link>https://john.sh/blog/2011/2/22/php-frameworks.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac79</guid><description><![CDATA[<p>The <a href="http://www.meetup.com/bostonphp/">Boston PHP Meetup Group</a> had a php framework bake-off yesterday that I attended. The following are my initial thoughts on the frameworks presented.<br /><br />1. <a href="http://cakephp.org/">CakePHP</a> and <a href="http://www.symfony-project.org/">Symfony</a><br />I've bundled these two together since they seem largely based on the same philosophy: rapid development and deployment of an applications by assembling prebuild code around data models. I think they're pretty interesting and worth looking into especially when you need a rapid prototype of an application. I'll probably focus on Symfony since it seemed slightly more intriguing.<br /><br />2. <a href="http://codeigniter.com/">CodeIgniter</a><br />The description of CodeIgniter given by the presenter was that it's "a framework for web frameworks" and that seemed largely true through his demo. There's a lot more code necessary then for CakePHP and Symphony, but it seems to be a much more flexible framework then them as well. I'm definitely going to be looking into it a lot more to see what I can do with it.<br /><br />3. <a href="http://framework.zend.com/">Zend Framework</a><br />I was not really impressed by the demo of Zend Framework. It seems overly complicated without providing a significant gain in development speed. I doubt I'll look into it any further unless I come across something I need that it really excels at.<br /><br />In summary:<br />Based on the demos I saw, I'll be looking into Symfony and CodeIgniter to be my frameworks of choice, Symfony when I need a rapid prototype and CodeIgniter when I need a very customizable base to work from. My caveat to this is that it's just a first impression and may change over time as I explore the functionality of the frameworks.</p>]]></description></item><item><title>Mass GTUG Chrome Hackathon</title><category>Development</category><category>Life</category><dc:creator>John Roepke</dc:creator><pubDate>Sun, 20 Feb 2011 01:45:15 +0000</pubDate><link>https://john.sh/blog/2011/2/19/mass-gtug-chrome-hackathon.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac7b</guid><description><![CDATA[<p id="yui_3_10_1_1_1394991786544_43368">Yesterday was the first Massachusetts Google Technology User Group (<a href="http://massgtug.gtugs.org/">Mass GTUG</a>) hackathon I've participated in, their Chrome Hackathon. It was a great experience with a pair of talks in the morning; one on building chrome apps (given by @<a href="https://twitter.com/jankleinert">jankleinert</a>) and the other on hacking Chromium OS. The main even was the afternoon (from 1-5pm) devoted to developing interesting apps and extensions for Chrome. There were a total of 10 demos from the afternoon's work ranging from a Chrome extension for managing a <a href="http://todotxt.com/">todo.txt</a> task list to a tool for <a href="http://texwith.me/">collaboratively editing LaTeX documents</a>.</p>
























  
    <img src="http://i.john.sh/n2jjkr/Screen-shot-2011-02-19-at-4.47.30-PM.png" width="300" height="226" align="right" title="A Social Wallboard" alt=""/>
  




  <p id="yui_3_10_1_1_1394991786544_43386">My project for the afternoon was a social wallboard. I had it in enough shape to do a short presentation by 5pm, but I didn't manage to get all the features that I wanted in (4hrs really isn't very long.) I have a good start however and intend to keep working on the project. Hopefully I'll have something usable in a few weeks after I've had a chance to solidify the OAuth authentication flow with the social services; I implemented the base code for Remember the Milk and Flickr during the Hackathon but I plan on adding a fair number of additional services over time (Twitter, Google Calendar, Delicious, etc...)</p><p id="yui_3_10_1_1_1394991786544_43391">My primary takeaways from the day of hacking are that if you want to have a good demonstration after one afternoon you really have to cut back on the scope of your project (i.e. don't involve too many third party services; especially ones with OAuth) and it would have really helped to have a team of people to distribute the load across and brainstorm with. Overall though, it was totally worth it and I'm looking forward to the next chance I have to participate in a Hackathon.</p>]]></description></item><item><title>Deploying OSGi Feature Repositories to a Maven Repository</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Wed, 01 Dec 2010 01:01:51 +0000</pubDate><link>https://john.sh/blog/2010/11/30/deploying-osgi-feature-repositories-to-a-maven-repository.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac7c</guid><description><![CDATA[Once you've started deploying bundles into maven; the next step is to 
collect your bundles into feature repositories and make them available in 
your maven repository. (Don't get confused by the terminology - A OSGi 
feature repository is different than a maven repository) Turns out it's 
fairly easy to get setup, since an OSGi feature repository is just an XML 
file. All you need to do is attach the XML file as an artifact to a maven 
project and you can deploy it and reference it from ServiceMix (the OSGi 
container I'm using; it might be possible in others, but I haven't tried). 
Here's the process I use to deploy feature repositories:]]></description><content:encoded><![CDATA[<p>Once you've started deploying bundles into maven; the next step is to collect your bundles into feature repositories and make them available in your maven repository. (Don't get confused by the terminology - A OSGi feature repository is different than a maven repository)<br /><br />Turns out it's fairly easy to get setup, since an OSGi feature repository is just an XML file. All you need to do is attach the XML file as an artifact to a maven project and you can deploy it and reference it from <a href="http://servicemix.apache.org/home.html">ServiceMix</a> (the OSGi container I'm using; it might be possible in others, but I haven't tried).<br /><br />Here's the process I use to deploy feature repositories:<br /><br />1. First you need to create the features.xml file you will be deploying. See <a href="http://fusesource.com/docs/esb/4.2/deploy_osgi/DeployFeatures-Create.html">FuseSource's Documentation</a> for a good overview.</p>
<p>It should look something like:</p>
<pre>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;<br />&lt;features&gt;<br />    &lt;feature name="tool-feature" version="${version}"&gt;<br />        &lt;bundle&gt;mvn:us.justjohn.osgi/test-tools/1.1/jar/osgi&lt;/bundle&gt;<br />        &lt;bundle&gt;mvn:us.justjohn.osgi/utilities/1.0/jar/osgi&lt;/bundle&gt;<br />    &lt;/feature&gt;<br />&lt;/features&gt;</pre>
<p><br />Note the <em>${version}</em>, we'll be using Maven's resource filtering to pull the version number in from the POM file.<br /><br />2. Create a Maven project and put your features.xml file in src/main/resources. Use the following in your pom.xml file.</p>
<pre>&lt;project xmlns="http://maven.apache.org/POM/4.0.0"<br />    &nbsp;xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;<br />   &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;<br /><br />   &lt;groupId&gt;us.justjohn.osgi&lt;/groupId&gt;<br />   &lt;artifactId&gt;test-feature-repo&lt;/artifactId&gt;<br />   &lt;packaging&gt;pom&lt;/packaging&gt;<br />   &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;<br />   &lt;name&gt;Test ServiceMix Feature Collection&lt;/name&gt;<br /><br />   &lt;build&gt;<br />     &lt;plugins&gt;<br />       &lt;plugin&gt;<br />         &lt;artifactId&gt;maven-resources-plugin&lt;/artifactId&gt;<br />         &lt;version&gt;2.4.3&lt;/version&gt;<br />         &lt;executions&gt;<br />           &lt;execution&gt;<br />             &lt;id&gt;copy-resources&lt;/id&gt;<br />             &lt;phase&gt;validate&lt;/phase&gt;<br />             &lt;goals&gt;<br />               &lt;goal&gt;copy-resources&lt;/goal&gt;<br />             &lt;/goals&gt;<br />             &lt;configuration&gt;<br />               &lt;outputDirectory&gt;${basedir}/target&lt;/outputDirectory&gt;<br />               &lt;resources&gt;<br />                 &lt;resource&gt;<br />                   &lt;directory&gt;src/main/resources&lt;/directory&gt;<br />                   &lt;filtering&gt;true&lt;/filtering&gt;<br />                 &lt;/resource&gt;<br />               &lt;/resources&gt;<br />             &lt;/configuration&gt;<br />           &lt;/execution&gt;<br />         &lt;/executions&gt;<br />       &lt;/plugin&gt;<br />       &lt;plugin&gt;<br />         &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;<br />         &lt;artifactId&gt;build-helper-maven-plugin&lt;/artifactId&gt;<br />         &lt;executions&gt;<br />           &lt;execution&gt;<br />             &lt;id&gt;attach-artifacts&lt;/id&gt;<br />             &lt;phase&gt;package&lt;/phase&gt;<br />             &lt;goals&gt;<br />               &lt;goal&gt;attach-artifact&lt;/goal&gt;<br />             &lt;/goals&gt;<br />             &lt;configuration&gt;<br />               &lt;artifacts&gt;<br />                 &lt;artifact&gt;<br />                   &lt;file&gt;target/features.xml&lt;/file&gt;<br />                   &lt;type&gt;xml&lt;/type&gt;<br />                   &lt;classifier&gt;features&lt;/classifier&gt;<br />                 &lt;/artifact&gt;<br />               &lt;/artifacts&gt;<br />             &lt;/configuration&gt;<br />           &lt;/execution&gt;<br />         &lt;/executions&gt;<br />       &lt;/plugin&gt;<br />     &lt;/plugins&gt;<br />   &lt;/build&gt;<br />&lt;/project&gt;</pre>
<p><br />Let's break this down and see what's going on. First, the maven-resources-plugin is configured to filter all the resources in src/main/resources. This will allow you to put maven properties into your features.xml file. (In the example above I used ${version} to get the version from the POM)<br /><br />Second, the build-helper-maven-plugin is configured to attach the filtered features.xml file as an artifact with the type xml and a classifier of features ( the classifier is optional, but makes the URL more readable and matches convention.)<br /><br />This will result in a URL is ServiceMix like:</p>
<pre><span>mvn:us.justjohn.osgi/test-feature-repo/1.0/xml/features</span></pre>
<p><br />3. Deploy the feature to your maven repository:</p>
<p><code>$ mvn clean deploy</code></p>
<p>4. Load the feature repository URL in ServiceMix. (assuming you've already configured ServiceMix with you maven repository)</p>
<p><code>features:addUrl mvn:us.justjohn.osgi/test-feature-repo/1.0/xml/features</code></p>
<p>You can verify a successful load with&nbsp;<span>features:listUrl, </span>you should see the new URL listed. Now you're ready to load any features defined in the feature repository, in the example there's only one, "tool-feature". You can check that it's in the list of available features with:</p>
<p><code>features:list | grep "tool-feature"</code></p>
<p>You should see something like:</p>
<p><code>[uninstalled] [1.0             ] tool-feature ...</code></p>]]></content:encoded></item><item><title>Hosting OSGi Artifacts with Classifiers in Maven</title><category>Development</category><dc:creator>John Roepke</dc:creator><pubDate>Sat, 27 Nov 2010 00:50:12 +0000</pubDate><link>https://john.sh/blog/2010/11/26/hosting-osgi-artifacts-with-classifiers-in-maven.html</link><guid isPermaLink="false">5031462f24ac2660dce9d64d:511e3347e4b08a2d8e26ac57:511e3347e4b08a2d8e26ac5d</guid><description><![CDATA[One of the requirements for the software that we're developing at work is 
the ability to load components into an OSGi container.This prompted an 
interesting question from one of the developers I work with about deploying 
both a regular JAR and an OSGi bundle from his project into our Maven 
repository. He couldn't access both of the artifacts after deploying them, 
only the last one deployed, the other returned an error related to "build 
number not found". After some digging I discovered this was being caused by 
our use of classifiers.]]></description><content:encoded><![CDATA[<p>One of the requirements for the software that we're developing at work is the ability to load components into an <a href="http://servicemix.apache.org/home.html">OSGi container</a>. &nbsp;This prompted an interesting question from one of the developers I work with about deploying both a regular JAR and an OSGi bundle from his project into our <a href="http://nexus.sonatype.org/">Maven repository</a>. He couldn't access both of the artifacts after deploying them, only the last one deployed, the other returned an error related to "build number not found". After some digging I discovered this was being caused by our use of classifiers.<br /><br />The way that we'd like to name our artifacts is to append an osgi classifier to the artifact name for the OSGi bundle version. This causes an issue if you try to deploy SNAPSHOT versions of both artifacts to the same maven repository because of a limitation in the Maven 2 format of repository metadata. The metadata only contains one build number and timestamp for the current snapshot of any given version.&nbsp;For reference a Maven 2 artifacts looks like this:</p><pre>artifactId-version-timestamp-buildnumber-classifier.jar</pre><p><br />Because the timestamp and build number are increased every time a new artifact is deployed and Maven 2 only stores one build number / timestamp for snapshots regardless of classifier, you can only ever access the latest deployed artifact. This is a major problem if you want to deploy multiple artifacts with the same snapshot version and different classifiers, such as an osgi bundle and a regular jar artifact. You can only access the most recently deployed artifact, since the build number won't match for any of the other versions with different classifiers.<br /><br />There are a few different solutions to this:</p><ol><br /><li>Upgrade to Maven 3, the issue is solved in the latest release. (See <a href="http://jira.codehaus.org/browse/MNG-4452">MNG-4452</a> for details)</li><li>Disable build numbers by setting uniqueVersion to false in the <a href="http://maven.apache.org/pom.html#Repository">maven distributionManagement settings</a></li><li>Use separate Maven repositories for the different build types.</li></ol><p><br />We ended up going with option 3 since we're not at a point where we can upgrade to Maven 3 and we want to have history for the snapshots we deploy.<br />Here's how we accomplished this:<br /><br />We already have a profile in our common parent POM to enable OSGi build (change the artifact type to bundle, set the "osgi" classifier, configure the <a href="http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html">maven-bundle-plugin</a>, etc...) The change I added to the profile was overriding the &nbsp;distributionMangement tag with a different set of repositories for snapshots and releases. While this adds a couple more repositories to maintain, it does have the nice side effect of segmenting all of our OSGi artifacts from the non-OSGi artifacts.<br /><br />I've also setup a repository group that contains both the snapshot and release repositories in it. This makes the ServiceMix configuration really easy. All we have to do is add the group URL to the list of repositories in the org.ops4j.pax.url.mvn configuration file and we're all set to begin loading bundles into ServiceMix.</p>]]></content:encoded></item></channel></rss>