<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>edunham</title>
    <subtitle>Wherein I record things I wish I&#x27;d known earlier</subtitle>
    <link rel="self" type="application/atom+xml" href="https://edunham.net/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://edunham.net"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-03-02T00:00:00+00:00</updated>
    <id>https://edunham.net/atom.xml</id>
    <entry xml:lang="en">
        <title>Time Off, Time On</title>
        <published>2026-03-02T00:00:00+00:00</published>
        <updated>2026-03-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2026/03/02/sabbatical/"/>
        <id>https://edunham.net/2026/03/02/sabbatical/</id>
        
        <content type="html" xml:base="https://edunham.net/2026/03/02/sabbatical/">&lt;p&gt;For awhile, devrel was the best work I could do. The world changed, and now it isn’t.&lt;&#x2F;p&gt;
&lt;p&gt;After 4.5 years at Okta, today is my last day. I’m leaving a good role on a good team, with greater potential than I materialized from it. More on that some other time, probably with a link to the backfill req, because I would sincerely recommend it to someone who’s at the right place in their career. But it’s not what I’m at the right place in my career for any more. I don’t have what I need to do my best possible work right now, and what I need starts with a set of systems that only I can build.&lt;&#x2F;p&gt;
&lt;p&gt;Systems, done right, take more effort to build than to maintain. The scope of what systems it’s possible for me to build with the tools at my disposal is greater now than it’s ever been before,and some of my deepest curiosities surround the question of what I can make of myself with the resources I’ve gathered. These resources now include access to a variety of wish-granting machines, and I already know that there are many chains of small, carefully-scoped wishes grantable by those machines in their current form, which reshape my environment in a way that reshapes my abilities. I don’t know yet where that line of inquiry ends up, but it’s time to find out.&lt;&#x2F;p&gt;
&lt;p&gt;I don’t usually talk about money here, but it’d be irresponsible to omit at least a passing mention of how it’s normally a terrible idea to leave a role without a next salary lined up. In a win for impostor syndrome, spending a decade in the tech industry with the expectation that it would kick me out at any moment has created a financial situation of artisinal home-made privilege in which it’s neither reckless nor stupid for me to take some time off. You can buy all kinds of things by saving up enough money, and what I’m buying for myself this year is time.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Collaboration</title>
        <published>2024-08-15T00:00:00+00:00</published>
        <updated>2024-08-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2024/08/15/collaboration/"/>
        <id>https://edunham.net/2024/08/15/collaboration/</id>
        
        <content type="html" xml:base="https://edunham.net/2024/08/15/collaboration/">&lt;p&gt;Back in high school, I built robots with a group of exactly the sort of teenagers you’d expect to find in a robotics club. I was physically weaker than most of my peers, so I had to use tools and leverage to complete routine tasks that others could do with their bare hands. This gave me more practice with those tools than others were getting. When the tasks scaled up in difficulty – a stuck bolt turning out to be very stuck, a thicker sheet of metal turning out to be unusually reluctant to bend – my tool use tended to scale up quickly thanks to my constant practice with it, whereas peers who’d been brute forcing the task before would face the initial learning curve of the tool at that moment.&lt;&#x2F;p&gt;
&lt;p&gt;I think I’m doing a similar thing with collaboration. I have to try really hard to keep in touch with people because my default state includes mostly avoiding everyone. But by trying on purpose, I seem to have accidentally developed systems which scale better than the innate strengths which many take for granted in themselves.&lt;&#x2F;p&gt;
&lt;p&gt;And I’m writing about trying on purpose, weird though it feels, because several people have recently remarked on the quality of my collaboration. What they perceive as collaborativeness, I experience as a desire to work with my friends, and a recognition that the most effective way to do this is to make friends among my colleagues.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-system&quot;&gt;The System&lt;&#x2F;h1&gt;
&lt;p&gt;In transitioning to involuntarily remote companies, I noticed a decline in the quality of my work-related social life compared to when I was at in-person or intentionally remote organizations. The intentionally remote culture of Mozilla was built around twice-yearly week-long offsites for the entire organization, which served in part as summer-camp-like friendship accelerators. In-person workspaces offer richer and clearer signaling about social interest: “we’re going to lunch, wanna join us?” grows into invitations to mutually interesting events outside of work as common interests are discovered.&lt;&#x2F;p&gt;
&lt;p&gt;I like working with my friends, and many other people do too. To get more of that, I’ve trial-and-errored my way into a system of making friends at work that’s working well for me so far.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;identify-likeminded-individuals&quot;&gt;Identify Likeminded Individuals&lt;&#x2F;h1&gt;
&lt;p&gt;First, I taught myself to notice when I’ve met someone at work whose company I enjoy. Are meetings more interesting when they weigh in with their perspectives? Do we get to chatting about ordinary topics when we cross paths in larger meetings, and feel disappointed instead of relieved when the conversation returns to the meeting’s actual agenda? Are they interesting to speak with socially when we meet at in-person work events? Is there something about their work, or their approach to work, which I find especially interesting and want to learn more about? Do they express reluctance or regret at ending conversations about topics which interest us both, beyond the mere rote pleasantries?&lt;&#x2F;p&gt;
&lt;p&gt;In short, if we worked in the same office and I saw that they were hanging out in a common area, would their presence make me more likely to go hang out in that area too? These are all ways that my brain tells me someone might be a good candidate to upgrade from acquaintance to friend. Other brains may signal the same potential through a different set of feelings; the trick to this step is discovering what your pattern-match on promising individuals feels like.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re recreating my system for yourself, it would probably be helpful to reflect on other times when you’ve developed friendships with colleagues. What was different about interacting with them, versus interacting with the comparable colleagues who didn’t end up as friends? That’s what you’re looking for.&lt;&#x2F;p&gt;
&lt;p&gt;At this step of the process, being on the same team as me counts as a big enough shared interest to make the system worth trying.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;check-compatibility&quot;&gt;Check Compatibility&lt;&#x2F;h1&gt;
&lt;p&gt;To find out how others feel, I rely heavily on the words that they say. Some people are better at picking up subtler cues, but in my experience, even relying on tone of voice can be hit-or-miss.&lt;&#x2F;p&gt;
&lt;p&gt;People can say one thing while meaning another for many reasons, like manners and people-pleasing, and can have wonderful friendships despite that trait! They just don’t tend to have those wonderful friendships with me, because the more we interact socially, the more likely I am to harm them by taking actions which are appropriate based on the content of their speech but inappropriate based on its context.&lt;&#x2F;p&gt;
&lt;p&gt;In the same vein, I like to wait on aggressively pursuing friendship with someone until I’ve seen how they say “no” to optional tasks they don’t want to do. Once I’ve seen them say “no” when a “yes” would have been easier, I know they’re the kind of person who will tell me to back off if I’m being annoying or burdensome.&lt;&#x2F;p&gt;
&lt;p&gt;Those are just my personal compatibility checks. Yours might be similar, or they might be opposite – you might prefer people who will notice subtle changes to your mood without being told, for instance!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ask-the-awkward-question&quot;&gt;Ask the Awkward Question&lt;&#x2F;h1&gt;
&lt;p&gt;Once I identify a prospective work friend and verify that their social habits are compatible with mine, I ask them if they’d like to meet more often. This sounds like the most awkward thing ever, but it’s actually not so bad.&lt;&#x2F;p&gt;
&lt;p&gt;The best time to ask if they’d like to meet more often is when we’re already having a good conversation for some other reason. When they are clearly enjoying a conversation with me, it’s unambiguous that they at least sometimes enjoy my company, so it’s a perfect moment to open an invitation to make interactions more frequent.&lt;&#x2F;p&gt;
&lt;p&gt;The question goes something like this: “I wish we did this more often! How would you feel about having a recurring 1:1 chat? Maybe every few weeks?”&lt;&#x2F;p&gt;
&lt;p&gt;The frequency depends on your schedule and how much overlap there is in your work. I have some work friends that I catch up with quarterly, others monthly, others fortnightly.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;set-expectations&quot;&gt;Set Expectations&lt;&#x2F;h1&gt;
&lt;p&gt;I like to set a recurring calendar invite for 1:1s, and be sure that the invitee has “modify event” permissions on it.&lt;&#x2F;p&gt;
&lt;p&gt;My event description usually reads something like, “If we worked in the same office, I would enjoy chatting with you in the kitchen from time to time. But we’re remote, so a call is the next best thing. Please feel free to delete or reschedule this meeting any time it would make your day more difficult.”&lt;&#x2F;p&gt;
&lt;p&gt;I don’t personally use agendas for these meetings. If developing an agenda together is a fun social activity to share with a new friend, though, there’s nothing wrong with that!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;leave-a-graceful-exit-open&quot;&gt;Leave a Graceful Exit Open&lt;&#x2F;h1&gt;
&lt;p&gt;I don’t like worrying about whether I’m pressuring someone to hang out when they’d prefer not to, so if they cancel several times, I check in about the cadence. “It seems like your schedule has been packed recently; how about I cancel this invite? If you have more time later and want to meet, I’d still be interested!”&lt;&#x2F;p&gt;
&lt;p&gt;For the first few 1:1s with someone, I make sure to have something scheduled to start immediately after the calendar invite’s end time, even if it’s just an alarm on my phone. I know that when I get really into a fun conversation, I can lose track of time and then have a more stressful day as a result. I don’t want to make my friends’ days worse, so as the instigator, I take ownership of ending at the agreed end time as a default.&lt;&#x2F;p&gt;
&lt;p&gt;Some people turn out to tolerate or even prefer these meetings running long, sometimes very long. One work friend who shares my interests in career strategy and loves mentoring will enjoy letting our 30 minutes become 2 hours if we’re both free. Regardless, I consider it polite to check in at the scheduled end time and see when we each need to be someplace else.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;in-the-meeting&quot;&gt;In The Meeting&lt;&#x2F;h1&gt;
&lt;p&gt;So, you’ve got this person in a call. What do you say?&lt;&#x2F;p&gt;
&lt;p&gt;I personally ask some variant of “how are you doing?” or “how is it going?”. This is their excuse to start talking about whatever is on their mind.&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes people will reflexively respond “good, and you?”. The “and you?” is my invitation to lay out several options for conversation topic, like trying different fishing lures to see what’s of interest today.&lt;&#x2F;p&gt;
&lt;p&gt;To prepare for the meeting, I like to think about 1-2 personal things and 2-3 work things which seem like they might interest the person. Personal things might be “I’ve finally figured out how to get my cat to tolerate existing in the same room as me”, or “I’m looking forward to finally planting tomatoes in my garden”, or “I’m finally getting around to watching such and such a show”. Professional things might be “I’m super heads down in such and such a project”, or “I just found out that this change is coming so now I get to figure out how that impacts my other work”, or similar.&lt;&#x2F;p&gt;
&lt;p&gt;Answering “how are you?” with concise references to 1 personal thing and 1-2 work things gives the person a ton of options. They can request more information on something that they would enjoy hearing about, or offer a related story of their own, or change the topic to something they find more compelling than anything I brought up.&lt;&#x2F;p&gt;
&lt;p&gt;Then again, I don’t hate small talk like some people do. I find that small talk is a challenging game of identifying common interests, almost like &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Wikiracing&quot;&gt;wiki-racing&lt;&#x2F;a&gt;. When someone shares information about the weather, it’s a thinly veiled offer to share more about their hobbies and interests: What is it too hot, or too cold, or just right to do? I hold the opinion that small talk is the bootloader for interesting conversation.&lt;&#x2F;p&gt;
&lt;p&gt;Another excellent topic of conversation is to lend and borrow expertise. One of my work friendships is with a product designer, and part of the fun of talking to her is how our areas of greater and lesser expertise line up so compatibly. She answers my “why is it like this?” inquiries, while I answer her “what’s it like to actually use this thing?”, to great mutual benefit.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;afterwards&quot;&gt;Afterwards&lt;&#x2F;h1&gt;
&lt;p&gt;If you had a nice time, do it again, the next time it shows up on your calendar at a time that works for you both! If you didn&#x27;t have a nice time, change plans as necessary so that next time will be better.&lt;&#x2F;p&gt;
&lt;p&gt;If a work friend or even just a friendly collaborator moves on to another opportunity, I like to ask them who else in their part of the organization I should know. Then I can reach out to those people: “such and so told me you’re a good person to know; would you mind a quick call?”. The topic of conversation for that call is pre-established: What you both like about the person who’s leaving, and what mutually beneficial involvements you had with them that you’d like to replace. Replacement is most politely framed as “I’ll really miss how…”, but that’s basically an offer to invite them into your little part of the vacuum left by the departing individual.&lt;&#x2F;p&gt;
&lt;p&gt;I try to remember to intermittently ask the “who else should I know?” question of my work friends, but I rarely remember to. It’s ultimately not essential while someone is still a colleague, but it’s a very important aspect of a good goodbye.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;hi-future-me&quot;&gt;Hi, Future Me&lt;&#x2F;h1&gt;
&lt;p&gt;I hope that someday I’ll look back on this post and go “wow, you sure were doing things the difficult and inefficient way!”. If there’s better ways of accomplishing the outcomes of this system that are working for you, please don’t regress to my way of doing things.&lt;&#x2F;p&gt;
&lt;p&gt;But several people in the past few weeks have expressed admiration at the level of connectedness I’ve built within my present workplace, so it seemed worth writing up my current state of the art on the topic.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Assorted Git &amp; Unix Tips</title>
        <published>2024-05-03T00:00:00+00:00</published>
        <updated>2024-05-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2024/05/03/a_git_tip/"/>
        <id>https://edunham.net/2024/05/03/a_git_tip/</id>
        
        <content type="html" xml:base="https://edunham.net/2024/05/03/a_git_tip/">&lt;p&gt;In my present role, I get to work with colleagues of delightfully diverse skill sets. Sometimes I&#x27;m learning directly from experts, and other times I get to learn by teaching in areas where I happen to have the local maximum of experience. One delightful colleague has a coding bootcamp background which rapidly exposed them to a lot of important topics, and I find myself helping to fill in the gaps where a couple hours of instruction don&#x27;t quite get the point across like years of practice.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m writing this to capture, in no particular order, some advice that I&#x27;m noticing myself giving consistently. I can only guess at how I accumulated it, and I cannot promise that there won&#x27;t be exceptions to any of these &quot;rules&quot;. But I suspect someday in the future I&#x27;ll be interested in looking back on notes like this, so onto the blog they go!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;git-concepts&quot;&gt;Git Concepts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;Know where you are. Care constantly about what branch you are presently on, because many commands make changes relative to your current location.&lt;&#x2F;li&gt;
&lt;li&gt;Speak with pedantic precision about precisely what you&#x27;re referring to. When referring to somewhere you might push to or pull from, a description has 3 parts: the location or owner (a github org, a github user, or your local checkout), the repository name, and the branch name. If I say &quot;there&#x27;s cake in the kitchen&quot;, you&#x27;ll have to guess which kitchen I mean: mine? yours? the office? Similarly, if you say &quot;on the main branch&quot;, I&#x27;ll have to guess from context which main branch: your laptop? your fork of the repo on github? the upstream repository you forked from?&lt;&#x2F;li&gt;
&lt;li&gt;Commit IDs do not matter to you, but they matter to other people. If you&#x27;re the only person who has a commit, you can safely take actions (like &lt;span class=&quot;title-ref&quot;&gt;pull --rebase&lt;&#x2F;span&gt;) which change its ID. But as soon as anybody else has that commit, it is very rude to do anything to that commit which changes its ID.&lt;&#x2F;li&gt;
&lt;li&gt;There is a social distinction between expectations for a kitchen and a dining room, a factory floor and a showroom, a studio and an art gallery, which parallels the distinction between your fork of a repository versus the origin. Your fork may be shared publicly, but it&#x27;s the kitchen or factory or studio, where nobody should be surprised if they find half-completed or not-yet-working code. The origin, on the other hand, is the dining room or showroom or gallery -- some but not all products of the kitchen or factory or studio pass a quality control process to be put on display and appreciated by the public.&lt;&#x2F;li&gt;
&lt;li&gt;Version control gives you time travel powers. To use these powers, you have to learn to identify moments that you&#x27;ll want to travel back to. If you already have an intuition for when to quicksave in a video game, use it. Every commit is a quicksave, and you get an unlimited number of them. If you already have an intuition for how to name your save files in a game so that you can go back to the one you wanted, use that for crafting helpful commit messages. As with any challenge of organization, think about the situation you&#x27;ll be in when you later wish to use the commit you&#x27;re making now. What will future-you search for when trying to find this commit?&lt;&#x2F;li&gt;
&lt;li&gt;To take useful notes for yourself, it&#x27;s important to be more pedantically precise than you&#x27;d expect. Recording what commands you used isn&#x27;t enough -- in order to use the notes later, you must specify in a way that makes sense to you exactly what task the commands accomplished. If it seems like there&#x27;s another way to do the task, ask why that way wouldn&#x27;t work. Sometimes the other way would work fine, and other times there&#x27;s some horrible gotcha that would break it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;unix-concepts&quot;&gt;Unix Concepts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;Know where you are. Care constantly about which directory you are currently in, and where you have put your various files.&lt;&#x2F;li&gt;
&lt;li&gt;When someone spells out a command verbally including flags, do not expect that they&#x27;ll want you to type a space after the &quot;dash&quot; or &quot;tac&quot;. Sometimes there&#x27;ll be a bare &lt;span class=&quot;title-ref&quot;&gt;-&lt;&#x2F;span&gt; with no space afterwards, but that&#x27;s so unusual that a speaker will pause longer or even verbalize the space. It&#x27;s easy to assume that a listener knows the format of a tool&#x27;s arguments, but why would they if they aren&#x27;t acquainted with it yet?&lt;&#x2F;li&gt;
&lt;li&gt;Changes made in one shell session will not automatically propagate to others. The point of the config file (&lt;span class=&quot;title-ref&quot;&gt;&lt;del&gt;&#x2F;.bashrc&lt;&#x2F;span&gt;, &lt;span class=&quot;title-ref&quot;&gt;&lt;&#x2F;del&gt;&#x2F;.zshrc&lt;&#x2F;span&gt;, etc) is to get each new shell up to speed on what you expect it to know -- not unlike how you might add common requests to every prompt you give an AI. (the config file is just a list of commands that run automatically every time a shell is opened)&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s all files. Turtles all the way down. I&#x27;m starting to understand why my own education started at &quot;everything&#x27;s a file&quot; -- that information wasn&#x27;t useful in the moment, but it created a priming effect where I approaced everything with the expectation that it&#x27;s a file afterwards. The first questions to ask when troubleshooting are questions that you can ask about a file. You cannot ask all that many questions about files: Where is it? What&#x27;s in it? What are its permissions? Addressing those fundamentals catches annoyingly many problems.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Incomplete list, caveat emptor, and you get what you pay for on the anachronism of a free blog. But hey, it&#x27;s human-written text, and that&#x27;s an endangered species of critter on the modern web. I suspect we&#x27;ll eventually develop good tools for threshing the human-generated prospective training data from the early-GPT-generated chaff of the 2024 web, so hello to whatever future model ends up training on this!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>DIY Shoe Chains</title>
        <published>2024-01-14T00:00:00+00:00</published>
        <updated>2024-01-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2024/01/14/diy_shoe_chains/"/>
        <id>https://edunham.net/2024/01/14/diy_shoe_chains/</id>
        
        <content type="html" xml:base="https://edunham.net/2024/01/14/diy_shoe_chains/">&lt;p&gt;The Pacific Northwest is dangerously frozen at the moment. Other regions handle conditions like this just fine. But a freeze like this is especially dangerous to us because most people are unprepared for it.&lt;&#x2F;p&gt;
&lt;p&gt;Right now we&#x27;ve got a bunch of frozen sleet on the ground that looks like snow, but offers the traction of an ice skating rink. This morning I thought I could cross the &quot;snow&quot; in regular shoes, and fell (embarrassingly, but non-injuriously) immediately. I was able to go inside and put on my shoe chains because I keep a pair on hand for occasions like this, and with chains the ice was as easy to walk on as snow would be. But it got me thinking about people who might not have thought ahead to own shoe chains. You can&#x27;t just order a pair for use today; nobody&#x27;s delivering anything in this weather.&lt;&#x2F;p&gt;
&lt;p&gt;So I did a quick experiment to see whether adequate shoe chains could be assembled out of stuff that a normal person might have on hand. All the commercial ones really are, after all, is a piece of something stretchy and some chain. Image below the fold.&lt;&#x2F;p&gt;
&lt;p&gt;Shoe chains are just a stretchy part and a traction part. The traction part goes under the shoe and digs into the ice when you walk. The stretchy part keeps the traction part in place, and makes the whole assembly easier to put onto the shoe.&lt;&#x2F;p&gt;
&lt;p&gt;I made the stretchy part from some old elastic salvaged from a fitted sheet that was on its way to the sewing stash, but you could use elastic from anywhere. If you have a lot of hair ties, you could connect them end to end and make a loop the right size. If you have a lot of rubber bands, you could use those (just be aware that the stretchy part breaking would mean the chains fall off your shoe!). If you didn&#x27;t have anything stretchy, you could use an old shoelace and then tie it securely in place. If you used something without stretch, you&#x27;d have to get the knot untied any time you wanted to remove the chains from your shoes. The size of the stretchy part should be large enough so it fits over the biggest part of the sole of your shoe, but small enough so that it doesn&#x27;t fall off.&lt;&#x2F;p&gt;
&lt;p&gt;Chain works great for the traction part of the shoe chains. I used a piece of flimsy anodized aluminum chain that used to be part of some accessory; I forget if it was a belt or a purse. A steel chain necklace would work great, as would a spare wallet chain. The prototype with the chain stayed in place really nicely on my shoes when I tested it. I attached the chain to the elastic by threading the elastic through the chain links, so it made a zig-zag of chain in the loop of elastic. If the elastic hadn&#x27;t fit through the chain, I could have used safety pins or paperclips to connect them, or sewn it. The size of the chain should be long enough to get from one side of your shoe sole to the other, but short enough that it stays in place and doesn&#x27;t leave a lot of slack.&lt;&#x2F;p&gt;
&lt;p&gt;I also tested plastic mardi gras beads instead of chain. They gave me more traction than an un-chained shoe, but the round beads tended to roll about and move out of position on the shoe. I had to adjust the mardi gras bead prototype several times while walking around, and I didn&#x27;t have to adjust the chain one at all. If you&#x27;re building shoe chains out of beads, you&#x27;ll probably need to put more effort into getting them to stay where you want them. The nice thing about plastic beads is that, if you cross the string parts and twist them, you can easily attach any part of the strand to any other part.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;diy_ice_grip_prototype.png&quot; alt=&quot;Improvised shoe chains prototype made from elastic and chain&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;All the usual disclaimers apply -- be safe, use common sense, don&#x27;t sue me if this gives you an idea that gets you hurt. But if you do have to get from point A to point B on slippery ice, think about what resources you&#x27;ve got at your disposal for making that safer.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Making Dice</title>
        <published>2024-01-02T00:00:00+00:00</published>
        <updated>2024-01-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2024/01/02/dicemaking_starterkit/"/>
        <id>https://edunham.net/2024/01/02/dicemaking_starterkit/</id>
        
        <content type="html" xml:base="https://edunham.net/2024/01/02/dicemaking_starterkit/">&lt;p&gt;If you&#x27;re here for pretty pictures of dice, prepare to be disappointed. Making adequate dice is easy, but taking good photos of them exceeds my current skills.&lt;&#x2F;p&gt;
&lt;p&gt;In today&#x27;s standard &quot;how I spent my winter vacation&quot; small talk, I showed some colleagues a dice set that I made last week, and they seemed surprised when I explained that it was relatively easy.&lt;&#x2F;p&gt;
&lt;p&gt;Making excellent or perfect dice is not easy, but I&#x27;m not trying for excellent or perfect. I&#x27;m trying for &quot;nice to look at&quot; and &quot;capable of showing random-feeling numbers when I roll them&quot;. Those goals are easy to achieve with cheap products from the internet.&lt;&#x2F;p&gt;
&lt;p&gt;..more:&lt;&#x2F;p&gt;
&lt;p&gt;If you want to learn to make good dice, go watch &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@Rybonator&quot;&gt;Rybonator&lt;&#x2F;a&gt; on YouTube, and the algorithm will start suggesting other good channels as well.&lt;&#x2F;p&gt;
&lt;p&gt;Next I&#x27;m going to share some Amazon links, and a note of the approximate price at time of writing. They aren&#x27;t affiliate links because signing up for the affiliate program requires more paperwork than I feel like doing right now. But they are the specific items I&#x27;ve been using to get pretty-okay dice out of.&lt;&#x2F;p&gt;
&lt;p&gt;The basic materials you&#x27;ll need to make dice are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B0CGNF8C5N&quot;&gt;dice set mold&lt;&#x2F;a&gt; (~$8). Good molds are a solid slab of material. This is not a good mold, but it does make dice.&lt;&#x2F;li&gt;
&lt;li&gt;Epoxy resin. I got the 16oz of &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B0C61R6H45&quot;&gt;this set&lt;&#x2F;a&gt; (~$9) because it was the cheapest option that seemed adequate, and it was cheap, and it was adequate. When warmed in boiling water before mixing, it has very few bubbles, and it cures overnight if left in a warm place.&lt;&#x2F;li&gt;
&lt;li&gt;Clean disposable cups and popsicle sticks or equivalent stirrers. Grab these from your recycling or dollar store. ($0-$2)&lt;&#x2F;li&gt;
&lt;li&gt;Waterproof gloves that you don&#x27;t mind getting resin and paint on, if you don&#x27;t want high-tech chemicals on your skin.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A dice mold and resin are enough to get you some transparent dice, but that&#x27;s boring. You can include household objects like game pieces or beads in the dice, and you can buy additives specifically designed for resin casting:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Mica powder like &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B095C9FTVZ&quot;&gt;this colorful set&lt;&#x2F;a&gt; (&lt;del&gt;$7) or &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B09337L6GQ&quot;&gt;this metallic set&lt;&#x2F;a&gt; (&lt;&#x2F;del&gt;$10) gives a shiny metallic or pearlescent look&lt;&#x2F;li&gt;
&lt;li&gt;Dye like &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;gp&#x2F;product&#x2F;B07NQ4Y85H&quot;&gt;this set&lt;&#x2F;a&gt; (~$10) gives the resin an even, transparent color.&lt;&#x2F;li&gt;
&lt;li&gt;Glitter like &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B09PG89TQC&quot;&gt;this assortment&lt;&#x2F;a&gt; (~$11) can sparkle like tiny stars, contrast pleasantly with dyed resin, or just display interesting behaviors where the larger pieces sink if the resin is too warm and the smaller pieces remain in suspension.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Plan what you want to include in a given dice set before starting to mix the resin. I find that 40ml (20ml each of resin and hardener) is just right for a single pour of the mold linked above. Then it&#x27;s just a matter of following the directions for the resin. After mixing, you can split the resin into several different disposable cups if you&#x27;d like to pour different colors together.&lt;&#x2F;p&gt;
&lt;p&gt;Once the dice are hard, unmold them and be amazed! If you want the numbers to be visible, consider flooding them with whatever paint you have on hand, then wiping off the excess with a paper towel. The molds emboss the numbes into the dice, so the number will be the only paint remaining after you wipe it.&lt;&#x2F;p&gt;
&lt;p&gt;If the dice come out too rough, &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B001BHGC7G&quot;&gt;zona papers&lt;&#x2F;a&gt; (~$12) are popular for sanding to a glass-like finish.&lt;&#x2F;p&gt;
&lt;p&gt;If the dice have huge bubbles, you can fix them with &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B07KK37R42&quot;&gt;UV resin&lt;&#x2F;a&gt; (~$10) and an &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B0953Q7RD7&quot;&gt;ultraviolet bulb&lt;&#x2F;a&gt;. The trick to the UV stuff is making sure that the wavelength required by the resin (405-410nm) is included in the spectrum emitted by the lamp (385-410nm). You may already have a UV lamp around if you do gel nails or resin printing.&lt;&#x2F;p&gt;
&lt;p&gt;I find that bubbles often show up in the corners of dice, and sticking some clear tape to the sides I&#x27;m mending with UV resin helps keep it where it belongs while letting the light in to harden it. I&#x27;ve also gotten some fun effects by painting the inside of the bubble a contrasting color before filling it with UV resin. After repairing bubbles with UV resin, the affected sides often need to be flattened out with a file or coarse sandpaper before polishing with fine sandpaper or zona papers.&lt;&#x2F;p&gt;
&lt;p&gt;In making several sets of increasingly less-bad dice, I&#x27;ve noticed that some techniques seem to yield better outcomes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Start with the resin really hot. I set both prats of the epoxy in a container of almost boiling water before use, then dry them off and measure it out immediately. Hot resin flows better.&lt;&#x2F;li&gt;
&lt;li&gt;When using large glitter, make sure to get some glitter-free resin into the very bottom of each die first, or stir it. Big inclusions have a nasty habit of blocking the resin from getting into the tip of the D4.&lt;&#x2F;li&gt;
&lt;li&gt;Place the mold on a plate or tray before pouring, and do not remove it from the tray until the dice are hard. Bending the mold at all changes the volume of the cavities, which presses resin out then sucks air in.&lt;&#x2F;li&gt;
&lt;li&gt;Smear some resin on the lid before capping the mold, and slowly roll the lid onto the mold. Setting the lid straight down allows air to be trapped under it in the middle.&lt;&#x2F;li&gt;
&lt;li&gt;Over-fill the mold cavities slightly.&lt;&#x2F;li&gt;
&lt;li&gt;Expect heavier inclusions, like large glitter, to sink to the bottom when the resin is poured hot. The pros often wait for the resin to get tacky before pouring part of a die, but that&#x27;s advanced technique and I have yet to try much of it.&lt;&#x2F;li&gt;
&lt;li&gt;Paint can be easily removed from the dice numbers with an ultrasonic cleaner. Don&#x27;t try to clean the dice this way if you want the paint to stay in!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This barely scratches the surface of dice-making, and there are better resources on every topic for becoming an expert, making custom molds, and other advanced topics. Although it&#x27;s very hard to make excellent dice, it&#x27;s shockingly cheap and easy to make mediocre dice, and mediocre dice are often more than adequate to have fun with.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Retroreflectors &amp; Storing Ground Glass</title>
        <published>2023-03-14T00:00:00+00:00</published>
        <updated>2023-03-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2023/03/14/retroreflectors_1/"/>
        <id>https://edunham.net/2023/03/14/retroreflectors_1/</id>
        
        <content type="html" xml:base="https://edunham.net/2023/03/14/retroreflectors_1/">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Retroreflector&quot;&gt;Retroreflectors&lt;&#x2F;a&gt; are fun. I finally got around to picking up some cheap glass blast media today (mine&#x27;s the 40&#x2F;70 grit recycled bottle glass from Harbor Freight &#x2F; Central Pneumatic) and did some testing with various paints and glues that I had lying around. I&#x27;m using it as retroreflective beads. When I hear &quot;beads&quot; I think of things with holes in them for putting on a string, but in this case it means more like beads of condensation -- tiny round blobs. They feel gritty like beach sand, and being made from clear glass, they look like unusually sparkly white sand as well.&lt;&#x2F;p&gt;
&lt;p&gt;Roughly a decade ago I stumbled across a PDF that did a good job of explaining the physics of glass bead retroreflectors, which I can of course no longer find. It was published by some highway department and contained extreme detail about the temperature tolerances for applying retroreflective beads to the fog lines on roads, because the retroreflective properties only work if the glass beads are embedded just over halfway into the paint. If the beads sink too far into the paint they can&#x27;t work their light-bending physics magic, and if they&#x27;re embedded too shallowly into the paint they&#x27;ll easily come un-stuck from it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mental-model&quot;&gt;Mental Model&lt;&#x2F;h1&gt;
&lt;p&gt;Here&#x27;s how I think about bead-type retroreflectors. Physics means that the glass beads basically bounce the light straight back toward where it came from, so the stick figure is holding the flashlight close to its eyes for maximum testing effectiveness over short distances. The beads are obviously not to scale.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;retroreflector-physics-diagram.png&quot; alt=&quot;Retroreflector physics diagram&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The beads have a different index of refraction from the surrounding air. That just means that light bends at a certain angle when it goes from air to glass, and it bends at that angle again when it goes from glass to air on its way out.&lt;&#x2F;p&gt;
&lt;p&gt;The surface of the bead that&#x27;s covered in paint reflects light, perhaps only some of the light depending on the color of the paint. White paint reflects the most light, of course.&lt;&#x2F;p&gt;
&lt;p&gt;The light goes into the bead, bends a little, bounces off the paint, goes almost straight back through the bead the way it came, bends a little when it hits the air, and ends up going more or less straight back toward the light source.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve drawn the light in orange before it bounces off the paint and in yellow after it bounces off, to kind of illustrate how it behaves differently hitting the painted floor when it&#x27;s gone through a retroreflective bead versus when it&#x27;s just gone through the air.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;storing-the-blast-media&quot;&gt;Storing the blast media&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;blast-media-storage-hack.png&quot; alt=&quot;Blast media storage with mylar seal&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The one problem with even 5lbs of ground glass is that it does its utmost to get everywhere. If the bottle is ever inverted with the lid on and the lid gets bumped, the sand-like glass ends up everywhere the next time you take the lid off.&lt;&#x2F;p&gt;
&lt;p&gt;Plastic bottles like this come with a &quot;safety&quot; seal when I buy food stuff like spices in them, and I&#x27;ve never had a problem with those getting all up in the cap and spilling everywhere.&lt;&#x2F;p&gt;
&lt;p&gt;I tried ironing a scrap of mylar food packaging (a fruit snacks bag was most conveniently accessible) onto the plastic bottle, and it stuck nicely! I used my clothes iron on the Cotton setting. Now the beads will stay in the jar until I want to use them again.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;paint-glue-testing&quot;&gt;Paint&#x2F;Glue Testing&lt;&#x2F;h1&gt;
&lt;p&gt;I put various sorts of paint and glue onto popsicle sticks and sprinkled them with the blast media. I then put them on the door of a room with good blackout curtains, took a photo, turned out the lights, and took a flash photo.&lt;&#x2F;p&gt;
&lt;p&gt;I used a commercial plastic retroreflector in the first two photos as a control for how well the DIY ones were working.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how it looked with the lights on:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;samples-ambient-light.png&quot; alt=&quot;Test samples under ambient light&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;From top to bottom, the samples are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Dark green puff paint &#x2F; fabric paint&lt;&#x2F;li&gt;
&lt;li&gt;Black puff paint &#x2F; fabric paint&lt;&#x2F;li&gt;
&lt;li&gt;Superglue&lt;&#x2F;li&gt;
&lt;li&gt;modpodge&lt;&#x2F;li&gt;
&lt;li&gt;clear nail polish&lt;&#x2F;li&gt;
&lt;li&gt;dark red nail polish&lt;&#x2F;li&gt;
&lt;li&gt;1 coat white nail polish, then half white half clear nail polish with beads on the second coat&lt;&#x2F;li&gt;
&lt;li&gt;white nail polish&lt;&#x2F;li&gt;
&lt;li&gt;black nail polish&lt;&#x2F;li&gt;
&lt;li&gt;black acrylic paint&lt;&#x2F;li&gt;
&lt;li&gt;white acrylic paint&lt;&#x2F;li&gt;
&lt;li&gt;metallic gold-ish paint stuff&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Everything except the two-coat enamel test had the glass beads poured directly onto the one layer of the paint or glue. Yes, I labeled modpodge upside down. I wasn&#x27;t planning to take photos at first but then I realized if I was going to post about it I probably should.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how that setup looks in a flash photo from maybe 10&#x27; away, in a dark room:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;samples-flash-distant.png&quot; alt=&quot;Flash photo from distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how it looks from the same distance, still a dark room, but I zoomed in more:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;samples-flash-zoomed.png&quot; alt=&quot;Zoomed flash photo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And here&#x27;s how it looks, still zoomed in, but with the commercial reflector removed:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;samples-flash-no-control.png&quot; alt=&quot;Zoomed flash without control reflector&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;The cheapest glass sandblast media that I could get ahold of works surprisingly well as a retroreflector when applied to white paint. The performance difference between beads on white and beads on darker colors was surprising. The clear glues (superglue and mod podge) worked better than the darker colored paints, which was a bit surprising.&lt;&#x2F;p&gt;
&lt;p&gt;My biggest surprise was how the white sample almost blended in with the white door in ambient lighting, yet stood out so much in the dark.&lt;&#x2F;p&gt;
&lt;p&gt;Further stuff to test with this includes doing the glue thing on a darker background color, and trying various types of clear topcoat over the bead layer. I don&#x27;t think those will work, but then again I did think the black paint might work, so it&#x27;s worth testing.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>DIY Thimble</title>
        <published>2022-04-05T00:00:00+00:00</published>
        <updated>2022-04-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2022/04/05/diy_sewing_thimble/"/>
        <id>https://edunham.net/2022/04/05/diy_sewing_thimble/</id>
        
        <content type="html" xml:base="https://edunham.net/2022/04/05/diy_sewing_thimble/">&lt;p&gt;Over the past couple weeks, my schedule has had a higher than usual concentration of the kind of meetings where one sits off-camera and listens to a presenter talk. Like many engineers who knit in meetings, I find that keeping my hands busy helps me focus. Knitting puts me on the losing side of a battle between &quot;don&#x27;t drop any stitches&quot; and the laws of physics, however, so instead I&#x27;ve been hand sewing quite a bit.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been aware for some time that sewing with a thimble is &lt;a href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;78nuLvUvloU?t=110&quot;&gt;Objectively Better&lt;&#x2F;a&gt; than sewing without, but I somehow made it to adulthood without learning to use a thimble properly, let alone avoid losing one between setting a project down one month and picking it up again the next. I have DIY&#x27;d a lot of various leather thimbles over the years to try to sew with them, but the designs have been a hassle to assemble, non-functional, or both (sewing a leather thimble is a chicken and egg problem: you need one to make one without great inconvenience or pain). However, I have finally stumbled across a design that doesn&#x27;t fail or annoy me in the ways that all the previous ones did.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s embarrassingly easy to make. By embarrassingly, I mean &quot;why didn&#x27;t I figure this out decades ago?!&quot; All you need is some surgical tape, and a piece of leather about as wide as your finger and long enough to wrap over your fingertip.&lt;&#x2F;p&gt;
&lt;p&gt;![image](&#x2F;pictures&#x2F;tape-wrap.png&lt;&#x2F;p&gt;
&lt;p&gt;1. Wrap your finger in a couple layers of surgical tape, sticky side out. You want it tight enough to not fall off too easily, but loose enough to slip on and off later.&lt;&#x2F;p&gt;
&lt;p&gt;![image](&#x2F;pictures&#x2F;leather-overlay.png&lt;&#x2F;p&gt;
&lt;p&gt;2. Put the piece of leather onto your finger over the tape, so it covers the spot you keep accidentally jabbing with the needle when you fail to use a thimble.&lt;&#x2F;p&gt;
&lt;p&gt;![image](&#x2F;pictures&#x2F;final-wrap.png&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Wrap the whole thing with a couple layers of surgical tape, sticky side in.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;![image](&#x2F;pictures&#x2F;finished-thimble.png&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s literally all there is to it. If you don&#x27;t like using leather, cardboard might work, or any plastic that&#x27;s flexible but sturdy enough to be hard to jab a needle through.&lt;&#x2F;p&gt;
&lt;p&gt;The one of these that I made earlier in the week and have been using ever since has molded to the shape of my finger and only gotten more comfortable over time.&lt;&#x2F;p&gt;
&lt;p&gt;![image](&#x2F;pictures&#x2F;before-after.png&lt;&#x2F;p&gt;
&lt;p&gt;P.S. That&#x27;s a heavy silk jacquard, pretty on both sides, 28&quot; wide without good selvedges but &lt;a href=&quot;https:&#x2F;&#x2F;www.fabric.com&#x2F;buy&#x2F;0795939&#x2F;100-silk-jacquard-double-face-medium-blue-orange&quot;&gt;they&lt;&#x2F;a&gt; currently have it on Please Go Away sale for $3.13&#x2F;yard. The fibers burn and smell like silk, and it feels like silk, so I don&#x27;t think they&#x27;re lying about the composition. The selvedges aren&#x27;t too nice, one side is fuzzy and the other seems to have just been cut, but for a price like that I can&#x27;t complain. The orange is more coppery in natural light than the photos make it look on my monitor.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Lumenator</title>
        <published>2022-03-18T00:00:00+00:00</published>
        <updated>2022-03-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2022/03/18/lumenator/"/>
        <id>https://edunham.net/2022/03/18/lumenator/</id>
        
        <content type="html" xml:base="https://edunham.net/2022/03/18/lumenator/">&lt;p&gt;About a year ago, I found out about &lt;a href=&quot;https:&#x2F;&#x2F;www.lesswrong.com&#x2F;posts&#x2F;hC2NFsuf5anuGadFm&#x2F;how-to-build-a-lumenator&quot;&gt;lumenators&lt;&#x2F;a&gt;. The theory is that if you put sunshine-ish amounts of light onto a creature, the creature reacts as it would in sunlight.&lt;&#x2F;p&gt;
&lt;p&gt;So, I built one. It&#x27;s technically brighter than the sun.&lt;&#x2F;p&gt;
&lt;p&gt;Mine is composed of 48 e26 sockets mounted in a 2&#x27;x4&#x27; piece of plywood, with some switches that allow the rows to be turned on and off independently. It currently plugs into the wall, although I might eventually get around to hardwiring it if I ever get serious about cable management.&lt;&#x2F;p&gt;
&lt;p&gt;Each of the 48 sockets is intended to hold an LED bulb of around 2500 lumens. I use a mix of color temperatures, with most bulbs 5000K but some 3000K. This means that with all the bulbs in and all the rows lit, it emits around 120,000 lumens from approximately 0.74 square meters, or about 162,162 lux. According to Wikipedia, sunlight is around 100,000 lux once you account for the atmosphere.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;01-darkness-to-light.png&quot; alt=&quot;Testing the lumenator&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how it looked with all the bulbs in it while I was testing it on my kitchen table. This photo was taken around 3pm in early April, 2021. That&#x27;s midafternoon springtime daylight for comparison, visible out through the window. Yes, I was testing it by sticking the exposed wires into a C13. No, it didn&#x27;t catch on fire. No, you should not try this at home.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve mounted the board of sockets onto the wall above my desk, so turning it on is like opening a variably sunny window. I typically use about 12-24 of the bulbs on a day to day basis, as more can make it bright enough to be slightly uncomfortable.&lt;&#x2F;p&gt;
&lt;p&gt;I put off writing this post for a year because I never actually got around to filling in all 48 of the lamp sockets. As well as a few bulbs being dead on arrival, half a dozen of the bulbs I bought were duds. They made a horrible buzzing noise when powered, so I removed them. Over time I&#x27;ve also swapped out some of the daylight bulbs from the apparatus to light other rooms of my house, and installed ordinary 750-lumen bulbs into those sockets because it turns out that a matrix of light sockets is an extremely convenient place to store extra bulbs.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;sourcing-materials&quot;&gt;Sourcing materials&lt;&#x2F;h1&gt;
&lt;p&gt;The plywood, paint, and romex were left over from other projects.&lt;&#x2F;p&gt;
&lt;p&gt;I had a tough time sourcing affordable e26 sockets. Apparently people don&#x27;t build ridiculous light fixtures from scratch on the cheap very much? I ended up using adapters meant to help you use e26 bulbs in GU24 sockets. The pins meant to interface with the GU24 socket were easy to solder wires onto later, and the cylindrical shape of the adapters made them easy to friction fit into holes in the plywood.&lt;&#x2F;p&gt;
&lt;p&gt;I made a big spreadsheet of price per lumen of e26 LED bulbs available various places, ruled out those whose specs were incomplete or self-contradictory, and got the cheapest bulbs that seemed likely to add up to Too Much Light.&lt;&#x2F;p&gt;
&lt;p&gt;The switches, 6-gang switchplate, and electrical plug came from Habitat For Humanity.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;build-process&quot;&gt;Build Process&lt;&#x2F;h1&gt;
&lt;p&gt;I chose the size based on what plywood I had around. On the plywood, I drew a grid of 96 rectangles. I drilled holes in alternating rectangles of the grid, to maximize space between bulbs, because I was frightened of making the kind of mistake that would cause the whole thing to overheat and catch on fire.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;02-grid-of-holes.png&quot; alt=&quot;Grid of drilled holes&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The holes were drilled with a bit just a hair smaller than the GU24 adapter sockets, so that the sockets would be held in place by friction. A few holes came out too small for the sockets, but were easily enlarged with a rasp.&lt;&#x2F;p&gt;
&lt;p&gt;I also rasped off any noticeable splinters. If I&#x27;d been doing fancy carpentry, I would have sanded the whole thing at this point. I wasn&#x27;t, so I didn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;I painted the board white, for maximum light reflection. I thought about trying to do some kind of mirror or chrome finish behind the bulbs, but that seemed way too hard. If I was building it again, I would look for a plastic mirror and secure it to the plywood before drilling the holes. Or I might try gluing a sheet of mylar onto the wood after drilling the holes but before installing the sockets.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;03-sockets-flush.png&quot; alt=&quot;Sockets installed flush with the board&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The sockets installed flush with the board, by pushing them through the holes from the front side. I made sure to install all of them in the same orientation for each row, though this was actually unnecessary because alternating current doesn&#x27;t care about polarity. To get the sockets flush with the board, I found that it was easiest to fit a row in and then place the whole board face-down on a flat surface and walk on it between the sockets. That way everything ended up even with the floor. If you don&#x27;t like walking on your projects, you could probably achieve a similar effect by carefully hitting it with a hammer.&lt;&#x2F;p&gt;
&lt;p&gt;I then wired the rows of lights to the switches. 3 of the switches control 12 bulbs each, and 2 control 6 bulbs each, because this seemed like the best compromise between using all 5 available switches and getting it to work with the limited quantity of romex that I had available.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;04-wire-stripper-tool.png&quot; alt=&quot;Wire stripper tool from clothespins and razor blades&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I test fit all the wires to make sure I had enough, then soldered them onto the pins of the GU24 adapters. Since I needed to strip off 1&quot; of wire insulation from the middle of a wire a bunch of times, I built a tool for the job, by gluing razor blades to each side of the jaws of a pair of clothes pins. That way I&#x27;d just clip the tool onto the cable where I needed to strip it, spin it around so the blades cut through the insulation, and cut off the inch of insulation with another knife.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;05-soldered-connections.png&quot; alt=&quot;Soldered connections to switch pins&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;06-switch-assembly.png&quot; alt=&quot;6-gang switch assembly&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You&#x27;ll notice in the photos that I secured the switches by screwing them directly to the switch plate instead of putting them in a box. You&#x27;re normally supposed to put switches in boxes, and I might regret this shortcut someday, but I didn&#x27;t have a 6-gang box and it&#x27;s been fine so far.&lt;&#x2F;p&gt;
&lt;p&gt;Mind-numbing amounts of soldering later, I tested the assembly to make sure nothing was shorted, then powered it on to make sure it could light. Before installing it on the wall, I covered all the exposed wiring in electircal tape, mostly because I dislike exposed wires. Also, safety. But you&#x27;d get just as zapped from connecting the poles of any of those exposed sockets on the front as you would by connecting a pair of wires on the back, so worrying about hiding exposed conductors on the back but not the front is mostly aesthetic.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;07-sheathed-wires.png&quot; alt=&quot;Wiring joints sheathed in electrical tape&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I then affixed it to the wall above my desk, using a piece of 2x4 at each short end of the plywood to hold everything well away from the wall. I left the top and bottom open to improve air circulation in case it ran into thermal problems, but in a year of use it hasn&#x27;t caught on fire.&lt;&#x2F;p&gt;
&lt;p&gt;It looks underwhelming in photos, because very bright lights are hard to photograph. This shows how I us it on an ordinary day like today, with a set of 12 5000K bulbs and a set of 6 3000K bulbs lit and the rest remaining off.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;08-the-false-sun.png&quot; alt=&quot;The false sun burning bright&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;costs&quot;&gt;Costs&lt;&#x2F;h1&gt;
&lt;p&gt;If you built one of these today, it&#x27;d cost you:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;about $20 for a 2&#x27;x4&#x27; chunk of plywood&lt;&#x2F;li&gt;
&lt;li&gt;about $40 for a pack of 50 GU24 to E26 adapters&lt;&#x2F;li&gt;
&lt;li&gt;about $160 for 48 2500-lumen e26 bulbs&lt;&#x2F;li&gt;
&lt;li&gt;about $30 for 15 feet of 12 gauge 2-wire romex&lt;&#x2F;li&gt;
&lt;li&gt;maybe $10 for switches and a 6-gang plate at your local Habitat For Humanity?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So you&#x27;d be looking at around $260 for the whole build, unless you had materials on hand or found better deals on materials.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;was-it-worth-it&quot;&gt;Was it worth it?&lt;&#x2F;h1&gt;
&lt;p&gt;I like having my false sun in my office, because I feel like being in bright light helps my brain and body agree that it&#x27;s actually daytime. It also helps me fake a summer sleep schedule when the weather outside suggests wintry hibernation. I notice that being in natural daylight at dusk (even when dusk is at 6pm!) causes me to feel sleepy, whereas being in midday-sunlight light levels circumvents that process.&lt;&#x2F;p&gt;
&lt;p&gt;The false sun was somewhat inconvenient to build and install, and would not be accessible for everyone. I was able to make it because I have woodworking and soldering equipment already available, and because I was able to screw it directly to the wall of my office without penalty.&lt;&#x2F;p&gt;
&lt;p&gt;If I was renting my home, I would have mounted the lightbulb array in a bookcase or other tall piece of furniture instead of attaching it onto the wall. If I was building one of these for use in a rental, I&#x27;d probably keep the holes-in-plywood scheme for supporting the lamp sockets, but I would size it to mount over the top couple shelves of an Ikea Billy or similar cheap bookcase, including some room for airflow.&lt;&#x2F;p&gt;
&lt;p&gt;For other locations in my house, I&#x27;ve become partial to the 5,000-lumen 4&#x27; LED &quot;shop&quot; lights that my local Harbor Freight sometimes has on sale for $20 apiece. I find them trivially easy to install and plenty bright to send most of that &quot;it&#x27;s daytime!&quot; signal. However, they&#x27;re over twice as expensive per lumen compared to the DIY version.&lt;&#x2F;p&gt;
&lt;p&gt;You probably shouldn&#x27;t build exactly what I did, as it&#x27;s bulky and inconvenient and not all that aesthetically pleasing. But I hope this project demonstrates that it&#x27;s perfectly achieveable to create a light fixture brighter than the sun! When playing with electricity, please learn what you&#x27;re doing beforehand, be mindful that alternating current can kill you, and generally use common sense. If you try to build or modify something like this without having a basic idea of electrical safety, you&#x27;re just sticking a fork in an outlet but with extra steps.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Playing Dress-Up With Starlink</title>
        <published>2022-03-11T00:00:00+00:00</published>
        <updated>2022-03-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2022/03/11/starlink_dressup/"/>
        <id>https://edunham.net/2022/03/11/starlink_dressup/</id>
        
        <content type="html" xml:base="https://edunham.net/2022/03/11/starlink_dressup/">&lt;p&gt;Some friends showed me &lt;a href=&quot;https:&#x2F;&#x2F;seclists.org&#x2F;nanog&#x2F;2022&#x2F;Mar&#x2F;63&quot;&gt;a post&lt;&#x2F;a&gt; investigating whether Starlink dishes still work when decorated in various ways. They asked whether I was able to reproduce the results. So I pulled the dish down off my roof and tested it with a few things that I had lying around the house and yard.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;methodology-notes&quot;&gt;Methodology Notes:&lt;&#x2F;h1&gt;
&lt;p&gt;I gathered data in my camera roll: a snapshot of the setup, followed by a screencap of the &quot;router &amp;lt;-&amp;gt; internet&quot; pane of the speedtest within the Starlink Android app. I left the dish set up in the same spot, which was at ground level but reported no obstructions when I did the sky scan thing in the app. I power cycled the dish once during the experiment, roughly halfway through the control tests for the &quot;is it slower?&quot; question, because I needed to plug something else into the extension cord that it was using for a minute.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m located south of the 45th parallel.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;will-it-send&quot;&gt;Will It Send?&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;starlink_dressedup.png&quot; alt=&quot;image&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;For my first batch of tests, I was curious whether I could get any data in and out via Starlink with various configurations of stuff on and around the dish. I only ran one speedtest each for these.&lt;&#x2F;p&gt;
&lt;p&gt;I started by suspending things over Starlink, because I knew some of the materials would get heavy (such as wet cotton cloth) and I was scared of hurting the little motors inside that it uses to move itself around. I draped a canvas tarp over the north side of a fence to make an impromptu photo studio for the dish. The dish had an unobstructed view of the sky upward and to the north. I placed metal folding chairs to the east and west of the dish, with their backs closest to the dish, to hold up the various covers. I used some old brake pads to weigh down the things that I draped over the chairs so they wouldn&#x27;t blow away.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;No cover, control to make sure Starlink works in this position. It sends. 146 Mbps down, 15 Mbps up.&lt;&#x2F;li&gt;
&lt;li&gt;Covered with clear-ish greenhouse plastic. It sends. 148 Mbps down, 15 Mbps up.&lt;&#x2F;li&gt;
&lt;li&gt;Same as (2) but with a cotton bedspread over the plastic. It sends. 170 Mbps down, 8 Mbps up.&lt;&#x2F;li&gt;
&lt;li&gt;Same as (3) but with the bedspread soaking wet. It does not send. The app reports &quot;offline, obstructed&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;Plastic from (2) plus a single layer of corrugated cardboard box. It sends. 62 Mbps down, 9 Mbps up.&lt;&#x2F;li&gt;
&lt;li&gt;Same as (5) but I dumped some water on the box. It mostly ran off. It sends. 65 Mbps down, 9 Mbps up.&lt;&#x2F;li&gt;
&lt;li&gt;Double layer of row cover, like you put on plants in the garden to keep the frost from hurting them. It sends. 94 Mbps down, 11 Mbps up.&lt;&#x2F;li&gt;
&lt;li&gt;Single layer of ratty old woven plastic tarp. It sends. 109 Mbps down, 6 Mbps up.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;For the final 2 tests, I got a bit braver about putting things directly on the dish, instead of just suspending them above it.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Dish in a black plastic trash bag like you&#x27;re throwing it out. It sends. 147 Mbps down, 10 Mbps up.&lt;&#x2F;li&gt;
&lt;li&gt;Same tarp as from (8), but directly on the dish instead of hanging above it. It sends. 207 Mbps down, 20 Mbps up.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;is-it-slower&quot;&gt;Is It Slower?&lt;&#x2F;h1&gt;
&lt;p&gt;As you&#x27;ll notice in the numbers that I was getting in the above trials, sometimes covering the dish yields a faster single speed test than having it exposed. That seems wrong. I suspect that this is normal variance based on what satellites are available, but I don&#x27;t actually know what behavior is normal. So I did a few more tests in configuration 10, and a bunch of tests in configuration 1, to make sure we&#x27;re not in a timeline where obstructing a radio link somehow makes it perform better.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Mbps Down&lt;&#x2F;th&gt;&lt;th&gt;Mbps Up&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;207&lt;&#x2F;td&gt;&lt;td&gt;20&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;173&lt;&#x2F;td&gt;&lt;td&gt;5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;138&lt;&#x2F;td&gt;&lt;td&gt;11&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;124&lt;&#x2F;td&gt;&lt;td&gt;13&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;180&lt;&#x2F;td&gt;&lt;td&gt;19&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;101&lt;&#x2F;td&gt;&lt;td&gt;9&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Plastic Tarp Covering Starlink (10)&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Mbps Down&lt;&#x2F;th&gt;&lt;th&gt;Mbps Up&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;104&lt;&#x2F;td&gt;&lt;td&gt;24&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;114&lt;&#x2F;td&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;119&lt;&#x2F;td&gt;&lt;td&gt;10&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;100&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;111&lt;&#x2F;td&gt;&lt;td&gt;14&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;147&lt;&#x2F;td&gt;&lt;td&gt;5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;147&lt;&#x2F;td&gt;&lt;td&gt;5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;97&lt;&#x2F;td&gt;&lt;td&gt;15&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;218&lt;&#x2F;td&gt;&lt;td&gt;15&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;87&lt;&#x2F;td&gt;&lt;td&gt;15&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;176&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;192&lt;&#x2F;td&gt;&lt;td&gt;6&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;125&lt;&#x2F;td&gt;&lt;td&gt;6&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Unobstructed Starlink (1)&lt;&#x2F;p&gt;
&lt;p&gt;I left it all out overnight, and the canvas tarp that I&#x27;d been using as a visual backdrop blew off of the fence so it was covering the dish. I made a video call on the connection with the canvas over the dish and didn&#x27;t notice any subjective degradation of service compared to what I&#x27;m accustomed to getting from it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;Starlink definitely still works when I put dry textiles between the dish and the satellite. It doesn&#x27;t seem to matter if the textiles are directly on the dish or suspended a couple feet above it.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m surprised that Eric Kuhnke&#x27;s Starlink worked with 2 layers of wet bedsheet over it. The only case in which I managed to cause my Starlink to report itself as being obstructed was when I had a wet bedspread suspended in the air over it. I don&#x27;t really want to start piling wet cloth directly on the dish, though, because wet cloth is heavy and I&#x27;m scared of overloading the actuators.&lt;&#x2F;p&gt;
&lt;p&gt;Starlink gets kind of warm during normal operation, as demonstrated by the &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;pics&#x2F;comments&#x2F;ru20n1&#x2F;starlink_works_great_until_the_cats_find_out_that&#x2F;&quot;&gt;cats&lt;&#x2F;a&gt;. I wouldn&#x27;t want to leave mine in a dark colored trash bag or under a dark colored tarp on a sunny day in case it overheated. And there&#x27;s no need to -- if you don&#x27;t want people to know you&#x27;re using one, you can just suspend an opaque and waterproof tarp above it and there&#x27;ll be better airflow around the dish itself and thus less risk of overheating.&lt;&#x2F;p&gt;
&lt;p&gt;Have fun!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>tree-style tab setup</title>
        <published>2022-02-11T00:00:00+00:00</published>
        <updated>2022-02-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2022/02/11/tree_style_tab_setup/"/>
        <id>https://edunham.net/2022/02/11/tree_style_tab_setup/</id>
        
        <content type="html" xml:base="https://edunham.net/2022/02/11/tree_style_tab_setup/">&lt;p&gt;How to get rid of the top bar in firefox after installing tree style tab:&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;span class=&quot;title-ref&quot;&gt;about:config&lt;&#x2F;span&gt; (accept the risk), search &lt;span class=&quot;title-ref&quot;&gt;toolkit.legacyUserProfileCustomizations.stylesheets&lt;&#x2F;span&gt; and hit the funny looking button to toggle it to true.&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;span class=&quot;title-ref&quot;&gt;about:support&lt;&#x2F;span&gt;, above the fold in the &quot;Application Basics&quot; section, find &lt;span class=&quot;title-ref&quot;&gt;Profile Directory&lt;&#x2F;span&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In that directory, &lt;span class=&quot;title-ref&quot;&gt;mkdir chrome&lt;&#x2F;span&gt;, then create &lt;span class=&quot;title-ref&quot;&gt;userChrome.css&lt;&#x2F;span&gt;, containing:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;#main-window[tabsintitlebar=&amp;quot;true&amp;quot;]:not([extradragspace=&amp;quot;true&amp;quot;]) #TabsToolbar
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  opacity: 0;
&lt;&#x2F;span&gt;&lt;span&gt;  pointer-events: none;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;#main-window:not([tabsintitlebar=&amp;quot;true&amp;quot;]) #TabsToolbar {
&lt;&#x2F;span&gt;&lt;span&gt;    visibility: collapse !important;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>top&#x2F;htop in windows is ctrl+shift+esc</title>
        <published>2022-01-30T00:00:00+00:00</published>
        <updated>2022-01-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2022/01/30/top_htop_in_windows/"/>
        <id>https://edunham.net/2022/01/30/top_htop_in_windows/</id>
        
        <content type="html" xml:base="https://edunham.net/2022/01/30/top_htop_in_windows/">&lt;p&gt;Helping a neighbor with a windows update issue today, I explained that asking a Linux admin to use Windows is like asking a Latin speaker to translate a document from Spanish. Most of the concepts are similar enough that they&#x27;ll be more helpful than a monolingual English speaker, but good guessing is not the same as fluency.&lt;&#x2F;p&gt;
&lt;p&gt;Since it was a problem with &lt;a href=&quot;https:&#x2F;&#x2F;support.microsoft.com&#x2F;en-us&#x2F;topic&#x2F;-we-couldn-t-update-system-reserved-partition-error-installing-windows-10-46865f3f-37bb-4c51-c69f-07271b6672ac&quot;&gt;good directions&lt;&#x2F;a&gt; on how to fix it, the process mostly went smoothly. As I complained to a Windows admin friend afterwards, it was fine except all the slashes in paths were backwards, and I couldn&#x27;t find the command prompt equivalent to Linux&#x27;s &lt;span class=&quot;title-ref&quot;&gt;top&lt;&#x2F;span&gt; or &lt;span class=&quot;title-ref&quot;&gt;htop&lt;&#x2F;span&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;My Windows friend pointed out the obvious solution to me: The windows equivalent to &lt;span class=&quot;title-ref&quot;&gt;top&lt;&#x2F;span&gt; is not a command at all, but rather the task manager GUI. Next time I need &lt;span class=&quot;title-ref&quot;&gt;top&lt;&#x2F;span&gt; or &lt;span class=&quot;title-ref&quot;&gt;htop&lt;&#x2F;span&gt; in Windows, I&#x27;ll try to remember to hit &lt;span class=&quot;title-ref&quot;&gt;ctrl+shift+esc&lt;&#x2F;span&gt; to summon that interface instead.&lt;&#x2F;p&gt;
&lt;p&gt;And next time I&#x27;m searching the web for &quot;windows command prompt top&quot; &quot;windows equivalent of top command&quot;, and other queries that assume it&#x27;ll let me live in the terminal like my preferred operating systems, I might just end up back on this very post. Hi, future me!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>transcription with mplayer and i3</title>
        <published>2021-06-12T00:00:00+00:00</published>
        <updated>2021-06-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2021/06/12/transcription/"/>
        <id>https://edunham.net/2021/06/12/transcription/</id>
        
        <content type="html" xml:base="https://edunham.net/2021/06/12/transcription/">&lt;p&gt;I recently wanted to manually transcribe an audio recording. I prefer to type into LibreOffice Writer for this purpose. Writer has an &lt;a href=&quot;https:&#x2F;&#x2F;extensions.libreoffice.org&#x2F;en&#x2F;extensions&#x2F;show&#x2F;transcriber&quot;&gt;audio player plugin&lt;&#x2F;a&gt; for transcription, but unfortunately its keyboard shortcuts didn&#x27;t work when I tried it.&lt;&#x2F;p&gt;
&lt;p&gt;I just want to play some audio in one workspace and have play&#x2F;pause and 5-second rewind shortcuts work even when another window is focused.&lt;&#x2F;p&gt;
&lt;p&gt;Since I am using i3wm on Ubuntu, I can glue up a serviceable transcription setup from stuff that&#x27;s already lying around.&lt;&#x2F;p&gt;
&lt;p&gt;The first challenge is to persuade an audio player to accept commands while it&#x27;s not the window in focus. By complaining about this problem to someone more knowledgeable than myself, I learned about mplayer&#x27;s slave mode. From &lt;a href=&quot;http:&#x2F;&#x2F;www.mplayerhq.hu&#x2F;DOCS&#x2F;tech&#x2F;slave.txt&quot;&gt;its docs&lt;&#x2F;a&gt;, I learn that I can instruct mplayer to take arbitrary commands on a fifo as follows:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ mkfifo &#x2F;tmp&#x2F;mplayerfifo
&lt;&#x2F;span&gt;&lt;span&gt;$ mplayer -slave -input file=&#x2F;tmp&#x2F;mplayerfifo audio-to-transcribe.mp3
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I can test whether mplayer is listening on the fifo. And indeed, the audio pauses when I tell it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ echo pause &amp;gt; &#x2F;tmp&#x2F;mplayerfifo
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this time I also test the incantation to rewind the audio by 5 seconds:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ echo seek -5 &amp;gt; &#x2F;tmp&#x2F;mplayerfifo
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since both commands work as expected, I can now create keyboard shortcuts for them in &lt;code&gt;.i3&#x2F;config&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;bindsym $mod+space exec &amp;quot;echo pause &amp;gt; &#x2F;tmp&#x2F;mplayerfifo&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;bindsym $mod+z exec &amp;quot;echo seek -5 &amp;gt; &#x2F;tmp&#x2F;mplayerfifo&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After writing the config, &lt;code&gt;$mod+shift+c&lt;&#x2F;code&gt; reloads it so i3 knows about the new shortcuts.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, I&#x27;ll make sure this keeps working after I reboot. I&#x27;ll make an alias in my &lt;code&gt;~&#x2F;.bashrc&lt;&#x2F;code&gt; to save having to remember the mplayer incantation:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ echo &amp;quot;alias transcribe=&amp;#39;mplayer -slave -input file=&#x2F;tmp&#x2F;mplayerfifo&amp;quot; &amp;gt;&amp;gt; ~&#x2F;.bashrc
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And to automatically create the fifo once on boot:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ echo &amp;quot;mkfifo &#x2F;tmp&#x2F;mplayerfifo&amp;quot; &amp;gt;&amp;gt; ~&#x2F;.profile 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now after I &lt;code&gt;source ~&#x2F;.bashrc&lt;&#x2F;code&gt;, I can play media with this &lt;code&gt;transcribe&lt;&#x2F;code&gt; alias, and the keyboard shortcuts control it from anywhere in my window manager.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>irssi and libera.chat</title>
        <published>2021-05-25T00:00:00+00:00</published>
        <updated>2021-05-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2021/05/25/irssi_and_libera_chat/"/>
        <id>https://edunham.net/2021/05/25/irssi_and_libera_chat/</id>
        
        <content type="html" xml:base="https://edunham.net/2021/05/25/irssi_and_libera_chat/">&lt;p&gt;I&#x27;m in some channels that are moving from Freenode to Libera.&lt;&#x2F;p&gt;
&lt;p&gt;My irssi runs on a DigitalOcean droplet, and whenever I try to connect to Libera from that instance, I get the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[libera] !tungsten.libera.chat *** Notice -- You need to identify via SASL to use this server
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Libera&#x27;s irssi guide (&lt;a href=&quot;https:&#x2F;&#x2F;libera.chat&#x2F;guides&#x2F;irssi&quot;&gt;https:&#x2F;&#x2F;libera.chat&#x2F;guides&#x2F;irssi&lt;&#x2F;a&gt;) says how to connect with SASL, and down in their sasl docs (&lt;a href=&quot;https:&#x2F;&#x2F;libera.chat&#x2F;guides&#x2F;sasl&quot;&gt;https:&#x2F;&#x2F;libera.chat&#x2F;guides&#x2F;sasl&lt;&#x2F;a&gt;) they mention that SASL is required for IP ranges that are easy to run bots on... including my VPS.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is to pop open an IRC client locally (or use web IRC), connect to Libera without SASL, and register one&#x27;s nick and password. After verifying one&#x27;s email address over the regular connection, the network can be reached via SASL from anywhere using the registered nick as the &lt;span class=&quot;title-ref&quot;&gt;username&lt;&#x2F;span&gt; and the nickserv password as the &lt;span class=&quot;title-ref&quot;&gt;password&lt;&#x2F;span&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Obvious in retrospect, but poorly SEO&#x27;d for how the problem looks at the outset, so that&#x27;s how I worked around problems reaching Libera from Irssi on a VPS.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Assembly Lines</title>
        <published>2021-04-03T00:00:00+00:00</published>
        <updated>2021-04-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2021/04/03/assemblyline/"/>
        <id>https://edunham.net/2021/04/03/assemblyline/</id>
        
        <content type="html" xml:base="https://edunham.net/2021/04/03/assemblyline/">&lt;p&gt;This time last year, my living room was occupied by a cotton mask production facility of my own devising. I had reverse engineered a leftover surgical mask to get the approximate dimensions, consulted pictures of actual surgeons&#x27; masks, and contrived a mask design which was easy-enough to sew in bulk, durable-enough to wash with one&#x27;s linens, and wearable-enough to fit most faces.&lt;&#x2F;p&gt;
&lt;p&gt;Tinkering with and improving the production line was delightful enough to make me wonder if I&#x27;d missed a deeper calling when I chose not to pursue industrial engineering as a career, but the actual work -- the parts where I used myself as jsut another machine to make more masks happen -- was profoundly miserable. At the time, it made more sense to attribute that misery to current events: The world as we knew it is ending, of course I&#x27;m grumpy. I took it for granted that the sewing project of making masks was equivalent to the design&#x2F;prototype&#x2F;build cycle of my more creative sewing endeavors, and assumed that it was supposed to be equally enjoyable.&lt;&#x2F;p&gt;
&lt;p&gt;A year later, however, I&#x27;m running a similar personal assembly line on an electrical project, and noticing some patterns. I have to do 4 steps each on 96 little widgets to complete this phase of the project. My engineering intuition says that the optimal process would be to do all of step 1, then all of step 2, then all of step 3, then all of step 4. That seems like it should be the fastest, and make me happy becuase it&#x27;s the best -- no wasted effort taking out then putting away the set of tools for each step several times.&lt;&#x2F;p&gt;
&lt;p&gt;The large-batch process would also yield consistency across all of its outputs, so that no one widget comes out much worse than any other. Consistency is aesthetic and satisfying in the end result, so the process which yields consistency should feel preferable... but instead, it feels deeply distasteful to stick with any one production phase for too long. What&#x27;s going on there? What assumption is one side of the internal argument using that the other side lacks?&lt;&#x2F;p&gt;
&lt;p&gt;It took me 2 steps over about 24 of the widgets to figure out what felt so wrong about that assembly-line reasoning: The claims of &quot;best&quot; and &quot;fastest&quot; only hold if the process being done remains exactly the same on widget 96 as it was on widget 1. That&#x27;s true if a machine is doing it, but false if the worker is able and allowed to think about the process they&#x27;re working on. Larger batch sizes are optimal if the assembly process is unchanging, but detrimental if the process needs to be modified for efficiency or ease of use along the way. For instance, I&#x27;d initially planned a design that needed about 36&#x27; of wire, but by examining and contemplating the project when it was ready to be wired up, I found a way to accomplish the same goals with only about 23&#x27; of wire. If I&#x27;d been &quot;perfectly efficient&quot; in treating the initial design as perfect, I would likely have cut the wire into the lengths that were needed for the 36&#x27; plan before the 23&#x27; design occurred to me, and that premature optimization would have destroyed the materials I&#x27;d need to assemble the more efficient design once I figured it out.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, a self-modifying assembly line necessarily shrinks the batch size that it&#x27;s worth producing. I&#x27;ve seen the same thing in software -- when automating a process, it&#x27;s best to do it by hand a couple times, and then test a script on a small batch of input and fix any errors, and then apply it to larger and larger batches as it gets closer and closer to the best I can get it. It&#x27;s just easier to notice the phenomenon in a process that uses the hands while leaving the brain mostly free than in processes of more intellectual labor.&lt;&#x2F;p&gt;
&lt;p&gt;And there was the answer as to why attempting to do all 96 of step 1, then all 96 of step 2, felt terrible: Because using the maximum batch size implied that the process was as good as I&#x27;d be able to get it, and that any improvements I might think of while working would be wasted if they weren&#x27;t backwards-compatible with the steps of the old process that were already completed. Smaller batch sizes, then, have an element of hope to them: There will be a &quot;next time&quot; of the whole process, so thinking about &quot;how I&#x27;d do it next time&quot; has a chance to pay off.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>pulseaudio &amp; volumio</title>
        <published>2020-05-30T00:00:00+00:00</published>
        <updated>2020-05-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2020/05/30/pulseaudio_on_volumio/"/>
        <id>https://edunham.net/2020/05/30/pulseaudio_on_volumio/</id>
        
        <content type="html" xml:base="https://edunham.net/2020/05/30/pulseaudio_on_volumio/">&lt;p&gt;The speakers in my living room are hooked up to a raspberry pi that runs &lt;a href=&quot;https:&#x2F;&#x2F;volumio.org&#x2F;&quot;&gt;Volumio&lt;&#x2F;a&gt;. It&#x27;s a nice way to play music from various sources without having to physically reconfigure the speakers between inputs.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;volumio-as-a-pulseaudio-output&quot;&gt;Volumio as a pulseaudio output&lt;&#x2F;h1&gt;
&lt;p&gt;For awhile, my laptop was able to treat Volumio as just another output device, based on the following setup:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the package &lt;span class=&quot;title-ref&quot;&gt;pulseaudio-module-zeroconf&lt;&#x2F;span&gt; was installed on the pi and on every laptop that wants to output audio through the living room speakers&lt;&#x2F;li&gt;
&lt;li&gt;the lines &lt;span class=&quot;title-ref&quot;&gt;load-module module-zeroconf-publish&lt;&#x2F;span&gt; and &lt;span class=&quot;title-ref&quot;&gt;load-module module-native-protocol-tcp&lt;&#x2F;span&gt; were added to &lt;span class=&quot;title-ref&quot;&gt;&#x2F;etc&#x2F;pulseaudio&#x2F;default.pa&lt;&#x2F;span&gt; on the pi&lt;&#x2F;li&gt;
&lt;li&gt;the line &lt;span class=&quot;title-ref&quot;&gt;load-module module-zeroconf-discover&lt;&#x2F;span&gt; was added to &lt;span class=&quot;title-ref&quot;&gt;&#x2F;etc&#x2F;pulse&#x2F;default.pa&lt;&#x2F;span&gt; on my Ubuntu laptop&lt;&#x2F;li&gt;
&lt;li&gt;pulseaudio was restarted on both devices after these changes (&lt;span class=&quot;title-ref&quot;&gt;pulseaudio -k&lt;&#x2F;span&gt; to kill, &lt;span class=&quot;title-ref&quot;&gt;pulseaudio&lt;&#x2F;span&gt; to start it)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;starting-pulseaudio-on-boot-on-volumio&quot;&gt;starting pulseaudio on boot on Volumio&lt;&#x2F;h1&gt;
&lt;p&gt;And then as long as the laptop was connected to the same wifi network as the pi, it Just Worked. Until, in the course of troubleshooting an issue that turned out to involve the laptop having chosen the wrong wifi, I power cycled the pi and it stopped working, because pulseaudio was not yet configured to start on boot.&lt;&#x2F;p&gt;
&lt;p&gt;The solution was to add the following to &lt;span class=&quot;title-ref&quot;&gt;&#x2F;etc&#x2F;systemd&#x2F;system&#x2F;pulseaudio.service&lt;&#x2F;span&gt; on the pi:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[Unit]
&lt;&#x2F;span&gt;&lt;span&gt;Description=PulseAudio system server
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[Service]
&lt;&#x2F;span&gt;&lt;span&gt;Type=notify
&lt;&#x2F;span&gt;&lt;span&gt;Exec=pulseaudio --daemonize=no --system --realtime --log-target=journal
&lt;&#x2F;span&gt;&lt;span&gt;User=volumio
&lt;&#x2F;span&gt;&lt;span&gt;ExecStart=&#x2F;usr&#x2F;bin&#x2F;pulseaudio
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[Install]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then enabling, starting, and troubleshooting any failures to start:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;systemctl --system enable pulseaudio.service
&lt;&#x2F;span&gt;&lt;span&gt;systemctl --system start pulseaudio.service
&lt;&#x2F;span&gt;&lt;span&gt;systemctl status pulseaudio.service -l # explain what went wrong
&lt;&#x2F;span&gt;&lt;span&gt;systemctl daemon-reload # run after editing the .service file
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https:&#x2F;&#x2F;rudd-o.com&#x2F;linux-and-free-software&#x2F;how-to-make-pulseaudio-run-once-at-boot-for-all-your-users&quot;&gt;Rudd-O&#x27;s blog post&lt;&#x2F;a&gt;, which got me 90% of the way to the &quot;start pulseaudio on boot&quot; solution. Apparently systemctl started caring more about having an ExecStart directive since that post was written, which meant I had to inspect the resulting errors, which means I&#x27;m writing down the resulting tidbit of knowledge so that I can find it again later.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;future-work&quot;&gt;future work&lt;&#x2F;h1&gt;
&lt;p&gt;Nobody in my household has yet found a good way to persuade the Windows computer who lives under the TV to speak pulseaudio yet. If I ever figure that out, I&#x27;ll update here.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Moving on from Mozilla</title>
        <published>2020-05-22T00:00:00+00:00</published>
        <updated>2020-05-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2020/05/22/moving_on_from_mozilla/"/>
        <id>https://edunham.net/2020/05/22/moving_on_from_mozilla/</id>
        
        <content type="html" xml:base="https://edunham.net/2020/05/22/moving_on_from_mozilla/">&lt;p&gt;Today -- Friday, May 22nd, 2020 -- is within days of my 5-year anniversary with Mozilla, and it&#x27;s also my last day there for a while. Working at Mozilla has been an amazing experience, and I&#x27;d recommend it to anyone.&lt;&#x2F;p&gt;
&lt;p&gt;There are some things that Mozilla does extremely well, and I&#x27;m excited to spread those patterns to other parts of the industry. And there are areas where Mozilla has room for improvement, where I&#x27;d like to see how others address those challenges and maybe even bring back what I learn to Moz someday.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-go&quot;&gt;Why go?&lt;&#x2F;h1&gt;
&lt;p&gt;When I try to predict what my 2025 or 2030 self will wish I&#x27;d done with my career around now, I anticipate that I&#x27;ll want access to opportunities which build on a background of technical leadership and mentoring junior engineers.&lt;&#x2F;p&gt;
&lt;p&gt;It wouldn&#x27;t be impossible to create these opportunities within Mozilla, but from talking with trusted mentors both inside and outside the company, I&#x27;ve concluded that I would get a lot more impact for the same effort if I was working within a growing organization.&lt;&#x2F;p&gt;
&lt;p&gt;As a mature organization, Mozilla&#x27;s internal leadership needs are very different from those of a younger and more actively growing company. There&#x27;s a far higher bar at Moz for what it takes to be the best person for a task, because the saturation of &quot;best people&quot; is quite high and the prevalence of entirely new tasks is relatively low in comparison. Technical leadership here seems to often require creating a need as well as filling it. At a growing organization, on the other hand, the types and availabilities of such opportunities are very different.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m especially looking forward to leveling up on a different stack in my next role, to improve my understanding of the nuances of the underlying problems our technolgies address. I think it&#x27;s a bit like learning a second language: only through comparing and contrasting multiple solutions to the same sort of problem can one understand what traits corrolate to all those solutions&#x27; strengths versus which details are simply incidental.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-now&quot;&gt;Why now?&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ll be the first to admit that May 2020 is a really strange time to be changing jobs. But I have an annual tradition of interviewing at several places, learning what their stacks and cultures and unique fractals of tech debt look like, and then turning down an offer or two because changing roles would be a step backwards for both my career development and overall quality of life.&lt;&#x2F;p&gt;
&lt;p&gt;Shortly before the global conference circuit ground to a halt along with everything else, I started interviewing from a DevOps Advocate position, just to explore what it might look like to turn my teaching hobby into a day job. By the time those interviews were complete, the tech evangelism space had been turned inside out and was rapidly reinventing itself, and the skills that qualified me for the old world of devrel were looking less and less like the kind of expertise that might be needed to succeed in the new one. However, a SRE from the technical interviews suggested that I interview for her team, and upon taking that advice I discovered an organization that keeps most of the stuff I loved about Mozilla while also offering the other opportunities that I was looking for.&lt;&#x2F;p&gt;
&lt;p&gt;As with anywhere, there are a few aspects of my new role that I suspect may not be as great yet as where I&#x27;m leaving, but these areas of improvement look like things that I&#x27;ll be able to have some influence over. Or at least there&#x27;ll be room to push the Overton Window in a good direction!&lt;&#x2F;p&gt;
&lt;p&gt;Want more details on the new role? I&#x27;ll be writing more about it after I start on June 1st!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Offboarding</title>
        <published>2020-05-22T00:00:00+00:00</published>
        <updated>2020-05-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2020/05/22/offboarding/"/>
        <id>https://edunham.net/2020/05/22/offboarding/</id>
        
        <content type="html" xml:base="https://edunham.net/2020/05/22/offboarding/">&lt;p&gt;Turns out that 5 years at a place gets you a bit of a pile of digital detritus. Future me might want notes on what-all steps I took to remove myself from everything, so here goes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub: Clicking the &quot;pull requests&quot; thing in that bar at the top gives a list of all open PRs created by me. I closed out everything work-related, by either finishing or wontfix-ing it. Additionally, I looked through the list of organizations in the sidebar of my account and kicked myself out of owner permissions that I no longer need. Since my GitHub workflow at Mozilla included a separate account for holding admin perms on some organizations, I revoked all of that account&#x27;s permissions and then deleted it.&lt;&#x2F;li&gt;
&lt;li&gt;Google Drive: (because moving documents around through the Google Docs interface is either prohibitively difficult or just impossible) I moved all notes docs that anyone might ever want again into a shared team folder.&lt;&#x2F;li&gt;
&lt;li&gt;Bugzilla: The &quot;my dashboard&quot; link at the top, when logged in, lists all needinfos and open assigned bugs. I went through all of these and removed the needinfos from closed bugs, changed the needinfos to appropriate people on open bugs, and reassigned assigned bugs to the people who are taking over my old projects. When reassigning, I linked the appropriate notes documents in the bugs and filled in any contextual information that they didn&#x27;t capture. I also checked that my Bugzilla admin had removed all settings to auto-assign me bugs in certain components.&lt;&#x2F;li&gt;
&lt;li&gt;Email deletion prep: I searched for my old work email address in my password manager to find all accounts that were using it. I deleted these accounts or switched them to a personal address, as necessary. It turned out that the only thing I needed to switch over was my Firefox account, which I initially set up to test a feature on a service I supported, but then found very useful.&lt;&#x2F;li&gt;
&lt;li&gt;Git repos: When purging pull requests and bugs, I pushed my latest work from actively developed branches, so that no work will be lost when I wipe my laptop&lt;&#x2F;li&gt;
&lt;li&gt;Assorted other perms: Some developers had granted me access to a repo of secrets, so I contacted them to get that access revoked.&lt;&#x2F;li&gt;
&lt;li&gt;Sharing contact info: I didn&#x27;t send an email to the all-company list, but I did email my contact info to my teammates and other colleagues with whom I&#x27;d like to keep in touch.&lt;&#x2F;li&gt;
&lt;li&gt;Take notes on points of contact. While I still have access to internal wikis, I note the email addresses of anyone I may need to contact if there are problems with my offboarding after my LDAP is decommissioned.&lt;&#x2F;li&gt;
&lt;li&gt;Wipe the laptop: That&#x27;s next. All the repos of Secret Secrets are encrypted on its disk and I&#x27;ll lose the ability to access an essential share of the decryption key when my LDAP account goes away, but it&#x27;s still best practices to wipe hardware before returning it. So I&#x27;ll power it off, boot it from a liveUSB, and then run a few different tools to wipe and overwrite the disk.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Git: moving a module into a monorepo</title>
        <published>2020-02-18T00:00:00+00:00</published>
        <updated>2020-02-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2020/02/18/git_move_module_to_monorepo/"/>
        <id>https://edunham.net/2020/02/18/git_move_module_to_monorepo/</id>
        
        <content type="html" xml:base="https://edunham.net/2020/02/18/git_move_module_to_monorepo/">&lt;p&gt;My team has a repo where we keep all our terraform modules, but we had a separate module off in its own repo for reasons that are no longer relevant.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s call the modules repo &lt;span class=&quot;title-ref&quot;&gt;git@github.com:our-org&#x2F;our-modules.git&lt;&#x2F;span&gt;. The module moving into it, let&#x27;s call it &lt;span class=&quot;title-ref&quot;&gt;git@github.com:our-org&#x2F;postgres-module.git&lt;&#x2F;span&gt;, because it&#x27;s a postgres module.&lt;&#x2F;p&gt;
&lt;p&gt;First, clone both repos.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;git clone git@github.com:our-org&#x2F;our-modules.git
&lt;&#x2F;span&gt;&lt;span&gt;git clone git@github.com:our-org&#x2F;postgres-module.git
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I can&#x27;t just add &lt;span class=&quot;title-ref&quot;&gt;postgres-module&lt;&#x2F;span&gt; as a remote to &lt;span class=&quot;title-ref&quot;&gt;our-modules&lt;&#x2F;span&gt; and pull from it, because I need the files to end up in a subdirectory of &lt;span class=&quot;title-ref&quot;&gt;our-modules&lt;&#x2F;span&gt;. Instead, I have to make a commit to &lt;span class=&quot;title-ref&quot;&gt;postgres-module&lt;&#x2F;span&gt; that puts its files in exactly the place that I want them to land in &lt;span class=&quot;title-ref&quot;&gt;our-modules&lt;&#x2F;span&gt;. If I didn&#x27;t, the &lt;span class=&quot;title-ref&quot;&gt;README.md&lt;&#x2F;span&gt; files from both repos would hit a merge conflict.&lt;&#x2F;p&gt;
&lt;p&gt;So, here&#x27;s how to make that one last commit:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;cd postgres-module
&lt;&#x2F;span&gt;&lt;span&gt;mkdir postgres
&lt;&#x2F;span&gt;&lt;span&gt;git mv *.tf postgres&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;git mv *.md postgres&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;git commit -m &amp;quot;postgres: prepare for move to modules repo&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;cd ..
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notice that I don&#x27;t push that commit anywhere. It just sits on my filesystem, because I&#x27;ll pull from that part of my filesystem instead of across the network to get the repo&#x27;s changes into the modules repo:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;cd our-modules
&lt;&#x2F;span&gt;&lt;span&gt;git remote add pg ..&#x2F;postgres-module&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;git pull pg master --allow-unrelated-histories
&lt;&#x2F;span&gt;&lt;span&gt;git remote rm pg
&lt;&#x2F;span&gt;&lt;span&gt;cd ..
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point, I have all the files and their history from the postgres module in the &lt;span class=&quot;title-ref&quot;&gt;postgres&lt;&#x2F;span&gt; directory of the &lt;span class=&quot;title-ref&quot;&gt;our-modules&lt;&#x2F;span&gt; repo. I can then follow the usual process to PR these changes to the &lt;span class=&quot;title-ref&quot;&gt;our-modules&lt;&#x2F;span&gt; remote:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;cd our-modules
&lt;&#x2F;span&gt;&lt;span&gt;git checkout -b import-pg-module
&lt;&#x2F;span&gt;&lt;span&gt;git push origin import-pg-module
&lt;&#x2F;span&gt;&lt;span&gt;firefox https:&#x2F;&#x2F;github.com&#x2F;our-org&#x2F;our-modules&#x2F;pull&#x2F;new&#x2F;import-pg-module
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We eventually ended up to skip importing the history on this module, but figuring out how to do it properly was still an educational exercise.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Finding a lost Minecraft base</title>
        <published>2020-01-04T00:00:00+00:00</published>
        <updated>2020-01-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2020/01/04/finding_lost_minecraft_base/"/>
        <id>https://edunham.net/2020/01/04/finding_lost_minecraft_base/</id>
        
        <content type="html" xml:base="https://edunham.net/2020/01/04/finding_lost_minecraft_base/">&lt;p&gt;I happen to administer a tiny, mostly-vanilla Minecraft server. The other day, I was playing there with some friends at a location out in the middle of nowhere. I slept in a bed at the base, thinking that would suffice to get me back again later.&lt;&#x2F;p&gt;
&lt;p&gt;After returning to spawn, installing a warp plugin (and learning that &lt;span class=&quot;title-ref&quot;&gt;&#x2F;warp&lt;&#x2F;span&gt; comes from Essentials), rebooting the server, and teleporting to some other coordinates to install their warps, I tried killing my avatar to return it to its bed. Instead of waking up in bed, it reappeared at spawn. Since my friends had long ago signed off for the night, I couldn&#x27;t just teleport to them. And I hadn&#x27;t written down the base&#x27;s coordinates. How could I get back?&lt;&#x2F;p&gt;
&lt;p&gt;Some digging in the docs revealed that there does not appear to be any console command to get a server to disclose the last seen location, or even the bed location, of an arbitrary player to an administrator. However, the server must know something about the players, because it will usually remember where their beds were when they rejoin the game.&lt;&#x2F;p&gt;
&lt;p&gt;On the server, there is a &lt;span class=&quot;title-ref&quot;&gt;world&#x2F;playerdata&#x2F;&lt;&#x2F;span&gt; directory, containing one file per player that the server has ever seen. The file names are the player UUIDs, which can be pasted into &lt;a href=&quot;https:&#x2F;&#x2F;minecraft-techworld.com&#x2F;uuid-lookup-tool&quot;&gt;this tool&lt;&#x2F;a&gt; to turn them into usernames. But I skipped the tool, because the last modified timestamps on the files told me which two belonged to the friends who had both been at our base. So, I copied a &lt;span class=&quot;title-ref&quot;&gt;.dat&lt;&#x2F;span&gt; file that appeared to correspond to a player whose location or bed location would be useful to me. Running &lt;span class=&quot;title-ref&quot;&gt;file&lt;&#x2F;span&gt; on the file pointed out that it was gzipped, but unzipping it and checking the result for anything useful with &lt;span class=&quot;title-ref&quot;&gt;strings&lt;&#x2F;span&gt; yielded nothing comprehensible.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;minecraft.gamepedia.com&#x2F;Player.dat_format&quot;&gt;The wiki&lt;&#x2F;a&gt; reminded me that the &lt;span class=&quot;title-ref&quot;&gt;.dat&lt;&#x2F;span&gt; was NBT-encoded. The recommended NBT Explorer tool appeared to require a bunch of Mono runtime stuff to be compatible with Linux, so instead I grabbed some code that claimed to be a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;twoolie&#x2F;NBT&quot;&gt;Python NBT wrapper&lt;&#x2F;a&gt; to see if it would do anything useful. With some help from its examples, I retrieved the player&#x27;s bed location:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;from nbt import *
&lt;&#x2F;span&gt;&lt;span&gt;n = nbt.NBTFile(&amp;quot;myfile.dat&amp;quot;,&amp;#39;rb&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;print(&amp;quot;x=%s, y=%s, z=%s&amp;quot; % (n[&amp;quot;SpawnX&amp;quot;], n[&amp;quot;SpawnY&amp;quot;], n[&amp;quot;SpawnZ&amp;quot;]))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Teleporting to those coordinates revealed that this was indeed the player&#x27;s bed, at the base I&#x27;d been looking all over for!&lt;&#x2F;p&gt;
&lt;p&gt;The morals of this story are twofold: First, I should not quit writing down coordinates I care about on paper, and second, Minecraft-adjacent programming is still not my idea of a good time.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Toy hypercube construction</title>
        <published>2019-12-30T00:00:00+00:00</published>
        <updated>2019-12-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2019/12/30/toy-hypercube/"/>
        <id>https://edunham.net/2019/12/30/toy-hypercube/</id>
        
        <content type="html" xml:base="https://edunham.net/2019/12/30/toy-hypercube/">&lt;p&gt;I think hypercubes are neat, so I tried to make one out of string to play with. In the process, I discovered that there are surprisingly many ways to fail to trace every edge of a drawing of a hypercube exactly once with a single continuous line.&lt;&#x2F;p&gt;
&lt;p&gt;This puzzle felt like the sort of problem that some nerd had probably solved before, so I searched the web and discovered that the shape I was trying to configure the string into is called an Eulerian Cycle.&lt;&#x2F;p&gt;
&lt;p&gt;I learned that any graph in which every vertex attaches to an even number of edges has such a cycle, which is useful for my craft project because the euler cycle is literally the path that the string needs to take to make a model of the object represented by the graph.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mathematical-materials&quot;&gt;Mathematical materials&lt;&#x2F;h1&gt;
&lt;p&gt;To construct a toy hypercube or any other graph, you need the graph. To make it from a single piece of string, every vertex should have an even number of edges.&lt;&#x2F;p&gt;
&lt;p&gt;Knowing the number of edges in the graph will be useful later, when marking the string.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;physical-materials&quot;&gt;Physical materials&lt;&#x2F;h1&gt;
&lt;p&gt;For the edges of the toy, I wanted something that&#x27;s a bit flexible but can sort of stand up on its own. I found that cotton clothesline rope worked well: it&#x27;s easy to mark, easy to pin vertex numbers onto, and sturdy but still flexible. I realized after completing the construction that it would have been clever to string items like beads onto the edges to make the toy prettier and identify which edge is which.&lt;&#x2F;p&gt;
&lt;p&gt;For the vertices, I pierced jump rings through the rope, then soldered them shut, to create flexible attachment points. This worked better than a previous prototype in which I used flimsier string and made the vertices from beads.&lt;&#x2F;p&gt;
&lt;p&gt;Vertices could be knotted, glued, sewn, or safety pinned. A bookbinding awl came in handy for making holes in the rope for the rings to go through.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mathematical-construction&quot;&gt;Mathematical construction&lt;&#x2F;h1&gt;
&lt;p&gt;First, I drew the graph of the shape I was trying to make -- in this case, a hypercube. I counted its edges per vertex, 4. I made sure to draw each vertex with spots to write numbers in, half as many numbers as there are edges, because each time the string passes through the vertex it makes 2 edges. So in this case, every vertex needs room to write 2 numbers on it.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the graph I started with. I drew the edges in a lighter color so I could see which had already been visited when drawing in the euler cycle.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;01-graph-initial.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Then I started from an arbitrary vertex and drew in the line. Any algorithm for finding euler paths will suffice to draw the line. The important part of tracing the line on the graph is to mark each vertex it encounters, sequentially. So the vertex I start at is 1, the first vertex I visit is 2, and so forth.&lt;&#x2F;p&gt;
&lt;p&gt;Since the euler path visits every vertex of my particular hypercube twice, every vertex will have 2 numbers (the one I started at will have 3) when I finish the math puzzle. These pairs of numbers are what tell me which part of the string to attach to which other part.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s what my graph looked like once I found an euler cycle in it and numbered the vertices that the cycle visited:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;02-graph-euler-numbered.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;physical-construction&quot;&gt;Physical construction&lt;&#x2F;h1&gt;
&lt;p&gt;Since my graph has 32 edges, I made 33 evenly spaced marks on the string. I used an index card to measure them because that seemed like an ok size, but in retrospect it would have been fine if I&#x27;d made it smaller.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;03-rope-marked.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I then numbered each mark in sequence, from 1 to 33. I numbered them by writing the numbers on slips of paper and pinning the papers to the rope, but if I was using a ribbon or larger rope, the numbers could have been written directly on it. If you&#x27;re doing this at home, you could mark the numbers on masking tape on the rope just as well.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;04-rope-numbered.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The really tedious step is applying the vertices. I just went through the graph, one vertex at a time, and attached the right points on the string together for it.&lt;&#x2F;p&gt;
&lt;p&gt;The first vertex had numbers 1, 25, and 33 on it for the euler cycle I drew and numbered on the graph, so I attached the string&#x27;s points 1, 25, and 33 together with a jump ring. The next vertex on the drawing had the numbers 2 and 18 on it, so I pierced together the points on the string that were labeled 2 and 18.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t think it matters what order the vertices are assembled in, as long as the process ultimately results in all the vertices on the graph being represented by rings affixing the corresponding points on the string together.&lt;&#x2F;p&gt;
&lt;p&gt;I also soldered the rings shut, because after all that work I don&#x27;t want them falling out.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;05-vertices-assembling.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s all there is to it!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;07-hypercube-flattened.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m going to have to find a faster way to apply the vertices before attempting a 6D hypercube. An ideal vertex would allow all edges to rotate and reposition themselves freely, but failing that, a lighter weight string and crimp fasteners large enough to hold 6 pieces of that string might do the trick.&lt;&#x2F;p&gt;
&lt;p&gt;The finished toy is not much to look at, but quite amusing to try to flatten into 3-space.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;06-hypercube-finished.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>kubectl unable to recognize STDIN</title>
        <published>2019-09-17T00:00:00+00:00</published>
        <updated>2019-09-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2019/09/17/kubectl_unable_to_recognize_stdin/"/>
        <id>https://edunham.net/2019/09/17/kubectl_unable_to_recognize_stdin/</id>
        
        <content type="html" xml:base="https://edunham.net/2019/09/17/kubectl_unable_to_recognize_stdin/">&lt;p&gt;Or, Stupid Error Of The Day. I&#x27;m talking to a GCP&#x27;s Kubernetes engine through several layers of intermediate tooling, and kubectl is failing:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;subprocess.CalledProcessError: Command &amp;#39;[&amp;#39;kubectl&amp;#39;, &amp;#39;apply&amp;#39;, &amp;#39;--record&amp;#39;, &amp;#39;-f&amp;#39;, &amp;#39;-&amp;#39;]&amp;#39; returned non-zero exit status 1.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Above that, in the wall of other debug info, is an error of the form:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;error: unable to recognize &amp;quot;STDIN&amp;quot;: Get https:&#x2F;&#x2F;11.22.33.44&#x2F;api?timeout=32s: dial tcp 11.22.33.44:443: i&#x2F;o timeout
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This error turned out to have such a retrospectively obvious fix that nobody else seems to have published it.&lt;&#x2F;p&gt;
&lt;p&gt;When setting up the cluster on which kubectl was failing, I added the IP from which my tooling would access it, and hit the &quot;done&quot; button to save my changes. (That&#x27;s under the Authorized Networks section in &quot;kubernetes engine -&amp;gt; clusters -&amp;gt; edit cluster&quot; if you&#x27;re looking for it in the GCP console.) However, the &quot;done&quot; button is only one of the two required steps to save changes: One also must scroll all the way to the bottom of the page and press the &quot;save&quot; button there.&lt;&#x2F;p&gt;
&lt;p&gt;So if you&#x27;re here because you Googled that error, go recheck that you really do have access to the cluster on which you&#x27;re trying to kubectl apply. Good luck!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>What to bring to CCC Camp next time</title>
        <published>2019-08-26T00:00:00+00:00</published>
        <updated>2019-08-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2019/08/26/ccc_camp/"/>
        <id>https://edunham.net/2019/08/26/ccc_camp/</id>
        
        <content type="html" xml:base="https://edunham.net/2019/08/26/ccc_camp/">&lt;p&gt;I took last week off work and attended CCC camp, which was wonderful on a variety of axes. I packed light, but through the week I noted some things it&#x27;d be worth packing less-lightly for.&lt;&#x2F;p&gt;
&lt;p&gt;So, here are my notes on what it&#x27;d be worth bringing if or when I attend it again:&lt;&#x2F;p&gt;
&lt;h1 id=&quot;clothing&quot;&gt;Clothing&lt;&#x2F;h1&gt;
&lt;p&gt;The site is dusty, extremely hot through the day, and quite cold at night. Fashion ranges from &quot;generic nerd&quot; to hippie, rave, and un-labelably eccentric. There is probably no wrong thing to wear, though I didn&#x27;t see a single suit or tie. A full base layer and a silk sleeping bag liner improve comfort at night. A big hat, or even an umbrella, offers protection from the day star.&lt;&#x2F;p&gt;
&lt;p&gt;I was glad to have 3 pairs of shoes: Lightweight waterproof sandals for showering, sturdier sandals for walking around in all day, and boots for early mornings and late nights. I saw quite a few long coats and even cloaks at night, and their inhabitants all looked very comfortably warm.&lt;&#x2F;p&gt;
&lt;p&gt;Doing sink laundry was more inconvenient at camp than for ordinary travel, and I was glad to have packed to minimize it.&lt;&#x2F;p&gt;
&lt;p&gt;A small comfortable bag, or large pockets in every outfit, are essential for keeping track of one&#x27;s wallet, phone, map, and water bottle.&lt;&#x2F;p&gt;
&lt;p&gt;I occasionally found myself wishing that I&#x27;d brought a washable dust mask, usually around midafternoon when camp became one big dust cloud.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;campsite-amenities&quot;&gt;Campsite Amenities&lt;&#x2F;h1&gt;
&lt;p&gt;Covering a tent in space blankets makes it look like a baked potato, but keeps it warm and dark at night and cool through early afternoon. Space blankets are super cheap online, but difficult to find locally.&lt;&#x2F;p&gt;
&lt;p&gt;For a particularly opulent tent experience, consider putting a doormat outside the entrance as a place to remove shoes or clean dusty feet before going inside. I improvised a doormat with a trash bag, which was alright but the real thing would have been nicer to sit on.&lt;&#x2F;p&gt;
&lt;p&gt;Biertisch tables and benches are prevalent around camp, so you can usually find somewhere to sit, but it doesn&#x27;t hurt to bring a camp chair of the folding or inflatable variety. Inflatable stuff, from furniture to swimming pools, tended to survive fine on the ground.&lt;&#x2F;p&gt;
&lt;p&gt;I was glad to have brought a full sized towel rather than a tiny travel one. A shower caddy or bag to carry soap, washcloth, hair stuff, and clean clothes would have been handy, though I improvised one from another bag that I had available.&lt;&#x2F;p&gt;
&lt;p&gt;String and duct tape came in predictably handy in customizing my campsite.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;electronics&quot;&gt;Electronics&lt;&#x2F;h1&gt;
&lt;p&gt;DECT phones are very fun at camp, but easy to pocket dial with. This is solved by finding the lock feature on the keypad, or picking a flip phone. I was shy about publishing my number and location in the phonebook, but after seeing how helpful the directory was for people to get ahold of new acquaintances for important reasons, I would be more public about my temporary number in the future.&lt;&#x2F;p&gt;
&lt;p&gt;Electricity is a limited resource but sunlight isn&#x27;t. Many tents sport portable solar panels. For those whose electronics have non-European plugs, a power strip from home is a good idea.&lt;&#x2F;p&gt;
&lt;p&gt;I packed a small headlamp and used it pretty much every day. Even with it, I found myself occasionally wishing that I&#x27;d brought a small LED lantern as well.&lt;&#x2F;p&gt;
&lt;p&gt;A battery to recharge cell phones is good to have as well, especially if you don&#x27;t run power to your tent. A battery can be left charging unattended in all kinds of public places where one would never leave one&#x27;s phone or laptop.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;food&quot;&gt;Food&lt;&#x2F;h1&gt;
&lt;p&gt;Potable water is free, both still and sparkling. Perhaps I&#x27;ve been spoiled by the quality of the tap water at my home, but I wished that I&#x27;d brought water flavorings to mask the local combination of minerals.&lt;&#x2F;p&gt;
&lt;p&gt;I brought a small medical kit, from which I ended up using or sharing some aspirin, ibuprofin, antihistamines, and lots of oral rehydration salt packets.&lt;&#x2F;p&gt;
&lt;p&gt;Meals were available for free (with donations gratefully accepted) at several camps for everyone, and at the Heaven kitchen for volunteers. There were also a variety of food carts with varyingly priced dishes. The food carts outside the gates in front of the venue were good for an icecream or fresh veggie snack, which were harder to find within camp.&lt;&#x2F;p&gt;
&lt;p&gt;Savory meals and all kinds of drinks were everywhere, but there didn&#x27;t seem to be any place nearby to just pick up straight chocolate. Small, nonperishable snacks like that are worth getting at a grocery before arrival, since they&#x27;re not readily available on the grounds.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;other&quot;&gt;Other&lt;&#x2F;h1&gt;
&lt;p&gt;If any of the special skills that your nerd friends ask you for help with require tools, bring them. I happen to always carry a needle and thread when traveling, and ended up using them to repair a giant inflatable computer-controlled sculpture.&lt;&#x2F;p&gt;
&lt;p&gt;A hammock, and something to shade it with, came in very handy and would be worth bringing again. There were lots of trees, and it might have been entertaining to set up a slackline for passers-by to fall off of, but I don&#x27;t think it&#x27;d be worth the weight of carrying one internationally.&lt;&#x2F;p&gt;
&lt;p&gt;Night time is basically a futuristic art show as well as a party. There&#x27;s no such thing as too much electroluminescent wire or too many LEDs, whether for decorating your camp or yourself. As a music party, it&#x27;s also extremely loud, so I was glad to have brought earplugs. Comfortable earplugs also improve sleep; music goes till 3 or 4 AM in many places and early risers start making noise around 8 or 9.&lt;&#x2F;p&gt;
&lt;p&gt;Camp has a lake, in which it&#x27;s popular to float large inflatable animals, especially unicorns. I saw more big inflatable unicorn floaties being used around camps as extra seating than being used in the lake, though.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a railway that goes around camp, and sometimes runs a steam train. I won&#x27;t say you &lt;em&gt;should&lt;&#x2F;em&gt; rig a little electric cart to fit its rails and drive around on it, but somebody did and looked like they were having a really wonderful time.&lt;&#x2F;p&gt;
&lt;p&gt;Bikes, and lots of folding bikes, were everywhere. Scooters, skateboards, and all sorts of other wheeled contrivances, often electric, were also prevalent. The only rolling transportation that I didn&#x27;t see at all around camp were roller blades and skates, because the ground is probably too rough for them.&lt;&#x2F;p&gt;
&lt;p&gt;I ran out of stickers, and wished I&#x27;d brought more. I didn&#x27;t see as many pins as some conferences have.&lt;&#x2F;p&gt;
&lt;p&gt;A small notebook also came in handy. Each day, I checked both the stage schedule and the calendar to find the official and unofficial events which looked interesting, and noted their times on paper. It was consistently convenient to have a means of jotting down notes which didn&#x27;t risk running out of battery. Flipping through the book afterwards, about 1&#x2F;4 of its contents is actually pictures I drew to explain various concepts to people I was chatting with, a few pages are daily schedule notes, and the rest is about half notes on things that presenters said and half ideas I jotted down to do something with later.&lt;&#x2F;p&gt;
&lt;p&gt;I was glad to have brought cash rather than just cards, not only for food but also because many workshops had a small fee to cover the cost of the materials that they provided.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;camp-advice&quot;&gt;Camp Advice&lt;&#x2F;h1&gt;
&lt;p&gt;Nobody even tries to maintain a normal sleep schedule. People sleep when they&#x27;re tired, and do stuff when they aren&#x27;t. Talks and events tend to be scheduled from around noon to around midnight. I don&#x27;t think it would be possible to attend camp with a rigorous plan for what to every day and both stick to that plan and get the most out of the experience.&lt;&#x2F;p&gt;
&lt;p&gt;In shared spaces, people pick the lowest common denominator of language -- at several workshops, even those initially scheduled to be held in German, presenters proactively asked if any attendees needed it to be in English then switched to English if asked. Behind-the-scenes, such as in the volunteers&#x27; kitchen, I found that this was reversed: Everyone speaks German, and only switches to give you instructions if you specifically ask for English. Plenty of attendees have no German at all and get along fine.&lt;&#x2F;p&gt;
&lt;p&gt;Volunteer! If something isn&#x27;t happening how it should, fix it, or ask &quot;how can I help?&quot;. Volunteering an hour or two for filing badges or washing dishes is a great way to make new friends and see another side of how camp works.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>More on Mentorship</title>
        <published>2019-06-24T00:00:00+00:00</published>
        <updated>2019-06-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2019/06/24/describing_mentorship/"/>
        <id>https://edunham.net/2019/06/24/describing_mentorship/</id>
        
        <content type="html" xml:base="https://edunham.net/2019/06/24/describing_mentorship/">&lt;p&gt;Last year, I &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2018&#x2F;08&#x2F;24&#x2F;job_move.html&quot;&gt;wrote&lt;&#x2F;a&gt; about some of the aspirations which motivated my move from Mozilla Research to the CloudOps team. At the recent Mozilla All Hands in Whistler, I had the &quot;how&#x27;s the new team going?&quot; conversation with many old and new friends, and that repetition helped me reify some ideas about what I really meant by &quot;I&#x27;d like better mentorship&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;To generalize about how mentors&#x27; careers affect what they can mentor me on, I&#x27;ve sketched up a quick figure in order to name some possible situations that people can be in relative to one another:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;mentorship-positions.png&quot; alt=&quot;Diagram showing where to find mentors at different career stages&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The first couple cases of mentorship are easy to describe, because I&#x27;ve experienced and thought about them for many years already:&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mentorship-across-industries&quot;&gt;Mentorship across industries&lt;&#x2F;h1&gt;
&lt;p&gt;Mentors from outside my own industry are valuable for high level perspectives, and for advice on general life and human topics that aren&#x27;t specialized to a single field. Additionally, specialists in other industries often represent the consumers of my own industry&#x27;s products. Wise and thoughtful people who share little to none of my domain knowledge can provide constructive feedback on why my industry&#x27;s work gets particular reactions from the people it affects -- just as someone who&#x27;s never read a particular book before is likely to catch more spelling errors than its own author, who&#x27;s been poring over the same manuscript for many hours a day for several years.&lt;&#x2F;p&gt;
&lt;p&gt;However, for more concrete problems within my particular career (&quot;this program is running slower than expected&quot;, or even &quot;how should I describe that role on my resume?&quot;), observers from outside of it can rarely offer a well tested recommendation of a path forward.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mentorship-across-companies-within-an-industry&quot;&gt;Mentorship across companies within an industry&lt;&#x2F;h1&gt;
&lt;p&gt;Similarly, mentors from other companies within my own industry are my go-to source of insight on general trends and technologies. A colleague in a distant corner of my field can tell me about the frustrations they encountered when using a piece of technology that I&#x27;m considering, and I can use that advice to make better-informed choices in my daily work.&lt;&#x2F;p&gt;
&lt;p&gt;But advice on a particular company&#x27;s peculiarities rarely translates well across organizations. A certain frequency of reorganization might be perfectly ordinary at my company, but a re-org might indicate major problems at another. This type of education, while difficult to get from someone at a different company, is perfectly feasible to pick up from anyone on another team within one&#x27;s own organization.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mentorship-across-teams-within-a-company&quot;&gt;Mentorship across teams within a company&lt;&#x2F;h1&gt;
&lt;p&gt;When I switched roles, I had trial-and-errored my way into the observation that there&#x27;s a large class of problems with which mentors from different teams within the same company cannot effectively help. I&#x27;d tentatively call these &quot;junior engineer problems&quot;, as having overcome their general cases seems to correlate strongly to seniority. In my own expeience, honing the improvement of code-adjacent skills such as the intuition for what problems should be effectively solvable from the docs versus when and whom to ask for help, how deeply to explore a prospective course of action before committing to it, and when to write off an experiment as &quot;effectively impossible&quot;, are all questions whose answers one derives from experience and observing expert peers rather than from just asking them with words.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mentorship-across-projects-or-specialties-within-a-team&quot;&gt;Mentorship across projects or specialties within a team&lt;&#x2F;h1&gt;
&lt;p&gt;I had assumed that simply being on the same team as people capable of imparting that highly specialized variant of common sense would suffice to expose me to it. However, my first few projects on my new team have clearly shown, in both the positive and the negative cases, that working on the same project as an expert is far more useful to my own growth than simply chancing to be bureaucracied into the same group.&lt;&#x2F;p&gt;
&lt;p&gt;The negative case was my first pair of projects: The migration of 2 small, simple services from my team&#x27;s AWS infrastructure to GCP. Although I was on the same team as experts in this process, the particular projects were essentially mine alone, and it was up to me to determine how far to proceed on each problem by myself before escalating it to interrupt a busy senior engineer. My heuristics for that process weren&#x27;t great, and I knew that at the outset, but my bias toward asking for help later than was optimal slowed the process of improving my ability to draw that line -- how can one enhance one&#x27;s discrimination between &quot;too soon&quot;, &quot;just right&quot;, and &quot;too late&quot; when all the data points one gathers are in the same one of those categories?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mentorship-within-a-project&quot;&gt;Mentorship within a project&lt;&#x2F;h1&gt;
&lt;p&gt;Finally, however, I&#x27;m in the midst of a project that demonstrates a positive case for the type of mentorship I switched teams to seek. I&#x27;m in the case labeled A on the diagram up above -- I&#x27;m working with a more-experienced teammate on a project which also includes close collaboration with members of another team within our organization. In examining why this is working so much better for me than my prior tasks, I&#x27;ve noticed some differences: First, I&#x27;m getting constant feedback on my own expectations for my work. This is no serious nor bureaucratic process, but simply a series of tiny interactions -- expressions of surprise when I complete a task effectively, or recommendations to move on to a different approach when something seems to take too long. Similarly, code review from someone immersed in the same problem that I&#x27;m working on is indescribably more constructive than review from someone who&#x27;s less familiar with the nuances of whatever objective my code is trying to achieve.&lt;&#x2F;p&gt;
&lt;p&gt;Another reason that I suspect I&#x27;m improving more quickly than before in this particular task is the opportunity to observe my teammate modeling the skills that I&#x27;m learning in his interactions with our colleagues from another team (those in position C on that chart). There&#x27;s always a particular trick to asking a question in a way that elicits the category of answer one actually wanted, and watching this trick done frequently in circumstances where I&#x27;m up to date on all the nuances and details is a great way to learn.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-foss-loophole&quot;&gt;The FOSS loophole&lt;&#x2F;h1&gt;
&lt;p&gt;I suspect I may have been slower to notice these differences than I otherwise might have been, because the start of my career included a lot of fantastic, same-project mentorship from individuals on other teams, at other companies, and even in other industries. This is because my earliest work was on free and open source software and infrastructure. In FOSS, anyone who can pay with their time and computer usage buys access to a cross-company, often cross-industry web of professionals and can derive all the benefits of working directly with mentors on a single project. I was particularly fortunate to draw a wage from the OSU Open Source Lab while doing that work, because the opportunity cost of a hours spent on FOSS by a student who also needs to spend those hours on work is far from free.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Rustacean Hat Pattern</title>
        <published>2019-04-06T00:00:00+00:00</published>
        <updated>2019-04-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2019/04/06/rustacean_hat_pattern/"/>
        <id>https://edunham.net/2019/04/06/rustacean_hat_pattern/</id>
        
        <content type="html" xml:base="https://edunham.net/2019/04/06/rustacean_hat_pattern/">&lt;p&gt;Based on feedback from the &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2016&#x2F;04&#x2F;11&#x2F;plushie_rustacean_pattern.html&quot;&gt;crab plushie pattern&lt;&#x2F;a&gt;, I took more pictures this time.&lt;&#x2F;p&gt;
&lt;p&gt;There are 40 pictures of the process below the fold.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;materials-required&quot;&gt;Materials required&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-01.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;About 1&#x2F;4 yard or 1&#x2F;4 meter of orange fabric. Maybe more if it&#x27;s particularly narrow. Polar fleece is good because it stretches a little and does not fray near seams.&lt;&#x2F;li&gt;
&lt;li&gt;A measuring device. You can just use a piece of string and mark it.&lt;&#x2F;li&gt;
&lt;li&gt;Scissors, a sewing machine, pins, orange thread&lt;&#x2F;li&gt;
&lt;li&gt;Scraps of black and white cloth to make the face&lt;&#x2F;li&gt;
&lt;li&gt;The measurements of the hat wearer&#x27;s head. I&#x27;m using a hat to guess the measurements from.&lt;&#x2F;li&gt;
&lt;li&gt;A pen or something to mark the fabric with is handy.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;constructing-the-pattern-pieces&quot;&gt;Constructing the pattern pieces&lt;&#x2F;h1&gt;
&lt;p&gt;If you&#x27;re using polar fleece, you don&#x27;t have to pre-wash it. Fold it in half. In these pictures, I have the fold on the left and the selvedges on the right.&lt;&#x2F;p&gt;
&lt;p&gt;The first step is to chop off a piece from the bottom of the fleece. We&#x27;ll use it to make the legs and spines later. Basically like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-02.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Next, measure the circumference you want the hat to be. I&#x27;ve measured on a hat to show you.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-03.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Find 1&#x2F;4 of that circumference. If you measured with a string, you can just fold it, like I folded the tape measure. Or you could use maths.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-04.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That quarter-of-the-circumference is the distance that you fold over the left side of the big piece of fabric. Like so:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-05.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Leave it folded over, we&#x27;ll get right back to it. Guesstimate the height that a hat piece might need to be, so that we can sketch a piece of the hat on it. I do this by measuring front to back on a hat and folding the string, I mean tape measure, in half:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-06.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Back on the piece we folded over, put down the measurement so we make sure not to cut the hat too short. That measurement tells you roughly where to draw a curvy triangle on the folded fabric, just like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-07.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now cut it out. Make sure not to cut off that folded edge. Like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-08.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Congratulations, you just cut out the lining of the hat! It should be all one piece. If we unfold the bit we just cut and the bit we cut it from, it&#x27;ll look like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-09.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now we&#x27;re going to use that lining piece as a template to cut the outside pieces. Set it down on the fabric like so:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-10.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And cut around it. Afterwards you have 1 lining piece and 2 outer pieces:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-11.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now grab that black and white scrap fabric and cut a couple eye sized black circles, and a couple bits of white for the light glints on the eyes. Also cut a black D shape to be the mouth if you want your hat to have a happy little mouth as well as just eyes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-12.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;construction&quot;&gt;Construction&lt;&#x2F;h1&gt;
&lt;p&gt;Put the black and white together, and sew an eye glint kind of shape in the same spot on both, like so:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-13.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Pull the top threads to the back so the stitching looks all tidy. Then cut off the excess white fabric so it looks all pretty:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-14.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now the fun part: Grab one of those outside pieces we cut before. Doesn&#x27;t matter which. Sew the eyes andmouth onto it like so:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-15.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now it&#x27;s time to give the hat some shape. On both outside pieces -- the one with the face and also the one without -- sew that little V shaped gap shut. Like so:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-16.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now they look kind of 3D, like so:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-17.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s sew up the lining piece next. It&#x27;s the bit we cut off of the fold earlier. Fold then sew the Vs shut, thusly:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-18.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Next, sew most of the remaining seam of the lining, but leave a gap at the top so we can turn the whole thing inside out later:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-19.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now that the lining is sewn, let&#x27;s sew 10 little legs. Grab that big rectangular strip we cut out at the very beginning, and sew its layers together into a bunch of little triangles with open bottoms. Then cut them apart and turn them inside out to get legs. Here&#x27;s how I did those steps:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-20.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Those little legs should have taken up maybe 1&#x2F;3 of big rectangular strip. With the rest of it, let&#x27;s make some spines to go across Ferris&#x27;s back. They&#x27;re little triangles, wider than the legs, sewn up the same way.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-21.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now put those spines onto one of the outside hat pieces. Leave some room at the bottom, because that&#x27;s where we&#x27;ll attach the claws that we&#x27;ll make later. The spines will stick toward the face when you pin them out, so when the whole thing turns right-side-out after sewing they&#x27;ll stick out.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-22.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Put the back of the outside onto this spine sandwich you&#x27;re building. Make sure the seam that sticks out is on the outside, because the outsides of this sandwich will end up inside the hat.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-23.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Pin and sew around the edge:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-24.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Note how the bottoms of the spines make the seam very bulky. Trim them closer to the seam, if you&#x27;re using a fabric which doesn&#x27;t fray, such as polar fleece.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-25.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The outer layer of the hat is complete!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-26.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;At this point, we remember that Ferris has some claws that we haven&#x27;t accounted for yet. That&#x27;s ok because there was some extra fabric left over when we cut out the lining and outer for the hat. On that extra fabric, draw two claws. A claw is just an oval with a pie slice misisng, plus a little stem for the arm. Make sure the arms are wide enough to turn the claw inside out through later. It&#x27;s ok to draw them straight onto the fabric with a pen, since the pen marks will end up inside the claw later.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-27.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Then sew around the claws. It doesn&#x27;t have to match the pen lines exactly; nobody will ever know (except the whole internet in this case). Here are the front and back of the cloth sandwich that I sewed claws with:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-28.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Cut them out, being careful not to snip through the stitching when cutting the bit that sticks inward, and turn them right-side out:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-29.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now it&#x27;s time to attach the liner and the hat outer together. First we need to pin the arms and legs in, making another sandwich kind of like we did with the spines along the back. I like to pin the arms sticking straight up and covering the outer&#x27;s side seams, like so:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-30.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Remember those 10 little legs we sewed earlier? Well, we need those now. And I used an extra spine from when we sewed the spines along Ferris&#x27;s back, in the center back, as a tail. Pin them on, 5 on each side, like little legs.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-31.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And finally, remember that liner we sewed, with a hole in the middle? Go find that one real quick:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-32.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now we&#x27;re going to put the whole hat outer inside of the lining, creating Ferris The Bowl. All the pretty sides of things are INSIDE the sandwich, so all the seam allowances are visible.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-33.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Rearrange your pins to allow sewing, then sew around the entire rim of Ferris The Bowl.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-34.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Snip off the extra bits of the legs and stuff, just like we snipped off the extra bits of the spines before, like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-35.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now Ferris The Bowl is more like Ferris The Football:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-36.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reach in through the hole in the end of Ferris The Football, grab the other end, and pull. First it&#x27;ll look like this...&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-37.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And then he&#x27;ll look like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-38.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Sew shut that hole in the bottom of the lining...&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-39.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Stuff that lining into the hat, to make the whole thing hat-shaped, and you&#x27;re done!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;ferris-hat-40.jpg&quot; alt=&quot;Ferris hat pattern step&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>When searching an error fails</title>
        <published>2019-02-28T00:00:00+00:00</published>
        <updated>2019-02-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2019/02/28/when_searching_an_error_fails/"/>
        <id>https://edunham.net/2019/02/28/when_searching_an_error_fails/</id>
        
        <content type="html" xml:base="https://edunham.net/2019/02/28/when_searching_an_error_fails/">&lt;p&gt;This blog has seen a dearth of posts lately, in part because my standard post formula is &quot;a public thing had a poorly documented problem whose solution seems worth exposing to search engines&quot;. In my present role, the tools I troubleshoot are more often private or so local that the best place to put such docs has been an internal wiki or their own READMEs.&lt;&#x2F;p&gt;
&lt;p&gt;This change of ecosystem has caused me to spend more time addressing a different kind of error: Those which one really can&#x27;t just Google.&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes, especially if it&#x27;s something that worked fine on another system and is mysteriously not working any more, the problem can be forehead-slappingly obvious in retrospect. Here are some general steps to catch an &quot;oops, that was obvious&quot; fix as soon as possible.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;find-the-command-that-yielded-the-error&quot;&gt;Find the command that yielded the error&lt;&#x2F;h1&gt;
&lt;p&gt;First, I identify what tool I&#x27;m trying to use. Ops tools are often an amalgam of several disparate tools glued together by a script or automation. This alias invokes that call to SSH, this internal tool wraps that API with an appropriate set of arguments by ascertaining them from its context. If I think that SSH, or the API, is having a problem, the first troubleshooting step is to figure out exactly what my toolchain fed into it. Then I can run that from my own terminal, and either observe a more actionable error or have something that can be compared against some reliable documentation.&lt;&#x2F;p&gt;
&lt;p&gt;Wrappers often elide some or all of the actual error messages that they receive. I ran into this quite recently when a multi-part shell command run by a script was silently failing, but running the ssh portion of that command in isolation yielded a helpful and familiar error that prompted me to add the appropriate key to my ssh-agent, which in turn allowed the entire script to run properly.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;make-sure-the-version-should-work&quot;&gt;Make sure the version &quot;should&quot; work&lt;&#x2F;h1&gt;
&lt;p&gt;Identifying the tool also lets me figure out where that tool&#x27;s source lives. Finding the source is essential for the next troubleshooting steps that I take.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ which toolname
&lt;&#x2F;span&gt;&lt;span&gt;$ toolname -version #
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I look for hints about whether the version of the tool that I&#x27;m using is supposed to be able to do the thing I&#x27;m asking it to do. Sometimes my version of the tool might be too new. This can be the case when the dates on all the docs that suggest it&#x27;s supposed to work the way it&#x27;s failing are more than a year or so old. If I suspect I might be on too new a version, I can find a list of releases near the tool&#x27;s source and try one from around the date of the docs.&lt;&#x2F;p&gt;
&lt;p&gt;More often, my version of a custom tool has fallen behind. If the date of the docs claiming the tool should work is recent, and the date of my local version is old, updating is an obvious next step.&lt;&#x2F;p&gt;
&lt;p&gt;If the tool was installed in a way other than my system package manager, I also check its README for hints about the versions of any dependencies it might expect, and make sure that it has those available on the system I&#x27;m running it from.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;look-for-interference-from-settings&quot;&gt;Look for interference from settings&lt;&#x2F;h1&gt;
&lt;p&gt;Once I have something that seems like the right version of the tool, I check the way its README or other docs looked as of the installed version, and note any config files that might be informing its behavior. Some tooling cares about settings in an individual file; some cares about certain environment variables; some cares about a dotfile nearby on the file system; some cares about configs stored somewhere in the homedir of the user invoking it. Many heed several of the above, usually prioritizing the nearest (env vars and local settings) over the more distant (system-wide settings).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;check-permissions&quot;&gt;Check permissions&lt;&#x2F;h1&gt;
&lt;p&gt;Issues where the user running a script has inappropriate permissions are usually obvious on the local filesystem, but verifying that you&#x27;re trying to do a task as a user allowed to do it is more complicated in the cloud. Especially when trying to do something that&#x27;s never worked before, it can be helpful to attempt to do the same task as your script manually through the cloud service&#x27;s web interface. If it lets you, you narrow down the possible sources of the problem; if it fails, it often does so with a far more human-friendly message than when you get the same failure through an API.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;trace-the-error-through-the-source&quot;&gt;Trace the error through the source&lt;&#x2F;h1&gt;
&lt;p&gt;I know where the error came from, I have the right versions of the tool and its dependencies, no settings are interfering with the tool&#x27;s operation, and permissions are set such that the tool should be able to succeed. When all this normal, generic troubleshooting has failed, it&#x27;s time to trace the error through the tool&#x27;s source.&lt;&#x2F;p&gt;
&lt;p&gt;This is straightforward when I&#x27;m fortunate enough to have a copy of that source: I pick some string from the error message that looks like it&#x27;ll always be the same for that particular error, and search it in the source. If there are dozens of hits, either the tool is aflame with technical debt or I picked a bad search string.&lt;&#x2F;p&gt;
&lt;p&gt;Locating what ran right before things broke leads to the part of the source that encodes the particular assumptions that the program makes about its environment, which can sometimes point out that I failed to meet one. Sometimes, I find that the error looked unfamiliar because it was actually escalated from some other program wrapped by the tool that showed it to me, in which case I restart this troubleshooting process from the beginning on that tool.&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes, when none of the aforementioned problems is to blame, I discover that the problem arose from a mismatch between documentation and the program&#x27;s functionality. In these cases, it&#x27;s often the docs that were &quot;right&quot;, and the proper solution is to point out the issue to the tool&#x27;s developers and possibly offer a patch. When the code&#x27;s behavior differs from the docs&#x27; claims, a patch to one or the other is always necessary.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Running a Python3 script in the right place every time</title>
        <published>2018-09-19T00:00:00+00:00</published>
        <updated>2018-09-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2018/09/19/running_a_python3_script_from_the_correct_directory/"/>
        <id>https://edunham.net/2018/09/19/running_a_python3_script_from_the_correct_directory/</id>
        
        <content type="html" xml:base="https://edunham.net/2018/09/19/running_a_python3_script_from_the_correct_directory/">&lt;p&gt;I just wrote a thing in a private repo that I suspect I&#x27;ll want to use again later, so I&#x27;ll drop it here.&lt;&#x2F;p&gt;
&lt;p&gt;The situation is that there&#x27;s a repo, and I&#x27;m writing a script which shall live in the repo and assist users with copying a project skeleton into its own directory.&lt;&#x2F;p&gt;
&lt;p&gt;The script, &lt;code&gt;newproject&lt;&#x2F;code&gt;, lives in the &lt;code&gt;bin&lt;&#x2F;code&gt; directory within the repo.&lt;&#x2F;p&gt;
&lt;p&gt;The script needs to do things from the root of the repository for the paths of its file copying and renaming operations to be correct.&lt;&#x2F;p&gt;
&lt;p&gt;If it was invoked from somewhere other than the root of the repo, it must thus change directory to the root of the repo before doing any other operations.&lt;&#x2F;p&gt;
&lt;p&gt;The snippet that I&#x27;ve tested to meet these constraints is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;# chdir to the root of the repo if needed
&lt;&#x2F;span&gt;&lt;span&gt;if __file__.endswith(&amp;quot;&#x2F;bin&#x2F;newproject&amp;quot;):
&lt;&#x2F;span&gt;&lt;span&gt;    os.chdir(__file__.strip(&amp;quot;&#x2F;bin&#x2F;newproject&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;if __file__ == &amp;quot;newproject&amp;quot;:
&lt;&#x2F;span&gt;&lt;span&gt;    os.chdir(&amp;quot;..&amp;quot;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In code review, it was pointed out that this simplifies to a one-liner:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;os.chdir(os.path.join(os.path.dirname(__file__), &amp;#39;..&amp;#39;))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will keep working right up until some malicious or misled individual moves the script to an entirely different location within the repository or filesystem and tries to run it from there.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>CFP tricks 1</title>
        <published>2018-09-17T00:00:00+00:00</published>
        <updated>2018-09-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2018/09/17/cfp_tricks_1/"/>
        <id>https://edunham.net/2018/09/17/cfp_tricks_1/</id>
        
        <content type="html" xml:base="https://edunham.net/2018/09/17/cfp_tricks_1/">&lt;p&gt;Or, &quot;how to make a selection committee do one of the hard parts of your job as a speaker for you&quot;. For values of &quot;hard parts&quot; that include fine-tuning your talk for your audience.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m giving talk advice to a friend today, which means I&#x27;m thinking about talk advice and realizing it&#x27;s applicable to lots of speakers, which means I&#x27;m writing a blog post.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-choosing-an-audience-is-hard&quot;&gt;Why choosing an audience is hard&lt;&#x2F;h1&gt;
&lt;p&gt;Deciding who you&#x27;re speaking to is one of the trickiest bits of writing an abstract, because a good abstract is tailored to bring in people who will be interested in and benefit from your talk. One of the reasons that it&#x27;s extra hard for a speaker to choose the right audience, especially at a conference that they haven&#x27;t attended before, is because they&#x27;re not sure who&#x27;ll be at the conference or track.&lt;&#x2F;p&gt;
&lt;p&gt;Knowing your audience lets you write an abstract full of relevant and interesting questions that your talk will answer. Not only do these questions show that you can teach your subject matter, but they&#x27;re an invaluable resource for assessing your own slides to make sure your talk delivers everything that you promised it would!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;tricks-for-choosing-an-audience&quot;&gt;Tricks for choosing an audience&lt;&#x2F;h1&gt;
&lt;p&gt;Some strategies I&#x27;ve recommended in the past for dealing with this include looking at the conference&#x27;s marketing materials to imagine who they would interest, and examining the abstracts of past years&#x27; talks.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;make-the-committee-choose-by-submitting-multiple-proposals&quot;&gt;Make the committee choose by submitting multiple proposals&lt;&#x2F;h1&gt;
&lt;p&gt;Once you narrow down the possible audiences, a good way to get the right talk in is to offload the final choice onto the selection committee! A classic example is to offer both a &quot;Beginner&quot; and an &quot;Advanced&quot; talk, on the same topic, so that the committee can pick whichever they think will be a better fit for the audience they&#x27;re targeting and the track they choose to schedule you for.&lt;&#x2F;p&gt;
&lt;p&gt;If the CFP allows notes to the committee, it can be helpful to add a note about how your talks are different, especially if their titles are similar: &quot;This is an introduction to Foo, whereas my other proposal is a deep dive into Foo&#x27;s Bar and Baz&quot;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;use-the-organizers-own-words&quot;&gt;Use the organizers&#x27; own words&lt;&#x2F;h1&gt;
&lt;p&gt;I always encourage resume writers to use the same buzzwords as the job posting to which they&#x27;re applying when possible. This shows that you&#x27;re paying attention, and makes it easy for readers to tell that you meet their criteria.&lt;&#x2F;p&gt;
&lt;p&gt;In the same way, if your talk concept ties into a buzzword that the organizers have used to describe their conference, or directly answers a question that their marketing materials claim the conference will answer, don&#x27;t be afraid to repeat those words!&lt;&#x2F;p&gt;
&lt;p&gt;When choosing between several possible talk titles, keep in mind that any jargon you use can show off your ability, or lack thereof, to relate to the conference&#x27;s target audience. For instance, a talk with &quot;Hacking&quot; in the title may be at an advantage in an infosec conference but at a disadvantage in a highly professional corporate conf. Another example is that spinning my Rust Community Automation talk to &quot;Life is Better with Rust&#x27;s Community Automation&quot; worked great for a conference whose tagline and theme was &quot;Life is Better with Linux&quot;, but would not have been as successful elsewhere.&lt;&#x2F;p&gt;
&lt;p&gt;Good luck with your talks!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Skill Tree Balancing with a Job Move</title>
        <published>2018-08-24T00:00:00+00:00</published>
        <updated>2018-08-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2018/08/24/job_move/"/>
        <id>https://edunham.net/2018/08/24/job_move/</id>
        
        <content type="html" xml:base="https://edunham.net/2018/08/24/job_move/">&lt;p&gt;I’ve recently identified some ways in which my former role wasn’t setting me up for career success, and taken steps to remedy them. Since not everybody lucks into this kind of process like I did, I’d like to write a bit about what I’ve learned in case it offers some reader a useful new framework for thinking about their skills and career growth.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Tl;dr:&lt;&#x2F;strong&gt; I’m moving from Research to Cloud Ops within Mozilla. The following wall of text and silly picture are a brain dump of new ideas about skills and career growth that I’ve built through the process.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;managers-as-mentors&quot;&gt;Managers as Mentors&lt;&#x2F;h1&gt;
&lt;p&gt;Oddly enough, I can trace the series of causes and effects that ultimately culminated in my decision to switch roles straight back to a company that I’ve never even worked at. By shaping my manager’s leadership skills, Microsoft’s policies have trickled down into benefits to my open source focused career at Mozilla.&lt;&#x2F;p&gt;
&lt;p&gt;Apparently, managers at Microsoft have targets to meet for their reports’ career growth and advancement, as well as the usual product and performance metrics. I’ve heard that a manager there who meets all their other deliverables but fails to promote the people under them can be demoted for that negligence! I’ve learned about this culture because it showed up in my own ex-Microsoft manager as a refusal to take “I’m happy where I am and don’t think I particularly need to go seek a promotion” as a complete answer when he asked about my goals for career growth and progression.&lt;&#x2F;p&gt;
&lt;p&gt;I have many peers whose managers seem fine with “I’m happy where I am” as an answer to career goal questions. Replacing an employee for their lower-level duties as they graduate to higher-level ones can be between inconvenient and almost impossible for a boss, so it can seem to be in everybody’s best interests to leave someone for a long time when they find a role where they’re content. But my own manager’s ingrained distaste for allowing that kind of stagnation led to a series of conversations that forced me to look at the bigger picture of my career, and realize the ways that sticking with a role that’s “good enough for now” could cause me serious problems years down the road.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;complacency-and-the-same-year-of-work&quot;&gt;Complacency and the Same Year of Work&lt;&#x2F;h1&gt;
&lt;p&gt;I’ve been warned over and over by mentors through the industry that it’s a terrible idea to do the “same year of work” over and over. I’ve even repeated that advice to others – “don’t tolerate a job that isn’t helping you grow! Keep moving roles till you find something you love!”. This advice is especially easy to follow when the “same year of work” you’re repeating is unpleasant in some way.&lt;&#x2F;p&gt;
&lt;p&gt;However, I found myself in a role at Mozilla Research where the “same year of work” is totally amazing! 3 times in a row, I came into a team with major ops needs, and delivered value in a way that I’m great at to a team of awesome people with expertise in areas where my knowledge is only superficial. And it feels great to help them out and solve problems with knowledge that’s old hat for me but novel to my colleagues. For each role, my progress eventually reached a point where I’d solved all the problems that I could fix in a timely manner – I’d hit the edge of my skills at the time. Although it’s educational, working outside my skill set eventually slowed down my performance and work quality to the point where it was better to have me shift focus to a group whose needs were within what I could execute rapidly and with high quality.&lt;&#x2F;p&gt;
&lt;p&gt;The parts of this cycle in which I’m attempting a load of tasks primarily outside my skill set (and often outside the set of skills that I have the prerequisite skills to bootstrap myself into in a timely manner) can be stressful, but on the whole it’s been a rewarding experience. I’ve felt like I’ve gotten a “new role” every year or so, on about the timeline that a peer starting with similar skills to my own might have had to switch companies to find new opportunities. Without my manager’s guidance, I might have continued in “same year of work” cycle indefinitely, but our conversations about career progression helped me improve my understanding of how I personally grow as an engineer.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;self-teachable-skills&quot;&gt;Self-Teachable Skills&lt;&#x2F;h1&gt;
&lt;p&gt;What’s that “set of skills that I have the prerequisite skills to bootstrap myself into in a timely manner” that I mentioned earlier? They’re those challenges which I can identify and frame in a way which lets me ask the kind of questions that get actionable answers. They’re knowledge gaps directly adjacent to my established areas of expertise, about which I can concisely frame questions whose answers I can use to accomplish a task. Despite being a “team of one”, I’ve successfully self-taught skills such as learning a new configuration management system and improving my FOSS community management knowledge to solve particular challenges. Since I was already more skilled than the average entry-level engineer in those areas when I started my solo ops role, I had good heuristics for framing questions in a way that was likely to yield the answers I wanted, and evaluating the credibility and applicability of sources to learn from.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, I’ve found that I can be successful at self-teaching a skill when I can write out a template for what I know the correct answer to a given question will look like. I might not know precisely which config management system will meet my needs, but I already know a variety of pain points to look out for and how to assess whether an example given in a tool’s docs is similar to the task I’m looking to apply the tool on. I might not know exactly what community management strategy is best for a given FOSS challenge, but I know enough about community management in general to frame a question about what strategy to use with all the relevant information and propose several viable options to ask an expert for their opinions in selecting between.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;non-self-teachable-skills&quot;&gt;Non-Self-Teachable skills&lt;&#x2F;h1&gt;
&lt;p&gt;I’ve found that, in working as a team of one, I’ve done pretty well at growing the skills that I had the appropriate foundation to “bootstrap” or “self-teach”. However, there are other skills for which this hasn’t been the case. Although these types of skills can be self-taught very slowly and inefficiently, I’m going to nickname them “non-self-teachable” for purposes of this discussion. “Infeasible-to-self-teach”, while technically more accurate, is just too much of a tangle to type out.&lt;&#x2F;p&gt;
&lt;p&gt;For skills with whose problem space I’m not fully acquainted, I find it extremely challenging to improve in a timely manner in isolation. In areas like prioritizing work across projects, and project planning, I find that my ability to self-teach is limited when I don’t have sufficient expertise to ask for help efficiently. When I don’t know what sort of answer would be actionable, I struggle to frame general questions and figure out where to ask them. And when I lack the expertise to identify what specific tasks I should be asking for help to improve at, I miss many opportunities to seek help.&lt;&#x2F;p&gt;
&lt;p&gt;How did I discover these “unknown unknowns” of skills that I lack the foundation to teach myself efficiently? I started noticing them when seeking resources to understand my career growth options in order to refine my career goals. In an internal wiki, I found a document which gives broad outlines for and descriptions of the competence and impact of employees at each pay grade. Mozilla’s is called a Job Family Architecture; other companies have similar documents with different names. Comparing my performance to the descriptions of higher levels helped me identify and articulate some particular skill areas that I haven’t improved at the same rate as those where I’ve successfully self-taught. Quantifying these areas that I’d like to work on has helped me figure out what changes I could make to get into circumstances more like those where I’ve improved rapidly at the “problem” skills in the past.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;bootstrapping-non-self-teachable-skills&quot;&gt;Bootstrapping Non-Self-Teachable Skills&lt;&#x2F;h1&gt;
&lt;p&gt;When an engineer enters their very first job or internship, or touches a programming language for the first time, it’s reasonable to assume that all the skills they need to build are in the category that I’m describing as non-self-teachable. Skills I’m currently expert at, and even teach to others, started out as non-self-teachable for me as well. So what changed? How did I go from having no idea what questions to ask about Git, to being able to solve even its gnarliest problems with only a handful of StackOverflow queries or man page checks?&lt;&#x2F;p&gt;
&lt;p&gt;When I look back at my career, the common denominators among times I’ve built any skill from non-self-teachable to self-teachable have been peers and mentors. Watching the way that a teammate addresses a challenge and comparing it to the way I would have approached it gives me new insights at a rate that no amount of struggling with the skill by myself ever could. And managers or mentors who are subject matter experts in my field can compare the way I approach a task to the way they would have, and point out differences to yield skill-improving feedback. When I work closely enough with an expert for a while , I build a mental model of the way they think, and for the rest of my career I can ask myself “How would they have architected this? How would they have tackled this problem?”.&lt;&#x2F;p&gt;
&lt;p&gt;These mental models can simulate a team in circumstances that are similar to those where I worked with the experts, but as I advance into novel work in isolation, the models become less and less useful because I can’t predict how the expert would have approached a task unlike anything I’ve seen them encounter.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;bridging-my-skill-gaps&quot;&gt;Bridging My Skill Gaps&lt;&#x2F;h1&gt;
&lt;p&gt;Once I identified my skill gaps, I first attempted to seek better mentorship in my existing role. After several different attempts, I determined that a mentor’s intimacy with the particular task they’re giving feedback on is integral to their ability to help me refine a technical skill. In both individual and group mentorship, I found it easy to refine skills that I could already self-teach, but between difficult and impossible to get good help on skills about which I wasn’t yet expert enough to frame good questions. Doing my best to improve within my existing role helped me figure out which skills it was reasonable to try to improve in place, and which others were infeasible to build in a timely manner under the circumstances.&lt;&#x2F;p&gt;
&lt;p&gt;I talked with my manager about what I’d learned about myself from the various attempts to build the skills I regard as needing work. We evaluated our options for putting me into a situation where I had the peers and mentorship that I’m looking for: What would it look like to change my current role so it had more peers? What would it look like to move me into a role on a team of ops folks elsewhere? When we took into account some other skill gaps that I’m interested in addressing, such as working with experts on infrastructure at larger scale, we concluded that the best step to address my current concerns was to explore my options for shifting to a different team.&lt;&#x2F;p&gt;
&lt;p&gt;At this point, I literally wrote down a list of relevant skills in my notebook. I brainstormed a section of skills where I feel I’m below where I’d like to be and would learn best from peers. I also outlined the skills I’ve succeeded with in my present role, and the skills at which I consider myself above average and thus am not worried about aggressively growing while I focus on improvement in other areas.&lt;&#x2F;p&gt;
&lt;p&gt;I summarized my lists into the 2 key reasons I’m interested in a role change. Those reasons are: I want to work with peers and mentors who can offer detailed technical feedback based on their expertise at the problems I’m solving, and I want to refine my prioritization and planning skills by being more closely exposed to good examples in work like mine.&lt;&#x2F;p&gt;
&lt;p&gt;As I sought other teams that might meet these needs for me and interviewed with them, I kept my lists and summary on my desk. I felt that having them in sight made a real difference in my ability to clearly articulate my interest in a role change, and helped me ask the right questions to determine whether any team looked like a good match for my priorities. As I interviewed, I paid attention to not only the technical topics that we discussed, but the prospective colleagues’ attitude toward the answers I got wrong. On the team I ended up selecting, I was especially impressed by the way that my future peers ended each question by filling in any gaps between my answer and the complete or best possible answer that they were “looking for”. This made the interviews feel like a constructive conversation, and even if I hadn’t taken the role, I would have left with a better understanding of the technologies we discussed.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;new-team&quot;&gt;New Team&lt;&#x2F;h1&gt;
&lt;p&gt;With all that, I’m excited to announce that I’m transitioning from Research to the Cloud Ops team within Mozilla! This team supports a multitude of projects, and there are always at least 2 ops engineers in a “buddy system” for every project they support. The new role is similar to my old one in that it juggles supporting many projects at once, but very different in that I’ll be working directly with expert colleagues to learn their best ways to do it.&lt;&#x2F;p&gt;
&lt;p&gt;And yeah, I’m staying at Mozilla. It might net me more cash to jump ship to another company, but monetary compensation is not what this move is optimizing for. The drawbacks I would experience if I chose a team at another company include greater uncertainty about what the team is actually like, and having to re-learn all the specialized bureaucracy that comes with onboarding anywhere. I also encounter very few other companies whose cultures are as closely aligned with my own values, expecially pertaining to open source, and none of them currently have openings that I can confirm are as good a match to my current skill building goals as the Cloud Ops team.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;visualization&quot;&gt;Visualization&lt;&#x2F;h1&gt;
&lt;p&gt;Drawing a picture is one of my favorite ways to gain control of and ask the right questions about new knowledge. Here’s an ugly little chart into which I’ve thrown a handful of skill areas and my approximate levels at them before and after my old role… and of course, the skill emphases that have been important to me in picking the right place to go next.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;skill_tree_chart.png&quot; alt=&quot;Net chart illustrating skill gaps filled in by emphases of new role&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If you want to make a similar chart, I made that one with the &lt;a href=&quot;https:&#x2F;&#x2F;help.libreoffice.org&#x2F;Chart&#x2F;Chart_Type_Net&quot;&gt;net chart type&lt;&#x2F;a&gt; in LibreOffice.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Why an ops career</title>
        <published>2018-07-20T00:00:00+00:00</published>
        <updated>2018-07-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2018/07/20/why_ops/"/>
        <id>https://edunham.net/2018/07/20/why_ops/</id>
        
        <content type="html" xml:base="https://edunham.net/2018/07/20/why_ops/">&lt;p&gt;&lt;em&gt;Disclaimers: Not all tasks that come to a person in an ops role meet my definition of ops tasks. Advanced ops teams move on from simple problems and choose more complex problems to solve, for a variety of reasons. This post contains generalizations, and all generalizations have counter-examples. This post also refers to feelings, and humans often experience different feelings in response to similar stimuli, so yours might not be like mine.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s been a great &quot;family reunion&quot; of FOSS colleagues and peers in the OSCON hallway track this week. I had a conversation recently in which I was asked &quot;Why did you choose ops as a career path?&quot;, and this caused me to notice that I&#x27;ve never blogged about this rationale before.&lt;&#x2F;p&gt;
&lt;p&gt;I work in roles revolving around software and engineering because they fall into a cultural sweet spot offering smart and interesting colleagues, opportunities for great work-life balance, and exemplary compensation. I also happen to have taken the opportunity to spend over a decade building my skills and reputation in this industry, which helps me keep the desirable roles and avoid the undesirable ones. Yet, many people in my field prefer software development over operations work.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve chosen, and stuck with, ops because it gives me the sensation of having better-defined success conditions than I get when developing code for others. When I tackle an ops problem, it is usually a task which I could tediously, miserably, but correctly perform by hand. This base case of &quot;if all else fails, the desired thing can be done by hand&quot; frames a problem more concretely and measurably than any written description of someone&#x27;s hopes and dreams about a piece of software.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, performing ops tasks by hand does not scale. Often, the speed with which a given task is performed is part of its success criteria. And if you ask a human to perform the same task 20 times, you&#x27;ll likely get 21 subtly different outputs. This is why we automate: Automation brings computers&#x27; strengths of speed and lack of boredom to the equation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Automation tasks are necessarily framed in terms of the specific behaviors, described in technical terms, that computers are supposed to be performing.&lt;&#x2F;strong&gt; The constraints of the infrastructure provide a rigorously defined abstraction layer between psychology and code. This vocabulary of infrastructure expresses the constraints for ops work such that even if I&#x27;m not the end user of a piece of automation code, I can experience a high level of confidence that I understand what the person requesting it believed that they wanted when they made the request.&lt;&#x2F;p&gt;
&lt;p&gt;Automation is unlike software engineering tasks with success conditions that hinge on human emotions and behavior. Any success condition with psychology integral to it becomes time-consuming, if not impossible, to test against. Throw in psychological effects that incline a human to have slightly different reactions to the same thing depending on when and how you show it to them, and you lose even basic repeatability from the simple task of testing whether your code is &quot;good enough&quot;. For software engineering tasks with human behavior and emotions in their success criteria, I cannot consistently prove to myself that success is even possible. Although I enjoy recreationally tackling potentially-impossible challenges from time to time, I do not enjoy the pressure and uncertainty that come from betting my career and compensation on such puzzles.&lt;&#x2F;p&gt;
&lt;p&gt;Even systems built solely from understandable components develop complexity and challenges. Emergent behaviors arise, perhaps necessarily, from complex systems. In ops work, I feel a certainty that each component is independently predictable when broken down small enough, and that it would be possible with enough work to rebuild the entire system incrementally from such &quot;atoms&quot; of predictability. Of course it is almost never worth the time and effort to actually rebuild the system from scratch, but simply knowing that it would be possible gives me confidence that the problems I encounter with my systems can be solved. (To reiterate, &quot;can be solved&quot; bears little relation to &quot;is worth solving&quot;, but it does affect the way I feel about tasks.) Contrast this &quot;certainty of solvability&quot; to the problems encountered when developing software for other people: &quot;the customer doesn&#x27;t like this!&quot;, &quot;users aren&#x27;t clicking where we want them to!&quot;. Those problems hinge on human components that would usually be highly unpleasant, unethical, and illegal to disassemble and debug. Software problems tightly coupled to psychology do not make me feel like I can be certain that &lt;em&gt;any&lt;&#x2F;em&gt; amount of effort would guarantee a solution.&lt;&#x2F;p&gt;
&lt;p&gt;No workflow can, nor should, eliminate the bigger-picture questions about what we want to be building, or how we want to go about building it. However, I find that the structure of roles that companies typically categorize as &quot;ops work&quot; supports decoupling the questions without answers from the questions with answers, and offloading the half you don&#x27;t want to deal with onto someone who enjoys them more.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Thoughts on retiring from a team</title>
        <published>2018-05-15T00:00:00+00:00</published>
        <updated>2018-05-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2018/05/15/team/"/>
        <id>https://edunham.net/2018/05/15/team/</id>
        
        <content type="html" xml:base="https://edunham.net/2018/05/15/team/">&lt;p&gt;The Rust Community Team has recently been having a conversation about what a team member&#x27;s &quot;retirement&quot; can or should look like. I used to be quite active on the team but now find myself without the time to contribute much, so I’m helping pioneer the “retirement” process. I’ve been talking with our subteam lead extensively about how to best do this, in a way that sets the right expectations and keeps the team membership experience great for everyone.&lt;&#x2F;p&gt;
&lt;p&gt;Nota bene: This post talks about feelings and opinions. They are mine and not meant to represent anybody else’s.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-join-a-team&quot;&gt;Why join a team?&lt;&#x2F;h1&gt;
&lt;p&gt;When I joined the Rust community subteam, its purpose was defined vaguely. It was a small group of “people who do community stuff”, and needed all the extra hands it could get. A lot of my time was devoted explicitly to Rust and Rust-related tasks. The tasks that I was doing anyways seemed closely aligned with the community team’s work, so stepping up as a team contributor made a lot of sense. Additionally, the team was so new that the only real story for “how to work with this team and contribute to its work” was “join the team” . We hadn’t yet pioneered the subteams and collaboration with community organizers outside the official community team which are now multiplying the team’s impact.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-leave&quot;&gt;Why leave?&lt;&#x2F;h1&gt;
&lt;p&gt;I’m grateful to the people who have the bandwidth and interest to put consistent work into participating on Rust’s community team today. As the team has grown and matured, its role has transitioned from “do community tasks” to “support and coordinate the many people doing those tasks”. I neither enjoy nor excel at such coordination tasks. Not only do I have less time to devote to Rust stuff, but the community team’s work has naturally grown into higher-impact categories that I personally find less fulfilling and more exhausting to work on.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;teams-and-people-change&quot;&gt;Teams and people change&lt;&#x2F;h1&gt;
&lt;p&gt;In a way, the team’s growth and refinement over the years reminds me of a microcosm of what I saw while working at a former startup as it built up into an enterprise company. Some peoples’ working style had been excellently suited to the 5-person company they originally joined, but clashed with the 50-person company into which that startup grew. Others who would never have thrived in a company of only 10 people were hiring on and having a fantastic impact scaling the company up to 1,000. And some were fine when the company was small and didn’t mind being part of a larger organization either. That experience reminds me that the fit between a person and organization at some point in the past does not guarantee that they’ll remain a good fit for each other over time, and neither is necessarily to blame for the eventual mismatch as both grow and change.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;does-leaving-harm-anyone&quot;&gt;Does leaving harm anyone?&lt;&#x2F;h1&gt;
&lt;p&gt;When you’re appreciated and valued for the work you do on a team, it’s easy to get the idea that the team would be harmed if you left. The tyres on my bike are a Very Important Part of the bike, and if I took them off, the bike wouldn’t be rideable. But a team isn’t just a machine – a team’s impact is an emergent phenomenon that comes out of many factors, not a static item. If a sports team has a really excellent coach, they’ll retain the lessons they learned from that coach’s mentorship even after the coach moves away. Older players will pass along the coach’s lessons to younger ones, and their ideas will stick around and improve the group even long after the original players’ retirement. When a team is coordinated well, one member leaving doesn’t hurt it. And if I leave on good terms rather than sticking around till I burn out or burn bridges, I can always be available for remaining members to consult when if need advice that only I can provide.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;would-staying-harm-anyone&quot;&gt;Would staying harm anyone?&lt;&#x2F;h1&gt;
&lt;p&gt;I think that in the case of the Rust community team, it would reflect poorly on the community as a whole if the exact same people comprised the community team for the entire life of the language.&lt;&#x2F;p&gt;
&lt;p&gt;If nobody new ever joins the team, we wouldn’t get new ideas and tactics, nor the priceless infusion of fresh patience and optimism that new team members bring to our perennial challenges and frustrations. So, new team members are essential. If new people joined on a regular basis but nobody ever left, the team would grow unboundedly large as time went on, and have you ever tried to get anything done with a hundred- or thousand-person committee? In my opinion, having established team members retire every now and then is an essential factor in preventing either of those undesirable hypotheticals.&lt;&#x2F;p&gt;
&lt;p&gt;The team selects for members who’ll step up and accomplish tasks when they need to. I think establishing turnover in a healthy and sustainable way is one of the most essential tasks for the team to build its skills at. The best way to get a healthy amount of turnover – not too much, but not too little either – is for every team member to step up to the personal challenge of identifying the best time to retire from active involvement. And for me, that happens to look like right now.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;aspirational-clutter&quot;&gt;Aspirational Clutter&lt;&#x2F;h1&gt;
&lt;p&gt;Do you have stuff in your house that you don’t use, and it’s taking up space, and you’re kind of annoyed at it for taking up space, but you don’t feel like you can get rid of it because you think you really should use it, or you’re sure you’re just going to make some personal change that will cause you to use it someday? I call that stuff aspirational clutter: It doesn’t belong to you, it belongs to some imaginary person who doesn’t exist but you aspire to become them someday.&lt;&#x2F;p&gt;
&lt;p&gt;A team meeting every week on your agenda can be aspirational clutter in the same way as a jumbled shelf of planners or a pile of sports gear covering a treadmill: It not only isn’t a good fit for who you are right now, but by wasting time or space it actually gets in the way of the habits and changes that would make you more like that person you aspire to be.&lt;&#x2F;p&gt;
&lt;p&gt;I find few experiences more existentially miserable than feeling obliged to promise work that I know I’ll lack the resources of time or energy to deliver. Sticking around on a team that I’m no longer a good fit for puts me in a situation where get to choose between feeling guilty if I don’t promise to get any work done, or feeling like a disappointment for letting others down if I commit to more than I’m able to deliver. Those aren’t feelings I want to experience, and I can avoid them easily by being honest with myself about the amount of time and energy I have available to commit to the team.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-benefits-of-contributing-from-a-non-team-member-role&quot;&gt;The benefits of contributing from a non-team-member role&lt;&#x2F;h1&gt;
&lt;p&gt;One scary idea that comes up when leaving a team is the question: “if I’m not on the team, how can I help with the team’s work?”.&lt;&#x2F;p&gt;
&lt;p&gt;In my opinion, it builds a healthier community if people who are good at a given team’s work spend some time interfacing with the team from the perspective of non-team-members. If I know how the community team gets stuff done and I go “undercover” as a non-team-member coming to them for help, I can give them essential feedback to improve the experience and processes that non-team-members encounter.&lt;&#x2F;p&gt;
&lt;p&gt;When I wear my non-team-member hat and try to get stuff done, I learn what it’s like for everyone else who tries to interface with the team. I can then use the skills that I built on by participating on the team to remedy any challenges that a non-team-member encounters. Those changes create a better experience for every community member who interacts with the team afterwards.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-next&quot;&gt;What next?&lt;&#x2F;h1&gt;
&lt;p&gt;As a community team alum, I’ll keep doing the Rust outreach – the meetup organizing, the conference talks, the cute swag, the stickers – that I’ve been doing all along. Stepping down from the official team member list just formalizes the state that my involvement has been in for the past year or so: Although I get the community team’s support for my endeavors when I need it, I’m not invested in the challenges of supporting others’ work which the team is now tackling.&lt;&#x2F;p&gt;
&lt;p&gt;I’m proud of the impact that the team has had while I’ve been a part of it, and I look forward to seeing what it will continue to accomplish. I&#x27;m grateful for all the leadership and hard work that have gone into making the Rust community subteam an organization from which I can step back while remaining confident that it will keep excelling and evolving.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-blog-all-that&quot;&gt;Why blog all that?&lt;&#x2F;h1&gt;
&lt;p&gt;I’m publishing my thoughts on leaving in the hopes that they can help you, dear reader, gain some perspective on your own commitments and curate them in whatever way is best for you.&lt;&#x2F;p&gt;
&lt;p&gt;If you read this and feel pressured to leave something you love and find fulfilling, please try to forget you ever saw in this post.&lt;&#x2F;p&gt;
&lt;p&gt;If you read this hoping it would give you some excuse to quit a burdensome commitment and feel disappointed that I didn’t provide one, here it is now: You don’t need a fancy eloquent excuse to stop doing something if you don&#x27;t want to any more. Replace unfulfilling pursuits with better ones.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Slacking from Irssi</title>
        <published>2018-02-23T00:00:00+00:00</published>
        <updated>2018-02-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2018/02/23/slacking_from_irc/"/>
        <id>https://edunham.net/2018/02/23/slacking_from_irc/</id>
        
        <content type="html" xml:base="https://edunham.net/2018/02/23/slacking_from_irc/">&lt;p&gt;UPDATE: SLACK DECIDED THIS SHOULD NO LONGER BE POSSIBLE AND IT WILL NOT WORK ANY MORE&lt;&#x2F;p&gt;
&lt;p&gt;My IRC client helps me work efficiently and minimize distraction. Over the years, I&#x27;ve configured it to behave exactly how I want: Notifying me when topics I care about are under discussion, and equally as important, refraining from notifications that I don&#x27;t want. Since my IRC client is developed under the GPL, I have confidence that the effort I put into customizing it to improve my workflow will never be thrown out by a proprietary tool&#x27;s business decisions.&lt;&#x2F;p&gt;
&lt;p&gt;But the point of chat is to talk to other humans, and a lot of humans these days are choosing to collaborate on Slack. Slack has its pros and cons, but some of the drawbacks can be worked around using open technologies.&lt;&#x2F;p&gt;
&lt;p&gt;Do you need to Slack from irssi? ------------------------------&lt;&#x2F;p&gt;
&lt;p&gt;If you feel ambivalent toward the web UI, going to the trouble of setting up an IRC client for Slack will likely be more hassle than reward.&lt;&#x2F;p&gt;
&lt;p&gt;If you loathe the Slack UI but don&#x27;t care much for IRC, you might be better off considering the &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@RiotChat&#x2F;slack-bridge-improvements-44c52fb712f4&quot;&gt;Matrix bridge&lt;&#x2F;a&gt; or seeking out other clients. I have not tried them and can&#x27;t vouch for any.&lt;&#x2F;p&gt;
&lt;p&gt;If you use WeeChat, you can use Slack IRC directly, or try the &lt;a href=&quot;https:&#x2F;&#x2F;robots.thoughtbot.com&#x2F;weechat-for-slacks-irc-gateway&quot;&gt;WeeChat Slack gateway&lt;&#x2F;a&gt;. A friend at a larger company informs me that the latter doesn&#x27;t hold up well on Slack workspaces in the tens of thousands of users, so if you&#x27;re on a particularly large Slack you might be better off treating it as an IRC server.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re looking for a terminal-based client to get started with IRC, consider choosing WeeChat over Irssi. WeeChat is extensible in more languages and allegedly has fewer edge cases like the saga detailed here.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-irc-bridge-won-t-work-on-all-slacks&quot;&gt;The IRC bridge won&#x27;t work on all Slacks&lt;&#x2F;h1&gt;
&lt;p&gt;First, a word of warning: You can only Slack from IRC if a workspace owner enables the IRC&#x2F;XMPP gateway for a given instance, which is disabled by default because Slack distrusts users&#x27; ability to make good security decisions. They&#x27;re not necessarily wrong, but generally users who care deeply enough to slog through their misleading instructions also know a thing or two about SSL.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;this-will-work-on-all-irc-clients-but&quot;&gt;This will work on all IRC clients, But...&lt;&#x2F;h1&gt;
&lt;p&gt;The good news is that once a Slack instance is exposing the IRC gateway, it looks to an IRC client just like a single standalone IRC server.&lt;&#x2F;p&gt;
&lt;p&gt;The bad news is that the &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@RiotChat&#x2F;slack-bridge-improvements-44c52fb712f4&quot;&gt;instructions Slack provides&lt;&#x2F;a&gt; will only work if you do them on an IRC client where you weren&#x27;t connected to any other IRC yet. This is because (at time of writing) they tell you only to add Slack as a server in your client.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;irssi-is-special&quot;&gt;Irssi is &quot;special&quot;.&lt;&#x2F;h1&gt;
&lt;p&gt;Let&#x27;s step back to the &lt;a href=&quot;http:&#x2F;&#x2F;talks.edunham.net&#x2F;seagl2014&#x2F;intermediateirc&#x2F;#6&quot;&gt;basics of how IRC works&lt;&#x2F;a&gt; for a minute: On IRC, a &lt;em&gt;network&lt;&#x2F;em&gt; is a group of &lt;em&gt;servers&lt;&#x2F;em&gt;, and on a given network you can join various channels. Being on a network is &lt;em&gt;intentionally agnostic of what server you&#x27;re connected to&lt;&#x2F;em&gt;, so that if one server goes away, you can connect to another and keep right on chatting.&lt;&#x2F;p&gt;
&lt;p&gt;All modern IRC clients that I&#x27;m aware of allow you to be connected to several networks at once. For instance I&#x27;m on the Freenode IRC network to talk to people about FOSS projects I use, and also the Mozilla IRC network because most of my work channels are there. Every command you issue to irssi is done in the context of some network -- do you want to auth to services? Join a new channel? Add a server? Irssi assumes that the server of the buffer in which you issue a command is the server you want the command to apply to, though some might require you to explicitly specify the network.&lt;&#x2F;p&gt;
&lt;p&gt;So, if you&#x27;re already on a network and you tell your client to add a server, what will happen? That&#x27;s right, your client will do the smart thing and add the server &lt;em&gt;to the network&lt;&#x2F;em&gt;. It will also likely connect you to that server, and do all the things on that server which you&#x27;ve asked it to do when you first connect to that particular network.&lt;&#x2F;p&gt;
&lt;p&gt;What happens if that new server is not part of the network in question at all, but is instead Slack? irssi will do the automatic things, like joining channels and trying to auth to services, that it&#x27;s supposed to do when it joins that network anyways, because you as a human told the client that the server was on the network. Even if that&#x27;s because some mean ol&#x27; documentation tricked you into it, irssi doesn&#x27;t know any better.&lt;&#x2F;p&gt;
&lt;p&gt;What happens when irssi autojoins a bunch of channels, but issues those join commands to the Slack server? Well, on Slack just as on IRC, the first person to join a channel &lt;a href=&quot;https:&#x2F;&#x2F;get.slack.help&#x2F;hc&#x2F;en-us&#x2F;articles&#x2F;201402297-Create-a-channel&quot;&gt;creates it&lt;&#x2F;a&gt;. So, you have just revealed to your whole Slack workspace exactly the names of the channels you were in on the IRC server from which you issued the &lt;code&gt;&#x2F;server&lt;&#x2F;code&gt; command.&lt;&#x2F;p&gt;
&lt;p&gt;Spuriously creating a bunch of channels isn&#x27;t the end of the world, you can just &lt;a href=&quot;https:&#x2F;&#x2F;get.slack.help&#x2F;hc&#x2F;en-us&#x2F;articles&#x2F;213185307-Delete-a-channel&quot;&gt;delete them&lt;&#x2F;a&gt;, right? Well, if you have owner or admin permissions on the Slack workspace, absolutely! If you are not an owner or admin, you will have to go find someone who is and ask them to clean up the mess.&lt;&#x2F;p&gt;
&lt;p&gt;Well, at least that&#x27;s what happens when an active IRC user blindly assumes that whoever wrote the Slack IRC connection instructions had tried them in an irssi instance they were actually using for IRC. My bad.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;don-t-follow-the-slack-docs-verbatim-from-irssi&quot;&gt;Don&#x27;t follow the Slack docs verbatim from Irssi.&lt;&#x2F;h1&gt;
&lt;p&gt;When you&#x27;re looking at &lt;a href=&quot;https:&#x2F;&#x2F;my.slack.com&#x2F;account&#x2F;gateways&quot;&gt;https:&#x2F;&#x2F;my.slack.com&#x2F;account&#x2F;gateways&lt;&#x2F;a&gt;, it has instructions like the following:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure that your IRC client is configured with your normal Slack username as your nick.&lt;&#x2F;li&gt;
&lt;li&gt;If you are connecting through a raw &lt;code&gt;&#x2F;server&lt;&#x2F;code&gt; command, your command will be: &lt;code&gt;&#x2F;server myserver.irc.slack.com 6667 myserver.Nosh5Neevot5Efua&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;If you have a more UI-oriented setup, your IRC server is &lt;code&gt;myserver.irc.slack.com&lt;&#x2F;code&gt;, and the server password is &lt;code&gt;myserver.Nosh5Neevot5Efua&lt;&#x2F;code&gt;. Accepted ports are 6667, 6697, and 8000.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That &lt;code&gt;Nosh5Neevot5Efua&lt;&#x2F;code&gt; bit is a password you shouldn&#x27;t share with anyone --for this post I&#x27;m using a string from pwgen so it looks more like the actual config.&lt;&#x2F;p&gt;
&lt;p&gt;To avoid the tale of woe that I outlined above, if you&#x27;re slacking from Irssi, you need to &lt;em&gt;add a network&lt;&#x2F;em&gt; before adding the server. This changes the steps to:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure that your IRC client is configured with your normal Slack username as your nick.&lt;&#x2F;li&gt;
&lt;li&gt;Add a network with the command &lt;code&gt;&#x2F;network add myslack&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;If you are connecting through a raw &lt;code&gt;&#x2F;server&lt;&#x2F;code&gt; command, your command will be: &lt;code&gt;&#x2F;server add -auto -network myslack myserver.irc.slack.com 6667 myserver.Nosh5Neevot5Efua&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;See &lt;a href=&quot;https:&#x2F;&#x2F;irssi.org&#x2F;documentation&#x2F;startup&#x2F;&quot;&gt;the irssi docs&lt;&#x2F;a&gt; for more options. Join the desired channels on the Slack network just as you would in IRC. When you&#x27;re done, remember to &lt;code&gt;&#x2F;save&lt;&#x2F;code&gt;, and your &lt;code&gt;.irssi&#x2F;config&lt;&#x2F;code&gt; should contain something like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;servers = (
&lt;&#x2F;span&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;span&gt;  {
&lt;&#x2F;span&gt;&lt;span&gt;    address = &amp;quot;myserver.irc.slack.com&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    chatnet = &amp;quot;myslack&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    port = &amp;quot;6697&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    use_ssl = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    ssl_verify = &amp;quot;no&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    autoconnect = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    password = &amp;quot;mozilla.Nosh5Neevot5Efua&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;chatnets = {
&lt;&#x2F;span&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;span&gt;  myslack = { type = &amp;quot;IRC&amp;quot;; };
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;channels = (
&lt;&#x2F;span&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;span&gt;  { name = &amp;quot;#slackchannel&amp;quot;; chatnet = &amp;quot;myslack&amp;quot;; autojoin = &amp;quot;yes&amp;quot;; },
&lt;&#x2F;span&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;now-slack-is-almost-irc&quot;&gt;Now Slack is almost IRC&lt;&#x2F;h1&gt;
&lt;p&gt;With the bridge set up, Slack behaves mostly like IRC. There remain some outstanding differences:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You cannot leave the workspace&#x27;s default channel. You can &lt;a href=&quot;https:&#x2F;&#x2F;get.slack.help&#x2F;hc&#x2F;en-us&#x2F;articles&#x2F;204411433-Mute-a-channel&quot;&gt;mute the channel&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;get.slack.help&#x2F;hc&#x2F;en-us&#x2F;articles&#x2F;201649323-Set-channel-notification-preferences&quot;&gt;turn off notifications&lt;&#x2F;a&gt; but Slack won&#x27;t let you leave.&lt;&#x2F;li&gt;
&lt;li&gt;When someone uses &lt;code&gt;@here&lt;&#x2F;code&gt; in a channel, Slack appends your username to the end of the message when forwarding it along to IRC to make sure you get pinged. The person did not actually type your nick when it occurrs in this context.&lt;&#x2F;li&gt;
&lt;li&gt;If you want to hilight a Slack user in a message, you must inlcude the &lt;code&gt;@&lt;&#x2F;code&gt; in their username. If you just say the string of their name, they won&#x27;t get notified. This is the opposite of IRC, where it&#x27;s a newbie mistake to include someone&#x27;s hat when addressing them.&lt;&#x2F;li&gt;
&lt;li&gt;Slack has &lt;a href=&quot;https:&#x2F;&#x2F;get.slack.help&#x2F;hc&#x2F;en-us&#x2F;articles&#x2F;115000769927-Message-threads&quot;&gt;message threading&lt;&#x2F;a&gt; and allows &lt;a href=&quot;https:&#x2F;&#x2F;get.slack.help&#x2F;hc&#x2F;en-us&#x2F;articles&#x2F;202395258-Edit-or-delete-messages&quot;&gt;editing and deleting messages&lt;&#x2F;a&gt;, neither of which are really a thing on IRC. Remember that Slack sends the first version of each message to the IRC bridge. Messages in a thread will look like they were sent to the channel. Messages that were later deleted will persist in your IRC logs. Edits won&#x27;t show up; IRC bridge users see only the first version of each. If you need to view an edited message or edit or delete your message, you have to use the Slack UI.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Have fun!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Some northwest area tech conferences and their approximate dates</title>
        <published>2017-12-12T00:00:00+00:00</published>
        <updated>2017-12-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/12/12/an_incomplete_list_of_pacific_northwest_tech_conferences/"/>
        <id>https://edunham.net/2017/12/12/an_incomplete_list_of_pacific_northwest_tech_conferences/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/12/12/an_incomplete_list_of_pacific_northwest_tech_conferences/">&lt;p&gt;Somebody asked me recently about what conferences a developer in the pacific northwest looking to attend more FOSS events should consider. Here&#x27;s an incomplete list of conferences I&#x27;ve attended or hear good things about, plus the approximate times of year to expect their CFPs.&lt;&#x2F;p&gt;
&lt;p&gt;The Southern California Linux Expo (SCaLE) is a large, established Linux and FOSS conference in Pasadena, California. Look for its CFP at socallinuxexpo.org in September, and expect the conference to be scheduled in late February or early March each year.&lt;&#x2F;p&gt;
&lt;p&gt;If you don&#x27;t mind a short flight inland, OpenWest is a similar conference held in Utah each year. Look for its CFP in March at openwest.org, and expect the conference to happen around July. I especially enjoy the way that OpenWest brings the conference scene to a bunch of fantastic technologists who don&#x27;t always make it onto the national or international conference circuit.&lt;&#x2F;p&gt;
&lt;p&gt;Moving northward, there are a couple DevOps Days conferences in the area: Look for a PDX DevOps Days CFP around March and conference around September, and keep an eye out in case Boise DevOps Days returns.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re into a balance of intersectional community and technical content, consider OSBridge (opensourcebridge.org) held in Portland around June, and OSFeels (osfeels.com) held around July in Seattle.&lt;&#x2F;p&gt;
&lt;p&gt;In Washington state, LinuxFest Northwest (CFP around December, conference around April, linuxfestnorthwest.org) in Bellingham, and SeaGL (seagl.org, CFP around June, conference around October) in Seattle are solid grass-roots FOSS conferences. For infosec in the area, consider toorcamp (toorcamp.toorcon.net, registration around March, conference around June) in the San Juan Islands.&lt;&#x2F;p&gt;
&lt;p&gt;And finally, if a full conference seems like overkill, considering attending a BarCamp event in your area. Portland has CAT BarCamp (catbarcamp.org) at Portland State University around October, and Corvallis has Beaver BarCamp (beaverbarcamp.org) each April.&lt;&#x2F;p&gt;
&lt;p&gt;This is by no means a complete list of conferences in the area, and I haven&#x27;t even tried to list the myriad specialized events that spring up around any technology. Meetup, and calagator.org for the Portland area, are also great places to find out about meetups and events.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>User is not authorized to perform iam:ChangePassword.</title>
        <published>2017-11-29T00:00:00+00:00</published>
        <updated>2017-11-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/11/29/user_is_not_authorized_to_perform_iam_changepassword/"/>
        <id>https://edunham.net/2017/11/29/user_is_not_authorized_to_perform_iam_changepassword/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/11/29/user_is_not_authorized_to_perform_iam_changepassword/">&lt;p&gt;Summary: A user who is otherwise authorized to change their password may get this error when attempting to change their password to a string which violates the Password Policy in your IAM Account Settings.&lt;&#x2F;p&gt;
&lt;p&gt;So, I was setting up the 3rd or 4th user in a small team&#x27;s AWS account, and I did the usual: Go to the console, make a user, auto-generate a password for them, tick &quot;force them to change their password on next login&quot;, chat them the password and an admonishment to change it ASAP.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s a compromise between convenience and security that works for us at the moment, since there&#x27;s all of about 10 minutes during which the throwaway credential could get intercepted by an attacker, and I&#x27;d have the instant feedback of &quot;that didn&#x27;t work&quot; if anyone but the intended recipient performed the password change.&lt;&#x2F;p&gt;
&lt;p&gt;So, the 8th or 10th user I&#x27;m setting up, same way as all the others, gets that error on the change password screen: &quot;User is not authorized to perform iam:ChangePassword&quot;. Oh no, did I do their permissions wrong? I try explicitly attaching the Amazon&#x27;s IAMUserChangePassword policy to them, because that should fix their not being authorized, right? Wrong; they try again and they&#x27;re still &quot;not authorized&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;OK, I have their temp password because I just gave it to them, so I&#x27;ll pop open private browsing and try logging in as them.&lt;&#x2F;p&gt;
&lt;p&gt;When I try putting in the same autogenerated password at the reset screen, I get &quot;Password does not conform to the account password policy.&quot;. This makes sense; there&#x27;s a &quot;prevent password reuse&quot; policy enabled under Account Settings within IAM.&lt;&#x2F;p&gt;
&lt;p&gt;OK, we won&#x27;t reuse the password. I&#x27;ll just set it to that most seekrit string, &quot;hunter2&quot;. Nope, the &quot;User is not authorized to perform iam:ChangePassword&quot; is back. That&#x27;s funny, but consistent with the rules just being checked in a slightly funny order.&lt;&#x2F;p&gt;
&lt;p&gt;Then, on a hunch, I try the autogenerated password with a 1 at the end as the new password. It changes just fine and allows me to log in! So, the user did have authorization to change their password all along... they were just getting an actively misleading error message about what was going wrong.&lt;&#x2F;p&gt;
&lt;p&gt;So, if you get this &quot;User is not authorized to perform iam:ChangePassword&quot; error but you should be authorized, take a closer look at the temporary password that was generated for you. Make sure that your new password matches or exceeds the old one for having lowercase letters, uppercase letters, numbers, special characters, and total length.&lt;&#x2F;p&gt;
&lt;p&gt;When poking at it some more, I discovered that one also gets the &quot;User is not authorized to perform iam:ChangePassword&quot; message when one puts an invalid value into the &quot;current password&quot; box on the change password screen. So, check for typos there as well.&lt;&#x2F;p&gt;
&lt;p&gt;This yak shave took about an hour to pin down the fact that it was the contents of the password string generating the permissions error, and I haven&#x27;t been able to find the error string in any of Amazon&#x27;s actual documentation, so hopefully I&#x27;ve said &quot;User is not authorized to perform iam:ChangePassword&quot; enough times in this post that it pops up in search results for anyone else frustrated by the same challenge.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Better remote teaming with distributed standups</title>
        <published>2017-11-01T00:00:00+00:00</published>
        <updated>2017-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/11/01/better_remote_teaming_with_distributed_standups/"/>
        <id>https://edunham.net/2017/11/01/better_remote_teaming_with_distributed_standups/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/11/01/better_remote_teaming_with_distributed_standups/">&lt;p&gt;Agile development&#x27;s artifact of the daily stand-up meeting is a great idea. In theory, the whole team should stand together (sitting or eating makes meetings take too long) for about 5 minutes every morning. Each person should comment on:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;What they did since yesterday&lt;&#x2F;li&gt;
&lt;li&gt;What they plan on doing today&lt;&#x2F;li&gt;
&lt;li&gt;Any blockers, thigns they&#x27;re waiting on to be able to get work done&lt;&#x2F;li&gt;
&lt;li&gt;Anything else&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And then, 5 minutes later, everybody gets back to work. But do they really?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;problems-with-in-person-standups&quot;&gt;Problems with in-person standups&lt;&#x2F;h1&gt;
&lt;p&gt;When I&#x27;ve participated in stand-up meetings in person, I&#x27;ve noticed a few major flaws:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Context switching into and out of the meeting &lt;a href=&quot;http:&#x2F;&#x2F;www.paulgraham.com&#x2F;makersschedule.html&quot;&gt;impacts a maker&#x27;s schedule&lt;&#x2F;a&gt; by substantially more than the meeting&#x27;s planned 5 minutes.&lt;&#x2F;li&gt;
&lt;li&gt;People naturally tend to &lt;strong&gt;problem-solve&lt;&#x2F;strong&gt; during the meeting, and overcoming this urge to be helpful can be difficult and frustrating. However, allowing this problem-solving is a waste of most attendees&#x27; time and can drag the meeting out to over an hour if left unchecked.&lt;&#x2F;li&gt;
&lt;li&gt;The content of the meeting isn&#x27;t &lt;strong&gt;recorded&lt;&#x2F;strong&gt;. If I run into an issue that I think I recall Jane saying she was blocked on last week, I can either interrupt her work to ask her about it, send her an email and wait for her to reply, or just fight it myself. I can&#x27;t look it up anywhere.&lt;&#x2F;li&gt;
&lt;li&gt;If a team decides to keep notes, this &quot;&lt;a href=&quot;https:&#x2F;&#x2F;www.nytimes.com&#x2F;2015&#x2F;02&#x2F;08&#x2F;opinion&#x2F;sunday&#x2F;sheryl-sandberg-and-adam-grant-on-women-doing-office-housework.html?_r=2&quot;&gt;office housework&lt;&#x2F;a&gt; may be distributed inequitably among team members. Or if everyone takes turns taking notes, well... not everyone is necessarily skilled at note-taking, so there&#x27;s little guarantee that the notes will be consistently useful.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When an international team decides to pursue standup meetings through a synchronous medium like a phone or video call, it keeps all of these drawbacks while adding the problem of &lt;strong&gt;time zones&lt;&#x2F;strong&gt;. Let&#x27;s say your San Francisco-based company holds your daily standup at the perfectly sensible hour of 10am. Colleagues in New York City may love this, as it&#x27;s 1pm their time so they have plenty of time to prepare for the meeting. But your &quot;perfectly reasonable&quot; 10am standup isn&#x27;t so reasonable internationally: it&#x27;s 5pm for a colleague in London, 6pm in Paris, 6am the next day in Auckland, and sleep-worthy hours like 4AM the next day in Sydney and 2am, also the next day, in Tokyo.&lt;&#x2F;p&gt;
&lt;p&gt;Is demanding that some team members stay late at the office every day at the expense of family and personal commitments, or wake up before sunrise, the way that you want to treat your team? Is making a request like this, which disproportionately impacts your international colleagues, consistent with your values?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-better-way-robots&quot;&gt;The better way: Robots!&lt;&#x2F;h1&gt;
&lt;p&gt;If you&#x27;re familiar with my talks about community automation, you won&#x27;t be surprised at my excitement to share another robot which makes life better.&lt;&#x2F;p&gt;
&lt;p&gt;A team I&#x27;m on has recently started using a Slack app called &lt;a href=&quot;https:&#x2F;&#x2F;geekbot.io&#x2F;&quot;&gt;Geekbot&lt;&#x2F;a&gt; to perform asynchronous, inherently logged, on-task standup meetings. The only thing special about Geekbot is that somebody else has already done the coding, testing, and debugging -- if your team uses IRC or another chat client, the basic &quot;ask questions of each team member and post their answers, once per day&quot; functionality is trivial to implement on any extensible platform.&lt;&#x2F;p&gt;
&lt;p&gt;These distributed, asynchronous standups are the best standup meetings I&#x27;ve ever participated in. Why?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;They stay on task. You answer specific questions to the robot in PM, the robot posts your answers to a channel, and anyone who wants to chat about what you said has to do so outside the main thread of that conversation.&lt;&#x2F;li&gt;
&lt;li&gt;They&#x27;re asynchronous. If we added colleagues in any time zone, they could configure the bot to ping them at a time they find convenient, and the rest of the team could still keep up with their progress as long as everyone keeps reporting roughly every 24 hours.&lt;&#x2F;li&gt;
&lt;li&gt;Others&#x27; updates minimize interruption. Rather than dropping what I&#x27;m doing to take a call for a meeting, I can use my ordinary chat-multitasking skills to read my colleagues&#x27; updates while waiting on another task to complete.&lt;&#x2F;li&gt;
&lt;li&gt;They&#x27;re self-recording. I can look back after lunch and see what I claimed I&#x27;d get done today; I can search my chat logs for &quot;who was working on that component?&quot;. I strongly prefer to answer easy questions like this myself instead of interrupting others -- I save that for the difficult, interesting questions -- so I deeply appreciate this ability to solve my own problems with the meeting&#x27;s inherently perfect notes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Basically, these robot-powered, distributed standup check-ins are showing all of the benefits, and none of the major drawbacks, of in-person standup meetings.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-catch-culture&quot;&gt;The catch: Culture&lt;&#x2F;h1&gt;
&lt;p&gt;Why is it working? We&#x27;ve had similar standup type platforms before, and they work poorly if at all. I believe these standups are working better than prior attempts to automate the process for 2 main reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The standups are performed in a &lt;strong&gt;convenient location&lt;&#x2F;strong&gt;. Rather than having to remember to log into some service which exists only for the standup, it comes to you in the chat medium where you were doing the rest of your team communication.&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;strong&gt;team&#x27;s culture&lt;&#x2F;strong&gt; values filling them out. If someone skips a standup, others will ask them where they were or what was going on. If you added a bot without adding a culture of appreciating standups, everyone would simply ignore or block the bot and nothing would change.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So, assess how your standups are working. Do they take your target amount of time and stay focused? Can you refer to their contents later as you need to? If they could use improvement, it&#x27;s worth investigating how a robot could help you out.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Saying Ping</title>
        <published>2017-10-05T00:00:00+00:00</published>
        <updated>2017-10-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/10/05/saying_ping/"/>
        <id>https://edunham.net/2017/10/05/saying_ping/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/10/05/saying_ping/">&lt;p&gt;There&#x27;s an idiom on IRC, and to a lesser extent other more modern communication media, where people indicate interest in performing a real-time conversation with someone by saying &quot;ping&quot; to them. This effectively translates to &quot;I would like to converse with you as soon as you are available&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;The traditional response to &quot;ping&quot; is to reply with &quot;pong&quot;. This means &quot;I am presently available to converse with you&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;If the person who pinged is not available at the time that the ping&#x27;s recipient replies, what happens? Well, as soon as they see the pong, they re-ping (either by saying &quot;ping&quot; or sometimes &quot;re-ping&quot; if they are impersonating a sufficiently complex system to hold some state).&lt;&#x2F;p&gt;
&lt;p&gt;This attempt at communication, like &quot;phone tag&quot;, can continue indefinitey in its default state.&lt;&#x2F;p&gt;
&lt;p&gt;It is an inefficient use of both time and mental overhead, since each missed &quot;ping&quot; leaves the recipient with a vague curiosity or concern: &quot;I wonder what the person who pinged wanted to talk to me about...&quot;. Additionally, even if both parties manage to arrange synchronous communication at some point in the future, there&#x27;s the very real risk that the initiator may forget why they originally pinged at all.&lt;&#x2F;p&gt;
&lt;p&gt;There is an extremely simple solution to the inefficiency of waiting until both parties are online, which is to stick a little metadata about your question onto the ping. &quot;Ping, could you look issue # xyz?&quot; &quot;Ping, can we chat about your opinions on power efficiency sometime?&quot;. And yet there appears to be a decent correlation between people I regard as knowing more than I do about IRC etiquette, and people who issue pings without attaching any context to them.&lt;&#x2F;p&gt;
&lt;p&gt;If you do this, and happen to read this, could you please explain why to me sometime?&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Resumes: 1 page or more?</title>
        <published>2017-07-28T00:00:00+00:00</published>
        <updated>2017-07-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/07/28/resumes_1_page_or_more/"/>
        <id>https://edunham.net/2017/07/28/resumes_1_page_or_more/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/07/28/resumes_1_page_or_more/">&lt;p&gt;Some of my IRC friends are job hunting at the moment, so I&#x27;ve been proofreading resumes. These friends are several years into their professional careers at this point, and I&#x27;ve found it really interesting to see what they include and exclude to make the best use of their resumes&#x27; space.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;no-wasted-space&quot;&gt;No wasted space&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ve also stumbled into a rule of thumb that I like a lot better than the &quot;1 page rule&quot;: The rule of &lt;strong&gt;No Wasted Space&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you spread 1 page worth of stuff across 2 pages, you&#x27;re wasting a page worth of space. This forces any reader to spend 2 pages worth of time on your 1 page worth of content, which is an act of disrespect to them.&lt;&#x2F;p&gt;
&lt;p&gt;Big blank areas are a waste of space if they push your resume to more pages than it needs to be. On the other side of that same coin, however, a bunch of cramped small text wastes its space if it sacrifices easy legibility and scannability for the sake of cramming everything relevant onto a single sheet. And excess words where fewer would have communicated just as well are a waste of ink, as well as interviewer time!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;number-those-pages&quot;&gt;Number those pages&lt;&#x2F;h1&gt;
&lt;p&gt;Resumes that run to multiple pages are vastly improved by a little note on the corner of each: &quot;Surname, Page X of Y&quot;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;i-existed&quot;&gt;&quot;I existed!&quot;&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;m still surprised by how often people share awards and titles as &quot;got X award&quot;, &quot;was President of Organization&quot;. I hold the opinion that identical achievements sound much cooler with active rather than passive verbs: &quot;Earned X award&quot;, &quot;Led Organization&quot;, etc.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;stuff-in-more-numbers&quot;&gt;Stuff in more numbers&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;m a little annoyed by whatever cognitive bias is in play with this one: People sound better at what they do when they stuff more numbers into their descriptions, even when the numbers aren&#x27;t objectively very useful or necessary. &quot;Tutored students&quot; vs &quot;Tutored 30 students&quot;, &quot;Improved performance&quot; vs &quot;Doubled performance&quot;, etc. This is another compelling reason to instrument your systems and measure them before and after making major changes, which is something I personally need to improve at work as well :)&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-mirror-trick&quot;&gt;The Mirror Trick&lt;&#x2F;h1&gt;
&lt;p&gt;When you&#x27;ve been staring at a document for hours, it&#x27;s really hard to take a step back and tell what kind of first impression its formatting is going to make. To counter this, make your resume in the format you expect an interviewer to see it. Printed out or fullscreened on a laptop are common choices. Then either make your computer flip it horizontally, or just hold it up to a mirror and look at the reflection. This makes it look just new enough that you can spot glaring formatting errors instead of just reading the individual words -- &quot;What&#x27;s that enormous white space doing there?&quot;, &quot;Wait why is that indented to a different level from everything else?&quot;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Opinion: Levels of Safety Online</title>
        <published>2017-06-27T00:00:00+00:00</published>
        <updated>2017-06-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/06/27/internet_safety/"/>
        <id>https://edunham.net/2017/06/27/internet_safety/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/06/27/internet_safety/">&lt;p&gt;The Mozilla All-Hands this week gave me the opportunity to explore an exhibit about the &quot;Mozilla Worldview&quot; that Mitchell Baker has been working on. The exhibit sparked some interesting and sometimes heated discussion (as direct result of my decision to express unpopular-sounding opinions), and helped me refine &lt;strong&gt;my opinions&lt;&#x2F;strong&gt; on what it means for someone to be &quot;safe&quot; on the internet.&lt;&#x2F;p&gt;
&lt;p&gt;Spoiler: I think that there are many different levels of safety that someone can have online, and the most desirable ones are also the most difficult to attain.&lt;&#x2F;p&gt;
&lt;p&gt;Obligatory disclaimer: These are my opinions. You&#x27;re welcome to think I&#x27;m wrong. I&#x27;d be happy to accept pull requests to this post adding tools for attaining each level of safety, but if you&#x27;re convinced I&#x27;m wrong, the best place to say that would be your own blog. Feel free to drop me a link if you do write up something like that, as I&#x27;d enjoy reading it!&lt;&#x2F;p&gt;
&lt;p&gt;Safety to Consume Desired Information -----------------------------------&lt;&#x2F;p&gt;
&lt;p&gt;I believe that the fundamental layer of safety that someone can have online is to be able to safely &lt;strong&gt;consume information&lt;&#x2F;strong&gt;. Even at this basic level, a lot of things can go wrong. To safely consume information, people need internet access. This might mean free public WiFi, or a cell phone data plan. Safety here means that the user won&#x27;t come to harm solely as a result of what they choose to learn. &quot;Desired information&quot; means that the person gets a chance to find the &quot;best&quot; answer to their question that&#x27;s available.&lt;&#x2F;p&gt;
&lt;p&gt;How could someone come to harm as a result of choosing to learn something? If you&#x27;ve ever joked about a particular search getting you &quot;put on a watch list&quot;, I&#x27;m sure you can guess. I happen to hold the opinion that knowledge is an amoral tool, and it&#x27;s the actions that people take for which they should be held accountable -- if you believe that there exist facts that are inherently unethical to know, we&#x27;ll necessarily differ on the importance of this safety.&lt;&#x2F;p&gt;
&lt;p&gt;How might someone fail to get the information they desired? Imagine someone searching for the best open source social networking tools on a &quot;free&quot; internet connection that&#x27;s provided and monitored by a social networking giant. Do you think the articles that turn up in their search results would be comparable to what they&#x27;d get on a connection provided by a less biased organization?&lt;&#x2F;p&gt;
&lt;p&gt;Why &quot;desired information&quot;, and not &quot;truth&quot;? My reason here is selfish. I enjoy learning about different viewpoints held by groups who each insist that the other is completely wrong. If somebody tried to moderate what information is &quot;true&quot; and what&#x27;s &quot;false&quot;, I would probably only be allowed to access the propaganda of at most one of those groups.&lt;&#x2F;p&gt;
&lt;p&gt;Sadly, if your ISP is monitoring your internet connection or tampering with the content you&#x27;re trying to view, there&#x27;s not a whole lot that you can do about it. The usual solution is to relocate -- either physically, or feign relocation by using an onion router or proxy. By building better tools, legislation, and localization, it&#x27;s plausible that we could extend this safety to almost everyone in the world within our lifetimes.&lt;&#x2F;p&gt;
&lt;p&gt;Safety to Produce Information Anonymously ---------------------------------------&lt;&#x2F;p&gt;
&lt;p&gt;I think the next layer of internet safety that people need is the ability to &lt;strong&gt;produce information anonymously&lt;&#x2F;strong&gt;. The caveat here is that, of course, nobody else is obligated to choose to host your content for you. The safety of hosting providers, especially coupled with their ability to take financial payment while maintaining user anonymity, is a whole other can of worms.&lt;&#x2F;p&gt;
&lt;p&gt;Why does producing information anonymously come before producing information with attribution? Consider the types of danger that accompany producing content online. Attackers often choose their victims based on characteristics that the victims have in the physical world. Attempted attacks often cause harm because the attacker could identify the victim&#x27;s physical location or social identity. While the best solution would of course be to prevent the attackers from behaving harmfully at all, a less ambitious but more attainable project is to simply prevent them from being able to find targets for their aggression. Imagine an attacker determined to harm all people in a certain group, on an internet where nobody discloses whether or not they&#x27;re a member of that group: The attacker is forced to go for nobody or everybody, neither of which is as effective as an individually targeted attack. And that&#x27;s just for verbal or digital assaults -- it is extremely difficult to threaten or enact physical harm upon someone whose location you do not know.&lt;&#x2F;p&gt;
&lt;p&gt;Systems that support anonymity and arbitrary account creation open themselves to attempted abuse, but they also provide people with extremely powerful tools to avoid being abused. There are of course tradeoffs -- it takes a certain amount of mental overhead, and might feel duplicitous, to use separate accounts for discussing your unfashionable polticical views and planning the local block party -- but there&#x27;s no denying how much less harm it is possible to come to when behaving anonymously than when advertising your physical identity and location.&lt;&#x2F;p&gt;
&lt;p&gt;How do you produce information anonymously? First, you access the internet in a way that won&#x27;t make it easy to trace your activity to your house. This could mean booting from a LiveCD and accessing a public internet connection, or booting from a LiveCD and using a proxy or onion router to connect to the sites you wish to access in order to mask your IP address. A LiveCD is safer than using your day-to-day computer profile because browsers store information from sites you visit, and some information about your operating system is sometimes visible to sites you visit. Using a brand-new copy of your operating system, which forgets everything when you shut down, is an easy way to avoid revealing those identifying pieces of information.&lt;&#x2F;p&gt;
&lt;p&gt;Proof read anything that you want to post anonymously to make sure it doesn&#x27;t contain any details about where you live, or facts that only someone with your experiences would know.&lt;&#x2F;p&gt;
&lt;p&gt;How do you put information online anonymously? Once you have a connection that&#x27;s hard to trace to your real-world self, it&#x27;s pretty simple to set up free accounts on mail and web hosting sites under some placeholder name.&lt;&#x2F;p&gt;
&lt;p&gt;Be aware that the vocabulary you use and the way you structure your sentences can sometimes be identifying, as well. A good way to strip all of the uniqueness from your writing voice is to run a piece of writing through &lt;a href=&quot;http:&#x2F;&#x2F;hemingwayapp.com&#x2F;&quot;&gt;http:&#x2F;&#x2F;hemingwayapp.com&#x2F;&lt;&#x2F;a&gt; and fix everything that it calls an error. After that, use a thesaurus to add some words you don&#x27;t usually use anywhere else. Alternately, you could run it through a couple different translation tools to make it sound less like you wrote it.&lt;&#x2F;p&gt;
&lt;p&gt;How do you share something you wrote anonymously with your friends? Here&#x27;s the hard part: You don&#x27;t. If you&#x27;re not careful, the way that you distribute a piece of information that you wrote anonymously can make it clear that it came from you. Anonymously posted information generally has to be shared publicly or to an entire forum, because to pick and choose exactly which individuals get to see a piece of content reveals a lot about the identity of the person posting it.&lt;&#x2F;p&gt;
&lt;p&gt;Doing these things can enable you to produce a piece of information on the internet that would be a real nuisance to trace back to you in real life. It&#x27;s not &lt;em&gt;impossible&lt;&#x2F;em&gt;, of course -- there are sneaky tricks like comparing the times when you use a proxy to the times when material shows up online -- but someone would only attempt such tricks if they already had a high level of technical knowledge and a grudge against you in particular.&lt;&#x2F;p&gt;
&lt;p&gt;Long story short, in most places with internet access, it is possible but inconvenient to exercise your safety to produce information anonymously. By building better online tools and hosting options, we can extend this safety to more people who have internet access.&lt;&#x2F;p&gt;
&lt;p&gt;Safety to Produce Information Pseudonymously ------------------------------------------&lt;&#x2F;p&gt;
&lt;p&gt;An important thing to note about producing information anonymously is that if you step up and take credit for another piece of information you posted, you&#x27;re less anonymous. Add another attribution, and you&#x27;re easier still to track. It&#x27;s most anonymous to produce every piece of information under a different throwaway identity, and least anonymous to produce everything under a single identity even if it&#x27;s made up.&lt;&#x2F;p&gt;
&lt;p&gt;Producing information pseudonymously is when you use a fake name and biography, but otherwise go about the internet as the same person from day to day. The technical mechanics of producing a single pseudonymous post are identical to what I described for acting &quot;anonymously&quot;, but I differentiate psyedonymity from anonymity in that the former is continuous -- you can form friendships with other humans under a pseudonym.&lt;&#x2F;p&gt;
&lt;p&gt;The major hazard to a pseudonymous online presence is that if you aggregate enough details about your physical life under a single account, someone reading all those details might use them to figure out who you are offline. This is addressed by private forums and boards, which limit the number of possible attackers who can see your posts, as well as by being careful of what information you disclose. Beware, however, that any software vulnerability in a private forum may mean its contents suddenly becomes public.&lt;&#x2F;p&gt;
&lt;p&gt;In my opinion, pseudonymous identity is an excellent compromise between the social benefits of always being the same person, and physical safety from hypothetical attackers. I find that behaving pseudonymously rather than anonymously helps me build friendships with people whom I&#x27;m not sure at first whether to trust, while maintaining a sense of accountability for my reputation that&#x27;s absent in strictly anonymous communication. But hey, I&#x27;m biased -- you probably don&#x27;t know my full name or home address from my web presence, so I&#x27;m on the pseudonymity spectrum too.&lt;&#x2F;p&gt;
&lt;p&gt;Safety to Produce Information with Accurate Attribution -----------------------------------------------------&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;safety&quot; to produce information with attribution is extremely complex, and the one on which I believe that most social justice advocates tend to focus on. It is as it sounds: Will someone come to harm if they choose to post their opinions and location under their real name?&lt;&#x2F;p&gt;
&lt;p&gt;For some people, this is the easiest safety to acquire: If you&#x27;re in a group that&#x27;s not subject to hate crimes in your area, and your content is only consumed by people who agree with you or feel neutrally toward your views, you have this freedom by default.&lt;&#x2F;p&gt;
&lt;p&gt;For others, this safety is almost impossible to obtain. If the combination of your appearance and the views you&#x27;re discussing would get you hurt if you said it in public, extreme social change would be required before you had even a chance at being comparably safe online.&lt;&#x2F;p&gt;
&lt;p&gt;I hold the opinion that solving the general case of linking created content to real-world identities is not a computer problem. It&#x27;s a social problem, requiring a world in which no person offended by something on the internet and aware of where its creator lives is physically able to take action against the content&#x27;s creator. So it&#x27;d be great, but we are not there yet, and the only fictional worlds I&#x27;ve encountered in which this safety can be said to exist are impossibly unrealistic, totalitarian dystopias, or both.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;in-summary&quot;&gt;In Summary&lt;&#x2F;h1&gt;
&lt;p&gt;In other words, I view misuse of the internet as a pattern of the form &quot;Creator posts content -&amp;gt; attacker views content -&amp;gt; attacker identifies creator -&amp;gt; attacker harms creator&quot;. This chain can break, with varying degrees of difficulty, at several points:&lt;&#x2F;p&gt;
&lt;p&gt;First, this chain of outcomes won&#x27;t begin if the creator doesn&#x27;t post the content at all. This is the easiest solution, and I point out the &quot;safety to consume desired content&quot; because even someone who never posts online can derive major benefits from the information available on the internet. It&#x27;s easy, but it&#x27;s not good enough: Producing as well as consuming content is part of what sets the internet apart from TV or books.&lt;&#x2F;p&gt;
&lt;p&gt;The next essential link in the chain is the attacker identifying the content&#x27;s creator. If someone has no way to contact you physically or digitally, all they can do is shout nasty things to the world online, and you&#x27;re free to either ignore them or shout right back. Having nasty things shouted about your work isn&#x27;t optimal, but it is difficult to feel that your physical or social wellbeing is jeopardized by someone when they have no idea who you are. This is why I believe that the safety to produce information anonymously is so important: It uses software to change the outcome even in circumstances where the attacker&#x27;s behavior cannot be modified. Perfect pseudonymity also breaks this link, but any software mishap or accidental over-sharing can invalidate it instantly. The link is broken with fewer potential points of failure by creating content anonymously.&lt;&#x2F;p&gt;
&lt;p&gt;The third solution is what I alluded to when discussing the safety of pseudonymity: Prevent the attacker from viewing the content. This is what private, interest-specific forums accomplish reasonably well. There are hazards here, especially if a forum&#x27;s contents become public unintentionally, or if a dedicated attacker masquerades as a member of the very group they wish to harm. So it helps, and can be improved technologically through proper security practices by forum administrators, and socially via appropriate moderation. It&#x27;s better, from the perspective that assuming the same online identity each day allows creators to build social bonds with one another, but it&#x27;s still not optimal.&lt;&#x2F;p&gt;
&lt;p&gt;The fourth and ideal solution is to break the cycle right at the very end, by preventing the attacker from harming the content creator. This seems to be where most advocates argue we should jump straight into, because it&#x27;s really perfect -- it requires no change or compromise from content creators, and total change from those who might be out to harm them. It&#x27;s the only solution in which people of all appearances and beliefs and locations are equally safe online. However, it&#x27;s also the most difficult place to break the cycle, and a place at which any error of implementation would create the potential for incalculable abuse.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve listed these safeties in an order that I regard as how feasible they are to implement with today&#x27;s social systems and technologies. I think it&#x27;s possible to recognize the 4th safety as the top of the heap, without using that as an excuse to neglect the benefits which can come from bringing more of the world its 3 lesser but far more attainable cousins.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Salt: Successful ping but highstate says &quot;minion did not return&quot;</title>
        <published>2017-05-23T00:00:00+00:00</published>
        <updated>2017-05-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/05/23/salt_successful_ping_but_highstate_says_minion_did_not_return/"/>
        <id>https://edunham.net/2017/05/23/salt_successful_ping_but_highstate_says_minion_did_not_return/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/05/23/salt_successful_ping_but_highstate_says_minion_did_not_return/">&lt;p&gt;Today I was setting up some new OSX hosts on Macstadium for Servo&#x27;s build cluster. The hosts are managed with SaltStack.&lt;&#x2F;p&gt;
&lt;p&gt;After installing &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;servo&#x2F;servo&#x2F;wiki&#x2F;SaltStack-Administration#osx&quot;&gt;all the things&lt;&#x2F;a&gt;, I ran a test ping and it looked fine:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;user@saltmaster:~$ salt newbuilder test.ping
&lt;&#x2F;span&gt;&lt;span&gt;newbuilder:
&lt;&#x2F;span&gt;&lt;span&gt;    True
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;However, running a highstate caused Salt to claim the minion was non-responsive:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;user@saltmaster:~$ salt newbuilder state.highstate
&lt;&#x2F;span&gt;&lt;span&gt;newbuilder:
&lt;&#x2F;span&gt;&lt;span&gt;    Minion did not return. [No response]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Googling this problem yielded a bunch of other &quot;minion did not return&quot; kind of issues, but nothing about what to do when the minion sometimes returns fine and other times does not.&lt;&#x2F;p&gt;
&lt;p&gt;The fix turned out to be simple: When a test ping succeeds but a longer-running state fails, it&#x27;s an issue with the master&#x27;s timeout setting. The timeout defaults to 5 seconds, so a sufficiently slow job will look to the master like the minion was unreachable.&lt;&#x2F;p&gt;
&lt;p&gt;As explained in &lt;a href=&quot;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;topics&#x2F;troubleshooting&#x2F;master.html#commands-time-out-or-do-not-return-output&quot;&gt;the Salt docs&lt;&#x2F;a&gt;, you can bump the timeout by adding the line &lt;code&gt;timeout: 30&lt;&#x2F;code&gt; (or whatever number of seconds you choose) to the file &lt;code&gt;&#x2F;etc&#x2F;salt&#x2F;master&lt;&#x2F;code&gt; on the salt master host.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Advice on storing encryption keys</title>
        <published>2017-03-01T00:00:00+00:00</published>
        <updated>2017-03-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/03/01/advice_on_storing_encryption_keys/"/>
        <id>https://edunham.net/2017/03/01/advice_on_storing_encryption_keys/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/03/01/advice_on_storing_encryption_keys/">&lt;p&gt;I saw an excellent question get some excellent infosec advice on IRC recently. I&#x27;m quoting the discussion here because I expect that I&#x27;ll want to reference it when answering others&#x27; questions in the future.&lt;&#x2F;p&gt;
&lt;p&gt;A user going by &lt;code&gt;Dagnabit&lt;&#x2F;code&gt; asked:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;May I borrow some advice specifically on how best to store an ecryption key? I have a python script that encrypts files using libsodium, My question is how can I securely store the encryption key within the file system? Is it best kept in an sqlite db that can only be accessed by the user owning the python script?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This user has run right into one of the fundamental challenges of security: How can my secrets (in this case, keys) be safe from attackers, while still being usable?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;binaryredneck.net&#x2F;&quot;&gt;HedgeMage&lt;&#x2F;a&gt; replied with a wall of useful advice. Quotations are her words, links and annotations between them are me adding some context and opinions.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;So, it depends on your security model: in most cases I&#x27;m prone to keeping my encryption key on a hardware token, so that even if the server is compromised, the secret key is not.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;You&#x27;re probably familiar with time-based one-time-pad &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Security_token&quot;&gt;hardware tokens&lt;&#x2F;a&gt;, but in the case of key management, the &quot;hardware token&quot; could be as simple as a USB stick locked in a safe. On the spectrum of compromise between security and convenience, a hardware token is toward the &lt;a href=&quot;https:&#x2F;&#x2F;www.schneier.com&#x2F;blog&#x2F;archives&#x2F;2010&#x2F;07&#x2F;dnssec_root_key.html&quot;&gt;DNSSEC keyholder&lt;&#x2F;a&gt; end.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;However, for some projects you are on virtualized infrastructure and can&#x27;t plug in a hardware token. It&#x27;s unfortunate, because that&#x27;s really the safest thing, but a reality for many of us.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This also applies to physical infrastructure in which an application might need to use a key without human supervision.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Without getting into anything crazy where a proxy server does signing, etc, you usually are better off trusting filesystem permissions than stuffing it in the database, for the following reasons:&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;While delegating the task of signing to a proxy server can make life more annoying to an attacker, you&#x27;re still going to have to choose between having a human hold the key and get interrupted whenever it&#x27;s needed, or trusting a server with it, at some point. You can compromise between those two extremes by using a setup like &lt;a href=&quot;https:&#x2F;&#x2F;wiki.debian.org&#x2F;Subkeys&quot;&gt;subkeys&lt;&#x2F;a&gt;, but it&#x27;s still inconvenient if a subkey gets compromised.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;* It&#x27;s easier to monitor the filesystem activity comprehensively, and detect intrusions&#x2F;compromises.&lt;&#x2F;p&gt;
&lt;p&gt;* Filesystem permissions are pretty dependable at this point, and if the application doing the signing has permission for the key, whether in a DB or the filesystem, it can compromise that key... so the database is giving you new attack surfaces (compromise of the DB access model) without any new protections.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;To put it even more bluntly, &lt;em&gt;any&lt;&#x2F;em&gt; unauthorized access to a machine has the potential to leak &lt;em&gt;all&lt;&#x2F;em&gt; of the secrets on it. The actions that you&#x27;ll need to take if you suspect the filesystem of a host was compromised are pretty much identical to those you&#x27;d take if the DB was.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;* Stuffing the key in the DB is nonstandard enough that you may be writing more of the implementation yourself, instead of depending as much as possible on widely-used, frequently-examined code.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Dagnabit&#x27;s reply saved me the work of summarizing the key takeaways:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;I will work on securing the distrubtion and removing any unnecessary packages.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll look at the possibility of using a hardware token to keep it secure&#x2F;private.&lt;&#x2F;p&gt;
&lt;p&gt;Reducing the attack surface is logical and something I had not considered.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Tech Internship Hunting Ideas</title>
        <published>2017-01-23T00:00:00+00:00</published>
        <updated>2017-01-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2017/01/23/tech_internship_hunting_ideas/"/>
        <id>https://edunham.net/2017/01/23/tech_internship_hunting_ideas/</id>
        
        <content type="html" xml:base="https://edunham.net/2017/01/23/tech_internship_hunting_ideas/">&lt;p&gt;A question from a computer science student crossed one of my IRC channels recently:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Them: what is the best way to fish for internships over the summer?
&lt;&#x2F;span&gt;&lt;span&gt;    Glassdoor?
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Me: It depends on what kind of internship you&amp;#39;re looking for. What kind of
&lt;&#x2F;span&gt;&lt;span&gt;    internship are you looking for?
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Them: Computer Science, anything really.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This caused me to type out a lot of advice. I&#x27;ll restate and elaborate on it here, so that I can provide a more timely and direct summary if the question comes up again.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;philosophy-of-job-hunting&quot;&gt;Philosophy of Job Hunting&lt;&#x2F;h1&gt;
&lt;p&gt;My opinion on job hunting, especially for early-career technologists, is that it&#x27;s important to get multiple offers whenever possible. Only once one has a viable alternative can one be said to truly choose a role, rather than being forced into it by financial necessity.&lt;&#x2F;p&gt;
&lt;p&gt;In my personal experience, cultivating multiple offers was an important step in disentangling impostor syndrome from my career choices. Multiple data points about one&#x27;s skills being valued by others can help balance out an internal monologue about how much one has yet to learn.&lt;&#x2F;p&gt;
&lt;p&gt;If you disagree that cultivating simulataneous opportunities then politely declining all but the best is a viable internship hunting strategy, the rest of this post may not be relevant or interesting to you.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;identifying-your-options&quot;&gt;Identifying Your Options&lt;&#x2F;h1&gt;
&lt;p&gt;To get an internship offer, you need to make a &lt;strong&gt;compelling application&lt;&#x2F;strong&gt; to a &lt;strong&gt;company which might hire you&lt;&#x2F;strong&gt;. I find that a useful first step is to come up with a list of such companies, so you can study their needs and determine what will make your application interest them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Use your social network&lt;&#x2F;strong&gt;. Ask your peers about what internships they&#x27;ve had or applied for. Ask your mentors whether they or their friends and colleagues hire interns.&lt;&#x2F;p&gt;
&lt;p&gt;When you ask someone about their experience with a company, remember to ask for their opinion of it. To put that opinion into perspective, it&#x27;s useful to also ask about their personal preferences for what they enjoy or hate about a workplace. Knowing that someone who prefers to work with a lot of background noise enjoyed a company&#x27;s busy open-plan office can be extremely useful if you need silence to concentrate! Listening with interest to a person&#x27;s opinions also strengthens your social bond with them, which never hurts if it turns out they can help you get into a company that you feel might be a good fit.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Use LinkedIn, Hacker News, Glassdoor, and your city&#x27;s job boards&lt;&#x2F;strong&gt;. The broader a net you cast to start with, the better your chances of eventually finding somewhere that you enjoy. If your job hunt includes certain fields (web dev, DevOps, big data, whatever), investigate whether there&#x27;s a meetup for professionals in that field in your region. If you have the opportunity to give a short talk on a personal project at such a meetup, do it and make sure to mention that you&#x27;re looking for an internship.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;identify-your-own-priorities&quot;&gt;Identify your own priorities&lt;&#x2F;h1&gt;
&lt;p&gt;Now that you have a list of places which might concievably want to hire you, it&#x27;s time to do some &lt;strong&gt;introspection&lt;&#x2F;strong&gt;. For each field that you&#x27;ve found a prospective company in, try to answer the question &lt;strong&gt;&quot;What makes you excited about working here?&quot;&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;You do not have to have know what you want to do with your life to know that, right now, you think DevOps or big data or frontend development is cool.&lt;&#x2F;p&gt;
&lt;p&gt;You do not have to personally commit to a single passion at the expense of all others -- it&#x27;s perfectly fine to be interested in several different languages or frameworks, even if the tech media tries to pit them against each other.&lt;&#x2F;p&gt;
&lt;p&gt;However, for each application, it&#x27;s prudent to only emphasize your interests in that particular field. It&#x27;s a bit of a faux pas to show up to a helpdesk interview and focus the whole time on your passion for building robots, or vice versa. And acting equally interested in every other field will cause an employer to doubt that you&#x27;re necessarily the best fit for a specialized role... So in an interview, try not to stray too far from the &lt;strong&gt;value that you&#x27;re able to deliver&lt;&#x2F;strong&gt; to that company.&lt;&#x2F;p&gt;
&lt;p&gt;This is also a good time to identify any &lt;strong&gt;deal-breakers&lt;&#x2F;strong&gt; that would cause you to decline a prospective internship. Are you ok with relocating? Is there some tool or technology that would cause you to dread going to work every day?&lt;&#x2F;p&gt;
&lt;p&gt;I personally think that it&#x27;s worth applying even to a role that you know you wouldn&#x27;t accept an offer from when you&#x27;re early in your career. If they decide to interview you, you&#x27;ll get practice experiencing a real interview without the pressure of &quot;I&#x27;ll lose my chance at my dream job if I mess this up!&quot;. Plus if they extend an offer to you, it can help you calibrate the financial value of your skills and negotiate with employers that you&#x27;d actually enjoy.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;craft-an-excellent-resume&quot;&gt;Craft an excellent resume&lt;&#x2F;h1&gt;
&lt;p&gt;I talk about this &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;tags&#x2F;resume.html&quot;&gt;elsewhere&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;There are a couple extra notes if you&#x27;re applying for an internship:&lt;&#x2F;p&gt;
&lt;p&gt;1) Emphasize the parts of your experience that relate most closely to what each employer values. If you can, it&#x27;s great to use the same words for skills that were used in the job description.&lt;&#x2F;p&gt;
&lt;p&gt;2) The bar for what skills go on your resume is lower when you have less experience. Did you play with Docker for a weekend recently and use it to deploy a toy app? Make sure to include that experience.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;practice-practice-practice&quot;&gt;Practice, Practice, Practice&lt;&#x2F;h1&gt;
&lt;p&gt;If you&#x27;re uncomfortable with interviewing, do it until it becomes second nature. If your current boss supports your internship search, do some mock interviews with them. If you&#x27;re nervous about things going wrong, have a friend roleplay as a really bad interview with you to help you practice coping strategies. If you&#x27;ll be in front of a panel of interviewers, try to get a panel of friends to gang up on you and ask tough questions!&lt;&#x2F;p&gt;
&lt;p&gt;To some readers this may be obvious, but to others it&#x27;s worth pointing out that you should also &lt;strong&gt;practice wearing the clothes&lt;&#x2F;strong&gt; that you&#x27;ll wear to an interview. If you wear a tie, learn to tie it well. If you wear shirts or pants that need to be ironed, learn to iron them comptently. If you wear shoes that need to be shined, learn to shine them. And if your interview will include lunch, learn to eat with good table manners and avoid spilling food on yourself.&lt;&#x2F;p&gt;
&lt;p&gt;Yes, the day-to-day dress codes of many tech offices are solidly in the &quot;sneakers, jeans, and t-shirt&quot; category for employees of all levels and genders. But many interviewers, especially mid- to late-career folks, grew up in an age when dressing casually at an interview was a sign of incompetence or disrespect. Although some may make an effort to overcome those biases, the subconscious conditioning is often still there, and you can take advantage of it by wearing at least business casual.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;apply-everywhere&quot;&gt;Apply Everywhere&lt;&#x2F;h1&gt;
&lt;p&gt;If you know someone at a company where you&#x27;re applying, try to get their feedback on how you can tailor your resume to be the best fit for the job you&#x27;re looking at! They might even be able to introduce you personally to your potential future boss.&lt;&#x2F;p&gt;
&lt;p&gt;I think it&#x27;s worth submitting a good resume to every company which you identify as being possibly interested in your skills, even the ones you don&#x27;t currently think you want to work for. Interview practice is worth more in potential future salary than the hours of your time it&#x27;ll take at this point in your career.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;follow-up&quot;&gt;Follow Up&lt;&#x2F;h1&gt;
&lt;p&gt;If you don&#x27;t hear back from a company for a couple weeks, a polite note is order. Restate your enthusiasm for their company or field, express your understanding that there are a lot of candidates and everything is busy, and politely solicit any feedback that they may be able to offer about your application. A delayed reply does not always mean rejection.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re rejected, follow up to thank HR for their time.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re invited to interview, reply promptly and set a time and date. For a virtual or remote interview, only offer times when you&#x27;ll have access to a quiet room with a good network connection.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;interview-excellently&quot;&gt;Interview Excellently&lt;&#x2F;h1&gt;
&lt;p&gt;I don&#x27;t have any advice that you won&#x27;t find a hundred times over on the rest of the web. The key points are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Show up on time, looking respectable&lt;&#x2F;li&gt;
&lt;li&gt;Let&#x27;s hope you didn&#x27;t lie on your resume&lt;&#x2F;li&gt;
&lt;li&gt;Restate each question in your answer&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s ok not to know an answer -- state what you would do if you encountered the problem at work. Would you Google a certain phrase? Ask a colleague? Read the manual?&lt;&#x2F;li&gt;
&lt;li&gt;Always ask questions at the end. When in doubt, ask your interviewer what they enjoy about working for the company.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;keep-following-up&quot;&gt;Keep Following Up&lt;&#x2F;h1&gt;
&lt;p&gt;After your interview, write to whoever arranged it and thank the interviewers for their time. For bonus points, mention something that you talked about in the interview, or include the answer to a question that you didn&#x27;t know off the top of your head at the time.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;getting-an-offer&quot;&gt;Getting an Offer&lt;&#x2F;h1&gt;
&lt;p&gt;Recruiters don&#x27;t usually like to disclose the details of offers in writing right away. They&#x27;ll often phone you to talk about it. You do not have to accept or decline during that first call -- if you&#x27;re trying to stall for a bit more time for another company to get back to you, an excuse like &quot;I&#x27;ll have to run that by my family to make sure those details will work&quot; is often safe.&lt;&#x2F;p&gt;
&lt;p&gt;Remember, though, that &lt;strong&gt;no offer is really a job until both you and the employer have signed a contract&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;declining-offers&quot;&gt;Declining Offers&lt;&#x2F;h1&gt;
&lt;p&gt;If you&#x27;ve applied to enough places with a sufficiently compelling resume, you&#x27;ll probably have multiple offers. If you&#x27;re lucky, they&#x27;ll all arrive around the same time.&lt;&#x2F;p&gt;
&lt;p&gt;If you wish to decline an offer from a company whom you&#x27;re certain you don&#x27;t want to work for, you can practice your negotiation skills. Read up on salary negotiation, try to talk the company into making you a better offer, and observe what works and what doesn&#x27;t. It&#x27;s not super polite to invest a bunch of their time in negotiations and then turn them down anyway, which is why I suggest only doing this to a place that you&#x27;re not very fond of.&lt;&#x2F;p&gt;
&lt;p&gt;To decline an offer without burning any bridges, be sure to thank them again for their time and regretfully inform them that you&#x27;ll be pursuing other opportunities at this time. It never hurts to also do them a favor like recommending a friend who&#x27;s job hunting and might be a good fit.&lt;&#x2F;p&gt;
&lt;p&gt;Again, though, don&#x27;t decline an offer until you have your actual job&#x27;s contract in writing.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Rust&#x27;s Community Automation</title>
        <published>2016-09-27T00:00:00+00:00</published>
        <updated>2016-09-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/09/27/rust_s_community_automation/"/>
        <id>https://edunham.net/2016/09/27/rust_s_community_automation/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/09/27/rust_s_community_automation/">&lt;p&gt;Here&#x27;s the text version, with clickable links, of my Automacon lightning talk today.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;m a DevOps engineer at Mozilla Research and a member of the Rust Community subteam, but the conclusions and suggestions in this talk are my own observations and opinions.&lt;&#x2F;p&gt;
&lt;p&gt;The slides are a result of trying to write my own CSS for &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kmcallister&#x2F;sliderust&quot;&gt;sliderust&lt;&#x2F;a&gt;... Sorry about the ugliness.&lt;&#x2F;p&gt;
&lt;p&gt;I have 10 minutes, so this is not the time to teach you any Rust. Check out &lt;a href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&#x2F;en-US&#x2F;&quot;&gt;rust-lang.org&lt;&#x2F;a&gt;, the &lt;a href=&quot;https:&#x2F;&#x2F;rust-community.github.io&#x2F;resources&#x2F;&quot;&gt;Rust Community Resources&lt;&#x2F;a&gt;, or your city&#x27;s Rust meetup to get started with the language.&lt;&#x2F;p&gt;
&lt;p&gt;What we &lt;em&gt;are&lt;&#x2F;em&gt; going to cover is how Rust automates some community tasks, and what you can learn from our automation.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;community&quot;&gt;Community&lt;&#x2F;h1&gt;
&lt;p&gt;I define &quot;community&quot;, in this context, as &quot;the human interaction work necessary for a project&#x27;s success&quot;. This work is done by a wide variety of people in many situations. Every interaction, from helping a new contributor to discussing a proposed code change to criticizing someone&#x27;s behavior, affects the overall climate of a project&#x27;s community.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;automation&quot;&gt;Automation&lt;&#x2F;h1&gt;
&lt;p&gt;To me, &quot;automation&quot; means &quot;offloading peoples&#x27; work onto a system&quot;. This can be a computer system, but I think it can also mean changes to the social systems that guide peoples&#x27; behavior.&lt;&#x2F;p&gt;
&lt;p&gt;So, community automation is a combination of:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Building tools to do things the humans used to have to&lt;&#x2F;li&gt;
&lt;li&gt;Tweaking the social systems to minimize the overhead they create&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;scoping-the-problem&quot;&gt;Scoping the Problem&lt;&#x2F;h1&gt;
&lt;p&gt;While not all things can be automated and not all factors of the community are under the project leadership&#x27;s control, it&#x27;s not totally hopeless.&lt;&#x2F;p&gt;
&lt;p&gt;Choices made and automation deployed by project leaders can help control:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Which contributors feel welcome or unwelcome in a project&lt;&#x2F;li&gt;
&lt;li&gt;What code makes it into the project&#x27;s tree&lt;&#x2F;li&gt;
&lt;li&gt;Robots!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;moderation&quot;&gt;Moderation&lt;&#x2F;h1&gt;
&lt;p&gt;Our robots and social systems to improve workflow and contributor experience all rely on community members&#x27; cooperation. To create a community of people who want to work constructively together and not be jerks to each other, Rust defines behavior expectations &lt;a href=&quot;https:&#x2F;&#x2F;rust-community.github.io&#x2F;resources&#x2F;&quot;&gt;code of conduct&lt;&#x2F;a&gt;. The important thing to note about the CoC is that half the document is a clear explanation of how the policies in it will be enforced. This would be impossible without the dedication of the amazing &lt;a href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&#x2F;en-US&#x2F;team.html#Moderation-team&quot;&gt;mod team&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The process of moderation cannot and should not be left to a computer, but we can use technology to make our mods&#x27; work as easy as possible. We leave the human tasks to humans, but let our technologies do the rest.&lt;&#x2F;p&gt;
&lt;p&gt;In this case, while the mods need to step in when a human has a complaint about something, we can automate the process of telling peole that the rules exist. You can&#x27;t join the IRC channel, post on the Discourse forums, or even read the Rust subreddit without being made aware that you&#x27;re expected to follow the CoC&#x27;s guidelines in official Rust spaces.&lt;&#x2F;p&gt;
&lt;p&gt;Depending on the forums where your project communicates, try to automate the process of excluding obvious spammers and trolls. Not everybody has the skills or interest to be an excellent moderator, so when you find them, avoid wasting their time on things that a computer could do for them!&lt;&#x2F;p&gt;
&lt;p&gt;It didn&#x27;t fit in the talk, but &lt;a href=&quot;https:&#x2F;&#x2F;developers.slashdot.org&#x2F;comments.pl?sid=8652809&amp;amp;cid=51352141&quot;&gt;this Slashdot post&lt;&#x2F;a&gt; is one of my favorite examples of somebody being filtered out of participating in the Rust community due to their personal convictions about how project leadership should work. While we do miss out on that person&#x27;s potential technical contributions, we also save all of the time that might be spent hashing out our disagreements with them if we had a less clear set of community guideines.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;robots&quot;&gt;Robots&lt;&#x2F;h2&gt;
&lt;p&gt;This lightning talk highlighted 4 categories of robots:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Maintaining code quality&lt;&#x2F;li&gt;
&lt;li&gt;Engaging in social pleasantries&lt;&#x2F;li&gt;
&lt;li&gt;Guiding new contributors&lt;&#x2F;li&gt;
&lt;li&gt;Widening the contributor pipeline&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Longer versions of this talk also touch on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brson&#x2F;taskcluster-crater&quot;&gt;automatically testing compiler releases&lt;&#x2F;a&gt;, but that&#x27;s more than 10 minutes of content on its own.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-not-rocket-science-rule-of-software-engineering&quot;&gt;The Not Rocket Science Rule of Software Engineering&lt;&#x2F;h1&gt;
&lt;p&gt;To my knowledge, &lt;a href=&quot;http:&#x2F;&#x2F;graydon.livejournal.com&#x2F;186550.html&quot;&gt;this blog post&lt;&#x2F;a&gt; by Rust&#x27;s inventor Graydon Hoare is the first time that this basic principle has been put so succinctly:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Automatically maintain a repository of code that always passes all the tests.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This policy guides the Rust compiler&#x27;s development workflow, and has trickled down into libraries and projects using the language.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;bors&quot;&gt;Bors&lt;&#x2F;h1&gt;
&lt;p&gt;The name Bors has been handed down from Graydon&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;graydon&#x2F;bors&quot;&gt;original autolander bot&lt;&#x2F;a&gt; to an instance of &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;servo&#x2F;homu&quot;&gt;Homu&lt;&#x2F;a&gt;, and is often verbed to refer to the simple actions he does:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Notice when a human says &quot;r+&quot; on a PR&lt;&#x2F;li&gt;
&lt;li&gt;Create a branch that looks like master will after the change is applied&lt;&#x2F;li&gt;
&lt;li&gt;Test that branch&lt;&#x2F;li&gt;
&lt;li&gt;Fastforward the master branch to the tested state, if it passed.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;keep-your-tree-green&quot;&gt;Keep your tree green&lt;&#x2F;h1&gt;
&lt;p&gt;Saying &quot;we can&#x27;t break the tests any more&quot; is a pretty significant cultural change, so be prepeared for some resistance. With that disclaimer, the path to following the Not Rocket Science Rule is pretty simple:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Write tests that fail when your code is bad and pass when it&#x27;s good&lt;&#x2F;li&gt;
&lt;li&gt;Run the tests on every change&lt;&#x2F;li&gt;
&lt;li&gt;Only merge code if it passes all the tests&lt;&#x2F;li&gt;
&lt;li&gt;Fix the tests whenever thy&#x27;re wrong.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This strategy encourages people to maintain the tests, because a broken test becomes everyone&#x27;s problem and interrupts their workflow until it&#x27;s fixed.&lt;&#x2F;p&gt;
&lt;p&gt;I believe that tests are necessary for all code that people work on. If the code was fully and perfectly correct, it wouldn&#x27;t need changes -- we only write code when something is wrong, whether that&#x27;s &quot;It crashes&quot; or &quot;It lacks such-and-such a feature&quot;. And regardless of the changes you&#x27;re making, tests are essential for catching any regressions you might accidentally introduce.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;automating-social-pleasantries&quot;&gt;Automating social pleasantries&lt;&#x2F;h1&gt;
&lt;p&gt;Have you ever submitted an issue or change request to a project, then not heard back for several months? It feels bad to be ignored, and the project loses out on potential contributors.&lt;&#x2F;p&gt;
&lt;p&gt;Rust automates basic social pleasantries with a robot called &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nrc&#x2F;highfive&quot;&gt;Highfive&lt;&#x2F;a&gt;. Her tasks are easy to explain, though the implementaion details can be tricky:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Notice when a change is submitted by a new contributor, then welcome them&lt;&#x2F;li&gt;
&lt;li&gt;Assign reviewers, based on what code changed, to all PRs&lt;&#x2F;li&gt;
&lt;li&gt;Nag the reviewer if they seem to have forgotten about their assignment&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If you don&#x27;t want a dedicated greeter-bot, you can get many of these features from your code management system:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Use issue and pull request templates to guide potential contributors to the docs that can help them improve their report or request.&lt;&#x2F;li&gt;
&lt;li&gt;Configure notifications so you find out when someone is trying to interact with your project. This could mean muting all the noise notifications so the signal ones are available, or intermittently polling the repositories that you maintain (a daily cron job or weekly calendar reminder works just fine).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;guide-new-contributors&quot;&gt;Guide new contributors&lt;&#x2F;h1&gt;
&lt;p&gt;In open source projects, &quot;I&#x27;m new; what can I work on?&quot; is a common inquiry. In internal projects, you&#x27;ll often meet colleagues from elsewhere in your organization who ask you to teach them something about the project or the skills you use when working on it.&lt;&#x2F;p&gt;
&lt;p&gt;The Rust-implemented browser engine &lt;a href=&quot;https:&#x2F;&#x2F;servo.org&#x2F;&quot;&gt;Servo&lt;&#x2F;a&gt; is actually a slightly better example of this than the compiler itself, since the smaller and younger codebase has more introductory-level issues remaining. The site &lt;a href=&quot;http:&#x2F;&#x2F;starters.servo.org&#x2F;&quot;&gt;starters.servo.org&lt;&#x2F;a&gt; automatically scrapes the organization&#x27;s issue trackers for easy and unclaimed issues.&lt;&#x2F;p&gt;
&lt;p&gt;Issue triage is often unrewarding, but using the tags for a project like this creates a greater incentive to keep them up to date.&lt;&#x2F;p&gt;
&lt;p&gt;When filing introductory issues, try to include links to the relevant documentation, instructions for reproducing the bug, and a suggestion of what file you would look in first if you tackled the problem yourself.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;automating-mentorship&quot;&gt;Automating mentorship&lt;&#x2F;h1&gt;
&lt;p&gt;Mentorship is a highly personalized process in which one human transfers their skills to another. However, large projects often have more contributors seeking the same basic skills than mentors with time to teach them.&lt;&#x2F;p&gt;
&lt;p&gt;The parts of mentorship which don&#x27;t explicitly require a human mentor can be offloaded onto technology.&lt;&#x2F;p&gt;
&lt;p&gt;The first way to automate mentorship tasks is to maintain correct and up-to-date documentation. Correct docs train humans to consult them before interrupting an expert, whereas docs that are frequently outdated or wrong condition their users to skip them entirely.&lt;&#x2F;p&gt;
&lt;p&gt;Use tools like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;labhr&#x2F;octohatrack&quot;&gt;octohatrack&lt;&#x2F;a&gt; and your project status updates to identify and recognize contributors who help with docs and issue triage. Docs contributions may actually save more developer and community time than new code features, so respect them accordingly.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, maintain a list of introductory or mentored issues -- even if that&#x27;s just a Google Doc or Etherpad.&lt;&#x2F;p&gt;
&lt;p&gt;Bear in mind that an introductory issue doesn&#x27;t necessarily mean &quot;suitable for someone who has never coded before&quot;. Someone with great skills in a scripting language might be looking for a place to help with an embedded codebase, or a UX designer might want to get involved with a web framework that they&#x27;ve used. Introductory issues should be clear about what knowledge a contributor should acquire in order to try them, but they don&#x27;t have to all be &quot;easy&quot;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;automating-the-pipeline&quot;&gt;Automating the pipeline&lt;&#x2F;h1&gt;
&lt;p&gt;Drive-by fixes are to being a core contributor as interviews are to full time jobs. Just as a company attempts to interview as many qualified candidates as it can, you can recruit more contributors by making your introductory issues widely available.&lt;&#x2F;p&gt;
&lt;p&gt;Before publicizing your project, make sure you have a &lt;code&gt;CONTRIBUTING.txt&lt;&#x2F;code&gt; or good &lt;code&gt;README&lt;&#x2F;code&gt; outlining where a new contributor should start, or you&#x27;ll be barraged with the same few questions over and over.&lt;&#x2F;p&gt;
&lt;p&gt;There are a variety of sites, which I call issue aggregators, where people who already know a bit about open source development can go to find a new project to work on. I keep a list on &lt;span class=&quot;title-ref&quot;&gt;this page &amp;lt;http:&#x2F;&#x2F;edunham.net&#x2F;pages&#x2F;issue_aggregators.html&amp;gt;&lt;&#x2F;span&gt;, &lt;span class=&quot;title-ref&quot;&gt;pull requests welcome &amp;lt;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;site&#x2F;blob&#x2F;master&#x2F;pages&#x2F;issue_aggregators.rst&amp;gt;&lt;&#x2F;span&gt; if I&#x27;m missing anything. Submitting your introductory issues to these sites broadens your pipeline, and may free up humans&#x27; recruiting time to focus on peole who need more help getting up to speed.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re working on internal rather than public projects, issue aggregators are less relevant. However, if you have the resources, it&#x27;s worthwhile to consider the recruiting device of open sourcing an internal tool that would be useful to others. If an engineer uses and improves that tool, you get a tool improvement and they get some mentorship. In the long term, you also get a unique opportunity to improve that engineer&#x27;s opinion of your organization while networking with your engineers, which can make them more likely to want to work for you later.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;follow-up&quot;&gt;Follow Up&lt;&#x2F;h1&gt;
&lt;p&gt;For questions, you&#x27;re welcome to chat with me on Twitter (@QEDunham), email (automacon &amp;lt;at&amp;gt; edunham &amp;lt;dot&amp;gt; net), or IRC (edunham on irc.freenode.net and irc.mozilla.org).&lt;&#x2F;p&gt;
&lt;p&gt;Slides from the talk are &lt;a href=&quot;http:&#x2F;&#x2F;talks.edunham.net&#x2F;automacon2&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Setting a Freenode channel&#x27;s taxonomy info</title>
        <published>2016-09-23T00:00:00+00:00</published>
        <updated>2016-09-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/09/23/setting_a_freenode_channel_s_taxonomy_info/"/>
        <id>https://edunham.net/2016/09/23/setting_a_freenode_channel_s_taxonomy_info/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/09/23/setting_a_freenode_channel_s_taxonomy_info/">&lt;p&gt;Some recent flooding in a Freenode channel sent me on a quest to discover whether the network&#x27;s services were capable of setting a custom message rate limit for each channel. As far as I can tell, they are not.&lt;&#x2F;p&gt;
&lt;p&gt;However, the problem caused me to re-read the ChanServ help section:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;msg chanserv help
&lt;&#x2F;span&gt;&lt;span&gt;- ***** ChanServ Help *****
&lt;&#x2F;span&gt;&lt;span&gt;- ...
&lt;&#x2F;span&gt;&lt;span&gt;- Other commands: ACCESS, AKICK, CLEAR, COUNT, DEOP, DEVOICE,
&lt;&#x2F;span&gt;&lt;span&gt;-                 DROP, GETKEY, HELP, INFO, QUIET, STATUS,
&lt;&#x2F;span&gt;&lt;span&gt;-                 SYNC, TAXONOMY, TEMPLATE, TOPIC, TOPICAPPEND,
&lt;&#x2F;span&gt;&lt;span&gt;-                 TOPICPREPEND, TOPICSWAP, UNQUIET, VOICE,
&lt;&#x2F;span&gt;&lt;span&gt;-                 WHY
&lt;&#x2F;span&gt;&lt;span&gt;- ***** End of Help *****
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Taxonomy is a cool word. Let&#x27;s see what &lt;code&gt;taxonomy&lt;&#x2F;code&gt; means in the context of IRC:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;msg chanserv help taxonomy
&lt;&#x2F;span&gt;&lt;span&gt;- ***** ChanServ Help *****
&lt;&#x2F;span&gt;&lt;span&gt;- Help for TAXONOMY:
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- The taxonomy command lists metadata information associated
&lt;&#x2F;span&gt;&lt;span&gt;- with registered channels.
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- Examples:
&lt;&#x2F;span&gt;&lt;span&gt;-     &#x2F;msg ChanServ TAXONOMY #atheme
&lt;&#x2F;span&gt;&lt;span&gt;- ***** End of Help *****
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Follow its example:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;msg chanserv taxonomy #atheme
&lt;&#x2F;span&gt;&lt;span&gt;- Taxonomy for #atheme:
&lt;&#x2F;span&gt;&lt;span&gt;- url                       : http:&#x2F;&#x2F;atheme.github.io&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;- ОХЯЕБУ                    : лололол
&lt;&#x2F;span&gt;&lt;span&gt;- End of #atheme taxonomy.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s neat; we can elicit a URL and some field with a cryllic and apparently custom name. But how do we put metadata into a Freenode channel&#x27;s taxonomy section? Google has no useful hits (hence this blog post), but further digging into ChanServ&#x27;s manual does help:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;msg chanserv help set
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;- ***** ChanServ Help *****
&lt;&#x2F;span&gt;&lt;span&gt;- Help for SET:
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- SET allows you to set various control flags
&lt;&#x2F;span&gt;&lt;span&gt;- for channels that change the way certain
&lt;&#x2F;span&gt;&lt;span&gt;- operations are performed on them.
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- The following subcommands are available:
&lt;&#x2F;span&gt;&lt;span&gt;- EMAIL           Sets the channel e-mail address.
&lt;&#x2F;span&gt;&lt;span&gt;- ...
&lt;&#x2F;span&gt;&lt;span&gt;- PROPERTY        Manipulates channel metadata.
&lt;&#x2F;span&gt;&lt;span&gt;- ...
&lt;&#x2F;span&gt;&lt;span&gt;- URL             Sets the channel URL.
&lt;&#x2F;span&gt;&lt;span&gt;- ...
&lt;&#x2F;span&gt;&lt;span&gt;- For more specific help use &#x2F;msg ChanServ HELP SET command.
&lt;&#x2F;span&gt;&lt;span&gt;- ***** End of Help *****
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;set-arbirary-metadata-with-msg-chanserv-set-channel-property-key-value&quot;&gt;Set arbirary metadata with &lt;code&gt;&#x2F;msg chanserv set #channel property key value&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;The commands &lt;code&gt;&#x2F;msg chanserv set #channel email a@b.com&lt;&#x2F;code&gt; and &lt;code&gt;&#x2F;msg chanserv set #channel property email a@b.com&lt;&#x2F;code&gt; appear to function identically, with the former being a convenient wrapper around the latter.&lt;&#x2F;p&gt;
&lt;p&gt;So that&#x27;s how &lt;code&gt;#atheme&lt;&#x2F;code&gt; got their fancy cryllic taxonomy: Someone with the appropriate permissions issued the command &lt;code&gt;&#x2F;msg chanserv set #atheme property ОХЯЕБУ лололол&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;behaviors-of-channel-properties&quot;&gt;Behaviors of channel properties&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ve attempted to deduce the rules governing custom metadata items, because I couldn&#x27;t find them documented anywhere.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Issuing a &lt;code&gt;set property&lt;&#x2F;code&gt; command with a property name but no value deletes the property, removing it from the taxonomy.&lt;&#x2F;li&gt;
&lt;li&gt;A property is overwritten each time someone with the appropriate permissions issues a &lt;code&gt;&#x2F;set&lt;&#x2F;code&gt; command with a matching property name (more on the matching in a moment). The property name and value are stored with the same capitalization as the command issued.&lt;&#x2F;li&gt;
&lt;li&gt;The algorithm which decides whether to overwrite an existing property or create a new one is &lt;strong&gt;not case sensitive&lt;&#x2F;strong&gt;. So if you &lt;code&gt;set ##test email test@example.com&lt;&#x2F;code&gt; and then &lt;code&gt;set ##test EMAIL foo&lt;&#x2F;code&gt;, the final taxonomy will show no field called &lt;code&gt;email&lt;&#x2F;code&gt; and one field called &lt;code&gt;EMAIL&lt;&#x2F;code&gt; with the value &lt;code&gt;foo&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;When displayed, taxonomy items are sorted first in alphabetical order (case insensitively), then by length. For instance, properties with the names &lt;code&gt;a&lt;&#x2F;code&gt;, &lt;code&gt;AA&lt;&#x2F;code&gt;, and &lt;code&gt;aAa&lt;&#x2F;code&gt; would appear in that order, because the initial alphebetization is case-insensitive.&lt;&#x2F;li&gt;
&lt;li&gt;Attempting to place &lt;a href=&quot;http:&#x2F;&#x2F;www.mirc.com&#x2F;colors.html&quot;&gt;mIRC color codes&lt;&#x2F;a&gt; in the property name results in the error &quot;Parameters are too long. Aborting.&quot; However, placing color codes in the value of a custom property works just fine.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;other-uses&quot;&gt;Other uses&lt;&#x2F;h1&gt;
&lt;p&gt;As a final note, you can also do basically the same thing with Freenode&#x27;s NickServ, to set custom information about your nickname instead of about a channel.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Adventures in Mercurial</title>
        <published>2016-08-02T00:00:00+00:00</published>
        <updated>2016-08-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/08/02/adventures_in_mercurial/"/>
        <id>https://edunham.net/2016/08/02/adventures_in_mercurial/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/08/02/adventures_in_mercurial/">&lt;p&gt;I adore Git, but have needed to ramp up my Mercurial (Hg) skills recently to dig prior work related to my current tasks out of a repo&#x27;s history. Here are some things I&#x27;m learning:&lt;&#x2F;p&gt;
&lt;h1 id=&quot;command-equivalences&quot;&gt;Command Equivalences&lt;&#x2F;h1&gt;
&lt;p&gt;As &lt;a href=&quot;https:&#x2F;&#x2F;web.archive.org&#x2F;web&#x2F;20150204065617&#x2F;http:&#x2F;&#x2F;mercurial.selenic.com&#x2F;wiki&#x2F;GitConcepts&quot;&gt;this tutorial&lt;&#x2F;a&gt; so helpfully explains, the two VCSes aren&#x27;t all that dissimilar under their hoods. I condensed the command comparison table into a single page and printed it out for quick reference; a PDF is &lt;a href=&quot;http:&#x2F;&#x2F;other.edunham.net&#x2F;git-hg-cheatsheet&#x2F;githg-cheatsheet.pdf&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;clone&quot;&gt;Clone&lt;&#x2F;h1&gt;
&lt;p&gt;The thing I want to clone lives at &lt;code&gt;http:&#x2F;&#x2F;hg.mozilla.org&#x2F;hgcustom&#x2F;version-control-tools&#x2F;file&#x2F;tip&#x2F;autoland&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Trying to clone the full URL yields a 404, but snipping the URL back to the top-level directory gets me the repo:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ hg clone http:&#x2F;&#x2F;hg.mozilla.org&#x2F;hgcustom&#x2F;version-control-tools&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;destination directory: version-control-tools
&lt;&#x2F;span&gt;&lt;span&gt;requesting all changes
&lt;&#x2F;span&gt;&lt;span&gt;adding changesets
&lt;&#x2F;span&gt;&lt;span&gt;adding manifests
&lt;&#x2F;span&gt;&lt;span&gt;adding file changes
&lt;&#x2F;span&gt;&lt;span&gt;added 4574 changesets with 10874 changes to 1971 files
&lt;&#x2F;span&gt;&lt;span&gt;updating to bookmark @
&lt;&#x2F;span&gt;&lt;span&gt;1428 files updated, 0 files merged, 0 files removed, 0 files unresolved
&lt;&#x2F;span&gt;&lt;span&gt;$ ls
&lt;&#x2F;span&gt;&lt;span&gt;version-control-tools
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;examine-log&quot;&gt;Examine Log&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;hg log | less&lt;&#x2F;code&gt; shows me that each commit&#x27;s summary in this repo includes the part of the codebase it touches, and a bug number.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;hg log | grep autoland: | less&lt;&#x2F;code&gt; gives me the summaries of every commit that touched autoland, but I cannot show a commit from summary alone.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;hgbook.red-bean.com&#x2F;read&#x2F;customizing-the-output-of-mercurial.html&quot;&gt;The Hg book&lt;&#x2F;a&gt; helped me construct a filter that will show a unique revision ID onthe same line as each description.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;hg log --template &#x27;{rev} {desc}\n&#x27; | grep autoland:&lt;&#x2F;code&gt; is much more useful. It gives me the local ID of each changeset whose description included &quot;autoland:&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;From here, I can use a bit more grep to narrow down the list of matching messages, then I&#x27;m ready to examine commits.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;examining-commits&quot;&gt;Examining Commits&lt;&#x2F;h1&gt;
&lt;p&gt;That &lt;code&gt;{rev}&lt;&#x2F;code&gt; in my filter was the &quot;repository-local changeset revision number&quot;. For these examples I&#x27;ll examine revision 2589.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;hg status --change 2589&lt;&#x2F;code&gt; lists the files that were touched by that revision, and &lt;code&gt;hg export 2589&lt;&#x2F;code&gt; yields a full diff of the changes introduced.&lt;&#x2F;p&gt;
&lt;p&gt;This gets me enough information to make an appropriate set of changes, run the tests, and create my own commits!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Thinkpad 13 Trackpoint slowdown in i3 window manager</title>
        <published>2016-07-01T00:00:00+00:00</published>
        <updated>2016-07-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/07/01/thinkpad_13_trackpoint_i3/"/>
        <id>https://edunham.net/2016/07/01/thinkpad_13_trackpoint_i3/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/07/01/thinkpad_13_trackpoint_i3/">&lt;p&gt;As has been &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;thinkpad&#x2F;comments&#x2F;4jku4c&#x2F;configuring_trackpoint_on_thinkpad_13&#x2F;&quot;&gt;mentioned on Reddit&lt;&#x2F;a&gt;, the Thinkpad 13 trackpoint settings aren&#x27;t in the same place as those of older thinkpads. Despite some troubleshooting, I haven&#x27;t yet found what files to edit to adjust the trackpoint&#x27;s speed and sensitivity in Ubuntu 16.04.&lt;&#x2F;p&gt;
&lt;p&gt;The trackpoint has been slightly sluggish&#x2F;unresponsive when I use the i3 window manager, and has additional intermittent slowdowns when using Chromium and Firefox in i3.&lt;&#x2F;p&gt;
&lt;p&gt;Although I don&#x27;t yet know the &lt;strong&gt;right&lt;&#x2F;strong&gt; way to fix trackpoint sensitivity on this machine, I accidentally discovered a highly effective workaround today:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Log into Unity (the default desktop that Ubuntu ships with) and configure the mouse and input settings as desired&lt;&#x2F;li&gt;
&lt;li&gt;Log out, and get back into i3wm&lt;&#x2F;li&gt;
&lt;li&gt;Launch &lt;code&gt;unity-settings-daemon&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;And suddenly, the mouse works correctly the way it did in Unity!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I fully realize that this is a nasty hack around identifying and solving the actual problem, but it succeeds at making the mouse responsive while minimizing time spent troubleshooting.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hieroglyph and Tinkerer Dependencies</title>
        <published>2016-06-27T00:00:00+00:00</published>
        <updated>2016-06-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/06/27/hieroglyph_and_tinkerer_dependencies/"/>
        <id>https://edunham.net/2016/06/27/hieroglyph_and_tinkerer_dependencies/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/06/27/hieroglyph_and_tinkerer_dependencies/">&lt;p&gt;In setting up virtualenvs for my slides and blog repos on my new laptop, I&#x27;ve been reminded that a variety of Sphinx-based tools require system dependencies as well as the ones in their virtualenvs.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;hieroglyph-dependency-issues&quot;&gt;Hieroglyph dependency issues&lt;&#x2F;h1&gt;
&lt;p&gt;The error resulting from &lt;code&gt;pip install -r requirements.txt&lt;&#x2F;code&gt; ended with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Command &amp;quot;...&#x2F;virtualenv&#x2F;bin&#x2F;python2 -u -c
&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;import setuptools,
&lt;&#x2F;span&gt;&lt;span&gt;tokenize;__file__=&amp;#39;&#x2F;tmp&#x2F;pip-build-lzbk_r&#x2F;Pillow&#x2F;setup.py&amp;#39;;exec(compile(getattr(tokenize,
&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;open&amp;#39;, open)(__file__).read().replace(&amp;#39;\r\n&amp;#39;, &amp;#39;\n&amp;#39;), __file__, &amp;#39;exec&amp;#39;))&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;install --record &#x2F;tmp&#x2F;pip-BNDc_6-record&#x2F;install-record.txt
&lt;&#x2F;span&gt;&lt;span&gt;--single-version-externally-managed --compile --install-headers
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;home&#x2F;edunham&#x2F;repos&#x2F;slides&#x2F;rustcommunity&#x2F;v&#x2F;include&#x2F;site&#x2F;python2.7&#x2F;Pillow&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;failed with error code 1 in &#x2F;tmp&#x2F;pip-build-lzbk_r&#x2F;Pillow&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Its fix, from &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;34631806&#x2F;fail-during-installation-of-pillow-python-module-in-linux&#x2F;34631976&quot;&gt;stackoverflow&lt;&#x2F;a&gt;, was:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk
&lt;&#x2F;span&gt;&lt;span&gt;$ pip install -r requirements.txt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;tinkerer-depencencies-too&quot;&gt;Tinkerer depencencies, too!&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;pip install -r requirements.txt&lt;&#x2F;code&gt; over in my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;site&quot;&gt;blog repo&lt;&#x2F;a&gt; yielded:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Command &amp;quot;...&#x2F;virtualenv&#x2F;bin&#x2F;python2 -u -c &amp;quot;import setuptools,
&lt;&#x2F;span&gt;&lt;span&gt;tokenize;__file__=&amp;#39;&#x2F;tmp&#x2F;pip-build-NVLSBY&#x2F;lxml&#x2F;setup.py&amp;#39;;exec(compile(getattr(tokenize,
&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;open&amp;#39;, open)(__file__).read().replace(&amp;#39;\r\n&amp;#39;, &amp;#39;\n&amp;#39;), __file__, &amp;#39;exec&amp;#39;))&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;install --record &#x2F;tmp&#x2F;pip-qD5QIe-record&#x2F;install-record.txt
&lt;&#x2F;span&gt;&lt;span&gt;--single-version-externally-managed --compile --install-headers
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;home&#x2F;edunham&#x2F;repos&#x2F;site&#x2F;v&#x2F;include&#x2F;site&#x2F;python2.7&#x2F;lxml&amp;quot; failed with error code
&lt;&#x2F;span&gt;&lt;span&gt;1 in &#x2F;tmp&#x2F;pip-build-NVLSBY&#x2F;lxml&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The fix is again to install the missing system deps, on Ubuntu:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo apt-get install libxml2-dev libxslt-dev
&lt;&#x2F;span&gt;&lt;span&gt;$ pip install -r requirements.txt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s it! I&#x27;m writing this down for SEO on the specific errors at hand, since the first several useful hits are currently stackoverflow.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re a pip developer reading this, please briefly contemplate whether it&#x27;d be worthwhile to have some built-in mechanism to translate common dependency errors to the appropriate system package names needed based on the OS on which the command is run.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>CFPs Made Easier</title>
        <published>2016-06-23T00:00:00+00:00</published>
        <updated>2016-06-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/06/23/cfps_made_easy/"/>
        <id>https://edunham.net/2016/06/23/cfps_made_easy/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/06/23/cfps_made_easy/">&lt;p&gt;Check out &lt;a href=&quot;http:&#x2F;&#x2F;lucybain.com&#x2F;blog&#x2F;2016&#x2F;conference-proposal-ideas&#x2F;&quot;&gt;this post&lt;&#x2F;a&gt; by Lucy Bain about how to come up with an idea for what to talk about at a conference. I blogged last year about &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;04&#x2F;15&#x2F;the_life_cycle_of_a_conference_talk.html&quot;&gt;how I turn abstracts into talks&lt;&#x2F;a&gt;, as well. Now that the &lt;a href=&quot;https:&#x2F;&#x2F;osem.seagl.org&#x2F;conference&#x2F;seagl2016&#x2F;program&#x2F;proposal&#x2F;new&quot;&gt;SeaGL CFP&lt;&#x2F;a&gt; is open, it&#x27;s time to look in a bit more detail about the process of going from a &lt;strong&gt;talk idea&lt;&#x2F;strong&gt; to a &lt;strong&gt;compelling abstract&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In this post, I&#x27;ll walk you through some exercises to clarify your understanding of your talk idea and find its audience, then help you use that information to outline the 7 essential parts of a complete abstract.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;getting-ready-to-write-your-abstract&quot;&gt;Getting ready to write your abstract&lt;&#x2F;h1&gt;
&lt;p&gt;Your abstract is a promise about what your talk will deliver. Have you ever gotten your hopes up for a talk based on its abstract, then attended only to hear something totally unrelated? You can save your audience from that disappointment by making sure that you present what your abstract says you will.&lt;&#x2F;p&gt;
&lt;p&gt;I find the abstract to be the hardest part of the talk to write, because it sets the stage for every other part of it. If your abstract is thorough and clear about what your talk will deliver, you can refer back to it throughout the writing process to make sure you&#x27;re including the information that your audience is there for!&lt;&#x2F;p&gt;
&lt;p&gt;For both you and your audience to get the most out of your talk, the following questions can help you refine your talk idea before you even start to write its abstract.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-do-you-love-this-idea&quot;&gt;Why do you love this idea?&lt;&#x2F;h2&gt;
&lt;p&gt;Start working on your abstract by taking some quick notes on why you&#x27;re excited about speaking on this topic. There are no wrong answers! Your reasons might include:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Document a topic you care about in a format that works well for those who learn by listening and watching&lt;&#x2F;li&gt;
&lt;li&gt;Impress a potential employer with your knowledge and skills&lt;&#x2F;li&gt;
&lt;li&gt;Meet others in the community who&#x27;ve solved similar problems before, to advise you&lt;&#x2F;li&gt;
&lt;li&gt;Recruit contributors to a project&lt;&#x2F;li&gt;
&lt;li&gt;Force yourself to finish a project or learn more detail about a tool&lt;&#x2F;li&gt;
&lt;li&gt;Save novices from a pitfall that you encountered&lt;&#x2F;li&gt;
&lt;li&gt;Travel to a conference location that you&#x27;ve always wanted to visit&lt;&#x2F;li&gt;
&lt;li&gt;Build your resume&lt;&#x2F;li&gt;
&lt;li&gt;Or something else entirely!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Starting out by identifying what you personally hope to gain from giving the talk will help ensure that you make the right promises in your abstract, and get the right people into the room.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-your-idea-s-scope&quot;&gt;What&#x27;s your idea&#x27;s scope?&lt;&#x2F;h2&gt;
&lt;p&gt;Make 2 quick little lists:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Topics I really want this presentation to cover&lt;&#x2F;li&gt;
&lt;li&gt;Topics I do not want this presentation to cover&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Once you think that you have your abstract all sorted out, come back to these lists and make sure that you included enough topics from the first list, and excluded those from the second.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;who-s-the-conference-s-target-audience&quot;&gt;Who&#x27;s the conference&#x27;s target audience?&lt;&#x2F;h2&gt;
&lt;p&gt;Keynotes and single-track conferences are special, but generally your talk does not have to appeal to every single person at the conference.&lt;&#x2F;p&gt;
&lt;p&gt;Write down all the major facts you know about the people who attend the conference to which you&#x27;re applying. How young or old might they be? How technically expert or inexperienced? What are their interests? Why are they there?&lt;&#x2F;p&gt;
&lt;p&gt;For example, here are some statements that I can make about the audience at SeaGL:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Expertise varies from university students and random community members to long-time contributors who&#x27;ve run multiple FOSS projects.&lt;&#x2F;li&gt;
&lt;li&gt;Age varies from a few school-aged kids (usually brought by speakers and attendees) to retirees.&lt;&#x2F;li&gt;
&lt;li&gt;The audience will contain some long-term FOSS contributors who don&#x27;t program, and some relatively expert programmers who might have minimal involvement in their FOSS communities&lt;&#x2F;li&gt;
&lt;li&gt;Most attendees will be from the vicinity of Seattle. It will be some attendees&#x27; first tech conference. A handful of speakers are from other parts of the US and Canada; international attendees are a tiny minority.&lt;&#x2F;li&gt;
&lt;li&gt;The audience comes from a mix of socioeconomic backgrounds, and many attendees have day jobs in fields other than tech.&lt;&#x2F;li&gt;
&lt;li&gt;Attendees typically come to SeaGL because they&#x27;re interested in FOSS community and software.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;where-s-your-niche&quot;&gt;Where&#x27;s your niche?&lt;&#x2F;h2&gt;
&lt;p&gt;Now that you&#x27;ve taken some guesses about who will be reading your abstract, think about which subset of the conference&#x27;s attendees would get the most benefit out of the topic that you&#x27;re planning to talk about.&lt;&#x2F;p&gt;
&lt;p&gt;Write down which parts of the audience will get the most from your talk --novices to open source? Community leaders who&#x27;ve found themselves in charge of an IRC channel but aren&#x27;t sure how to administer it? Intermediate Bash users looking to learn some new tricks?&lt;&#x2F;p&gt;
&lt;p&gt;If your talk will appeal to multiple segments of the community (developers interested in moving into DevOps, and managers wondering what their operations people do all day?), write one question that your talk will answer for each segment.&lt;&#x2F;p&gt;
&lt;p&gt;You&#x27;ll use this list to customize your abstract and help get the right people into the room for your talk.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;still-need-an-idea&quot;&gt;Still need an idea?&lt;&#x2F;h2&gt;
&lt;p&gt;Conferences with a diverse audience often offer an introductory track to help enthusiastic newcomers get up to speed. If you have intermediate skills in a technology like Bash, Git, LaTeX, or IRC, offer an introductory talk to help newbies get started with it! Can you teach a topic that you learned recently in a way that&#x27;s useful to newbies?&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re an expert in a field that&#x27;s foreign to most attendees (psychology? beekeeping? Cray Supercomputer assembly language?), consider an intersection talk: &quot;What you can learn from X about Y&quot;. Can you combine your hobby, background, or day job with a theme from the conference to come up with something unique?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-anatomy-of-an-abstract&quot;&gt;The Anatomy of an Abstract&lt;&#x2F;h1&gt;
&lt;p&gt;There are many ways to structure a good abstract. Here are the 7 elements that I try to always include:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set the scene with a &lt;strong&gt;strong introductory sentence&lt;&#x2F;strong&gt;, which reminds your target audience of your topic&#x27;s relevance to them. Some of mine have included:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Git is the most popular source code management and version control system in the open source community.&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;When you&#x27;re new to programming, or self-taught with an emphasis on those topics that are directly relevant to your current project, it&#x27;s easy to skip learning about analyzing the complexity of algorithms.&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ask some questions&lt;&#x2F;strong&gt;, which the talk promises to answer. These questions should be asked from the perspective of your target audience, which you identified earlier. This is the least essential piece of an abstract, and can be skipped if you make sure your exposition clearly shows that you understand your target audience in some other way. Here are a couple of questions I&#x27;ve used in abstracts that were accepted to conferences:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Do you know how to control what information people can discover about you on an IRC network?&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Is the project of your dreams ignoring your pull requests?&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Drop some &lt;strong&gt;hints about the format&lt;&#x2F;strong&gt; that the talk will take. This shows the selection commitee that you&#x27;ve planned ahead, and helps audience members select session that&#x27;re a good fit for their learning styles. Useful words here include:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Overview of&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Case study&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Demonstration&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Deep dive into&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Outline X principles for&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Live coding&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Identify what &lt;strong&gt;background knowledge&lt;&#x2F;strong&gt; the audience will need to get the talk&#x27;s benefit, if applicable. Being specific about this helps welcome audience members who&#x27;re undecided about whether the talk is applicable to them. Useful phrases include:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&quot;This talk will assume no background knowledge of...&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;If you&#x27;ve used &lt;code&gt;____&lt;&#x2F;code&gt; to &lt;code&gt;____&lt;&#x2F;code&gt;, ...&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;If you&#x27;ve completed the &lt;code&gt;____&lt;&#x2F;code&gt; tutorial...&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;State a &lt;strong&gt;specific benefit&lt;&#x2F;strong&gt; that audience members will get from having attended the talk. Benefits can include:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Halve your Django website&#x27;s page load times&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Get help on IRC&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Learn from &lt;code&gt;____&lt;&#x2F;code&gt;&#x27;s mistakes&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Ask the right questions about &lt;code&gt;____&lt;&#x2F;code&gt;&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Reinforce and quantify &lt;strong&gt;your credibility&lt;&#x2F;strong&gt;. If you&#x27;re presenting a case study into how your company deployed a specific tool, be sure to mention your role on the team! For instance, you might say:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Presented by &lt;code&gt;[the original author | a developer | a maintainer | a long-term user]&lt;&#x2F;code&gt; of &lt;code&gt;[the project]&lt;&#x2F;code&gt;, this talk will...&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;End with a &lt;strong&gt;recap&lt;&#x2F;strong&gt; of the talk&#x27;s basic promise, and welcome audience members to attend.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;These 7 pieces of information don&#x27;t have to each be in their own sentence --for instance, establishing your credibility and indicating the talk&#x27;s format often fit together nicely in a single sentence.&lt;&#x2F;p&gt;
&lt;p&gt;Once you&#x27;ve got all of the essential pieces of an abstract, munge them around until it sounds like concise, fluent English. Get some feedback on &lt;a href=&quot;http:&#x2F;&#x2F;helpmeabstract.com&#x2F;&quot;&gt;helpmeabstract.com&lt;&#x2F;a&gt; if you&#x27;d like assistance!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;give-it-a-title&quot;&gt;Give it a title&lt;&#x2F;h1&gt;
&lt;p&gt;Naming things is hard. Here are some assorted tips:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Keep it under about 50 characters, or it might not fit on the program&lt;&#x2F;li&gt;
&lt;li&gt;Be polite. Rude puns or metaphors might be eye-catching, but probably violate your conference or community&#x27;s code of conduct, and will definitely alienate part of your prospective audience.&lt;&#x2F;li&gt;
&lt;li&gt;For general talks, it&#x27;s hard to go wrong with &quot;Intro to &lt;code&gt;___&lt;&#x2F;code&gt;&quot; or &quot;&lt;code&gt;___&lt;&#x2F;code&gt; for &lt;code&gt;___&lt;&#x2F;code&gt; users&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;The form &quot;[topic]: A [historymelodramalove story]&quot; is generally reliable. Well, I&#x27;m kidding about &quot;melodrama&quot; and &quot;love story&quot;... Mostly.&lt;&#x2F;li&gt;
&lt;li&gt;Clickbait is underhanded, but it works. &quot;&lt;code&gt;___&lt;&#x2F;code&gt; things I wish I&#x27;d known about &lt;code&gt;___&lt;&#x2F;code&gt;&quot;, anyone?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Good luck, and happy conferencing!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>2ish weeks with the Thinkpad 13</title>
        <published>2016-05-27T00:00:00+00:00</published>
        <updated>2016-05-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/05/27/thinkpad_13/"/>
        <id>https://edunham.net/2016/05/27/thinkpad_13/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/05/27/thinkpad_13/">&lt;p&gt;I recently got a Thinkpad 13 to try replacing my X230 as a personal laptop. Here&#x27;s the relevant specs from my order confirmation:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Battery&lt;&#x2F;td&gt;&lt;td&gt;3cell 42Wh&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;System Unit&lt;&#x2F;td&gt;&lt;td&gt;13&amp;amp;S2 IG i5-6200U NvPro&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Camera&lt;&#x2F;td&gt;&lt;td&gt;720p HD Camera&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;AC Adapter and Power Cord&lt;&#x2F;td&gt;&lt;td&gt;45W 2pin US&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Processor&lt;&#x2F;td&gt;&lt;td&gt;Intel Core i5-6200U MB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Hard drive&lt;&#x2F;td&gt;&lt;td&gt;128GB SSD SATA3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Keyboard Language&lt;&#x2F;td&gt;&lt;td&gt;KYB SR ENG&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Publication Language&lt;&#x2F;td&gt;&lt;td&gt;PUB ENG&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Total memory&lt;&#x2F;td&gt;&lt;td&gt;4GB DDR4 2133 SoDIMM&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;OS DPK&lt;&#x2F;td&gt;&lt;td&gt;W10 Home&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Pointing device&lt;&#x2F;td&gt;&lt;td&gt;3+2BCP NoFPR SR&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Preload Language&lt;&#x2F;td&gt;&lt;td&gt;W10 H64-ENG&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Preload OS&lt;&#x2F;td&gt;&lt;td&gt;Windows 10 Home 64&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Preload Type&lt;&#x2F;td&gt;&lt;td&gt;Standard Image&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;TPM Setting&lt;&#x2F;td&gt;&lt;td&gt;Software TPM Enabled&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Display Panel&lt;&#x2F;td&gt;&lt;td&gt;13&amp;amp;S2 FHD IPS AG AL SR&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;WiFi wireless LAN adapters&lt;&#x2F;td&gt;&lt;td&gt;Intel 8260AC+BT 2x2 vPro&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;I picked mediocre CPU and RAM because the RAM&#x27;s easy to upgrade, and I&#x27;m curious about whether I actually need top-of-the-line CPUs to have an acceptable experience on a personal laptop.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-the-13&quot;&gt;Why the 13?&lt;&#x2F;h1&gt;
&lt;p&gt;I had a few hard requirements for my next personal laptop:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Trackpoint with buttons&lt;&#x2F;li&gt;
&lt;li&gt;Decent key travel (the X1 carbon has &lt;a href=&quot;http:&#x2F;&#x2F;www.laptopmag.com&#x2F;reviews&#x2F;laptops&#x2F;lenovo-thinkpad-x1-carbon-2015&quot;&gt;1.86mm&lt;&#x2F;a&gt; and typing on it for too long made my hands hurt)&lt;&#x2F;li&gt;
&lt;li&gt;USBC port&lt;&#x2F;li&gt;
&lt;li&gt;Under $1,000&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Plus a few nice-to-haves:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Small and light are nice, including charger&lt;&#x2F;li&gt;
&lt;li&gt;Screen not much worse than 1920x1080&lt;&#x2F;li&gt;
&lt;li&gt;Good battery life&lt;&#x2F;li&gt;
&lt;li&gt;Metal case and design for durability make me happy&lt;&#x2F;li&gt;
&lt;li&gt;My house is already full of Thinkpad chargers, so a laptop that uses them helps reduce additional clutter&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;ll be the first to admit that this is an atypical set of priorities. My laptop is home to Git, Vim, and a variety of tools for interacting with the internet, so the superficial I&#x2F;O differences matter more to me than the machine&#x27;s internal specs.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;things-i-like-about-the-13&quot;&gt;Things I like about the 13&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;www.laptopmag.com&#x2F;articles&#x2F;lenovo-thinkpad-13-hands-on&quot;&gt;2.1mm&lt;&#x2F;a&gt; key travel is everything I hoped for. At least, I&#x27;ve used it all day and my hands don&#x27;t hurt.&lt;&#x2F;li&gt;
&lt;li&gt;Battery life is pretty decent, and battery will be easy to replace when it starts to fail.&lt;&#x2F;li&gt;
&lt;li&gt;Light-enough weight. Lighter charger than other Thinkpads I&#x27;ve had.&lt;&#x2F;li&gt;
&lt;li&gt;Smallest Thinkpad charger that I&#x27;ve ever seen.&lt;&#x2F;li&gt;
&lt;li&gt;Case screws are all captive.&lt;&#x2F;li&gt;
&lt;li&gt;Mystery hole in the bottom case turns out to be a highly convenient hard shutdown button.&lt;&#x2F;li&gt;
&lt;li&gt;Hinges feel pretty solid and hold the screen up nicely.&lt;&#x2F;li&gt;
&lt;li&gt;No keyboard backlight. I dislike them.&lt;&#x2F;li&gt;
&lt;li&gt;4GB of RAM is a single stick, easy to add more (and I&#x27;ll need to for a smoother web browsing experience; neither Firefox nor Chromium is particularly happy with only 4GB)&lt;&#x2F;li&gt;
&lt;li&gt;A vanilla Ubuntu 16.04 iso Just Worked for installing Linux. It must have shipped with whatever magic signatures were required to play nice with the new security measures, because the install process was delightfully non-thought-provoking.&lt;&#x2F;li&gt;
&lt;li&gt;~7mm plastic bezel between buttons and trackpad reduces likelihood of accidentally moving cursor when clicking.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-button-bezel.jpg&quot; alt=&quot;Button bezel&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Screen&#x27;s the same as my X240, xrandr calls it 1920x1080 294mm x 165mm. This fits 211 columns of a default font, or 127 columns of a font that&#x27;s comfortably legible when the laptop is on the other side of my desk.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;nitpicks-about-the-13&quot;&gt;Nitpicks about the 13&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;Color.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When I purchased mine at the end of April, only the silver chassis had a metal lid and shipped with a nice screen by default (the higher-res screen is available in the all-plastic black model for an additional charge). So now I own a non-black laptop for the first time since my Dell Latitude D410 in high school. The screen bezel and keys are black, though, and if I really cared I could probably paint the rest of it.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Power button.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-power-button.jpg&quot; alt=&quot;Power button&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It feels horribly... squishy? There&#x27;s no satisfying &lt;code&gt;click&lt;&#x2F;code&gt; to tell me when I&#x27;ve pushed it far enough. Holding it for 10 seconds only sometimes shuts the laptop off (though there&#x27;s a reset switch on the mobo accessible by a paperclip-hole in the bottom panel which forces shutdown instantly when pushed). There&#x27;s a circle on the pwoer button that looks like it might be an LED, but it never lights up.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Cutesy font.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-lenovo-font.jpg&quot; alt=&quot;Lenovo font&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is a tiny nitpick, but they&#x27;ve changed the Lenovo logo on the lid, pre-BIOS boot screen, and screen bezel from the already-mediocre font to a super condescending, childish, roundy one. Fortunately the lid one is easily hidden under some stickers.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Bottom panel held on by clips as well as screws.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;More on this one in the disassembly section below, but I&#x27;m afraid they&#x27;ll break with how often I take my laptops apart.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Mouse buttons feel cheap and plastic-ey.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;They feel like thin plastic shells instead of solid buttons like on older Thinkpads. I&#x27;m not sure precisely why they feel that way, but it&#x27;s a reminder that you&#x27;re using a lower-end machine.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Longest side is about 1cm greater than the short side of a TSA xray tub.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;My X240 fits perfectly along the short end of the tub, leaving room for my shoes beside it. I have to use two tubs or separate my pair of shoes when putting the 13 through the scanner. (see, I wasn&#x27;t kidding when I said &quot;nitpicks&quot;)&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The Trackpoint top is not interchangeable with those of older Thinkpads.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-trackpoints.jpg&quot; alt=&quot;Trackpoints comparison&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The round part is the same size, but the square hole in the bottom is about 2mm to a side rather than the 4mm of the one on an x220 keyboard. Plus the cylinder bit is about 2mm long rather than the x220&#x27;s 3.5mm, so even with an adapter for the square hole, older trackpoints would risk leaving marks on the screen.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The fan is a little loud.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I anticipate that this will get a lot less annoying when I upgrade to 16 or 32GB of ram and maybe tune it in software using &lt;code&gt;thinkfan&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;thinkpad-13-partial-disassembly-photos&quot;&gt;Thinkpad 13 partial disassembly photos&lt;&#x2F;h1&gt;
&lt;p&gt;To get the bottom case off, pull all the visible screws and also remove the 3 tiny rubber feet from under the palm rest. I stuck my tiny rubber feet in a plastic bag and filed it away, because repeated removal would eventually destroy the glue and get them lost.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-slide-and-pry.jpg&quot; alt=&quot;Slide and pry disassembly&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The bottom case comes off with a combination of sliding and prying. Getting it back on again requires sliding the palmrest edge just right, then snapping the sides and back on before the palm rest slips out of place. It&#x27;s tricky.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-bendy-battery.jpg&quot; alt=&quot;Battery removal&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The battery is easily removed by pulling out a single (non-captive) screw. It seems to be a thin plastic wrapper around 3 cell phone batteries. The battery has no glue holding it in, just screws.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-mobo.jpg&quot; alt=&quot;Motherboard&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s its guts, with battery removed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;13-mobo-annotated.jpg&quot; alt=&quot;Motherboard annotated&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Note the convenient hard power cycle button (accessible via a tiny hole in the bottom case when assembled), pair of RAM slots and SSD form factor, and airspace compartment that almost looks intended for hiding half a dozen very small items. The coin cell battery (in sky blue shrink wrap) flaps around awkwardly when the machine is disassembled, but at least it&#x27;s not glued down.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reflections on my first live webcast</title>
        <published>2016-05-10T00:00:00+00:00</published>
        <updated>2016-05-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/05/10/reflections_on_my_first_live_webcast/"/>
        <id>https://edunham.net/2016/05/10/reflections_on_my_first_live_webcast/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/05/10/reflections_on_my_first_live_webcast/">&lt;p&gt;This morning, I participated in the O&#x27;Reilly &lt;a href=&quot;http:&#x2F;&#x2F;www.oreilly.com&#x2F;pub&#x2F;e&#x2F;3718&quot;&gt;Emerging Languages Webcast&lt;&#x2F;a&gt; with my &quot;&lt;a href=&quot;http:&#x2F;&#x2F;talks.edunham.net&#x2F;oscon-webcast2016&#x2F;oscon-webcast-final.pdf&quot;&gt;Rust from a Scripting Background&lt;&#x2F;a&gt;&quot; talk. Here&#x27;s how it went.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;preparation&quot;&gt;Preparation&lt;&#x2F;h1&gt;
&lt;p&gt;I was contacted about a month before my webcast and asked to present my OSCON talk as part of the event. I explained why my &quot;How to learn Rust&quot; talk didn&#x27;t sound like a good fit for the emerging languages webcast, and suggested the &quot;Starting Rust from a Scripting Background&quot; talk that I gave at my &lt;a href=&quot;http:&#x2F;&#x2F;www.meetup.com&#x2F;pdxrust&#x2F;&quot;&gt;local Rust meetup&lt;&#x2F;a&gt; recently as an alternative.&lt;&#x2F;p&gt;
&lt;p&gt;After we agreed on what talk would be suitable, O&#x27;Reilly&#x27;s Online Events Producers emailed me a contract to e-sign. The contract gives O&#x27;Reilly the opportunity to reuse and redistribute my talk from the webcast, and promises me a percentage of the proceeds if my recording is sold, licensed, or otherwise used to make them money.&lt;&#x2F;p&gt;
&lt;p&gt;During the week before the webcast, I did a test call in which an O&#x27;Reilly representative walked me through how to use the webcast software and verified that my audio was good on the phone I planned to use for the webcast.&lt;&#x2F;p&gt;
&lt;p&gt;A final copy of the slides for the webcast, in my option of PDF or Powerpoint, was due at 5pm the day before the event.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-software-worked-for-me-on-ubuntu&quot;&gt;The Software (worked for me on Ubuntu)&lt;&#x2F;h1&gt;
&lt;p&gt;O&#x27;Reilly Media provided an application called &quot;Presentation Manager XD&quot; from on24.com that presenters log into during the event.&lt;&#x2F;p&gt;
&lt;p&gt;According to my email from O&#x27;Reilly, the requirements for the event are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Slides - PowerPoint or PDF only please with no embedded video or audio. Screen ratio of 4:3&lt;&#x2F;li&gt;
&lt;li&gt;Robust Internet connection&lt;&#x2F;li&gt;
&lt;li&gt;Clear, reliable phone line.&lt;&#x2F;li&gt;
&lt;li&gt;Windows 7 or 8 running IE 8+, Firefox 22+ or Chrome 27+&lt;&#x2F;li&gt;
&lt;li&gt;Mac OS 10.6+ running Firefox 22+ or Chrome 27+&lt;&#x2F;li&gt;
&lt;li&gt;Latest version of Flash Player&lt;&#x2F;li&gt;
&lt;li&gt;If you plan to share your screen, you will need to install a small application - you will be prompted to install it the first time you log into the platform.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Some of these requirements are lies. I used Firefox 46.0 on Ubuntu 14.04. I did rewrite my slides in LibreOffice because it emits better PDFs than the HTML tools I normally use, but I was also looking for an excuse to rewrite them to clean up their organization.&lt;&#x2F;p&gt;
&lt;p&gt;I clicked around in the &quot;Presentation Manager XD&quot; UI and downloaded a file called &quot;ON24-ScreenShare-plugin&quot;, then &lt;code&gt;chmod +x&lt;&#x2F;code&gt;&#x27;d it and executed it with &lt;code&gt;.&#x2F;ON24-ScreenShare-plugin&lt;&#x2F;code&gt;. This caused Wine to run, install some Gecko stuff, and start the screenshare plugin sufficiently well to share my screen to the webcast tool.&lt;&#x2F;p&gt;
&lt;p&gt;I had to re-run the plugin in Wine after logging out of and back into my window manager, of course. Additionally, the screenshare window&#x27;s resizing is finicky. It&#x27;s fine to grab and drag the highlighted parts of the window&#x27;s border with the mouse, but the meta+click command with which one usually moves windows in i3 causes the sides of the screenshare window to move independently of each other.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s what the webcast UI looked like during streaming, just at the end of the Kotlin talk while I was getting ready to start mine:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;oreilly-presenter-ui.png&quot; alt=&quot;O&amp;#39;Reilly Presentation Manager XD interface during webcast&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-talk&quot;&gt;The Talk&lt;&#x2F;h1&gt;
&lt;p&gt;As previously mentioned, I rewrote my talk in LibreOffice Impress --ostensibly to get a prettier PDF, but also because it&#x27;s been a month or two since I last prepared for it and re-writing helps me refresh my memory and verify that all my facts are up to date.&lt;&#x2F;p&gt;
&lt;p&gt;GUI-based slide editing is downright painful after using rst-based tools for so long, especially because LibreOffice has no good way to embed code samples out of the box. I ended up going with screenshots from the Rust playground, which were probably better than embedded code samples, but relearning how to edit slides like a regular person wasn&#x27;t a pleasant experience.&lt;&#x2F;p&gt;
&lt;p&gt;I took more notes than I normally do, since nobody on the webcast could see whether I was reciting or reading. I&#x27;m glad I did, as having the notes on a physical page in front of me was reassuring and helped me avoid missing any important points.&lt;&#x2F;p&gt;
&lt;p&gt;I rehearsed the timing of each section of my slides individually, since it naturally broke down into 7 or so discrete parts and I had previously calculated how much of my hour to allocate to each section. Most sections ran consistently over time when preparing, yet under time during the actual talk. The lesson here is to rehearse until I&#x27;m happy with a section and can make it the same duration twice in a row.&lt;&#x2F;p&gt;
&lt;p&gt;The experience of presenting a talk in a subjectively empty room made me realize just what high-bandwidth communication regular conferences are.&lt;&#x2F;p&gt;
&lt;p&gt;Pros:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;No need to worry about eye contact&lt;&#x2F;li&gt;
&lt;li&gt;All the notes you want&lt;&#x2F;li&gt;
&lt;li&gt;Can&#x27;t see anyone sleeping&lt;&#x2F;li&gt;
&lt;li&gt;Chat channel allowed instant distribution of links&lt;&#x2F;li&gt;
&lt;li&gt;Chat channel allowed expert attendees to help answer questions&lt;&#x2F;li&gt;
&lt;li&gt;Presentation software allowed gracefully skipping slides, rather than the usual paging back and forth with the arrow keys&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Cons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Can&#x27;t take quick surveys by show of hands&lt;&#x2F;li&gt;
&lt;li&gt;Negligible feedback on how many people are there and their body language of engagement&#x2F;disengagement&lt;&#x2F;li&gt;
&lt;li&gt;Silences are super awkward&lt;&#x2F;li&gt;
&lt;li&gt;Can&#x27;t see the shy attendees, in order to encourage participation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The audience asked fewer questions during the talk than I expected. Fortunately, they came up with plenty of questions at the end -- extra fortunate because I overcompensated on time and finished my slides about 15 minutes before the end of my speaking slot!&lt;&#x2F;p&gt;
&lt;p&gt;Q&amp;amp;A was surprisingly relaxing, as it was totally up to me which questions to answer. I&#x27;ll admit that I did select in favor of those that I could answer concisely and eloquently, deferring the questions that didn&#x27;t make as much sense to think about them while I answered easier ones.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;tl-dr&quot;&gt;tl;dr&lt;&#x2F;h1&gt;
&lt;p&gt;In my experience, presenting a webcast was lower-stress and comparably impactful to a conference talk.&lt;&#x2F;p&gt;
&lt;p&gt;For would-be presenters concerned about their or the audience&#x27;s appearance, the visual anonymity of a webcast could be a great place to start a speaking career.&lt;&#x2F;p&gt;
&lt;p&gt;Speakers accustomed to presenting in rooms full of humans should expect subtle feedback, like nods, smiles, and laughter, to be totally invisible in a webcast environment.&lt;&#x2F;p&gt;
&lt;p&gt;And if O&#x27;Reilly asks you to do a webcast with them, I&#x27;d say go for it -- they made the whole experience as seamless and easy as possible.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Paths Into DevOps</title>
        <published>2016-05-05T00:00:00+00:00</published>
        <updated>2016-05-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/05/05/paths_into_devops/"/>
        <id>https://edunham.net/2016/05/05/paths_into_devops/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/05/05/paths_into_devops/">&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;carol-tweet.png&quot; alt=&quot;Carol&amp;#39;s tweet asking about DevOps transitions&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Today, Carol &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;Carols10cents&#x2F;status&#x2F;728260834976276480&quot;&gt;asked&lt;&#x2F;a&gt; me about how a current sysadmin can pivot into a junior &quot;devops&quot; role. 10 tweets into the reply, it became obvious that my thoughts on that type of transition won&#x27;t fit well into 140-character blocks.&lt;&#x2F;p&gt;
&lt;p&gt;My goal with this post is to catalog what I&#x27;ve done to reach my current level of success in the field, and outline the steps that a reader could take to mimic me.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;facets-of-devops&quot;&gt;Facets of DevOps&lt;&#x2F;h1&gt;
&lt;p&gt;In my opinion, 3 distinct areas of focus have made me the kind of person from whom others solicit DevOps advice:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Cultural background&lt;&#x2F;li&gt;
&lt;li&gt;Technical skills&lt;&#x2F;li&gt;
&lt;li&gt;Self-promotion&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I place &quot;cultural background&quot; first because many people with all the skills to succeed at &quot;DevOps&quot; roles choose or get stuck with other job titles, and everyone touches a slightly different point on the metaphorical elephant of what &quot;DevOps&quot; even means.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;cultural-background&quot;&gt;Cultural Background&lt;&#x2F;h1&gt;
&lt;p&gt;What does &quot;DevOps&quot; mean to you?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Sysadmins who aren&#x27;t afraid of automation?&lt;&#x2F;li&gt;
&lt;li&gt;2 sets of job requirements for the price of 1 engineer?&lt;&#x2F;li&gt;
&lt;li&gt;Developers who understand what the servers are actually doing?&lt;&#x2F;li&gt;
&lt;li&gt;Reducing the traditional divide between &quot;development&quot; and &quot;operations&quot; silos?&lt;&#x2F;li&gt;
&lt;li&gt;A buzzword that increases your number of weekly recruiter emails?&lt;&#x2F;li&gt;
&lt;li&gt;People who use configuration management, aka &quot;infrastructure as code&quot;?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;From my experiences starting Oregon State University&#x27;s DevOps Bootcamp training program, speaking on DevOps related topics at a variety of industry conferences, and generally being a professional in the field, I&#x27;ve seen the term defined all of those ways and more.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Someone switching from &quot;sysadmin&quot; to &quot;devops&quot; should clearly define how they want their day-to-day job duties to change, and how their skills will need to change as a result.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;technical-skills&quot;&gt;Technical Skills&lt;&#x2F;h1&gt;
&lt;p&gt;The best way to figure out the technical skills required for your dream job will always be to talk to people in the field, and read a lot of job postings to see what you&#x27;ll need on your resume and LinkedIn to catch a recruiter&#x27;s eye.&lt;&#x2F;p&gt;
&lt;p&gt;In my opinion, the bare minimum of technical skills that an established sysadmin would need in order to apply for DevOps roles are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Use a configuration management tool -- Chef, Puppet, Salt, or Ansible -- to set up a web server in a VM.&lt;&#x2F;li&gt;
&lt;li&gt;Write a script in Python to do something more than Hello World -- an IRC bot or tool to gather data from an API is fine.&lt;&#x2F;li&gt;
&lt;li&gt;Know enough about Git and GitHub to submit a pull request to fix something about an open source tool that other sysadmins use, even if it&#x27;s just a typo in the docs.&lt;&#x2F;li&gt;
&lt;li&gt;Understand enough about continuous integration testing to use it on a personal project, such as TravisCI on a GitHub repo, and appreciate the value of unit and integration tests for software.&lt;&#x2F;li&gt;
&lt;li&gt;Be able to tell a story about successfully troubleshooting a problem on a Linux or BSD server, and what you did to prevent it from happening again.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Keep in mind that your job in an interview is to represent what you know and how well you can learn new things. If you&#x27;re missing one of the above skills, go ask for help on how to build it.&lt;&#x2F;p&gt;
&lt;p&gt;Once you have all the experiences that I listed, &lt;strong&gt;you are no longer allowed to skip applying for an interesting role because you don&#x27;t feel you know enough&lt;&#x2F;strong&gt;. It&#x27;s the employer&#x27;s job to decide whether they want to grow you into the candiate of their dreams, and your job to give them a chance. Remember that a job posting describes the person leaving a role, and if you started with every skill listed, you&#x27;d probably be bored and not challenged to your full potential.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;self-promotion&quot;&gt;Self Promotion&lt;&#x2F;h1&gt;
&lt;p&gt;&quot;DevOps&quot; is a label that engineers apply to themselves, then justify with various experiences and qualifications.&lt;&#x2F;p&gt;
&lt;p&gt;The path to becoming a community leader begins at engaging with the community. &lt;strong&gt;Look up DevOps-related conferences&lt;&#x2F;strong&gt; -- find video recordings of talks from recent DevOps Days events, and see what names are on the schedules of upcoming DevOps conferences.&lt;&#x2F;p&gt;
&lt;p&gt;Look at which technologies the recent conferences have discussed, then look up talks about them from other events. Get into the IRC or Slack channels of the tools you want to become more expert at, listen until you know the answers to common questions, then &lt;strong&gt;start helping beginners newer than yourself&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Reach out to speakers&lt;&#x2F;strong&gt; whose talks you&#x27;ve enjoyed, and don&#x27;t be afraid to ask them for advice. Remember that they&#x27;re often extremely busy, so a short message with a compliment on their talk and a specific request for a suggestion is more likely to get a reply than overly vague requests. This type of networking will make your name familiar when their companies ask them to help recruit DevOps engineers, and can build valuable professional friendships that provide job leads and other assistance.&lt;&#x2F;p&gt;
&lt;p&gt;Contribute to the DevOps-related projects that you identify as having healthy communities. For configuration management, I&#x27;ve found that SaltStack is a particularly welcoming group. Find the source code on GitHub, examine the issue tracker, pick something easy, and submit a pull request fixing the bug. As you graduate to working on more challenging or larger issues, remember to &lt;strong&gt;advertise your involvment with the project on your LinkedIn profile&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;Additionally, help others out by blogging what you learn during these adventures. If you ever find that Google doesn&#x27;t have useful results for an error message that you searched, &lt;strong&gt;write a blog post&lt;&#x2F;strong&gt; with the message and how you fixed it. If you&#x27;re tempted to bikeshed over which blogging platform to use, default to GitHub Pages, as a static site hosted there is easy to move to your own hosting later if you so desire.&lt;&#x2F;p&gt;
&lt;p&gt;Examine job postings for roles like you want, and make sure the key buzzwords appear on your LinkedIn profile wherever appropriate. A complete LinkedIn profile for even a relatively new DevOps engineer draws a surprising number of recruiters for DevOps-related roles. If you&#x27;re just starting out in the field, I&#x27;d recommend expressing interest in &lt;em&gt;every&lt;&#x2F;em&gt; opportunity that you&#x27;re contacted about, progressing to at least a phone interview if possible, and getting all the feedback you can about your performance afterwards. It&#x27;s especially important to &lt;strong&gt;interview at companies that you can&#x27;t see yourself enjoying a job at&lt;&#x2F;strong&gt;, because you can practice asking probing questions that tell you whether an employer will be a good fit for you. (check out &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;doctorj&#x2F;interview-questions&quot;&gt;this post&lt;&#x2F;a&gt; for ideas).&lt;&#x2F;p&gt;
&lt;p&gt;Another trick for getting to an interview is to start something with DevOps in the name. It could be anything from a curated blog to a meetup to an online &quot;book club&quot; for DevOps-related papers, but leading something with a cool name seems to be highly attractive to recruiters. Another way to increase your visibility in the field is to &lt;strong&gt;give a talk&lt;&#x2F;strong&gt; at any local conference, especially LinuxFest and DevOpsDays events. Putting together an introductory talk on a useful technology only requires intermediate proficiency, and is a great way to build your personal brand.&lt;&#x2F;p&gt;
&lt;p&gt;To summarize, there are really 4 tricks to getting DevOps interviews, and you should interview as much as you can to get a feeling for what DevOps means to different parts of the industry:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Contribute back to the open source tools that you use&lt;&#x2F;li&gt;
&lt;li&gt;Network with established professionals&lt;&#x2F;li&gt;
&lt;li&gt;Optimize your LinkedIn and other professional profiles to draw recruiters&lt;&#x2F;li&gt;
&lt;li&gt;Be the founder of something.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;questions&quot;&gt;Questions?&lt;&#x2F;h1&gt;
&lt;p&gt;I collect interesting job search and interview advice links at the bottom of my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;resume&#x2F;blob&#x2F;master&#x2F;README.md&quot;&gt;resume repo readme&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I bolded each paragraph&#x27;s key points in the hopes of making them easier to read.&lt;&#x2F;p&gt;
&lt;p&gt;You&#x27;re welcome to reach out to me at &lt;span class=&quot;title-ref&quot;&gt;blog&lt;&#x2F;span&gt; @ &lt;span class=&quot;title-ref&quot;&gt;edunham.net&lt;&#x2F;span&gt; or &lt;span class=&quot;title-ref&quot;&gt;@qedunham&lt;&#x2F;span&gt; on Twitter if you have other questions. If I made a dumb typo or omitted some information in this post, either tell me about it or just throw a pull request at &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;site&quot;&gt;the repo&lt;&#x2F;a&gt; to fix it and give yourself credit.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Persona and third-party cookies in Firefox</title>
        <published>2016-04-18T00:00:00+00:00</published>
        <updated>2016-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/04/18/persona_and_3rd_party_cookies_in_firefox/"/>
        <id>https://edunham.net/2016/04/18/persona_and_3rd_party_cookies_in_firefox/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/04/18/persona_and_3rd_party_cookies_in_firefox/">&lt;p&gt;Although its front page claims we&#x27;ve deprecated &lt;a href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;Persona&quot;&gt;persona&lt;&#x2F;a&gt;, it&#x27;s the only way to log into the &lt;a href=&quot;http:&#x2F;&#x2F;statusupdates.dev.mozaws.net&#x2F;&quot;&gt;statusboard&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;air.mozilla.org&#x2F;&quot;&gt;Air Mozilla&lt;&#x2F;a&gt;. For a long time, I was unable to log into any site using Persona from Firefox 43 and 44 because of an error about my browser not being configured to accept third-party cookies.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;support.mozilla.org&#x2F;en-US&#x2F;kb&#x2F;enable-and-disable-cookies-website-preferences&quot;&gt;support article&lt;&#x2F;a&gt; on the topic says that checking the &quot;always accept cookies&quot; box should fix the problem. I tried setting &quot;accept third-party cookies&quot; to &quot;Always&quot;, and yet the error persisted. (setting the top-level history configuration to &quot;always remember history&quot; didn&#x27;t affect the error either).&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, there&#x27;s also an &quot;Exceptions&quot; button by the &quot;Accept cookies from sites&quot; checkbox. Editing the exceptions list to universally allow &quot;&lt;a href=&quot;http:&#x2F;&#x2F;persona.org&quot;&gt;http:&#x2F;&#x2F;persona.org&lt;&#x2F;a&gt;&quot; lets me use Persona in Firefox normally.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;persona-exception.png&quot; alt=&quot;Firefox Persona exception dialog&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Note: The original screenshot is not available in this archive.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s the fix, but I don&#x27;t know whose bug it is. Did Firefox mis-balance privacy against convenience? Is the &quot;always accept third-party cookies&quot; setting&#x27;s failure to accept a cookie without an exception some strange edge case of a broken regex? Is Persona in the wrong for using a design that requires third-party cookies at all? Who knows!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Plushie Rustacean Pattern</title>
        <published>2016-04-11T00:00:00+00:00</published>
        <updated>2016-04-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/04/11/plushie_rustacean_pattern/"/>
        <id>https://edunham.net/2016/04/11/plushie_rustacean_pattern/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/04/11/plushie_rustacean_pattern/">&lt;p&gt;I made a &lt;a href=&quot;http:&#x2F;&#x2F;www.rustacean.net&#x2F;&quot;&gt;Rustacean&lt;&#x2F;a&gt;. He&#x27;s cute. You can make one too.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;claws-on-canvas.jpg&quot; alt=&quot;Ferris crab on sewing pattern&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;you-ll-need&quot;&gt;You&#x27;ll Need&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;A couple square feet of orange polar fleece, or any other orange fabric that won&#x27;t stretch or fray too much&lt;&#x2F;li&gt;
&lt;li&gt;A handful of stuffing. I cannibalized a throw pillow.&lt;&#x2F;li&gt;
&lt;li&gt;A needle and some orange thread&lt;&#x2F;li&gt;
&lt;li&gt;Black and white fabric scraps and thread, or black and white embroidery floss, for making the face.&lt;&#x2F;li&gt;
&lt;li&gt;Intermediate sewing skills&lt;&#x2F;li&gt;
&lt;li&gt;This pattern&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;the-pattern&quot;&gt;The Pattern&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;tinycrab-blueprint.png&quot; alt=&quot;Ferris plushie sewing pattern blueprint&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Get yourself a front, back, underside, and claw drawn on paper, either by printing them out or tracing from a screen. The front, back, and underside should have horizontal symmetry, except for the face placement. Make sure the points marked in red and blue on this pattern are noted on your paper.&lt;&#x2F;p&gt;
&lt;p&gt;Mine measure about 6&quot; wide between the points marked in red.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;sewing-vocabulary&quot;&gt;Sewing vocabulary&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;right side&lt;&#x2F;code&gt; of a fabric is what ends up on the outside of the finished item. The &lt;code&gt;wrong side&lt;&#x2F;code&gt; ends up where you can&#x27;t see it. Some fabrics have both sides the same; in that case, the wrong side is whichever one you feel like tracing the pattern onto.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;seam allowance&lt;&#x2F;code&gt; is some extra fabric that ends up on the inside of the item when you&#x27;re done. The pattern above does &lt;strong&gt;not&lt;&#x2F;strong&gt; include seam allowance. This means that if you cut the fabric along the lines in the pattern, your finished rustacean will be tiny and sad and shaped wrong. You cut the paper along the lines, then trace it onto the fabric, then sew along the lines.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;applique&lt;&#x2F;code&gt; is where you sew one piece of fabric onto the surface of another to make a design.&lt;&#x2F;li&gt;
&lt;li&gt;There are a bunch of great youtube videos on basic sewing skills. Watch whichever ones you need.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;assembly&quot;&gt;Assembly&lt;&#x2F;h1&gt;
&lt;ol&gt;
&lt;li&gt;Trace a front, a back, an underside, and the 2 claws onto the wrong side of your fabric with whatever will write on it without bleeding through. Make sure to transfer the blue centerline marks and the red three-point join marks.&lt;&#x2F;li&gt;
&lt;li&gt;Cut out the shapes you just traced, leaving about 1&quot; of margin around them. We&#x27;ll trim the seams properly later, so don&#x27;t worry about getting it exact.&lt;&#x2F;li&gt;
&lt;li&gt;Find a couple claw-sized chunks of leftover fabric and pin one to the back of each claw (right sides together, of course).&lt;&#x2F;li&gt;
&lt;li&gt;Sew around both claws, leaving the arm ends open so you can turn them. I find it&#x27;s easiest to backstitch, and you can get away with stitches up to about 1.5mm apart with normal weight polar fleece.&lt;&#x2F;li&gt;
&lt;li&gt;Trim around the outside of the seams on the claws to leave about 1&#x2F;4&quot; seam allowance, and clip right up to the stitches in the concave spot. If you backstitched, make sure flip them over before trimming the seams so you don&#x27;t accidentally cut through the longer stitches.&lt;&#x2F;li&gt;
&lt;li&gt;Turn the claws so the right side of the fabric is out and the seams are on the inside, and stuff them with stuffing or fabric scraps. A pair of wooden chopsticks from a fast food place are a great tool for turning and stuffing.&lt;&#x2F;li&gt;
&lt;li&gt;Put the front and back pieces right sides together so the points marked in red and blue on the pattern line up. Pin them together.&lt;&#x2F;li&gt;
&lt;li&gt;Sew from one red mark to the other along Ferris&#x27;s spiky back.&lt;&#x2F;li&gt;
&lt;li&gt;Trim around the spikes leaving about 1&#x2F;4&quot; seam allowance, clipping right up to the seam in the concave spots.&lt;&#x2F;li&gt;
&lt;li&gt;Figure out which side is front (hint, it has only 2 legs rather than 4). Imagine where Ferris&#x27;s little face will go when he&#x27;s finished. Now, pin both claws onto the &lt;strong&gt;right side&lt;&#x2F;strong&gt; of the front piece, so they&#x27;ll be oriented correctly when he&#x27;s done. If in doubt, pin the bottom front in place and turn the whole thing inside out to make sure the claws are right.&lt;&#x2F;li&gt;
&lt;li&gt;Match the center front of the underside with the center of Ferris&#x27;s front (both have a blue &lt;code&gt;+&lt;&#x2F;code&gt; on the pattern). Be sure the pieces have their right sides together and the claws are sandwiched between them.&lt;&#x2F;li&gt;
&lt;li&gt;Match the points marked with red triangles on each side of the front and underside together and pin them. &lt;strong&gt;If the claws are sticking out at this point, go back to step 10 and try again&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Sew from one red mark to the other to join Ferris&#x27;s front to the front of his underside. Put a few extra stitches in the part of the seam where his &quot;arms&quot;&#x2F;claws are attached, to make sure they can&#x27;t be pulled out.&lt;&#x2F;li&gt;
&lt;li&gt;Trim around the 2 tiny legs that you&#x27;ve sewn so far, with about 1&#x2F;8&quot; seam allowance.&lt;&#x2F;li&gt;
&lt;li&gt;Now you can applique his face onto the right side of his front. Or embroider it if you know how. Cut the black and white felt scraps into face-shaped pieces and sew them down, giving Ferris whatever expression you want.&lt;&#x2F;li&gt;
&lt;li&gt;Line up the 4 back legs on the underside and back pieces, and pin them right sides toether. Sew everything except the part marked in green --that&#x27;s the hole through which you&#x27;ll turn him inside out.&lt;&#x2F;li&gt;
&lt;li&gt;Trim around those last 4 legs, leaving at least 1&#x2F;8&quot; seam allowance. Don&#x27;t cut away any more fabric from the bit marked in green. If you leave a bit of extra fabric around the leg seams, they&#x27;ll be harder to turn but require less stuffing.&lt;&#x2F;li&gt;
&lt;li&gt;Turn Ferris right side out. Again, chopsticks or the non-pointy end of a barbeque skewer are useful for getting the pointy bits to do the right thing.&lt;&#x2F;li&gt;
&lt;li&gt;Stuff Ferris with the filling. I filled mine quite loosely, because it makes him softer and more huggable. If you overfill his body, his spikes will look silly. If you overfill his legs, they&#x27;ll stick out in funny directions and not bend right.&lt;&#x2F;li&gt;
&lt;li&gt;Tuck the seam allowance back into the hole through which you stuffed Ferris and sew it shut. Congratulations, you have your own toy crab!&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;the-finished-product&quot;&gt;The Finished Product&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;pocket-ferris-finished.jpg&quot; alt=&quot;Completed palm-sized Ferris plushie with scale reference&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;He&#x27;s cute, cuddly, and palm-sized. Lego dude for scale.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Could Rust have a left-pad incident?</title>
        <published>2016-03-24T00:00:00+00:00</published>
        <updated>2016-03-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/03/24/could_rust_have_a_left_pad_incident/"/>
        <id>https://edunham.net/2016/03/24/could_rust_have_a_left_pad_incident/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/03/24/could_rust_have_a_left_pad_incident/">&lt;p&gt;The short answer: No.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-happened-with-left-pad&quot;&gt;What happened with &lt;code&gt;left-pad&lt;&#x2F;code&gt;?&lt;&#x2F;h1&gt;
&lt;p&gt;The Node community had a lot of &lt;a href=&quot;http:&#x2F;&#x2F;blog.npmjs.org&#x2F;post&#x2F;141577284765&#x2F;kik-left-pad-and-npm&quot;&gt;drama&lt;&#x2F;a&gt; this week when a developer unpublished a package on which a lot of the world depended.&lt;&#x2F;p&gt;
&lt;p&gt;This was fundamentally possible because NPM offers an &lt;a href=&quot;https:&#x2F;&#x2F;docs.npmjs.com&#x2F;cli&#x2F;unpublish&quot;&gt;unpublish&lt;&#x2F;a&gt; feature. Although the docs for &lt;code&gt;unpublish&lt;&#x2F;code&gt; admonish users that &quot;It is generally considered bad behavior to remove versions of a library that others are depending on!&quot; in large bold print, the feature is available.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-the-rust-equivalent&quot;&gt;What&#x27;s the Rust equivalent?&lt;&#x2F;h1&gt;
&lt;p&gt;The Rust package manager, Cargo, is similar to NPM in that it helps users get the libraries on which their projects depend. Rust&#x27;s analog to the NPM index is &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;&quot;&gt;crates.io&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The best explanation of Cargo&#x27;s robustness against &lt;code&gt;unpublish&lt;&#x2F;code&gt; exploits is &lt;a href=&quot;http:&#x2F;&#x2F;doc.crates.io&#x2F;crates-io.html&quot;&gt;the docs themselves&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;cargo yank&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Occasions may arise where you publish a version of a crate that actually ends up being broken for one reason or another (syntax error, forgot to include a file, etc.). For situations such as this, Cargo supports a “yank” of a version of a crate.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cargo yank --vers 1.0.1
&lt;&#x2F;span&gt;&lt;span&gt;$ cargo yank --vers 1.0.1 --undo
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A yank &lt;strong&gt;does not&lt;&#x2F;strong&gt; delete any code. This feature is not intended for deleting accidentally uploaded secrets, for example. If that happens, you must reset those secrets immediately.&lt;&#x2F;p&gt;
&lt;p&gt;The semantics of a yanked version are that no new dependencies can be created against that version, but all existing dependencies continue to work. One of the major goals of crates.io is to act as a permanent archive of crates that does not change over time, and allowing deletion of a version would go against this goal. Essentially a yank means that all projects with a &lt;code&gt;Cargo.lock&lt;&#x2F;code&gt; will not break, while any future &lt;code&gt;Cargo.lock&lt;&#x2F;code&gt; files generated will not list the yanked version.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;As Cargo author Alex Crichton clarified in &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;servo&#x2F;servo&#x2F;issues&#x2F;10142#issuecomment-200444583&quot;&gt;a GitHub comment&lt;&#x2F;a&gt; yesterday, the only way that it&#x27;s possible to remove code from &lt;code&gt;crates.io&lt;&#x2F;code&gt; is to compel the Rust tools team to edit the database and S3 bucket.&lt;&#x2F;p&gt;
&lt;p&gt;Even if a crate maintainer leaves the community in anger or legal action is taken against a crate, this workflow ensures that code deletion is only possible by a small group of people with the motivation and authority to do it in the way that&#x27;s least problematic for users of the Rust language.&lt;&#x2F;p&gt;
&lt;p&gt;For more information on the crates.io package and copyright policies, see &lt;a href=&quot;https:&#x2F;&#x2F;internals.rust-lang.org&#x2F;t&#x2F;crates-io-package-policies&#x2F;1041&quot;&gt;this internals thread&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;but-i-just-want-to-left-pad-a-string-in-rust&quot;&gt;But I just want to left pad a string in Rust??&lt;&#x2F;h1&gt;
&lt;p&gt;Although a &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;left-pad&quot;&gt;left-pad crate&lt;&#x2F;a&gt; was created as a joke, you should probably just use the &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;fmt&#x2F;index.html#fillalignment&quot;&gt;format! built-in&lt;&#x2F;a&gt; from the standard library.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reducing SaltStack log verbosity for TravisCI</title>
        <published>2016-03-23T00:00:00+00:00</published>
        <updated>2016-03-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/03/23/reducing_saltstack_log_verbosity_for_travisci/"/>
        <id>https://edunham.net/2016/03/23/reducing_saltstack_log_verbosity_for_travisci/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/03/23/reducing_saltstack_log_verbosity_for_travisci/">&lt;p&gt;Servo has some &lt;a href=&quot;http:&#x2F;&#x2F;saltstack.com&#x2F;&quot;&gt;Salt&lt;&#x2F;a&gt; configs, hosted on GitHub, for which changes are smoke-tested on TravisCI before they&#x27;re deployed. Travis only shows the first 10k lines of log output, so I want to minimize the amount of extraneous information that the states print.&lt;&#x2F;p&gt;
&lt;p&gt;My salt state looks like::&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;android-sdk:
&lt;&#x2F;span&gt;&lt;span&gt;  archive.extracted:
&lt;&#x2F;span&gt;&lt;span&gt;    - name: {{ common.homedir }}&#x2F;android&#x2F;sdk&#x2F;{{ android.sdk.version }}
&lt;&#x2F;span&gt;&lt;span&gt;    - source: https:&#x2F;&#x2F;dl.google.com&#x2F;android&#x2F;android-sdk_{{
&lt;&#x2F;span&gt;&lt;span&gt;      android.sdk.version }}-linux.tgz
&lt;&#x2F;span&gt;&lt;span&gt;    - source_hash: sha512={{ android.sdk.sha512 }}
&lt;&#x2F;span&gt;&lt;span&gt;    - archive_format: tar
&lt;&#x2F;span&gt;&lt;span&gt;    - archive_user: user
&lt;&#x2F;span&gt;&lt;span&gt;    - if_missing: {{ common.homedir }}&#x2F;android&#x2F;sdk&#x2F;{{ android.sdk.version
&lt;&#x2F;span&gt;&lt;span&gt;      }}&#x2F;android-sdk-linux
&lt;&#x2F;span&gt;&lt;span&gt;    - require:
&lt;&#x2F;span&gt;&lt;span&gt;      - user: user
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The output in TravisCI is::&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ID: android-sdk
&lt;&#x2F;span&gt;&lt;span&gt;Function: archive.extracted
&lt;&#x2F;span&gt;&lt;span&gt;Name: &#x2F;home&#x2F;user&#x2F;android&#x2F;sdk&#x2F;r24.4.1
&lt;&#x2F;span&gt;&lt;span&gt;Result: True
&lt;&#x2F;span&gt;&lt;span&gt;Comment: https:&#x2F;&#x2F;dl.google.com&#x2F;android&#x2F;android-sdk_r24.4.1-linux.tgz extracted in &#x2F;home&#x2F;user&#x2F;android&#x2F;sdk&#x2F;r24.4.1&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;Started: 17:46:25.900436
&lt;&#x2F;span&gt;&lt;span&gt;Duration: 19540.846 ms
&lt;&#x2F;span&gt;&lt;span&gt;Changes:
&lt;&#x2F;span&gt;&lt;span&gt;    ----------
&lt;&#x2F;span&gt;&lt;span&gt;    directories_created:
&lt;&#x2F;span&gt;&lt;span&gt;        - &#x2F;home&#x2F;user&#x2F;android&#x2F;sdk&#x2F;r24.4.1&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        - &#x2F;home&#x2F;user&#x2F;android&#x2F;sdk&#x2F;r24.4.1&#x2F;android-sdk-linux
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    extracted_files:
&lt;&#x2F;span&gt;&lt;span&gt;        ... 2755 lines listing one file per line that I don&amp;#39;t want to see in the log
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;ref&#x2F;states&#x2F;all&#x2F;salt.states.archive.html&quot;&gt;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;ref&#x2F;states&#x2F;all&#x2F;salt.states.archive.html&lt;&#x2F;a&gt; has useful guidance on how to increase the &lt;code&gt;tar&lt;&#x2F;code&gt; state&#x27;s verbosity, but not to decrease it. This is because the extra 2755 lines aren&#x27;t coming from &lt;code&gt;tar&lt;&#x2F;code&gt; itself, but from Salt assuming that we want to know.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;terse-outputter-settings&quot;&gt;&lt;code&gt;terse&lt;&#x2F;code&gt; outputter settings&lt;&#x2F;h1&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;ref&#x2F;output&#x2F;all&#x2F;salt.output.highstate.html&quot;&gt;outputter&lt;&#x2F;a&gt; takes several &lt;code&gt;state_output&lt;&#x2F;code&gt; setting options. The &lt;code&gt;terse&lt;&#x2F;code&gt; option summarizes the result of each state into a single line.&lt;&#x2F;p&gt;
&lt;p&gt;There are a couple places you can set this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Invoke Salt with &lt;code&gt;salt --state-output=terse hostname state.highstate&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Add the line &lt;code&gt;state_output: terse&lt;&#x2F;code&gt; to &lt;code&gt;&#x2F;etc&#x2F;salt&#x2F;minion&lt;&#x2F;code&gt;, if you&#x27;re using &lt;code&gt;salt-call&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Setting &lt;code&gt;state_output_terse&lt;&#x2F;code&gt; is &lt;a href=&quot;http:&#x2F;&#x2F;fossies.org&#x2F;linux&#x2F;salt&#x2F;salt&#x2F;output&#x2F;highstate.py&quot;&gt;apparently&lt;&#x2F;a&gt; an option, though I can&#x27;t find any example of a real-world salt config that uses it&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Setting the &lt;code&gt;terse&lt;&#x2F;code&gt; option in &lt;code&gt;&#x2F;etc&#x2F;salt&#x2F;minion&lt;&#x2F;code&gt; dropped the output of a highstate from over 10,000 lines to about 2500.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Fixing sudo errors from the command line on OSX</title>
        <published>2016-03-14T00:00:00+00:00</published>
        <updated>2016-03-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/03/14/fixing_sudo_on_osx/"/>
        <id>https://edunham.net/2016/03/14/fixing_sudo_on_osx/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/03/14/fixing_sudo_on_osx/">&lt;p&gt;The first symptom that I had made a terrible mistake showed up in an Ansible playbook:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;GATHERING FACTS
&lt;&#x2F;span&gt;&lt;span&gt;***************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;fatal: [...] =&amp;gt; ssh connection closed waiting for a privilege escalation password prompt
&lt;&#x2F;span&gt;&lt;span&gt;fatal: [...] =&amp;gt; ssh connection closed waiting for a privilege escalation password prompt
&lt;&#x2F;span&gt;&lt;span&gt;fatal: [...] =&amp;gt; ssh connection closed waiting for sudo password prompt
&lt;&#x2F;span&gt;&lt;span&gt;fatal: [...] =&amp;gt; ssh connection closed waiting for sudo password prompt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That looks like the sudo binary might be broken. To rule out Ansible problems, remote into the machine and try to use sudo:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;administrators-Mac-mini:~ administrator$ sudo ls
&lt;&#x2F;span&gt;&lt;span&gt;sudo: effective uid is not 0, is sudo installed setuid root?
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This meant that there was a file permissions problem:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;working-host administrator$ ls -al &#x2F;usr&#x2F;bin&#x2F;sudo
&lt;&#x2F;span&gt;&lt;span&gt;-r-s--x--x  1 root  wheel  164560 Sep  9  2014 &#x2F;usr&#x2F;bin&#x2F;sudo
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;broken-host administrator$ ls -al &#x2F;usr&#x2F;bin&#x2F;sudo
&lt;&#x2F;span&gt;&lt;span&gt;-rwxrwxr-x  1 root  wheel  164560 Sep  9  2014 &#x2F;usr&#x2F;bin&#x2F;sudo
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the problem is reduced to fixing the permissions. One does not simply sudo to root, because there&#x27;s no working sudo. However, Apple provides &lt;a href=&quot;http:&#x2F;&#x2F;azchipka.thechipkahouse.com&#x2F;2013&#x2F;11&#x2F;29&#x2F;enabling-root-user-mavericks-mac-os-10-9&#x2F;&quot;&gt;a utility&lt;&#x2F;a&gt; which allows you to enable root login using only the administrator account&#x27;s permissions:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;broken-host administrator$ dsenableroot
&lt;&#x2F;span&gt;&lt;span&gt;username = administrator
&lt;&#x2F;span&gt;&lt;span&gt;user password:
&lt;&#x2F;span&gt;&lt;span&gt;root password:
&lt;&#x2F;span&gt;&lt;span&gt;verify root password:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;dsenableroot:: ***Successfully enabled root user.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first password is the current one for the administrator account, and the other two should be the same string and will become the root account&#x27;s password.&lt;&#x2F;p&gt;
&lt;p&gt;After enabling root login, disconnect then SSH into the host as root:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;broken-host root# chmod 4411 &#x2F;usr&#x2F;bin&#x2F;sudo
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And test that the fix fixed it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;broken-host root# su administrator
&lt;&#x2F;span&gt;&lt;span&gt;broken-host administrator$ sudo ls
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, clean up after yourself to inconvenience any future attackers:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;broken-host administrator$ dsenableroot -d
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Moral of the story: Errant chowns of &lt;code&gt;&#x2F;usr&#x2F;bin&lt;&#x2F;code&gt; are just as bad when they come from automation as when they come from humans.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Ansible, Vagrant, and changed host keys</title>
        <published>2016-03-08T00:00:00+00:00</published>
        <updated>2016-03-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/03/08/ansible_vagrant_and_changed_host_keys/"/>
        <id>https://edunham.net/2016/03/08/ansible_vagrant_and_changed_host_keys/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/03/08/ansible_vagrant_and_changed_host_keys/">&lt;p&gt;Related to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible&#x2F;issues&#x2F;9442&quot;&gt;this bug&lt;&#x2F;a&gt;, the &lt;a href=&quot;https:&#x2F;&#x2F;www.vagrantup.com&#x2F;docs&#x2F;provisioning&#x2F;ansible.html&quot;&gt;Vagrant Ansible provisioner&lt;&#x2F;a&gt; seems to ignore some system settings.&lt;&#x2F;p&gt;
&lt;p&gt;The symptom is that when you update a previously used Vagrant box, or otherwise change its host key, Ansible provisioning fails with the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;fatal: [hostname] =&amp;gt; SSH Error: Host key verification failed.
&lt;&#x2F;span&gt;&lt;span&gt;    while connecting to 127.0.0.1:2200
&lt;&#x2F;span&gt;&lt;span&gt;It is sometimes useful to re-run the command using -vvvv, which prints SSH
&lt;&#x2F;span&gt;&lt;span&gt;debug output to help diagnose the issue.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The standard solution would be to forget about the old host key with &lt;code&gt;ssh-keygen -R 127.0.0.1:2200&lt;&#x2F;code&gt; or ignore the change with &lt;code&gt;export ANSIBLE_HOST_KEY_CHECKING=false&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you trust the box not to be evil and expect its host key to change frequently due to your testing, a fix which the Ansible provisioner does respect is to add &lt;code&gt;ansible.host_key_checking = false&lt;&#x2F;code&gt; to the Vagrantfile, like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Vagrant.configure(2) do |config|
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    config.vm.define &amp;quot;hostname&amp;quot; do |prodmaster|
&lt;&#x2F;span&gt;&lt;span&gt;        hostname.vm.provision &amp;quot;ansible&amp;quot; do |ansible|
&lt;&#x2F;span&gt;&lt;span&gt;            ansible.playbook = &amp;quot;provision&#x2F;hostname.yaml&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;            ansible.sudo = true
&lt;&#x2F;span&gt;&lt;span&gt;            ansible.host_key_checking = false
&lt;&#x2F;span&gt;&lt;span&gt;            ansible.verbose = &amp;#39;vvvv&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;            ansible.extra_vars = { ansible_ssh_user: &amp;#39;vagrant&amp;#39;}
&lt;&#x2F;span&gt;&lt;span&gt;        end
&lt;&#x2F;span&gt;&lt;span&gt;    end
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;end
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Vidyo with Ubuntu and i3wm</title>
        <published>2016-03-07T00:00:00+00:00</published>
        <updated>2016-03-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/03/07/vidyo_with_ubuntu_and_i3wm/"/>
        <id>https://edunham.net/2016/03/07/vidyo_with_ubuntu_and_i3wm/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/03/07/vidyo_with_ubuntu_and_i3wm/">&lt;p&gt;Mozilla uses &lt;a href=&quot;http:&#x2F;&#x2F;www.vidyo.com&#x2F;&quot;&gt;Vidyo&lt;&#x2F;a&gt; for virtual meetings across distributed teams. If it doesn&#x27;t work on your laptop, you can use the mobile client or book a meeting room in an office, but neither of those solutions is optimal when working from home.&lt;&#x2F;p&gt;
&lt;p&gt;Vidyo users within Mozilla can download a &lt;code&gt;.deb&lt;&#x2F;code&gt; or &lt;code&gt;.rpm&lt;&#x2F;code&gt; installer from &lt;a href=&quot;https:&#x2F;&#x2F;edunham.net&#x2F;2016&#x2F;03&#x2F;07&#x2F;vidyo_with_ubuntu_and_i3wm&#x2F;v.mozilla.org&quot;&gt;v.mozilla.org&lt;&#x2F;a&gt;. On Ubuntu, it&#x27;s easy to install the downloaded package with &lt;code&gt;sudo dpkg -i path&#x2F;to&#x2F;the&#x2F;file.deb&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The issue is that when you invoke &lt;code&gt;VidyoDesktop&lt;&#x2F;code&gt; from your launcher of choice (dmenu for me), i3 does what&#x27;s usually the right thing and makes the client fullscreen in a tile. This doesn&#x27;t allow the interface to pop up a floating window with the confirm dialog when you try to join a room, so you can&#x27;t.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mod-shift-space&quot;&gt;mod + shift + space&lt;&#x2F;h1&gt;
&lt;p&gt;Mod was &lt;code&gt;alt&lt;&#x2F;code&gt; by default last time I installed i3, but I&#x27;ve since remapped it to the window key (as IRC clients use alt for switching windows). Some people use caps lock as their mod key.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;mod + shift + space&lt;&#x2F;code&gt; makes the window &lt;a href=&quot;https:&#x2F;&#x2F;i3wm.org&#x2F;docs&#x2F;userguide.html#_floating&quot;&gt;floating&lt;&#x2F;a&gt;, which allows it to pop up the confirmation dialog when you try to join a call.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;float-windows-by-default&quot;&gt;Float windows by default&lt;&#x2F;h1&gt;
&lt;p&gt;Alternately, stick the line:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;for_window [class=&amp;quot;VidyoDesktop&amp;quot;] floating enable
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;in your &lt;code&gt;~&#x2F;.i3&#x2F;config&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;installing-vidyo-despite-the-libqt4-gui-error&quot;&gt;Installing Vidyo despite the libqt4-gui error&lt;&#x2F;h1&gt;
&lt;p&gt;Edited as of May 2017: Recent Vidyos depend on a package that&#x27;s not available in Ubuntu&#x27;s repos. The easiest workaround is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sudo dpkg -i --ignore-depends=libqt4-gui path&#x2F;to&#x2F;VidyoInstaller.deb
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Are we &#x27;are we&#x27; yet?</title>
        <published>2016-02-26T00:00:00+00:00</published>
        <updated>2016-02-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/02/26/are_we_building_are_we_sites_yet/"/>
        <id>https://edunham.net/2016/02/26/are_we_building_are_we_sites_yet/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/02/26/are_we_building_are_we_sites_yet/">&lt;p&gt;The Rust community, being founded and enjoyed by a variety of Mozilians, seems to have inherited the tradition of tracking top-level progress metrics using &lt;a href=&quot;https:&#x2F;&#x2F;wiki.mozilla.org&#x2F;Areweyet&quot;&gt;are we&lt;&#x2F;a&gt; sites.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;areweconcurrentyet.com&#x2F;&quot;&gt;Are we concurrent yet?&lt;&#x2F;a&gt; tracks the progres of Rust&#x27;s concurrency ecosystem&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;www.arewewebyet.org&#x2F;&quot;&gt;Are we web yet?&lt;&#x2F;a&gt; tracks the status of Rust&#x27;s HTTP stack, web frameworks, and related libraries&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;areweideyet.com&#x2F;&quot;&gt;Are we IDE yet?&lt;&#x2F;a&gt; provides a list of what features are supported for Rust per IDE, and links to the relevant tracking issues and RFCs&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If this blog post was an &#x27;are we&#x27; page itself, the big text at the top would probably say &quot;Getting There&quot;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Buildbot WithProperties</title>
        <published>2016-02-19T00:00:00+00:00</published>
        <updated>2016-02-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/02/19/buildbot_withproperties/"/>
        <id>https://edunham.net/2016/02/19/buildbot_withproperties/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/02/19/buildbot_withproperties/">&lt;p&gt;Today, I copied an existing command from a Buildbot configuration and then modified it to print a date into a file.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;if &amp;quot;cargo&amp;quot; in component:
&lt;&#x2F;span&gt;&lt;span&gt;    cargo_date_cmd = &amp;quot;echo `date +&amp;#39;%Y-%m-%d&amp;#39;` &amp;gt; &amp;quot; + final_dist_dir + &amp;quot;&#x2F;cargo-build-date.txt&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    f.addStep(MasterShellCommand(name=&amp;quot;Write date to cargo-build-date.txt&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;                             command=[&amp;quot;sh&amp;quot;, &amp;quot;-c&amp;quot;, WithProperties(cargo_date_cmd)] ))
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It broke:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Failure: twisted.internet.defer.FirstError: FirstError[#8, [Failure instance: Traceback (failure with no frames): &amp;lt;class &amp;#39;twisted.internet.defer.FirstError&amp;#39;&amp;gt;: FirstError[#2, [Failure instance: Traceback: &amp;lt;type &amp;#39;exceptions.ValueError&amp;#39;&amp;gt;: unsupported format character &amp;#39;Y&amp;#39; (0x59) at index 14
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Why? &lt;a href=&quot;http:&#x2F;&#x2F;docs.buildbot.net&#x2F;0.8.3&#x2F;WithProperties.html&quot;&gt;WithProperties&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;It turns out that WithProperties should only be used when you need to interpolate strings into an argument, using either &lt;code&gt;%s&lt;&#x2F;code&gt;, &lt;code&gt;%d&lt;&#x2F;code&gt;, or &lt;code&gt;%(propertyname)s&lt;&#x2F;code&gt; syntax in the string.&lt;&#x2F;p&gt;
&lt;p&gt;The lesson here is Buildbot will happily accept &lt;code&gt;WithProperties(&quot;echo &#x27;this command uses no interpolation&#x27;&quot;)&lt;&#x2F;code&gt; in a &lt;code&gt;command&lt;&#x2F;code&gt; argument, and then blow up at you if you ever change the command to have a &lt;code&gt;%&lt;&#x2F;code&gt; in it.&lt;&#x2F;p&gt;
&lt;p&gt;However, it appears that build steps run as &lt;code&gt;MasterShellCommand&lt;&#x2F;code&gt;s without &lt;code&gt;WithProperties&lt;&#x2F;code&gt; do not display their &lt;code&gt;name&lt;&#x2F;code&gt; in the waterfall, but rather say &quot;running&quot; or &quot;ran&quot;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Using Notty</title>
        <published>2016-02-15T00:00:00+00:00</published>
        <updated>2016-02-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/02/15/using_notty/"/>
        <id>https://edunham.net/2016/02/15/using_notty/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/02/15/using_notty/">&lt;p&gt;I recently got the &quot;Hey, you&#x27;re a Rust Person!&quot; question of how to install &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;withoutboats&#x2F;notty&quot;&gt;notty&lt;&#x2F;a&gt; and interact with it.&lt;&#x2F;p&gt;
&lt;p&gt;A TTY was &lt;a href=&quot;http:&#x2F;&#x2F;www.cl.cam.ac.uk&#x2F;~djg11&#x2F;howcomputerswork&#x2F;&quot;&gt;originally&lt;&#x2F;a&gt; a teletypewriter. Linux users will have most likely encountered the concept of TTYs in the context of the TTY1 interface where you end up if your distro fails to start its window manager. Since you use &lt;code&gt;ctrl + alt + f[1,2,...]&lt;&#x2F;code&gt; to switch between these interfaces, it&#x27;s easy to assume that &quot;TTY&quot; refers to an interactive workspace.&lt;&#x2F;p&gt;
&lt;p&gt;Notty itself is &lt;strong&gt;only&lt;&#x2F;strong&gt; a virtual terminal. Think of it as a library meant as a building block for creating graphical terminal emulators. This means that a user who saw it on Hacker News and wants to play around should not ask &quot;how do I install notty&quot;, but rather &quot;how do I run a terminal emulator built on notty?&quot;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;easy-mode&quot;&gt;Easy Mode&lt;&#x2F;h1&gt;
&lt;p&gt;Get some Rust:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;curl -sf https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;brson&#x2F;multirust&#x2F;master&#x2F;blastoff.sh | sh
&lt;&#x2F;span&gt;&lt;span&gt;multirust update nightly
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Get the system dependencies:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sudo apt-get install libcairo2-dev libgdk-pixbuf2.0 libatk1.0 libsdl-pango-dev libgtk-3-dev
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run Notty:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;git clone https:&#x2F;&#x2F;github.com&#x2F;withoutboats&#x2F;notty.git
&lt;&#x2F;span&gt;&lt;span&gt;cd notty&#x2F;scaffolding
&lt;&#x2F;span&gt;&lt;span&gt;multirust run nightly cargo run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And there you have it! As mentioned in the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;withoutboats&#x2F;notty&quot;&gt;notty&lt;&#x2F;a&gt; README, &quot;This terminal is buggy and feature poor and not intended for general use&quot;. Notty is meant as a library for building graphical terminals, and scaffolding is only a &lt;strong&gt;minimal&lt;&#x2F;strong&gt; proof of concept.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;explanation-getting-rust&quot;&gt;Explanation: Getting Rust&lt;&#x2F;h1&gt;
&lt;p&gt;Since the Rust language is still under active development, many features are available in the Nightly version of the compiler which are not yet available in Stable. If you got Rust from your &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;07&#x2F;07&#x2F;rust_packaging_status_across_distros.html&quot;&gt;package manager&lt;&#x2F;a&gt;, you probably are using Stable. To check, run &lt;code&gt;rustc --version&lt;&#x2F;code&gt; and see whether the result says &quot;nightly&quot; in it.&lt;&#x2F;p&gt;
&lt;p&gt;Notty uses some features that&#x27;re available in Nightly but not Stable. If you try to compile it with Stable, you&#x27;ll get an error that makes this obvious:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Compiling notty v0.1.0 (file:&#x2F;&#x2F;&#x2F;home&#x2F;edunham&#x2F;code&#x2F;notty)
&lt;&#x2F;span&gt;&lt;span&gt;src&#x2F;lib.rs:16:1: 16:16 error: #[feature] may not be used on the stable release channel
&lt;&#x2F;span&gt;&lt;span&gt;src&#x2F;lib.rs:16 #![feature(io)]
&lt;&#x2F;span&gt;&lt;span&gt;              ^~~~~~~~~~~~~~~
&lt;&#x2F;span&gt;&lt;span&gt;error: aborting due to previous error
&lt;&#x2F;span&gt;&lt;span&gt;Could not compile `notty`.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When you need to switch between Rust versions frequently, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brson&#x2F;multirust&quot;&gt;multirust&lt;&#x2F;a&gt; is the tool for the job.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;explanation-getting-system-dependencies&quot;&gt;Explanation: Getting system dependencies&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ve reproduced the following error messages in full to help out any confused new Rustaceans Googling for them:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;cairographics.org&#x2F;download&#x2F;&quot;&gt;Cairo&lt;&#x2F;a&gt; is a graphics library that you can get from your system package manager. If you try to compile notty&#x27;s dependencies without it, you&#x27;ll get an error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Build failed, waiting for other jobs to finish...
&lt;&#x2F;span&gt;&lt;span&gt;failed to run custom build command for `cairo-sys-rs v0.2.1`
&lt;&#x2F;span&gt;&lt;span&gt;Process didn&amp;#39;t exit successfully:
&lt;&#x2F;span&gt;&lt;span&gt;`&#x2F;home&#x2F;edunham&#x2F;code&#x2F;notty&#x2F;notty-cairo&#x2F;target&#x2F;release&#x2F;build&#x2F;cairo-sys-rs-1d0cf50d5d2dab2f&#x2F;build-script-build`
&lt;&#x2F;span&gt;&lt;span&gt;(exit code: 101)
&lt;&#x2F;span&gt;&lt;span&gt;--- stderr
&lt;&#x2F;span&gt;&lt;span&gt;thread &amp;#39;&amp;lt;main&amp;gt;&amp;#39; panicked at &amp;#39;`&amp;quot;pkg-config&amp;quot; &amp;quot;--libs&amp;quot; &amp;quot;--cflags&amp;quot; &amp;quot;cairo&amp;quot;` did
&lt;&#x2F;span&gt;&lt;span&gt;not exit successfully: exit code: 1
&lt;&#x2F;span&gt;&lt;span&gt;--- stderr
&lt;&#x2F;span&gt;&lt;span&gt;Package cairo was not found in the pkg-config search path.
&lt;&#x2F;span&gt;&lt;span&gt;Perhaps you should add the directory containing `cairo.pc&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;to the PKG_CONFIG_PATH environment variable
&lt;&#x2F;span&gt;&lt;span&gt;No package &amp;#39;cairo&amp;#39; found
&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &#x2F;home&#x2F;edunham&#x2F;.multirust&#x2F;toolchains&#x2F;nightly&#x2F;cargo&#x2F;registry&#x2F;src&#x2F;github.com-0a35038f75765ae4&#x2F;cairo-sys-rs-0.2.1&#x2F;build.rs:9
&lt;&#x2F;span&gt;&lt;span&gt;note: Run with `RUST_BACKTRACE=1` for a backtrace.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The only other gotcha about the dependencies is that errors about &lt;code&gt;gdk&lt;&#x2F;code&gt; actually mean you need to install the &lt;code&gt;libgtk-3-dev&lt;&#x2F;code&gt; package:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;failed to run custom build command for `gdk-sys v0.2.1`
&lt;&#x2F;span&gt;&lt;span&gt;Process didn&amp;#39;t exit successfully:
&lt;&#x2F;span&gt;&lt;span&gt;`&#x2F;home&#x2F;edunham&#x2F;code&#x2F;notty&#x2F;scaffolding&#x2F;target&#x2F;release&#x2F;build&#x2F;gdk-sys-e1b0a13b32593729&#x2F;build-script-build`
&lt;&#x2F;span&gt;&lt;span&gt;(exit code: 101)
&lt;&#x2F;span&gt;&lt;span&gt;--- stderr
&lt;&#x2F;span&gt;&lt;span&gt;thread &amp;#39;&amp;lt;main&amp;gt;&amp;#39; panicked at &amp;#39;`&amp;quot;pkg-config&amp;quot; &amp;quot;--libs&amp;quot; &amp;quot;--cflags&amp;quot; &amp;quot;gdk-3.0&amp;quot;` did
&lt;&#x2F;span&gt;&lt;span&gt;not exit successfully: exit code: 1
&lt;&#x2F;span&gt;&lt;span&gt;--- stderr
&lt;&#x2F;span&gt;&lt;span&gt;Package gdk-3.0 was not found in the pkg-config search path.
&lt;&#x2F;span&gt;&lt;span&gt;Perhaps you should add the directory containing `gdk-3.0.pc&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;to the PKG_CONFIG_PATH environment variable
&lt;&#x2F;span&gt;&lt;span&gt;No package &amp;#39;gdk-3.0&amp;#39; found
&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &#x2F;home&#x2F;edunham&#x2F;.multirust&#x2F;toolchains&#x2F;nightly&#x2F;cargo&#x2F;registry&#x2F;src&#x2F;github.com-0a35038f75765ae4&#x2F;gdk-sys-0.2.1&#x2F;build.rs:17
&lt;&#x2F;span&gt;&lt;span&gt;note: Run with `RUST_BACKTRACE=1` for a backtrace.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;running-notty&quot;&gt;Running notty&lt;&#x2F;h1&gt;
&lt;p&gt;Compiling and running &lt;code&gt;scaffolding&lt;&#x2F;code&gt; necessarily builds a bunch of dependencies, some of which throw various warnings. You also might be able to crash &lt;code&gt;scaffolding&lt;&#x2F;code&gt; with an error such as:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;thread &amp;#39;&amp;lt;main&amp;gt;&amp;#39; panicked at &amp;#39;not yet implemented&amp;#39;, ...&#x2F;notty&#x2F;src&#x2F;datatypes&#x2F;mod.rs:160
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;withoutboats&#x2F;notty&#x2F;blob&#x2F;master&#x2F;src&#x2F;datatypes&#x2F;mod.rs#L160&quot;&gt;This&lt;&#x2F;a&gt;, along with everywhere else that &lt;code&gt;unimplemented!()&lt;&#x2F;code&gt; occurrs in the notty source code, is an opportunity for you to contribute and help improve the project!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How much knowledge do you need to give a conference talk?</title>
        <published>2016-01-19T00:00:00+00:00</published>
        <updated>2016-01-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/01/19/how_much_knowledge_do_you_need_to_give_a_conference_talk/"/>
        <id>https://edunham.net/2016/01/19/how_much_knowledge_do_you_need_to_give_a_conference_talk/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/01/19/how_much_knowledge_do_you_need_to_give_a_conference_talk/">&lt;p&gt;I was recently asked an excellent question when I promoted the &lt;a href=&quot;http:&#x2F;&#x2F;www.linuxfestnorthwest.org&#x2F;2016&#x2F;present&quot;&gt;LFNW CFP&lt;&#x2F;a&gt; on IRC:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;As someone who has never done a talk, but wants to, what kind of knowledge do you need about a subject to give a talk on it?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;If you answer &quot;yes&quot; to any of the following questions, you know enough to propose a talk:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Do you have a &lt;strong&gt;hobby&lt;&#x2F;strong&gt; that most tech people aren&#x27;t experts on? Talk about applying a lesson or skill from that hobby to tech! For instance, I turned a habit of reading about psychology into my &lt;a href=&quot;http:&#x2F;&#x2F;talks.edunham.net&#x2F;scale13x&#x2F;#1&quot;&gt;Human Hacking&lt;&#x2F;a&gt; talk.&lt;&#x2F;li&gt;
&lt;li&gt;Have you ever spent a bunch of hours forcing two tools to work with each other, because the documentation wasn&#x27;t very helpful and Googling didn&#x27;t get you very far, and built something useful? &quot;How to build ___ with ___&quot; makes a catchy talk title, if the &lt;strong&gt;thing you built&lt;&#x2F;strong&gt; solves a common problem.&lt;&#x2F;li&gt;
&lt;li&gt;Have you ever had a mentor sit down with you and explain a tool or technique, and the new understanding improved the quality of your work or code? Passing along useful &lt;strong&gt;lessons from your mentors&lt;&#x2F;strong&gt; is a valuable talk, because it allows others to benefit from the knowledge without taking as much of your mentor&#x27;s time.&lt;&#x2F;li&gt;
&lt;li&gt;Have you seen a dozen newbies ask the same question over the course of a few months? When your &lt;strong&gt;answer to a common question&lt;&#x2F;strong&gt; starts to feel like a broken record, it&#x27;s time to compose it into a talk then link the newbies to your slides or recording!&lt;&#x2F;li&gt;
&lt;li&gt;Have you taken a really &lt;strong&gt;interesting class&lt;&#x2F;strong&gt; lately? Can you distill part of it into a 1-hour lesson that would appeal to nerds who don&#x27;t have the time or resources to take the class themselves? (thanks &lt;a href=&quot;http:&#x2F;&#x2F;lucywyman.me&#x2F;&quot;&gt;lucyw&lt;&#x2F;a&gt; for adding this to the list!)&lt;&#x2F;li&gt;
&lt;li&gt;Have you built a cool thing that over a dozen other people use? A &lt;strong&gt;tutorial talk&lt;&#x2F;strong&gt; can not only expand your community, but its recording can augment your documentation and make the project more accessible for those who prefer to learn directly from humans!&lt;&#x2F;li&gt;
&lt;li&gt;Did you benefit from a really great introductory talk when you were learning a tool? Consider doing your own tutorial! Any conference with beginners in their target audience needs at least one Git lesson, an IRC talk, and some discussions of how to use basic Unix utilities. These &lt;strong&gt;introductory talks&lt;&#x2F;strong&gt; are actually better when given by someone who learned the technology relatively recently, because newer users remember what it&#x27;s like not to know how to use it. Just remember to have a more expert user look over your slides before you present, in case you made an incorrect assumption about the tool&#x27;s more advanced functionality.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I personally try to propose talks I want to hear, because the dealine of a CFP or conference is great motivation to prioritize a cool project over ordinary chores.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Buildbot and EOFError</title>
        <published>2016-01-16T00:00:00+00:00</published>
        <updated>2016-01-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/01/16/buildbot_and_eoferror/"/>
        <id>https://edunham.net/2016/01/16/buildbot_and_eoferror/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/01/16/buildbot_and_eoferror/">&lt;p&gt;More SEO-bait, after tracking down an poorly documented problem:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;# buildbot start master
&lt;&#x2F;span&gt;&lt;span&gt;Following twistd.log until startup finished..
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:49+0000 [-] Log opened.
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:49+0000 [-] twistd 14.0.2 (&#x2F;usr&#x2F;bin&#x2F;python 2.7.6) starting up.
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:49+0000 [-] reactor class: twisted.internet.epollreactor.EPollReactor.
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:49+0000 [-] Starting BuildMaster -- buildbot.version: 0.8.12
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:49+0000 [-] Loading configuration from &amp;#39;&#x2F;home&#x2F;user&#x2F;buildbot&#x2F;master&#x2F;master.cfg&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:53+0000 [-] error while parsing config file:
&lt;&#x2F;span&gt;&lt;span&gt;    Traceback (most recent call last):
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;twisted&#x2F;internet&#x2F;defer.py&amp;quot;, line 577, in _runCallbacks
&lt;&#x2F;span&gt;&lt;span&gt;        current.result = callback(current.result, *args, **kw)
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;twisted&#x2F;internet&#x2F;defer.py&amp;quot;, line 1155, in gotResult
&lt;&#x2F;span&gt;&lt;span&gt;        _inlineCallbacks(r, g, deferred)
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;twisted&#x2F;internet&#x2F;defer.py&amp;quot;, line 1099, in _inlineCallbacks
&lt;&#x2F;span&gt;&lt;span&gt;        result = g.send(result)
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;buildbot&#x2F;master.py&amp;quot;, line 189, in startService
&lt;&#x2F;span&gt;&lt;span&gt;        self.configFileName)
&lt;&#x2F;span&gt;&lt;span&gt;    --- &amp;lt;exception caught here&amp;gt; ---
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;buildbot&#x2F;config.py&amp;quot;, line 156, in loadConfig
&lt;&#x2F;span&gt;&lt;span&gt;        exec f in localDict
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;home&#x2F;user&#x2F;buildbot&#x2F;master&#x2F;master.cfg&amp;quot;, line 415, in &amp;lt;module&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        extra_post_params={&amp;#39;secret&amp;#39;: HOMU_BUILDBOT_SECRET},
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;buildbot&#x2F;status&#x2F;status_push.py&amp;quot;, line 404, in __init__
&lt;&#x2F;span&gt;&lt;span&gt;        secondaryQueue=DiskQueue(path, maxItems=maxDiskItems))
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;buildbot&#x2F;status&#x2F;persistent_queue.py&amp;quot;, line 286, in __init__
&lt;&#x2F;span&gt;&lt;span&gt;        self.secondaryQueue.popChunk(self.primaryQueue.maxItems()))
&lt;&#x2F;span&gt;&lt;span&gt;      File &amp;quot;&#x2F;usr&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;dist-packages&#x2F;buildbot&#x2F;status&#x2F;persistent_queue.py&amp;quot;, line 208, in popChunk
&lt;&#x2F;span&gt;&lt;span&gt;        ret.append(self.unpickleFn(ReadFile(path)))
&lt;&#x2F;span&gt;&lt;span&gt;    exceptions.EOFError:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:53+0000 [-] Configuration Errors:
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:53+0000 [-]   error while parsing config file:  (traceback in logfile)
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:53+0000 [-] Halting master.
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:53+0000 [-] Main loop terminated.
&lt;&#x2F;span&gt;&lt;span&gt;2016-01-17 04:35:53+0000 [-] Server Shut Down.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This happened after the buildmaster&#x27;s disk filled up and a bunch of stuff was manually deleted. There were no changes to master.cfg since it worked perfectly.&lt;&#x2F;p&gt;
&lt;p&gt;The fix was to examine &lt;code&gt;master.cfg&lt;&#x2F;code&gt; to see &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;servo&#x2F;saltfs&#x2F;blob&#x2F;master&#x2F;buildbot&#x2F;master&#x2F;master.cfg#L413&quot;&gt;where the HttpStatusPush was created&lt;&#x2F;a&gt;, of the form:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;c[&amp;#39;status&amp;#39;].append(HttpStatusPush(
&lt;&#x2F;span&gt;&lt;span&gt;    serverUrl=&amp;#39;http:&#x2F;&#x2F;build.servo.org:54856&#x2F;buildbot&amp;#39;,
&lt;&#x2F;span&gt;&lt;span&gt;    extra_post_params={&amp;#39;secret&amp;#39;: HOMU_BUILDBOT_SECRET},
&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Digging in the Buildbot source reveals that &lt;code&gt;persistent_queue.py&lt;&#x2F;code&gt; wants to unpickle a cache file from &lt;code&gt;&#x2F;events_build.servo.org&#x2F;-1&lt;&#x2F;code&gt; if there was nothing in &lt;code&gt;&#x2F;events_build.servo.org&#x2F;&lt;&#x2F;code&gt;. To fix this the right way, create that file and make sure Buildbot has &lt;code&gt;+rwx&lt;&#x2F;code&gt; on it.&lt;&#x2F;p&gt;
&lt;p&gt;Alternately, you can give up on writing your status push cache to disk entirely by adding the line &lt;code&gt;maxDiskItems=0&lt;&#x2F;code&gt; to the creation of the HttpStatusPush, giving you:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;c[&amp;#39;status&amp;#39;].append(HttpStatusPush(
&lt;&#x2F;span&gt;&lt;span&gt;   serverUrl=&amp;#39;http:&#x2F;&#x2F;build.servo.org:54856&#x2F;buildbot&amp;#39;,
&lt;&#x2F;span&gt;&lt;span&gt;   maxDiskItems=0,
&lt;&#x2F;span&gt;&lt;span&gt;   extra_post_params={&amp;#39;secret&amp;#39;: HOMU_BUILDBOT_SECRET},
&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The real moral of the story is &quot;remember to use &lt;a href=&quot;http:&#x2F;&#x2F;www.linuxcommand.org&#x2F;man_pages&#x2F;logrotate8.html&quot;&gt;logrotate&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Who would you hire?</title>
        <published>2016-01-13T00:00:00+00:00</published>
        <updated>2016-01-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/01/13/who_would_you_hire/"/>
        <id>https://edunham.net/2016/01/13/who_would_you_hire/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/01/13/who_would_you_hire/">&lt;p&gt;If you&#x27;re using open source as a portfolio to make yourself a more competitive job candidate, it can feel like you have to start your own project to show off your skills.&lt;&#x2F;p&gt;
&lt;p&gt;In the words of one job seeker I chatted with recently, &quot;I feel like most of my contributions [to other peoples&#x27; projects] aren&#x27;t that significant or noteworthy&quot;. Here&#x27;s a thought experiment to justify including projects to which you contribute, even without a leadership role, on your resume:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Imagine you want to hire a coder.&lt;&#x2F;p&gt;
&lt;p&gt;Candidate A always works alone and refuses to contribute to a project if it doesn&#x27;t make her look like a rockstar.&lt;&#x2F;p&gt;
&lt;p&gt;Candidate B triages the unglamorous issues that affect multiple users, and steadily produces small, self-contained fixes that avoid introducing new bugs.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;When the situation is framed in these terms, I hope that it&#x27;s obvious which coder you&#x27;d want on your team.&lt;&#x2F;p&gt;
&lt;p&gt;When writing your resume, there&#x27;s only space to include a few of the many activities in which you invest your time. It&#x27;s tempting to only include your biggest, highest-profile solo projects, while disregarding those projects to which you&#x27;ve made a small but steady stream of useful contributions.&lt;&#x2F;p&gt;
&lt;p&gt;Reread your resume from the perspective of someone who hasn&#x27;t met you yet and has only the information in that document available to form a first impression of your character. Which of the 2 hypothetical coders does it make you sound like? Is that how you really are?&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Troubleshooting stunnel</title>
        <published>2016-01-09T00:00:00+00:00</published>
        <updated>2016-01-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2016/01/09/troubleshooting_stunnel/"/>
        <id>https://edunham.net/2016/01/09/troubleshooting_stunnel/</id>
        
        <content type="html" xml:base="https://edunham.net/2016/01/09/troubleshooting_stunnel/">&lt;p&gt;Today I&#x27;ve learned a few things aout how &lt;a href=&quot;https:&#x2F;&#x2F;www.stunnel.org&#x2F;docs.html&quot;&gt;stunnel&lt;&#x2F;a&gt; works. The main takeaway is that Googling for specific errors in the stunnel log is incredibly unhelpful, resulting in a variety of mailing list posts with no replies. Tracking an error message through the source of the program doesn&#x27;t lead to any useful comments, either. So here&#x27;s some SEO bait with concrete troubleshooting suggestions.&lt;&#x2F;p&gt;
&lt;p&gt;I started out, as usual, with a pile of errors:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;usr&#x2F;local&#x2F;bin&#x2F;stunnel
&lt;&#x2F;span&gt;&lt;span&gt;[ ] Clients allowed=2000
&lt;&#x2F;span&gt;&lt;span&gt;[ ] Cron thread initialized
&lt;&#x2F;span&gt;&lt;span&gt;[.] stunnel 5.27 on x86_64-apple-darwin14.0.0 platform
&lt;&#x2F;span&gt;&lt;span&gt;[.] Compiled&#x2F;running with OpenSSL 1.0.2e 3 Dec 2015
&lt;&#x2F;span&gt;&lt;span&gt;[.] Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,FIPS,OCSP,PSK,SNI
&lt;&#x2F;span&gt;&lt;span&gt;[ ] errno: (*__error())
&lt;&#x2F;span&gt;&lt;span&gt;[.] Reading configuration from file stunnel.conf
&lt;&#x2F;span&gt;&lt;span&gt;[.] UTF-8 byte order mark not detected
&lt;&#x2F;span&gt;&lt;span&gt;[ ] Initializing service [9987]
&lt;&#x2F;span&gt;&lt;span&gt;[!] Error resolving &amp;quot;127.0.0.1&amp;quot;: Neither nodename nor servname known (EAI_NONAME)
&lt;&#x2F;span&gt;&lt;span&gt;[ ] Cannot resolve connect target - delaying DNS lookup
&lt;&#x2F;span&gt;&lt;span&gt;[ ] No certificate or private key specified
&lt;&#x2F;span&gt;&lt;span&gt;[ ] SSL options: 0x03000004 (+0x03000000, -0x00000000)
&lt;&#x2F;span&gt;&lt;span&gt;[.] Configuration successful
&lt;&#x2F;span&gt;&lt;span&gt;[ ] Listening file descriptor created (FD=6)
&lt;&#x2F;span&gt;&lt;span&gt;[!] bind: Address already in use (48)
&lt;&#x2F;span&gt;&lt;span&gt;[!] Error binding service [9987] to 127.0.0.1:9987
&lt;&#x2F;span&gt;&lt;span&gt;[ ] Closing service [9987]
&lt;&#x2F;span&gt;&lt;span&gt;[ ] Service [9987] closed
&lt;&#x2F;span&gt;&lt;span&gt;stunnel startup failed, already running?
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;error-resolving-127-0-0-1-neither-nodename-nor-servname-known&quot;&gt;[!] Error resolving &quot;127.0.0.1&quot;: Neither nodename nor servname known&lt;&#x2F;h1&gt;
&lt;p&gt;This was the biggest &lt;a href=&quot;https:&#x2F;&#x2F;www.stunnel.org&#x2F;docs.html&quot;&gt;wat&lt;&#x2F;a&gt;, and the hardest to track down because the solution is so obvious.&lt;&#x2F;p&gt;
&lt;p&gt;&quot;Error resolving&quot; sounds like the machine hasn&#x27;t been informed of localhost&#x27;s existance, so let&#x27;s check:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cat &#x2F;etc&#x2F;hosts
&lt;&#x2F;span&gt;&lt;span&gt;127.0.0.1   localhost
&lt;&#x2F;span&gt;&lt;span&gt;255.255.255.255 broadcasthost
&lt;&#x2F;span&gt;&lt;span&gt;::1             localhost
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And I can even ping &lt;code&gt;127.0.0.1&lt;&#x2F;code&gt; successfully. So the message and I have different ideas about what it means to &quot;resolve&quot; an IP.&lt;&#x2F;p&gt;
&lt;p&gt;I found the fix here by diffing the &lt;code&gt;stunnel.conf&lt;&#x2F;code&gt; against that on a working machine, and learned that I&#x27;d neglected to specify the correct port number on the destination host.&lt;&#x2F;p&gt;
&lt;p&gt;The solution to the &quot;Error resolving localhost&quot; turned out to be &lt;strong&gt;specifying the correct port for the other end of the stunnel&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cat stunnel.conf
&lt;&#x2F;span&gt;&lt;span&gt;pid =
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[9987]
&lt;&#x2F;span&gt;&lt;span&gt;client = yes
&lt;&#x2F;span&gt;&lt;span&gt;accept = 127.0.0.1:9987
&lt;&#x2F;span&gt;&lt;span&gt;cafile = .&#x2F;cert.pem
&lt;&#x2F;span&gt;&lt;span&gt;verify = 3
&lt;&#x2F;span&gt;&lt;span&gt;connect = 01.23.456.789:9988
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Wow. Painfully obvious after you realize what&#x27;s wrong, and just plain painful before.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;error-binding-service-9987-to-127-0-0-1-9987&quot;&gt;[!] Error binding service [9987] to 127.0.0.1:9987&lt;&#x2F;h1&gt;
&lt;p&gt;The &quot;already running?&quot; hint is correct here. This error means stunnel didn&#x27;t let go of the port despite failing to start on a previous attempt.&lt;&#x2F;p&gt;
&lt;p&gt;Easy fix; check whether it&#x27;s really stunnel hogging the port:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ lsof -i tcp:9987
&lt;&#x2F;span&gt;&lt;span&gt;COMMAND PID      USER   FD   TYPE             DEVICE SIZE&#x2F;OFF NODE NAME
&lt;&#x2F;span&gt;&lt;span&gt;stunnel 363        me    6u  IPv4 0x20f17e5e0dd35277      0t0  TCP localhost:dsm-scm-target (LISTEN)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and if so, whack it with a metaphorical hammer:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo killall stunnel
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;tada&quot;&gt;Tada!&lt;&#x2F;h1&gt;
&lt;p&gt;After getting the destination IP+port combination specified correctly and the old broken stunnel killed, the stunnel starts successfully.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Questions about Open Source and Design</title>
        <published>2015-12-21T00:00:00+00:00</published>
        <updated>2015-12-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/12/21/questions_about_open_source_and_design/"/>
        <id>https://edunham.net/2015/12/21/questions_about_open_source_and_design/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/12/21/questions_about_open_source_and_design/">&lt;p&gt;Today, I posed a question to some professional UI and UX designers:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;How can an open source project without dedicated design experts collaborate with amateur, volunteer designers to produce a well-designed product?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;They revealed that they&#x27;ve faced similar collaboration challenges, but knew of neither a specific process to solve the problem nor an organization that had overcome it in the past.&lt;&#x2F;p&gt;
&lt;p&gt;Have you solved this problem? Have you tried some process or technique and learned that it&#x27;s not able to solve the problem? Email me (&lt;code&gt;design@edunham.net&lt;&#x2F;code&gt;) if you know of an open source project that&#x27;s succeeded at opening their design as well, and I&#x27;ll update back here with what I learn!&lt;&#x2F;p&gt;
&lt;p&gt;In no particular order, here are some of the problems that we were talking about:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Non-designers struggle to give constructive feedback on design. I can say &quot;that&#x27;s ugly&quot; or &quot;that&#x27;s hard to use&quot; more easily than I can say &quot;here&#x27;s how you can make it better&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;Projects without designers in the main decision-making team can have a hard time evaluating the quality of a proposed design.&lt;&#x2F;li&gt;
&lt;li&gt;Non-designers struggle to articulate the objective design needs of their projects, so design remains a single monolithic problem rather than being decomposed into bite-sized, introductory issues the way code problems are.&lt;&#x2F;li&gt;
&lt;li&gt;Volunteer designers have a difficult time finding open source projects to get involved with.&lt;&#x2F;li&gt;
&lt;li&gt;Non-designers don&#x27;t know the difference between different types of design, and tend to bikeshed on superficial, obvious traits like colors when they should be focusing on more subtle parts of the user experience. We as non-designers are like clients who ask for a web site without knowing that there&#x27;s a difference between frontend development, back end development, database administration, and systems administration.&lt;&#x2F;li&gt;
&lt;li&gt;The tests which designers apply to their work are often almost impossible to automate. For instance, I gather that a lot of user interaction testing involves watching new users attempt to complete a task using a given design, and observing the challenges they encounter.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Again, if you know of an open source project that&#x27;s overcome any of these challenges, please email me at &lt;code&gt;design@edunham.net&lt;&#x2F;code&gt; and tell me about it!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Linode vs AWS</title>
        <published>2015-12-03T00:00:00+00:00</published>
        <updated>2015-12-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/12/03/linode_plan_names_and_pricing/"/>
        <id>https://edunham.net/2015/12/03/linode_plan_names_and_pricing/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/12/03/linode_plan_names_and_pricing/">&lt;p&gt;I&#x27;m examining a Linode account in order to figure out how to switch the application its instances are running to AWS. The first challenge is that instance types in the main dashboard are described by arbitrary numbers (&quot;UI Name&quot; in the chart below), rather than a statistic about their resources or pricing. Here&#x27;s how those magic numbers line up to hourly rates and their corresponding monthly price caps:&lt;&#x2F;p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr class=&quot;header&quot;&gt;
&lt;th&gt;RAM&lt;&#x2F;th&gt;
&lt;th&gt;Hourly $&lt;&#x2F;th&gt;
&lt;th&gt;Monthly $&lt;&#x2F;th&gt;
&lt;th&gt;UI Name&lt;&#x2F;th&gt;
&lt;th&gt;Cores&lt;&#x2F;th&gt;
&lt;th&gt;GB SSD&lt;&#x2F;th&gt;
&lt;&#x2F;tr&gt;
&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr class=&quot;odd&quot;&gt;
&lt;td&gt;1GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.015&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$10&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;1024&lt;&#x2F;td&gt;
&lt;td&gt;1&lt;&#x2F;td&gt;
&lt;td&gt;24&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;even&quot;&gt;
&lt;td&gt;2GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.03&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$20&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;2048&lt;&#x2F;td&gt;
&lt;td&gt;2&lt;&#x2F;td&gt;
&lt;td&gt;48&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;odd&quot;&gt;
&lt;td&gt;4GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.06&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$40&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;4096&lt;&#x2F;td&gt;
&lt;td&gt;4&lt;&#x2F;td&gt;
&lt;td&gt;96&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;even&quot;&gt;
&lt;td&gt;8GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.12&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$80&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;8192&lt;&#x2F;td&gt;
&lt;td&gt;6&lt;&#x2F;td&gt;
&lt;td&gt;192&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;odd&quot;&gt;
&lt;td&gt;16GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.24&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$160&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;16384&lt;&#x2F;td&gt;
&lt;td&gt;8&lt;&#x2F;td&gt;
&lt;td&gt;384&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;even&quot;&gt;
&lt;td&gt;32GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.48&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$320&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;32768&lt;&#x2F;td&gt;
&lt;td&gt;12&lt;&#x2F;td&gt;
&lt;td&gt;768&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;odd&quot;&gt;
&lt;td&gt;48GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.72&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$480&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;49152&lt;&#x2F;td&gt;
&lt;td&gt;16&lt;&#x2F;td&gt;
&lt;td&gt;1152&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;even&quot;&gt;
&lt;td&gt;64GB&lt;&#x2F;td&gt;
&lt;td&gt;$0.96&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$640&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;65536&lt;&#x2F;td&gt;
&lt;td&gt;20&lt;&#x2F;td&gt;
&lt;td&gt;1536&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr class=&quot;odd&quot;&gt;
&lt;td&gt;96GB&lt;&#x2F;td&gt;
&lt;td&gt;$1.44&#x2F;hr&lt;&#x2F;td&gt;
&lt;td&gt;&lt;blockquote&gt;
&lt;p&gt;$960&#x2F;mo&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;&lt;&#x2F;td&gt;
&lt;td&gt;98304&lt;&#x2F;td&gt;
&lt;td&gt;20&lt;&#x2F;td&gt;
&lt;td&gt;1920&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;
&lt;&#x2F;table&gt;
&lt;h1 id=&quot;aws-equivalents&quot;&gt;AWS &quot;Equivalents&quot;&lt;&#x2F;h1&gt;
&lt;p&gt;AWS T2 instances have burstable performance. &lt;code&gt;M*&lt;&#x2F;code&gt; instances are general-purpose; &lt;code&gt;C*&lt;&#x2F;code&gt; are compute-optimized; &lt;code&gt;R*&lt;&#x2F;code&gt; are memory-optimized. &lt;code&gt;*3&lt;&#x2F;code&gt; instances run on slightly older Ivy Bridge or Sandy Bridge processors, while &lt;code&gt;*4&lt;&#x2F;code&gt; instances run on the newer Haswells. I&#x27;m disergarding the &lt;code&gt;G2&lt;&#x2F;code&gt; (GPU-optimized), &lt;code&gt;D2&lt;&#x2F;code&gt; (dense-storage), and &lt;code&gt;I2&lt;&#x2F;code&gt; (IO-optmized) instance types from this analysis.&lt;&#x2F;p&gt;
&lt;p&gt;Note that the AWS specs page has memory in GiB rather than GB. I&#x27;ve converted everything into GB in the following table, since the Linode specs are in GB and the AWS RAM amounts don&#x27;t seem to follow any particular pattern that would lose information in the conversion.&lt;&#x2F;p&gt;
&lt;p&gt;Hourly price is the Linux&#x2F;UNIX rate for US West (Northern California) on 2015-12-03. Monthly price estimate is the hourly price multiplied by 730.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Instance&lt;&#x2F;th&gt;&lt;th&gt;vCPU&lt;&#x2F;th&gt;&lt;th&gt;GB RAM&lt;&#x2F;th&gt;&lt;th&gt;$&#x2F;hr&lt;&#x2F;th&gt;&lt;th&gt;$&#x2F;month&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;t2.micro&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;1.07&lt;&#x2F;td&gt;&lt;td&gt;.017&lt;&#x2F;td&gt;&lt;td&gt;12.41&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;t2.small&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;2.14&lt;&#x2F;td&gt;&lt;td&gt;.034&lt;&#x2F;td&gt;&lt;td&gt;24.82&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;t2.medium&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;4.29&lt;&#x2F;td&gt;&lt;td&gt;.068&lt;&#x2F;td&gt;&lt;td&gt;49.64&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;t2.large&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;8.58&lt;&#x2F;td&gt;&lt;td&gt;.136&lt;&#x2F;td&gt;&lt;td&gt;99.28&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m4.large&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;8.58&lt;&#x2F;td&gt;&lt;td&gt;.147&lt;&#x2F;td&gt;&lt;td&gt;107.31&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m4.xlarge&lt;&#x2F;td&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;17.18&lt;&#x2F;td&gt;&lt;td&gt;.294&lt;&#x2F;td&gt;&lt;td&gt;214.62&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m4.2xlarge&lt;&#x2F;td&gt;&lt;td&gt;8&lt;&#x2F;td&gt;&lt;td&gt;34.36&lt;&#x2F;td&gt;&lt;td&gt;.588&lt;&#x2F;td&gt;&lt;td&gt;429.24&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m4.4xlarge&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;td&gt;68.72&lt;&#x2F;td&gt;&lt;td&gt;1.176&lt;&#x2F;td&gt;&lt;td&gt;858.48&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m4.10xlarge&lt;&#x2F;td&gt;&lt;td&gt;40&lt;&#x2F;td&gt;&lt;td&gt;171.8&lt;&#x2F;td&gt;&lt;td&gt;2.94&lt;&#x2F;td&gt;&lt;td&gt;2146.2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m3.medium&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;4.02&lt;&#x2F;td&gt;&lt;td&gt;.077&lt;&#x2F;td&gt;&lt;td&gt;56.21&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m3.large&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;8.05&lt;&#x2F;td&gt;&lt;td&gt;.154&lt;&#x2F;td&gt;&lt;td&gt;112.42&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m3.xlarge&lt;&#x2F;td&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;16.11&lt;&#x2F;td&gt;&lt;td&gt;.308&lt;&#x2F;td&gt;&lt;td&gt;224.84&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;m3.2xlarge&lt;&#x2F;td&gt;&lt;td&gt;8&lt;&#x2F;td&gt;&lt;td&gt;32.21&lt;&#x2F;td&gt;&lt;td&gt;.616&lt;&#x2F;td&gt;&lt;td&gt;449.68&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c4.large&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;4.02&lt;&#x2F;td&gt;&lt;td&gt;.138&lt;&#x2F;td&gt;&lt;td&gt;100.74&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c4.xlarge&lt;&#x2F;td&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;8.05&lt;&#x2F;td&gt;&lt;td&gt;.276&lt;&#x2F;td&gt;&lt;td&gt;201.48&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c4.2xlarge&lt;&#x2F;td&gt;&lt;td&gt;8&lt;&#x2F;td&gt;&lt;td&gt;16.11&lt;&#x2F;td&gt;&lt;td&gt;.552&lt;&#x2F;td&gt;&lt;td&gt;402.96&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c4.4xlarge&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;td&gt;32.21&lt;&#x2F;td&gt;&lt;td&gt;1.104&lt;&#x2F;td&gt;&lt;td&gt;805.92&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c4.8xlarge&lt;&#x2F;td&gt;&lt;td&gt;36&lt;&#x2F;td&gt;&lt;td&gt;64.42&lt;&#x2F;td&gt;&lt;td&gt;2.208&lt;&#x2F;td&gt;&lt;td&gt;1611.84&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c3.large&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;4.02&lt;&#x2F;td&gt;&lt;td&gt;.12&lt;&#x2F;td&gt;&lt;td&gt;87.6&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c3.xlarge&lt;&#x2F;td&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;8.05&lt;&#x2F;td&gt;&lt;td&gt;.239&lt;&#x2F;td&gt;&lt;td&gt;174.47&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c3.2xlarge&lt;&#x2F;td&gt;&lt;td&gt;8&lt;&#x2F;td&gt;&lt;td&gt;16.11&lt;&#x2F;td&gt;&lt;td&gt;.478&lt;&#x2F;td&gt;&lt;td&gt;348.94&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c3.4xlarge&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;td&gt;32.21&lt;&#x2F;td&gt;&lt;td&gt;.956&lt;&#x2F;td&gt;&lt;td&gt;697.88&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;c3.8xlarge&lt;&#x2F;td&gt;&lt;td&gt;32&lt;&#x2F;td&gt;&lt;td&gt;64.42&lt;&#x2F;td&gt;&lt;td&gt;1.912&lt;&#x2F;td&gt;&lt;td&gt;1395.76&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;r3.large&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;16.37&lt;&#x2F;td&gt;&lt;td&gt;.195&lt;&#x2F;td&gt;&lt;td&gt;142.35&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;r3.xlarge&lt;&#x2F;td&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;32.75&lt;&#x2F;td&gt;&lt;td&gt;.39&lt;&#x2F;td&gt;&lt;td&gt;284.7&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;r3.2xlarge&lt;&#x2F;td&gt;&lt;td&gt;8&lt;&#x2F;td&gt;&lt;td&gt;65.50&lt;&#x2F;td&gt;&lt;td&gt;.78&lt;&#x2F;td&gt;&lt;td&gt;569.4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;r3.4xlarge&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;td&gt;131&lt;&#x2F;td&gt;&lt;td&gt;1.56&lt;&#x2F;td&gt;&lt;td&gt;1138.8&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;r3.8xlarge&lt;&#x2F;td&gt;&lt;td&gt;32&lt;&#x2F;td&gt;&lt;td&gt;262&lt;&#x2F;td&gt;&lt;td&gt;3.12&lt;&#x2F;td&gt;&lt;td&gt;2277.6&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h1 id=&quot;comparison&quot;&gt;Comparison&lt;&#x2F;h1&gt;
&lt;p&gt;Linode and AWS do not compare cleanly at all. The smallest AWS instance to match a given Linode type&#x27;s RAM typically has fewer vCPUs and costs more in the region where I compared them. Conversely, the smallest AWS instance to match a Linode type&#x27;s number of cores often has almost double the RAM of the Linode, and costs substantially more.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;switching-from-linode-to-aws&quot;&gt;Switching from Linode to AWS&lt;&#x2F;h1&gt;
&lt;p&gt;When I examine the Servo build machines&#x27; utilization graphs via the Linode dashboard, it becomes clear that even their load spikes aren&#x27;t fully utilizing the available CPUs. To view memory usage stats on Linode, it&#x27;s necessary to configure hosts to run the &lt;a href=&quot;https:&#x2F;&#x2F;www.linode.com&#x2F;docs&#x2F;platform&#x2F;longview&#x2F;longview&quot;&gt;longview&lt;&#x2F;a&gt; client. After installation, the client begins reporting data to Linode immediately.&lt;&#x2F;p&gt;
&lt;p&gt;After a few days, these metrics can be used to find the smallest AWS instance whose specs exceed what your application is actually using on Linode.&lt;&#x2F;p&gt;
&lt;p&gt;Sources:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linode.com&#x2F;pricing&quot;&gt;linode specs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linode.com&#x2F;docs&#x2F;platform&#x2F;billing-and-payments&quot;&gt;linode pricing&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;ec2&#x2F;instance-types&#x2F;&quot;&gt;AWS specs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Giving Thanks to Rust Contributors</title>
        <published>2015-11-25T00:00:00+00:00</published>
        <updated>2015-11-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/11/25/giving_thanks_to_rust_contributors/"/>
        <id>https://edunham.net/2015/11/25/giving_thanks_to_rust_contributors/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/11/25/giving_thanks_to_rust_contributors/">&lt;p&gt;It&#x27;s the day before Thanksgiving here in the US, and the time of year when we&#x27;re culturally conditioned to be a bit more public than usual in giving thanks for things.&lt;&#x2F;p&gt;
&lt;p&gt;As always, I&#x27;m grateful that I&#x27;m working in tech right now, because almost any job in the tech industry is enough to fulfill all of one&#x27;s tangible needs like food and shelter and new toys. However, plenty of my peers have all those material needs met and yet still feel unsatisfied with the impact of their work. I&#x27;m grateful to be involved with the Rust project because I know that my work makes a difference to a project that I care about.&lt;&#x2F;p&gt;
&lt;p&gt;Rust is satisfying to be involved with because it makes a difference, but that would not be true without its community. To say thank you, I&#x27;ve put together a little visualization for insight into one facet of how that community works its magic:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;rust_contributor_impact_visualization.png&quot; alt=&quot;Visualization of Rust contributor impact and organization-wide commit statistics&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The stats page is interactive and available at &lt;a href=&quot;http:&#x2F;&#x2F;edunham.github.io&#x2F;rust-org-stats&#x2F;&quot;&gt;http:&#x2F;&#x2F;edunham.github.io&#x2F;rust-org-stats&#x2F;&lt;&#x2F;a&gt;. The pretty graphs take a moment to render, since they&#x27;re built in your browser.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a whole lot of data on that page, and you can scroll down for a list of all authors. It&#x27;s especially great to see the high impact that the month&#x27;s new contributors have had, as shown in the group comparison at the bottom of the &quot;natural log of commits&quot; chart!&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s made with the little toy I wrote a while ago called &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;orglog&quot;&gt;orglog&lt;&#x2F;a&gt;, which builds on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;youknowone&#x2F;gitstat&quot;&gt;gitstat&lt;&#x2F;a&gt; to help visualize how many people contribute code to a GitHub organization. It&#x27;s deployed to GitHub Pages with TravisCI (&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;orglog&#x2F;blob&#x2F;master&#x2F;out&#x2F;forcepush.sh&quot;&gt;eww&lt;&#x2F;a&gt;) and &lt;a href=&quot;https:&#x2F;&#x2F;nightli.es&#x2F;&quot;&gt;nightli.es&lt;&#x2F;a&gt; so that the Rust&#x27;s organization-wide contributor stats will be automatically rebuilt and updated every day.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;d like to help improve the page, you can contribute to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;youknowone&#x2F;gitstat&quot;&gt;gitstat&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;orglog&quot;&gt;orglog&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>PSA: Docker on Ubuntu</title>
        <published>2015-11-23T00:00:00+00:00</published>
        <updated>2015-11-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/11/23/docker_on_ubuntu/"/>
        <id>https://edunham.net/2015/11/23/docker_on_ubuntu/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/11/23/docker_on_ubuntu/">&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo apt-get install docker
&lt;&#x2F;span&gt;&lt;span&gt;$ which docker
&lt;&#x2F;span&gt;&lt;span&gt;$ docker
&lt;&#x2F;span&gt;&lt;span&gt;The program &amp;#39;docker&amp;#39; is currently not installed. You can install it by typing: 
&lt;&#x2F;span&gt;&lt;span&gt;apt-get install docker
&lt;&#x2F;span&gt;&lt;span&gt;$ apt-get install docker
&lt;&#x2F;span&gt;&lt;span&gt;Reading package lists... Done
&lt;&#x2F;span&gt;&lt;span&gt;Building dependency tree       
&lt;&#x2F;span&gt;&lt;span&gt;Reading state information... Done
&lt;&#x2F;span&gt;&lt;span&gt;docker is already the newest version.
&lt;&#x2F;span&gt;&lt;span&gt;0 upgraded, 0 newly installed, 0 to remove and 13 not upgraded.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Oh, you wanted to run a docker container? The &lt;code&gt;docker&lt;&#x2F;code&gt; package in Ubuntu is some window manager dock thingy. The &lt;code&gt;docker&lt;&#x2F;code&gt; binary that runs containers comes from the &lt;code&gt;docker.io&lt;&#x2F;code&gt; system package.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo apt-get install docker.io
&lt;&#x2F;span&gt;&lt;span&gt;$ which docker
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;usr&#x2F;bin&#x2F;docker
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Also, if it can&#x27;t connect to its socket:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;FATA[0000] Post http:&#x2F;&#x2F;&#x2F;var&#x2F;run&#x2F;docker.sock&#x2F;v1.18&#x2F;containers&#x2F;create: dial
&lt;&#x2F;span&gt;&lt;span&gt;unix &#x2F;var&#x2F;run&#x2F;docker.sock: permission denied. Are you trying to connect to a
&lt;&#x2F;span&gt;&lt;span&gt;TLS-enabled daemon without TLS? 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;you need to make sure you&#x27;re in the right group:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sudo usermod -aG docker &amp;lt;username&amp;gt;; newgrp docker
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(thanks, &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;29294286&#x2F;fata0000-get-http-var-run-docker-sock-v1-17-version-dial-unix-var-run-doc&quot;&gt;stackoverflow&lt;&#x2F;a&gt;!)&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Installing Rust without root</title>
        <published>2015-11-17T00:00:00+00:00</published>
        <updated>2015-11-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/11/17/installing_rust_without_root/"/>
        <id>https://edunham.net/2015/11/17/installing_rust_without_root/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/11/17/installing_rust_without_root/">&lt;p&gt;I just got a good question from a friend on IRC: &quot;Should I ask my university&#x27;s administration to install Rust on our shared servers?&quot; The answer is &quot;you don&#x27;t have to&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Pick one of the two following sets of directions. I&#x27;d recommend using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brson&#x2F;multirust&quot;&gt;Multirust&lt;&#x2F;a&gt;, because it automatically checks the packages it downloads and lets you switch between Rust versions trivially.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;without-multirust&quot;&gt;Without multirust&lt;&#x2F;h1&gt;
&lt;p&gt;If you just want one version of Rust, &lt;a href=&quot;https:&#x2F;&#x2F;www.codejam.info&#x2F;2015&#x2F;03&#x2F;portable-rust-installation.html&quot;&gt;this blog post&lt;&#x2F;a&gt; by Valérian Galliat has a fix in 7 lines:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;cd ~&#x2F;.rust
&lt;&#x2F;span&gt;&lt;span&gt;wget https:&#x2F;&#x2F;static.rust-lang.org&#x2F;dist&#x2F;rust-nightly-x86_64-unknown-linux-gnu.tar.gz
&lt;&#x2F;span&gt;&lt;span&gt;tar xf rust-nightly-x86_64-unknown-linux-gnu.tar.gz
&lt;&#x2F;span&gt;&lt;span&gt;mv rust-nightly-x86_64-unknown-linux-gnu rust
&lt;&#x2F;span&gt;&lt;span&gt;export LD_LIBRARY_PATH=~&#x2F;opt&#x2F;rust&#x2F;rustc&#x2F;lib:$LD_LIBRARY_PATH
&lt;&#x2F;span&gt;&lt;span&gt;export PATH=~&#x2F;.rust&#x2F;rust&#x2F;rustc&#x2F;bin:$PATH
&lt;&#x2F;span&gt;&lt;span&gt;export PATH=~&#x2F;.rust&#x2F;rust&#x2F;cargo&#x2F;bin:$PATH
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you want rust stable instead of rust nightly, use the URL &lt;code&gt;https:&#x2F;&#x2F;static.rust-lang.org&#x2F;dist&#x2F;rust-stable-x86_64-unknown-linux-gnu.tar.gz&lt;&#x2F;code&gt; in the &lt;code&gt;wget&lt;&#x2F;code&gt; step to download the latest stable release.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re security-conscious, you might want to verify the integrity of the tarball before inflating it and running its contents. We provide a GPG signature of every tarball, and &lt;code&gt;sha256&lt;&#x2F;code&gt; sums of the tarballs and signatures.&lt;&#x2F;p&gt;
&lt;p&gt;You can construct the URL for shasum or GPG signature by adding the desired extension to the tarball&#x27;s URL, so for nightly:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;https:&#x2F;&#x2F;static.rust-lang.org&#x2F;dist&#x2F;rust-nightly-x86_64-unknown-linux-gnu.tar.gz
&lt;&#x2F;span&gt;&lt;span&gt;https:&#x2F;&#x2F;static.rust-lang.org&#x2F;dist&#x2F;rust-nightly-x86_64-unknown-linux-gnu.tar.gz.sha256
&lt;&#x2F;span&gt;&lt;span&gt;https:&#x2F;&#x2F;static.rust-lang.org&#x2F;dist&#x2F;rust-nightly-x86_64-unknown-linux-gnu.tar.gz.asc
&lt;&#x2F;span&gt;&lt;span&gt;https:&#x2F;&#x2F;static.rust-lang.org&#x2F;dist&#x2F;rust-nightly-x86_64-unknown-linux-gnu.tar.gz.asc.sha256
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To &lt;a href=&quot;https:&#x2F;&#x2F;www.gnupg.org&#x2F;gph&#x2F;en&#x2F;manual&#x2F;x135.html&quot;&gt;verify the GPG signature&lt;&#x2F;a&gt;, you&#x27;ll also need a copy of the Rust project&#x27;s public key. This key is available through several channels:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&#x2F;rust-key.gpg.ascii&quot;&gt;on the Rust website&lt;&#x2F;a&gt;, available only over HTTPS.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;keybase.io&#x2F;rust&quot;&gt;on keybase.io&lt;&#x2F;a&gt;, correlated to Rust&#x27;s Twitter account and URL. Don&#x27;t worry, we authenticated the key by signing a string from Keybase with it locally. We don&#x27;t trust them to ever see our private key.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust-www&#x2F;blob&#x2F;master&#x2F;rust-key.gpg.ascii&quot;&gt;on GitHub&lt;&#x2F;a&gt;, in the website&#x27;s repository.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Remember, verifying the signature only guarantees that the tarball you downloaded matches the one that was produced by the Rust project&#x27;s build infrastructure. As with any piece of software, there exist a variety of threat models from which verifying the signatures cannot completely protect you.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;multirust-without-root&quot;&gt;Multirust without root&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brson&#x2F;multirust&quot;&gt;Multirust&lt;&#x2F;a&gt; is a tool that makes it easy to use multiple Rust versions on the same system. Although the absolute easiest way to use it is &lt;code&gt;curl -sf https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;brson&#x2F;multirust&#x2F;master&#x2F;blastoff.sh | sh&lt;&#x2F;code&gt; (which will interactively request a sudo password partway through), it can be installed without root as well:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;git clone --recursive https:&#x2F;&#x2F;github.com&#x2F;brson&#x2F;multirust &amp;amp;&amp;amp; cd multirust
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;build.sh # create install.sh
&lt;&#x2F;span&gt;&lt;span&gt;mkdir ~&#x2F;.rust
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;install.sh --prefix=~&#x2F;.rust&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;echo &amp;quot;PATH=~&#x2F;.rust&#x2F;bin:$PATH&amp;quot; &amp;gt;&amp;gt; ~&#x2F;.bashrc; source ~&#x2F;.bashrc
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you run into an error like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;install: WARNING: failed to run ldconfig. this may happen when not installing
&lt;&#x2F;span&gt;&lt;span&gt;as root. run with --verbose to see the error
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or in verbose mode,:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;install: running ldconfig
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;sbin&#x2F;ldconfig.real: Can&amp;#39;t create temporary cache file &#x2F;etc&#x2F;ld.so.cache~:
&lt;&#x2F;span&gt;&lt;span&gt;Permission denied
&lt;&#x2F;span&gt;&lt;span&gt;install: WARNING: failed to run ldconfig. this may happen when not installing
&lt;&#x2F;span&gt;&lt;span&gt;as root. run with --verbose to see the error
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It means you don&#x27;t have permissions to write to &lt;code&gt;&#x2F;etc&#x2F;ld.so.cache&lt;&#x2F;code&gt;. Until &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brson&#x2F;multirust&#x2F;issues&#x2F;113&quot;&gt;this issue&lt;&#x2F;a&gt; gets fixed, the easiest workaround to lacking those permissions is to change the script called by the installer to pass &lt;code&gt;-C&lt;&#x2F;code&gt; to &lt;code&gt;ldconfig&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sed -i &amp;#39;s&#x2F;   ldconfig&#x2F;   ldconfig -C ~\&#x2F;.rust\&#x2F;ld.so.cache&#x2F;&amp;#39; build&#x2F;work&#x2F;multirust-0.7.0&#x2F;install.sh
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then you should be able to &lt;code&gt;.&#x2F;install.sh --prefix=~&#x2F;.rust&lt;&#x2F;code&gt; without the prior warning. Nasty hack, but the easiest way to get it working today.&lt;&#x2F;p&gt;
&lt;p&gt;This technically breaks &lt;code&gt;rustc&lt;&#x2F;code&gt; (since it&#x27;s dynamically linked), but if you&#x27;re building a Rust project or library, you&#x27;ll be using the statically linked &lt;code&gt;cargo&lt;&#x2F;code&gt; tool and thus won&#x27;t be affected.&lt;&#x2F;p&gt;
&lt;p&gt;By the way, this is an example of why people who write system utilities like &lt;code&gt;ldconfig&lt;&#x2F;code&gt; should make them able to read their serttings out of environment variables as well as just command-line arguments.&lt;&#x2F;p&gt;
&lt;p&gt;Now you can &lt;code&gt;multirust default nightly&lt;&#x2F;code&gt; to install rust-nightly and configure it as the default, and you&#x27;re ready to roll!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;testing-your-rust-installation&quot;&gt;Testing your Rust installation&lt;&#x2F;h1&gt;
&lt;p&gt;You can now make a package that says &quot;Hello World&quot; in just 5 commands, using a workflow that will scale to packaging and distributing larger projects:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;cargo new hello --bin
&lt;&#x2F;span&gt;&lt;span&gt;echo &amp;quot;fn main(){println!(\&amp;quot;Hello World\&amp;quot;);}&amp;quot; &amp;gt; hello&#x2F;src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;span&gt;cd hello
&lt;&#x2F;span&gt;&lt;span&gt;cargo build
&lt;&#x2F;span&gt;&lt;span&gt;cargo run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Congratulations, you&#x27;re running Rust!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Multiple languages on TravisCI</title>
        <published>2015-11-12T00:00:00+00:00</published>
        <updated>2015-11-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/11/12/multiple_languages_on_travisci/"/>
        <id>https://edunham.net/2015/11/12/multiple_languages_on_travisci/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/11/12/multiple_languages_on_travisci/">&lt;p&gt;Today I noticed an assumption which was making my life unnecessarily difficult: I assumed that if my &lt;code&gt;.travis.yml&lt;&#x2F;code&gt; said &lt;code&gt;language: ruby&lt;&#x2F;code&gt; on the first line, I was supposed to only run Ruby code from it.&lt;&#x2F;p&gt;
&lt;p&gt;Travis lets you run code much more arbitrary than that.&lt;&#x2F;p&gt;
&lt;p&gt;I did a bunch of tests on a &lt;a href=&quot;https:&#x2F;&#x2F;travis-ci.org&#x2F;edunham&#x2F;travis-test&#x2F;builds&quot;&gt;toy repo&lt;&#x2F;a&gt; to see what would happen if I ignored my preconceptions about how you can and can&#x27;t test stuff, and learned some interesting things:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You can install PyPI packages in a test suite that&#x27;s technically Ruby, or gems in a test suite that&#x27;s technically Python.&lt;&#x2F;li&gt;
&lt;li&gt;If your project is &lt;code&gt;language:ruby&lt;&#x2F;code&gt;, you need to &lt;code&gt;sudo pip install&lt;&#x2F;code&gt; dependencies. If it&#x27;s &lt;code&gt;language:python&lt;&#x2F;code&gt;, you can just &lt;code&gt;gem install&lt;&#x2F;code&gt; dependencies without &lt;code&gt;sudo&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;If I specify multiple instances of &lt;code&gt;language:&lt;&#x2F;code&gt; or multiple build matrices, Travis uses the language whose build matrix occurs last. If I specify a Python matrix and then a Ruby one, the Ruby matrix will be run.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This is especially useful when testing or deployment requires hitting an API whose libraries are most up to date in a language other than that of the project.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Beyond Openhatch</title>
        <published>2015-11-04T00:00:00+00:00</published>
        <updated>2015-11-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/11/04/beyond_openhatch/"/>
        <id>https://edunham.net/2015/11/04/beyond_openhatch/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/11/04/beyond_openhatch/">&lt;p&gt;Update: I&#x27;m now maintaining the issue aggregator list at &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;pages&#x2F;issue_aggregators.html&quot;&gt;http:&#x2F;&#x2F;edunham.net&#x2F;pages&#x2F;issue_aggregators.html&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;openhatch.org&#x2F;&quot;&gt;OpenHatch&lt;&#x2F;a&gt; is a wonderful place to help new contributors find their first open source issues to work on. Their training materials are unparalleled, and the &quot;projects submit easy bugs with mentors&quot; model makes their list of introductory issues reliably high-quality.&lt;&#x2F;p&gt;
&lt;p&gt;However, once you know the basics of how to engage with an open source project, you&#x27;re no longer in the target audience for OpenHatch&#x27;s list. Where should you look for introductory issues when you want to get involved with a new project, but you&#x27;re already familiar with open source in general?&lt;&#x2F;p&gt;
&lt;p&gt;An excellent &lt;a href=&quot;http:&#x2F;&#x2F;www.joshmatthews.net&#x2F;fsoss15&#x2F;&quot;&gt;slide deck&lt;&#x2F;a&gt; by Josh Matthews contains several answers to this question:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;issuehub.io&quot;&gt;issuehub.io&lt;&#x2F;a&gt; scrapes GitHub by labels and language&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;up-for-grabs.net&#x2F;&quot;&gt;up-for-grabs&lt;&#x2F;a&gt; has an opt-in list of projects looking for new contributors, and scrapes their issue trackers for their &quot;jump in&quot;, &quot;up for grabs&quot; or other &quot;new contributors welcome&quot; tags.&lt;&#x2F;li&gt;
&lt;li&gt;If you&#x27;re looking for Mozilla-specific contributions outside of just code, &lt;a href=&quot;http:&#x2F;&#x2F;up-for-grabs.net&#x2F;#&#x2F;&quot;&gt;What can I do for Mozilla?&lt;&#x2F;a&gt; can help direct you into any of Mozilla&#x27;s myriad opportunities for involvement.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Additionally, the &lt;a href=&quot;http:&#x2F;&#x2F;servo.github.io&#x2F;servo-starters&#x2F;&quot;&gt;servo-starters&lt;&#x2F;a&gt; page has a custom view of easy issues sorted by Servo&#x27;s project-specific tags.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;github-tricks&quot;&gt;GitHub Tricks&lt;&#x2F;h1&gt;
&lt;p&gt;If you&#x27;re looking for open issues across all repos owned by a particular user or organization, you can use the search at &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pulls&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;pulls&lt;&#x2F;a&gt; and specify the &quot;user&quot; (or org) in the search bar. For instance, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pulls?utf8=%E2%9C%93&amp;amp;q=is%3Aopen+user%3Arust-lang+no%3Aassignee+label%3AE-Easy+&quot;&gt;this search&lt;&#x2F;a&gt; will find all the unassigned, easy-tagged issues in the &lt;code&gt;rust-lang&lt;&#x2F;code&gt; org. Breaking down the search:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user:rust-lang&lt;&#x2F;code&gt; searches all repos owned by &lt;code&gt;github.com&#x2F;rust-lang&lt;&#x2F;code&gt;. It could also be someone&#x27;s github username.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;is:open&lt;&#x2F;code&gt; searches only open issues.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;no:assignee&lt;&#x2F;code&gt; will filter out the issues which are obviously claimed. Note that some issues without an assignee set may still have a comment saying &quot;I&#x27;ll do this!&quot;, if it was claimed by a user who did not have permissions to set assignees and then not triaged.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;label:E-Easy&lt;&#x2F;code&gt; uses my prior knowledge that most repos within &lt;code&gt;rust-lang&lt;&#x2F;code&gt; annotate introductory bugs with the &lt;code&gt;E-easy&lt;&#x2F;code&gt; tag. When in doubt, check the &lt;code&gt;contributing.md&lt;&#x2F;code&gt; file at the top level in the org&#x27;s most popular repository for an explanation of what various issue labels mean. If that information isn&#x27;t in the contributing file or the README, file a bug!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Am I missing your favorite introductory issue aggregator? Shoot me an email to &lt;code&gt;___@edunham.net&lt;&#x2F;code&gt; (fill in the blank with anything; the email will get to me) with a link, and I&#x27;ll add it here if it looks good!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>PSA: Pin Versions</title>
        <published>2015-10-29T00:00:00+00:00</published>
        <updated>2015-10-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/10/29/psa_pin_versions/"/>
        <id>https://edunham.net/2015/10/29/psa_pin_versions/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/10/29/psa_pin_versions/">&lt;p&gt;Today, the website&#x27;s build &lt;a href=&quot;https:&#x2F;&#x2F;travis-ci.org&#x2F;rust-lang&#x2F;rust-www&#x2F;builds&#x2F;88167123&quot;&gt;broke&lt;&#x2F;a&gt;. We made no changes to the tests, yet a wild dependency error emerged:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Generating... 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  Dependency Error: Yikes! It looks like you don&amp;#39;t have redcarpet or one of
&lt;&#x2F;span&gt;&lt;span&gt;its dependencies installed. In order to use Jekyll as currently configured,
&lt;&#x2F;span&gt;&lt;span&gt;you&amp;#39;ll need to install this gem. The full error message from Ruby is: &amp;#39;cannot
&lt;&#x2F;span&gt;&lt;span&gt;load such file -- redcarpet&amp;#39; If you run into trouble, you can find helpful
&lt;&#x2F;span&gt;&lt;span&gt;resources at http:&#x2F;&#x2F;jekyllrb.com&#x2F;help&#x2F;! 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  Conversion error: Jekyll::Converters::Markdown encountered an error while
&lt;&#x2F;span&gt;&lt;span&gt;converting &amp;#39;conduct.md&amp;#39;:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                    redcarpet
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;             ERROR: YOUR SITE COULD NOT BE BUILT:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                    ------------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                    redcarpet
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The command &amp;quot;jekyll build&amp;quot; exited with 1.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Although Googling the error was unhelpful, a bit more digging revealed that our last working build had been on Jekyll &lt;code&gt;2.5.3&lt;&#x2F;code&gt; and the builds breaking on a Redcarpet error all used &lt;code&gt;3.0.0&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The moral of the story is that where the &lt;code&gt;.travis.yml&lt;&#x2F;code&gt; said &lt;code&gt;- gem install jekyll&lt;&#x2F;code&gt;, it should have said &lt;code&gt;- gem install jekyll -v 2.5.3&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>SeaGL 2015 Retrospective</title>
        <published>2015-10-25T00:00:00+00:00</published>
        <updated>2015-10-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/10/25/seagl_2015_retrospective/"/>
        <id>https://edunham.net/2015/10/25/seagl_2015_retrospective/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/10/25/seagl_2015_retrospective/">&lt;p&gt;As well as nominally helping organize the event, I attended and spoke at &lt;a href=&quot;http:&#x2F;&#x2F;seagl.org&#x2F;&quot;&gt;SeaGL&lt;&#x2F;a&gt; 2015 this weekend. The slides from my talk are &lt;a href=&quot;http:&#x2F;&#x2F;talks.edunham.net&#x2F;seagl2015&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;My talk drew an audience of perhaps a dozen people on Friday afternoon. I didn&#x27;t record this instance of the talk, but will probably give it at least one more time and be sure to record then.&lt;&#x2F;p&gt;
&lt;p&gt;One of the more useful tools I learned about is called &lt;a href=&quot;https:&#x2F;&#x2F;myrepos.branchable.com&#x2F;&quot;&gt;myrepos&lt;&#x2F;a&gt;. It lets you update all of the Git repositories on a machine at the same time, as well as other neat tricks like replaying actions that failed due to network problems. Its &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;joeyh&quot;&gt;author&lt;&#x2F;a&gt; has written a variety of other useful Git wrappers, as well.&lt;&#x2F;p&gt;
&lt;p&gt;Additionally, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;RichiH&#x2F;vcsh&quot;&gt;VCSH&lt;&#x2F;a&gt; seems to be the &quot;I knew somebody else wrote that already!&quot; tool for keeping parts of a home directory in Git.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Upgrading Buildbot 0.8.6 to 0.8.12</title>
        <published>2015-10-14T00:00:00+00:00</published>
        <updated>2015-10-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/10/14/upgrading_buildbot_0_8_6_to_0_8_12/"/>
        <id>https://edunham.net/2015/10/14/upgrading_buildbot_0_8_6_to_0_8_12/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/10/14/upgrading_buildbot_0_8_6_to_0_8_12/">&lt;p&gt;Here are some quick notes on upgrading Buildbot.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;system-dependencies&quot;&gt;System Dependencies&lt;&#x2F;h1&gt;
&lt;p&gt;There are more now. In order to successfully install all of Buildbot&#x27;s dependencies with Pip, I needed a few more apt packages:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;python-dev
&lt;&#x2F;span&gt;&lt;span&gt;python-openssl
&lt;&#x2F;span&gt;&lt;span&gt;libffi-dev
&lt;&#x2F;span&gt;&lt;span&gt;libssl-dev
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then for sanity&#x27;s sake make a virtualenv, and install the following packages. Note that having too new a &lt;code&gt;sqlalchemy&lt;&#x2F;code&gt; will &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;17031471&#x2F;why-buildbot-throw-importerror-cannot-import-name-exceptions&quot;&gt;break things&lt;&#x2F;a&gt;.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;buildbot==0.8.12
&lt;&#x2F;span&gt;&lt;span&gt;boto
&lt;&#x2F;span&gt;&lt;span&gt;pyopenssl
&lt;&#x2F;span&gt;&lt;span&gt;cryptography
&lt;&#x2F;span&gt;&lt;span&gt;SQLAlchemy&amp;lt;=0.7.10
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;virtualenvs&quot;&gt;Virtualenvs&lt;&#x2F;h1&gt;
&lt;p&gt;Troubleshooting compatibility issues with system packages on a host that runs several Python services with various dependency versions is predictably terrible.&lt;&#x2F;p&gt;
&lt;p&gt;The potential problem with switching to running Buildbot only from a virtualenv is that developers with access to the buildmaster might want to restart it and miss the extra step of activating the virtualenv. I addressed this by adding the command to activate the virtualenv (using the virtualenv&#x27;s absolute path) to the &lt;code&gt;~&#x2F;.bashrc&lt;&#x2F;code&gt; of the user that we run Buildbot as. This way, we&#x27;ve gained the benefits of having our dependencies consolidated without adding the cost of an extra workflow step to remember.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;template-changes&quot;&gt;Template changes&lt;&#x2F;h1&gt;
&lt;p&gt;Most of Buildbot&#x27;s status pages worked fine after the upgrade, but the console view threw a template error because it couldn&#x27;t find any variable named &quot;categories&quot;. The fix was to simply copy the new template from &lt;code&gt;venv&#x2F;local&#x2F;lib&#x2F;python2.7&#x2F;site-packages&#x2F;buildbot&#x2F;status&#x2F;web&#x2F;templates&#x2F;console.html&lt;&#x2F;code&gt; to &lt;code&gt;my-buildbot&#x2F;master&#x2F;templates&#x2F;console.html&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;that-s-it&quot;&gt;That&#x27;s it!&lt;&#x2F;h1&gt;
&lt;p&gt;Rust currently has these updates on the development buildmaster, but not yet (as of 10&#x2F;14&#x2F;2015) in prod.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Carrying credentials between environments</title>
        <published>2015-09-29T00:00:00+00:00</published>
        <updated>2015-09-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/09/29/carrying_credentials_between_environments/"/>
        <id>https://edunham.net/2015/09/29/carrying_credentials_between_environments/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/09/29/carrying_credentials_between_environments/">&lt;p&gt;This scenario is simplified for purposes of demonstration.&lt;&#x2F;p&gt;
&lt;p&gt;I have 3 machines: &lt;code&gt;A&lt;&#x2F;code&gt;, &lt;code&gt;B&lt;&#x2F;code&gt;, and &lt;code&gt;C&lt;&#x2F;code&gt;. &lt;code&gt;A&lt;&#x2F;code&gt; is my laptop, &lt;code&gt;B&lt;&#x2F;code&gt; is a bastion, and &lt;code&gt;C&lt;&#x2F;code&gt; is a server that I only access through the bastion.&lt;&#x2F;p&gt;
&lt;p&gt;I use an SSH keypair helpfully named &lt;code&gt;AB&lt;&#x2F;code&gt; to get from &lt;code&gt;me@A&lt;&#x2F;code&gt; to &lt;code&gt;me@B&lt;&#x2F;code&gt;. On &lt;code&gt;B&lt;&#x2F;code&gt;, I &lt;code&gt;su&lt;&#x2F;code&gt; to &lt;code&gt;user&lt;&#x2F;code&gt;. I then use an SSH keypair named &lt;code&gt;BC&lt;&#x2F;code&gt; to get from &lt;code&gt;user@B&lt;&#x2F;code&gt; to &lt;code&gt;user@C&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I do not wish to store the &lt;code&gt;BC&lt;&#x2F;code&gt; private key on host &lt;code&gt;B&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ssh-agent-forwarding&quot;&gt;SSH Agent Forwarding&lt;&#x2F;h1&gt;
&lt;p&gt;I have keys &lt;code&gt;AB&lt;&#x2F;code&gt; and &lt;code&gt;BC&lt;&#x2F;code&gt; on host &lt;code&gt;A&lt;&#x2F;code&gt;, where I start. Host &lt;code&gt;A&lt;&#x2F;code&gt; is running &lt;code&gt;ssh-agent&lt;&#x2F;code&gt;, which is installed by default on most Linux distributions. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;me@A$ ssh-add ~&#x2F;.ssh&#x2F;AB     # Add keypair AB to ssh-agent&amp;#39;s keychain
&lt;&#x2F;span&gt;&lt;span&gt;me@A$ ssh-add ~&#x2F;.ssh&#x2F;BC     # Add keypair BC to the keychain
&lt;&#x2F;span&gt;&lt;span&gt;me@A$ ssh -A me@B           # Forward my ssh-agent 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I&#x27;m logged into host &lt;code&gt;B&lt;&#x2F;code&gt; and have access to the &lt;code&gt;AB&lt;&#x2F;code&gt; and &lt;code&gt;BC&lt;&#x2F;code&gt; keypairs. An attacker who gains access to &lt;code&gt;B&lt;&#x2F;code&gt; after I log out will have no way to steal the &lt;code&gt;BC&lt;&#x2F;code&gt; keypair, unlike what would happen if that keypair was stored on &lt;code&gt;B&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;See &lt;a href=&quot;http:&#x2F;&#x2F;www.unixwiz.net&#x2F;techtips&#x2F;ssh-agent-forwarding.html&quot;&gt;here&lt;&#x2F;a&gt; for pretty pictures explaining in more detail how agent forwarding works.&lt;&#x2F;p&gt;
&lt;p&gt;Anyways, I could now &lt;code&gt;ssh me@C&lt;&#x2F;code&gt; with no problem. But if I &lt;code&gt;sudo su user&lt;&#x2F;code&gt;, my agent is no longer forwarded, so I can&#x27;t then use the key that I added back on &lt;code&gt;A&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;switch-user-while-preserving-environment-variables&quot;&gt;Switch user while preserving environment variables&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;me@B$ sudo -E su user
&lt;&#x2F;span&gt;&lt;span&gt;user@B$ sudo -E ssh user@C
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;what&quot;&gt;What?&lt;&#x2F;h1&gt;
&lt;p&gt;The &lt;code&gt;-E&lt;&#x2F;code&gt; flag to sudo preserves the environment variables of the user you&#x27;re logged in as. &lt;code&gt;ssh-agent&lt;&#x2F;code&gt; uses a socket whose name is of the form &lt;code&gt;&#x2F;tmp&#x2F;ssh-AbCdE&#x2F;agent.12345&lt;&#x2F;code&gt; to call back to host &lt;code&gt;A&lt;&#x2F;code&gt; when it&#x27;s time to do the handshake involving key &lt;code&gt;BC&lt;&#x2F;code&gt;, and the socket&#x27;s name is stored in &lt;code&gt;me&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;SSH_AUTH_SOCK&lt;&#x2F;code&gt; environment variable. So by telling sudo to preserve environment variables when switching user, we allow &lt;code&gt;user&lt;&#x2F;code&gt; to pass ssh handshake stuff back to &lt;code&gt;A&lt;&#x2F;code&gt;, where the &lt;code&gt;BC&lt;&#x2F;code&gt; key is available.&lt;&#x2F;p&gt;
&lt;p&gt;Why is &lt;code&gt;sudo -E&lt;&#x2F;code&gt; required to ssh to &lt;code&gt;C&lt;&#x2F;code&gt;? Because &lt;code&gt;&#x2F;tmp&#x2F;sshAbCdE&#x2F;agent.12345&lt;&#x2F;code&gt; is owned by &lt;code&gt;me:me&lt;&#x2F;code&gt;, and only the file&#x27;s owner may read, write, or execute it. Additionally, the socket itself (&lt;code&gt;agent.12345&lt;&#x2F;code&gt;) is owned by &lt;code&gt;me:me&lt;&#x2F;code&gt;, and is not writable by others.&lt;&#x2F;p&gt;
&lt;p&gt;If you must run ssh on &lt;code&gt;B&lt;&#x2F;code&gt; without sudo, &lt;code&gt;chown -R &#x2F;tmp&#x2F;ssh-AbCdE&lt;&#x2F;code&gt; to the user who needs to end up using the socket. Making them world read&#x2F;writable would allow any user on the system to use any key currently added to the &lt;code&gt;ssh-agent&lt;&#x2F;code&gt; on &lt;code&gt;A&lt;&#x2F;code&gt;, which is a terrible idea.&lt;&#x2F;p&gt;
&lt;p&gt;For what it&#x27;s worth, the actual value of &lt;code&gt;&#x2F;tmp&#x2F;ssh-AbCdE&#x2F;agent.12345&lt;&#x2F;code&gt; is available at any time in this workflow as the result of &lt;code&gt;printenv | grep SSH_AUTH_SOCK | cut -f2 -d =&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-catch&quot;&gt;The Catch&lt;&#x2F;h1&gt;
&lt;p&gt;Did you see what just happened there? An arbitrary user with sudo on &lt;code&gt;B&lt;&#x2F;code&gt; just gained access to all the keys added to ssh-agent on &lt;code&gt;A&lt;&#x2F;code&gt;. &lt;a href=&quot;http:&#x2F;&#x2F;exyr.org&#x2F;about&#x2F;&quot;&gt;Simon&lt;&#x2F;a&gt; pointed out that the right way address this issue is to use &lt;a href=&quot;https:&#x2F;&#x2F;heipei.github.io&#x2F;2015&#x2F;02&#x2F;26&#x2F;SSH-Agent-Forwarding-considered-harmful&#x2F;&quot;&gt;ProxyCommand&lt;&#x2F;a&gt; instead of agent forwarding.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;no-i-really-don-t-want-my-keys-accessible-on-b&quot;&gt;No, I &lt;em&gt;really&lt;&#x2F;em&gt; don&#x27;t want my keys accessible on &lt;code&gt;B&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;See &lt;code&gt;man ssh_config&lt;&#x2F;code&gt; for more of the details on &lt;code&gt;ProxyCommand&lt;&#x2F;code&gt;. In &lt;code&gt;~&#x2F;.ssh&#x2F;config&lt;&#x2F;code&gt; on &lt;code&gt;A&lt;&#x2F;code&gt;, I can put:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Host B
&lt;&#x2F;span&gt;&lt;span&gt;    User me
&lt;&#x2F;span&gt;&lt;span&gt;    Hostname 111.222.333.444
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Host C
&lt;&#x2F;span&gt;&lt;span&gt;    User user
&lt;&#x2F;span&gt;&lt;span&gt;    Hostname 222.333.444.555
&lt;&#x2F;span&gt;&lt;span&gt;    Port 2222
&lt;&#x2F;span&gt;&lt;span&gt;    ProxyCommand ssh -q -w %h:%p B
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So then, on &lt;code&gt;A&lt;&#x2F;code&gt;, I can &lt;code&gt;ssh C&lt;&#x2F;code&gt; and be forwarded through &lt;code&gt;B&lt;&#x2F;code&gt; transparently.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Ansible: Conditional role dependencies</title>
        <published>2015-09-10T00:00:00+00:00</published>
        <updated>2015-09-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/09/10/ansible_conditional_role_dependencies/"/>
        <id>https://edunham.net/2015/09/10/ansible_conditional_role_dependencies/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/09/10/ansible_conditional_role_dependencies/">&lt;p&gt;I&#x27;ve recently been working on an Ansible role that applies to both Ubuntu and OSX hosts. It has some dependencies which are only needed on OSX. There doesn&#x27;t seem to be a central document on all the options available for solving this problem, so here are my notes.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;scenario&quot;&gt;Scenario&lt;&#x2F;h1&gt;
&lt;p&gt;The role which must apply to both Ubuntu and OSX hosts builds a Rust compiler capable of cross-compiling to Android, so I call it &lt;code&gt;crosscompiler&lt;&#x2F;code&gt;. To run the &lt;code&gt;crosscompiler&lt;&#x2F;code&gt; role on a Mac, you need the &lt;code&gt;xcode&lt;&#x2F;code&gt; role installed, but applying the &lt;code&gt;xcode&lt;&#x2F;code&gt; role to an Ubuntu host will fail.&lt;&#x2F;p&gt;
&lt;p&gt;A simplified version of this setup looks like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ansible-configs&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;├── galaxy_roles.yaml
&lt;&#x2F;span&gt;&lt;span&gt;├── hosts
&lt;&#x2F;span&gt;&lt;span&gt;├── roles
&lt;&#x2F;span&gt;&lt;span&gt;│   ├── crosscompiler
&lt;&#x2F;span&gt;&lt;span&gt;│   │   ├── defaults
&lt;&#x2F;span&gt;&lt;span&gt;│   │   │   └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;│   │   ├── meta
&lt;&#x2F;span&gt;&lt;span&gt;│   │   │   └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;│   │   └── tasks
&lt;&#x2F;span&gt;&lt;span&gt;│   │       └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;│   └── xcode
&lt;&#x2F;span&gt;&lt;span&gt;│       ├── defaults
&lt;&#x2F;span&gt;&lt;span&gt;│       │   └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;│       ├── meta
&lt;&#x2F;span&gt;&lt;span&gt;│       │   └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;│       └── tasks
&lt;&#x2F;span&gt;&lt;span&gt;│           └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;└── site.yaml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here are a bunch of different ways to avoid applying a Mac-specific task to an Ubuntu host, or vice versa. Note that &lt;strong&gt;any&lt;&#x2F;strong&gt; of the following steps in isolation will solve the problem -- it should not be necessary to use more than one of them.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;check-os-on-each-task-of-the-role&quot;&gt;Check OS on each task of the role&lt;&#x2F;h1&gt;
&lt;p&gt;Add the line &lt;code&gt;when: ansible_os_family == &#x27;Darwin&#x27;&lt;&#x2F;code&gt; at the end of each task in &lt;code&gt;roles&#x2F;xcode&#x2F;tasks&#x2F;main.yaml&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This needlessly bloats the code and makes it more difficult to read.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;refactor-depended-role-to-ignore-non-target-platforms&quot;&gt;Refactor depended role to ignore non-target platforms&lt;&#x2F;h1&gt;
&lt;p&gt;Move the entire contents of &lt;code&gt;roles&#x2F;xcode&#x2F;main.yaml&lt;&#x2F;code&gt; into &lt;code&gt;roles&#x2F;xcode&#x2F;osx.yaml&lt;&#x2F;code&gt;, then create a new &lt;code&gt;main.yaml&lt;&#x2F;code&gt; containing:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;- include: osx.yaml
&lt;&#x2F;span&gt;&lt;span&gt;  when: ansible_os_family == &amp;#39;Darwin&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This avoids the bloat induced by running the conditional on each task, while accomplishing the same goal. Now the &lt;code&gt;xcode&lt;&#x2F;code&gt; role looks like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;xcode
&lt;&#x2F;span&gt;&lt;span&gt;├── defaults
&lt;&#x2F;span&gt;&lt;span&gt;│   └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;├── meta
&lt;&#x2F;span&gt;&lt;span&gt;│   └── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;└── tasks
&lt;&#x2F;span&gt;&lt;span&gt;    ├── main.yaml
&lt;&#x2F;span&gt;&lt;span&gt;    └── osx.yaml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the best solution for a role which might later expand to support additional platforms.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;make-the-dependency-conditional-in-meta-main-yaml-of-depending-role&quot;&gt;Make the dependency conditional in &lt;code&gt;meta&#x2F;main.yaml&lt;&#x2F;code&gt; of depending role&lt;&#x2F;h1&gt;
&lt;p&gt;Edit &lt;code&gt;ansible-configs&#x2F;roles&#x2F;crosscompiler&#x2F;main.yaml&lt;&#x2F;code&gt; so that the dependency on &lt;code&gt;xcode&lt;&#x2F;code&gt; reads:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;dependencies:
&lt;&#x2F;span&gt;&lt;span&gt;  - { role: &amp;#39;xcode&amp;#39;, when: ansible_os_family == &amp;#39;Darwin&amp;#39; }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the best solution when the inner role will only ever target one platform, as is the case with &lt;code&gt;xcode&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;install-role-conditionally-from-site-yaml&quot;&gt;Install role conditionally from site.yaml&lt;&#x2F;h1&gt;
&lt;p&gt;Edit &lt;code&gt;ansible-configs&#x2F;site.yaml&lt;&#x2F;code&gt; to read:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Provision cross-compile hosts
&lt;&#x2F;span&gt;&lt;span&gt;  hosts: xcompilehosts
&lt;&#x2F;span&gt;&lt;span&gt;  roles:
&lt;&#x2F;span&gt;&lt;span&gt;    - { role: xcode, when: ansible_os_family == &amp;#39;Darwin&amp;#39; }
&lt;&#x2F;span&gt;&lt;span&gt;    - crosscompiler
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is problematic because if I was to distribute the &lt;code&gt;crosscompiler&lt;&#x2F;code&gt; role on the Ansible Galaxy, its dependency logic would not be distributed to other users correctly.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h1&gt;
&lt;p&gt;You can conditionally include dependencies in your roles. It&#x27;s helpful to end users when galaxy roles only try to apply platform-specific tasks to their target platforms, since you can&#x27;t be sure how others will use your code.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Apache Licenses</title>
        <published>2015-08-28T00:00:00+00:00</published>
        <updated>2015-08-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/08/28/apache_licenses/"/>
        <id>https://edunham.net/2015/08/28/apache_licenses/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/08/28/apache_licenses/">&lt;p&gt;At the bottom of the Apache 2.0 License file, there&#x27;s an appendix:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;APPENDIX: How to apply the Apache License to your work.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Copyright [yyyy] [name of copyright owner]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Does that look like an invitation to fill in the blanks to you? It sure does to me, and has &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;pull&#x2F;24068&quot;&gt;for&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;pull&#x2F;13391#issuecomment-40270597&quot;&gt;others&lt;&#x2F;a&gt; in the Rust community as well.&lt;&#x2F;p&gt;
&lt;p&gt;Today I was doing some licensing housekeeping and made the same &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;regex&#x2F;pull&#x2F;117&quot;&gt;embarrassing&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;regex&#x2F;pull&#x2F;118&quot;&gt;mistake&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This is a PSA to double check whether inviting blanks are part of the appendix before filling them out in Apache license texts.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>X240 trackpoint speed</title>
        <published>2015-08-24T00:00:00+00:00</published>
        <updated>2015-08-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/08/24/x240_trackpoint_speed/"/>
        <id>https://edunham.net/2015/08/24/x240_trackpoint_speed/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/08/24/x240_trackpoint_speed/">&lt;p&gt;The screen on my X1 Carbon gave out after a couple months, and my loaner laptop in the meantime is an X240.&lt;&#x2F;p&gt;
&lt;p&gt;The worst thing about this laptop is how slowly the trackpoint moves with a default Ubuntu installation. However, it&#x27;s fixable:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;cat &#x2F;sys&#x2F;devices&#x2F;platform&#x2F;i8042&#x2F;serio1&#x2F;serio2&#x2F;speed
&lt;&#x2F;span&gt;&lt;span&gt;cat &#x2F;sys&#x2F;devices&#x2F;platform&#x2F;i8042&#x2F;serio1&#x2F;serio2&#x2F;sensitivity
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the starting values in case anything goes wrong, then fiddle around:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;echo 255 | sudo tee &#x2F;sys&#x2F;devices&#x2F;platform&#x2F;i8042&#x2F;serio1&#x2F;serio2&#x2F;sensitivity
&lt;&#x2F;span&gt;&lt;span&gt;echo 255 | sudo tee &#x2F;sys&#x2F;devices&#x2F;platform&#x2F;i8042&#x2F;serio1&#x2F;serio2&#x2F;speed
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Some binary search themed prodding and a lot of &lt;code&gt;tee: &#x2F;sys&#x2F;devices&#x2F;platform&#x2F;i8042&#x2F;serio1&#x2F;serio2&#x2F;sensitivity: Numerical result out of range&lt;&#x2F;code&gt; has confirmed that both files accept values between 0-255. Interestingly, setting them to 0 does not seem to disable the trackpoint completely.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re wondering why the configuration settings look like ordinary files but choke on values bigger or smaller than a short, go &lt;a href=&quot;https:&#x2F;&#x2F;www.kernel.org&#x2F;pub&#x2F;linux&#x2F;kernel&#x2F;people&#x2F;mochel&#x2F;doc&#x2F;papers&#x2F;ols-2005&#x2F;mochel.pdf&quot;&gt;read about sysfs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Folklore and fallacy</title>
        <published>2015-08-17T00:00:00+00:00</published>
        <updated>2015-08-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/08/17/kangaroos/"/>
        <id>https://edunham.net/2015/08/17/kangaroos/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/08/17/kangaroos/">&lt;p&gt;I was a student employee at the OSU Open Source Lab, on and off between internships and other jobs, for 4 years. Being part of the lab helped shape my life and career, in almost overwhelmingly positive ways. However, the farther I get from the lab the more clearly I notice how being part of it changed the way I form expectations about my own technical skills.&lt;&#x2F;p&gt;
&lt;p&gt;To show you the fallacy that I noticed myself falling into, I&#x27;d like to tell you a completely made-up story about some alphabetically named kangaroos. Below the fold, there&#x27;ll be pictures!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;jumping-school-begins.jpg&quot; alt=&quot;Kangaroos starting their jumping school in the desert&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once upon a time, some kangaroos lived in a desert. One day, for some inscrutable marsupial reason, a bunch of young kangaroos got together to practice jumping. Since they&#x27;re not particularly creative creatures, they called the group jumping school.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;aggie-jumps.jpg&quot; alt=&quot;Aggie the kangaroo jumping&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Every kangaroo who came to jumping school started out only being able to jump 1 foot, and got better at a rate of 1 foot per year, and stayed for 4 years.&lt;&#x2F;p&gt;
&lt;p&gt;A kangaroo called Aggie was one of the school&#x27;s first students. She came in only able to jump 1 foot, but she improved by 1 foot per year.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;bill-joins.jpg&quot; alt=&quot;Bill joins the jumping school&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;At the start of the second year that the school was around, a kangaroo named Bill joined. Bill could only jump 1 foot when he started, and improved at a rate of 1 foot per year. But Aggie could always jump 2 feet farther than Bill while she was still in school, because they were both improving at the same rate.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;aggie-vs-bill.jpg&quot; alt=&quot;Aggie always jumping 2 feet farther than Bill&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Nobody new joined for a while, and Aggie left after her fourth year to go cross roads in front of unwary motorists, but Bill stayed in school and kept improving. When she left school, Aggie could jump 5 feet. She knew she&#x27;d worked hard, and could always jump farther than Bill, so she felt pretty good about herself.&lt;&#x2F;p&gt;
&lt;p&gt;At the start of the school&#x27;s fourth year, after Aggie had left, a new student named Claire joined. Claire could only jump 1 foot at first, but improved at a rate of 1 foot per year.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;claire-joins.jpg&quot; alt=&quot;Claire joins the jumping school&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Claire and Bill would chat about school sometimes, and Claire observed that Bill could always jump 2 feet farther than her. When she commented on it, Bill said &quot;If you think I can jump far, you should have seen Aggie! She was a student here before you came, and she could always jump 2 feet farther than me!&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;At the end of the school&#x27;s 6th year, Bill finished up and went away to fight in boxing matches. (When Bill left, he could jump 5 feet. He always suspected he could have done better, since he remembered Aggie always being able to jump just a bit farther than him.)&lt;&#x2F;p&gt;
&lt;p&gt;A new student, Dave, joined after Bill left. Dave started out being able to only jump 1 foot and was able to jump 5 feet by the time he&#x27;d been at the school for 4 years. Dave knew that Claire could always jump 2 feet farther than him while they were in school together. Dave heard stories from Claire about Bill and Aggie, who could both jump even farther than her!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;claire-jumps.jpg&quot; alt=&quot;Claire jumping at the school&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;2 years after Dave joined, Claire left jumping school for a full-time job tearing up farmers&#x27; crops and gardens. She knew she&#x27;d tried her best, and she could jump 5 feet after she&#x27;d been at school for 4 years, but she also knew that Bill had always been a better jumper than her and Aggie had been even better than Bill. Her 5 feet didn&#x27;t seem particularly impressive, since (she even double-checked her math on it!) Aggie would have been able to jump 4 feet farther, so that must have meant Aggie was a student who could jump 9 feet.&lt;&#x2F;p&gt;
&lt;p&gt;A couple of years later, Dave was finally finished with school. He&#x27;d come in only being able to jump 1 foot, and left being able to jump 5 feet! But he wasn&#x27;t sure if this was better or worse than normal, so he thought about the other kangaroos who&#x27;d also gone to the school.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;dave-compares.jpg&quot; alt=&quot;Dave thinking about his jumping compared to his predecessors&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Dave knew that Claire was a student, and she was able to jump 2 feet farther than him whenever they studied together. Bill was a student who could jump 2 feet farther than Claire, and Aggie was a student who could jump 2 feet farther than Bill! Since Dave could jump 5 feet, he concluded that Claire could jump 7 feet, Bill could jump 9, and Aggie could jump 11! Not only was Dave the worst of the lot, he wasn&#x27;t even half as good as the school&#x27;s first student! He felt pretty bad about his accomplishments, and wondered why students were getting worse every year.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;epilogue&quot;&gt;Epilogue&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;reunion.jpg&quot; alt=&quot;The kangaroo reunion - all jumping about 6 feet&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once upon a somewhat later time, Jumping School had a reunion and all of the former students attended. Claire and Dave were a little afraid of meeting Aggie, since they&#x27;d heard such impressive stories of how far she could jump. All 4 kangaroos tested how far they could jump, just for old times&#x27; sake, and they found that they could all jump about 6 feet! They compared stories about their experiences since leaving school, and found that their rates of improvement had slowed down as they got closer to the limit of how far their species was able to jump.&lt;&#x2F;p&gt;
&lt;p&gt;In reality, red kangaroos &lt;a href=&quot;http:&#x2F;&#x2F;animals.nationalgeographic.com&#x2F;animals&#x2F;mammals&#x2F;red-kangaroo&#x2F;&quot;&gt;only jump about 1.8 meters&lt;&#x2F;a&gt;, which is the only factually accurate part of this entire story.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-moral&quot;&gt;The Moral&lt;&#x2F;h1&gt;
&lt;p&gt;I think that a similar effect distorted my perception of my own competence when I compared myself to past OSL students. One can spot the fallacy pretty easily when everything is spelled out with cute photos: Relative skill levels don&#x27;t translate reliably into absolute ones over time. It&#x27;s tricker to spot the same fallacy in real life, but you might have an easier time now that you&#x27;ve seen the pattern once before.&lt;&#x2F;p&gt;
&lt;p&gt;Photo credits, in order:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;File:Kangaroo_Island_kangaroos.jpg&quot;&gt;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;File:Kangaroo_Island_kangaroos.jpg&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;chrissamuel&#x2F;5366854530&quot;&gt;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;chrissamuel&#x2F;5366854530&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;commons.wikimedia.org&#x2F;wiki&#x2F;File:Flying-kangaroo.jpg&quot;&gt;https:&#x2F;&#x2F;commons.wikimedia.org&#x2F;wiki&#x2F;File:Flying-kangaroo.jpg&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;kaptainkobold&#x2F;3007004396&quot;&gt;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;kaptainkobold&#x2F;3007004396&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;File:-_fighting_red_kangaroos_1.jpg&quot;&gt;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;File:-_fighting_red_kangaroos_1.jpg&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;opencage.info&#x2F;pics&#x2F;files&#x2F;800_11526.jpg&quot;&gt;http:&#x2F;&#x2F;opencage.info&#x2F;pics&#x2F;files&#x2F;800_11526.jpg&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;moonfish&#x2F;3147269483&quot;&gt;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;moonfish&#x2F;3147269483&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;chrissamuel&#x2F;5480184535&quot;&gt;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;chrissamuel&#x2F;5480184535&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>RustCamp videos are available</title>
        <published>2015-08-17T00:00:00+00:00</published>
        <updated>2015-08-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/08/17/rustcamp_videos_are_available/"/>
        <id>https://edunham.net/2015/08/17/rustcamp_videos_are_available/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/08/17/rustcamp_videos_are_available/">&lt;p&gt;The videos from RustCamp are available &lt;a href=&quot;http:&#x2F;&#x2F;confreaks.tv&#x2F;events&#x2F;rustcamp2015&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I asked Gankro what was up with the milkshake thing in &lt;a href=&quot;http:&#x2F;&#x2F;confreaks.tv&#x2F;videos&#x2F;rustcamp2015-who-owns-this-stream-of-data&quot;&gt;his talk&lt;&#x2F;a&gt;, and learned about &lt;a href=&quot;http:&#x2F;&#x2F;knowyourmeme.com&#x2F;memes&#x2F;i-drink-your-milkshake&quot;&gt;this meme&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Don&#x27;t Starve</title>
        <published>2015-08-09T00:00:00+00:00</published>
        <updated>2015-08-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/08/09/don_t_starve/"/>
        <id>https://edunham.net/2015/08/09/don_t_starve/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/08/09/don_t_starve/">&lt;p&gt;It was a lazy Sunday afternoon and I wanted to play Don&#x27;t Starve. This actually ended up meaning about 3 hours of intermittent troubleshooting and 1 hour of games, because Linux.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;get-the-files&quot;&gt;Get the files&lt;&#x2F;h1&gt;
&lt;p&gt;I bought Don&#x27;t Starve from the Humble Bundle store, although there are other methods of obtaining it which strike a different balance between cost and convenience.&lt;&#x2F;p&gt;
&lt;p&gt;The downloaded file is &lt;code&gt;dontstarve_x64_july21.tar.gz&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;this-just-works&quot;&gt;This Just Works&lt;&#x2F;h2&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ yaourt -S libcurl-compat
&lt;&#x2F;span&gt;&lt;span&gt;$ tar -xvf dontstarve_x64_july21.tar.gz
&lt;&#x2F;span&gt;&lt;span&gt;$ cd dontstarve&#x2F;bin
&lt;&#x2F;span&gt;&lt;span&gt;$ LD_PRELOAD=libcurl.so.3 .&#x2F;dontstarve
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Below the fold is the troubleshooting process I went through to make it look so easy. Hopefully it&#x27;ll be of assistance to those searching for the errors that I ran into!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;this-part-has-seo-for-all-the-intermediate-errors&quot;&gt;This part has SEO for all the intermediate errors&lt;&#x2F;h2&gt;
&lt;h1 id=&quot;try-to-run-the-script&quot;&gt;Try to run the script&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cd dontstarve
&lt;&#x2F;span&gt;&lt;span&gt;$ .&#x2F;dontstarve.sh
&lt;&#x2F;span&gt;&lt;span&gt;sh: symbol lookup error: sh: undefined symbol: rl_signal_event_hook
&lt;&#x2F;span&gt;&lt;span&gt;sh: symbol lookup error: sh: undefined symbol: rl_signal_event_hook
&lt;&#x2F;span&gt;&lt;span&gt;sh: symbol lookup error: sh: undefined symbol: rl_signal_event_hook
&lt;&#x2F;span&gt;&lt;span&gt;sh: symbol lookup error: sh: undefined symbol: rl_signal_event_hook
&lt;&#x2F;span&gt;&lt;span&gt;sh: symbol lookup error: sh: undefined symbol: rl_signal_event_hook
&lt;&#x2F;span&gt;&lt;span&gt;Fontconfig error: &amp;quot;&#x2F;etc&#x2F;fonts&#x2F;conf.d&#x2F;10-scale-bitmap-fonts.conf&amp;quot;, line 70: non-double matrix element
&lt;&#x2F;span&gt;&lt;span&gt;Fontconfig error: &amp;quot;&#x2F;etc&#x2F;fonts&#x2F;conf.d&#x2F;10-scale-bitmap-fonts.conf&amp;quot;, line 70: non-double matrix element
&lt;&#x2F;span&gt;&lt;span&gt;Fontconfig warning: &amp;quot;&#x2F;etc&#x2F;fonts&#x2F;conf.d&#x2F;10-scale-bitmap-fonts.conf&amp;quot;, line 78: saw unknown, expected number
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(updater:2135): Pango-WARNING **: failed to choose a font, expect ugly output. engine-type=&amp;#39;PangoRenderFc&amp;#39;, script=&amp;#39;latin&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(updater:2135): Pango-WARNING **: failed to choose a font, expect ugly output. engine-type=&amp;#39;PangoRenderFc&amp;#39;, script=&amp;#39;common&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;dontstarve.sh: line 2: unexpected EOF while looking for matching ``&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;dontstarve.sh: line 4: syntax error: unexpected end of file
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Look what doesn&#x27;t like me! The fonts are sad. Spoiler: You don&#x27;t actually need those fonts at all.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;much-frustration&quot;&gt;Much Frustration&lt;&#x2F;h1&gt;
&lt;p&gt;It turns out that the &lt;code&gt;.&#x2F;dontstarve.sh&lt;&#x2F;code&gt; in the root of the unzipped &lt;code&gt;dontstarve&lt;&#x2F;code&gt; directory is solely an updater, which is &lt;strong&gt;totally irrelevant&lt;&#x2F;strong&gt; if you just want to play whatever version of the game they happened to ship you. It launches an updater that requests a game key and has hideously broken fonts and generally sprouts new errors like a Hydra every time you think you&#x27;re making progress.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;try-running-the-correct-dontstarve-executable&quot;&gt;Try running the correct &lt;code&gt;dontstarve&lt;&#x2F;code&gt; executable&lt;&#x2F;h1&gt;
&lt;p&gt;The correct script to run actually lives in &lt;code&gt;bin&#x2F;dontstarve.sh&lt;&#x2F;code&gt;, but if you try to run it from outside that directory, it can&#x27;t find other files:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ bin&#x2F;dontstarve.sh
&lt;&#x2F;span&gt;&lt;span&gt;$ bin&#x2F;dontstarve.sh: line 3: .&#x2F;dontstarve: No such file or directory
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Cute. So instead:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cd bin
&lt;&#x2F;span&gt;&lt;span&gt;$ .&#x2F;dontstarve.sh
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;dontstarve: &#x2F;usr&#x2F;lib&#x2F;libcurl.so.4: version `CURL_OPENSSL_3&amp;#39; not found (required by .&#x2F;dontstarve)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;OR:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ bin&#x2F;dontstarve # Spoiler, this will still be broken later
&lt;&#x2F;span&gt;&lt;span&gt;bin&#x2F;dontstarve: &#x2F;usr&#x2F;lib&#x2F;libcurl.so.4: version `CURL_OPENSSL_3&amp;#39; not found (required by bin&#x2F;dontstarve)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;fixing-the-libcurl-error&quot;&gt;Fixing the &lt;code&gt;libcurl&lt;&#x2F;code&gt; error&lt;&#x2F;h1&gt;
&lt;p&gt;Get &lt;code&gt;libcurl-compat&lt;&#x2F;code&gt; from the AUR:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ yaourt -S libcurl-compat
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At the end of its installation process, it&#x27;ll give you a helpful little warning about preloading the library:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Sometimes you have to preload library!
&lt;&#x2F;span&gt;&lt;span&gt; e.g. if you see this message:
&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;usr&#x2F;lib&#x2F;libcurl.so.4: version `CURL_OPENSSL_3&amp;#39; not found&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt; Do this:
&lt;&#x2F;span&gt;&lt;span&gt; LD_PRELOAD=libcurl.so.3 youprogname 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;run-dontstarve-with-the-right-libcurl&quot;&gt;Run &lt;code&gt;dontstarve&lt;&#x2F;code&gt; with the right &lt;code&gt;libcurl&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ LD_PRELOAD=libcurl.so.3 bin&#x2F;dontstarve
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;hello-segfaults&quot;&gt;Hello Segfaults&lt;&#x2F;h1&gt;
&lt;p&gt;And then we get a nice reproduceable segfault, ending in:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ERROR: Missing Shader &amp;#39;shaders&#x2F;font.ksh&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;Assert failure &amp;#39;0&amp;#39; at ..&#x2F;source&#x2F;renderlib&#x2F;OpenGL&#x2F;HWEffect.cpp(86)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Assert failure &amp;#39;BREAKPT:&amp;#39; at ..&#x2F;source&#x2F;renderlib&#x2F;OpenGL&#x2F;HWEffect.cpp(86)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Assert failure &amp;#39;datasize + mReadHead &amp;lt;= mBufferLength&amp;#39; at ..&#x2F;source&#x2F;util&#x2F;reader.h(28)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Assert failure &amp;#39;BREAKPT:&amp;#39; at ..&#x2F;source&#x2F;util&#x2F;reader.h(28)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Segmentation fault (core dumped)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is &lt;strong&gt;NOT&lt;&#x2F;strong&gt; the cue to go shave a graphics card yak, despite what Googling the error would lead one to believe. This is the cue to:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cd bin
&lt;&#x2F;span&gt;&lt;span&gt;$ LD_PRELOAD=libcurl.so.3 .&#x2F;dontstarve
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If it&#x27;s stupid, but it works, it ain&#x27;t stupid. Running the executable from the right directory makes the game work on an X230 and on an X1 Carbon, so in my (un)professional opinion, it&#x27;s got nothing to do with special fancy graphics drivers.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;it-works&quot;&gt;It works!&lt;&#x2F;h1&gt;
&lt;p&gt;Scroll way back up to the top for the short version. Have fun, and Don&#x27;t Starve!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;p-s&quot;&gt;P.S.&lt;&#x2F;h1&gt;
&lt;p&gt;I tried this with the x32 version. It goes all:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;~&#x2F;Downloads&#x2F;dontstarve&#x2F;bin $ LD_PRELOAD=libcurl.so.3 .&#x2F;dontstarve
&lt;&#x2F;span&gt;&lt;span&gt;bash: .&#x2F;dontstarve: No such file or directory
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The script and executable have the same permissions when unzipped from the 32-or 64-bit tarballs... Just, the 32-bit one doesn&#x27;t work. There&#x27;s probably a good reason for this, possibly related to the fact that I&#x27;m running on a 64-bit system, but since the &lt;code&gt;x64&lt;&#x2F;code&gt; variant of the game runs just fine I didn&#x27;t dig into the &lt;code&gt;x32&lt;&#x2F;code&gt;&#x27;s malfunctions any deeper.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How many Rust channels are there?</title>
        <published>2015-07-31T00:00:00+00:00</published>
        <updated>2015-07-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/31/how_many_rust_channels_are_there/"/>
        <id>https://edunham.net/2015/07/31/how_many_rust_channels_are_there/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/31/how_many_rust_channels_are_there/">&lt;p&gt;I&#x27;m using &lt;a href=&quot;https:&#x2F;&#x2F;search.mibbit.com&#x2F;search&#x2F;%23rust&quot;&gt;search.mibbit.com&lt;&#x2F;a&gt; to count these. All have at least one user in them as of 4pm PST 2015-07-31.&lt;&#x2F;p&gt;
&lt;p&gt;There are &lt;strong&gt;53&lt;&#x2F;strong&gt; Rust-related channels on &lt;code&gt;irc.mozilla.org&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;List below the fold.&lt;&#x2F;p&gt;
&lt;p&gt;41 General and Project channels:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;##rustfmt
&lt;&#x2F;span&gt;&lt;span&gt;#cargo
&lt;&#x2F;span&gt;&lt;span&gt;#hematite (Minecraft-in-Rust)
&lt;&#x2F;span&gt;&lt;span&gt;#hyper (an HTTP library, https:&#x2F;&#x2F;github.com&#x2F;hyperium&#x2F;hyper)
&lt;&#x2F;span&gt;&lt;span&gt;#iron (a web framework, https:&#x2F;&#x2F;github.com&#x2F;iron&#x2F;iron)
&lt;&#x2F;span&gt;&lt;span&gt;#mio
&lt;&#x2F;span&gt;&lt;span&gt;#rust
&lt;&#x2F;span&gt;&lt;span&gt;#rust-api
&lt;&#x2F;span&gt;&lt;span&gt;#rust-apidesign
&lt;&#x2F;span&gt;&lt;span&gt;#rust-audio
&lt;&#x2F;span&gt;&lt;span&gt;#rust-bikeshed
&lt;&#x2F;span&gt;&lt;span&gt;#rust-bots
&lt;&#x2F;span&gt;&lt;span&gt;#rust-casino
&lt;&#x2F;span&gt;&lt;span&gt;#rust-community
&lt;&#x2F;span&gt;&lt;span&gt;#rust-config
&lt;&#x2F;span&gt;&lt;span&gt;#rust-crypto
&lt;&#x2F;span&gt;&lt;span&gt;#rust-data
&lt;&#x2F;span&gt;&lt;span&gt;#rust-design
&lt;&#x2F;span&gt;&lt;span&gt;#rust-dev
&lt;&#x2F;span&gt;&lt;span&gt;#rust-diverse
&lt;&#x2F;span&gt;&lt;span&gt;#rust-fsnotify
&lt;&#x2F;span&gt;&lt;span&gt;#rust-gamedev
&lt;&#x2F;span&gt;&lt;span&gt;#rust-internals
&lt;&#x2F;span&gt;&lt;span&gt;#rust-lang
&lt;&#x2F;span&gt;&lt;span&gt;#rust-learners
&lt;&#x2F;span&gt;&lt;span&gt;#rust-libs
&lt;&#x2F;span&gt;&lt;span&gt;#rust-music
&lt;&#x2F;span&gt;&lt;span&gt;#rust-newspeak
&lt;&#x2F;span&gt;&lt;span&gt;#rust-osdev
&lt;&#x2F;span&gt;&lt;span&gt;#rust-politics
&lt;&#x2F;span&gt;&lt;span&gt;#rust-tls
&lt;&#x2F;span&gt;&lt;span&gt;#rust-tools
&lt;&#x2F;span&gt;&lt;span&gt;#rust-triage
&lt;&#x2F;span&gt;&lt;span&gt;#rust-tty
&lt;&#x2F;span&gt;&lt;span&gt;#rust-war
&lt;&#x2F;span&gt;&lt;span&gt;#rust-webdev
&lt;&#x2F;span&gt;&lt;span&gt;#rust-workshop
&lt;&#x2F;span&gt;&lt;span&gt;#rust-ww
&lt;&#x2F;span&gt;&lt;span&gt;#rust_offtopic
&lt;&#x2F;span&gt;&lt;span&gt;#rustaudio
&lt;&#x2F;span&gt;&lt;span&gt;#servo
&lt;&#x2F;span&gt;&lt;span&gt;#winapi
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;3 social channels:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;#rust-chat
&lt;&#x2F;span&gt;&lt;span&gt;#rust-offtopic
&lt;&#x2F;span&gt;&lt;span&gt;#rust-offtopic-offtopic
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;9 apparently language- or location-specific channels:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;#rust-br
&lt;&#x2F;span&gt;&lt;span&gt;#rust-de
&lt;&#x2F;span&gt;&lt;span&gt;#rust-fr
&lt;&#x2F;span&gt;&lt;span&gt;#rust-hu
&lt;&#x2F;span&gt;&lt;span&gt;#rust-learners-de
&lt;&#x2F;span&gt;&lt;span&gt;#rust-nyc
&lt;&#x2F;span&gt;&lt;span&gt;#rust-ru
&lt;&#x2F;span&gt;&lt;span&gt;#rust-seattle
&lt;&#x2F;span&gt;&lt;span&gt;#rust.fi
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Good times</title>
        <published>2015-07-28T00:00:00+00:00</published>
        <updated>2015-07-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/28/good_times/"/>
        <id>https://edunham.net/2015/07/28/good_times/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/28/good_times/">&lt;p&gt;People sometimes say &quot;morning&quot; or &quot;evening&quot; on IRC for a time zone unlike my own. Here&#x27;s a bash one-liner that emits the correct time-of-day generalization based on the datetime settings of the machine you run it on.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;case $(($(date +%H)&#x2F;6)) in 0|1)m=&amp;quot;morning&amp;quot;;;2)m=&amp;quot;afternoon&amp;quot;;;3)m=&amp;quot;night&amp;quot;;;esac; echo good $m
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;how&quot;&gt;How?&lt;&#x2F;h1&gt;
&lt;p&gt;First, check if the feature is already implemented. &lt;code&gt;man date&lt;&#x2F;code&gt; and try not to giggle. Search for &lt;code&gt;morning&lt;&#x2F;code&gt;. It&#x27;s not there.&lt;&#x2F;p&gt;
&lt;p&gt;So we need a &lt;a href=&quot;http:&#x2F;&#x2F;tldp.org&#x2F;LDP&#x2F;Bash-Beginners-Guide&#x2F;html&#x2F;sect_07_03.html&quot;&gt;switch&#x2F;case&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;case EXPRESSION in CASE1) COMMAND-LIST;; CASE2) COMMAND-LIST;; ... CASEN) COMMAND-LIST;; esac
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the expression we&#x27;re switching on will be the current hour:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;date +%H
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;My first attempt &lt;strong&gt;does not work&lt;&#x2F;strong&gt; because I expect too much of Bash:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;case $(date +%H) in [0-12]) m=&amp;quot;morning&amp;quot;;;[13-18]) m=&amp;quot;afternoon&amp;quot;;;[19-21])m=&amp;quot;evening&amp;quot;;;*)m=&amp;quot;night&amp;quot;;;esac; echo $m
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It fails because the &quot;ranges&quot; are actually just &lt;a href=&quot;http:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;bash&#x2F;manual&#x2F;bashref.html#Pattern-Matching&quot;&gt;shell patterns&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I could either expand my script to handle all hours, or compress ranges of hours down into something that can be expressed by patterns. The latter sounds shorter and easier. I want to divide the current hour by 6, to tell which quarter of the day I&#x27;m in.&lt;&#x2F;p&gt;
&lt;p&gt;A bit of trial and error reveals that a syntax that allows me to do math on the result of &lt;code&gt;date&lt;&#x2F;code&gt; is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$(( $(date +%H)&#x2F;6 ))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;because it&#x27;s shorthand for assigning the result of the math into a variable and using it immediately. This only adds a few characters to the one-liner:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;case $(($(date +%H)&#x2F;6)) in 0|1)m=&amp;quot;morning&amp;quot;;;2)m=&amp;quot;afternoon&amp;quot;;;3)m=&amp;quot;night&amp;quot;;;esac; echo $m
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s it!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Printing</title>
        <published>2015-07-20T00:00:00+00:00</published>
        <updated>2015-07-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/20/printing/"/>
        <id>https://edunham.net/2015/07/20/printing/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/20/printing/">&lt;p&gt;The office printers have instructions for setting them up under Windows, Mac, and Ubuntu. I had forgotten how to wrangle printers, since the last time I had to set up new ones was half a decade ago when I first joined the OSL.&lt;&#x2F;p&gt;
&lt;p&gt;Setting up printers on Arch is easy once you know the right incantations, but can waste some time if you try to do it by skimming the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;CUPS&quot;&gt;huge wiki page&lt;&#x2F;a&gt; rather than either reading it thoroughly or just following these steps:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Install the CUPS client&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ yaourt -S libcups
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Add a magic line to &#x2F;etc&#x2F;cups&#x2F;cups-files.conf&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;SystemGroup username
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With your username on the system, assuming you have root and will log in as yourself in the dialog it prompts for. That line can go anywhere in the file.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Make the daemon go&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo systemctl enable org.cups.cupsd.service
&lt;&#x2F;span&gt;&lt;span&gt;$ sudo systemctl start org.cups.cupsd.service
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Visit the web interface&lt;&#x2F;strong&gt; at &lt;a href=&quot;http:&#x2F;&#x2F;localhost:631&#x2F;&quot;&gt;http:&#x2F;&#x2F;localhost:631&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Then you have a GUI sufficiently similar to the one in the instructions for Ubuntu!&lt;&#x2F;p&gt;
&lt;p&gt;There is no GUI client for CUPS to install. If you find yourself mucking about with &lt;code&gt;gpr&lt;&#x2F;code&gt;, &lt;code&gt;xpp&lt;&#x2F;code&gt;, &lt;code&gt;kdeprint&lt;&#x2F;code&gt;, or &lt;code&gt;&#x2F;etc&#x2F;cups&#x2F;client.conf&lt;&#x2F;code&gt;, you have gone way too far down the wrong rabbit hole.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Outage postmortem: Replacing Rust Buildbot&#x27;s outdated cert</title>
        <published>2015-07-17T00:00:00+00:00</published>
        <updated>2015-07-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/17/replacing_buildbot_s_outdated_cert/"/>
        <id>https://edunham.net/2015/07/17/replacing_buildbot_s_outdated_cert/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/17/replacing_buildbot_s_outdated_cert/">&lt;p&gt;At the end of the day on July 14th, 2015, the certificate that Rust&#x27;s buildbot slaves were using to communicate with the buildmaster expired. This &lt;a href=&quot;https:&#x2F;&#x2F;internals.rust-lang.org&#x2F;t&#x2F;buildbot-is-down-for-a-bit&#x2F;2365&quot;&gt;broke things&lt;&#x2F;a&gt;. The problem started at midnight on July 15th, and was only fully resolved at the end of July 16th. Much of the reason for this outage&#x27;s duration was that I was learning about Buildbot as I went along.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how the outage got resolved, just in case anyone (especially future-me) finds themself Googling a similar problem.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;&#x2F;h1&gt;
&lt;p&gt;Dave Huseby pointed out the problem on IRC when the slaves that he runs were unable to connect to the buildmaster:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;16:48:23 &amp;lt;&amp;amp;brson&amp;gt; edunham: dhuseby said this earlier &amp;lt;huseby&amp;gt; it seems like the verify=3 in the stunnel config is the problem
&lt;&#x2F;span&gt;&lt;span&gt;16:48:39 &amp;lt;&amp;amp;brson&amp;gt; if he changed &amp;#39;verify&amp;#39; to some other value in the stunnel config it worked
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A quick check fo the &lt;a href=&quot;https:&#x2F;&#x2F;www.stunnel.org&#x2F;howto.html&quot;&gt;stunnel docs&lt;&#x2F;a&gt; shows that &lt;code&gt;verify=3&lt;&#x2F;code&gt; is the strictest setting, and will fail if the locally installed cert isn&#x27;t right. This supports the hypothesis that our cert might be expired. On the buildmaster, I found the cert and examined its metadata:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ find . -type f -name &amp;quot;*.pem&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;$ openssl x509 -noout -issuer -subject -dates -in certname.pem 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On the old cert, the results contained:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ openssl x509 -noout -issuer -subject -dates -in rust-bot-cert.pem 
&lt;&#x2F;span&gt;&lt;span&gt;issuer= &#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    O=Rust Project&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    OU=Bot&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    CN=bot.rust-lang.org&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    emailAddress=admin@rust-lang.org
&lt;&#x2F;span&gt;&lt;span&gt;subject= &#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    O=Rust Project&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    OU=Bot&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    CN=bot.rust-lang.org&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    emailAddress=admin@rust-lang.org
&lt;&#x2F;span&gt;&lt;span&gt;notBefore=Jul 14 02:28:50 2012 GMT
&lt;&#x2F;span&gt;&lt;span&gt;notAfter=Jul 14 02:28:50 2015 GMT
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This tells me that the cert was created in 2012 and had its expiry set for the seemingly distant future of 2015.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;make-a-new-cert&quot;&gt;Make a New Cert&lt;&#x2F;h1&gt;
&lt;p&gt;To determine whether the old key had a passphrase on it, go &lt;code&gt;openssl rsa -check -in keyname.pem&lt;&#x2F;code&gt;. It writes the private key to your terminal if there&#x27;s no password, or prompt for a passphrase if the key has one.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;www.stunnel.org&#x2F;howto.html&quot;&gt;stunnel docs&lt;&#x2F;a&gt; give most of the relevant incantation. Since no file in our buildbot directory is named precisely &lt;code&gt;stunnel.conf&lt;&#x2F;code&gt;, &lt;code&gt;make cert&lt;&#x2F;code&gt; doesn&#x27;t quite work right. But it works fine to manually run a variant of the command given in the docs:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ openssl req -new -x509 -days 3650 -nodes -out cert.pem -keyout key.pem
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That prompted me for a variety of information, which I entered where applicable and left blank where it wasn&#x27;t. The metadata is primarily for the benefit of others verifying that a cert belongs to the correct person, which isn&#x27;t a relevant concern in our use case.&lt;&#x2F;p&gt;
&lt;p&gt;I then backed up the old key and cert (although they&#x27;re no longer usable, they contain a bunch of metadata that I didn&#x27;t know whether I&#x27;d need later) and moved the new key and cert to match the old ones&#x27; original file names.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, I &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust-buildbot&#x2F;pull&#x2F;21&quot;&gt;updated the repository&lt;&#x2F;a&gt; with the new certificate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;make-buildbot-spin-up-amis-with-the-new-cert&quot;&gt;Make Buildbot spin up AMIs with the new cert&lt;&#x2F;h2&gt;
&lt;p&gt;This was the tricky bit. Since the slave image does not pull updates to its copy of the Rust Buildbot github repo when it boots, the file had to be statically edited and then the AMIs re-saved. But Buildbot makes its instance requests based on AMI ID, and the IDs are unique to a particular image. So the workflow goes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Figure out which AMI Buildbot will spin up for a given job&lt;&#x2F;li&gt;
&lt;li&gt;Spin up an instance of the AMI&lt;&#x2F;li&gt;
&lt;li&gt;Remote into it and manually update the cert&lt;&#x2F;li&gt;
&lt;li&gt;Save the instance into a new AMI, noting its ID&lt;&#x2F;li&gt;
&lt;li&gt;Update Buildbot&#x27;s configuration with the new ID&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;figure-out-which-ami-buildbot-will-use&quot;&gt;Figure out which AMI Buildbot will use&lt;&#x2F;h1&gt;
&lt;p&gt;The AMI IDs used for spot requests are stored in &lt;code&gt;&#x2F;home&#x2F;rustbuild&#x2F;rust-buildbot&#x2F;master&#x2F;slave-list.txt&lt;&#x2F;code&gt; on the buildmaster. From that file I determined that we only had 4 unique AMIs in use:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ami-b74fa1f3 -- windows
&lt;&#x2F;span&gt;&lt;span&gt;ami-7fd23e3b -- generic linux 
&lt;&#x2F;span&gt;&lt;span&gt;ami-381e197d -- android
&lt;&#x2F;span&gt;&lt;span&gt;ami-dbac5f9f -- centos5 (builds snapshots that work with ancient Linux)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The slave list also told me that all requests would be made for instance type &lt;code&gt;c3.2xlarge&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;spin-up-an-instance-of-the-ami&quot;&gt;Spin up an instance of the AMI&lt;&#x2F;h1&gt;
&lt;p&gt;Since there were only 4, I did this manually. If it was a recurring task or there had been more AMIs, I would have automated this part of the process.&lt;&#x2F;p&gt;
&lt;p&gt;In the AWS Console, go to EC2, then click the AMIs link under &quot;Images&quot; at the left.&lt;&#x2F;p&gt;
&lt;p&gt;Search for the AMI ID in the search box. Only one AMI is found, because IDs are unique. Then click the big blue Launch button up at the left.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;aws-ami-launch.png&quot; alt=&quot;AWS EC2 console showing AMI search and launch button&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The only &quot;gotcha&quot; in the ensuing 7-step process is making sure to put the instance into the correct security group. I spun my temporary instances up into the same group as a host I know I can get to from the Bastion server with my credentials, to reduce the number of steps I&#x27;d have to troubleshoot if they were difficult to access.&lt;&#x2F;p&gt;
&lt;p&gt;It also helps to tag the spot request with the name of the AMI it was created from, when processing several at once.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;remote-into-the-instance-and-update-the-cert&quot;&gt;Remote into the instance and update the cert&lt;&#x2F;h1&gt;
&lt;p&gt;For Linux-flavored instances, this just meant &lt;code&gt;ssh rustbuild@00.00.00.00&lt;&#x2F;code&gt; (using the instance&#x27;s public IP, visible in the main instances list) from the Bastion. I then found the old cert on the host, and verified that it matched the old cert on the buildmaster. Checking that the certs matched was as simple as running &lt;code&gt;md5sum cert.pem&lt;&#x2F;code&gt; on both and visually comparing the results, and reassured me that I was overwriting the correct file.&lt;&#x2F;p&gt;
&lt;p&gt;Getting into the Windows hosts requires &lt;a href=&quot;http:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;AWSEC2&#x2F;latest&#x2F;WindowsGuide&#x2F;connecting_to_windows_instance.html&quot;&gt;using RDP&lt;&#x2F;a&gt; after setting up an SSH tunnel to the bastion. Since I was on airport wifi at the time, I had a teammate stick the cert onto the Windows instances instead.&lt;&#x2F;p&gt;
&lt;p&gt;The cert that &lt;code&gt;stunnel&lt;&#x2F;code&gt; actually uses on a Windows host with our configurations actually lives at &lt;code&gt;C:\Program Files (x86)\stunnel\cert.pem&lt;&#x2F;code&gt;, not in the actual repo like on all the sensible operating systems. Although there exists a &lt;code&gt;C:\bot\cert.pem&lt;&#x2F;code&gt;, replacing it does not cause &lt;code&gt;stunnel&lt;&#x2F;code&gt; to connect successfully.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;save-the-instance-into-a-new-ami&quot;&gt;Save the instance into a new AMI&lt;&#x2F;h1&gt;
&lt;p&gt;Check the box by the instance&#x27;s name in the EC2 instances list, then follow the menus around for Actions -&amp;gt; Image -&amp;gt; Create Image. Note the new AMI&#x27;s ID, and replace all instances of the old AMI&#x27;s ID with the new one in the buildmaster&#x27;s &lt;code&gt;slave-list.txt&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;kick-buildbot-a-bit&quot;&gt;Kick Buildbot a bit&lt;&#x2F;h2&gt;
&lt;p&gt;After the AMIs and FreeBSD, Bitrig, and Mac builders all had the new cert, I restarted Buildbot on the buildmaster and reran its script for creating the stunnels. Althoug it didn&#x27;t gracefully pick up where it had left off on partially built pull requests, closing then re-opening the PRs caused it to notice them and resume building successfully.&lt;&#x2F;p&gt;
&lt;p&gt;Hopefully TaskCluster gets OSX support soon, so we can start switching off of Buildbot.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;prevent-it-from-happening-again&quot;&gt;Prevent it from happening again&lt;&#x2F;h1&gt;
&lt;p&gt;After I first published this post, &lt;a href=&quot;http:&#x2F;&#x2F;www.gerv.net&quot;&gt;Gerv&lt;&#x2F;a&gt; pointed out that the correct final step would be &quot;Add an alarm to the shared IT calendar for a month before the new cert expires&quot;. In my case, the analog to that alarm is &quot;Make sure we move away from Buildbot in less than a decade&quot;. However, if you&#x27;re reading this post to solve a similar problem in an infrastructure that will still exist at the date of the cert&#x27;s expiry, you should automate a reminder so that you or your successor doesn&#x27;t get the same unpleasant surprise.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Airport Wifi</title>
        <published>2015-07-16T00:00:00+00:00</published>
        <updated>2015-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/16/airport_wifi/"/>
        <id>https://edunham.net/2015/07/16/airport_wifi/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/16/airport_wifi/">&lt;p&gt;Many &quot;free&quot; wifi hotspots give you a limited time per computer. If you&#x27;re traveling light and forgot to bring extra devices, it&#x27;s easy to give a Linux laptop multiple personalities:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ip link
&lt;&#x2F;span&gt;&lt;span&gt;    1: lo
&lt;&#x2F;span&gt;&lt;span&gt;    2: wlp4s0
&lt;&#x2F;span&gt;&lt;span&gt;    3: enp0s25
&lt;&#x2F;span&gt;&lt;span&gt;$ ip link set dev wlp4s0 down
&lt;&#x2F;span&gt;&lt;span&gt;$ macchanger -r wlp4s0
&lt;&#x2F;span&gt;&lt;span&gt;$ ip link set dev wlp4s0 up
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;... And then connect to the wifi and jump through its silly captive portal hoops again!&lt;&#x2F;p&gt;
&lt;p&gt;Changing your MAC address occasionally can be part of a healthy security diet, making your device slightly more difficult to track, as well.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Interactive Rust Examples in Static Pages</title>
        <published>2015-07-13T00:00:00+00:00</published>
        <updated>2015-07-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/13/interactive_rust_examples_in_static_pages/"/>
        <id>https://edunham.net/2015/07/13/interactive_rust_examples_in_static_pages/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/13/interactive_rust_examples_in_static_pages/">&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;rustbyexample.com&#x2F;hello&#x2F;print.html&quot;&gt;Rust by Example&lt;&#x2F;a&gt; has a little box where readers can interact with some example Rust code, run it using the &lt;a href=&quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;&quot;&gt;playground&lt;&#x2F;a&gt;, and see the results in the page. As a sysadmin I&#x27;m loath to recommend that anybody trust the playground for anything, but as a nerd and coder I recognize that it&#x27;s super cool and people want to use it.&lt;&#x2F;p&gt;
&lt;p&gt;There are 2 ways to stuff a Playground into your website: The easy way, and the &quot;right&quot; way. Here&#x27;s how to do it the easy way, and where to look for examples of the hard way.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-easy-way&quot;&gt;The Easy Way&lt;&#x2F;h1&gt;
&lt;p&gt;There are these cool things called &lt;a href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Mozilla&#x2F;Tech&#x2F;XUL&#x2F;iframe&quot;&gt;iframes&lt;&#x2F;a&gt; that basically let you put websites in your websites...&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;yo-dawg-iframes.jpg&quot; alt=&quot;Xzibit &amp;quot;Yo dawg&amp;quot; meme about putting iframes in websites&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;All you have to do is stick this one line in your page&#x27;s source, and modern browsers will go load and inject the specified page -- in this case, the playpen. The exact technique for injecting raw HTML will differ based on your blogging platform. With Tinkerer, the source will look like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;.. raw:: html 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;iframe src=&amp;quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;&amp;quot; style=&amp;quot;width:100%; height:400px;&amp;quot;&amp;gt;&amp;lt;&#x2F;iframe&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;ll render with the default contents of the playpen. Note that it&#x27;ll someties try to be clever and load the code that the viewer most recently opened in it when loaded with no arguments:&lt;&#x2F;p&gt;
&lt;iframe src=&quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;&quot; style=&quot;width:100%; height:400px;&quot;&gt;&lt;&#x2F;iframe&gt;
&lt;h1 id=&quot;custom-code-in-the-playpen&quot;&gt;Custom Code in the Playpen&lt;&#x2F;h1&gt;
&lt;p&gt;When you load the Playpen, you can specify the ID of a &lt;a href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;&quot;&gt;gist&lt;&#x2F;a&gt; whose contents should appear in the text area. Let&#x27;s say we&#x27;re doing a simple hello world:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; This code is editable and runnable!
&lt;&#x2F;span&gt;&lt;span&gt;fn main() {
&lt;&#x2F;span&gt;&lt;span&gt;    println!(&amp;quot;Hello from edunham&amp;#39;s blog!&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I &lt;a href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;edunham&#x2F;df9d98db4c29089e93b8&quot;&gt;uploaded it&lt;&#x2F;a&gt; and noted the unique hash on the URL, &lt;code&gt;df9d98db4c29089e93b8&lt;&#x2F;code&gt;. That&#x27;s the magic number that I&#x27;ll stick on the end of the playpen link to see the gist, like &lt;a href=&quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;?gist=df9d98db4c29089e93b8&quot;&gt;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;?gist=df9d98db4c29089e93b8&lt;&#x2F;a&gt;. Here&#x27;s the raw HTML for an iframe that loads the playpen with the contents of that gist:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&amp;lt;iframe src=&amp;quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;?gist=df9d98db4c29089e93b8&amp;quot; style=&amp;quot;width:100%; height:400px;&amp;quot;&amp;gt;&amp;lt;&#x2F;iframe&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;ll render like this:&lt;&#x2F;p&gt;
&lt;iframe src=&quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;?gist=df9d98db4c29089e93b8&quot; style=&quot;width:100%; height:400px;&quot;&gt;&lt;&#x2F;iframe&gt;
&lt;h1 id=&quot;the-hard-way&quot;&gt;The Hard Way&lt;&#x2F;h1&gt;
&lt;p&gt;I also tried pulling out the requisite HTML, Javascript, and CSS from &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust-www&quot;&gt;the main Rust site&lt;&#x2F;a&gt;. If I come back later and get it working, I&#x27;ll update this post with instructions, but till then the iframe technique is substantially easier. If you get it working the way the main site did using a Sphinx-based blogging platform, please let me know!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;thanks&quot;&gt;Thanks&lt;&#x2F;h1&gt;
&lt;p&gt;As usual in my web-related endeavors, I accidentally &lt;a href=&quot;https:&#x2F;&#x2F;imgs.xkcd.com&#x2F;comics&#x2F;nerd_sniping.png&quot;&gt;nerd-sniped&lt;&#x2F;a&gt; &lt;a href=&quot;http:&#x2F;&#x2F;www.mythmon.com&#x2F;&quot;&gt;Mythmon&lt;&#x2F;a&gt; with this problem, and he spent quite a while helping me troubleshoot it and teaching me about Firefox&#x27;s developer tools.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Rust&#x27;s Packaging Status Across Distros</title>
        <published>2015-07-07T00:00:00+00:00</published>
        <updated>2015-07-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/07/rust_packaging_status_across_distros/"/>
        <id>https://edunham.net/2015/07/07/rust_packaging_status_across_distros/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/07/rust_packaging_status_across_distros/">&lt;p&gt;One of many questions facing the Rust infrastructure team right now is &quot;What&#x27;s our packaging situation?&quot;. We don&#x27;t have a centralized source of information on what version of Rust is available in which systems&#x27; package managers, and we don&#x27;t even know where to find that information.&lt;&#x2F;p&gt;
&lt;p&gt;This post is the notes I&#x27;ve taken in researching Rust&#x27;s packaging status across distributions.&lt;&#x2F;p&gt;
&lt;p&gt;I last updated this post on 8&#x2F;17&#x2F;2015.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;in-summary&quot;&gt;In Summary&lt;&#x2F;h1&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Distro&lt;&#x2F;th&gt;&lt;th&gt;Rust&#x27;s Status&lt;&#x2F;th&gt;&lt;th&gt;Cargo&#x27;s Status&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Debian&lt;&#x2F;td&gt;&lt;td&gt;1.0.0 in unstable&lt;&#x2F;td&gt;&lt;td&gt;Requested&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Ubuntu&lt;&#x2F;td&gt;&lt;td&gt;Community package&lt;&#x2F;td&gt;&lt;td&gt;Community package, bad docs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Gentoo&lt;&#x2F;td&gt;&lt;td&gt;1.1.0 in index&lt;&#x2F;td&gt;&lt;td&gt;Community package&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;OpenSUSE&lt;&#x2F;td&gt;&lt;td&gt;1.0.0 in index&lt;&#x2F;td&gt;&lt;td&gt;Recent version in index&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Arch&lt;&#x2F;td&gt;&lt;td&gt;multirust in index&lt;&#x2F;td&gt;&lt;td&gt;2 community packages; 1 bad&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Fedora&lt;&#x2F;td&gt;&lt;td&gt;Community package&lt;&#x2F;td&gt;&lt;td&gt;No separate package&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Slackware*&lt;&#x2F;td&gt;&lt;td&gt;Community package&lt;&#x2F;td&gt;&lt;td&gt;No separate package&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Windows&lt;&#x2F;td&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;chocolatey.org&#x2F;packages&#x2F;rust&#x2F;1.0.0-alpha2&quot;&gt;in chocolatey&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;No separate package&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Mac&lt;&#x2F;td&gt;&lt;td&gt;&lt;a href=&quot;http:&#x2F;&#x2F;braumeister.org&#x2F;formula&#x2F;rust&quot;&gt;in Brew&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;No separate package&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;FreeBSD&lt;&#x2F;td&gt;&lt;td&gt;Community port (?)&lt;&#x2F;td&gt;&lt;td&gt;No separate package&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;There are an unreasonable number of tiny Linux distributions out there, but they fall into a handful of &quot;families&quot;. For a visualization of the history of various distros, check out this &lt;a href=&quot;https:&#x2F;&#x2F;upload.wikimedia.org&#x2F;wikipedia&#x2F;commons&#x2F;1&#x2F;1b&#x2F;Linux_Distribution_Timeline.svg&quot;&gt;map&lt;&#x2F;a&gt;. I&#x27;ve elided CentOS and RHEL as &quot;flavors of Fedora&quot;, and a bunch of popular new Ubuntu flavors (ElementaryOS, Mint, etc.) as &quot;compatible with Ubuntu packages, and probably smart enough to Google for Ubuntu docs if there aren&#x27;t any specific to their OS&quot;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;debian&quot;&gt;Debian&lt;&#x2F;h1&gt;
&lt;p&gt;Debian, Ubuntu, Kubuntu, Mint, and Knoppix all use &lt;code&gt;.deb&lt;&#x2F;code&gt; packages installed through package managers that wrap the &lt;code&gt;dpkg&lt;&#x2F;code&gt; tool.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;packages.debian.org&#x2F;stable&#x2F;&quot;&gt;Debian Stable&lt;&#x2F;a&gt; package list has dedicated sections for some languages, but Rust is too young to be among them. Rust does not appear in the &lt;a href=&quot;https:&#x2F;&#x2F;packages.debian.org&#x2F;stable&#x2F;allpackages&quot;&gt;Jessie stable package list&lt;&#x2F;a&gt;, the &lt;a href=&quot;https:&#x2F;&#x2F;packages.debian.org&#x2F;testing&#x2F;allpackages&quot;&gt;Debian testing package list&lt;&#x2F;a&gt; or the &lt;a href=&quot;https:&#x2F;&#x2F;packages.debian.org&#x2F;unstable&#x2F;allpackages&quot;&gt;Debian unstable package list&lt;&#x2F;a&gt;. Debian &lt;a href=&quot;https:&#x2F;&#x2F;bugs.debian.org&#x2F;cgi-bin&#x2F;bugreport.cgi?bug=786432&quot;&gt;bug #786432&lt;&#x2F;a&gt; is a request for Cargo to be packaged, which describes what Cargo will need to do to be packageable. &lt;a href=&quot;https:&#x2F;&#x2F;bugs.debian.org&#x2F;cgi-bin&#x2F;bugreport.cgi?bug=689207&quot;&gt;Bug #689207&lt;&#x2F;a&gt; tracks the status of packaging &lt;code&gt;rustc&lt;&#x2F;code&gt; for Debian.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a &lt;a href=&quot;https:&#x2F;&#x2F;packages.debian.org&#x2F;unstable&#x2F;main&#x2F;rustc&quot;&gt;rustc package in Debian Unstable&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The Debian Wiki has a &lt;a href=&quot;https:&#x2F;&#x2F;wiki.debian.org&#x2F;Teams&#x2F;RustPackaging&quot;&gt;rust packaging page&lt;&#x2F;a&gt; to track the status of packaging Rust for Debian.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ubuntu&quot;&gt;Ubuntu&lt;&#x2F;h1&gt;
&lt;p&gt;The official &lt;a href=&quot;http:&#x2F;&#x2F;packages.ubuntu.com&#x2F;&quot;&gt;Ubuntu&lt;&#x2F;a&gt; package repo has no mention of Rust or Cargo in the &lt;a href=&quot;http:&#x2F;&#x2F;packages.ubuntu.com&#x2F;trusty&#x2F;allpackages?format=txt.gz&quot;&gt;trusty&lt;&#x2F;a&gt; or &lt;a href=&quot;http:&#x2F;&#x2F;packages.ubuntu.com&#x2F;vivid&#x2F;allpackages?format=txt.gz&quot;&gt;vivid&lt;&#x2F;a&gt; package lists.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;http:&#x2F;&#x2F;www.rust-ci.org&#x2F;help&#x2F;&quot;&gt;rust-ci.org help&lt;&#x2F;a&gt; tells users to install Cargo from &lt;a href=&quot;https:&#x2F;&#x2F;launchpad.net&#x2F;~cmrx64&#x2F;+archive&#x2F;ubuntu&#x2F;cargo&quot;&gt;Corey Richardson&#x27;s PPA&lt;&#x2F;a&gt; (last updated July 2014, build currently failing) and Rust from &lt;a href=&quot;https:&#x2F;&#x2F;launchpad.net&#x2F;~hansjorg&#x2F;+archive&#x2F;ubuntu&#x2F;rust&quot;&gt;Hans Hoel&#x27;s PPA&lt;&#x2F;a&gt;, in which &lt;code&gt;rust-stable&lt;&#x2F;code&gt;, &lt;code&gt;rust-nightly&lt;&#x2F;code&gt;, and &lt;code&gt;cargo-nightly&lt;&#x2F;code&gt; appear to be up to date.&lt;&#x2F;p&gt;
&lt;p&gt;Googling for how to install Rust on Ubuntu also turns up a &lt;a href=&quot;https:&#x2F;&#x2F;www.vultr.com&#x2F;docs&#x2F;installing-rust-on-ubuntu-14-04&quot;&gt;page on vultr.com&lt;&#x2F;a&gt; directing users to intsall Rust using &lt;code&gt;rustup.sh&lt;&#x2F;code&gt;. &lt;a href=&quot;http:&#x2F;&#x2F;www.randomhacks.net&#x2F;2014&#x2F;05&#x2F;30&#x2F;rust-on-ubuntu-10.04-lucid&#x2F;&quot;&gt;Another&lt;&#x2F;a&gt; of the top hits also instructs users to use &lt;code&gt;rustup.sh&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;gentoo&quot;&gt;Gentoo&lt;&#x2F;h1&gt;
&lt;p&gt;There&#x27;s a Rust 1.1.0 package in the &lt;a href=&quot;https:&#x2F;&#x2F;packages.gentoo.org&#x2F;package&#x2F;dev-lang&#x2F;rust&quot;&gt;Gentoo package index&lt;&#x2F;a&gt;. On GitHub, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Heather&#x2F;gentoo-rust&quot;&gt;Heather&lt;&#x2F;a&gt; has packaged a Rust overlay for Gentoo and appears to be actively developing it, although the build is currently failing.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a &lt;a href=&quot;http:&#x2F;&#x2F;gpo.zugaina.org&#x2F;dev-rust&#x2F;cargo&quot;&gt;cargo portage overlay&lt;&#x2F;a&gt; but cargo is not provided through the package index.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;opensuse&quot;&gt;OpenSUSE&lt;&#x2F;h1&gt;
&lt;p&gt;Rust 1.0.0 is &lt;a href=&quot;http:&#x2F;&#x2F;software.opensuse.org&#x2F;package&#x2F;rust&quot;&gt;packaged for OpenSUSE&lt;&#x2F;a&gt;. &lt;a href=&quot;http:&#x2F;&#x2F;duncan.mac-vicar.com&#x2F;2014&#x2F;01&#x2F;16&#x2F;trying-rust-language-on-opensuse&#x2F;&quot;&gt;Duncan Mac-Vicar&lt;&#x2F;a&gt; has a blog post on trying Rust on OpenSUSE from 2014. My search for &quot;How to install Rust on OpenSUSE&quot; also turned up a &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=_z1M0uHY4So&quot;&gt;youtube video&lt;&#x2F;a&gt; on installing Rust from the nightly tarball. It seems to just be a walkthrough of what happens when you follow Rust&#x27;s installation guidelines, but kudos to the OpenSUSE community for catering to diverse learning styles.&lt;&#x2F;p&gt;
&lt;p&gt;An &lt;a href=&quot;http:&#x2F;&#x2F;software.opensuse.org&#x2F;package&#x2F;cargo&quot;&gt;OpenSUSE Cargo package&lt;&#x2F;a&gt; is also available in what appears to be the main index.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;arch&quot;&gt;Arch&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Rust&quot;&gt;The Arch Wiki&lt;&#x2F;a&gt; has a very useful page on Rust that includes instructions on cross-compiling. There&#x27;s also a &lt;a href=&quot;https:&#x2F;&#x2F;www.archlinux.org&#x2F;packages&#x2F;?name=rust&quot;&gt;community package&lt;&#x2F;a&gt; of rust 1.1.0. The Arch User Repository (AUR) provides Cargo through a choice of &lt;a href=&quot;https:&#x2F;&#x2F;aur.archlinux.org&#x2F;packages&#x2F;cargo-bin&#x2F;&quot;&gt;cargo-bin&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;aur.archlinux.org&#x2F;packages&#x2F;cargo-git&#x2F;&quot;&gt;cargo-git&lt;&#x2F;a&gt;, though the latter hasn&#x27;t been updated since February.&lt;&#x2F;p&gt;
&lt;p&gt;I later learned that you need to install the &lt;a href=&quot;https:&#x2F;&#x2F;aur.archlinux.org&#x2F;packages&#x2F;multirust&#x2F;&quot;&gt;multirust&lt;&#x2F;a&gt; package to get Rust and Cargo.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;fedora&quot;&gt;Fedora&lt;&#x2F;h1&gt;
&lt;p&gt;Fedora has this &lt;a href=&quot;https:&#x2F;&#x2F;copr.fedoraproject.org&#x2F;coprs&#x2F;&quot;&gt;copr&lt;&#x2F;a&gt; build system thing in which one apparently finds community packages. At least, I found &lt;a href=&quot;https:&#x2F;&#x2F;copr.fedoraproject.org&#x2F;coprs&#x2F;fabiand&#x2F;rust-binary&#x2F;&quot;&gt;rust-binary&lt;&#x2F;a&gt; (poorly documented and last built 6 months ago) and &lt;a href=&quot;https:&#x2F;&#x2F;copr.fedoraproject.org&#x2F;coprs&#x2F;fabiand&#x2F;rust-unofficial&#x2F;&quot;&gt;rust-unofficial&lt;&#x2F;a&gt; (last built 18 months ago) in it. A blog post about &lt;a href=&quot;http:&#x2F;&#x2F;minhdo.org&#x2F;posts&#x2F;2013-07-27-building-rust-on-fedora.html&quot;&gt;building Rust on Fedora&lt;&#x2F;a&gt;, posted in 2013 but with good SEO for queries about &quot;how to install Rust on Fedora&quot;, recommends building from source. There&#x27;s no &lt;code&gt;rust&lt;&#x2F;code&gt; or &lt;code&gt;cargo&lt;&#x2F;code&gt; in the &lt;a href=&quot;http:&#x2F;&#x2F;mirror.centos.org&#x2F;centos&#x2F;6&#x2F;os&#x2F;x86_64&#x2F;Packages&#x2F;&quot;&gt;CentOS 6 package list&lt;&#x2F;a&gt;, either.&lt;&#x2F;p&gt;
&lt;p&gt;Fedora is tracking the official effort to package &lt;code&gt;rustc&lt;&#x2F;code&gt; in redhat bug #915042.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;25728336&#x2F;can-you-build-rust-for-old-redhat-5-vintage-linux&quot;&gt;A stackoverflow question&lt;&#x2F;a&gt; from late last year asked about how to build Rust for Redhat 5. The &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dockingbay&#x2F;fedora-rust&quot;&gt;fedora-rust&lt;&#x2F;a&gt; repo on GitHub provides a sporadically-updated Docker image with Rust installed.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;freebsd&quot;&gt;FreeBSD&lt;&#x2F;h1&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;wiki.freebsd.org&#x2F;Rust&quot;&gt;FreeBSD Wiki&lt;&#x2F;a&gt; tracks the status of the Rust port. The &lt;a href=&quot;http:&#x2F;&#x2F;portsmon.freebsd.org&#x2F;portoverview.py?category=lang&amp;amp;portname=rust&quot;&gt;port overview page&lt;&#x2F;a&gt; tracks which version is available, which is the latest Rust version available in July 2015. There&#x27;s also a &lt;a href=&quot;https:&#x2F;&#x2F;internals.rust-lang.org&#x2F;t&#x2F;rust-on-freebsd&#x2F;2132&quot;&gt;Rust on FreeBSD&lt;&#x2F;a&gt; internals thread that tracks other relevant information and tools.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;slackware&quot;&gt;Slackware*&lt;&#x2F;h1&gt;
&lt;p&gt;According to the &lt;a href=&quot;https:&#x2F;&#x2F;brashear.me&#x2F;blog&#x2F;2014&#x2F;05&#x2F;18&#x2F;results-of-the-2014-slash-r-slash-linux-distribution-survey&#x2F;&quot;&gt;2014 r&#x2F;linux survey&lt;&#x2F;a&gt;, 53 of the 10,292 respondants used Slackware on a desktop or server. A variety of search queries for Rust and Slackware turn up Rust&#x27;s generic Linux installation docs as the first hits, and one &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;issues&#x2F;17474&quot;&gt;github issue from a slackware user&lt;&#x2F;a&gt;. Extensive digging reveals a Rust 1.1.0 package in the &lt;a href=&quot;http:&#x2F;&#x2F;slackbuilds.org&#x2F;repository&#x2F;14.1&#x2F;development&#x2F;rust&#x2F;&quot;&gt;SlackBuilds repository&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;* The fact that I researched whether anyone had packaged Rust for Slackware is in no way intended to imply any plans for ever supporting such a package. I was just curious. Really.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;thanks&quot;&gt;Thanks!&lt;&#x2F;h1&gt;
&lt;p&gt;I appreciate readers Huon Wilson and Seo Sanghyeon taking the time to point out useful links for this post! Both mentioned the presence of the Debian Unstable package, and Seo informed me of &lt;a href=&quot;https:&#x2F;&#x2F;bugzilla.redhat.com&#x2F;show_bug.cgi?id=915043&quot;&gt;redhat bug #915043&lt;&#x2F;a&gt; as well.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Mozilla Onboarding</title>
        <published>2015-07-06T00:00:00+00:00</published>
        <updated>2015-07-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/07/06/mozilla_onboarding/"/>
        <id>https://edunham.net/2015/07/06/mozilla_onboarding/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/07/06/mozilla_onboarding/">&lt;p&gt;On April 16th, I threw an application toward &lt;a href=&quot;https:&#x2F;&#x2F;web.archive.org&#x2F;web&#x2F;20150316234909&#x2F;https:&#x2F;&#x2F;careers.mozilla.org&#x2F;en-US&#x2F;position&#x2F;oymA0fwe&quot;&gt;this job posting&lt;&#x2F;a&gt; on careers.mozilla.org. I doubted whether I&#x27;d be qualified, but I reminded myself that most people apply to jobs where they meet only 80% of the critiera. I could, with some creative redefinition (&quot;of course an internship at Intel is a year of relevant experience!&quot;), meet every listed criterion. So I applied, since the worst they could say was no.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a rundown my experience getting interviewed and onboarded, which might be of interest to current Mozilla employees curious about how things have changed since they joined, and to anyone interested in working there.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;application&quot;&gt;Application&lt;&#x2F;h1&gt;
&lt;p&gt;The recruiter replied to my application only 3 hours after I submitted it. This was certainly an improvement since the 6 months of ultimately fruitless bureaucratic limbo when I applied for a Mozilla internship a few years ago!&lt;&#x2F;p&gt;
&lt;p&gt;I found out later that the 3-hour turnaround time included a member of the Servo team reading my resume and verifying that my background looked relevant to their interests, which makes it all the more impressive.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;phone-screens&quot;&gt;Phone Screens&lt;&#x2F;h1&gt;
&lt;p&gt;On April 20th, I had a quick phone chat with my recruiter. It was totally non-tecnical, and I summarized it to my friends afterward as &quot;Did you read your own resume? Did you read the job description? Ok, cool!&quot;. I&#x27;m sure they were screening for other stuff as well, perhaps basic social skills and an acceptable level of fluency in spoken English, but that screen was not nearly as scary as I&#x27;d been prepared for.&lt;&#x2F;p&gt;
&lt;p&gt;My first techncial phone screen was on 4&#x2F;22. A second recruiter organized the details of the call, and I talked with Brian Anderson about my background and the Rust programming language&#x27;s DevOps needs. He was particularly interested in my work with Chef and Jenkins in production at my Urban Airship job. After this phone screen, the title on the job posting got changed from &quot;Operations Engineer&quot; to &quot;DevOps Engineer&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;On 4&#x2F;24, I talked with Lars Bergstrom about the Servo project&#x27;s needs. Learning about the place that project was at compared to Rust started giving me some perspective into the similarities between the DevOps Engineer role and my work juggling systems administration for a variety of open source projects at the OSU Open Source Lab.&lt;&#x2F;p&gt;
&lt;p&gt;My final phone screen was with Jack Moffitt, on 4&#x2F;29. This conversation was less a review of my experiences and more a brainstorming session about techniques for speeding up the run time of Rust&#x27;s continuous integration tests. Although I wasn&#x27;t familiar with the specific scenario yet, I was able to apply my knowledge of CI and Git techniques to propose some possible solutions that the team hadn&#x27;t considered yet.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-on-site-interviews&quot;&gt;The On-Site Interviews&lt;&#x2F;h1&gt;
&lt;p&gt;I interviewd on-site at Mozilla&#x27;s San Francisco office on May 4th. My day was scheduled from 10am to 3pm, and I spoke with 5 different Mozilians from various teams. Ordinarily these interviews would have been scheduled at least a week later, but a friend and I had been planning a road trip down to the bay area anyway, so we shifted the dates a bit to fit with the interviews.&lt;&#x2F;p&gt;
&lt;p&gt;Again, I asked the recruiters whether they had any advice on what to expect from the whiteboarding component of the interviews, and again they gave no answer.&lt;&#x2F;p&gt;
&lt;p&gt;Two interviews were with managers higher up in Mozilla Research&#x27;s organizational hierarchy. One was very concerned about community and avoiding the problems of &quot;not invented here&quot;, and felt unfamiliar with most &quot;DevOps&quot; related terminology. In that interview, I drew pictures on the whiteboard to explain the similarities between public and private clouds, and we talked a lot about open source community. The other manager wasn&#x27;t hesitant to air her concerns about my relative lack of corporate experience, which gave me a chance to explain the applicability of my open source leadership background and OSU Open Source Lab experience to the DevOps Engineer role.&lt;&#x2F;p&gt;
&lt;p&gt;My technical interview with a Rust team member during my onsite interviews was more of a friendly chat about the infrastructure and how to improve its performance. After the first couple of interviews, I went to lunch with a variety of research and devops team members. We chatted about the infrastructure, and I enjoyed the chance to get to know my prospective coworkers in a less formal setting. I find it important to interact with people in a setting where it&#x27;s socially appropriate for them to complain about their jobs -- the nature of those complaints (or lack thereof) is one of the most useful cues about a project&#x27;s real culture rather than the polite facade it necessarily puts on for newcomers.&lt;&#x2F;p&gt;
&lt;p&gt;After lunch, I had the only interview that I&#x27;d consider particularly technical. My interviewer framed a question about designing an algorithm to copy a linked list in memory and asked me to describe a solution (psuedocode optional), rather than worrying about the details of writing in any specific language. The question had a trick to it and I would have solved it much faster if I&#x27;d seen it before, but my interviewer fed me hints and helped me stay on the right track. This collaborative approach made the interview feel like I was solving a real problem with a co-worker, rather than the &quot;we want to see what you&#x27;re like when we terrify you to the breaking point&quot; of some other companies&#x27; interview styles. The tone of that interview cemented my impression that Mozilla had the collaborative, non-malicious culture that I was looking for.&lt;&#x2F;p&gt;
&lt;p&gt;My final interview was with a TaskCluster developer. Since we&#x27;d already spoken at lunch (and apparently I&#x27;d made a sufficient impression of my technical skills), he told me &quot;Instead of an interview, I&#x27;m going to sell you TaskCluster&quot;. We then went through a slide deck on how TaskCluster is designed and its intent to replace BuildBot, and I got a lot of my introductory questions about the tool out of the way.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-offer&quot;&gt;The Offer&lt;&#x2F;h1&gt;
&lt;p&gt;On May 6th, I had a quick phone chat with my original recruiter to discuss compensation. I had already talked with a manager for my prospective role about Mozilla&#x27;s bonus and benefits package, which allowed me to do the math on what kind of base salary would result in a total compensation package comparable to the other offers I had on the table. I discussed these numbers and my reasoning behind them with my recruiter. When doing the math on base salary, I also privately calculated the ranges of offer for which I would want to try to negotiate higher, and what ranges I&#x27;d be happy to simply accept without haggling.&lt;&#x2F;p&gt;
&lt;p&gt;On May 8th, we had another call during which my recruiter disclosed my specific offer. The number is comfortably competitive for my skills and role -- although it doesn&#x27;t precisely match my other offers in salary, the difference is less than the benefit to my career of working at a company that understands and values open source.&lt;&#x2F;p&gt;
&lt;p&gt;I received my offer letter on May 12th, verified that the confidentiality agreement was sane (it protects the user data that Mozilla gathers with peoples&#x27; permission, rather than trying to restrict employees&#x27; open source contributions), and signed and returned the paperwork.&lt;&#x2F;p&gt;
&lt;p&gt;Later that day, the Mozilla onboarding system started sending me emails. It walked me through setting up accounts, ordering my work laptop, filing tax paperwork, and all the other bureaucratic minutae associated with starting a new job. The system handed out onboarding tasks in small groups on a set schedule, all with friendly and enthusiastic messages explaining what was going on with that set of forms. This made the whole process much easier than other places I&#x27;ve onboarded, where one spends several hours shut in a room with an HR person doing paperwork on the first day.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;time-passes&quot;&gt;Time Passes...&lt;&#x2F;h1&gt;
&lt;p&gt;Between my offer and start date, I found a new place and moved into it, as well as completing the onboarding tasks as they trickled out of the automated onboarding system. I picked up my laptop from Mozilla IT on May 22, then took it home and installed Arch Linux. I &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;05&#x2F;23&#x2F;oh_windows.html&quot;&gt;tried Windows&lt;&#x2F;a&gt; just to give it a chance, but it rapidly failed my informal tests of usability.&lt;&#x2F;p&gt;
&lt;p&gt;When I came into the office to pick up my laptop, our wonderful front desk ninja of all trades Katt took my photo and issued me a badge. This was somewhat surprising, as nobody had warned me that I&#x27;d be getting photographed that day, but it was ultimately convenient since it provided me with a badge to enter the office on my first day.&lt;&#x2F;p&gt;
&lt;p&gt;I also sent my SSH and GPG public keys to a coworker who started setting up my access to the systems I now administer.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;first-day&quot;&gt;First Day&lt;&#x2F;h1&gt;
&lt;p&gt;My first day was actually a Tuesday, 5&#x2F;26, since that Monday was memorial day. I attended a mandatory IT orientation in which they explained fancy new technologies such as IRC and gmail filters, from 8:30 to 11am. It could have been a lot shorter if they&#x27;d packaged it as wiki pages or individual videos to allow us to skip or skim the topics we already knew, but I guess they find it important to put a personal touch on the orientation by having a real live human present it. I&#x27;m sure that if I was more extraverted or less symbiotic with my computer, I would have appreciated it a lot more.&lt;&#x2F;p&gt;
&lt;p&gt;The things that people say about a &quot;firehose of information&quot; are all true. There&#x27;s a comprehensive but somewhat obsolete wiki, a less thorough but closer to up to date jira, and a bunch of public documentation as well. I&#x27;ve been careful to sign up for only those mailing lists which coworkers tell me are directly relevant to my job right now, or else the deluge of facts would be even worse.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;first-month-retrospective&quot;&gt;First Month Retrospective&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ve learned that there are benefits and drawbacks to working at a company that I associate with the most friendly and knowledgeable people I&#x27;ve ever met. The benefits are that my coworkers are amazing and inspire me to be the best professional and open source community member that I can. The &quot;drawback&quot;, if one can call it that, is that everyone suddenly assumes I&#x27;m just as friendly and knowledgeable as everyone else. The title of &quot;Mozilla Employee&quot; seems at times to be a glowing crown emblazoned with &quot;Ask Me Firefox Questions&quot;, but I&#x27;ve so far been able to redirect everyone onto my more knowledgeable peers to get their problems solved. And on the whole, there&#x27;s no better way of becoming that archetype of friendliness and knowledge than to suddenly have the entire world expecting me to embody it.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve always struggled, as I&#x27;m sure you might as well, with accurately assessing my own techncial comptence. At Mozilla, I never feel like the smartest person in the room, and yet I bring specialized knowledge to my team that it would lack without me. This balance keeps me constantly learning, while reassuring me that my contributions are valuable, which is close to optimal for my overall happiness.&lt;&#x2F;p&gt;
&lt;p&gt;The Rust and Servo teams and communities have been incredibly welcoming to me, and they communicate in ways that I find easy to work with.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>One free ticket to Rust Camp</title>
        <published>2015-06-30T00:00:00+00:00</published>
        <updated>2015-06-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/30/one_free_ticket_to_rust_camp/"/>
        <id>https://edunham.net/2015/06/30/one_free_ticket_to_rust_camp/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/30/one_free_ticket_to_rust_camp/">&lt;p&gt;&lt;strong&gt;UPDATE&lt;&#x2F;strong&gt;: The ticket has been claimed by a local student! Thanks everyone for helping boost the signal about this.&lt;&#x2F;p&gt;
&lt;p&gt;Right now, there are 11 tickets being given away by community members to help &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;Carols10cents&#x2F;status&#x2F;613701655100530688&quot;&gt;women&lt;&#x2F;a&gt; and &lt;a href=&quot;http:&#x2F;&#x2F;graydon2.dreamwidth.org&#x2F;217152.html&quot;&gt;minorities&lt;&#x2F;a&gt; attend &lt;a href=&quot;http:&#x2F;&#x2F;rustcamp.com&#x2F;&quot;&gt;Rust Camp&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I have one spare ticket, which I got for a friend who ended up having other commitments on the day of the conference. I would like to give this ticket to someone who would otherwise be unable to attend Rust Camp, regardless of whether or not they&#x27;re a minority. To maximize the amount of impact that all our free tickets have, I&#x27;d ask that anyone qualified for one of &lt;a href=&quot;http:&#x2F;&#x2F;graydon2.dreamwidth.org&#x2F;217152.html&quot;&gt;Graydon&#x27;s tickets&lt;&#x2F;a&gt; appeal to him first, so that the ticket I&#x27;m giving away can help someone who wouldn&#x27;t have another option to attend.&lt;&#x2F;p&gt;
&lt;p&gt;I also happen to have a voucher from United Airlines for $500 worth of flights, so if you could not attend Rust Camp without travel assistance within that amount, let me know.&lt;&#x2F;p&gt;
&lt;p&gt;To recap: Are you a Rust community member who couldn&#x27;t attend without a sponsored ticket? Are you unable to get another free ticket? Send me an email, &lt;code&gt;rustcamp&lt;&#x2F;code&gt; at &lt;code&gt;edunham.net&lt;&#x2F;code&gt;. Since I&#x27;m relatively new to the community and don&#x27;t really know who&#x27;s who yet, I&#x27;d love to see a link or two to your involvement with Rust -- a blog post, tweet, GitHub project, StackOverflow question&#x2F;answer, or anything else. I&#x27;ll update or take down this post once the ticket has been claimed.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to find a Buildbot slave&#x27;s IP</title>
        <published>2015-06-26T00:00:00+00:00</published>
        <updated>2015-06-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/26/how_to_find_a_buildbot_slave_s_ip/"/>
        <id>https://edunham.net/2015/06/26/how_to_find_a_buildbot_slave_s_ip/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/26/how_to_find_a_buildbot_slave_s_ip/">&lt;p&gt;Today I got a seemingly ordinary request from a community member who volunteers a build slave for Rust&#x27;s buildbot:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;my builder is behind a firewall that just cycled IP&amp;#39;s
&lt;&#x2F;span&gt;&lt;span&gt;and I don&amp;#39;t know what it is
&lt;&#x2F;span&gt;&lt;span&gt;edunham: can you get the IP address of the bitrig builder for me?
&lt;&#x2F;span&gt;&lt;span&gt;I have admin access to the builders website but it doesn&amp;#39;t list the IP addresses of builders
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;no-officially-supported-technique-to-find-slave-ips&quot;&gt;No officially supported technique to find slave IPs&lt;&#x2F;h1&gt;
&lt;p&gt;I logged into the buildmaster and checked the slave&#x27;s logs. I found that although slaves reported a variety of information to the master, nothing appears to log their IPs.&lt;&#x2F;p&gt;
&lt;p&gt;I consulted the Buildbot docs, other users, and a project maintainer, but nothing could tell me a &quot;correct&quot;, supported way to find a slave&#x27;s IP.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-workaround&quot;&gt;The Workaround&lt;&#x2F;h1&gt;
&lt;p&gt;I know that the slave whose IP I want is running builds successfully, which means it has an &lt;a href=&quot;https:&#x2F;&#x2F;www.stunnel.org&#x2F;index.html&quot;&gt;stunnel&lt;&#x2F;a&gt; back to the master.&lt;&#x2F;p&gt;
&lt;p&gt;There are few enough slaves in this setup that it&#x27;ll be faster to manually check what host each stunnel corresponds to than to automate it with a script.&lt;&#x2F;p&gt;
&lt;p&gt;I logged into the buildmaster, assumed the identity of the buildbot user, and used &lt;code&gt;netstat&lt;&#x2F;code&gt; to get a list of the open stunnels:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;buildbot@buildmaster:~$ netstat -tupn | grep stunnel
&lt;&#x2F;span&gt;&lt;span&gt;(Not all processes could be identified, non-owned process info
&lt;&#x2F;span&gt;&lt;span&gt; will not be shown, you would have to be root to see it all.)
&lt;&#x2F;span&gt;&lt;span&gt;tcp        0      0 10.190.147.69:9988      54.193.203.23:49167     ESTABLISHED 1038&#x2F;stunnel4
&lt;&#x2F;span&gt;&lt;span&gt;tcp        0      0 127.0.0.1:58123         127.0.0.1:9989          ESTABLISHED 1038&#x2F;stunnel4   
&lt;&#x2F;span&gt;&lt;span&gt;tcp        0      0 127.0.0.1:33051         127.0.0.1:9989          ESTABLISHED 1038&#x2F;stunnel4   
&lt;&#x2F;span&gt;&lt;span&gt;tcp        0      0 10.190.147.69:9988      54.219.68.106:36176     ESTABLISHED 1038&#x2F;stunnel4   
&lt;&#x2F;span&gt;&lt;span&gt;tcp        0      0 10.190.147.69:9988      63.245.221.32:54336     ESTABLISHED 1038&#x2F;stunnel4 
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The IP of the host I&#x27;m looking for will be somewhere in the second column of addresses. Since there were only a couple more than I&#x27;ve shown you up there, it was straightforward to test each plausible candidate individually.&lt;&#x2F;p&gt;
&lt;p&gt;To see whether a given IP is the slave who moved, I can use reverse DNS to approximate what hostname it belongs to. The &lt;code&gt;host&lt;&#x2F;code&gt; command from &lt;code&gt;dnsutils&lt;&#x2F;code&gt; is a convenient wrapper to reverse DNS when you give it an IP:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ host 54.193.203.23
&lt;&#x2F;span&gt;&lt;span&gt;23.203.193.54.in-addr.arpa domain name pointer ec2-54-193-203-23.us-west-1.compute.amazonaws.com.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;$ host 63.245.221.32
&lt;&#x2F;span&gt;&lt;span&gt;32.221.245.63.in-addr.arpa domain name pointer corp.mtv2.mozilla.com.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So the first of those IPs is one of our EC2 buildslaves, and the second is a mac sitting under my coworker&#x27;s desk in the Mountain View office. There was one IP that corresponded to neither AWS nor the Mozilla network, and it belonged to the missing build slave.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s also possible, with a bit more typing, to perform &lt;a href=&quot;http:&#x2F;&#x2F;www.dnsstuff.com&#x2F;reverse-dns-faq&#x2F;&quot;&gt;reverse DNS lookups&lt;&#x2F;a&gt; with &lt;code&gt;dig&lt;&#x2F;code&gt;. You just reverse the order of the blocks of digits in the IP address, and add &lt;code&gt;.in-addr.arpa&lt;&#x2F;code&gt; to the end. For instance, &lt;code&gt;abc.def.ghi.jkl&lt;&#x2F;code&gt; would become &lt;code&gt;jkl.ghi.def.abc.in-addr.arpa&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Moving a Jekyll site from GitHub Pages to Amazon S3</title>
        <published>2015-06-25T00:00:00+00:00</published>
        <updated>2015-06-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/25/moving_a_jekyll_site_from_github_pages_to_amazon_s3/"/>
        <id>https://edunham.net/2015/06/25/moving_a_jekyll_site_from_github_pages_to_amazon_s3/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/25/moving_a_jekyll_site_from_github_pages_to_amazon_s3/">&lt;p&gt;The rust-lang.org web site used to be hosted on GitHub Pages. This gave it excellent uptime and made deploying changes easy, but did not support HTTPS.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-options&quot;&gt;The Options&lt;&#x2F;h1&gt;
&lt;p&gt;I identified 3 major options for how we could alleviate users&#x27; anger at not seeing the little lock icon in their URL bars:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Route all requests to the site through our nginx proxy, which has SSL set up
&lt;ul&gt;
&lt;li&gt;This is problematic because it introduces a new single point of failure in the system, if the proxy host goes down.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Stick a CDN, CloudFlare, between the GitHub page and the world.
&lt;ul&gt;
&lt;li&gt;This will encrypt communications between the CDN and the user, but not between CloudFlare and GitHub. This means the lock icon in the URL bar is present, but actively lying to the user about the security of the page.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Move the site to Amazon S3 and have CloudFront distribute content securely to users.
&lt;ul&gt;
&lt;li&gt;This is similar to the GitHub&#x2F;CloudFlare model, with the important difference that there&#x27;s negligible opportunity to MITM between S3 and CloudFront&lt;&#x2F;li&gt;
&lt;li&gt;The inconvenience is that I have to rebuild the functionality of GitHub pages where pushing to the repo automatically rebuilds the site.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Update: A comment on July 1 in the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;isaacs&#x2F;github&#x2F;issues&#x2F;156#issuecomment-117856640&quot;&gt;github thread&lt;&#x2F;a&gt; pointed out that &lt;a href=&quot;https:&#x2F;&#x2F;surge.sh&#x2F;&quot;&gt;surge.sh&lt;&#x2F;a&gt; offers static web publishing with Jekyll for free, or with custom SSL for $13&#x2F;month. It could probably be deployed automatically from Travis, and might be a better fit than AWS for other use cases.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Since I want to improve security without harming performance or adding unnecessary new moving parts to Rust&#x27;s infrastructure, the third option is distinctly preferable.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;new-workflow&quot;&gt;New Workflow&lt;&#x2F;h1&gt;
&lt;p&gt;The only change visible to site authors will be that content is added to the &lt;code&gt;master&lt;&#x2F;code&gt; branch rather than to &lt;code&gt;gh-pages&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Under the hood, there are several pieces to this transition:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Make Travis rebuild the site on every commit to &lt;code&gt;master&lt;&#x2F;code&gt;, and upload the successfully built site to S3&lt;&#x2F;li&gt;
&lt;li&gt;Send out a CloudFront invalidation when the site is rebuilt, to guarantee that users see the newest version of pages&lt;&#x2F;li&gt;
&lt;li&gt;Update the docs to explain the new workflow, and do some housekeeping like adding a custom error page instead of the GitHub pages 404.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;make-travis-build-site-and-upload-to-s3&quot;&gt;Make Travis build site and upload to S3&lt;&#x2F;h1&gt;
&lt;p&gt;In S3, create a bucket with a descriptive name for the files to end up in. Remember that having dots in bucket names won&#x27;t work with CloudFront, and bucket names have to be globally unique. Mine is called &lt;code&gt;www-rust-lang-org&lt;&#x2F;code&gt;, since it exists to hold the www.rust-lang.org site.&lt;&#x2F;p&gt;
&lt;p&gt;Follow the directions to &lt;a href=&quot;http:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;IAM&#x2F;latest&#x2F;UserGuide&#x2F;Using_SettingUpUser.html&quot;&gt;create an IAM user&lt;&#x2F;a&gt; for Travis. Download the credentials and keep them somewhere safe -- you&#x27;ll later encrypt them to allow Travis to use them to push your site to s3. Set the Travis account&#x27;s &lt;a href=&quot;http:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;IAM&#x2F;latest&#x2F;UserGuide&#x2F;policies_using-managed.html&quot;&gt;policies&lt;&#x2F;a&gt; to allow it to upload to the bucket you created, deploy a CloudFront invalidation, and not touch anything else in your infrastructure. It&#x27;s &lt;a href=&quot;http:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;IAM&#x2F;latest&#x2F;UserGuide&#x2F;IAMBestPracticesAndUseCases.html&quot;&gt;best practices&lt;&#x2F;a&gt; to give the account the minimum permissions with which it can get its job done, to minimize the harm that will occur if someone breaks the encryption of its credentials.&lt;&#x2F;p&gt;
&lt;p&gt;On &lt;a href=&quot;http:&#x2F;&#x2F;travis-ci.org&#x2F;&quot;&gt;the Travis site&lt;&#x2F;a&gt;, push the button that turns on CI for your repository. In the repo settings menu of the user interface (travis-ci.org&#x2F;owner&#x2F;repo&#x2F;settings), make sure that building pull requests is turned off (or the build will fail on PRs, since for security reasons a PR cannot decrypt the encrypted environment variables).&lt;&#x2F;p&gt;
&lt;p&gt;Then follow the &lt;a href=&quot;http:&#x2F;&#x2F;docs.travis-ci.com&#x2F;user&#x2F;deployment&#x2F;s3&#x2F;&quot;&gt;s3 deployment guide&lt;&#x2F;a&gt; to create your &lt;code&gt;.travis.yml&lt;&#x2F;code&gt; file. During the interactive prompt for encryption, make sure that the Travis CLI uses the key for the correct repository, by passing the &lt;code&gt;-r owner&#x2F;repo&lt;&#x2F;code&gt; flag. Each repo has a unique keypair, and a value encrypted with your fork&#x27;s key cannot be decrypted once your changes are merged to the organization. A couple quick local tests have revealed that Travis seems to guess what repo it&#x27;s encrypting for from the GitHub URL of the current repository&#x27;s &lt;code&gt;origin&lt;&#x2F;code&gt; remote.&lt;&#x2F;p&gt;
&lt;p&gt;Push or PR your changes to the repo for which the credentials are encrypted, and verify that the correct files showed up in the bucket. Then &lt;a href=&quot;http:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;AmazonCloudFront&#x2F;latest&#x2F;DeveloperGuide&#x2F;GettingStarted.html&quot;&gt;set up CloudFront&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;make-travis-automatically-invalidate-old-pages&quot;&gt;Make Travis automatically invalidate old pages&lt;&#x2F;h1&gt;
&lt;p&gt;The benefit to CloudFront is that it gives excellent availability by caching pages geographically close to users. The problem with this is that caching is hard, and can cause users to see stale pages after a site update unless we inform the CDN that it should invalidate cached copies of changed pages.&lt;&#x2F;p&gt;
&lt;p&gt;Ideally, I want Travis to send out a CloudFront invalidation immediately after uploading fresh pages to S3. Travis will be running a Ruby environment because the site is built with Jekyll, so I found a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;laurilehmijoki&#x2F;cf-s3-invalidator&quot;&gt;ruby gem&lt;&#x2F;a&gt; which automatically generates Cloudfront invalidations.&lt;&#x2F;p&gt;
&lt;p&gt;Since I&#x27;ll be trusting the gem with credentials to my infrastructure, I compared its source to the &lt;a href=&quot;http:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;AmazonCloudFront&#x2F;latest&#x2F;DeveloperGuide&#x2F;Invalidation.html&quot;&gt;invalidation API instructions&lt;&#x2F;a&gt; to make sure it wasn&#x27;t doing anything obviously malicious or incorrect.&lt;&#x2F;p&gt;
&lt;p&gt;There are 3 new instructions to add to &lt;code&gt;.travis.yml&lt;&#x2F;code&gt; in order to make that gem do its thing: decrypting the invalidation instructions, installing the invalidator, and running the invalidation.&lt;&#x2F;p&gt;
&lt;p&gt;Craft a &lt;code&gt;_cf_s3_invalidator.yml&lt;&#x2F;code&gt; file according to the instructions in the ruby gem, then encrypt it with the command:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;travis encrypt-file -r owner&#x2F;repo  _cf_s3_invalidator.yml --add
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;--add&lt;&#x2F;code&gt; flag dumps the decryption command into the &lt;code&gt;before_install&lt;&#x2F;code&gt; step of your .travis.yml. Read the output of the travis command in your terminal, because it&#x27;s important: You need to add the encrypted file, but not the original, to git.&lt;&#x2F;p&gt;
&lt;p&gt;Add these lines to &lt;code&gt;.travis.yml&lt;&#x2F;code&gt; to install the invalidator and run it when necessary:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;after_success: cf-s3-inv                                                        
&lt;&#x2F;span&gt;&lt;span&gt;script: jekyll build  
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;custom-error-page&quot;&gt;Custom Error Page&lt;&#x2F;h1&gt;
&lt;p&gt;To have Jekyll build a custom error page with a picture on it, put your picture in the directory with your other images and write a simple HTML page to display the error. Mine looks like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust-www&#x2F;blob&#x2F;master&#x2F;error.html&quot;&gt;this&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In the settings for your S3 bucket, specify &lt;code&gt;error.html&lt;&#x2F;code&gt; as your custom error page.&lt;&#x2F;p&gt;
&lt;p&gt;In the settings for your CloudFront distribution, customize which error page gets returned for various HTTP response codes.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>DMARC</title>
        <published>2015-06-17T00:00:00+00:00</published>
        <updated>2015-06-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/17/dmarc/"/>
        <id>https://edunham.net/2015/06/17/dmarc/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/17/dmarc/">&lt;p&gt;Today, the security alias for a site I administer got an automated message pointing out that we lacked a DMARC record. Here&#x27;s what I learned about how to set up and test them.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;how-do-clients-detect-forged-emails&quot;&gt;How do clients detect forged emails?&lt;&#x2F;h1&gt;
&lt;p&gt;There are two main ways for an email client to check whether a message really came from the server it claims it&#x27;s from:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;DomainKeys_Identified_Mail&quot;&gt;DKIM&lt;&#x2F;a&gt;, or DomainKeys Identified Mail, attaches a digital signature to each message and publishes the public key for that signature in the DNS. The client looks up the alleged origin server&#x27;s public key, and if it can decrypt the signature, that means the message was signed with the corresponding private key (and thus ostensibly originated on a server controlled by the same person who controlls the DNS).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Sender_Policy_Framework&quot;&gt;SPF&lt;&#x2F;a&gt;, or Sender Policy Framework, uses TXT records to publish a list of hosts from which legitimate messages will be sent. The client compares the origin of a message to that list, and if the message came from one of the listed hosts, the client knows that it was sent from a machine that the person who controls the DNS considers authorized.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Of course there are other techniques as well, such as signing a message with the sender&#x27;s GPG key and then having the recipient manually look up the sender in the trustweb, but DKIM and SPF are the most widely used automatic systems for spam detection.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-happens-when-they-find-a-fake&quot;&gt;What happens when they find a fake?&lt;&#x2F;h1&gt;
&lt;p&gt;This is where &lt;a href=&quot;http:&#x2F;&#x2F;dmarc.org&quot;&gt;DMARC&lt;&#x2F;a&gt; comes in. With just DKIM and SPF, the alleged sender (ie, the domain who&#x27;s either sending the mail or being spoofed) has no way to tell clients what to do when messages fail both authenticity checks.&lt;&#x2F;p&gt;
&lt;p&gt;DMARC is simply a TXT record published in a server&#x27;s DNS that tells clients what the person controlling that DNS wants them to do if a message fails both DKIM and SPF checks.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-in-a-dmarc-record&quot;&gt;What&#x27;s in a DMARC record?&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;Version, which as of 2015 will only ever be &lt;code&gt;v=DMARC1&lt;&#x2F;code&gt;. This field is for future-proofing in case the protocol needs to change someday.&lt;&#x2F;li&gt;
&lt;li&gt;Policy, which will be one of &lt;code&gt;p=none&lt;&#x2F;code&gt;, &lt;code&gt;p=quarantine&lt;&#x2F;code&gt;, or &lt;code&gt;p=reject&lt;&#x2F;code&gt;. The policy tells clients what to do with messages that appear to be spam.&lt;&#x2F;li&gt;
&lt;li&gt;Percent, which tells clients how often to check messages from this domain. &lt;code&gt;pct=100&lt;&#x2F;code&gt; will ensure that all messages are checked.&lt;&#x2F;li&gt;
&lt;li&gt;Reporting address is a URI that specifies who clients should tell about messages which failed both SPF and DKIM. This will probably look like &lt;code&gt;rua=mailto:dmarcreports@yourdomain.tld&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;what-policy-should-i-use&quot;&gt;What policy should I use?&lt;&#x2F;h1&gt;
&lt;p&gt;If you choose &lt;strong&gt;reject&lt;&#x2F;strong&gt;, you&#x27;re asking clients to throw away all messages from your domain which fail both SPF and DKIM authentication. Only use this setting if you&#x27;re extremely confident that users will never need to see the contents of unauthenticated or incorrectly authenticated mail that comes from legitimate servers on your domain.&lt;&#x2F;p&gt;
&lt;p&gt;Use &lt;strong&gt;quarantine&lt;&#x2F;strong&gt; if messages that fail both SPF and DKIM authentication should be marked as spam but delivered by clients. This can be a good compromise between ensuring that users are notified of messages that fail authentication, yet letting legitimate but poorly-authenticated messages get to somewhere the users can see them if they check their spam folders.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;strong&gt;none&lt;&#x2F;strong&gt; setting is for testing purposes. If you&#x27;re just starting out with DMARC, setting your record to &lt;code&gt;none&lt;&#x2F;code&gt; for the first week or two will allow you to see clients&#x27; reports of which legitimate emails from your domain are failing their DKIM and SPF checks. It&#x27;s a good idea to leave your DMARC policy set to &lt;code&gt;none&lt;&#x2F;code&gt; until you have DKIM, SPF, or both for every legitimate service that sends emails from your domain. Remember that a message only has to pass one authentication method to be considered not spam -- DMARC is only relevant to the messages which fail &lt;em&gt;both&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;deploying-dmarc&quot;&gt;Deploying DMARC&lt;&#x2F;h1&gt;
&lt;p&gt;First, pick the email address to which reports of apparently-spoofed messages should be sent. It&#x27;s a good idea to create a new email alias (this should be trivial if you already control your own DNS), so that you can add other administrators if your team grows.&lt;&#x2F;p&gt;
&lt;p&gt;Next, figure out what the record should say. It might look like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;v=DMARC1;p=none;pct=100;rua=mailto:dmarcstuff@yourdomain.com
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once you have the record, add it to your DNS as a TXT record for &lt;code&gt;_dmarc.yourdomain.com&lt;&#x2F;code&gt;. Then wait a few minutes for the world&#x27;s DNS servers to get the memo about your new record, and verify that it&#x27;s correctly deployed!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;verify-the-dmarc-record&quot;&gt;Verify the DMARC record&lt;&#x2F;h1&gt;
&lt;p&gt;From the command line, you can use &lt;code&gt;dig txt _dmarc.somedomain.tld&lt;&#x2F;code&gt; to see what that domain&#x27;s policies are. For instance, Google&#x27;s policy is to quarantine messages and inform a &lt;code&gt;mailauth-reports&lt;&#x2F;code&gt; address:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ dig txt _dmarc.google.com
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;;; ANSWER SECTION:
&lt;&#x2F;span&gt;&lt;span&gt;_dmarc.google.com.  484 IN  TXT &amp;quot;v=DMARC1\; p=quarantine\; rua=mailto:mailauth-reports@google.com&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you prefer a pretty online interface, you could use a free online tool like &lt;a href=&quot;https:&#x2F;&#x2F;dmarcian.com&#x2F;dmarc-inspector&#x2F;&quot;&gt;dmarcian.com&lt;&#x2F;a&gt; instead.&lt;&#x2F;p&gt;
&lt;p&gt;If you happen to have sufficiently dark-gray hat handy, you could try spoofing a message to yourself from your own domain:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ mail -r spoofed@yourdomain.tld you@gmail.com
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will require no small amount of fiddling with firewalls to get working, since many personal systems are configured by default to be unable to send mail.&lt;&#x2F;p&gt;
&lt;p&gt;Alternately, you can just wait until legitimate mail gets sent from your domain, then see what clients report back. The &lt;code&gt;pct=100&lt;&#x2F;code&gt; directive asks clients to report on all mail recieved from your domain, regardless of whether it passed or failed.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;next-steps&quot;&gt;Next Steps&lt;&#x2F;h1&gt;
&lt;p&gt;Leave the &lt;code&gt;p=none&lt;&#x2F;code&gt; setting in place until all the systems which you expect to send legitimate emails will have sent something. Then audit the logs emailed to you by client mail hosting providers and see whether any legitimate messages failed both DKIM and SPF.&lt;&#x2F;p&gt;
&lt;p&gt;Fix the systems which sent those poorly-authenticated messages, check the logs from after your fix to make sure no messages are failing both authenticity tests any more, and then increase the &lt;code&gt;p&lt;&#x2F;code&gt; setting to either &lt;code&gt;quarantine&lt;&#x2F;code&gt; or &lt;code&gt;reject&lt;&#x2F;code&gt; by editing the TXT record.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Downloading an S3 bucket</title>
        <published>2015-06-17T00:00:00+00:00</published>
        <updated>2015-06-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/17/downloading_an_s3_bucket/"/>
        <id>https://edunham.net/2015/06/17/downloading_an_s3_bucket/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/17/downloading_an_s3_bucket/">&lt;p&gt;Since I&#x27;m curious about how often files are downloaded from S3, I enabled logging on the buckets serving them and directed the logs into a bucket which I created to hold them. Then I wanted to move everything on that logs bucket to my local machine, so I could poke around in the logs and ascertain the best way to turn them into useful information.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;install-the-cli&quot;&gt;Install the CLI&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;8659382&#x2F;downloading-an-entire-s3-bucket&quot;&gt;Stackoverflow&lt;&#x2F;a&gt; pointed me toward the &lt;code&gt;awscli&lt;&#x2F;code&gt; Python module. Since I don&#x27;t keep a system pip, I installed it to a virtualenv:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ virtualenv2 v
&lt;&#x2F;span&gt;&lt;span&gt;$ source v&#x2F;bin&#x2F;activate
&lt;&#x2F;span&gt;&lt;span&gt;$ pip install awscli
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;tell-the-cli-your-credentials&quot;&gt;Tell the CLI your credentials&lt;&#x2F;h1&gt;
&lt;p&gt;It just needs your Access Key ID and Secret Access Key. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ aws configure
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;download-the-desired-bucket&quot;&gt;Download the desired bucket&lt;&#x2F;h1&gt;
&lt;p&gt;Decide where you want to save the bucket contents to, then sync it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ aws s3 sync s3:&#x2F;&#x2F;bucket-of-logs ~&#x2F;bucket-logs&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;that-s-it&quot;&gt;That&#x27;s it!&lt;&#x2F;h1&gt;
&lt;p&gt;It&#x27;s surprisingly easy to download the contents of an entire S3 bucket, considering that the documentation on how to do it is spread out over several different sources and buried in &lt;a href=&quot;http:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;cli&#x2F;latest&#x2F;index.html&quot;&gt;comprehensive reference guides&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Deleting spam logs</title>
        <published>2015-06-15T00:00:00+00:00</published>
        <updated>2015-06-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/15/deleting_spam_logs/"/>
        <id>https://edunham.net/2015/06/15/deleting_spam_logs/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/15/deleting_spam_logs/">&lt;p&gt;Some spammers got onto the Mozilla network, scraped a major channel&#x27;s user list, and PMed everybody requests to join their network from almost 1,000 different nicks. Here&#x27;s how I tidied up afterwards.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;close-spurious-pm-buffers&quot;&gt;Close spurious PM buffers&lt;&#x2F;h1&gt;
&lt;p&gt;Simple fix from &lt;a href=&quot;http:&#x2F;&#x2F;www.irssi.org&#x2F;documentation&#x2F;settings&quot;&gt;the irssi docs&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;set autoclose_windows on
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will automatically close any PM buffer where the other person (or bot) is no longer online. Another cool setting is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;set autoclose_query 172800
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That will close any private message window that&#x27;s been silent for more than 2 days. You can tweak the number of seconds higher or lower to fit your use case and prevent it from deleting real conversations.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;delete-logs-by-length&quot;&gt;Delete logs by length&lt;&#x2F;h1&gt;
&lt;p&gt;This particular spammer pasted one or two lines per log, and I happen to know that I have had no one-line conversations on that network whose logs I wish to keep. Here&#x27;s a slightly ugly chunk of shell to delete every log whose length was between 1 and 5 lines (inclusive):&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;~&#x2F;irclogs&#x2F;moz$ wc -l * | grep &amp;quot;[1-5] [a-zA-Z]&amp;quot; | cut -d &amp;quot; &amp;quot; -f 9 | xargs rm
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Pipes are our friends! Just run the commands one at a time to see what they do:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wc -l&lt;&#x2F;code&gt; counts the lines in the file, with a return of the form &lt;code&gt;# filename&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;grep&lt;&#x2F;code&gt; finds things which match the regular expression. &lt;code&gt;[1-5]&lt;&#x2F;code&gt; matches any single digit between 1 and 5, the space matches a space, and the &lt;code&gt;[a-zA-Z]&lt;&#x2F;code&gt; matches the first letter of the name of the log.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cut&lt;&#x2F;code&gt; selects one column of output. The &lt;code&gt;-d&lt;&#x2F;code&gt; part says that I want the columns to be space-delimited, and the &lt;code&gt;-f 9&lt;&#x2F;code&gt; selects the 9th column from the output. It&#x27;s 9 becuase &lt;code&gt;wc&lt;&#x2F;code&gt; pads its output with a bunch of spaces before the single digit of file length.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;xargs&lt;&#x2F;code&gt; takes the thing you piped into it and feeds it as an argument into &lt;code&gt;rm&lt;&#x2F;code&gt;. In this case, &lt;code&gt;cut&lt;&#x2F;code&gt; spits out a list of filenames, so &lt;code&gt;rm&lt;&#x2F;code&gt; will go through and remove each one.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;delete-logs-by-contents&quot;&gt;Delete logs by contents&lt;&#x2F;h1&gt;
&lt;p&gt;Since it&#x27;s the same line in every spam log, I can also delete all PM logs that contain the spam message:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;~&#x2F;irclogs&#x2F;moz$ grep -l --exclude=&amp;quot;#*&amp;quot; &amp;quot;join my irc network irc.cooldudeirc.com &amp;quot; * | xargs rm
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grep&lt;&#x2F;code&gt; finds all files containing the stated string. The &lt;code&gt;-l&lt;&#x2F;code&gt; says &quot;only return file names, not the part of the contents that matched&quot; and the &lt;code&gt;--exclude&lt;&#x2F;code&gt; pattern makes the search return only private message logs (since channel logs start with a &lt;code&gt;#&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;xargs&lt;&#x2F;code&gt; does the same thing as before.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Installing Playpen on Ubuntu</title>
        <published>2015-06-10T00:00:00+00:00</published>
        <updated>2015-06-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/10/installing_playpen_on_ubuntu/"/>
        <id>https://edunham.net/2015/06/10/installing_playpen_on_ubuntu/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/10/installing_playpen_on_ubuntu/">&lt;p&gt;One of the more egregious inconsistencies in Rust&#x27;s architecture is that &lt;code&gt;play.rust-lang.org&lt;&#x2F;code&gt; lives on an Arch box, while everything else is Ubuntu. Before the team has a dedicated operations person, the argument for using Arch was that &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;thestinger&#x2F;playpen&quot;&gt;playpen&lt;&#x2F;a&gt; comes &lt;a href=&quot;https:&#x2F;&#x2F;www.archlinux.org&#x2F;packages&#x2F;community&#x2F;x86_64&#x2F;playpen&#x2F;&quot;&gt;pre-packaged&lt;&#x2F;a&gt; for it, whereas one has to build it oneself on Ubuntu.&lt;&#x2F;p&gt;
&lt;p&gt;But standardizing the infrastructure to one OS is a really cool thing to do, since it requires that much less thought and effort to do any update that affects every system (I&#x27;m looking at you, security patches).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-goal&quot;&gt;The Goal&lt;&#x2F;h1&gt;
&lt;p&gt;I want to install playpen on an Ubuntu host. For testing purposes, it&#x27;s a DigitalOcean droplet, but eventually it&#x27;ll be an AWS EC2 instance.&lt;&#x2F;p&gt;
&lt;p&gt;Additionally, I&#x27;d like to automate the installation process to make it repeatable. For this, I&#x27;m using &lt;a href=&quot;http:&#x2F;&#x2F;www.ansible.com&#x2F;home&quot;&gt;ansible&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m currently using Ubuntu 15.04, since it has the &lt;code&gt;libsystemd-dev&lt;&#x2F;code&gt; package available and I&#x27;ll be updating to the 16.04 LTS when it comes out at the end of 15.04&#x27;s life anyways.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-playbook&quot;&gt;The Playbook&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;- hosts: play
&lt;&#x2F;span&gt;&lt;span&gt;  remote_user: root
&lt;&#x2F;span&gt;&lt;span&gt;  tasks:
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Create .ssh directory
&lt;&#x2F;span&gt;&lt;span&gt;    file:
&lt;&#x2F;span&gt;&lt;span&gt;        # The ansible_ssh_user is specified in the hosts file
&lt;&#x2F;span&gt;&lt;span&gt;        path=&#x2F;.ssh&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        state=directory
&lt;&#x2F;span&gt;&lt;span&gt;        owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;        group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Install known_hosts file
&lt;&#x2F;span&gt;&lt;span&gt;    copy:
&lt;&#x2F;span&gt;&lt;span&gt;        src=known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        dest=&#x2F;.ssh&#x2F;known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;        group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Clone Playpen
&lt;&#x2F;span&gt;&lt;span&gt;    git:
&lt;&#x2F;span&gt;&lt;span&gt;        repo=https:&#x2F;&#x2F;github.com&#x2F;thestinger&#x2F;playpen.git
&lt;&#x2F;span&gt;&lt;span&gt;        dest=&#x2F;home&#x2F;{{ ansible_ssh_user }}&#x2F;playpen_source
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Install prerequisite packages for building playpen
&lt;&#x2F;span&gt;&lt;span&gt;    apt:
&lt;&#x2F;span&gt;&lt;span&gt;        pkg={{ item }}
&lt;&#x2F;span&gt;&lt;span&gt;        state=present
&lt;&#x2F;span&gt;&lt;span&gt;    with_items:
&lt;&#x2F;span&gt;&lt;span&gt;        - make
&lt;&#x2F;span&gt;&lt;span&gt;        - pkg-config
&lt;&#x2F;span&gt;&lt;span&gt;        - clang
&lt;&#x2F;span&gt;&lt;span&gt;        - libglib2.0-dev
&lt;&#x2F;span&gt;&lt;span&gt;        - gcc
&lt;&#x2F;span&gt;&lt;span&gt;        - libseccomp-dev
&lt;&#x2F;span&gt;&lt;span&gt;        - libsystemd-dev
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Make Install Playpen
&lt;&#x2F;span&gt;&lt;span&gt;    # Note: Playpen requires Linux 3.8 or later. 3.16 counts as &amp;quot;later&amp;quot;.
&lt;&#x2F;span&gt;&lt;span&gt;    command: make install
&lt;&#x2F;span&gt;&lt;span&gt;        chdir=&#x2F;home&#x2F;{{ansible_ssh_user }}&#x2F;playpen_source&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        creates=&#x2F;usr&#x2F;local&#x2F;bin&#x2F;playpen
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As I update it, you can see the changes &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;toy-ansible&#x2F;blob&#x2F;master&#x2F;playpen.yml&quot;&gt;here&lt;&#x2F;a&gt;. It assumes that you have a hosts file, which groups the hosts which need playpen installed as &lt;code&gt;play&lt;&#x2F;code&gt; and specifies the &lt;code&gt;ansible_ssh_user&lt;&#x2F;code&gt; for them.&lt;&#x2F;p&gt;
&lt;p&gt;My hosts file currently looks like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[server]
&lt;&#x2F;span&gt;&lt;span&gt;play ansible_ssh_host=104.236.16.38 ansible_ssh_user=root
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I apply the playbook like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible-playbook -i hosts playpen.yml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;setup&quot;&gt;Setup&lt;&#x2F;h1&gt;
&lt;p&gt;I create a DigitalOcean droplet, $5&#x2F;month tier, specifying Ubuntu 15.04 LTS and my SSH key.&lt;&#x2F;p&gt;
&lt;p&gt;I add the SSH key to my keyring with &lt;code&gt;ssh-add ~&#x2F;.ssh&#x2F;keyname&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I note the droplet&#x27;s IP address in the DigitalOcean console, and edit my Ansible hosts file to add it.&lt;&#x2F;p&gt;
&lt;p&gt;I attempt to SSH to root at that IP, to check that it&#x27;s available and to install Python (Ansible&#x27;s only prerequisite on managed nodes). If I get a &lt;code&gt;remote host identification has changed&lt;&#x2F;code&gt; error, I run &lt;code&gt;ssh-keygen -R &amp;lt;ip&amp;gt;&lt;&#x2F;code&gt; to remove the old key. This is a pretty common error when you delete a droplet then recreate another immediately, since DigitalOcean reuses IP addresses.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;run-the-playbook&quot;&gt;Run the Playbook&lt;&#x2F;h1&gt;
&lt;p&gt;Once Python is installed on the DigitalOcean droplet and its IP is up to date in my hosts file, I can install Playpen with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible-playbook -i hosts playpen.yml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here&#x27;s a closer look at what it does. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;- hosts: play
&lt;&#x2F;span&gt;&lt;span&gt;  remote_user: root
&lt;&#x2F;span&gt;&lt;span&gt;  tasks:
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is Ansible boilerplate. It specifies which hosts to run the commands on, what user to log into those hosts as, and then starts the list of tasks. The commands I&#x27;d have to run by hand every time without Ansible are all listed in the tasks section, whose breakdown follows. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Create .ssh directory
&lt;&#x2F;span&gt;&lt;span&gt;  file:
&lt;&#x2F;span&gt;&lt;span&gt;      # The ansible_ssh_user is specified in the hosts file
&lt;&#x2F;span&gt;&lt;span&gt;      path=&#x2F;.ssh&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;      state=directory
&lt;&#x2F;span&gt;&lt;span&gt;      owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;      group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;re going to do a HTTPS clone from GitHub in a couple steps, but first we need to tell the host to recognize GitHub&#x27;s public SSH key. This is that &quot;The authenticity of host &#x27;whatever&#x27; can&#x27;t be established. Are you sure you want to continue?&quot; message that you get in the habit of blindly typing &lt;code&gt;Y&lt;&#x2F;code&gt; to during the first time you interact with a given server from a clean install... But you can&#x27;t just type &lt;code&gt;Y&lt;&#x2F;code&gt;, because it&#x27;s all automated and awesome, so instead we teach the server to recognize GitHub so the host isn&#x27;t unknown. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Install known_hosts file
&lt;&#x2F;span&gt;&lt;span&gt;  copy:
&lt;&#x2F;span&gt;&lt;span&gt;      src=known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;      dest=&#x2F;.ssh&#x2F;known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;      owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;      group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;known_hosts&lt;&#x2F;code&gt; file in my repo contains a copy of GitHub&#x27;s public key. The &lt;code&gt;copy&lt;&#x2F;code&gt; module simply copies a file from the Ansible repo over onto the remote server. Now the server getting the install will recognize GitHub when it tries to clone the Playpen repo, and we can continue. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Clone Playpen
&lt;&#x2F;span&gt;&lt;span&gt;  git:
&lt;&#x2F;span&gt;&lt;span&gt;      repo=https:&#x2F;&#x2F;github.com&#x2F;thestinger&#x2F;playpen.git
&lt;&#x2F;span&gt;&lt;span&gt;      dest=&#x2F;home&#x2F;{{ ansible_ssh_user }}&#x2F;playpen_source
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;git&lt;&#x2F;code&gt; module works just like command-line git. This clones the repo, in this case into &lt;code&gt;&#x2F;home&#x2F;root&#x2F;playpen_source&lt;&#x2F;code&gt; because that seems like as good a place as any to put it. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Install prerequisite packages for building playpen
&lt;&#x2F;span&gt;&lt;span&gt;  apt:
&lt;&#x2F;span&gt;&lt;span&gt;      pkg={{ item }}
&lt;&#x2F;span&gt;&lt;span&gt;      state=present
&lt;&#x2F;span&gt;&lt;span&gt;  with_items:
&lt;&#x2F;span&gt;&lt;span&gt;      - make
&lt;&#x2F;span&gt;&lt;span&gt;      - pkg-config
&lt;&#x2F;span&gt;&lt;span&gt;      - clang
&lt;&#x2F;span&gt;&lt;span&gt;      - libglib2.0-dev
&lt;&#x2F;span&gt;&lt;span&gt;      - gcc
&lt;&#x2F;span&gt;&lt;span&gt;      - libseccomp-dev
&lt;&#x2F;span&gt;&lt;span&gt;      - libsystemd-dev
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I constructed the list of dependencies by trial and error: Cloned the repo on a clean Ubuntu install, ran &lt;code&gt;make install&lt;&#x2F;code&gt;, read the errors, installed the dep that threw the error. Over and over. This is how it feels to be a log tailer running in an infinite loop. But the good news is that since I did this, you don&#x27;t have to! Just copy my list of dependencies if you&#x27;re lazy, or try it yourself and you&#x27;ll get the same thing.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;with_items&lt;&#x2F;code&gt; directive in Ansible is pretty neat: it iterates over the list it&#x27;s handed, and runs the command (in this case, &lt;code&gt;apt&lt;&#x2F;code&gt;) on every item. It&#x27;s a nice concise way to install a lot of stuff. :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Make Install Playpen
&lt;&#x2F;span&gt;&lt;span&gt;  # Note: Playpen requires Linux 3.8 or later. 3.16 counts as &amp;quot;later&amp;quot;.
&lt;&#x2F;span&gt;&lt;span&gt;  command: make install
&lt;&#x2F;span&gt;&lt;span&gt;      chdir=&#x2F;home&#x2F;{{ansible_ssh_user }}&#x2F;playpen_source&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;      creates=&#x2F;usr&#x2F;local&#x2F;bin&#x2F;playpen
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This changes directory into &lt;code&gt;&#x2F;home&#x2F;root&#x2F;playpen_source&#x2F;&lt;&#x2F;code&gt; and runs &lt;code&gt;make install&lt;&#x2F;code&gt;. The bit about &lt;code&gt;creates&lt;&#x2F;code&gt; tells Ansible that &lt;code&gt;&#x2F;usr&#x2F;local&#x2F;bin&#x2F;playpen&lt;&#x2F;code&gt; gets generated by this command, so if that file exists at the start of the run, Ansible doesn&#x27;t need to bother re-doing the entire &lt;code&gt;make install&lt;&#x2F;code&gt; step.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;and-there-we-have-it&quot;&gt;And there we have it!&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;m still pretty new to Ansible, so if you have suggestions for improving this script, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;toy-ansible&#x2F;issues&#x2F;new&quot;&gt;file an issue&lt;&#x2F;a&gt; or email me, &lt;code&gt;anything &amp;lt;at&amp;gt; edunham &amp;lt;dot&amp;gt; net&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Display Defaults</title>
        <published>2015-06-08T00:00:00+00:00</published>
        <updated>2015-06-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/08/display_defaults/"/>
        <id>https://edunham.net/2015/06/08/display_defaults/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/08/display_defaults/">&lt;p&gt;I&#x27;ve been running Arch on my work laptop and it&#x27;s pretty much working. However, I have a nice external monitor on my desk, and I keep having to manually configure the output to it with &lt;code&gt;arandr&lt;&#x2F;code&gt;. Here&#x27;s how I made it configure itself by default when X starts.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;first-make-the-layout&quot;&gt;First, make the layout&lt;&#x2F;h1&gt;
&lt;p&gt;Arranging monitors is one of the few places where I prefer using a GUI over the command line. I use &lt;code&gt;arandr&lt;&#x2F;code&gt;, a graphical &lt;code&gt;xrandr&lt;&#x2F;code&gt; frontend, to organize my monitors.&lt;&#x2F;p&gt;
&lt;p&gt;Once the displays are set as desired, I go to &lt;code&gt;layout&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;save as&lt;&#x2F;code&gt; and save it as &lt;code&gt;~&#x2F;.screenlayout&#x2F;default.sh&lt;&#x2F;code&gt;. That file contains:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;#!&#x2F;bin&#x2F;sh
&lt;&#x2F;span&gt;&lt;span&gt;xrandr --output VIRTUAL1 --off --output eDP1 --mode 2560x1440 --pos 0x1200
&lt;&#x2F;span&gt;&lt;span&gt;--rotate normal --output DP1 --off --output HDMI2 --mode 1920x1200 --pos
&lt;&#x2F;span&gt;&lt;span&gt;312x0 --rotate normal --output HDMI1 --off --output DP2 --off
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;then-execute-the-command-on-login&quot;&gt;Then, execute the command on login&lt;&#x2F;h1&gt;
&lt;p&gt;Since &lt;code&gt;arandr&lt;&#x2F;code&gt; outputs the &lt;code&gt;xrandr&lt;&#x2F;code&gt; command needed to set the layout correctly, I took the lazy way out and just dumped the &lt;code&gt;xrandr&lt;&#x2F;code&gt; incantation from &lt;code&gt;~&#x2F;.screenlayout&#x2F;default.sh&lt;&#x2F;code&gt; into the start of &lt;code&gt;~&#x2F;.xinitrc&lt;&#x2F;code&gt;. I might someday come back and replace it with a more sophisticated script to detect whether the second monitor is plugged in, but it&#x27;s good enough for now.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;that-s-it&quot;&gt;That&#x27;s it!&lt;&#x2F;h1&gt;
&lt;p&gt;Yet I had to dig that knowledge out of the &lt;a href=&quot;http:&#x2F;&#x2F;bbs.archbang.org&#x2F;viewtopic.php?id=2629&quot;&gt;forums&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Xrandr&quot;&gt;xrandr wiki page&lt;&#x2F;a&gt;. But seriously, &lt;code&gt;arandr&lt;&#x2F;code&gt; and &lt;code&gt;.xinitrc&lt;&#x2F;code&gt; are all it takes. It was much easier than I expected.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Playing with Ansible</title>
        <published>2015-06-08T00:00:00+00:00</published>
        <updated>2015-06-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/08/playing_with_ansible/"/>
        <id>https://edunham.net/2015/06/08/playing_with_ansible/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/08/playing_with_ansible/">&lt;p&gt;Although I currently expect that I&#x27;ll end up choosing Salt for work, I&#x27;ve gotten &lt;a href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;356&#x2F;&quot;&gt;nerdsniped&lt;&#x2F;a&gt; by the apparent simplicity and power of Ansible. Since I&#x27;m trying to make a habit of narrating my first encounters with various tools, here&#x27;s a short novel of 0 through cloning a repo.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;starting-out&quot;&gt;Starting Out&lt;&#x2F;h1&gt;
&lt;p&gt;Google hands me the &lt;a href=&quot;http:&#x2F;&#x2F;docs.ansible.com&#x2F;intro_getting_started.html&quot;&gt;intro doc&lt;&#x2F;a&gt; right away. I noticed this when researcing my config management comparison post as well -- Googling for a given topic results in more useful docs and less marketing with Ansible than CFEngine, Puppet, or Chef.&lt;&#x2F;p&gt;
&lt;p&gt;I install Ansible with &lt;code&gt;yaourt -S ansible&lt;&#x2F;code&gt;. This installs it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible --version
&lt;&#x2F;span&gt;&lt;span&gt;ansible 1.9.1
&lt;&#x2F;span&gt;&lt;span&gt;  configured module search path = None
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I wonder what that search path info is for... I&#x27;m sure the docs will tell me when I need it.&lt;&#x2F;p&gt;
&lt;p&gt;So, I need an &lt;code&gt;&#x2F;etc&#x2F;ansible&#x2F;hosts&lt;&#x2F;code&gt; First, I need a host to put in it. Since I use DigitalOcean for my VPS and have some free credit lying around from the &lt;a href=&quot;https:&#x2F;&#x2F;education.github.com&#x2F;&quot;&gt;GitHub Education Pack&lt;&#x2F;a&gt;, I go spin up a $5&#x2F;month droplet to play with. I make sure to check the box to add my SSH key, and add the key to my agent locally, to avoid hassles in the future.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;write-etc-ansible-hosts&quot;&gt;Write &lt;code&gt;&#x2F;etc&#x2F;ansible&#x2F;hosts&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;I grab the droplet&#x27;s IP address from &lt;a href=&quot;https:&#x2F;&#x2F;cloud.digitalocean.com&#x2F;droplets&quot;&gt;the digitalocean console&lt;&#x2F;a&gt;, then I drop it into &lt;code&gt;&#x2F;etc&#x2F;ansible&#x2F;hosts&lt;&#x2F;code&gt;. I wonder for a minute whether there&#x27;s any way to alias the host or generally refer to it by something more friendly than an IP address, thn I remember that there ways of grouping hosts in order to address them and the tutorial gets to that later on.&lt;&#x2F;p&gt;
&lt;p&gt;Oh yeah, and I did that thing I always do and forgot to open the file in &lt;code&gt;&#x2F;etc&#x2F;&lt;&#x2F;code&gt; with sudo, hitting that familiar error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;E45: &amp;#39;readonly&amp;#39; option is set (add ! to override)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The fix is to write with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;:w !sudo tee %
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This says &quot;write the file; shell out to &lt;code&gt;sudo tee&lt;&#x2F;code&gt;, give it the contents of the whole file (&lt;code&gt;%&lt;&#x2F;code&gt;) as an argument&quot; (thanks &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;2600783&#x2F;how-does-the-vim-write-with-sudo-trick-work&quot;&gt;stackoverflow&lt;&#x2F;a&gt;!)&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ping-the-host-s&quot;&gt;Ping the Host(s)&lt;&#x2F;h1&gt;
&lt;p&gt;Next tutorial step is &lt;code&gt;ansible all -m ping&lt;&#x2F;code&gt;. I fumbled the typing on the first try, and learned something from the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible all -m png
&lt;&#x2F;span&gt;&lt;span&gt;162.243.134.126 | FAILED =&amp;gt; module png not found in configured module paths
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This tells me that the &lt;code&gt;-m ping&lt;&#x2F;code&gt; is actually running a module on it. I took a look at the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible&quot;&gt;source&lt;&#x2F;a&gt; to see if the ping module is easy to find to read, but I&#x27;m not familiar enough to know exactly where to find it. A bit more clicking around suggests it&#x27;s probably somewhere in &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible-modules-core&quot;&gt;here&lt;&#x2F;a&gt;. Oh hey, I found it! It&#x27;s only in like the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible-modules-core&#x2F;blob&#x2F;devel&#x2F;system&#x2F;ping.py&quot;&gt;second place I checked&lt;&#x2F;a&gt;. It&#x27;s short and easy to read. The docstring is pretty interesting; the &lt;code&gt;C(path)&lt;&#x2F;code&gt; syntax appears to be some kind of cross-referencing directive. So we make a module with some boilerplate in it, import the basic utils, then blindly call &lt;code&gt;main()&lt;&#x2F;code&gt;. This is totally something I could write if I needed to.&lt;&#x2F;p&gt;
&lt;p&gt;So, when I spell &lt;code&gt;ping&lt;&#x2F;code&gt; correctly, I get:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible all -m ping
&lt;&#x2F;span&gt;&lt;span&gt;The authenticity of host &amp;#39;162.243.134.126 (162.243.134.126)&amp;#39; can&amp;#39;t be
&lt;&#x2F;span&gt;&lt;span&gt;established.
&lt;&#x2F;span&gt;&lt;span&gt;ECDSA key fingerprint is
&lt;&#x2F;span&gt;&lt;span&gt;SHA256:pMoI7FvPgBiFqosItd7rmlHABpiKWBToM&#x2F;asCOgbAh8.
&lt;&#x2F;span&gt;&lt;span&gt;Are you sure you want to continue connecting (yes&#x2F;no)?
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and then tell it yes. Cool story, standard SSH stuff.&lt;&#x2F;p&gt;
&lt;p&gt;And then... :&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible all -m ping
&lt;&#x2F;span&gt;&lt;span&gt;162.243.134.126 | FAILED =&amp;gt; SSH Error: Permission denied
&lt;&#x2F;span&gt;&lt;span&gt;(publickey,password).
&lt;&#x2F;span&gt;&lt;span&gt;while connecting to 162.243.134.126:22
&lt;&#x2F;span&gt;&lt;span&gt;It is sometimes useful to re-run the command using -vvvv, which prints
&lt;&#x2F;span&gt;&lt;span&gt;SSH debug output to help diagnose the issue.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well okay then, I guess that makes sense considering that the user on the remote box is root and locally I&#x27;m edunham. Let&#x27;s see... I could totally look this up, but let&#x27;s blindly guess that the &lt;code&gt;-u&lt;&#x2F;code&gt; flag is what&#x27;s necessary...:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible all -u root -m ping
&lt;&#x2F;span&gt;&lt;span&gt;162.243.134.126 | success &amp;gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;changed&amp;quot;: false,
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;ping&amp;quot;: &amp;quot;pong&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ah-ha, it behaves like a proper little Unix utility with guessable flags! Good social engineering tactic there, Ansible, making me feel all clever...&lt;&#x2F;p&gt;
&lt;h1 id=&quot;let-s-install-a-thing&quot;&gt;Let&#x27;s install a thing!&lt;&#x2F;h1&gt;
&lt;p&gt;With this initial success, I&#x27;m going to deviate from the tutorial a bit and see how Ansible handles trying to install a package from source. I&#x27;ve decided to build a mockup of &lt;code&gt;play.rust-lang.org&lt;&#x2F;code&gt;, since it&#x27;s one of the SPOFs that&#x27;s scaring me the worst about Rust&#x27;s infrastructure at the moment. It&#x27;s an Arch box that hasn&#x27;t been updated in a while, running Arch because this tool called &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;thestinger&#x2F;playpen&quot;&gt;playpen&lt;&#x2F;a&gt; comes packaged for Arch but you have to biuld it yourself on Ubuntu. (Yes, the devs were doing the ops work before I got there).&lt;&#x2F;p&gt;
&lt;p&gt;So, I want to install playpen from source. First, I guess, I should probably install Git from the package manager, so that Ansible can clone playpen.&lt;&#x2F;p&gt;
&lt;p&gt;The tutorial&#x27;s next step is to run an echo command, so I guess I could repurpose it into an apt-get command, but that seems very wrong. Let&#x27;s see what&#x27;s next...&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;http:&#x2F;&#x2F;docs.ansible.com&#x2F;intro_inventory.html&quot;&gt;inventory&lt;&#x2F;a&gt; section of the intro comes next, and it explains how to name groups of hosts. Turns out that happens in &lt;code&gt;&#x2F;etc&#x2F;ansible&#x2F;hosts&lt;&#x2F;code&gt; as well... I&#x27;d really rather not keep the metadata on how things are grouped up over in &lt;code&gt;&#x2F;etc&#x2F;&lt;&#x2F;code&gt;. I feel like it might be better to put the inventory in the config repo... and &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;21958727&#x2F;where-to-store-ansible-host-file-on-osx&quot;&gt;stackoverflow&lt;&#x2F;a&gt; points out that one can pass the &lt;code&gt;-i&lt;&#x2F;code&gt; flag to specify a custom inventory location. The best practices doc (thank you, Ansible, for having a best practices doc that&#x27;s actually easy to find) has a section on &lt;a href=&quot;https:&#x2F;&#x2F;docs.ansible.com&#x2F;playbooks_best_practices.html#content-organization&quot;&gt;content organization&lt;&#x2F;a&gt;, which on the one hand doesn&#x27;t say much about keeping a copy of the hosts file, but on the other hand reassures me by not forbidding it either. I&#x27;m just a little bit worried about keeping the grouping metadata of the hosts file from getting lost, since running commands on the &lt;em&gt;correct&lt;&#x2F;em&gt; hosts is a core feature of any good CM tool.&lt;&#x2F;p&gt;
&lt;p&gt;So, change workflow a little:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cp &#x2F;etc&#x2F;ansible&#x2F;hosts ~&#x2F;repos&#x2F;toy-ansible&#x2F;hosts
&lt;&#x2F;span&gt;&lt;span&gt;$ cat hosts
&lt;&#x2F;span&gt;&lt;span&gt;[server]
&lt;&#x2F;span&gt;&lt;span&gt;play ansible_ssh_host=162.243.134.126
&lt;&#x2F;span&gt;&lt;span&gt;$ ansible play -u root -m ping -i hosts
&lt;&#x2F;span&gt;&lt;span&gt;play | success &amp;gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;changed&amp;quot;: false,
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;ping&amp;quot;: &amp;quot;pong&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Okay, now I can keep this metadata in the repository if I want to. Still not totally sure what best practices will be here for my particular use case; maybe using DNS; maybe storing the exact IPs in a file that never gets committed but leaving hosts as a skeleton to document what goes where if anyone else tries to set up a copy; maybe publishing it and just trusting AWS firewall to do what i tell it to. Because if ansible gets run from or via the bastion, I can leave SSH access just as locked down as it&#x27;s always been.&lt;&#x2F;p&gt;
&lt;p&gt;So. One PR to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible&#x2F;pull&#x2F;11194&quot;&gt;fix confusing wording&lt;&#x2F;a&gt; later, I&#x27;m back to figuring out the next file to stick in my repo to explain to the Ansible world that this &quot;play&quot; host needs to have Git installed on it.&lt;&#x2F;p&gt;
&lt;p&gt;...okay, that&#x27;s a lot of &lt;code&gt;&#x2F;etc&#x2F;ansible&#x2F;whatever&lt;&#x2F;code&gt; files and dirs in the tutorial. Maybe I&#x27;m supposed to be keeping all of &lt;code&gt;&#x2F;etc&#x2F;ansible&lt;&#x2F;code&gt; in Git, rather than my arbitrary repos place? Maybe there&#x27;s some prefix in an environment variable that I can set so I odn&#x27;t have to keep passing &lt;code&gt;-i&lt;&#x2F;code&gt; every time?&lt;&#x2F;p&gt;
&lt;p&gt;Okay, tutorial. All this stuff about managing many hosts is cool and I&#x27;ll come back to it later, but can we get on with the single host case already?&lt;&#x2F;p&gt;
&lt;p&gt;And no, tutorial, I do NOT want to learn about ad-hoc commands before playbooks. Okay, you can shut everything down on Christmas, but that will make people Quite Unhappy. I want to live in a world where special snowflakes and one-offs are always a bad thing, so I&#x27;m jumping straight to the &lt;a href=&quot;http:&#x2F;&#x2F;docs.ansible.com&#x2F;playbooks.html&quot;&gt;playbooks&lt;&#x2F;a&gt; section.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;playbooks&quot;&gt;Playbooks&lt;&#x2F;h1&gt;
&lt;p&gt;Ok, so I&#x27;m just really bad at recognizing YAML. I claimed elsewhere that I didn&#x27;t recognize the syntax of Ansible playbooks, which is true, but that&#x27;s my fault and not theirs.&lt;&#x2F;p&gt;
&lt;p&gt;Their sample playbook makes sense! Let&#x27;s try writing something of my own... oh wait, I don&#x27;t know what file extension nor location it belongs with. Fine, guess I&#x27;ve gotta actually keep reading the docs for awhile.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-next-day&quot;&gt;The Next Day&lt;&#x2F;h2&gt;
&lt;p&gt;So, I come back and find the &lt;a href=&quot;http:&#x2F;&#x2F;docs.ansible.com&#x2F;playbooks_intro.html&quot;&gt;playbooks tutorial&lt;&#x2F;a&gt; conveniently open in a tab. Cool, that&#x27;s what to put in a playbook... but what do I call it? Dig around for best practices, don&#x27;t find any, file &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible&#x2F;issues&#x2F;11204&quot;&gt;another bug&lt;&#x2F;a&gt;, call it &lt;code&gt;server.yml&lt;&#x2F;code&gt; because who cares. Guess from the sample playbook how to translate from Yum to Apt.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;install-git&quot;&gt;install Git&lt;&#x2F;h1&gt;
&lt;p&gt;We&#x27;re going to jump right into trying to install Playpen from source, because I find I learn the most from doing things wrong.&lt;&#x2F;p&gt;
&lt;p&gt;First, I&#x27;ll try to install Git. From the tutorial, I&#x27;m guessing this should work:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;- hosts: play
&lt;&#x2F;span&gt;&lt;span&gt;  remote_user: root
&lt;&#x2F;span&gt;&lt;span&gt;  tasks:
&lt;&#x2F;span&gt;&lt;span&gt;  - name: install Git
&lt;&#x2F;span&gt;&lt;span&gt;    apt:
&lt;&#x2F;span&gt;&lt;span&gt;        pkg=git
&lt;&#x2F;span&gt;&lt;span&gt;        state=latest
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, the right thing to do here would be see whether it runs, but I&#x27;m going to do the wrong thing and try to figure out how to install Playpen from source as well. Let&#x27;s just pretend that changing too many things at once is a test of the quality of those inevitable error messages I&#x27;m going to induce.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;shave-a-yak-and-install-known-hosts&quot;&gt;shave a yak and install known_hosts&lt;&#x2F;h1&gt;
&lt;p&gt;There&#x27;s a post about &lt;a href=&quot;https:&#x2F;&#x2F;groups.google.com&#x2F;forum&#x2F;#!topic&#x2F;ansible-project&#x2F;R4Ho-oqJf3o&quot;&gt;installing redis&lt;&#x2F;a&gt; on the Google group, and the guy who wrote most of Ansible chimes in with some advice on best practices (though the thread is from 2013, so it may be totally obsolete by now). Looking for the right way to do a &lt;code&gt;git clone&lt;&#x2F;code&gt; through Ansible reveals that it &lt;a href=&quot;http:&#x2F;&#x2F;www.tomaz.me&#x2F;2013&#x2F;10&#x2F;14&#x2F;solution-for-ansible-git-module-getting-stuck-on-clone.html&quot;&gt;sometimes gets stuck&lt;&#x2F;a&gt;, usually when &lt;code&gt;known_hosts&lt;&#x2F;code&gt; is missing. Looks like I get to learn how to put a file in place, before learning to git clone.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m not totally sure if the boilerplate about &lt;code&gt;ansible_ssh_user&lt;&#x2F;code&gt; that I&#x27;m copying is actually going to accomplish my goal, but we&#x27;ll see when I run it. Since I&#x27;m &lt;code&gt;edunham&lt;&#x2F;code&gt; on the machine where I&#x27;m running Ansible and &lt;code&gt;root&lt;&#x2F;code&gt; on the remote system, it&#x27;ll be obvious to which user that variable referred.&lt;&#x2F;p&gt;
&lt;p&gt;Now my &lt;code&gt;server.yml&lt;&#x2F;code&gt; looks like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;- hosts: play
&lt;&#x2F;span&gt;&lt;span&gt;  remote_user: root
&lt;&#x2F;span&gt;&lt;span&gt;  tasks:
&lt;&#x2F;span&gt;&lt;span&gt;  - name: install Git
&lt;&#x2F;span&gt;&lt;span&gt;    apt:
&lt;&#x2F;span&gt;&lt;span&gt;        pkg=git
&lt;&#x2F;span&gt;&lt;span&gt;        state=latest
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Install known_hosts file
&lt;&#x2F;span&gt;&lt;span&gt;    copy:
&lt;&#x2F;span&gt;&lt;span&gt;        src=known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        dest=&#x2F;home&#x2F;${ansible_ssh_user}&#x2F;.ssh&#x2F;known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        owner=${ansible_ssh_user}
&lt;&#x2F;span&gt;&lt;span&gt;        group=${ansible_ssh_user}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I then moved my laptop&#x27;s &lt;code&gt;~&#x2F;.ssh&#x2F;known_hosts&lt;&#x2F;code&gt; to a backup location, tried to pull from github, added the key, and copied the now-much-shorter &lt;code&gt;~&#x2F;.ssh&#x2F;known_hosts&lt;&#x2F;code&gt; into my Ansible repo.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;trial-and-error-and-error&quot;&gt;Trial and error and error&lt;&#x2F;h1&gt;
&lt;p&gt;After putting my local backup back into place so my laptop knows more hosts than just GitHub, it&#x27;s time to see whether Ansible can apply that playbook:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible-playbook server.yml
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PLAY [play]
&lt;&#x2F;span&gt;&lt;span&gt;*******************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;skipping: no hosts matched
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PLAY RECAP
&lt;&#x2F;span&gt;&lt;span&gt;********************************************************************
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Huh, I clearly did something wrong... Oh, that&#x27;s right, it wouldn&#x27;t know which host &lt;code&gt;play&lt;&#x2F;code&gt; is because I moved the ansible &lt;code&gt;hosts&lt;&#x2F;code&gt; file into the repo!:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible-playbook -i hosts server.yml
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PLAY [play]
&lt;&#x2F;span&gt;&lt;span&gt;*******************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;GATHERING FACTS
&lt;&#x2F;span&gt;&lt;span&gt;***************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;fatal: [play] =&amp;gt; SSH Error: Permission denied (publickey,password).
&lt;&#x2F;span&gt;&lt;span&gt;while connecting to 162.243.134.126:22
&lt;&#x2F;span&gt;&lt;span&gt;It is sometimes useful to re-run the command using -vvvv, which
&lt;&#x2F;span&gt;&lt;span&gt;prints SSH debug output to help diagnose the issue.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;TASK: [install Git]
&lt;&#x2F;span&gt;&lt;span&gt;***********************************************************
&lt;&#x2F;span&gt;&lt;span&gt;FATAL: no hosts matched or all hosts have already failed -- aborting
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PLAY RECAP
&lt;&#x2F;span&gt;&lt;span&gt;********************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;to retry, use: --limit @&#x2F;home&#x2F;edunham&#x2F;server.retry
&lt;&#x2F;span&gt;&lt;span&gt;play                       : ok=0    changed=0 unreachable=1    failed=0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And that would be a failure to add the relevant ssh key after reboot. I add the ssh key to my agent, and this time it works:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ansible-playbook -i hosts server.yml
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PLAY [play] *******************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;GATHERING FACTS ***************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;ok: [play]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;TASK: [install Git] ***********************************************************
&lt;&#x2F;span&gt;&lt;span&gt;changed: [play]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;TASK: [Install known_hosts file] **********************************************
&lt;&#x2F;span&gt;&lt;span&gt;failed: [play] =&amp;gt; {&amp;quot;checksum&amp;quot;: &amp;quot;926e119d8b84c44f4790c47436967ada72e05ba3&amp;quot;, &amp;quot;failed&amp;quot;: true}
&lt;&#x2F;span&gt;&lt;span&gt;msg: Destination directory &#x2F;home&#x2F;${ansible_ssh_user}&#x2F;.ssh does not exist
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;FATAL: all hosts have already failed -- aborting
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PLAY RECAP ********************************************************************
&lt;&#x2F;span&gt;&lt;span&gt;           to retry, use: --limit @&#x2F;home&#x2F;edunham&#x2F;server.retry
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;play                       : ok=2    changed=1    unreachable=0    failed=1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Okay, the &lt;code&gt;ansible_ssh_user&lt;&#x2F;code&gt; stuff was indeed screwed up. If I Google a bit more, I find that I actually skipped something importat in the &lt;a href=&quot;http:&#x2F;&#x2F;docs.ansible.com&#x2F;intro_inventory.html&quot;&gt;inventory intro&lt;&#x2F;a&gt;: setting the user for the host. So I add &lt;code&gt;ansible_ssh_user=root&lt;&#x2F;code&gt; to the hosts file, and try again... and it fails again. Even when I substitute &lt;code&gt;root&lt;&#x2F;code&gt; for &lt;code&gt;${ansible_ssh_user}&lt;&#x2F;code&gt; in the playbook, it fails the same way. Looks like it&#x27;s not automatically creating the directory for me.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;create-a-directory&quot;&gt;Create a directory&lt;&#x2F;h1&gt;
&lt;p&gt;So, I get to make &lt;code&gt;.ssh&lt;&#x2F;code&gt; by hand. Cool story. So now that task looks like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Install known_hosts file
&lt;&#x2F;span&gt;&lt;span&gt;  file:
&lt;&#x2F;span&gt;&lt;span&gt;      path=&#x2F;home&#x2F;${ansible_ssh_user}&#x2F;.ssh&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;      state=directory
&lt;&#x2F;span&gt;&lt;span&gt;      owner=${ansible_ssh_user}
&lt;&#x2F;span&gt;&lt;span&gt;      group=${ansible_ssh_user}
&lt;&#x2F;span&gt;&lt;span&gt;  copy:
&lt;&#x2F;span&gt;&lt;span&gt;      src=known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;      dest=&#x2F;home&#x2F;${ansible_ssh_user}&#x2F;.ssh&#x2F;known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;      owner=${ansible_ssh_user}
&lt;&#x2F;span&gt;&lt;span&gt;      group=${ansible_ssh_user}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;for which I&#x27;m rewarded with the error &lt;code&gt;ERROR: multiple actions specified in task: &#x27;file&#x27; and &#x27;Install known_hosts file&#x27;&lt;&#x2F;code&gt;. So I give a separate name to each action, try again, and get a new error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;failed: [play] =&amp;gt; {&amp;quot;failed&amp;quot;: true, &amp;quot;gid&amp;quot;: 0, &amp;quot;group&amp;quot;: &amp;quot;root&amp;quot;, &amp;quot;mode&amp;quot;: &amp;quot;0755&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;owner&amp;quot;: &amp;quot;root&amp;quot;, &amp;quot;path&amp;quot;: &amp;quot;&#x2F;home&#x2F;${ansible_ssh_user}&amp;quot;, &amp;quot;size&amp;quot;: 4096, &amp;quot;state&amp;quot;:
&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;directory&amp;quot;, &amp;quot;uid&amp;quot;: 0}
&lt;&#x2F;span&gt;&lt;span&gt;msg: chown failed: failed to look up user ${ansible_ssh_user}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point, since Google wasn&#x27;t giving me helpful results in a timely manner, I pinged a friend on IRC and he suggested an alternate syntax, which works:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;- hosts: play
&lt;&#x2F;span&gt;&lt;span&gt;  remote_user: root
&lt;&#x2F;span&gt;&lt;span&gt;  tasks:
&lt;&#x2F;span&gt;&lt;span&gt;  - name: install Git
&lt;&#x2F;span&gt;&lt;span&gt;    apt:
&lt;&#x2F;span&gt;&lt;span&gt;        pkg=git
&lt;&#x2F;span&gt;&lt;span&gt;        state=latest
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Create .ssh directory
&lt;&#x2F;span&gt;&lt;span&gt;    file:
&lt;&#x2F;span&gt;&lt;span&gt;        path=&#x2F;home&#x2F;{{ ansible_ssh_user }}&#x2F;.ssh&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        state=directory
&lt;&#x2F;span&gt;&lt;span&gt;        owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;        group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Install known_hosts file
&lt;&#x2F;span&gt;&lt;span&gt;    copy:
&lt;&#x2F;span&gt;&lt;span&gt;        src=known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        dest=&#x2F;home&#x2F;{{ ansible_ssh_user }}&#x2F;.ssh&#x2F;known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;        group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;m glad it works like that, because it feels more like all the other Python-flavored templating that I&#x27;ve touched (Flask, Django, etc.). And it&#x27;s definitely a Python habit, but I prefer brain damage.&lt;&#x2F;p&gt;
&lt;p&gt;I wonder if it&#x27;d be okay to put spaces around the &lt;code&gt;=&lt;&#x2F;code&gt; signs... one &lt;code&gt;:%s&#x2F;=&#x2F; = &#x2F;g&lt;&#x2F;code&gt; and an &lt;code&gt;ansible-playbook&lt;&#x2F;code&gt; invocation later, I find that adding spaces causes it to fail with an erorr like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;fatal: [play] =&amp;gt; a duplicate parameter was found in the argument string ()
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not freaking helpful. But next time I see it, I&#x27;ll recognize it as too many spaces rather than just being totally confused.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;time-to-clone-playpen&quot;&gt;Time to clone playpen&lt;&#x2F;h1&gt;
&lt;p&gt;Now I should, in theory, have the ability to clone a repo from github. Time to test this hypothesis.... I&#x27;m starting by reading then copying from the &lt;a href=&quot;http:&#x2F;&#x2F;docs.ansible.com&#x2F;git_module.html&quot;&gt;git module docs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s try it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;- name: Clone Playpen
&lt;&#x2F;span&gt;&lt;span&gt;  git:
&lt;&#x2F;span&gt;&lt;span&gt;      repo=git@github.com:thestinger&#x2F;playpen.git
&lt;&#x2F;span&gt;&lt;span&gt;      dest=&#x2F;home&#x2F;{{ ansible_ssh_user }}&#x2F;playpen_source
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And it fails, because it got a GitHub host key that wasn&#x27;t recognized. I guess there are actually several keys:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;# ssh-keygen -r github.com
&lt;&#x2F;span&gt;&lt;span&gt;github.com IN SSHFP 1 1 7bc4945739c3552b9de0260f4524e05329587dea
&lt;&#x2F;span&gt;&lt;span&gt;github.com IN SSHFP 1 2 b040403fc0992ef0bf9144d8aaa25049d8564839821eb592d7338399e456609c
&lt;&#x2F;span&gt;&lt;span&gt;github.com IN SSHFP 2 1 ce76002677a077bf43dabb446a23e86bb127c8c3
&lt;&#x2F;span&gt;&lt;span&gt;github.com IN SSHFP 2 2 858dec00d20192cf334f54c96cccd5900f4540c2975e5d24c8236c255618c10c
&lt;&#x2F;span&gt;&lt;span&gt;github.com IN SSHFP 3 1 261f7e4445378789267eb92c744e9e0d32a5f98d
&lt;&#x2F;span&gt;&lt;span&gt;github.com IN SSHFP 3 2 a4ca08ec5bcf801885aa8b08b5deeb9a51c006988a5814e833f6ac08e81b021f
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In investigating which key I actually added to &lt;code&gt;known_hosts&lt;&#x2F;code&gt;, I discover a couple of worrisome things:&lt;&#x2F;p&gt;
&lt;p&gt;First, my initial fumbling managed to create a &lt;code&gt;&#x2F;home&#x2F;${ansible_ssh_user}&lt;&#x2F;code&gt; directory, which is empty. Ha ha, I guess? I&#x27;ve manually removed it.&lt;&#x2F;p&gt;
&lt;p&gt;Second, although Ansible claims to have installed the &lt;code&gt;known_hosts&lt;&#x2F;code&gt; file, I can&#x27;t actually see it. Actually after a bit more digging, it turns out that I was just being dumb at Unix. The system has a &lt;code&gt;&#x2F;.ssh&lt;&#x2F;code&gt; created by DigitalOcean, which contains only my &lt;code&gt;authorized_keys&lt;&#x2F;code&gt; file. Ansible successfully created the &lt;code&gt;&#x2F;home&#x2F;root&#x2F;.ssh&#x2F;known_hosts&lt;&#x2F;code&gt; file, but &lt;code&gt;cd&lt;&#x2F;code&gt; as root takes you to &lt;code&gt;&#x2F;&lt;&#x2F;code&gt; rather than to &lt;code&gt;&#x2F;home&#x2F;root&lt;&#x2F;code&gt;. Duh.&lt;&#x2F;p&gt;
&lt;p&gt;Turns out that if I throw the &lt;code&gt;known_hosts&lt;&#x2F;code&gt; file into &lt;code&gt;&#x2F;.ssh&lt;&#x2F;code&gt;, I can move on to the next error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;failed: [play] =&amp;gt; {&amp;quot;cmd&amp;quot;: &amp;quot;&#x2F;usr&#x2F;bin&#x2F;git ls-remote &amp;#39;&amp;#39; -h refs&#x2F;heads&#x2F;HEAD&amp;quot;, &amp;quot;failed&amp;quot;: true, &amp;quot;rc&amp;quot;: 128}
&lt;&#x2F;span&gt;&lt;span&gt;stderr: Permission denied (publickey).
&lt;&#x2F;span&gt;&lt;span&gt;fatal: Could not read from remote repository.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Please make sure you have the correct access rights
&lt;&#x2F;span&gt;&lt;span&gt;and the repository exists.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;msg: Permission denied (publickey).
&lt;&#x2F;span&gt;&lt;span&gt;fatal: Could not read from remote repository.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Please make sure you have the correct access rights
&lt;&#x2F;span&gt;&lt;span&gt;and the repository exists.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Should&#x27;ve been using the HTTPS url, since I don&#x27;t want to add a public key from this machine to anybody&#x27;s github account, and I&#x27;ll never need to push back to the repo from my play server.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;success&quot;&gt;Success!&lt;&#x2F;h1&gt;
&lt;p&gt;With that fix, it&#x27;s now successfully changed! The working playbook looks like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;- hosts: play
&lt;&#x2F;span&gt;&lt;span&gt;  remote_user: root
&lt;&#x2F;span&gt;&lt;span&gt;  tasks:
&lt;&#x2F;span&gt;&lt;span&gt;  - name: install Git
&lt;&#x2F;span&gt;&lt;span&gt;    apt:
&lt;&#x2F;span&gt;&lt;span&gt;        pkg=git
&lt;&#x2F;span&gt;&lt;span&gt;        state=latest
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Create .ssh directory
&lt;&#x2F;span&gt;&lt;span&gt;    file:
&lt;&#x2F;span&gt;&lt;span&gt;        # The ansible_ssh_user is specified in the hosts file
&lt;&#x2F;span&gt;&lt;span&gt;        # For this host I&amp;#39;m using root, so .ssh location is special
&lt;&#x2F;span&gt;&lt;span&gt;        path=&#x2F;.ssh&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        state=directory
&lt;&#x2F;span&gt;&lt;span&gt;        owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;        group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Install known_hosts file
&lt;&#x2F;span&gt;&lt;span&gt;    copy:
&lt;&#x2F;span&gt;&lt;span&gt;        src=known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        dest=&#x2F;.ssh&#x2F;known_hosts
&lt;&#x2F;span&gt;&lt;span&gt;        owner={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;        group={{ ansible_ssh_user }}
&lt;&#x2F;span&gt;&lt;span&gt;  - name: Clone Playpen
&lt;&#x2F;span&gt;&lt;span&gt;    git:
&lt;&#x2F;span&gt;&lt;span&gt;        repo=https:&#x2F;&#x2F;github.com&#x2F;thestinger&#x2F;playpen.git
&lt;&#x2F;span&gt;&lt;span&gt;        dest=&#x2F;home&#x2F;{{ ansible_ssh_user }}&#x2F;playpen_source
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;next-up&quot;&gt;Next Up&lt;&#x2F;h1&gt;
&lt;p&gt;My next steps will be to install the tools necesassary to build Playpen, and get Ansible to build it. I probably won&#x27;t keep up the &quot;let&#x27;s-play&quot; blog style, because it exponentially increases the amount of typing involved, and if you read this far you&#x27;re either a creepy stalker, thoroughly sick of hearing about my flailing at Ansible, or possibly both.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Configuration Management Comparison</title>
        <published>2015-06-05T00:00:00+00:00</published>
        <updated>2015-06-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/05/configuration_management_comparison/"/>
        <id>https://edunham.net/2015/06/05/configuration_management_comparison/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/05/configuration_management_comparison/">&lt;p&gt;Let&#x27;s just say that it&#x27;s pretty clear why my team at Mozilla decided to hire an operations specialist when they did. For the infrastructure which supports the Rust programming language, I get the relatively rare (compared to just hacking on an existing deployment) privilege of deploying configuration management from the ground up.&lt;&#x2F;p&gt;
&lt;p&gt;As usual, this means I&#x27;m overthinking everything. It seems important to choose the right tool, when in reality it&#x27;s really only critical to choose a tool that fulfills all the requirements and has a few compelling reasons to be selected over the others.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-system&quot;&gt;The System&lt;&#x2F;h1&gt;
&lt;p&gt;The infrastructure has a relatively small number of hosts, and the part which will do the most scaling is deployment of build workers.&lt;&#x2F;p&gt;
&lt;p&gt;Currently, the infrastructure consists of several EC2 instances:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Bastion for SSH access from outside the system&lt;&#x2F;li&gt;
&lt;li&gt;Nginx proxy which routes traffic to the correct hosts&lt;&#x2F;li&gt;
&lt;li&gt;Play, the server which hosts sandboxes&lt;&#x2F;li&gt;
&lt;li&gt;Buildbot leader&lt;&#x2F;li&gt;
&lt;li&gt;Buildbot workers&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Additional infrastructure includes a stack of Mac Minis under a desk, which perform OSX builds.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-requirements&quot;&gt;The Requirements&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;m looking for a configuration management solution which supports a variety of platforms:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;OSX (preferably multiple versions)&lt;&#x2F;li&gt;
&lt;li&gt;Windows&lt;&#x2F;li&gt;
&lt;li&gt;FreeBSD&lt;&#x2F;li&gt;
&lt;li&gt;Linux&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For the relevant operating systems, the right configuraiton management solution will Just Work on both AWS and a cross-platform virtual machine and&#x2F;or container solution. Containers would be lighter weight for contributors running Linux to test infrastructure changes on, but I think it&#x27;s important to make sure that people running OSX or Windows are able to play with a toy copy of the infrastructure as well.&lt;&#x2F;p&gt;
&lt;p&gt;The right configuration management solution will be available under an open source license, and I&#x27;ll also be assessing the product&#x27;s maturity, implementation language, and project culture.&lt;&#x2F;p&gt;
&lt;p&gt;A good solution will balance security with openness. There&#x27;s some information which is &quot;secret&quot; in that it allows access to a given instance of the infrastructure, and will be unique per instance. It&#x27;s important that others could spin up their own copies of our infrastructure if they desired, but equally important that only authorized people can access the instance associated with our domain and signing key. This means that a good configuration management solution will make it easy to separate &quot;secret&quot; data from the rest.&lt;&#x2F;p&gt;
&lt;p&gt;As well as making sure that each solution checks the above boxes, the solution&#x27;s docs should make it easy to find out:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;How are commands distributed to hosts (master or masterless?)&lt;&#x2F;li&gt;
&lt;li&gt;How will a repository of configurations be structured?&lt;&#x2F;li&gt;
&lt;li&gt;How do I install a package from the package manager, create a file, and start a service?&lt;&#x2F;li&gt;
&lt;li&gt;How do I install a package from source?&lt;&#x2F;li&gt;
&lt;li&gt;How do I get a report of which files were changed when the tool last ran?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;the-contenders&quot;&gt;The Contenders&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ll be looking at 5 of the most popular open source configuration management solutions currently available: Ansible, CFengine, Chef, Puppet, and Salt.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;is-it-open-source&quot;&gt;Is It Open Source?&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible&#x2F;blob&#x2F;devel&#x2F;COPYING&quot;&gt;Ansible&#x27;s license&lt;&#x2F;a&gt; is GPLv3.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cfengine&#x2F;core&#x2F;blob&#x2F;master&#x2F;LICENSE&quot;&gt;CFEngine&#x27;s license&lt;&#x2F;a&gt; can be GPLv3 or Commercial Open Source License (COSL) depending on the user&#x27;s preference.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;chef&#x2F;chef&#x2F;blob&#x2F;master&#x2F;LICENSE&quot;&gt;Chef&#x27;s license&lt;&#x2F;a&gt; is Apache v2.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;puppetlabs&#x2F;puppet&#x2F;blob&#x2F;master&#x2F;LICENSE&quot;&gt;Puppet&#x27;s license&lt;&#x2F;a&gt; is Apache v2.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;saltstack&#x2F;salt&#x2F;blob&#x2F;develop&#x2F;LICENSE&quot;&gt;Salt&#x27;s license&lt;&#x2F;a&gt; is Apache v2.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;age-language-and-community&quot;&gt;Age, Language, and Community&lt;&#x2F;h1&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;Ansible&lt;&#x2F;th&gt;&lt;th&gt;CFEngine&lt;&#x2F;th&gt;&lt;th&gt;Chef&lt;&#x2F;th&gt;&lt;th&gt;Puppet&lt;&#x2F;th&gt;&lt;th&gt;Salt&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Age&lt;&#x2F;td&gt;&lt;td&gt;3yrs&lt;&#x2F;td&gt;&lt;td&gt;22yrs&lt;&#x2F;td&gt;&lt;td&gt;6yrs&lt;&#x2F;td&gt;&lt;td&gt;10yrs&lt;&#x2F;td&gt;&lt;td&gt;4yrs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Language&lt;&#x2F;td&gt;&lt;td&gt;Python&lt;&#x2F;td&gt;&lt;td&gt;C&lt;&#x2F;td&gt;&lt;td&gt;Ruby&lt;&#x2F;td&gt;&lt;td&gt;Ruby&lt;&#x2F;td&gt;&lt;td&gt;Python&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;People&lt;&#x2F;td&gt;&lt;td&gt;1,060&lt;&#x2F;td&gt;&lt;td&gt;71&lt;&#x2F;td&gt;&lt;td&gt;385&lt;&#x2F;td&gt;&lt;td&gt;376&lt;&#x2F;td&gt;&lt;td&gt;1,123&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Issues&lt;&#x2F;td&gt;&lt;td&gt;535&#x2F;4371&lt;&#x2F;td&gt;&lt;td&gt;(Redmine)&lt;&#x2F;td&gt;&lt;td&gt;337&#x2F;589&lt;&#x2F;td&gt;&lt;td&gt;(Jira)&lt;&#x2F;td&gt;&lt;td&gt;2645&#x2F;6384&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;% Open&lt;&#x2F;td&gt;&lt;td&gt;10.9%&lt;&#x2F;td&gt;&lt;td&gt;40.3%&lt;&#x2F;td&gt;&lt;td&gt;36.4%&lt;&#x2F;td&gt;&lt;td&gt;9.4%&lt;&#x2F;td&gt;&lt;td&gt;29.3%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Commits&lt;&#x2F;td&gt;&lt;td&gt;14,366&lt;&#x2F;td&gt;&lt;td&gt;12,387&lt;&#x2F;td&gt;&lt;td&gt;12,721&lt;&#x2F;td&gt;&lt;td&gt;20,210&lt;&#x2F;td&gt;&lt;td&gt;54,144&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;% by 1&lt;&#x2F;td&gt;&lt;td&gt;18.6%&lt;&#x2F;td&gt;&lt;td&gt;20.6%&lt;&#x2F;td&gt;&lt;td&gt;15.4%&lt;&#x2F;td&gt;&lt;td&gt;17.7%&lt;&#x2F;td&gt;&lt;td&gt;13.3%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;% by 6&lt;&#x2F;td&gt;&lt;td&gt;34.2%&lt;&#x2F;td&gt;&lt;td&gt;53%&lt;&#x2F;td&gt;&lt;td&gt;39.2%&lt;&#x2F;td&gt;&lt;td&gt;37.4%&lt;&#x2F;td&gt;&lt;td&gt;34.2%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The &quot;age&quot; is how long from when it was founded to 2015, based on the &quot;first release&quot; dates found on &lt;a href=&quot;http:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Comparison_of_open-source_configuration_management_software&quot;&gt;wikipedia&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;language&quot; is what that Wikipedia page reports it being implemented in, confirmed by examining the GitHub language stats.&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;people&quot; stat is the number of unique contributors that GitHub identifies as having contributed to the main tree (the same repos as the licenses were linked from above).&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;issues&quot; stat is the number of open vs the number of closed issues in their GitHub issue tracker, if that&#x27;s what they&#x27;re using. Otherwise, I noted what issue tracking tool they&#x27;re using instead.&lt;&#x2F;p&gt;
&lt;p&gt;Percent open is the number of open issues divided by the total number of issues (open and closed). As far as I could tell, &lt;a href=&quot;https:&#x2F;&#x2F;dev.cfengine.com&#x2F;projects&#x2F;core&#x2F;&quot;&gt;the CFEngine Redmine&lt;&#x2F;a&gt; appears to have 754 open issues of 1871 issues total, and &lt;a href=&quot;https:&#x2F;&#x2F;tickets.puppetlabs.com&#x2F;browse&#x2F;PUP&#x2F;&quot;&gt;the Puppet Jira&lt;&#x2F;a&gt; appears to have 1,260 issues open out of 13475 total.&lt;&#x2F;p&gt;
&lt;p&gt;Of the total commits reported by GitHub, I used the contributor graphs to calculate what percentage of the commits were by the top 1 most prolific contributor, and by the top 6. I had no mathematically compelling reason to choose 6 as the threshhold, but it tends to encompass people who&#x27;ve joined later in the project&#x27;s lifecycle and become prolific contributors as well as those who were there from the very beginning.&lt;&#x2F;p&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s an obvious spectrum of workflows contrasting CFEngine, Chef&#x2F;Puppet, and Salt&#x2F;Ansible. The newer tools are clearly using a more open development process and successfully getting contributions from more people. I&#x27;m not sure exactly what the numbers on open tickets say about a project&#x27;s workflow, but they&#x27;re interesting to look at.&lt;&#x2F;p&gt;
&lt;p&gt;It was interesting to examine the GitHub contribution graphs for these projects, because they offer a visual representation of the ebb and flow of contributions that people have put into a given project. The &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;puppetlabs&#x2F;puppet&#x2F;graphs&#x2F;contributors&quot;&gt;puppet graph&lt;&#x2F;a&gt; tells a story of lak starting the project then leaving and hlindberg taking over. The &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible&#x2F;graphs&#x2F;contributors&quot;&gt;ansible graph&lt;&#x2F;a&gt; shows a similar situation, in which mpdehaan left around October, as jimi-c transitioned in to take their place. The &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;saltstack&#x2F;salt&#x2F;graphs&#x2F;contributors&quot;&gt;salt graph&lt;&#x2F;a&gt; is quite different, in that thatch45 has been slowly and steadily contributing from the very beginning of the project but hasn&#x27;t moved on in the way that the original authors of other tools did.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;cross-platform&quot;&gt;Cross-Platform?&lt;&#x2F;h1&gt;
&lt;p&gt;All the tools I&#x27;m looking at support Linux for both the server (if their model has a server) and the client machines. The real question is whether they&#x27;ll support Mac, Windows, and BSD clients gracefully and in a well-documented manner.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Ansible:&lt;&#x2F;strong&gt; Works on OSX if Xcode and Python are installed, and can support Windows using Powershell instead of SSH. Works on FreeBSD. First hits were blogs, though official docs are easy to find. Provides a module for EC2 support.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;CFEngine:&lt;&#x2F;strong&gt; Can be installed on Mac with a Homebrew recipe. Community edition (the free kind) can be installed with Cygwin after a bit of fiddling around with dependencies; commercial version supports Windows out of the box. Works on FreeBSD. First hits were blogs and sales pitches for enterprise edition. Has a demo for EC2, so I guess that means it works?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Chef:&lt;&#x2F;strong&gt; They provide &lt;a href=&quot;https:&#x2F;&#x2F;www.chef.io&#x2F;chef&#x2F;install&#x2F;&quot;&gt;an installer&lt;&#x2F;a&gt; which allegedly Just Works. The installer and some docs were the first hits when I searched. Provides &lt;code&gt;knife ec2&lt;&#x2F;code&gt; plugin.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Puppet:&lt;&#x2F;strong&gt; Has official support on Mac and Windows, and looks like good community support on FreeBSD. Official docs were the first hits for Mac and Windows, and Puppet-specific community forum was first hit for FreeBSD. First hit for EC2 support looks like a sales pitch, though their &lt;a href=&quot;https:&#x2F;&#x2F;puppetlabs.com&#x2F;blog&#x2F;rapid-scaling-with-auto-generated-amis-using-puppet&quot;&gt;auto-generated AMIs&lt;&#x2F;a&gt; thing looks neat.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Salt:&lt;&#x2F;strong&gt; Has official support for Windows, Mac, and FreeBSD. Mac support has options of Homebrew, MacPorts, and pip. For Windows installation, they provide an exe. Same section of the docs was first hit to all 3 searches. &quot;Salt Cloud&quot; offers an &lt;code&gt;ec2&lt;&#x2F;code&gt; provider.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;secrets-and-security&quot;&gt;Secrets and Security&lt;&#x2F;h1&gt;
&lt;p&gt;On the whole, any configuration management will result in a more secure system than none. Most vulnerabilities come from running old, unpatched versions of common utilities with known bugs, and config management makes it easy to keep systems up to date. However, any program that you run on a server can itself have bugs which introduce vulnerabilities.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Ansible&lt;&#x2F;strong&gt;: To my inexpert sensibilities, the agentless model (you don&#x27;t run an Ansible daemon on each managed machine, unlike the other CM tools) seems like it could limit the impact of an error in the tool itself.&lt;&#x2F;p&gt;
&lt;p&gt;For keeping certain variables secret, you can &lt;a href=&quot;http:&#x2F;&#x2F;hakunin.com&#x2F;six-ansible-practices#keep-your-secret-vars-separate&quot;&gt;keep them in another file&lt;&#x2F;a&gt; and add it to your &lt;code&gt;.gitignore&lt;&#x2F;code&gt;. There&#x27;s also a &lt;a href=&quot;http:&#x2F;&#x2F;docs.ansible.com&#x2F;playbooks_vault.html&quot;&gt;vault playbook&lt;&#x2F;a&gt; to automate encrypting those files which contain sensitive information.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;CFEngine&lt;&#x2F;strong&gt;: The best practices manual basically says &lt;a href=&quot;https:&#x2F;&#x2F;auth.cfengine.com&#x2F;archive&#x2F;manuals&#x2F;cf3-bestpractice#Security&quot;&gt;get thee to a security policy&lt;&#x2F;a&gt;, which doesn&#x27;t really help. Googling didn&#x27;t help either, so I asked some colleagues, who responded by asking whether &quot;don&#x27;t use CFEngine&quot; was an option. If there exists a best practice, it&#x27;s so inconvenient to find that it&#x27;s relatively unhelpful.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Chef&lt;&#x2F;strong&gt;: Chef supports &lt;a href=&quot;https:&#x2F;&#x2F;docs.chef.io&#x2F;chef&#x2F;essentials_data_bags.html&quot;&gt;encrypted data bags&lt;&#x2F;a&gt;, which can then only be edited via knife or the management console. There are also a &lt;a href=&quot;https:&#x2F;&#x2F;coderanger.net&#x2F;chef-secrets&#x2F;&quot;&gt;variety of other options&lt;&#x2F;a&gt;. An interesting solution is &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;poise&#x2F;citadel&quot;&gt;citadel&lt;&#x2F;a&gt;, which uses AWS as a trusted third party to control which nodes get access to secrets, but in turn requires the entire infrastructure to be AWS-based. Although relying solely on AWS would be possible at this point in the Rust infrastructure&#x27;s development, I&#x27;m reluctant to needlessly commit us to sticking with it in the future.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Puppet&lt;&#x2F;strong&gt;: The &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;crayfishx&#x2F;hiera-gpg&quot;&gt;hiera-gpg&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;puppetlabs.com&#x2F;blog&#x2F;encrypt-your-data-using-hiera-eyaml&quot;&gt;hiera-eyaml&lt;&#x2F;a&gt; tools result in sufficiently secure files that major open source projects like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;apache&#x2F;infrastructure-puppet&#x2F;blob&#x2F;deployment&#x2F;data&#x2F;common.eyaml&quot;&gt;Apache&#x27;s infrastructure&lt;&#x2F;a&gt; are comfortable with publishing them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Salt&lt;&#x2F;strong&gt;: Salt uses &lt;a href=&quot;http:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;topics&#x2F;pillar&#x2F;index.html&quot;&gt;pillars&lt;&#x2F;a&gt; to expose data to target minions. There&#x27;s also a &lt;a href=&quot;http:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;ref&#x2F;renderers&#x2F;all&#x2F;salt.renderers.gpg.html&quot;&gt;gpg renderer&lt;&#x2F;a&gt; for encrypting data to be stored in source control, much like all the other modern encryption solutions I&#x27;m examining.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-paradigms&quot;&gt;The Paradigms&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the elevator pitch for what each tool purports to do, and how they do it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ansible&quot;&gt;Ansible&lt;&#x2F;h1&gt;
&lt;p&gt;You write a YAML description of your inventory, or which hosts should be available. Then you describe tasks to perform in each role, and write Playbooks which map between hosts and roles. You can then run Ansible from any machine (there&#x27;s no dedicated master, nor daemon on the machines being managed) and it uses SSH to remote into the nodes and execute the commands which all those YAML files described.&lt;&#x2F;p&gt;
&lt;p&gt;Because of its simplicity, Ansible claims fewer consistency guarantees than other tools, and provides minimal debugging data about files that it changed.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;cfengine&quot;&gt;CFEngine&lt;&#x2F;h1&gt;
&lt;p&gt;When CFEngine was first created, configuration management as we know it today did not exist. As the &lt;a href=&quot;https:&#x2F;&#x2F;auth.cfengine.com&#x2F;the-history-of-cfengine&quot;&gt;history page&lt;&#x2F;a&gt; explains, by 2003 the code base had morphed into something that nobody fully understood, so it was rewritten into CFEngine 3. In a nutshell, CFEngine allows you to write promises that describe the desired state of a machine. You can read more at &lt;a href=&quot;https:&#x2F;&#x2F;auth.cfengine.com&#x2F;archive&#x2F;manuals&#x2F;cf3-quickstart&quot;&gt;the CFEngine 3 quickstart&lt;&#x2F;a&gt;, but be warned that the abstract yet condescending tone of the piece makes reading it feel like trying to learn Haskell.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chef&quot;&gt;Chef&lt;&#x2F;h1&gt;
&lt;p&gt;You write Ruby to write recipes describing everything necessary to configure a system, then gather those recipes into cookbooks. A community tool, &lt;a href=&quot;http:&#x2F;&#x2F;acrmp.github.io&#x2F;foodcritic&#x2F;&quot;&gt;foodcritic&lt;&#x2F;a&gt;, is available to test cookbooks for common errors and stylistic enforcement. Cookbooks also have run-lists, which specify the order in which recipes are applied.&lt;&#x2F;p&gt;
&lt;p&gt;Chef-client runs on every host being managed, and queries the chef-server to determine which changes should be applied.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;puppet&quot;&gt;Puppet&lt;&#x2F;h1&gt;
&lt;p&gt;You describe the desired state of your machines in Puppet&#x27;s DSL, nodes request information from the puppetmaster, the master provides the information, then the nodes run an agent which applies whatever changes are necessary to bring the machine into the described state. The &lt;a href=&quot;https:&#x2F;&#x2F;docs.puppetlabs.com&#x2F;puppet&#x2F;4.1&#x2F;reference&#x2F;architecture.html&quot;&gt;puppet architecture&lt;&#x2F;a&gt; is at the highest level pretty similar to CFEngine&#x27;s, with the massive advantage that you don&#x27;t have to go learn promise theory in order to understand it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;salt&quot;&gt;Salt&lt;&#x2F;h1&gt;
&lt;p&gt;Salt can be run masterless on each minion, but in production is run from a master. You write &lt;code&gt;.sls&lt;&#x2F;code&gt; files describing the desired state of each minion, which can optionally use data out of &lt;a href=&quot;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;topics&#x2F;pillar&#x2F;index.html&quot;&gt;pillars&lt;&#x2F;a&gt;. I don&#x27;t recognize the language used to describe salt states as being a standard I&#x27;ve worked with before, but it&#x27;s simple and blatantly obvious.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-opinions&quot;&gt;My Opinions&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Ansible&lt;&#x2F;strong&gt;: The minimalism and simplicity of Ansible seem seductive, but the same traits that make it easy to learn will make it less adapted to handling the edge cases which inevitably emerge at scale.&lt;&#x2F;p&gt;
&lt;p&gt;I really like the idea of Ansible and will consider switching to it once the community tools surrounding it are more mature. Googling and asking other users has failed to turn up any good way to report exactly what changes to a file Ansible overwrote when it ran. Although I don&#x27;t need such fine-grained reporting right away, the fact that they&#x27;re missing indicates that building nice-to-have features into my configuration management and monitoring later on would require spending a lot of time hacking on the tool itself.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;CFEngine&lt;&#x2F;strong&gt;: I worked extensively with CFEngine 2 at the OSU Open Source Lab, and my resulting confidence that CFEngine 3 really can&#x27;t be as hard as it looks is the only reason I reread that quickstart a few more times until it started making sense. Considering the other options available, I think it&#x27;d be needlessly cruel to point a novice contributor to the infrastructure at these docs and say &quot;first, learn the tool&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Although CFEngine is far better than no configuration management at all, it can&#x27;t compete with the other tools on this list for simplicity, documentation quality, and ease of collaborating on configurations written in it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Chef&lt;&#x2F;strong&gt;: I spent a lot of time modifying Chef configs for established systems during my time at Urban Airship, and on the whole their infrastructure automation workflow was great. Looking back at that success in more detail, I&#x27;m pretty sure that it resulted from using &lt;em&gt;any&lt;&#x2F;em&gt; modern configuration management in conjunction with appropriate levels of automation-inspiring &quot;laziness&quot;, rather than from any trait unique to Chef.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s a little tempting to take the tool that&#x27;s good-enough and familiar, but there were enough annoyances associated with it (starting with the need to go learn Ruby) that I&#x27;m willing to set it aside in favor of simpler, more contributable alternatives.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Puppet&lt;&#x2F;strong&gt;: It is really hard for me to make an &quot;unbiased&quot; comparison of Puppet to Salt, because the local Portland tech scene includes the headquarters of Puppet Labs and I&#x27;m friends with the authors of the &lt;a href=&quot;http:&#x2F;&#x2F;www.amazon.com&#x2F;Pro-Puppet-Spencer-Krum&#x2F;dp&#x2F;1430260408&#x2F;r&quot;&gt;puppet book&lt;&#x2F;a&gt;. This means that when I run into problems with Puppet and complain on IRC, I tend to get immediate answers from expert users. However, other contributors to our infrastructure wouldn&#x27;t necessarily have this advantage. In my experience Googling for answers to Puppet best practices questions, the results are comparably &quot;we want to sell you a solution&quot; to the CFEngine and Chef docs.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Salt&lt;&#x2F;strong&gt;: Servo is already &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;servo&#x2F;saltfs&quot;&gt;using salt&lt;&#x2F;a&gt; to manage its infrastructure, and I&#x27;d like to eventually end up with Rust and Servo using the same configuration management solution. I&#x27;m more comfortable reading Python than Ruby, and of the models that rely on a master server, Salt is the newest, least commercial-feeling, and most straightforward to learn.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;salt-vs-ansible&quot;&gt;Salt vs Ansible&lt;&#x2F;h2&gt;
&lt;p&gt;So now I have a problem: Two great tools, either of which would be astronomically better than the current state.&lt;&#x2F;p&gt;
&lt;p&gt;When I look farther ahead at automating the Rust infrastructure, I anticipate that I&#x27;ll be doing a lot of work with parallelizing buildbot runs (to speed things up and also prepare for an eventual migration to &lt;a href=&quot;http:&#x2F;&#x2F;docs.taskcluster.net&#x2F;&quot;&gt;TaskCluster&lt;&#x2F;a&gt; once its features catch up to our needs). There&#x27;s a lot of overhead involved in setting up a host as a buildbot worker, compiling and setting up depenencies for the test suite, so it would be nice to pick a solution that makes it easy to build containers or AMIs.&lt;&#x2F;p&gt;
&lt;p&gt;It looks like both &lt;a href=&quot;http:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;ref&#x2F;states&#x2F;all&#x2F;salt.states.dockerio.html&quot;&gt;salt&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;blog.codeship.com&#x2F;packer-ansible&#x2F;&quot;&gt;ansible&lt;&#x2F;a&gt; play nicely with Packer, which would be a good choice for constructing build workers with all the dependencies already in place.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, there&#x27;s a &lt;a href=&quot;https:&#x2F;&#x2F;missingm.co&#x2F;2013&#x2F;06&#x2F;ansible-and-salt-a-detailed-comparison&#x2F;&quot;&gt;blog post by missingm&lt;&#x2F;a&gt;, which links repos that perform identical tasks using both Salt and Ansible. I agree with the blog post&#x27;s conclusion that the Ansible playbook is easier to read than the Salt states, but the real challenge is to determine how hard each type of configuration will be to write well.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Recording Screencasts on Arch</title>
        <published>2015-06-01T00:00:00+00:00</published>
        <updated>2015-06-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/06/01/recording_screencasts_on_arch/"/>
        <id>https://edunham.net/2015/06/01/recording_screencasts_on_arch/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/06/01/recording_screencasts_on_arch/">&lt;p&gt;Today I learned that there&#x27;s a trick to getting sound to work on Arch using &lt;a href=&quot;http:&#x2F;&#x2F;recordmydesktop.sourceforge.net&#x2F;about.php&quot;&gt;recordmydesktop&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-issue&quot;&gt;The Issue&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;yaourt -S recordmydesktop
&lt;&#x2F;span&gt;&lt;span&gt;recordmydesktop test.ogv
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Was met with the classic error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Couldn&amp;#39;t open PCM device hw:0,0
&lt;&#x2F;span&gt;&lt;span&gt;Error while opening&#x2F;configuring soundcard hw:0,0
&lt;&#x2F;span&gt;&lt;span&gt;Try running with the --no-sound or specify a correct device.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;&#x2F;h1&gt;
&lt;p&gt;I tried passing all the devices from &lt;code&gt;&#x2F;dev&#x2F;snd&lt;&#x2F;code&gt;, and none of them worked.&lt;&#x2F;p&gt;
&lt;p&gt;To see whether a graphical UI might install some silent dependency, I installed &lt;code&gt;qt-recordmydesktop&lt;&#x2F;code&gt; and ran it. This failed to record in the same way as before, but an advanced settings tab mentioned that &lt;code&gt;jackd&lt;&#x2F;code&gt; was not running.&lt;&#x2F;p&gt;
&lt;p&gt;I then manually tried to start &lt;a href=&quot;http:&#x2F;&#x2F;jackaudio.org&#x2F;&quot;&gt;jack&lt;&#x2F;a&gt; by invoking &lt;code&gt;jackd&lt;&#x2F;code&gt; at the command line, and hit a useful error message emitted by &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jackaudio&#x2F;jack1&#x2F;blob&#x2F;master&#x2F;config&#x2F;os&#x2F;gnu-linux&#x2F;sanitycheck.c&quot;&gt;the sanity check&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-fix&quot;&gt;The Fix&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;recordmydesktop&lt;&#x2F;code&gt; was unable to work out of the box because the user as whom I invoked it was not a member of the audio group:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;usermod -a -G audio username
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;made the error disappear, and now &lt;code&gt;recordmydesktop&lt;&#x2F;code&gt; works with the default device.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;epilogue&quot;&gt;Epilogue&lt;&#x2F;h1&gt;
&lt;p&gt;My first successful recording, test.ogv, is about 5 seconds of me wondering aloud where the program went, cursing at it, and typing loudly to &lt;code&gt;killall recordmydesktop&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Oh, Windows...</title>
        <published>2015-05-23T00:00:00+00:00</published>
        <updated>2015-05-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/05/23/oh_windows/"/>
        <id>https://edunham.net/2015/05/23/oh_windows/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/05/23/oh_windows/">&lt;p&gt;I got a shiny new Thinkpad X1 Carbon 3rd Gen for my new job. It came with Windows pre-installed. Out of morbid curiosity and willingness to consider giving this shiny new allegedly-less-terrible Win8 thing a chance, I booted it up into the default Windows installation before wiping everything to install arch.&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Hi there! Tell us a bunch of information!&lt;&#x2F;p&gt;
&lt;p&gt;Me: Okay...&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Log in to your Microsoft account!&lt;&#x2F;p&gt;
&lt;p&gt;Me: Can I skip this? I can&#x27;t skip this? I have no such thing. Let&#x27;s stick some bogus credentials in and see how it fails.&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Aww, those credentials were wrong. Want to just skip this step?&lt;&#x2F;p&gt;
&lt;p&gt;Me: YES! I would swear that link wasn&#x27;t there before. Okay... &lt;em&gt;sets up local account&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Windows: I&#x27;m going to reboot now.&lt;&#x2F;p&gt;
&lt;p&gt;Me: Ah, good ol&#x27; classic Windows.&lt;&#x2F;p&gt;
&lt;p&gt;Windows: &lt;em&gt;reboots quickly, then screen gradients from color to color while installing apps&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Me: Whoa, trippy!&lt;&#x2F;p&gt;
&lt;p&gt;Windows: &lt;em&gt;shows relatively familiar-looking Windows-ey desktop&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Me: Hmm, my skills from middle school might still apply. &lt;em&gt;opens internet exploder, goes to mozilla.com&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Here&#x27;s the mozilla site! Here&#x27;s a big button for downloading Firefox.&lt;&#x2F;p&gt;
&lt;p&gt;Me: Thank you, Moz, for knowing that&#x27;s why I&#x27;m here.&lt;&#x2F;p&gt;
&lt;p&gt;Windows: You wanna install this? You sure?&lt;&#x2F;p&gt;
&lt;p&gt;Me: Yes, sure I&#x27;m sure. Oh, and let&#x27;s see if we can partition disks politely from that little tool that used to be around here somewhere. &lt;em&gt;clicks the thing where the start menu should be&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Hello human, I hear that humans find squares and bright colors aesthetically satisfying! Here are many bright colors, and many squares!&lt;&#x2F;p&gt;
&lt;p&gt;Me: Ogod make it stop... they warned me about this... &lt;em&gt;slams window key&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Aww, okay, here&#x27;s your desktop back.&lt;&#x2F;p&gt;
&lt;p&gt;Me: &lt;em&gt;types &quot;windows 8 how to partition disks&quot; into exploder address bar, since ff is still downloading&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Here are some Bing hits for what you want! First one is some disk-partition.com thing.&lt;&#x2F;p&gt;
&lt;p&gt;Me: Okay... &lt;em&gt;reads&lt;&#x2F;em&gt;... windows+r, type &lt;code&gt;diskmanagement.msc&lt;&#x2F;code&gt;. Yay, it&#x27;s almost like a fake command prompt!&lt;&#x2F;p&gt;
&lt;p&gt;Windows: You have 5 partitions! 1,000MB, 260MB, 217GB Windows, 12.42GB Recovery, and 7GB OEM!&lt;&#x2F;p&gt;
&lt;p&gt;Me: Congrats. Let&#x27;s delete that recovery stuff; I like living dangerously.&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Nope. Not gonna show you those buttons; you might hurt yourself.&lt;&#x2F;p&gt;
&lt;p&gt;Me: Alrighty then, is there sudo or anything? &lt;em&gt;checks help docs&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Windows: Haha nope. Also, have an error about how you&#x27;re trying to run the system updater twice at once, even though you didn&#x27;t invoke either.&lt;&#x2F;p&gt;
&lt;p&gt;Me: Fine then, have it your way. &lt;em&gt;wipes entire disk and installs Arch Linux&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Open Infrastructure</title>
        <published>2015-05-20T00:00:00+00:00</published>
        <updated>2015-05-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/05/20/open_infrastructure/"/>
        <id>https://edunham.net/2015/05/20/open_infrastructure/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/05/20/open_infrastructure/">&lt;h1 id=&quot;my-new-ops-job&quot;&gt;My New Ops Job&lt;&#x2F;h1&gt;
&lt;p&gt;Next week, I&#x27;m starting work as the only DevOps Engineer on the Mozilla Research team. While I&#x27;ve held a variety of superficially similar jobs in the past few years, this one offers a special opportunity to apply the values I appreciate as a software developer to the infrastructure design and maintenance which represents my work as an ops guy.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;wait-what-s-devops&quot;&gt;Wait, what&#x27;s DevOps?&lt;&#x2F;h1&gt;
&lt;p&gt;Many of the operations engineers whom I look up to regard the term &quot;DevOps&quot; as an absurd buzzword. I share their antipathy toward its use alongside &quot;cloud&quot; to mean some mysterious panacea of modernization, often spouted by the same technologically illiterate individuals who&#x27;d assume a hypervisor to be some sort of new-and-improved sunshade.&lt;&#x2F;p&gt;
&lt;p&gt;Although it could do with a less abused name, the modern systems administration skills that companies are seeking when they open a job req for &quot;DevOps Engineer&quot; deserve to be differentiated from &quot;old-school operations&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;In my opinion, the most useful meaning of &quot;DevOps&quot; is to describe to the paradigm in which infrastructure is code. In this sense, the operations team are necessarily developers, since their main goal is to write code which describes the tasks that they would have had to do manually in the olden days. This change of perspective encourages a &quot;DevOps&quot; team to apply best practices which were unattainable back when every server was a special snowflake: Testing, deployment automation, version control of configurations, and code review, to name only a few examples.&lt;&#x2F;p&gt;
&lt;p&gt;There are those who use the vagueness of &quot;DevOps&quot; to justify asking one person (or group) to perform the roles of an entire development team and an entire operations team simultaneously. This could concievably have some benefits, such as improved communication between teams, but it mostly misses the point. I&#x27;ve talked to many of my peers unfortunate enough to end up in the misguided type of &quot;DevOps&quot; role, and learned that they tend to spend about 90% of their time with one of the two hats on and only switch to the other when they have to.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;so-infrastructure-is-code&quot;&gt;So, Infrastructure is Code...&lt;&#x2F;h1&gt;
&lt;p&gt;Yes! And that&#x27;s where it all gets interesting. Mozilla&#x27;s values of openness and the Research team&#x27;s experimental nature mean that the code I&#x27;ll write to solve infrastructure problems has no secrets in its &lt;em&gt;design&lt;&#x2F;em&gt;. There are a handful of necessary secrets related to our &lt;em&gt;instantiation&lt;&#x2F;em&gt; of the code, such as the AWS credentials of accounts whose bill Mozilla pays, but they don&#x27;t have nearly the crippling impact on openness that a &lt;em&gt;design&lt;&#x2F;em&gt; secret would.&lt;&#x2F;p&gt;
&lt;p&gt;At a typical software company, enabling anyone else to precisely duplicate your product is a Really Bad Idea (TM). Any company that sells software has to have some secrets -- people won&#x27;t usually pay for code that they could get for free. There are a few software-related companies which appear to operate quite profitably without secrets, but examine them more closely and you&#x27;ll find that they&#x27;re actually selling a &lt;em&gt;service&lt;&#x2F;em&gt; like support. When a company has some secret in their code&#x27;s design, concern for protecting it trickles down and metamorphoses into concern for hiding the details of the infrastructure necessary to deploy the code as well.&lt;&#x2F;p&gt;
&lt;p&gt;If you debunk the illusion that sharing their infrastructure will harm the company, operations teams are still extremely reluctant to share their code with others. The typical excuse for opening only a choice library or two, rather than the entire ops codebase, is embarrassment at its quality. Cloaked in the altruistic guise of &quot;I don&#x27;t think our code would help anyone else, and might lead them astray&quot;, the underlying sentiment is one of regret that a given infrastructure project never met those lofty criteria to which its authors aspired. It&#x27;s the same psychological effect that discourages amateur artists -- we gain the ability to recognize &lt;em&gt;good&lt;&#x2F;em&gt; art (or code) more quickly than the ability to produce it ourselves. And in the case of ops, we often ship code which works but could be greatly improved if only more time was available. Publicly sharing work that one knows was only &quot;good enough&quot; is scary and embarrassing, and I don&#x27;t fault people for their reluctance to do it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;so-we-should-be-sharing-it&quot;&gt;...So we should be sharing it!&lt;&#x2F;h1&gt;
&lt;p&gt;At least, I think so. I think it&#x27;s rare and wonderful to have an important infrastructure project with no reason to keep its code secret, and I have hoped from the beginning that whoever gets this Mozilla Research DevOps Engineer job will take advantage of the opportunity. It&#x27;s entirely a selfish wish, and I have it because I find myself teaching devops concepts to newbies a lot and wanting some really good, real-world repositories to point them at as examples. I was pretty outspoken about these opinions throughout the interview process, so I can only conclude that the team which decided to hire me concurrs.&lt;&#x2F;p&gt;
&lt;p&gt;To recap, being Mozilla Research&#x27;s very own in-house &quot;DevOps&quot; Engineer is a unicorn of a job because it has cleared the first hurdle that prevents infrastructure-as-code from being open source, and has a running start and a tailwind toward the second. The challenge we&#x27;ve already overcome is that of &lt;em&gt;design&lt;&#x2F;em&gt; secrets, critical company information which would be leaked by sharing even a sanitized version of the infrastructure, and we&#x27;ve overcome it by not having any. The major remaining challenge is that publishing work done in a hurry is scary, because it might be bad and might make me look bad to have it out there with my name on it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;if-we-build-it-will-they-come&quot;&gt;If We Build It, Will They Come?&lt;&#x2F;h1&gt;
&lt;p&gt;That is, in my opinion, the hardest question of all. If I publish the infrastructure that I&#x27;m building in real time, warts and all, will anyone benefit?&lt;&#x2F;p&gt;
&lt;p&gt;If the infrastructure works at all, and includes sufficient documentation for anyone to set up their own instance of it if they so desire, then students wishing to learn about &quot;DevOps&quot; in the real world have already benefitted.&lt;&#x2F;p&gt;
&lt;p&gt;Selfishly, though, I&#x27;d like to get some contributions back. In my experience with the kinds of code that do and don&#x27;t get contributors, here are some factors which I think will be important to gaining contributions from people who aren&#x27;t me:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Relevance. Mozilla Research is a great place to be for this, because Rust is a shiny new programming language with quite a bit of attention on it, so the code that describes its continuous integration and hosting is likely to be of interest to people experimenting with it.&lt;&#x2F;li&gt;
&lt;li&gt;Prestige. People want to get commits into a project that their peers and prospective employers have heard about. With the right promotion, the build infrastructure for Rust and Servo piggyback on the projects&#x27; fame.&lt;&#x2F;li&gt;
&lt;li&gt;Prove Someone Wrong. If I offer the infrastructure as a canonical example of how to correctly use the tools it&#x27;s built on, I will offend and anger the power users and authors of those tools, and they will very likely point out many places where my code needs improvement. Sufficient repetitions of this cycle should cause the code to converge into a state which both works and appears to be a good example of how to use the tools. If it&#x27;s offered as a real-world example in the tools&#x27; tutorials and documentation, that puts a bunch of newbie eyes on the code, and the resulting questions are a great way to identify additional areas for improvement.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So in the best case I&#x27;ll get a bunch of help and advice. In the worse case, where everyone totally ignores it, I&#x27;ll still be better off because the imagined public eye will push me to document my decisions better and adhere to higher standards of code quality.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Starting Rust: Introduction</title>
        <published>2015-05-07T00:00:00+00:00</published>
        <updated>2015-05-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/05/07/starting_rust/"/>
        <id>https://edunham.net/2015/05/07/starting_rust/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/05/07/starting_rust/">&lt;p&gt;When discussing my experience (or lack thereof) with the Rust programming language during a recent interview, I learned that experienced developers might be interested in a stream-of-consciousness, &quot;let&#x27;s-play&quot; type narration of my learning curve through the language.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s an excrutiatingly detailed account of it below the fold. You&#x27;ve been warned.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;setting&quot;&gt;Setting&lt;&#x2F;h1&gt;
&lt;p&gt;It&#x27;s 8:30pm by my internal clock, 9:30pm local time, and I&#x27;m chilling in a hotel room in Provo, Utah and feel like doing something useful. So, it&#x27;s time to learn some Rust!&lt;&#x2F;p&gt;
&lt;p&gt;I travel with a Thinkpad X201, a nice solid box with a small screen, good keyboard, XKCD mouse, and habit of getting a bit toasty when asked to do any heavy lifting.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;my-background&quot;&gt;My Background&lt;&#x2F;h1&gt;
&lt;p&gt;I first touched C about 8 years ago, then started Python and Java 5 years ago, and taught a bit of C++ (though I still won&#x27;t claim proficiency in it) 4 years ago. I use Python for most things, and C when I have to. In the past 5 years I&#x27;ve played with (but not built anything particularly notable in) a couple Lisps, Haskell, Prolog, Forth, Go, Javascript, and the E derivative Monte.&lt;&#x2F;p&gt;
&lt;p&gt;I skimmed a Rust tutorial the other day and got about as far as noticing that the syntax for guards reminds me of the E paradigms that I&#x27;ve learned through Monte, and overall it looks more or less like any other C-derived syntax. I&#x27;m wondering whether it will have arrows in it (their use in Monte has been explained a few times to me, but not in a way that&#x27;s stuck yet), and if so, whether they&#x27;ll be explained well.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;finding-a-tutorial&quot;&gt;Finding a Tutorial&lt;&#x2F;h1&gt;
&lt;p&gt;I Google technical topics when logged into my primary account in the same browser. This helps me find pages in my history across devices easily, and gives it a contextual clue that when I say &quot;patchwork&quot; I&#x27;m more likely to be solving a Django problem than a quilting one. Results look like this...&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;google-search-rust-game.png&quot; alt=&quot;Google search results for &amp;quot;Rust&amp;quot; showing video game instead of programming language&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;...And I mutter under my breath about someday getting around to writing that lightning talk on naming things. No, I&#x27;m not interested in the video game.&lt;&#x2F;p&gt;
&lt;p&gt;I guess I&#x27;ll try the first hit first, though I&#x27;ll bet it&#x27;ll redirect me to the second. If I get frustrated or stuck, the third will always be there, but honestly I&#x27;d forget that it exists if I wasn&#x27;t writing this down right now.&lt;&#x2F;p&gt;
&lt;p&gt;So... &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;tutorial.html&quot;&gt;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;tutorial.html&lt;&#x2F;a&gt; is a deprecation notice in favor of the book. That&#x27;s cute, I guess. Oh, and scripts are blocked... I guess I&#x27;ll globally allow rust-lang.org, though I rather wonder why it needs js on a mere redirection page. There, I allowed it, and the fonts got prettier. Was that really necessary?&lt;&#x2F;p&gt;
&lt;p&gt;Ok, that link about the book takes me to &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;book&#x2F;&quot;&gt;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;book&#x2F;&lt;&#x2F;a&gt; . That&#x27;s... not the book that showed up in Google hits. Okay then? Skim the usual first page.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-book-introduction&quot;&gt;The Book, Introduction&lt;&#x2F;h1&gt;
&lt;p&gt;Huh, there are 7 sections, starting with the introduction, then a bunch of links. I remember those! I clicked straight through to the &quot;syntax and semantics&quot; thing last time I looked at it, because I was in a huge hurry. Scroll down a little; it has contributing guide.&lt;&#x2F;p&gt;
&lt;p&gt;When last I looked, I assumed the intro ended after the contributing section, but now that I&#x27;m being a bit more thorough so I don&#x27;t look silly in a blog I scroll down further. Looks like there&#x27;s an introduction thing, picking out the key points of the language.&lt;&#x2F;p&gt;
&lt;p&gt;Do I have to read that intro, or should I just grab one of those 7 links? Do I have to read the links in order? May I jump around? I see now why Knuth directed the readers of TAOCP with psuedocode and flowcharts.&lt;&#x2F;p&gt;
&lt;p&gt;I guess this brief intro thing will answer the &quot;why&quot; questions I might run into later.&lt;&#x2F;p&gt;
&lt;p&gt;So, yeah, it starts with code:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;fn main() {
&lt;&#x2F;span&gt;&lt;span&gt;    let mut x = vec![&amp;quot;Hello&amp;quot;, &amp;quot;world&amp;quot;];
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Huh, copying that code caused me to mouse over the code block, which pops up a little arrow-ish icon in the upper right of the block... That arrow takes me to a rather odd URL:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;http:&#x2F;&#x2F;play.rust-lang.org&#x2F;?code=fn%20main%28%29%20{%0A%20%20%20%20let%20mut%20x%20%3D%20vec![%22Hello%22%2C%20%22world%22]%3B%0A}%0A
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Whoa, it&#x27;s some kind of in-browser Rust environment! That&#x27;s bloody awesome, except &quot;Firefox can&#x27;t find the server at play.rust-lang.org&quot;. Womp womp, sad trombone.&lt;&#x2F;p&gt;
&lt;p&gt;Wait, no, dropping &quot;play.rust-lang.org&quot; into the browser takes me to the &lt;code&gt;https&lt;&#x2F;code&gt; version of the site, which is totally up and happy. Really, no redirect? I sincerely doubt any security implications of redirecting everythign to the &lt;em&gt;one working site&lt;&#x2F;em&gt; would really be that serious compared to the annoyance implications of having the book&#x27;s examples broken. Maybe I should just fix the book?&lt;&#x2F;p&gt;
&lt;p&gt;And... now when I re-click the arrow thingy, it does redirect. Is it the hotel wifi&#x27;s fault, or the server? Probably the wifi. Oh well.&lt;&#x2F;p&gt;
&lt;p&gt;So yeah, that syntax... The &lt;code&gt;!&lt;&#x2F;code&gt; is cute; I suppose it might be an assignment but I&#x27;m so used to seeing it as negation that I&#x27;m tempted to read &quot;vec not hello world&quot;...&lt;&#x2F;p&gt;
&lt;h1 id=&quot;get-distracted-by-macros&quot;&gt;Get distracted by macros&lt;&#x2F;h1&gt;
&lt;p&gt;Ok, so it makes a &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;&#x2F;code&gt;. I think I saw that &lt;code&gt;type&amp;lt;thing&amp;gt;&lt;&#x2F;code&gt; notation in... was it Go? I&#x27;m going to tentatively read it as &quot;a vector of things of type T&quot;, though that could be totally wrong.&lt;&#x2F;p&gt;
&lt;p&gt;Oh, the bang was a macro. So... constructors == macros? Close enough. I tend to think of macros as being like the &lt;code&gt;#define&lt;&#x2F;code&gt; in the C preprocessor, since that&#x27;s where I first met them -- macros in LaTeX (I guess I should&#x27;ve listed that among languages I&#x27;ve touched, but it&#x27;s really more markup?) also just expand into a chunk of code. Hmm, maybe I should actually click on &quot;macro&quot; where it&#x27;s blue.&lt;&#x2F;p&gt;
&lt;p&gt;Nope, I should not have clicked on that. It just takes me to &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;book&#x2F;macros.html&quot;&gt;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;book&#x2F;macros.html&lt;&#x2F;a&gt; , which is a huge long tangent... skim the first page... ooh, &quot;syntactic abstraction&quot;. So this all could still be a fancy way of saying &quot;expand this out into the other thing&quot;, just like the other places I&#x27;ve seen it? Scroll, scroll... yeah, okay. That&#x27;s not hugely more syntax than LaTeX macros, maybe less, and yes there are arrows (or perhaps &quot;oh no&quot;?), and I&#x27;d better get back to the bit that tells me what the syntax means before continuing with the part about expanding syntax into other syntax (yo dawg!).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;return-to-introduction&quot;&gt;Return to introduction&lt;&#x2F;h1&gt;
&lt;p&gt;Pop that tangent off the stack, and I&#x27;m back to introduction. Why am I tired... it&#x27;s only 9pm laptop time, 10pm local time. Maybe it&#x27;s all the brain-ing and the typing. Oh well, I&#x27;ll finish the intro for this post at least.&lt;&#x2F;p&gt;
&lt;p&gt;Ok, now we&#x27;re learning about mutable and bindings. I know about mutable vs immutable from some obnoxious mistakes early in my Python days; bindings are just a thing... are bindings perfectly equivalent to assignment? I think not; I think I may have gotten at least one lecture at some point on the difference, but I&#x27;ll just accept that they act sufficiently like assignment for the time being (since &lt;code&gt;bindings&lt;&#x2F;code&gt; isn&#x27;t a link to anything) and keep going.&lt;&#x2F;p&gt;
&lt;p&gt;And the bit about not needing type annotations is a whole paragraph bragging about the fact that it doesn&#x27;t force you to type stuff out when the stuff in question is so stupid-simple that even a computer could do it reliably. Well done. (mildly sarcastic applause, but thanks for telling me)&lt;&#x2F;p&gt;
&lt;p&gt;So, stack vs heap stuff. Scroll back up to the code, seek where the heck they&#x27;re getting the knowledge of where stuff&#x27;s allocated from... Oh, I wasn&#x27;t actually expected to know that yet. For some reason the way the words were put together made me feel like it was telling me about some bit of syntax which indicated where things went, but no.&lt;&#x2F;p&gt;
&lt;p&gt;Yes, I grok stacks vs heaps; I have 95% of a bachelor&#x27;s of Computer Science. No need to tangent down the rust-specifics rabbit hole just now.&lt;&#x2F;p&gt;
&lt;p&gt;Okay, you&#x27;re finally getting around to the business of ownership. Ok, stuff about when x goes out of scope... do you realize you&#x27;ve never mentioned what scopes are? Luckily I already know that I can usually assume that a scope is represented by a set of &lt;code&gt;{}&lt;&#x2F;code&gt;, and you&#x27;ve given me no reason to suspect things would be different here. the wording of &quot;x goes out of scope&quot; is a bit funny, like x &quot;goes&quot; anywhere once its scope is over... I thought things out of their scopes just &lt;em&gt;weren&#x27;t there&lt;&#x2F;em&gt;, just went poof. But okay. Yay, no gc, no malloc, hypothetically all solvable by a sufficiently smart compiler. And the determinism is a huge plus for testing anything.&lt;&#x2F;p&gt;
&lt;p&gt;Okay, next line. Ownership system, borrowing... aaand borrowing is blue. Great, another hugely long page of complex stuff I don&#x27;t know yet... though I guess I should click before judging. Yep, long page. Starting with &#x27;meta&#x27;. And I&#x27;m supposed to have read the ownership page first, and I&#x27;d bet that for that I&#x27;m supposed to have finished the introduction...&lt;&#x2F;p&gt;
&lt;h1 id=&quot;more-grumbling-about-format&quot;&gt;More Grumbling About Format&lt;&#x2F;h1&gt;
&lt;p&gt;It&#x27;s the best and worst part of static, linked-up tutorials, how they can express the circular dependencies of knowledge inherent to breaking into any new knowledge domain. It can be a cheat around teaching in what I regard as the right way, which is to handle the circle by spiralling out from 0 knowledge: explain the minimum knowledge of the term inline at first, then go into medium detail next time you come around to it, then greater detail later on. That&#x27;s how writing a talk (and presenting the info sequantially in real time) forces you to do it, but those constraints are gone on the web. Oh well.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;and-back&quot;&gt;And Back&lt;&#x2F;h1&gt;
&lt;p&gt;Back to the book. Ok, borrowed bindings don&#x27;t de-allocate when they go out of scope... but it&#x27;s not the &lt;em&gt;binding&lt;&#x2F;em&gt; that de-allocates, it&#x27;s the compiler. Such pedantry...&lt;&#x2F;p&gt;
&lt;p&gt;Baby&#x27;s first Rust error. I see why you introduced borrowing up there, after all. Sure, we pointed the intrinsically immutable y at x, so we kinda lose any benefit from making x mutable or we&#x27;d break y&#x27;s immutability promises... Ok, it&#x27;s a big wall on how you took away a foot-gun. That was nice of you. I think I&#x27;m okay with this.&lt;&#x2F;p&gt;
&lt;p&gt;And a bit more on what that &quot;going out of scope&quot; bit means, quite specifically! That&#x27;s neat. You even finally get around to mentioning what scopes are, a little.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-end-of-the-introduction&quot;&gt;The End (of the introduction)&lt;&#x2F;h1&gt;
&lt;p&gt;Today I learned that typing all this up takes an awful lot of typing. I think I&#x27;ll go retrieve one of the icecream bars that I stashed in the hotel fridge&#x27;s freezer, reward myself for a job thoroughly (albeit less than grammatically) done, and hit section 2 (Getting Started) tomorrow.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Command-line Keyboard Shortcuts</title>
        <published>2015-04-16T00:00:00+00:00</published>
        <updated>2015-04-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/04/16/command_line_keyboard_shortcuts/"/>
        <id>https://edunham.net/2015/04/16/command_line_keyboard_shortcuts/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/04/16/command_line_keyboard_shortcuts/">&lt;p&gt;Just another installment of &quot;How did I not know that already?&quot;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;backstory&quot;&gt;Backstory&lt;&#x2F;h1&gt;
&lt;p&gt;I came across a copy of the first edition of &lt;a href=&quot;http:&#x2F;&#x2F;www.nostarch.com&#x2F;howlinuxworks2&quot;&gt;How Linux Works&lt;&#x2F;a&gt; recently, and have been reading through it to see whether I missed anything important in my hodge-podge of formal and informal open source training.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s nice to be reassured that I have the right idea for all the basics that it&#x27;s covered so far. Along the way, it&#x27;s answering a few of those un-Googleable questions that aren&#x27;t really a big enough deal to warrant remembering to ask someone more knowledgeable about them.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;today-i-learned&quot;&gt;Today I Learned&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;Ctrl+A&lt;&#x2F;code&gt; jumps the cursor to the beginning of the line, and &lt;code&gt;Ctrl+E&lt;&#x2F;code&gt; jumps to the end. (The &lt;code&gt;Home&lt;&#x2F;code&gt; and &lt;code&gt;End&lt;&#x2F;code&gt; keys also work in my shell, but they&#x27;re slower and more awkward since they require moving one&#x27;s hands from home row)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Ctrl+W&lt;&#x2F;code&gt; deletes the word in which the cursor is currently located (words being defined as strings separated by spaces), and &lt;code&gt;Ctrl+U&lt;&#x2F;code&gt; deletes the entire line.&lt;&#x2F;p&gt;
&lt;p&gt;Perhaps less usefully, there are also control characters to subsitute for arrow keys, with easy mneumonics for them:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Sequence&lt;&#x2F;th&gt;&lt;th&gt;Arrow&lt;&#x2F;th&gt;&lt;th&gt;Mnemonic&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ctrl+B&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;left&lt;&#x2F;td&gt;&lt;td&gt;Back&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ctrl+F&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;right&lt;&#x2F;td&gt;&lt;td&gt;Forward&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ctrl+P&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;up&lt;&#x2F;td&gt;&lt;td&gt;Previous&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ctrl+N&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;down&lt;&#x2F;td&gt;&lt;td&gt;Next&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h1 id=&quot;other-relevant-facts&quot;&gt;Other Relevant Facts&lt;&#x2F;h1&gt;
&lt;p&gt;The book hasn&#x27;t covered these yet, but if the above was new to you, make sure you know these things too:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Ctrl+R&lt;&#x2F;code&gt; enters &lt;code&gt;reverse-i-search&lt;&#x2F;code&gt; mode, in which you type any substring of the command you want. As you type, it will match the most recent command in which the thing you typed appears.&lt;&#x2F;p&gt;
&lt;p&gt;The up and down arrow keys scroll through your command history. Page-up jumps to the first command in your history, and page-down jumps to the last. All that history lives in &lt;code&gt;~&#x2F;.bash_history&lt;&#x2F;code&gt; by default, and the &lt;code&gt;HISTFILESIZE&lt;&#x2F;code&gt; and &lt;code&gt;HISTSIZE&lt;&#x2F;code&gt; options in &lt;code&gt;~&#x2F;.bashrc&lt;&#x2F;code&gt; configure how much history gets saved.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Ctrl+C&lt;&#x2F;code&gt; kills the running program, &lt;code&gt;Ctrl+D&lt;&#x2F;code&gt; sends the &lt;code&gt;EOF&lt;&#x2F;code&gt; (end-of-file) character, and &lt;code&gt;q&lt;&#x2F;code&gt; quits you out of pretty much any program with &lt;code&gt;:&lt;&#x2F;code&gt; instead of your usualy &lt;code&gt;$&lt;&#x2F;code&gt; prompt.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The life cycle of a conference talk</title>
        <published>2015-04-15T00:00:00+00:00</published>
        <updated>2015-04-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/04/15/the_life_cycle_of_a_conference_talk/"/>
        <id>https://edunham.net/2015/04/15/the_life_cycle_of_a_conference_talk/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/04/15/the_life_cycle_of_a_conference_talk/">&lt;p&gt;Although lines of code are the most convenient metric of success in open source, I&#x27;m proud of those of my own contributions that have taken the form of documentation, teaching, and outreach.&lt;&#x2F;p&gt;
&lt;p&gt;One such skill that I&#x27;ve been working on improving over the past couple years is public speaking, specifically educational talks on a variety of open source related topics. At my current skill level, I&#x27;m finding it increasingly important to understand what works and what doesn&#x27;t in the process that I use when creating my talks. Below the fold will be a few pages of my thinking &quot;out loud&quot; about the topic.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-idea&quot;&gt;The Idea&lt;&#x2F;h1&gt;
&lt;p&gt;&quot;I should give a talk about that!&quot;&lt;&#x2F;p&gt;
&lt;p&gt;That sentence is my cue to write the idea down somewhere, because otherwise it will disappear. If I have any idea of what the talk&#x27;s content should include, it goes straight into a &lt;code&gt;.rst&lt;&#x2F;code&gt; file in my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;slides&quot;&gt;slides repo&lt;&#x2F;a&gt;. Whenever I see an interesting article or teaching tool that relates to the topic, I drop the link into that file.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;outline&quot;&gt;Outline&lt;&#x2F;h1&gt;
&lt;p&gt;The few hours when an idea is shiney and new and still makes sense in the context where it originated are the best time to outline a talk. I think of the outline as the brainstorming phase of story development: concepts from the outline often end up as sections of the final talk, but those which don&#x27;t fit can easily be thrown out along the way.&lt;&#x2F;p&gt;
&lt;p&gt;The better-developed I can get my outline, the easier it is to continue development of the idea later. It also helps me to write abstracts when submitting the talk to conferences.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;abstract&quot;&gt;Abstract&lt;&#x2F;h1&gt;
&lt;p&gt;The abstract is that single-paragraph summary of a talk which convinces a conference&#x27;s committee that the talk would be a good fit, and then appears in the program if the talk is selected.&lt;&#x2F;p&gt;
&lt;p&gt;A good abstract is essential to the success of my talks, because it guides the rest of the development process. The title and abstract are how I make sure that only people interested in what I have to say will show up to my talk. They&#x27;re the promises that the finished slide deck needs to fulfill.&lt;&#x2F;p&gt;
&lt;p&gt;With this in mind, I&#x27;ve identified some common traits in the abstracts which get accepted (vs the many talks I&#x27;ve had rejected from conferences).&lt;&#x2F;p&gt;
&lt;p&gt;A good abstract:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Starts with a hook that will catch the reader&#x27;s attention&lt;&#x2F;li&gt;
&lt;li&gt;Demonstrates understanding of the audience&#x27;s needs&lt;&#x2F;li&gt;
&lt;li&gt;Asks questions from the audience&#x27;s perspective, which the talk will answer&lt;&#x2F;li&gt;
&lt;li&gt;Summarizes why the presenter is qualified to teach the subject&lt;&#x2F;li&gt;
&lt;li&gt;Identifies any prerequisite knowledge that the audience will need to get the most benefit from the talk, or reassures beginners that their questions will be welcome&lt;&#x2F;li&gt;
&lt;li&gt;Ends with a recap of what the audience can expect to learn and an encouragement to attend&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;These factors can make it seem tempting to promise more than the talk can deliver. Don&#x27;t fall into that trap! If you have any doubt about your slides&#x27; ability to fulfill a promise made in the abstract, go write the slides for that part of the talk and make sure they&#x27;re sufficient before continuing.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;application&quot;&gt;Application&lt;&#x2F;h1&gt;
&lt;p&gt;Once I have an outline, abstract, and title for a talk, I&#x27;m ready to look for conferences whose attendees might be interested in it.&lt;&#x2F;p&gt;
&lt;p&gt;I use the site &lt;a href=&quot;http:&#x2F;&#x2F;calltospeakers.com&#x2F;&quot;&gt;Call To Speakers&lt;&#x2F;a&gt; to find new conferences, as well as applying to everything in my local area which overlaps with my knowledge and interests.&lt;&#x2F;p&gt;
&lt;p&gt;I typically submit 2-5 applications to conferences I&#x27;ve spoken at before and enjoyed, or interesting new conferences which:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Occur on dates when I expect to be available&lt;&#x2F;li&gt;
&lt;li&gt;Are near home or offer travel assistance for speakers (though this will be a much less pressing concern with a grownup job than it was during school)&lt;&#x2F;li&gt;
&lt;li&gt;Seem likely to interest the kinds of people whose company I enjoy, such as open source nerds&lt;&#x2F;li&gt;
&lt;li&gt;Have a reasonable code of conduct that shows they&#x27;re prepared to handle both real and imagined problems in a way that&#x27;s fair and reasonable&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;when-to-apply&quot;&gt;When To Apply&lt;&#x2F;h1&gt;
&lt;p&gt;A conference&#x27;s Call for Proposals (CFP) typically closes 3-9 months before the date of the conference, with speakers getting notified 1-2 months after that. Dates vary for each conference, of course.&lt;&#x2F;p&gt;
&lt;p&gt;After attending and enjoying a conference, I put &quot;check for &amp;lt;conference&amp;gt; CFP dates&quot; on my calendar for a reasonable-sounding time the next year. If there&#x27;s no update on CFP timing when my calendar emails me a reminder of that event, I move it forward a month and check again then.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;slides&quot;&gt;Slides&lt;&#x2F;h1&gt;
&lt;p&gt;After a talk gets accepted to a conference for the first time, I convert its outline into a slide deck. The purpose of the slides is twofold:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Slides are a good excuse for speaker notes on my screen, which help reassure me that I haven&#x27;t forgotten any of the important content&lt;&#x2F;li&gt;
&lt;li&gt;They give the audience something to look at, to reduce their tendency to look at their computers or phones. Ideally, the picture on each slide complements the topic I&#x27;m discussing on it, and slides change frequently enough to prevent anyone from getting bored.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;My favorite slide decks have averaged close to only 1 word per slide. My usual slide decks are pictures with section titles above them, interspersed with the occasional graph or bulleted list.&lt;&#x2F;p&gt;
&lt;p&gt;Google&#x27;s search features for finding images licensed for reuse are extremely useful in compiling this type of slides. Wikimedia images are especially nice because their pages have the citation pre-written for you.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;rehersal&quot;&gt;Rehersal&lt;&#x2F;h1&gt;
&lt;p&gt;This is the part of speaking which I hate most, and tend to procrastinate badly on. When the slides seem about 75% done, I fire up &lt;a href=&quot;http:&#x2F;&#x2F;audacity.sourceforge.net&#x2F;&quot;&gt;Audacity&lt;&#x2F;a&gt; and record my entire talk as if I&#x27;m presenting it at the conference.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s horrible. It&#x27;s always horrible. There&#x27;s a certain quota of horrible-ness in a talk that&#x27;s guaranteed to be there at first, and the only way to get it out is through rehersals.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t take notes while recording the rehersal, since that would mess up my estimate of how long the talk takes, but I do jot down some notes on slide order and which transitions were awkward immediately after finishing the talk.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;analysis&quot;&gt;Analysis&lt;&#x2F;h1&gt;
&lt;p&gt;Okay, I lied about rehersal being the worst part of preparing a talk; that honor actually goes to the part where I have to listen to the recording.&lt;&#x2F;p&gt;
&lt;p&gt;This is the part where I have to be my own poor audience, enticed by the talk&#x27;s abstract into stumbling into a room and being subjected to an hour of my shrill, obnoxious voice. I don&#x27;t like this part. But I make it feel productive by taking notes, either on paper or in my slides themselves, on how to make it slightly less terrible.&lt;&#x2F;p&gt;
&lt;p&gt;These improvements tend to fall into three categories:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Ordering and transitions. I rarely get my content into an order that flows optimally on the first try, and have to re-learn the transitions between sections every time I rearrange my slides.&lt;&#x2F;li&gt;
&lt;li&gt;Pruning extraneous content. When I really like a topic, I often want to digress into cool stories every slide, and listening to those digressions is the best way to identify which are useful and which sound irrelevant.&lt;&#x2F;li&gt;
&lt;li&gt;Adding facts and citations to substantiate the claims that I&#x27;m likely to make when discussing a topic.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;repeat&quot;&gt;Repeat&lt;&#x2F;h1&gt;
&lt;p&gt;Unfortunately, a single rehersal is never enough. The more times I practice a talk, the better it gets. It&#x27;s two years and a dozen talks into my speaking career, and I still cringe at having to listen to my own voice, so I doubt it&#x27;ll get better. But it&#x27;s worth it, to create finished talks that others benefit from and enjoy.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;finishing-touches&quot;&gt;Finishing Touches&lt;&#x2F;h1&gt;
&lt;p&gt;Shortly before my presentation, I put the canonical copy of my slides at &lt;code&gt;talks.edunham.net&#x2F;conferencename&#x2F;talkname&lt;&#x2F;code&gt;. I also add that URL to the intro and Q&amp;amp;A slides, so that viewers can find the slides easily.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Culling the GitHub notification spam</title>
        <published>2015-04-12T00:00:00+00:00</published>
        <updated>2015-04-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/04/12/culling_the_github_notification_spam/"/>
        <id>https://edunham.net/2015/04/12/culling_the_github_notification_spam/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/04/12/culling_the_github_notification_spam/">&lt;p&gt;Long ago, I set up an email filter to make GitHub notifications skip my inbox. This made it easy to ignore the volume of irrelevant notifications that I was getting, and I only noticed them when they got in the way of a search for the one or two useful emails that GitHub sends.&lt;&#x2F;p&gt;
&lt;p&gt;However, it also means that I won&#x27;t find out if something legitimately notification-worthy happens, such as a new contributor opening a pull request to one of the handful of projects I actually care about.&lt;&#x2F;p&gt;
&lt;p&gt;Here are the steps one can take to improve that signal-to-noise ratio.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;quit-auto-subscribing&quot;&gt;Quit Auto-Subscribing&lt;&#x2F;h1&gt;
&lt;p&gt;Go to your &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;settings&#x2F;notifications&quot;&gt;notification settings&lt;&#x2F;a&gt; and un-check the &lt;strong&gt;automatically watch repositories&lt;&#x2F;strong&gt; box.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;autowatch.png&quot; alt=&quot;auto watch settings&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This will prevent you from automatically getting added to as a watcher to new repositories when you&#x27;re given access to them. It&#x27;s especially important to my GitHub workflow because I&#x27;m in several organizations that grant all members access to new repositories by default, and most of those repos are irrelevant to me.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;assess-the-damage&quot;&gt;Assess the Damage&lt;&#x2F;h1&gt;
&lt;p&gt;Head over to the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;watching&quot;&gt;watching repos list&lt;&#x2F;a&gt; to see how many repositories you&#x27;re automatically getting notifications for. In my case, there are too many to sort through by hand, so the best solution is going to be to bulk unsubscribe.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;stopwatching.png&quot; alt=&quot;stop watching repos&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;declare-notification-bankrupcy&quot;&gt;Declare Notification Bankrupcy&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;notifications.png&quot; alt=&quot;notifications triage&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While you&#x27;re thinning your watch list, head on over to the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;notifications&quot;&gt;notifications list&lt;&#x2F;a&gt; and take action on any notifications that need it, or just use the magical &lt;code&gt;Mark all as read&lt;&#x2F;code&gt; button in the upper right.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s important to get rid of all the spurious notifications generated by repos you shouldn&#x27;t have been watching in the first place, so that in the future, having unread notifications will mean &quot;something interesting happened&quot; rather than &quot;there&#x27;s still background noise&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;no_new_notifications.png&quot; alt=&quot;blessed silence: zero notifications&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;re-watch-selectively&quot;&gt;Re-watch selectively&lt;&#x2F;h1&gt;
&lt;p&gt;There are only about half a dozen repos where I need to take immediate action when an event such as a new PR occurrs.&lt;&#x2F;p&gt;
&lt;p&gt;For each such repo, switch your status to &quot;watching&quot; on its home page.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;rewatch.png&quot; alt=&quot;selective re-watching&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;remove-those-email-filters&quot;&gt;Remove those email filters&lt;&#x2F;h1&gt;
&lt;p&gt;The whole point of this exercise was to extract signal from the noise, and to complete that goal, we have to make the signal visible somewhere. For gmail, the filter ignoring everything from github will be somewhere in the &lt;a href=&quot;https:&#x2F;&#x2F;mail.google.com&#x2F;mail&#x2F;u&#x2F;0&#x2F;#settings&#x2F;filters&quot;&gt;filters list&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;maintenance&quot;&gt;Maintenance&lt;&#x2F;h1&gt;
&lt;p&gt;To keep seeing only the relevant notifications, respond to irrelevant notification emails by un-watching the repo that they came from.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Removing Blackarch</title>
        <published>2015-04-08T00:00:00+00:00</published>
        <updated>2015-04-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/04/08/removing_blackarch/"/>
        <id>https://edunham.net/2015/04/08/removing_blackarch/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/04/08/removing_blackarch/">&lt;p&gt;I installed &lt;a href=&quot;http:&#x2F;&#x2F;blackarch.org&#x2F;&quot;&gt;Blackarch&lt;&#x2F;a&gt; on top of my ordinary Arch install for &lt;a href=&quot;http:&#x2F;&#x2F;oregonctf.org&#x2F;&quot;&gt;the Oregon CTF&lt;&#x2F;a&gt; a few weeks ago.&lt;&#x2F;p&gt;
&lt;p&gt;Now it&#x27;s taking up a lot of space on my system and I no longer need it around.&lt;&#x2F;p&gt;
&lt;p&gt;None of the Google queries I&#x27;ve tried have discussed how to get rid of it, and I&#x27;ve had to resort to reading the install script to figure out how to make its repositories go away. Here&#x27;s what I learned.&lt;&#x2F;p&gt;
&lt;p&gt;First, assuming that you used &lt;a href=&quot;http:&#x2F;&#x2F;blackarch.org&#x2F;strap.sh&quot;&gt;strap.sh&lt;&#x2F;a&gt; to install all of the blackarch packages like I did, you can uninstall them simply by chopping the package names out of the list of all blackarch packages and instructing Pacman to remove them:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ pacman -Sl | grep blackarch | cut -f 2 -d &amp;quot; &amp;quot; | xargs sudo pacman -Rn
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pacman -Sl&lt;&#x2F;code&gt; searches all of the package databases and lists their contents. The output will be of the form &lt;code&gt;reponame packagename version&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;grep blackarch&lt;&#x2F;code&gt; narrows that list to only those packages with blackarch in their titles&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cut -f 2 -d &quot; &quot;&lt;&#x2F;code&gt; says &quot;I want only the second field, and fields are delineated by the space character&quot;. So far, we&#x27;ve selected the package name of each package whose listing contains the string &quot;blackarch&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;xargs&lt;&#x2F;code&gt; executes the given command on each item in its standard in. The pipes (&lt;code&gt;|&lt;&#x2F;code&gt;) each attach stdout of the preceding command to stdin of the following one. Xargs is necessary here because Pacman takes its arguments only on the command line, and does not gracefully handle taking args on stdin.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;pacman -Rn&lt;&#x2F;code&gt; says &quot;Remove the given package, and don&#x27;t worry about backing up its files&quot;. I passed the &lt;code&gt;-n&lt;&#x2F;code&gt; flag because one of my main reasons for removing blackarch is to save space on my rather small SSD, so I don&#x27;t want to keep around a bunch of backups for the configurations of programs I never ended up using.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Even after removing all Blackarch packages, the Blackarch repository entry remains in pacman.conf. To remove it, open &lt;code&gt;&#x2F;etc&#x2F;pacman.conf&lt;&#x2F;code&gt; and remove the blackarch repo, which will probably be the final two lines of the file. Those lines will look something like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[blackarch]                                                                     
&lt;&#x2F;span&gt;&lt;span&gt;Server = http:&#x2F;&#x2F;mirror.team-cymru.org&#x2F;blackarch&#x2F;$repo&#x2F;os&#x2F;$arch  
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And now it&#x27;s gone. No more packages to update, and no more repo to check every time updates get installed.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>SSHFS to public_html</title>
        <published>2015-04-06T00:00:00+00:00</published>
        <updated>2015-04-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/04/06/sshfs_to_public_html/"/>
        <id>https://edunham.net/2015/04/06/sshfs_to_public_html/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/04/06/sshfs_to_public_html/">&lt;p&gt;Today, someone asked the &lt;code&gt;#osu-lug&lt;&#x2F;code&gt; channel how to mount their &lt;code&gt;public_html&lt;&#x2F;code&gt; directory on OSU&#x27;s shell server to a location on their local Linux machine using sshfs.&lt;&#x2F;p&gt;
&lt;p&gt;So I basically googled &lt;a href=&quot;https:&#x2F;&#x2F;www.howtoforge.com&#x2F;mounting-remote-directories-with-sshfs-on-ubuntu-11.10&quot;&gt;a tutorial&lt;&#x2F;a&gt; and parroted it back at them. However, it would have been even faster to link them to a blog post, so here we go:&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-sshfs&quot;&gt;What&#x27;s SSHFS?&lt;&#x2F;h1&gt;
&lt;p&gt;As the name suggests, &lt;a href=&quot;https:&#x2F;&#x2F;help.ubuntu.com&#x2F;community&#x2F;SSHFS&quot;&gt;SSHFS&lt;&#x2F;a&gt; is a tool for using the Secure Shell protocol (&lt;code&gt;SSH&lt;&#x2F;code&gt;) to mount a remote file system (&lt;code&gt;FS&lt;&#x2F;code&gt;). In other words, it lets you treat files on a remote server (such as &lt;code&gt;shell.onid.oregonstate.edu&lt;&#x2F;code&gt;) as if they were on your local machine, so you can use your favorite IDE to work with them and then see your changes immediately appear on the remote server.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;setting-up&quot;&gt;Setting Up&lt;&#x2F;h1&gt;
&lt;p&gt;First, install &lt;code&gt;sshfs&lt;&#x2F;code&gt;. On Ubuntu, this looks like &lt;code&gt;sudo apt-get install sshfs&lt;&#x2F;code&gt;. On Arch, &lt;code&gt;yaourt -S sshfs&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Second, check that you&#x27;re able to SSH to shell. &lt;code&gt;username&lt;&#x2F;code&gt; will be your ONID username throughout this discussion. From a terminal:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ssh username@shell.onid.oregonstate.edu
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If it accepts your ONID password and logs you in, congratulations! If you get an error, Google it and try the solutions, or check the &lt;a href=&quot;http:&#x2F;&#x2F;oregonstate.edu&#x2F;helpdocs&#x2F;accounts&#x2F;onid-osu-network-id&#x2F;using-your-onid&#x2F;shell-access-and-unix&quot;&gt;ONID help docs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, you need an empty directory to which you wish to mount the remote directory. It&#x27;s where your files will appear to show up. Let&#x27;s give it a descriptive name:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ mkdir ~&#x2F;shell_public_html
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;mount-the-remote-directory&quot;&gt;Mount the remote directory&lt;&#x2F;h1&gt;
&lt;p&gt;The syntax for specifying which directory on the remote host you want to mount is roughly the same as for &lt;code&gt;scp&lt;&#x2F;code&gt;, so you can give the directory&#x27;s path relative to your homedir. In other words, mounting your &lt;code&gt;public_html&lt;&#x2F;code&gt; directory to the local directory &lt;code&gt;shell_public_html&lt;&#x2F;code&gt; will look like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sshfs username@shell.onid.oregonstate.edu:public_html shell_public_html
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And now you can edit files in &lt;code&gt;shell_public_html&lt;&#x2F;code&gt; and their changes will be automatically written to your &lt;code&gt;public_html&lt;&#x2F;code&gt; on &lt;code&gt;shell.onid.oregonstate.edu&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;To test changes, see whether the file you edited locally shows up in &lt;code&gt;http:&#x2F;&#x2F;people.oregonstate.edu&#x2F;~username&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;other-neat-tricks&quot;&gt;Other Neat Tricks&lt;&#x2F;h1&gt;
&lt;p&gt;Once you&#x27;ve got that working, you might want to look into:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Automatically mounting the directory at boot&lt;&#x2F;li&gt;
&lt;li&gt;Handling disconnection more gracefully&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This &lt;a href=&quot;http:&#x2F;&#x2F;askubuntu.com&#x2F;questions&#x2F;43363&#x2F;how-to-auto-mount-using-sshfs&quot;&gt;Ubuntu Forums thread&lt;&#x2F;a&gt; discusses some options to solve those problems.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Optional Arguments in LaTeX Macros</title>
        <published>2015-03-30T00:00:00+00:00</published>
        <updated>2015-03-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/03/30/optional_arguments_in_latex_macros/"/>
        <id>https://edunham.net/2015/03/30/optional_arguments_in_latex_macros/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/03/30/optional_arguments_in_latex_macros/">&lt;p&gt;As I&#x27;ve mentioned &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;02&#x2F;14&#x2F;resume_improvement_with_latex_macros.html&quot;&gt;before&lt;&#x2F;a&gt;, I use LaTeX to typeset my resume. I recently found a convenient workaround to handle formatting which differs based on whether or not a macro&#x27;s argument is present.&lt;&#x2F;p&gt;
&lt;p&gt;The solution to testing whether a given macro argument is empty is buried deep within the &lt;a href=&quot;http:&#x2F;&#x2F;www.latex-community.org&#x2F;forum&#x2F;viewtopic.php?f=5&amp;amp;t=5976&quot;&gt;LaTeX community forums&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\newcommand{\maybe}[2]{
&lt;&#x2F;span&gt;&lt;span&gt;    \ifx&amp;amp;#1&amp;amp;
&lt;&#x2F;span&gt;&lt;span&gt;        Argument 1 was blank!
&lt;&#x2F;span&gt;&lt;span&gt;    \else
&lt;&#x2F;span&gt;&lt;span&gt;        Argument 1 was not blank.
&lt;&#x2F;span&gt;&lt;span&gt;    \fi
&lt;&#x2F;span&gt;&lt;span&gt;    Argument 2 was #2. 
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The LaTeX wikibook goes into more detail on the &lt;a href=&quot;http:&#x2F;&#x2F;en.wikibooks.org&#x2F;wiki&#x2F;TeX&#x2F;ifx&quot;&gt;ifx&lt;&#x2F;a&gt; command. It appears that &lt;code&gt;&amp;amp;#1&lt;&#x2F;code&gt; is interpereted as a macro for purposes of equality comparison, then compared against the &lt;code&gt;&amp;amp;&lt;&#x2F;code&gt; empty macro.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;http:&#x2F;&#x2F;en.wikibooks.org&#x2F;wiki&#x2F;Category:TeX&quot;&gt;wikibook&#x27;s TeX category&lt;&#x2F;a&gt; discusses a variety of other &lt;code&gt;if&lt;&#x2F;code&gt; commands. Of particular interest is the &lt;a href=&quot;http:&#x2F;&#x2F;en.wikibooks.org&#x2F;wiki&#x2F;TeX&#x2F;ifnum&quot;&gt;ifnum&lt;&#x2F;a&gt; command, which tests whether a value is equal to a given integer.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Case of the Mixed-Case Tags</title>
        <published>2015-03-30T00:00:00+00:00</published>
        <updated>2015-03-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/03/30/the_case_of_the_mixed_case_tags/"/>
        <id>https://edunham.net/2015/03/30/the_case_of_the_mixed_case_tags/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/03/30/the_case_of_the_mixed_case_tags/">&lt;p&gt;I accidentally discovered the &quot;feature&quot; that Tinkerer tags are somewhat case-sensitive.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;background&quot;&gt;Background&lt;&#x2F;h1&gt;
&lt;p&gt;I sometimes write about LaTeX. On one post, I got lazy and typed the tag for it as &lt;code&gt;latex&lt;&#x2F;code&gt;, while on another, I capitalized it correctly as &lt;code&gt;LaTeX&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To make this post into an example of the behavior, I am tagging it &lt;code&gt;EXAMPLE&lt;&#x2F;code&gt;. I&#x27;ve added the tag &lt;code&gt;example&lt;&#x2F;code&gt; to my &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;01&#x2F;13&#x2F;blogging_with_tinkerer.html&quot;&gt;first post&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;behavior&quot;&gt;Behavior&lt;&#x2F;h1&gt;
&lt;p&gt;Both the uppercase and lowercase versions of the tag appear in the tags listing in the right sidebar. However, clicking either capitalization will take you to &lt;code&gt;http:&#x2F;&#x2F;edunham.net&#x2F;tags&#x2F;example.html&lt;&#x2F;code&gt;. That tag listing will only show the posts tagged with the &lt;strong&gt;lowercase&lt;&#x2F;strong&gt; spelling of the tag name being viewed.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-gotcha&quot;&gt;The Gotcha&lt;&#x2F;h1&gt;
&lt;p&gt;If all the posts with a given tag use the same capitalization, they will all appear on the tag&#x27;s page. To see this in action, look at the &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;tags&#x2F;test.html&quot;&gt;test tag&lt;&#x2F;a&gt; listing at &lt;code&gt;http:&#x2F;&#x2F;edunham.net&#x2F;tags&#x2F;test.html&lt;&#x2F;code&gt;. This is the only post with that tag, so all posts tagged with any capitalization of &quot;test&quot; have the same tag, so all of them show up on that page.&lt;&#x2F;p&gt;
&lt;p&gt;However, if I were to tag another post &lt;code&gt;test&lt;&#x2F;code&gt;, this post would cease appearing on the &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;tags&#x2F;test.html&quot;&gt;test tag&lt;&#x2F;a&gt; page. This behavior is probably wrong, but what would one do about it -- preserve case into the tag display page URLs?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;in-conclusion&quot;&gt;In Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;Be aware that Tinkerer modifies your tag names. Make sure you capitalize each tag the same way on every post, or the posts with a spelling which deviates from all lower-case may get lost.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Fixing Mplayer</title>
        <published>2015-03-15T00:00:00+00:00</published>
        <updated>2015-03-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/03/15/fixing_mplayer/"/>
        <id>https://edunham.net/2015/03/15/fixing_mplayer/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/03/15/fixing_mplayer/">&lt;p&gt;When trying to fix some errors related to installing &lt;a href=&quot;http:&#x2F;&#x2F;blackarch.org&#x2F;&quot;&gt;BlackArch&lt;&#x2F;a&gt; last week, I made a poorly thought out decision and deleted some kernel modules that I didn&#x27;t think were necessary. This rendered my system impossible to boot or chroot into for a bit.&lt;&#x2F;p&gt;
&lt;p&gt;The solution to most of that problem was to boot off of a livecd USB, then run the following commands:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;mount &#x2F;dev&#x2F;sda1 &#x2F;mnt
&lt;&#x2F;span&gt;&lt;span&gt;pacman -Syu -r &#x2F;mnt&#x2F; linux
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point, it could not boot because it was missing &lt;code&gt;&#x2F;sbin&#x2F;init&lt;&#x2F;code&gt;. This was solved by reinstalling &lt;code&gt;systemd-sysvcompat&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;However, even after uninstalling and reinstalling mplayer, I got the following error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;mplayer: error while loading shared libraries: librtmp.so.1: cannot open shared object file: No such file or directory
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In my case, Google did not have a useful fix for the error on the first page.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;yaourt -Ss librtmp&lt;&#x2F;code&gt; reveals that there&#x27;s a &lt;code&gt;librtmp0&lt;&#x2F;code&gt; nominally available, although it was unable to build successfully on my system.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The solution was to reinstall ``rtmpdump``&lt;&#x2F;strong&gt;. This restored the necessary kernel modules, and mplayer works now.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>hieroglyph2beamer with Pandoc</title>
        <published>2015-03-05T00:00:00+00:00</published>
        <updated>2015-03-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/03/05/hieroglyph2beamer_with_pandoc/"/>
        <id>https://edunham.net/2015/03/05/hieroglyph2beamer_with_pandoc/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/03/05/hieroglyph2beamer_with_pandoc/">&lt;p&gt;I&#x27;ve played with getting Sphinx to generate PDFs &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;02&#x2F;24&#x2F;making_a_pdf_of_hieroglyph_slides.html&quot;&gt;before&lt;&#x2F;a&gt;, and while &lt;code&gt;rst2pdf&lt;&#x2F;code&gt; generates a PDF with all the notes and pictures present, the results aren&#x27;t as beautifully typeset as I&#x27;ve come to expect from LaTeX.&lt;&#x2F;p&gt;
&lt;p&gt;This made me wonder whether any tool exists to convert Hieroglyph slides into &lt;a href=&quot;http:&#x2F;&#x2F;texdoc.net&#x2F;texmf-dist&#x2F;doc&#x2F;latex&#x2F;beamer&#x2F;doc&#x2F;beameruserguide.pdf&quot;&gt;Beamer&lt;&#x2F;a&gt; presentations.&lt;&#x2F;p&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;Skip to the &lt;a href=&quot;https:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;03&#x2F;05&#x2F;hieroglyph2beamer_with_pandoc&#x2F;#end&quot;&gt;end&lt;&#x2F;a&gt; if you just want the code for converting Hieroglyph sources to Beamer slides.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-not-just-write-latex&quot;&gt;Why not just write LaTeX?&lt;&#x2F;h1&gt;
&lt;p&gt;I sometimes do write LaTeX directly, such as for my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;resume&quot;&gt;resume&lt;&#x2F;a&gt;. LaTeX is the right choice when a project requires:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Extremely precise control of spacing, text size, and text position&lt;&#x2F;li&gt;
&lt;li&gt;Output only to PDF&lt;&#x2F;li&gt;
&lt;li&gt;Infrequent and relatively minor changes&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;However, the tradeoff is that one types a lot of boilerplate LaTeX code for a relatively small amount of output, and the resulting documents are relatively difficult to modify.&lt;&#x2F;p&gt;
&lt;p&gt;I find that slides are a better fit with ReStructuredText&#x27;s strengths:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Minimal boilerplate&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to read as plain text&lt;&#x2F;li&gt;
&lt;li&gt;Fast to modify&lt;&#x2F;li&gt;
&lt;li&gt;Styling is completely divorced from content (HTML vs CSS)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Supported out of the box almost everywhere I need it&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Toolchain fits my needs&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Powerful enough to describe image scaling and tables&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;This is my major complaint against Markdown&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So, I want to keep writing ReStructuredText, but I want the prettiness of Beamer slides. It&#x27;s time to see whether a tool for this has already been built for me, or whether I&#x27;ll have to make it myself.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;install-pandoc&quot;&gt;Install Pandoc&lt;&#x2F;h1&gt;
&lt;p&gt;Pip only has pandoc up through 1.0.0a8, which raises an import error if it can&#x27;t find a version of itself that starts with 1.12 &#x2F; 1.13. So, I need system pandoc.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s see if we can get a system pandoc of a more recent version:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ yaourt -S pandoc-static
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This takes quite a long time because it brings in a bunch of Haskell toolchain stuff, &lt;code&gt;ghc&lt;&#x2F;code&gt; in particular. The amount of memory consumed by Yaourt&#x27;s attempt to install Pandoc caused the rather disadvantaged laptop I&#x27;m currently using to freeze up, but &lt;code&gt;cabal update; cabal install pandoc&lt;&#x2F;code&gt; worked fine.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;make-slides&quot;&gt;Make Slides&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;~&#x2F;.cabal&#x2F;bin&#x2F;pandoc -t beamer index.rst -o test.pdf
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It generates pretty Beamer slides, just like that! If you want to get fancier with it, &lt;a href=&quot;http:&#x2F;&#x2F;andrewgoldstone.com&#x2F;blog&#x2F;2014&#x2F;12&#x2F;24&#x2F;slides&#x2F;&quot;&gt;this blog post&lt;&#x2F;a&gt; is an interesting read.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;comparison-to-sphinx-s-make-latexpdf&quot;&gt;Comparison to Sphinx&#x27;s &lt;code&gt;make latexpdf&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;ol&gt;
&lt;li&gt;Pandoc builds slides. &lt;code&gt;make latexpdf&lt;&#x2F;code&gt; doesn&#x27;t.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;2) &lt;code&gt;make latexpdf&lt;&#x2F;code&gt; is incredibly loud. Pandoc is silent. Loud, you say? Running it with its default settings yields more lines of output than there were lines in the source it&#x27;s comiling, and that&#x27;s just silly.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ wc -l index.rst
&lt;&#x2F;span&gt;&lt;span&gt;1053
&lt;&#x2F;span&gt;&lt;span&gt;$ make latexpdf | wc -l
&lt;&#x2F;span&gt;&lt;span&gt;1429
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;3) Pandoc is better at making the images the right size. With its default settings, each image fills its slide, which is how they look when built by Hieroglyph.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Pandoc goes over twice as fast:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ time make latexpdf
&lt;&#x2F;span&gt;&lt;span&gt;real    2m14.815s
&lt;&#x2F;span&gt;&lt;span&gt;user    2m12.820s
&lt;&#x2F;span&gt;&lt;span&gt;sys 0m0.783s
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;time ~&#x2F;.cabal&#x2F;bin&#x2F;pandoc -t beamer index.rst -o test.pdf
&lt;&#x2F;span&gt;&lt;span&gt;real    0m57.835s
&lt;&#x2F;span&gt;&lt;span&gt;user    0m57.523s
&lt;&#x2F;span&gt;&lt;span&gt;sys 0m0.233s
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;allow-page-breaks&quot;&gt;Allow Page Breaks&lt;&#x2F;h1&gt;
&lt;p&gt;My slides are somewhat atypical in that all the useful content is in the speaker notes, and the slides are filled by pictures for people to look at if they get bored listening to me.&lt;&#x2F;p&gt;
&lt;p&gt;I typically make a PDF copy of my slides for distribution after a talk, so it&#x27;s nice when readers can see my speaker notes. They&#x27;re currently where all of my reference URLs and other bonus content reside. Notes are visible by default in Pandoc&#x27;s beamer output, but they tend to be pushed off the slides by the large images.&lt;&#x2F;p&gt;
&lt;p&gt;Beamer has an &lt;code&gt;allowframebreaks&lt;&#x2F;code&gt; option, which makes sure all your content is visible by wrapping it to as many slides as needed. I learned how to inject a custom preamble from &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;agoldst&#x2F;tex&#x2F;tree&#x2F;master&#x2F;lecture-slides&#x2F;notes&quot;&gt;agoldst&#x27;s lecture notes repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;First, put the following lines into &lt;code&gt;preamble.tex&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\let\oldframe\frame
&lt;&#x2F;span&gt;&lt;span&gt;\renewcommand\frame[1][allowframebreaks]{\oldframe[#1]}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then tell Pandoc to include it in the &quot;header&quot;::&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ~&#x2F;.cabal&#x2F;bin&#x2F;pandoc -t beamer -H preamble.tex -V fontsize=8pt index.rst -o test.pdf
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;the-pipe-problem&quot;&gt;The Pipe Problem&lt;&#x2F;h1&gt;
&lt;p&gt;Sphinx and Hieroglyph allow the use of pipes (&lt;code&gt;|&lt;&#x2F;code&gt;) to force a blank line in html output, which I often use to align my images aesthetically in my slides. However, this use of pipes is neither mentioned in the &lt;a href=&quot;http:&#x2F;&#x2F;docutils.sourceforge.net&#x2F;docs&#x2F;ref&#x2F;rst&#x2F;restructuredtext.html&quot;&gt;rst spec&lt;&#x2F;a&gt; nor supported by Pandoc.&lt;&#x2F;p&gt;
&lt;p&gt;Although the functionality of ignoring pipes could probably be implemented as a &lt;a href=&quot;http:&#x2F;&#x2F;johnmacfarlane.net&#x2F;pandoc&#x2F;scripting.html&quot;&gt;filter&lt;&#x2F;a&gt;, I decided to take the path of less Haskell-writing and leverage Pandoc&#x27;s ability to behave like a nice Unix utility.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sed &amp;#39;s&#x2F;^|$&#x2F;&#x2F;&amp;#39; index.rst | ~&#x2F;.cabal&#x2F;bin&#x2F;pandoc -t beamer -f rst -o test.pdf -H preamble.tex
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that in this command I also tell Pandoc the file type it&#x27;s converting from, with the &lt;code&gt;-f&lt;&#x2F;code&gt; option.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;shrinking-figures-a-bit&quot;&gt;Shrinking Figures A Bit&lt;&#x2F;h1&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;Pandoc tries to emit its output in the format specified by the extension of the output file that you give it. &lt;code&gt;-o test.pdf&lt;&#x2F;code&gt; renders the Beamer slides as a pdf, whereas &lt;code&gt;-o test.tex&lt;&#x2F;code&gt; simply produces LaTeX which could later be rendered into slides. This is helpful for debugging purposes.&lt;&#x2F;p&gt;
&lt;p&gt;The output is getting prettier, but there are still blank slides before some of the images. It appears that the blank slides are only inserted before the images whose height is close to the full height of the slide.&lt;&#x2F;p&gt;
&lt;p&gt;In the LaTeX source of the pandoc-generated beamer slides, figures look something like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\begin{figure}[htbp]
&lt;&#x2F;span&gt;&lt;span&gt;\centering
&lt;&#x2F;span&gt;&lt;span&gt;\includegraphics{_drawn&#x2F;hello.png}
&lt;&#x2F;span&gt;&lt;span&gt;\caption{}  
&lt;&#x2F;span&gt;&lt;span&gt;\end{figure}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;m using the preamble trick mentioned above to customize each figure. I found the parameters for scaling only those images which would otherwise be too large on &lt;a href=&quot;http:&#x2F;&#x2F;tex.stackexchange.com&#x2F;questions&#x2F;11954&#x2F;automatically-scale-big-and-small-graphics-for-beamer-presentations&quot;&gt;stackoverflow&lt;&#x2F;a&gt;. Renewing the &lt;code&gt;\includegraphics&lt;&#x2F;code&gt; command is &lt;a href=&quot;http:&#x2F;&#x2F;tex.stackexchange.com&#x2F;questions&#x2F;79724&#x2F;resize-all-images-in-latex-to-a-percentage-width&quot;&gt;tricky&lt;&#x2F;a&gt;, because it has an optional parameter, but it can be done with the &lt;code&gt;letltxmacro&lt;&#x2F;code&gt; package. I added these lines to my &lt;code&gt;preamble.tex&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\usepackage{letltxmacro}
&lt;&#x2F;span&gt;&lt;span&gt;\LetLtxMacro{\OldIncludegraphics}{\includegraphics}
&lt;&#x2F;span&gt;&lt;span&gt;\renewcommand{\includegraphics}[2][]{\OldIncludegraphics[width=0.5\textwidth,height=0.5\textheight,keepaspectratio,
&lt;&#x2F;span&gt;&lt;span&gt;#1]{#2}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now images are displayed as no larger than half the size of the total text area on a slide.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span id=&quot;end&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;all-together-now&quot;&gt;All Together Now&lt;&#x2F;h2&gt;
&lt;p&gt;The Makefile gets these lines. Remember to use hard tabs, not spaces, because it is a Makefile:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;pdf:                                                                            
&lt;&#x2F;span&gt;&lt;span&gt;    sed &amp;quot;s&#x2F;^|$$&#x2F;&#x2F;&amp;quot; index.rst | ~&#x2F;.cabal&#x2F;bin&#x2F;pandoc -t beamer -f rst -V fontsize=8pt -o $(BUILDDIR)&#x2F;slides.pdf -H preamble.tex
&lt;&#x2F;span&gt;&lt;span&gt;    @echo                                                                       
&lt;&#x2F;span&gt;&lt;span&gt;    @echo &amp;quot;Build finished. The PDF is at $(BUILDDIR)&#x2F;slides.pdf.&amp;quot;               
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the &lt;code&gt;preamble.tex&lt;&#x2F;code&gt; gets the following:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;% Allow notes to wrap to additional slides.                                     
&lt;&#x2F;span&gt;&lt;span&gt;\let\oldframe\frame                                                             
&lt;&#x2F;span&gt;&lt;span&gt;\renewcommand\frame[1][allowframebreaks]{\oldframe[#1]}                         
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;% Prevent spurious blank slides by shrinking images when needed.                
&lt;&#x2F;span&gt;&lt;span&gt;\usepackage{letltxmacro}                                                        
&lt;&#x2F;span&gt;&lt;span&gt;\LetLtxMacro{\OldIncludegraphics}{\includegraphics}                             
&lt;&#x2F;span&gt;&lt;span&gt;\renewcommand{\includegraphics}[2][]{\OldIncludegraphics[width=0.5\textwidth,height=0.5\textheight,keepaspectratio,#1]{#2}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now &lt;code&gt;make pdf&lt;&#x2F;code&gt; turns the &lt;code&gt;index.rst&lt;&#x2F;code&gt; of Hieroglyph slides into a relatively beautiful Beamer presentation!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Magic of Extundelete</title>
        <published>2015-03-02T00:00:00+00:00</published>
        <updated>2015-03-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/03/02/the_magic_of_extundelete/"/>
        <id>https://edunham.net/2015/03/02/the_magic_of_extundelete/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/03/02/the_magic_of_extundelete/">&lt;p&gt;Last night, I was just falling asleep when my roommate knocked on my door with a Linux problem. Our CS480 assignment was due at midnight and she&#x27;d finished the work, then accidentally overwritten the most important file in her program with a malformed tar command.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;seek-possible-backups&quot;&gt;Seek Possible Backups&lt;&#x2F;h1&gt;
&lt;p&gt;My first action was to go down the list of possible locations where a copy of the file might be:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Did you back it up? (no)&lt;&#x2F;li&gt;
&lt;li&gt;Did you email it to anyone? (no)&lt;&#x2F;li&gt;
&lt;li&gt;Did you upload it to the school servers to test it? (no)&lt;&#x2F;li&gt;
&lt;li&gt;Are you using revision control? (no)&lt;&#x2F;li&gt;
&lt;li&gt;Are there any swap files from your editor? (no)&lt;&#x2F;li&gt;
&lt;li&gt;Is the file in use by any program? (no)&lt;&#x2F;li&gt;
&lt;li&gt;Is there a compiled file that we might be able to decompile into ugly but submit-able source? (no)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Out of familiar options, I had her email the professor before the deadline had technically passed to explain the situation. She then phoned her father, who&#x27;s an engineer at Xerox and used to teach CS480. He ran her through the same questions that I had already asked, mirrored my decision to save the &lt;em&gt;Git is your best friend&lt;&#x2F;em&gt; discussion for a more reasonable hour, and then suggested a tool called &lt;code&gt;extundelete&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;delete-vs-overwrite&quot;&gt;Delete vs Overwrite&lt;&#x2F;h1&gt;
&lt;p&gt;To spoil the end of this saga, I found that it was easier to recover a deleted file than an overwritten one. Although the Ubuntu Forums have &lt;a href=&quot;http:&#x2F;&#x2F;ubuntuforums.org&#x2F;showthread.php?t=2113182&quot;&gt;instructions&lt;&#x2F;a&gt; for recovering an overwritten file, they did not work for me. I suspect it may be because the file was overwritten with the output of &lt;code&gt;tar&lt;&#x2F;code&gt;, rather than being overwritten with an empty file.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;save-the-inodes&quot;&gt;Save the Inodes&lt;&#x2F;h1&gt;
&lt;p&gt;Unix-style filesystems keep track of where files live on the disk using sequentially numbered datastructures called index nodes, or &lt;code&gt;inodes&lt;&#x2F;code&gt;. When a file is deleted, the inode&#x27;s contents aren&#x27;t actually overwritten until some other program opens a file and needs that inode.&lt;&#x2F;p&gt;
&lt;p&gt;In order to prevent anything from overwriting the inode of a deleted file, &lt;strong&gt;stop using the computer immediately after the accidental deletion&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d expect an OS to clean up after itself when politely asked to shut down, and the artefacts I&#x27;d like to recover would be regarded as garbage by those cleanup routines. This meant the next step was to &lt;strong&gt;unplug the laptop&#x27;s power supply and then pull out its battery&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, it is critically important that you &lt;strong&gt;do not boot from the drive with the deleted file until after successful file recovery&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;boot-carefully-from-external-media&quot;&gt;Boot, Carefully, from External Media&lt;&#x2F;h1&gt;
&lt;p&gt;After powering down the laptop, I plugged in an Arch Linux livecd USB stick that I happened to have lying around. On a spare machine, I Googled which function keys would take me into BIOS settings, because the BIOS screen can flash past extremely fast on newer computers.&lt;&#x2F;p&gt;
&lt;p&gt;I then entered the BIOS settings and instructed the laptop to have USB first in its boot priority order. To be super safe, I removed the internal HDD from the list of boot devices entirely.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately, my USB stick did not have &lt;code&gt;extundelete&lt;&#x2F;code&gt;, trying to install it from the package manager onto the stick did not work (and probably isn&#x27;t supposed to), and whatever C++ compiler it shipped with got really grumpy when I tried to build &lt;code&gt;extundelete&lt;&#x2F;code&gt; on the USB stick from source. Life lesson: Roll your recovery CDs before, rather than after, burning them.&lt;&#x2F;p&gt;
&lt;p&gt;However, I have the SSD from my laptop in a USB enclosure and have been booting an older box off of it until my new laptop arrives. I booted my roommate&#x27;s laptop off of my SSD, and had my entire desktop environment, package manager, and other useful tools available.&lt;&#x2F;p&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;The bootloader on a drive accustomed to being in a laptop will default to booting the OS on &lt;code&gt;&#x2F;dev&#x2F;sda1&lt;&#x2F;code&gt;. It is very important to edit this entry to have it boot from &lt;code&gt;&#x2F;dev&#x2F;sdb1&lt;&#x2F;code&gt; (or the first letter after all the laptop&#x27;s internal drives are accounted for), since internal drives are seen first and get earlier letters. Remember the part about &lt;strong&gt;do not boot from the drive with the deleted file until after successful file recovery&lt;&#x2F;strong&gt;?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;find-the-right-partition&quot;&gt;Find the Right Partition&lt;&#x2F;h1&gt;
&lt;p&gt;The &lt;a href=&quot;http:&#x2F;&#x2F;ubuntuforums.org&#x2F;showthread.php?t=2113182&quot;&gt;instructions&lt;&#x2F;a&gt; I was following said to mount the partition containing the overwritten file, so I mounted the &lt;code&gt;sda&lt;&#x2F;code&gt; partitions one at a time until finding which one contained Linux (&lt;code&gt;&#x2F;dev&#x2F;sda4&lt;&#x2F;code&gt;). As per the instructions, I removed the overwritten file and then unmounted the partition.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;back-up&quot;&gt;Back Up?&lt;&#x2F;h1&gt;
&lt;p&gt;The partition in question is 500GB, which is bigger than the largest spare HDD that I have lying around. If it was smaller, I would have &lt;strong&gt;very very carefully&lt;&#x2F;strong&gt; used &lt;code&gt;dd&lt;&#x2F;code&gt; to clone the partition in case undeletion went wrong.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;run-extundelete&quot;&gt;Run Extundelete&lt;&#x2F;h1&gt;
&lt;p&gt;Ok, by this time it was 1am and I&#x27;m still recovering from conference flu, so I did a stupid thing: I tried to run extundelete before unmounting the partition. It caused this error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;The partition should be unmounted to undelete any files without further
&lt;&#x2F;span&gt;&lt;span&gt;data loss.
&lt;&#x2F;span&gt;&lt;span&gt;If the partition is not currently mounted, this message indicates
&lt;&#x2F;span&gt;&lt;span&gt;it was improperly unmounted, and you should run fsck before continuing.
&lt;&#x2F;span&gt;&lt;span&gt;If you decide to continue, extundelete may overwrite some of the deleted
&lt;&#x2F;span&gt;&lt;span&gt;files and make recovering those files impossible.  You should unmount the
&lt;&#x2F;span&gt;&lt;span&gt;file system and check it with fsck before using extundelete.
&lt;&#x2F;span&gt;&lt;span&gt;Would you like to continue? (y&#x2F;n)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;The correct answer is No&lt;&#x2F;strong&gt;. This error only occurs when, as it says, the partition either is mounted or was unmounted wrong.&lt;&#x2F;p&gt;
&lt;p&gt;If the partition was unmounted when you ran &lt;code&gt;extundelete&lt;&#x2F;code&gt; and you got that error, a &lt;code&gt;fsck&lt;&#x2F;code&gt; might help. I&#x27;d recommend running &lt;code&gt;fsck -Nn&lt;&#x2F;code&gt; for a dry run, to see what &lt;code&gt;fsck&lt;&#x2F;code&gt; would do if you let it. &lt;code&gt;fsck&lt;&#x2F;code&gt; is useful in cases of filesystem corruption, where a file exists on disk but does not have an inode.&lt;&#x2F;p&gt;
&lt;p&gt;Another possible reason for getting that error would be if you were trying to run extundelete from the same partition as the lost file was on. In that case, you are a terrible person for ignoring literally all the instructions about running extundelete from a livecd, and &lt;strong&gt;do not boot from the drive with the deleted file until after successful file recovery&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I unmounted the partition, and the error did not reappear.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;undelete-by-file-path&quot;&gt;Undelete by File Path&lt;&#x2F;h1&gt;
&lt;p&gt;You probably have to run these commands as root. I elevated to a root shell anyways, because at this point I&#x27;m playing with so much fire that avoiding the distraction of constantly typing &lt;code&gt;sudo&lt;&#x2F;code&gt; is a net benefit:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ extundelete &#x2F;dev&#x2F;sda4 --restore-directory home&#x2F;username&#x2F;path&#x2F;to&#x2F;dir&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that &lt;code&gt;extundelete --help&lt;&#x2F;code&gt; explains how the path is relative to the root of the partition, and thus does not need a leading slash.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;restore-directory&lt;&#x2F;code&gt; command didn&#x27;t actually work (if it&#x27;d worked, a bunch of files would have appeared in &lt;code&gt;RECOVERED_FILES&#x2F;&lt;&#x2F;code&gt;) but it did print a long list of files and directories, their inodes, and whether they were deleted or not:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;File name                                       | Inode number | Deleted        
&lt;&#x2F;span&gt;&lt;span&gt;status                                                                          
&lt;&#x2F;span&gt;&lt;span&gt;.                                                 5243979                       
&lt;&#x2F;span&gt;&lt;span&gt;..                                                5243970                       
&lt;&#x2F;span&gt;&lt;span&gt;cs411                                             5375933                       
&lt;&#x2F;span&gt;&lt;span&gt;cs480                                             5252709                       
&lt;&#x2F;span&gt;&lt;span&gt;Essay 1.odt                                       5248187        Deleted        
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;digging-around-by-inode&quot;&gt;Digging Around by Inode&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;extundelete --help&lt;&#x2F;code&gt; reveals that I can use it to examine individual inodes. Since the missing file is in the &lt;code&gt;cs480&lt;&#x2F;code&gt; directory, my next command examined its contents:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ extundelete &#x2F;dev&#x2F;sda4 --inode 5252709
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All this does is give me a list of the directory&#x27;s contents, with their respective inodes and deletion status:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Milestone2_lexical_analyzer                       5375950                       
&lt;&#x2F;span&gt;&lt;span&gt;Milestone4_Code_Generation_Constants              5377172                       
&lt;&#x2F;span&gt;&lt;span&gt;Milestone1_gforth_basics                          5374187                       
&lt;&#x2F;span&gt;&lt;span&gt;Milestone3_parser                                 5375948                       
&lt;&#x2F;span&gt;&lt;span&gt;gforth_problem_6.txt                              5252921        Deleted  
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The missing file was in the &lt;code&gt;Milestone4&lt;&#x2F;code&gt; directory, so my next command examined it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ extundelete &#x2F;dev&#x2F;sda4 --inode 5377172
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we&#x27;re getting somewhere! The overwritten file was named &lt;code&gt;code_generator.c&lt;&#x2F;code&gt;, and in the output of that last inode listing I have:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;code_generator.c                                  5378104        Deleted        
&lt;&#x2F;span&gt;&lt;span&gt;code_generator.o                                  5378085        Deleted        
&lt;&#x2F;span&gt;&lt;span&gt;code_generator.h                                  5378092                       
&lt;&#x2F;span&gt;&lt;span&gt;.code_generator.c.swp                             5378066        Deleted        
&lt;&#x2F;span&gt;&lt;span&gt;.code_generator.c.swx                             5378065        Deleted        
&lt;&#x2F;span&gt;&lt;span&gt;code_generator.c~                                 5378102        Deleted
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;restore-from-inodes&quot;&gt;Restore From Inodes&lt;&#x2F;h1&gt;
&lt;p&gt;I restored each file separately, though looking at the help again I could easily have done them as a list:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ extundelete &#x2F;dev&#x2F;sda4 --restore-inode 5378104,5378066,5378065,5378102
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s to grab the &lt;code&gt;.c&lt;&#x2F;code&gt;, the &lt;code&gt;.swp&lt;&#x2F;code&gt; and &lt;code&gt;.swx&lt;&#x2F;code&gt;, and the &lt;code&gt;.c~&lt;&#x2F;code&gt; files.&lt;&#x2F;p&gt;
&lt;p&gt;Whenever a file is successfully undeleted, it gets saved as &lt;code&gt;RECOVERED_FILES&#x2F;file.xxxxxx&lt;&#x2F;code&gt; where &lt;code&gt;xxxxxx&lt;&#x2F;code&gt; is its inode number.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;results&quot;&gt;Results&lt;&#x2F;h1&gt;
&lt;p&gt;Despite following the &lt;a href=&quot;http:&#x2F;&#x2F;ubuntuforums.org&#x2F;showthread.php?t=2113182&quot;&gt;instructions&lt;&#x2F;a&gt; as correctly as I could at 1am, the recovered &lt;code&gt;code_generator.c&lt;&#x2F;code&gt; file was still overwritten with the gibberish of the errant &lt;code&gt;tar&lt;&#x2F;code&gt; command.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;code_generator.c~&lt;&#x2F;code&gt; file contained a recent copy of the file which had been overwritten, so my roommate emailed it to the professor.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;postscript-swap-file-recovery-attempt&quot;&gt;Postscript: Swap File Recovery Attempt&lt;&#x2F;h1&gt;
&lt;p&gt;But what if the &lt;code&gt;code_generator.c~&lt;&#x2F;code&gt; file hadn&#x27;t been mysteriously created by whatever utility spits out &lt;code&gt;.c~&lt;&#x2F;code&gt; files?&lt;&#x2F;p&gt;
&lt;p&gt;I tried to recover the swap files with &lt;code&gt;vim -r code_generator.c&lt;&#x2F;code&gt;, and it detected both of them, but I got the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;E307: .code_generator.c.swx does not look like a Vim swap file
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And indeed, it is not a Vim swap file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ file .code_generator.c.swx 
&lt;&#x2F;span&gt;&lt;span&gt;.code_generator.c.swx: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Something about these files has massively confused Linux. Let&#x27;s see if they contain anything interesting:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ strings .code_generator.c.swx | less
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It looks a bit like output from running the code generator program interspersed with paths on the filesystem. Whatever this thing is, it&#x27;s sure not a valid Vim swap file.&lt;&#x2F;p&gt;
&lt;p&gt;To see what valid swap files look like, I forced Vim to generate one by opening a file, typing into it, waiting 4 seconds, then closing that terminal.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The swap file is updated after typing 200 characters or when you have not typed anything for four seconds.&lt;&#x2F;p&gt;
&lt;p&gt;-- (from the &lt;a href=&quot;http:&#x2F;&#x2F;vimdoc.sourceforge.net&#x2F;htmldoc&#x2F;recover.html&quot;&gt;Vim docs&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Unsurprisingly, it&#x27;s a valid swap file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ file .test.c.swp 
&lt;&#x2F;span&gt;&lt;span&gt;.test.c.swp: Vim swap file, version 7.4
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and running &lt;code&gt;strings&lt;&#x2F;code&gt; on it prints out the text that I had typed before killing Vim.&lt;&#x2F;p&gt;
&lt;p&gt;The moral of this tangent is that it would not have been possible to recover the &lt;code&gt;.c&lt;&#x2F;code&gt; files solely from their Vim swap files in this case.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Code Coverage in the Monte Tests</title>
        <published>2015-02-28T00:00:00+00:00</published>
        <updated>2015-02-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/28/code_coverage_in_the_monte_tests/"/>
        <id>https://edunham.net/2015/02/28/code_coverage_in_the_monte_tests/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/28/code_coverage_in_the_monte_tests/">&lt;p&gt;My schoolwork is particularly bad this weekend, so I&#x27;m procrastinating by learning to analyze the test coverage of a moderately complex Python codebase. Specifically, the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;monte-language&#x2F;monte&quot;&gt;reference implementation&lt;&#x2F;a&gt; of the &lt;a href=&quot;http:&#x2F;&#x2F;monte.readthedocs.org&#x2F;en&#x2F;latest&#x2F;&quot;&gt;Monte programming language&lt;&#x2F;a&gt;. This effort is hampered only slightly by the fact that I&#x27;ve never done much with code coverage tools before.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;nedbat-s-tool&quot;&gt;Nedbat&#x27;s tool&lt;&#x2F;h1&gt;
&lt;p&gt;Some cursory searching and reading the resulting blogs points out that the Ned Batchelder&#x27;s &lt;a href=&quot;http:&#x2F;&#x2F;nedbatchelder.com&#x2F;code&#x2F;coverage&#x2F;&quot;&gt;coverage&lt;&#x2F;a&gt; is the most-recommended Python code coverage analyser. You run it on your program; it spits out a report of any lines that weren&#x27;t executed and optionally makes that report into pretty HTML.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;trial-coverage&quot;&gt;Trial --coverage&lt;&#x2F;h1&gt;
&lt;p&gt;I then realized that since Monte&#x27;s tests are run by Trial, it would probably make more sense to ask Trial to report the coverage statistics. Sure enough, there&#x27;s a &lt;code&gt;--coverage&lt;&#x2F;code&gt; option in &lt;code&gt;trial --help&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;--coverage           Generate coverage information in the coverage file in
&lt;&#x2F;span&gt;&lt;span&gt;                     the directory specified by the temp-directory
&lt;&#x2F;span&gt;&lt;span&gt;                     option.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Wow, that&#x27;s... almost helpful. This is the only reference to the &lt;code&gt;temp-directory&lt;&#x2F;code&gt; option in the entire help, but maybe the output of running the command will tell me what &lt;code&gt;temp-directory&lt;&#x2F;code&gt; is being used:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ trial --coverage monte.test.test_runtime    
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;PASSED (successes=231)
&lt;&#x2F;span&gt;&lt;span&gt;Setting coverage directory to &#x2F;full&#x2F;path&#x2F;to&#x2F;pwd&#x2F;_trial_temp&#x2F;coverage.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;$ ls _trial_temp&#x2F;coverage&#x2F; | wc -l
&lt;&#x2F;span&gt;&lt;span&gt;175
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well, it ran whatever &lt;code&gt;coverage&lt;&#x2F;code&gt; function it&#x27;s using on &lt;strong&gt;everything&lt;&#x2F;strong&gt;: the code I wanted coverage for, and all of its dependencies. But the &lt;code&gt;.cover&lt;&#x2F;code&gt; filenames are formatted in a way that makes it easy to tell what they are:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;monte.ast.cover
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;twisted.web.sux.cover
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;how-to-read-cover-files&quot;&gt;How to read .cover files&lt;&#x2F;h1&gt;
&lt;p&gt;The &lt;code&gt;.cover&lt;&#x2F;code&gt; files looked like scary nonsense at first when I opened them, because they had a bunch of seemingly random numbers and unfamiliar code. I stared at them for a while and realized that a &lt;code&gt;.cover&lt;&#x2F;code&gt; file is exactly the source of the file it reports on, but with each line prefaced by the number of times it was run. (That&#x27;s what I get for learning this stuff on parts of a codebase that I&#x27;ve barely touched!)&lt;&#x2F;p&gt;
&lt;p&gt;The closest thing I could find to an explanation of the &lt;code&gt;.cover&lt;&#x2F;code&gt; files was a &lt;a href=&quot;https:&#x2F;&#x2F;developer.apple.com&#x2F;library&#x2F;mac&#x2F;documentation&#x2F;Darwin&#x2F;Reference&#x2F;ManPages&#x2F;man1&#x2F;trial.1.html&quot;&gt;twisted man page&lt;&#x2F;a&gt; in the Mac OSX 10.9 docs. I don&#x27;t have a man page for Trial on my own system, perhaps because it&#x27;s installed in a virtualenv rather than through my OS&#x27;s package manager.&lt;&#x2F;p&gt;
&lt;p&gt;The official Trial docs mention that the coverage tool exists, but provide no help on how to do things with its output.&lt;&#x2F;p&gt;
&lt;p&gt;The moral of the story is that you should grep through the &lt;code&gt;.cover&lt;&#x2F;code&gt; file for instances of &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;code&gt;, which point at lines that were never run.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;getting-prettier-output&quot;&gt;Getting Prettier Output&lt;&#x2F;h1&gt;
&lt;p&gt;After reading the Twisted developers &lt;a href=&quot;https:&#x2F;&#x2F;twistedmatrix.com&#x2F;trac&#x2F;ticket&#x2F;4374&quot;&gt;arguing about including coverage.py&lt;&#x2F;a&gt; and explaining its use &lt;a href=&quot;http:&#x2F;&#x2F;twistedmatrix.com&#x2F;pipermail&#x2F;twisted-python&#x2F;2012-April&#x2F;025487.html&quot;&gt;on the mailing list&lt;&#x2F;a&gt;, I realized that Nedbat&#x27;s &lt;a href=&quot;http:&#x2F;&#x2F;nedbatchelder.com&#x2F;code&#x2F;coverage&#x2F;&quot;&gt;coverage&lt;&#x2F;a&gt; is probably the &quot;right answer&quot; after all.&lt;&#x2F;p&gt;
&lt;p&gt;The easiest way to run &lt;code&gt;coverage&lt;&#x2F;code&gt; is to just wrap it around trial:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ coverage run `which trial` monte.test.test_runtime
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This initially has the same problem of testing all the imported files, whose coverage doesn&#x27;t matter to me right now. Fortunately, &lt;code&gt;coverage&lt;&#x2F;code&gt; has actual documentation on how to use it to omit files in certain directories:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ coverage run `which trial` monte.test.test_runtime 
&lt;&#x2F;span&gt;&lt;span&gt;$ coverage report --omit=venv&#x2F;lib&#x2F;*
&lt;&#x2F;span&gt;&lt;span&gt;$ coverage html -i --omit=venv&#x2F;lib&#x2F;*
&lt;&#x2F;span&gt;&lt;span&gt;$ firefox htmlcov&#x2F;index.html
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the &lt;code&gt;-i&lt;&#x2F;code&gt; flag is necessary because of &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;2386975&#x2F;no-source-for-code-message-in-coverage-py&quot;&gt;an issue&lt;&#x2F;a&gt; where &lt;code&gt;coverage&lt;&#x2F;code&gt; thinks it should have source for files that it actually shouldn&#x27;t, in my case:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ coverage html --omit=v&#x2F;lib&#x2F;*
&lt;&#x2F;span&gt;&lt;span&gt;No source for code: &amp;#39;&#x2F;pymeta_generated_code&#x2F;pymeta_grammar__CycleRenamer.py&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hardware Saga, part 1</title>
        <published>2015-02-27T00:00:00+00:00</published>
        <updated>2015-02-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/27/hardware_saga_part_1/"/>
        <id>https://edunham.net/2015/02/27/hardware_saga_part_1/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/27/hardware_saga_part_1/">&lt;p&gt;At around 1pm on 2&#x2F;26&#x2F;2015, my ThinkPad X230&#x27;s screen died without warning. I put the laptop away to take a quiz in class, walked home, tried to boot, an it remained black. The LEDs and CPU fan went through their familiar boot process, but the screen remained stubbornly blank.&lt;&#x2F;p&gt;
&lt;p&gt;Below the fold you&#x27;ll find several pages of excrutiating detail on the situation, because I enjoy writing these things down.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;&#x2F;h1&gt;
&lt;p&gt;When the screen&#x27;s power inverter breaks, images are faintly visible on the screen under bright light. However, I looked at it with my phone&#x27;s flashlight while it booted and saw nothing.&lt;&#x2F;p&gt;
&lt;p&gt;I also unplugged and re-seated the screen&#x27;s cables, at both the motherboard and the LCD panel, in order to verify that they hadn&#x27;t come loose if I had bumped the laptop wrong without noticing while carrying it home. Re-seating the cables did not change the screen&#x27;s behavior; the machine has survived drops from a table to a hardwood floor without any detriment to its performance.&lt;&#x2F;p&gt;
&lt;p&gt;This narrows it down to two components which could be at fault: the cables which run through the hinges and supply power to the screen might be damaged, or the GPU itself could have given out. I tried plugging my VGA port into an external monitor and the monitor showed nothing during boot, although this is not useful evidence since I never ran such a test while the machine was working correctly and thus don&#x27;t know what should have happened.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ordering-a-successor&quot;&gt;Ordering a Successor&lt;&#x2F;h1&gt;
&lt;p&gt;Replacing the motherboard with another of the same quality would cost around $300, and when shopping for replacements, I discovered that I could get a slightly older machine with still-decent specs for less than half that price. Since I have a bit of a Thinkpad collection and have been envying the form factor of a coworker&#x27;s X201 for quite some time, I ordered an X201 with an i5-m520. It&#x27;ll ship with 2GB of RAM but I can put in the 8GB from my X230 without trouble.&lt;&#x2F;p&gt;
&lt;p&gt;However, the new machine won&#x27;t arrive till around March 9th, so I&#x27;m having fun persuading my old disk to boot on various systems until then.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;brain-transplant-t60&quot;&gt;Brain Transplant: T60&lt;&#x2F;h1&gt;
&lt;p&gt;I pulled out my X230&#x27;s SSD and stuck it into the USB enclosure that I usually use for my backup disks. I plugged it into a T60, hit f12 during boot to select the USB HDD as a temporary boot device, and it got as far as the bootloader. However, my Arch kernel was incompatible with the T60&#x27;s i616 CPU.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;brain-transplant-t440s&quot;&gt;Brain Transplant: T440S&lt;&#x2F;h1&gt;
&lt;p&gt;I also have a T440s in my room. It&#x27;s technically my work computer, so I generally avoid using it for non-work purposes, but &quot;I have an assignment due and need a working laptop&quot; is something of an exception.&lt;&#x2F;p&gt;
&lt;p&gt;Plug in HDD, boot, select boot device, get bootloader. Hit enter to select booting Arch Linux... and a second later, I&#x27;m greeted by the Ubuntu login screen. Wat?&lt;&#x2F;p&gt;
&lt;p&gt;Try again. Reboot, select boot device, get bootloader. This time, hit tab on Arch Linux to edit the options. It&#x27;s trying to boot from &lt;code&gt;&#x2F;dev&#x2F;sda1&lt;&#x2F;code&gt;. In all the cases like this that I&#x27;ve encountered, a laptop&#x27;s internal drive is assigned to &lt;code&gt;sda&lt;&#x2F;code&gt;, and external ones get &lt;code&gt;sdb&lt;&#x2F;code&gt; and higher. Edit the bootloader item to &lt;code&gt;sdb&lt;&#x2F;code&gt; instead of &lt;code&gt;sda&lt;&#x2F;code&gt;, hit enter, and it works!&lt;&#x2F;p&gt;
&lt;p&gt;Log into Arch, and everything works beautifully except the wireless. Am I missing drivers? Let&#x27;s hope not; if they&#x27;re missing, it&#x27;ll be a royal pain to acquire them:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sudo ip link set wlp3s0 up
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and there&#x27;s wifi again!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;brain-transplant-t61&quot;&gt;Brain Transplant: T61&lt;&#x2F;h1&gt;
&lt;p&gt;The next day, I retrieved the hand-me-down T61 that I&#x27;d rescued when a roommate asked whether I wanted anything from a big stack of computers destined for recycling.&lt;&#x2F;p&gt;
&lt;p&gt;Its battery is totally shot, and it&#x27;s missing the hard drive enclosure, but in other regards it&#x27;s a decently usable machine. It has a good keyboard, physical buttons for the trackpoint, and working wifi, so it wins most of my battle against technology already.&lt;&#x2F;p&gt;
&lt;p&gt;Booting it has been distinctly interesting. Pull 320GB spinny-disk out of x100e, verify that the connections look the same, stuff it into the gaping hole where T61&#x27;s hdd enclosure belongs. Try to boot. &quot;Operating System Not Found&quot;. Okay then.&lt;&#x2F;p&gt;
&lt;p&gt;Plug in bootable Arch USB. Reboot. Pick the &lt;code&gt;x86_64&lt;&#x2F;code&gt; kernel; it boots. Mount partititions of the HDD; they&#x27;re readable.&lt;&#x2F;p&gt;
&lt;p&gt;Swap the spinny-disk for my X230&#x27;s SSD. Try to boot. No &quot;operating system not found&quot; error this time; it doesn&#x27;t see the disk at all. Maybe it&#x27;s doing a spin check on boot, and the SSD says mu?&lt;&#x2F;p&gt;
&lt;p&gt;Stick the SSD back in its USB enclosure, plug it in, reboot. It boots happily, and I&#x27;m typing this from my X230&#x27;s arch install, booted by the T61, right now.&lt;&#x2F;p&gt;
&lt;p&gt;Next steps will be to stick the X230&#x27;s old spinny-disk in and see if it&#x27;s a) recognized, b) bootable. Find disk that&#x27;s ok to get wiped, and visible to the T61, and install to it. But that takes effort, and the system is usable for now.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;if-it-s-stupid-but-it-works&quot;&gt;If It&#x27;s Stupid But It Works...&lt;&#x2F;h1&gt;
&lt;p&gt;The annoying part of the T61 setup is that the enclosure end of the USB cable is prone to getting bumped, which means that any process currently in memory keeps going but starting new processes becomes nearly impossible.&lt;&#x2F;p&gt;
&lt;p&gt;My fix for this (it only has to last a week, while my X201 is shipping) is stupid, but it works.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;stupid.jpg&quot; alt=&quot;A ThinkPad laptop held together with packing tape&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Packing tape!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The SomeoneWhoCares Hosts File</title>
        <published>2015-02-26T00:00:00+00:00</published>
        <updated>2015-02-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/26/my_favorite_ad_blocker/"/>
        <id>https://edunham.net/2015/02/26/my_favorite_ad_blocker/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/26/my_favorite_ad_blocker/">&lt;p&gt;I&#x27;ve been having intermittent problems with Firefox freezing up, pegging a CPU, eating all my memory, and freezing my desktop environment over the past few weeks. The only apparent trend in their occurrence was that they happened while I was active on HabitRPG and other ridiculously Javascript-heavy sites.&lt;&#x2F;p&gt;
&lt;p&gt;I disabled AdBlock Plus, and haven&#x27;t had a similar freeze since then. I also haven&#x27;t seen an increase in the amount of annoying ads, even when I visit questionable websites. This is because I took 30 seconds when first setting up my Arch installation to take an incredibly easy and effective action against ads.&lt;&#x2F;p&gt;
&lt;p&gt;The way this works is based on the premise that most intrusive ads come from the same 10,000 or so domains. Wouldn&#x27;t it be neat if, instead of spending a bunch of resources looking up the domain of an annoying ad and then loading it to barrage your screen with distracting and obnoxious content, your computer just recognized when a page was trying to load an ad and said &quot;nope&quot;?&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s exactly what the &lt;a href=&quot;http:&#x2F;&#x2F;someonewhocares.org&#x2F;hosts&#x2F;&quot;&gt;SomeoneWhoCares hosts file&lt;&#x2F;a&gt; does. Since computers are not implicitly smart, they have to take dozens of tiny actions in order to load an ad or web page. One of those actions is &quot;look up where to find it&quot;, using the Domain Name System (DNS).&lt;&#x2F;p&gt;
&lt;p&gt;You can think of DNS as basically the phone book of the internet. Back when people used land-line phones, it was pretty common to keep a list of useful numbers pinned to the wall near the handset. It&#x27;s faster to look up a number on a little note nearby than to dig through the phone book trying to find it. A Linux computer keeps a similar short-list of addresses it&#x27;s expected to need in a file called &lt;code&gt;&#x2F;etc&#x2F;hosts&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;How does this help with removing ads? Well, back in the landline days, did you ever go through a bad breakup and scratch out your crazy ex&#x27;s number on the list on the wall, writing &quot;NO DO NOT CALL THEM&quot; in its place? You can use &lt;code&gt;&#x2F;etc&#x2F;hosts&lt;&#x2F;code&gt; to make your computer do basically the same thing. By putting advertisers&#x27; names in the hosts file with their numbers listed as &lt;code&gt;127.0.0.1&lt;&#x2F;code&gt;, you do the same thing: If your computer wants to talk to a site that only serves ads, it&#x27;s told &quot;just call yourself&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;If you were really enterprising, you could set up a server to offer pictures of kittens in reply to all queries to localhost, so that you&#x27;d get kittens instead of ads. However, I&#x27;m lazy, so I&#x27;m content to have 404 warnings instead of banner ads.&lt;&#x2F;p&gt;
&lt;p&gt;If you want to use the hosts file too, it&#x27;s super simple:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ wget someonewhocares.org&#x2F;hosts&#x2F;hosts
&lt;&#x2F;span&gt;&lt;span&gt;$ sudo cat hosts &amp;gt;&amp;gt; &#x2F;etc&#x2F;hosts
&lt;&#x2F;span&gt;&lt;span&gt;$ rm hosts
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The second line uses sudo because regular users don&#x27;t have permission to change &#x2F;etc&#x2F;hosts. It&#x27;s safer to concatenate the downloaded hosts file onto the end of the existing hosts file than to overwrite it, so that any hosts settings that you start out with will be preserved.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Making a PDF of Hieroglyph slides</title>
        <published>2015-02-24T00:00:00+00:00</published>
        <updated>2015-02-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/24/making_a_pdf_of_hieroglyph_slides/"/>
        <id>https://edunham.net/2015/02/24/making_a_pdf_of_hieroglyph_slides/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/24/making_a_pdf_of_hieroglyph_slides/">&lt;p&gt;I build slides for my presentations with Hieroglyph, which makes beautiful HTML presentations out of raw ReStructuredText. However, HTML slides with my speaker notes in a JavaScript console are not ideal for redistribution.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;try-building-via-latex&quot;&gt;Try Building via LaTeX&lt;&#x2F;h1&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;Skip down to the rst2pdf part if you want an easy and effective solution to this problem. I&#x27;m only writing up the LaTeX stuff in case it&#x27;s useful to people Googling the errors that I hit.&lt;&#x2F;p&gt;
&lt;p&gt;According to the &lt;a href=&quot;http:&#x2F;&#x2F;sphinx-doc.org&#x2F;tutorial.html&quot;&gt;Sphinx tutorial&lt;&#x2F;a&gt;, the recommended way to build PDFs of Sphinx documentation is through the &lt;a href=&quot;http:&#x2F;&#x2F;sphinx-doc.org&#x2F;builders.html#sphinx.builders.latex.LaTeXBuilder&quot;&gt;LaTeX builder&lt;&#x2F;a&gt;. According to the docs, this should enable me to simply use &lt;code&gt;make latexpdf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I had installed &lt;code&gt;texlive-core&lt;&#x2F;code&gt; to do basic PDF rendering, but it did not include certain required packages such as &lt;code&gt;titlesec.sty&lt;&#x2F;code&gt;. At the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;TeX_Live&quot;&gt;Arch Wiki&#x27;s suggestion&lt;&#x2F;a&gt;, I installed &lt;code&gt;texlive-most&lt;&#x2F;code&gt;. The fonts took a long time to download, but it will prevent further problems with missing packages.&lt;&#x2F;p&gt;
&lt;p&gt;With &lt;code&gt;texlive-most&lt;&#x2F;code&gt; installed, it finds the required styles but has a problem with an errant Unicode character in the input file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;! Package inputenc Error: Unicode char \u8:­ not set up for use with LaTeX.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A thread on &lt;a href=&quot;http:&#x2F;&#x2F;tex.stackexchange.com&#x2F;questions&#x2F;83440&#x2F;inputenc-error-unicode-char-u8-not-set-up-for-use-with-latex&quot;&gt;StackExchange&lt;&#x2F;a&gt; theorizes that the &lt;code&gt;\u8&lt;&#x2F;code&gt; represents a &quot;no-break space&quot; character, so I think it&#x27;s safe to strip it out entirely. A rather heavy-handed fix &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;8562354&#x2F;remove-unicode-characters-from-textfiles-sed-other-bash-shell-methods&quot;&gt;from elsewhere&lt;&#x2F;a&gt; is to forcibly convert the whole file from UTF-8 to ASCII:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;iconv -c -f utf-8 -t ascii index.rst &amp;gt; temp &amp;amp;&amp;amp; mv temp index.rst 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;I previously had that command as &lt;code&gt;iconv -c -f utf-8 -t ascii index.rst &amp;gt; index.rst&lt;&#x2F;code&gt; which actually deletes the entire contents of index.rst. No wonder the pdf wouldn&#x27;t build :)&lt;&#x2F;p&gt;
&lt;p&gt;After violently taking away its unicode, the file builds a pdf in &lt;code&gt;_build&#x2F;latex&#x2F;&lt;&#x2F;code&gt;. But there are a bunch of gratuitous chapter titles, some pages are arbitrarily left blank, and the images&#x27; sizes seem totally random --some take up a full page; others are tiny.&lt;&#x2F;p&gt;
&lt;p&gt;Rather than digging further into the LaTeX rabbit hole, I decided to see if there wasn&#x27;t a more direct way to generate a pdf.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;build-directly-to-pdf&quot;&gt;Build Directly to PDF&lt;&#x2F;h1&gt;
&lt;p&gt;According to the &lt;a href=&quot;http:&#x2F;&#x2F;sphinx-doc.org&#x2F;builders.html&quot;&gt;Sphinx docs&lt;&#x2F;a&gt;, there exists a builder which converts rst directly into pdf.&lt;&#x2F;p&gt;
&lt;p&gt;To set it up, add the builder in &lt;code&gt;conf.py&lt;&#x2F;code&gt; by adding this line to the end:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;extensions += [&amp;#39;rst2pdf.pdfbuilder&amp;#39;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Also add the pdf target to the Makefile:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;pdf:                                                                            
&lt;&#x2F;span&gt;&lt;span&gt;    $(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) $(BUILDDIR)&#x2F;pdf                      
&lt;&#x2F;span&gt;&lt;span&gt;    @echo                                                                       
&lt;&#x2F;span&gt;&lt;span&gt;    @echo &amp;quot;Build finished. The PDF is in $(BUILDDIR)&#x2F;pdf.&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Remember that Makefiles need hard tabs; a tab expanded into spaces will not be recognized by Make. You can set this behavior up automatically with Vim&#x27;s filetype plugin, or just type &lt;code&gt;:set noexpandtab&lt;&#x2F;code&gt; when editing Makefiles.&lt;&#x2F;p&gt;
&lt;p&gt;Now &lt;code&gt;make pdf&lt;&#x2F;code&gt; throws the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;WARNING: &amp;quot;pdf_documents&amp;quot; config value references unknown document contents
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This means that &lt;code&gt;rst2pdf&lt;&#x2F;code&gt; wants some extra configuration information. &lt;a href=&quot;http:&#x2F;&#x2F;ralsina.me&#x2F;static&#x2F;manual.pdf&quot;&gt;The manual&lt;&#x2F;a&gt; doesn&#x27;t discuss Sphinx-specific configuration, but &lt;a href=&quot;https:&#x2F;&#x2F;bitbucket.org&#x2F;birkenfeld&#x2F;sphinx&#x2F;issue&#x2F;999&#x2F;create-pdf-using-rst2pdf&quot;&gt;a Sphinx issue&lt;&#x2F;a&gt; explains that you need one more line in &lt;code&gt;conf.py&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;pdf_documents = [(&amp;#39;index&amp;#39;, u&amp;#39;sample&amp;#39;, u&amp;#39;Sample rst2pdf doc&amp;#39;, u&amp;#39;Your Name&amp;#39;),]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After adding the &lt;code&gt;pdf_documents&lt;&#x2F;code&gt; configuration, &lt;code&gt;make pdf&lt;&#x2F;code&gt; generates an oddly styled but entirely legible pdf copy of the presentation&#x27;s content.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Scale13x Retrospective</title>
        <published>2015-02-23T00:00:00+00:00</published>
        <updated>2015-02-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/23/scale13x_retrospective/"/>
        <id>https://edunham.net/2015/02/23/scale13x_retrospective/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/23/scale13x_retrospective/">&lt;p&gt;This year I had the pleasure of attending and speaking at the Southern California Linux Expo. Here&#x27;s what I observed about the location, the conference&#x27;s organization, and my own talk.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;la-logistics&quot;&gt;LA logistics&lt;&#x2F;h1&gt;
&lt;p&gt;Gettting a room in the same hotel as the conference was, as always, an excellent choice. I booked about 3 weeks before the conference with no trouble, but a coworker who tried to book only a week before was redirected to the Mariott next door because the Hilton was full.&lt;&#x2F;p&gt;
&lt;p&gt;The Los Angeles Airpot Hilton is in something of a food desert. Within walking distance there are a Burger King, a Carl&#x27;s Jr, and a Denny&#x27;s. The hotel contains a Starbucks, a sit-down restaraunt, and a bar, though the prices are 2 to 3 times what one would consider reasonable outside the combination of conference, hotel, and California. My hotel room contained a coffee maker but no fridge, so I was glad to have brought along non-perishable breakfast and snack foods. All of the fast food joints nearby make a pretty decent attempt at offering salads, non-deep-fried chicken, and other staples of my apathetically health-conscious dietary preferences.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-conference&quot;&gt;The Conference&lt;&#x2F;h1&gt;
&lt;p&gt;The registration process was not immediately obvious, and the self-checkin kiosks demanded the exact email address and zip code with which one registered for the conference. The line for getting badges and bags after checkin seemed to go quite fast, though.&lt;&#x2F;p&gt;
&lt;p&gt;The expo hall spilled into the smaller rooms across from the hotel&#x27;s main ballroom as well as out into the hallways, which made it feel almost like two separate halls. The dense population and energy of the expo floor felt similar to OSCON, but SCALE&#x27;s booths were predominantly actual open-source projects and companies which contribute back upstream, rather than big-name sponsors looking solely to hire. The larger booths, such as HP, were arranged to take several consecutive spaces in an aisle rather than to stand as their own little islands like OSCON&#x27;s biggest sponsors tend to.&lt;&#x2F;p&gt;
&lt;p&gt;The conference organizers were extremely friendly and helpful, which created a welcoming and friendly atmosphere. I felt that if an incident involving misconduct or bad judgement had happened to occur among attendees, the organizers would have been able to handle it immediately and effectively. There was almost always at least one organizer hanging out in the speaker lounge, often their affable publicist Larry Cafiero, to assist speakers and anwer any questions. This made it easy to get help with problems small enough not to warrant seeking out or interrupting a busier organizer, but large enough to be annoying if left unsolved.&lt;&#x2F;p&gt;
&lt;p&gt;The rooms were often packed to capacity -- I think that there were more people in attendance than the venue was designed to comfortably hold. I doubt that all of the attendees would have fit into even the largest room at once; keynotes were standing room only and relatively early in the morning.&lt;&#x2F;p&gt;
&lt;p&gt;Despite the room size constraints, the layout of the conference was generally good. All of the presentation rooms were centrally located on the second floor and the expo hall was on the ground floor, so it never took more than 2 or 3 minutes to get from one room to another.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;my-talk&quot;&gt;My Talk&lt;&#x2F;h1&gt;
&lt;p&gt;I had completely rewritten my Human Hacking talk since I gave it at SeaGL, and ironically several of the strengths that I saw in its previous incarnation became weaknesses here. However, the weaknesses from SeaGL became strengths at SCALE as well.&lt;&#x2F;p&gt;
&lt;p&gt;The good:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Organization at the slide-order level was much better.&lt;&#x2F;li&gt;
&lt;li&gt;Putting more work into my slides meant I had greater trust that they contained all of the important information, and caused me to take fewer tangents.&lt;&#x2F;li&gt;
&lt;li&gt;Extra rehersals of the talk helped me excise most of the anecdotes, keeping only those that were directly relevant to the content. This slight narrowing of scope gave me more time to repeat key points.&lt;&#x2F;li&gt;
&lt;li&gt;I made the audience laugh, and people remained engaged throughout the presentation.&lt;&#x2F;li&gt;
&lt;li&gt;The little &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;Q-n-A&quot;&gt;Q&amp;amp;A app&lt;&#x2F;a&gt; which Lucy Wyman helped me throw together the day before functioned flawlessly for my interactive demo. And the demo actually did what I expected this time, unlike the ones at SeaGL which got the opposite of the effects I&#x27;d hoped for.&lt;&#x2F;li&gt;
&lt;li&gt;My laptop&#x27;s VGA output Just Worked with the projectors. Nothing in the stack of web browser, window manager, operating system, hardware, cables, and projectors decided to spontaneoulsy quit while I was talking.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The bad:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I spoke in the keynote room to an audience of about 40 people (surprisingly large for 3pm on the last day of the conference), and let myself get quite nervous. This caused me to regress back to using a variety of annoying speech habits which I&#x27;ve been making an effort to eliminate.&lt;&#x2F;li&gt;
&lt;li&gt;I was unprepared for the wierd acoustics of the stage (another function of room size). The speakers were in the corners of the room and I was in the middle with a wireless mic, and I experienced perhaps half a second of delay between saying a word and hearing it over the sound system. Before my next talk I&#x27;m going to find a way to simulate that effect, and practice with it occurring, because I found it quite distracting and it&#x27;s probably common in similar audio setups.&lt;&#x2F;li&gt;
&lt;li&gt;Due to last-minute advice about potential network problems and admonitions to use a local copy of my slides, I presented off their second-to-last version since it was the most recent that I had built on my laptop. It had inconsistent capitalization in the slide titles and the wrong URL on the first and last slides.&lt;&#x2F;li&gt;
&lt;li&gt;My transitions between individual slides, and organization of ideas within each slide&#x27;s topic, were less polished than at SeaGL. This has taught me that the quality of my transitions depends on planning them ahead of time and leaving myself plenty of notes, no matter how often I practice a talk.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;On the whole, I think that my talk was quite successful, and I get the impression (from body language and watching some people taking notes) that most members of the audience enjoyed at least some parts of the information-packed hour. I&#x27;m not particularly looking forward to watching the recordings, because I&#x27;m well aware of the verbal bad habits (such as filler words) that I engaged in, but it&#x27;ll help me target my speaking practice to avoid such problems at future conferences.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Automating Irssi</title>
        <published>2015-02-16T00:00:00+00:00</published>
        <updated>2015-02-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/16/automating_irssi/"/>
        <id>https://edunham.net/2015/02/16/automating_irssi/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/16/automating_irssi/">&lt;p&gt;I use Irssi as my IRC client because I don&#x27;t need to write my own plugins for it, and its documentation is excellent even when you&#x27;re not sure what questions to ask. If I needed plugins that haven&#x27;t already been written by others, and didn&#x27;t mind either reading the entire manual or constantly asking for help to solve simple problems, I would switch to Weechat.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve configured Irssi to automatically join the networks that I want, authenticate me to services, then join my preferred channels. The &lt;a href=&quot;http:&#x2F;&#x2F;irssi.org&#x2F;beginner&#x2F;&quot;&gt;Irssi beginner docs&lt;&#x2F;a&gt; are a great reference for client commands to set these functions up, but if I was doing it over again, it would save a lot of keystrokes to simply hand-write the config file which those commands end up generating. Here&#x27;s how you can do that.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;directory-structure&quot;&gt;Directory structure&lt;&#x2F;h1&gt;
&lt;p&gt;Irssi loads its configuration from the files in &lt;code&gt;~&#x2F;.irssi&#x2F;&lt;&#x2F;code&gt; by default, and I&#x27;ve never encountered a reason to put the configs elsewhere:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ tree .irssi
&lt;&#x2F;span&gt;&lt;span&gt;.irssi&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;├── config
&lt;&#x2F;span&gt;&lt;span&gt;├── default.theme
&lt;&#x2F;span&gt;&lt;span&gt;├── sasl.auth
&lt;&#x2F;span&gt;&lt;span&gt;└── scripts
&lt;&#x2F;span&gt;&lt;span&gt;    └── autorun
&lt;&#x2F;span&gt;&lt;span&gt;            └── cap_sasl.pl
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;irssi-scripts-autorun-cap-sasl-pl&quot;&gt;.irssi&#x2F;scripts&#x2F;autorun&#x2F;cap_sasl.pl&lt;&#x2F;h1&gt;
&lt;p&gt;This is how you automatically authenticate yourself to services before joining channels, so that you&#x27;ll always have your cloak on.&lt;&#x2F;p&gt;
&lt;p&gt;You can read &lt;a href=&quot;https:&#x2F;&#x2F;freenode.net&#x2F;sasl&#x2F;sasl-irssi.shtml&quot;&gt;Freenode&#x27;s directions&lt;&#x2F;a&gt; on setting this up, or just download &lt;a href=&quot;https:&#x2F;&#x2F;freenode.net&#x2F;sasl&#x2F;cap_sasl.pl&quot;&gt;cap_sasl.pl&lt;&#x2F;a&gt; and put it into &lt;code&gt;~&#x2F;.irssi&#x2F;scripts&#x2F;autorun&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When &lt;code&gt;cap_sasl.pl&lt;&#x2F;code&gt; runs, it will read &lt;code&gt;sasl.auth&lt;&#x2F;code&gt; and authenticate you as specified in that file.&lt;&#x2F;p&gt;
&lt;p&gt;The format of the file is &lt;code&gt;networkname&lt;&#x2F;code&gt;, &lt;code&gt;hard tab&lt;&#x2F;code&gt;, &lt;code&gt;nick&lt;&#x2F;code&gt;, &lt;code&gt;password&lt;&#x2F;code&gt;, &lt;code&gt;hard tab&lt;&#x2F;code&gt;, &lt;code&gt;PLAIN&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If my username&#x2F;password combo to authenticate with Freenode&#x27;s nickserv was hello&#x2F;world, and on work IRC was username&#x2F;mypass, the &lt;code&gt;sasl.auth&lt;&#x2F;code&gt; file would look like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;freenode    hello world     PLAIN
&lt;&#x2F;span&gt;&lt;span&gt;work        username mypass PLAIN
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;irssi-config&quot;&gt;.irssi&#x2F;config&lt;&#x2F;h1&gt;
&lt;p&gt;It&#x27;s best to edit the config after running Irssi once, since it contains a bunch of defaults for aliases and display features which I won&#x27;t discuss here.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;servers&quot;&gt;servers&lt;&#x2F;h2&gt;
&lt;p&gt;Each server has an address, a nickname (&quot;chatnet&quot;), a port, SSL settings, and whether it should autoconnect. &lt;strong&gt;You should connect with SSL whenever possible&lt;&#x2F;strong&gt;, because encrypting all your communications with the server makes it safer to send your login credentials in plaintext.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;servers = (
&lt;&#x2F;span&gt;&lt;span&gt;  {
&lt;&#x2F;span&gt;&lt;span&gt;    address = &amp;quot;irc.freenode.net&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    chatnet = &amp;quot;freenode&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    port = &amp;quot;6697&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    use_ssl = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    ssl_verify = &amp;quot;no&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    autoconnect = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;  },
&lt;&#x2F;span&gt;&lt;span&gt;  {
&lt;&#x2F;span&gt;&lt;span&gt;    address = &amp;quot;irc.workplace.com&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    chatnet = &amp;quot;work&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    port = &amp;quot;6697&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    password = &amp;quot;Work Company&amp;#39;s Super Seekrit Passphrase&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    use_ssl = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    ssl_verify = &amp;quot;no&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    autoconnect = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can add as many servers as you want, following that format.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chatnets&quot;&gt;chatnets&lt;&#x2F;h2&gt;
&lt;p&gt;Reassure Irssi that each network&#x27;s nickname does belong to an IRC network:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;chatnets = {
&lt;&#x2F;span&gt;&lt;span&gt;  freenode = { type = &amp;quot;IRC&amp;quot;; };
&lt;&#x2F;span&gt;&lt;span&gt;  work = { type = &amp;quot;IRC&amp;quot;; };
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a chatnet entry for each network that you refered to when configuring the servers settings.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;channels&quot;&gt;channels&lt;&#x2F;h2&gt;
&lt;p&gt;Add one of these for each channel that your client should autojoin:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;channels = (
&lt;&#x2F;span&gt;&lt;span&gt;  { name = &amp;quot;#osu-lug&amp;quot;; chatnet = &amp;quot;freenode&amp;quot;; autojoin = &amp;quot;yes&amp;quot;; },
&lt;&#x2F;span&gt;&lt;span&gt;  { name = &amp;quot;#osuosl&amp;quot;; chatnet = &amp;quot;freenode&amp;quot;; autojoin = &amp;quot;yes&amp;quot;; },
&lt;&#x2F;span&gt;&lt;span&gt;  { name = &amp;quot;#infra&amp;quot;; chatnet = &amp;quot;work&amp;quot;; autojoin = &amp;quot;yes&amp;quot;; },
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;aliases&quot;&gt;aliases&lt;&#x2F;h2&gt;
&lt;p&gt;You don&#x27;t need to touch these to set up autoconnects. To learn more about them, check out &lt;a href=&quot;http:&#x2F;&#x2F;linuxreviews.org&#x2F;software&#x2F;irc&#x2F;irssi&#x2F;#toc4&quot;&gt;this tutorial&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;settings&quot;&gt;settings&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;code&gt;real_name&lt;&#x2F;code&gt; variable is where you set your display name, if Irssi was unable to find it in your GECOS information.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hilights&quot;&gt;hilights&lt;&#x2F;h2&gt;
&lt;p&gt;They&#x27;ll look like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;hilights = (
&lt;&#x2F;span&gt;&lt;span&gt;  { text = &amp;quot;edunham&amp;quot;; nick = &amp;quot;yes&amp;quot;; word = &amp;quot;yes&amp;quot;; },
&lt;&#x2F;span&gt;&lt;span&gt;  {
&lt;&#x2F;span&gt;&lt;span&gt;    text = &amp;quot;batsignal&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    color = &amp;quot;%G&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    nick = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    word = &amp;quot;yes&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;    channels = ( &amp;quot;#infra&amp;quot; );
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This way, whenever anyone says my name in any channel, I&#x27;ll get hilighted in the default color (purpleish). Whenever someone says &quot;batsignal&quot; in the infra channel, I&#x27;ll get hilighted in green.&lt;&#x2F;p&gt;
&lt;p&gt;More information about setting hilights with regexes, coloring, and other cool tricks can be found by grepping for &lt;code&gt;hilight&lt;&#x2F;code&gt; in &lt;a href=&quot;http:&#x2F;&#x2F;www.irssi.org&#x2F;documentation&#x2F;manual&quot;&gt;the manual&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ignores&quot;&gt;ignores&lt;&#x2F;h2&gt;
&lt;p&gt;Your ignores list specifies what message types (&quot;levels&quot;) should be ignored in certain channels:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ignores = (
&lt;&#x2F;span&gt;&lt;span&gt;  { level = &amp;quot;JOINS PARTS QUITS NICKS&amp;quot;; channels = ( &amp;quot;#osuosl&amp;quot; )},
&lt;&#x2F;span&gt;&lt;span&gt;  { level = &amp;quot;JOINS PARTS QUITS&amp;quot;; channels = ( &amp;quot;#infra&amp;quot; ) }
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Section 2 of &lt;a href=&quot;http:&#x2F;&#x2F;www.irssi.org&#x2F;documentation&#x2F;manual&quot;&gt;the manual&lt;&#x2F;a&gt; has more information on levels.&lt;&#x2F;p&gt;
&lt;p&gt;I personally like to turn off joins and parts in larger or busier channels, but leave them on in smaller channels where it&#x27;s important to notice when a new community member arrives.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;windows&quot;&gt;windows&lt;&#x2F;h2&gt;
&lt;p&gt;This sets which buffer goes where when Irssi starts. Setting the layout like this is useful to me because I get accustomed to buffers being in particular places, and tend to memorize the numbers of important channels so I know immediately where I&#x27;ve been pinged:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;windows = {
&lt;&#x2F;span&gt;&lt;span&gt;  1 = { immortal = &amp;quot;yes&amp;quot;; name = &amp;quot;(status)&amp;quot;; level = &amp;quot;ALL&amp;quot;; };
&lt;&#x2F;span&gt;&lt;span&gt;  2 = {
&lt;&#x2F;span&gt;&lt;span&gt;    items = (
&lt;&#x2F;span&gt;&lt;span&gt;      { type = &amp;quot;CHANNEL&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        chat_type = &amp;quot;IRC&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        name = &amp;quot;#osu-lug&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        tag = &amp;quot;freenode&amp;quot;; }
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;  };
&lt;&#x2F;span&gt;&lt;span&gt;  3 = {
&lt;&#x2F;span&gt;&lt;span&gt;    items = (
&lt;&#x2F;span&gt;&lt;span&gt;      { type = &amp;quot;CHANNEL&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        chat_type = &amp;quot;IRC&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        name = &amp;quot;#infra&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        tag = &amp;quot;work&amp;quot;; }
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;  };
&lt;&#x2F;span&gt;&lt;span&gt;  4 = {
&lt;&#x2F;span&gt;&lt;span&gt;    items = (
&lt;&#x2F;span&gt;&lt;span&gt;      { type = &amp;quot;QUERY&amp;quot;; 
&lt;&#x2F;span&gt;&lt;span&gt;        chat_type = &amp;quot;IRC&amp;quot;; 
&lt;&#x2F;span&gt;&lt;span&gt;        name = &amp;quot;chanserv&amp;quot;; 
&lt;&#x2F;span&gt;&lt;span&gt;        tag = &amp;quot;freenode&amp;quot;; }
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;  };
&lt;&#x2F;span&gt;&lt;span&gt;  5 = {
&lt;&#x2F;span&gt;&lt;span&gt;    items = (
&lt;&#x2F;span&gt;&lt;span&gt;      { type = &amp;quot;CHANNEL&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        chat_type = &amp;quot;IRC&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        name = &amp;quot;#osuosl&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;        tag = &amp;quot;freenode&amp;quot;; }
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;  };
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;what-next&quot;&gt;What next?&lt;&#x2F;h1&gt;
&lt;p&gt;Read about &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;02&#x2F;15&#x2F;starting_screen_irssi_at_boot.html&quot;&gt;starting Irssi automatically when your VPS boots&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Starting screen+irssi at boot</title>
        <published>2015-02-15T00:00:00+00:00</published>
        <updated>2015-02-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/15/starting_screen_irssi_at_boot/"/>
        <id>https://edunham.net/2015/02/15/starting_screen_irssi_at_boot/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/15/starting_screen_irssi_at_boot/">&lt;p&gt;Installing security updates isn&#x27;t always enough to apply them; you have to reboot from time to time if you want the benefit of any kernel upgrades that don&#x27;t use &lt;a href=&quot;http:&#x2F;&#x2F;linux.slashdot.org&#x2F;story&#x2F;15&#x2F;02&#x2F;12&#x2F;1853215&#x2F;live-patching-now-available-for-linux&quot;&gt;live patching&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s also a small chance that my hosting provider might reboot my VPS. This has only happened once so far, as part of the Spectre&#x2F;Meltdown mitigation in early 2018. After that reboot, my IRC setup came back to life without any problems as a result of the configuration that I documented in this post.&lt;&#x2F;p&gt;
&lt;p&gt;In both cases, I would like my IRC client to get back online as soon as it can after reboot.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-easy-way&quot;&gt;The Easy Way&lt;&#x2F;h1&gt;
&lt;p&gt;Create a script in your homedir for starting screen with your desired settings:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cat ~&#x2F;startscreen.sh
&lt;&#x2F;span&gt;&lt;span&gt;#! &#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;screen -dmS irc bash -c &amp;#39;&#x2F;usr&#x2F;bin&#x2F;irssi&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first line of the script tells your system to run it with bash. The screen command says:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-dm&lt;&#x2F;code&gt; tells screen to start in detached mode.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;-S irc&lt;&#x2F;code&gt; means that the session will be named &lt;code&gt;irc&lt;&#x2F;code&gt;, which is hard-coded into a couple of scripts I use to automatically connect to it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;-c &#x2F;usr&#x2F;bin&#x2F;irssi&lt;&#x2F;code&gt; tells screen to start irssi in the newly created session.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As root, add the following line to &lt;code&gt;&#x2F;etc&#x2F;rc.local&lt;&#x2F;code&gt;, substituting the correct value for &lt;code&gt;username&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;su - username -c &amp;quot;&#x2F;home&#x2F;username&#x2F;startscreen.sh&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Leave &lt;code&gt;exit 0&lt;&#x2F;code&gt; as the last line in the file.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;rc.local&lt;&#x2F;code&gt; is automatically run at the end of each multiuser runlevel, which makes it useful for starting programs at boot.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;without-root&quot;&gt;Without Root&lt;&#x2F;h1&gt;
&lt;p&gt;Let&#x27;s say that you want to get the above behavior on a system to which you do not have root access.&lt;&#x2F;p&gt;
&lt;p&gt;Hypothetically, one should be able to just drop the line &lt;code&gt;@reboot &#x2F;usr&#x2F;bin&#x2F;screen -dmS irc -c &quot;&#x2F;usr&#x2F;bin&#x2F;irssi&quot;&lt;&#x2F;code&gt; or &lt;code&gt;@reboot &#x2F;home&#x2F;username&#x2F;startscreen.sh&lt;&#x2F;code&gt; into one&#x27;s crontab (&lt;code&gt;crontab -e&lt;&#x2F;code&gt;), where it would be executed at startup. This works for most scripts, but screen doesn&#x27;t like to start when it&#x27;s not attached to a terminal.&lt;&#x2F;p&gt;
&lt;p&gt;The most common workaround for this, in an &lt;em&gt;&quot;if it&#x27;s stupid but it works, it&#x27;s not stupid&quot;&lt;&#x2F;em&gt; kind of way, is to SSH to localhost and then start screen. Fortunately, a helpful user named znx typed up the instructions for that process &lt;a href=&quot;http:&#x2F;&#x2F;www.linux-noob.com&#x2F;forums&#x2F;index.php?&#x2F;topic&#x2F;2421-start-screen-irssi-on-boot&#x2F;#entry11892&quot;&gt;here&lt;&#x2F;a&gt;. Here&#x27;s a summary, if you&#x27;re on a new install with no authorized keys set yet:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ssh-keygen # accept all defaults
&lt;&#x2F;span&gt;&lt;span&gt;$ cp ~&#x2F;.ssh&#x2F;id_rsa.pub ~&#x2F;.ssh&#x2F;authorized_keys
&lt;&#x2F;span&gt;&lt;span&gt;$ vim ~&#x2F;.ssh&#x2F;config
&lt;&#x2F;span&gt;&lt;span&gt;    Host lo
&lt;&#x2F;span&gt;&lt;span&gt;        HostKeyAlias localhost
&lt;&#x2F;span&gt;&lt;span&gt;        HostName localhost
&lt;&#x2F;span&gt;&lt;span&gt;        IdentityFile &#x2F;home&#x2F;username&#x2F;.ssh&#x2F;id_localhost
&lt;&#x2F;span&gt;&lt;span&gt;        User username
&lt;&#x2F;span&gt;&lt;span&gt;        Port 22
&lt;&#x2F;span&gt;&lt;span&gt;$ ssh lo # accept the key this first time
&lt;&#x2F;span&gt;&lt;span&gt;$ crontab -e # add following line and save
&lt;&#x2F;span&gt;&lt;span&gt;    @reboot ssh lo &#x2F;home&#x2F;username&#x2F;startscreen.sh
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;with-systemd&quot;&gt;With systemd&lt;&#x2F;h1&gt;
&lt;p&gt;Check out &lt;a href=&quot;http:&#x2F;&#x2F;www.mythmon.com&#x2F;posts&#x2F;2015-02-15-systemd-weechat.html&quot;&gt;mythmon&#x27;s writeup&lt;&#x2F;a&gt; with how to make your own systemd services and configure them to run automatically.&lt;&#x2F;p&gt;
&lt;p&gt;I haven&#x27;t tried this yet myself, but I expect that any problems repeating it with screen would be most likely to happen when trying to start a detached screen without a terminal, and could be circumvented by the ssh-to-localhost method described above.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-next&quot;&gt;What next?&lt;&#x2F;h1&gt;
&lt;p&gt;Having Irssi running is cool, but only really useful if it&#x27;s connected to the right newtorks and channels. The &lt;a href=&quot;http:&#x2F;&#x2F;irssi.org&#x2F;beginner&#x2F;&quot;&gt;irssi docs&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;freenode.net&#x2F;sasl&#x2F;sasl-irssi.shtml&quot;&gt;freenode sasl guide&lt;&#x2F;a&gt; contain all the necessary information.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Résumé Improvement with LaTeX Macros</title>
        <published>2015-02-14T00:00:00+00:00</published>
        <updated>2015-02-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/14/resume_improvement_with_latex_macros/"/>
        <id>https://edunham.net/2015/02/14/resume_improvement_with_latex_macros/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/14/resume_improvement_with_latex_macros/">&lt;p&gt;It&#x27;s career fair season at OSU, which means that I&#x27;m taking my annual dive into LaTeX programming in order to improve my résumé.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m discovering how powerful and easy to use the macros in LaTeX are. Let&#x27;s talk about why you might want to convert your résumé to LaTeX, and how to do it easily.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-typeset-your-resume-in-latex&quot;&gt;Why typeset your résumé in LaTeX?&lt;&#x2F;h1&gt;
&lt;p&gt;1) &lt;em&gt;Use version control&lt;&#x2F;em&gt;. This is essential for putting back sections that you removed earlier, and for undoing mistakes.&lt;&#x2F;p&gt;
&lt;p&gt;2) &lt;em&gt;Demonstrate technical competence&lt;&#x2F;em&gt;. I want to work at the kind of company that hires applicants who enjoy learning the best tool for an important job, and LaTeX is the best free tool for beautiful typesetting.&lt;&#x2F;p&gt;
&lt;p&gt;3) &lt;em&gt;More prettiness for less frustration&lt;&#x2F;em&gt;. I personally hate fighting with a word processor&#x27;s GUI, trying to standardize various disparate sections and convince everything to align correctly. When your document&#x27;s appearance is represented as code, it&#x27;s also easier to copy and paste other people&#x27;s fixes for common problems that you encounter.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;latex-macros&quot;&gt;LaTeX Macros&lt;&#x2F;h1&gt;
&lt;p&gt;If you&#x27;ve only used LaTeX to typeset math assignments and reports, its default formatting has probably worked fine for you.&lt;&#x2F;p&gt;
&lt;p&gt;However, résumés are a special case that requires identical formatting for multiple items. In my own experimentation and experience, résumés look best when all the section names are formatted identically, and when all the items within each section match each other.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;en.wikibooks.org&#x2F;wiki&#x2F;LaTeX&#x2F;Macros&quot;&gt;Macros&lt;&#x2F;a&gt; are LaTeX&#x27;s way of solving that standardization problem.&lt;&#x2F;p&gt;
&lt;p&gt;As the &lt;a href=&quot;http:&#x2F;&#x2F;en.wikibooks.org&#x2F;wiki&#x2F;LaTeX&#x2F;Macros&quot;&gt;WikiBooks article&lt;&#x2F;a&gt; explains, the format of a macro is simple. In the preamble (before &lt;code&gt;\begin{document}&lt;&#x2F;code&gt;), you define the macro&#x27;s name, number of arguments, and behavior:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\newcommand{\example}[3] {
&lt;&#x2F;span&gt;&lt;span&gt;    \emph{#1} was the first argument\\
&lt;&#x2F;span&gt;&lt;span&gt;    % the double backslash forces a line break
&lt;&#x2F;span&gt;&lt;span&gt;    \textbf{#2} was the second\\
&lt;&#x2F;span&gt;&lt;span&gt;    ``#3&amp;#39;&amp;#39; was the third
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then in your document, you call the macro by passing it the number of arguments that were stated in the square brackets:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\example{why}{hello}{there}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The macro simply inserts its text, substituting in its first argument for &lt;code&gt;#1&lt;&#x2F;code&gt;, second argument for &lt;code&gt;#2&lt;&#x2F;code&gt;, and so forth.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;latex-macro-example.png&quot; alt=&quot;LaTeX macro code example showing formatting&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Remember the backslash before the macro name when declaring the &lt;code&gt;newcommand&lt;&#x2F;code&gt;. Since the macro inserted the text as a paragraph, the first line defaults to being indented. To prevent default indentation behavior, put &lt;code&gt;\setlength{\parindent}{0pt}&lt;&#x2F;code&gt; in your document&#x27;s preamble.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;my-resume-macros&quot;&gt;My Résumé Macros&lt;&#x2F;h1&gt;
&lt;p&gt;The most important macros in my résumé are the one which formats a heading, and the one which formats an experience. I include the ulem package for convenient underlining in the titles:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\documentclass{article}
&lt;&#x2F;span&gt;&lt;span&gt;\usepackage[normalem]{ulem}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;\newcommand{\heading}[1]{
&lt;&#x2F;span&gt;&lt;span&gt;    \section*{\uline{\hfill #1}}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;\newcommand{\experience}[3]{
&lt;&#x2F;span&gt;&lt;span&gt;    \item[{#1}, \emph{#2}]
&lt;&#x2F;span&gt;&lt;span&gt;    \hfill #3
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In the body of the document, I can call each macro wherever it&#x27;s needed:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\begin{document}
&lt;&#x2F;span&gt;&lt;span&gt;\heading{Employment}
&lt;&#x2F;span&gt;&lt;span&gt;\begin{description}
&lt;&#x2F;span&gt;&lt;span&gt;\experience{Awesome Company}
&lt;&#x2F;span&gt;&lt;span&gt;           {My Job Title}
&lt;&#x2F;span&gt;&lt;span&gt;           {date - present}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    I did a thing and improved the buzzword by 40\%.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Collaborated synergistically with diverse enthusiasm.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;\experience{Old Company}
&lt;&#x2F;span&gt;&lt;span&gt;           {Just an Intern}
&lt;&#x2F;span&gt;&lt;span&gt;           {date - date}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Wrote code and fixed bugs.
&lt;&#x2F;span&gt;&lt;span&gt;\end{description}
&lt;&#x2F;span&gt;&lt;span&gt;\heading{Education}
&lt;&#x2F;span&gt;&lt;span&gt;\begin{description}
&lt;&#x2F;span&gt;&lt;span&gt;\experience{Academic Institution}
&lt;&#x2F;span&gt;&lt;span&gt;           {The Degree I Got}
&lt;&#x2F;span&gt;&lt;span&gt;           {grad year}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    I had a GPA!
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    I took classes in subjects!
&lt;&#x2F;span&gt;&lt;span&gt;\end{description}
&lt;&#x2F;span&gt;&lt;span&gt;\end{document}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the indentation is decorative, but the blank lines are necessary to prevent LaTeX from assuming everything should be one line and wrapping only when the line is full.&lt;&#x2F;p&gt;
&lt;p&gt;With macros, LaTeX is actually easy to read! It becomes trivially easy to test changes -- &quot;what if I italicized all the job titles?&quot; can be tested with half a dozen keystrokes, rather than the clumsily wielded flamethrower of global find and replace.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;resume-styled-output.png&quot; alt=&quot;Styled résumé output showing macro results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s what the code in this post looks like when rendered. Lots of prettiness, not very much code. &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;resume&#x2F;blob&#x2F;master&#x2F;resume.tex&quot;&gt;My own résumé&lt;&#x2F;a&gt; is only slightly more complex -- most of the extra code is dedicated to shrinking the margins and reducing the spacing between lines in order to fit its content correctly onto a single page. (A PDF of my résumé is available &lt;a href=&quot;http:&#x2F;&#x2F;resume.edunham.net&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;more-latex-tricks&quot;&gt;More LaTeX Tricks&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To make quotes show up correctly, use ```for opening-quotes and&lt;code&gt;&#x27;&#x27;\&lt;&#x2F;code&gt;` for closing-quotes.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;You can make C++ display more prettily, thanks to a macro from the &lt;a href=&quot;http:&#x2F;&#x2F;www.parashift.com&#x2F;c++-faq-lite&#x2F;latex-macros.html&quot;&gt;FAQ&lt;&#x2F;a&gt;. Unfortunately, most of it has to be a single line or LaTeX will add extra spaces:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;\newcommand{\CPP}{C\hspace{-.05em}\raisebox{.4ex}{\tiny\bf +}\hspace{-.10em}\raisebox{.4ex}{\tiny\bf +}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My Second Steps with Javascript</title>
        <published>2015-02-13T00:00:00+00:00</published>
        <updated>2015-02-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/13/my_second_steps_with_javascript/"/>
        <id>https://edunham.net/2015/02/13/my_second_steps_with_javascript/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/13/my_second_steps_with_javascript/">&lt;p&gt;There are many things to hate about Javascript. I&#x27;m not a fan of the language, and I&#x27;ve been known to laugh at people when they make unqualified claims that it&#x27;s &quot;good&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;However, I sometimes find myself wanting to build toys and share them with everyone who has a web browser. Compared to the security and scaling implications of making my poor little VPS run a bunch of other people&#x27;s code for them, Javascript becomes the only viable option.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;lazy-sysadmin-use-case&quot;&gt;Lazy Sysadmin Use Case&lt;&#x2F;h1&gt;
&lt;p&gt;Sometimes I have an idea which would fit beautifully into a shell script, and is a useful tool or toy that I&#x27;d like to share with the world. Unfortunately, &quot;the world&quot; consists primarily of people who can&#x27;t or won&#x27;t run a shell script themselves. The lowest common demoninator in this target audience is the ability to use a web browser.&lt;&#x2F;p&gt;
&lt;p&gt;One option would be to write the scripts in Bash or Python or PHP and set up my web server so that it runs the code for visitors to my site, with whatever inputs they give it. Unfortunately, from a security perspective, the problems with this idea are second only to handing over all your banking information to a disinherited royal heir. (Sure, some people do the latter &lt;a href=&quot;http:&#x2F;&#x2F;www.419eater.com&#x2F;&quot;&gt;for fun&lt;&#x2F;a&gt;, but they have a lot more free time than I do.)&lt;&#x2F;p&gt;
&lt;p&gt;There are ways that I could have my server run other people&#x27;s code in a &quot;secure&quot; way, but it&#x27;s a lot easier to simply automate their browsers to run their calculations for them.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;first-project-novel-word-calculator&quot;&gt;First Project: Novel Word Calculator&lt;&#x2F;h1&gt;
&lt;p&gt;For &lt;a href=&quot;http:&#x2F;&#x2F;nanowrimo.org&#x2F;&quot;&gt;National Novel Writing Month&lt;&#x2F;a&gt;, crazy people like myself attempt to write 50,000 words in 30 days. We also write a lot of other words, on forums and IRC, to encourage one another and sometimes procrastinate on the 50,000. The NaNoWriMo site lets you track your word count over time, but I had some extra calculations that I found motivational and wanted to share.&lt;&#x2F;p&gt;
&lt;p&gt;Novelists have some overlap with other types of nerd, but on the whole, I couldn&#x27;t just hand out my custom &lt;a href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;edunham&#x2F;a5ff33070b359fcebc1c&quot;&gt;python script&lt;&#x2F;a&gt; and say &quot;run it!&quot; unless I wanted to become tech support for hundreds of novices trying to install Python on Mac and Windows.&lt;&#x2F;p&gt;
&lt;p&gt;So, to share it with the world (and procrastinate on that 50,000-word deadline looming in the distance), I ported it to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;toys&#x2F;blob&#x2F;master&#x2F;nano&#x2F;calc.js&quot;&gt;javascript&lt;&#x2F;a&gt; and put it &lt;a href=&quot;http:&#x2F;&#x2F;nano.edunham.net&#x2F;&quot;&gt;in a subdomain of my site&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;second-project-github-stalking-tool&quot;&gt;Second Project: GitHub Stalking Tool&lt;&#x2F;h1&gt;
&lt;p&gt;I wrote a cute but useless little &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;02&#x2F;04&#x2F;please_license_your_code.html&quot;&gt;article&lt;&#x2F;a&gt; preaching to the choir about how licenses are cool, and then wanted an easy way to check whether I was following my own advice.&lt;&#x2F;p&gt;
&lt;p&gt;&quot;GitHub has an API, right? JS can do stuff like make API calls, right? This shouldn&#x27;t be too hard...&quot;&lt;&#x2F;p&gt;
&lt;p&gt;One week and many hours of confusion later, I have a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;pleaselicense&quot;&gt;piece of code&lt;&#x2F;a&gt; that works for the originally intended purpose. Learning experiences here have been that CSS is still hard, JS still tends to defy one&#x27;s expectations, the API has a 60 request per hour rate limit unauthenticated, and One Does Not Simply OAuth. Or more accurately, OAuth requires a server-side program with an application secret, and everything gets exponentially more complicated from there.&lt;&#x2F;p&gt;
&lt;p&gt;Next time I feel like experiencing many hours of frustration, I&#x27;ll work through the process of adding OAuth (so that you can log into github and click a button to automatically add licenses to unlicensed repos, all through my site), then write up a tutorial. For now, though, &lt;a href=&quot;http:&#x2F;&#x2F;licensecheck.edunham.net&#x2F;&quot;&gt;it works okay&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Interview on opensource.com</title>
        <published>2015-02-12T00:00:00+00:00</published>
        <updated>2015-02-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/12/interview_on_opensource_com/"/>
        <id>https://edunham.net/2015/02/12/interview_on_opensource_com/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/12/interview_on_opensource_com/">&lt;p&gt;As part of my upcoming &lt;a href=&quot;https:&#x2F;&#x2F;www.socallinuxexpo.org&#x2F;scale&#x2F;13x&#x2F;presentations&#x2F;human-hacking&quot;&gt;talk at SCALE&lt;&#x2F;a&gt;, I was &lt;a href=&quot;http:&#x2F;&#x2F;opensource.com&#x2F;life&#x2F;15&#x2F;2&#x2F;interview-emily-dunham-student-systems-engineer-OSU-open-source-lab&quot;&gt;interviewed on opensource.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Nitish Tiwari interviewed me by email about my talk, then edited my comments into what I feel like ended up as a very nice publication. I appreciate the CC-BY-SA license under which he released the article.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Installing gavrasm on Arch</title>
        <published>2015-02-11T00:00:00+00:00</published>
        <updated>2015-02-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/11/gavrasm/"/>
        <id>https://edunham.net/2015/02/11/gavrasm/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/11/gavrasm/">&lt;p&gt;Avra, the AVR assembler that I have been using for my ECE375 assignments, started throwing cryptic error messages such as &quot;&lt;code&gt;PRAGMA directives currently ignored&lt;&#x2F;code&gt;&quot; from an include file which had previously been working fine.&lt;&#x2F;p&gt;
&lt;p&gt;In order to sanity check whether the problem is in my code or the compiler, I installed &lt;code&gt;gavrasm&lt;&#x2F;code&gt;, the assembler which the &lt;a href=&quot;http:&#x2F;&#x2F;web.engr.oregonstate.edu&#x2F;~johnstay&#x2F;ece375&#x2F;&quot;&gt;ECE375 lab website&lt;&#x2F;a&gt; recommends for Mac users.&lt;&#x2F;p&gt;
&lt;p&gt;I wrote this up because I couldn&#x27;t find a simple, no-extra-thinking-required walkthrough of how to build and install &lt;code&gt;gavrasm&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;broken-pkgbuild&quot;&gt;Broken pkgbuild&lt;&#x2F;h1&gt;
&lt;p&gt;gavrasm is available in the AUR, but its pkgbuild is broken:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ yaourt -S gavrasm
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; Downloading gavrasm PKGBUILD from AUR...
&lt;&#x2F;span&gt;&lt;span&gt;x PKGBUILD
&lt;&#x2F;span&gt;&lt;span&gt;Comment by fatmike  (2011-09-30 09:26)
&lt;&#x2F;span&gt;&lt;span&gt;Updated and moved fpc to makedepends.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;gavrasm 3.3-1  (Mon Jul 25 01:43:11 PDT 2011)
&lt;&#x2F;span&gt;&lt;span&gt;( Unsupported package: Potentially dangerous ! )
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; gavrasm dependencies:
&lt;&#x2F;span&gt;&lt;span&gt; - fpc (already installed)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; Continue building gavrasm ? [Y&#x2F;n]
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; ---------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; Building and installing package
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; ERROR: Missing package() function in
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;tmp&#x2F;yaourt-tmp-username&#x2F;aur-gavrasm&#x2F;.&#x2F;PKGBUILD
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; ERROR: Makepkg was unable to build gavrasm.
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; Restart building gavrasm ? [y&#x2F;N]
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; --------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;installing-from-source&quot;&gt;Installing from Source&lt;&#x2F;h1&gt;
&lt;p&gt;Install fpc, the Free Pascal Compiler:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ yaourt -S fpc
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Download and unzip the &lt;a href=&quot;http:&#x2F;&#x2F;www.avr-asm-tutorial.net&#x2F;gavrasm&#x2F;v34&#x2F;gavrasm_sources_lin_34.zip&quot;&gt;source&lt;&#x2F;a&gt;. Files will end up in a directory with a name like &lt;code&gt;Sourcefiles_v3_4&lt;&#x2F;code&gt;, rather than a sensible name like &lt;code&gt;gavrasm&lt;&#x2F;code&gt;, by default.&lt;&#x2F;p&gt;
&lt;p&gt;Copy the language file to its correct location. If you want the English version, this is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cp gavrlang_en.pas gavrlang.pas
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you forget this step, you&#x27;ll hit an error when compiling:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;gavrline.pas(9,28) Fatal: Can&amp;#39;t find unit gavrlang used by gavrline
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once you&#x27;ve copied the language file, you&#x27;re ready to compile:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ fpc gavrasm.pas
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In order to invoke gavrasm from the command line, copy the resulting executable into a directory on your &lt;code&gt;$PATH&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo cp gavrasm &#x2F;usr&#x2F;bin&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s it! You can now compile assembly with &lt;code&gt;$ gavrasm myfile.asm&lt;&#x2F;code&gt; anywhere on your system!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Mailman and Multiple Addresses</title>
        <published>2015-02-10T00:00:00+00:00</published>
        <updated>2015-02-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/10/mailman_and_multiple_addresses/"/>
        <id>https://edunham.net/2015/02/10/mailman_and_multiple_addresses/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/10/mailman_and_multiple_addresses/">&lt;p&gt;&quot;Your message to the list requires moderator approval&quot;, replies Mailman when you try to post to your mailing list.&lt;&#x2F;p&gt;
&lt;p&gt;&quot;But I&#x27;m &lt;em&gt;on&lt;&#x2F;em&gt; the list!&quot; you complain, then waste a few minutes of your day finding the administrative password and releasing your message.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re like me, you probably have several email addresses all forwarding into a single inbox.&lt;&#x2F;p&gt;
&lt;p&gt;When a Mailman list is set to permit only members to post to it, your message will only get through if it happens to be sent from the address with which you&#x27;re subscribed. Gmail, however, isn&#x27;t quite smart enough to default to sending to a list from the same address at which you usually recieve that list&#x27;s mail.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s an easy workaround if you&#x27;re a list administrator, which is only slightly less convenient if you don&#x27;t have admin permissions. The fix is to subscribe the address from which you prefer to send mail to the list, then change its subscription settings so that it&#x27;s never sent any mail (otherwise you&#x27;d end up with multiple copies of each message!).&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re a list administrator, it takes 4 steps to add all of your alternate addresses. If you&#x27;re not using admin privileges, it takes 3 steps for each alternate address you want to subscribe.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;list-administrator-technique&quot;&gt;List Administrator Technique&lt;&#x2F;h1&gt;
&lt;p&gt;1) Navigate to the list&#x27;s administrative interface and log in. If the list is &lt;code&gt;listname@domain.tld&lt;&#x2F;code&gt;, the Mailman interface probably lives at &lt;code&gt;lists.domain.tld&#x2F;mailman&#x2F;admin&#x2F;listname&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;2) Go to the &quot;Mass Subscription&quot; tab under &quot;Membership Management&quot; in the Configuration Categories menu. Or just build the URL of the form &lt;code&gt;lists.domain.tld&#x2F;mailman&#x2F;admin&#x2F;listname&#x2F;members&#x2F;add&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;3) Set the radio buttons to choose Subscribe, No welcome message, and No notification of new subscription (to avoid spamming other list owners). Then enter the other addresses that you&#x27;d like to send mail from, one per line, in the box. Submit the changes.&lt;&#x2F;p&gt;
&lt;p&gt;4) Now navigate to the &quot;Membership List&quot; link, or build the URL &lt;code&gt;lists.domain.tld&#x2F;mailman&#x2F;admin&#x2F;listname&#x2F;members&#x2F;list&lt;&#x2F;code&gt;. Find each of your alternate addresses, and check the box in the &quot;nomail [reason]&quot; column on their rows. Remember to leave one of your addresses&#x27; nomail box un-checked, or you won&#x27;t get any mail from the list at all! Use the Submit your Changes button to save the configuration.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;non-admin-technique&quot;&gt;Non-Admin Technique&lt;&#x2F;h1&gt;
&lt;p&gt;1) On the list&#x27;s info page (&lt;code&gt;lists.domain.tld&#x2F;mailman&#x2F;listinfo&#x2F;listname&lt;&#x2F;code&gt;), subscribe your alternate address to the list. (There&#x27;s also a link in the footer of every email sent to the list.)&lt;&#x2F;p&gt;
&lt;p&gt;2) On the list info page, enter your alternate address in the &quot;Unsubscribe or Edit Options&quot; box down at the bottom, then enter your list password when prompted.&lt;&#x2F;p&gt;
&lt;p&gt;3) Scroll down to the &quot;Mail delivery&quot; setting, and switch the radio button to Disabled. If you choose &quot;Set globally&quot;, delivery to your alternate address will be disabled for every subscription that you have in that Mailman instance.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Automating Arch, I3, and Terminator to do the right thing at startup</title>
        <published>2015-02-05T00:00:00+00:00</published>
        <updated>2015-02-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/05/making_arch_do_the_right_thing_at_startup/"/>
        <id>https://edunham.net/2015/02/05/making_arch_do_the_right_thing_at_startup/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/05/making_arch_do_the_right_thing_at_startup/">&lt;p&gt;I&#x27;m currently on my second clean install of Arch Linux. On the whole I&#x27;ve been glad that I ditched most of my old configuration when I installed Arch to the new SSD I got for my laptop at the beginning of the school year. However, I&#x27;d been procrastinating on re-implementing a few things which worked last install without me really knowing which of my many attempts had fixed them. This time, I know what I&#x27;m doing and what questions to ask.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;desired-behaviors&quot;&gt;Desired Behaviors&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;Start ssh-agent automatically at login&lt;&#x2F;li&gt;
&lt;li&gt;Start X automatically at login&lt;&#x2F;li&gt;
&lt;li&gt;Automatically connect to wifi if I&#x27;m at home&lt;&#x2F;li&gt;
&lt;li&gt;Open a terminal in workspace 1 and Firefox in workspace 2&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;preemptive-troubleshooting&quot;&gt;Preemptive Troubleshooting&lt;&#x2F;h1&gt;
&lt;p&gt;Certain types of error when you&#x27;re testing commands to auto-run at login will immediately log you out again, with no error message displayed. The easy way to fix these problems is to have at least one other user account on your system with sufficient permissions to edit your account&#x27;s startup scripts. That way if you end up in such a failure loop, you can just login as them, alter the offending line, and immediately test logging in as yourself again.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re testing automatic behaviors at login for the only account on your system and the root account doesn&#x27;t have its own password and things break, the best ways I&#x27;ve found to get a bad line out of your config files are to either reboot into a recovery mode or boot any old distro off of a USB drive if you happen to have an old install disk sitting around.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;starting-programs-at-login&quot;&gt;Starting programs at login&lt;&#x2F;h1&gt;
&lt;p&gt;The wonderful Arch Wiki has an entire page on how to &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Start_X_at_login&quot;&gt;start X at login&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In summary, just add &lt;code&gt;exec startx&lt;&#x2F;code&gt; to the end of &lt;code&gt;~&#x2F;.bash_profile&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;ssh-agent&lt;&#x2F;code&gt; is a special snowflake and causes things to fail silently if you try to invoke it along the lines of &lt;code&gt;exec ssh-agent &amp;amp;&amp;amp; exec startx&lt;&#x2F;code&gt;. However, the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;SSH_keys#ssh-agent_as_a_wrapper_program&quot;&gt;arch wiki&lt;&#x2F;a&gt; comes to the rescue again, citing a &lt;a href=&quot;http:&#x2F;&#x2F;upc.lbl.gov&#x2F;docs&#x2F;user&#x2F;sshagent.shtml&quot;&gt;ssh-agent tutorial&lt;&#x2F;a&gt; that explains you can run &lt;code&gt;ssh-agent&lt;&#x2F;code&gt; as a wrapper:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;exec ssh-agent startx
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(which of course goes as the last line of your &lt;code&gt;~&#x2F;.bash_profile&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;automatically-connecting-to-one-wifi-network&quot;&gt;Automatically connecting to one wifi network&lt;&#x2F;h1&gt;
&lt;p&gt;The Arch Wiki&#x27;s page on &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Netctl#Basic_method&quot;&gt;netctl&lt;&#x2F;a&gt; has the relevant line buried, as usual, in a huge wall of otherwise-extraneous information.&lt;&#x2F;p&gt;
&lt;p&gt;Note that I&#x27;ve been manually connecting to networks with &lt;code&gt;wifi-menu&lt;&#x2F;code&gt; before, so I already have profiles with saved passwords for the networks to which I wish to autoconnect.&lt;&#x2F;p&gt;
&lt;p&gt;If the SSID is &lt;code&gt;My Network&lt;&#x2F;code&gt;, there&#x27;s a file in &lt;code&gt;&#x2F;etc&#x2F;netctl&#x2F;&lt;&#x2F;code&gt; with a name something like &lt;code&gt;wlp3s0-My Network&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To make it autoconnect, just give the command:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sudo netctl enable wlp3s0-My\ Network
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that if the profile ever changes, such as for a change in password, you&#x27;ll have to &lt;code&gt;netctl reenable&lt;&#x2F;code&gt; it in order to automatically connect.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;automatically-connecting-to-the-right-network-out-of-several&quot;&gt;Automatically connecting to the right network out of several&lt;&#x2F;h1&gt;
&lt;p&gt;The &lt;code&gt;netctl enable&lt;&#x2F;code&gt; stuff works great if the first enabled network is in range when the system boots, but fails and doesn&#x27;t try others if it isn&#x27;t. At this point I&#x27;m not sure whether it&#x27;s supposed to be possible to autoconnect to multiple networks with netctl at all.&lt;&#x2F;p&gt;
&lt;p&gt;When I asked some other Arch users how to get netctl to do the multiple networks thing, they reported having used network-manager instead:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;yaourt -S networkmanager
&lt;&#x2F;span&gt;&lt;span&gt;yaourt -S network-manager-applet
&lt;&#x2F;span&gt;&lt;span&gt;yaourt -S gnome-keyring
&lt;&#x2F;span&gt;&lt;span&gt;sudo systemctl enable NetworkManager.service
&lt;&#x2F;span&gt;&lt;span&gt;sudo systemctl start NetworkManager.service
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;NetworkManager#Enable_NetworkManager&quot;&gt;wiki page&lt;&#x2F;a&gt; on networkmanager explains how to set up permissions so users without root can add networks, but that step solves a nonexistant problem for me since I&#x27;m the only person who should be changing network configuration on my laptop.&lt;&#x2F;p&gt;
&lt;p&gt;Whenever I need to add a new network, I start &lt;code&gt;nm-applet&lt;&#x2F;code&gt; through dmenu and add the network. When I&#x27;m at home or school, the networks I&#x27;ve already added can autoconnect and I don&#x27;t have to have the &lt;code&gt;nm-applet&lt;&#x2F;code&gt; icon cluttering up my toolbar.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;automatically-starting-stuff-in-terminator-and-i3&quot;&gt;Automatically Starting Stuff in Terminator and I3&lt;&#x2F;h1&gt;
&lt;p&gt;The stuff to start is a terminal with the command to ssh to my VPS and reconnect to my IRC screen, and Firefox.&lt;&#x2F;p&gt;
&lt;p&gt;Since you can&#x27;t start terminator with a bash alias in the &lt;code&gt;--command&lt;&#x2F;code&gt; argument, and my irc alias is long and contains quotes itself, I&#x27;ve created a custom Terminator profile for launching irc. Either my Google-fu is weak or you can&#x27;t have Terminator profiles inherit from one another, so I added a new &lt;code&gt;[[irc]]&lt;&#x2F;code&gt; profile to my &lt;code&gt;.config&#x2F;terminator&#x2F;config&lt;&#x2F;code&gt; file, with copies of the settings I care about from my default profile:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[[irc]]                                                                       
&lt;&#x2F;span&gt;&lt;span&gt;  scrollbar_position = hidden                                                 
&lt;&#x2F;span&gt;&lt;span&gt;  use_system_font = False                                                     
&lt;&#x2F;span&gt;&lt;span&gt;  cursor_shape = ibeam                                                        
&lt;&#x2F;span&gt;&lt;span&gt;  background_image = None                                                     
&lt;&#x2F;span&gt;&lt;span&gt;  show_titlebar = False                                                       
&lt;&#x2F;span&gt;&lt;span&gt;  color_scheme = green_on_black                                               
&lt;&#x2F;span&gt;&lt;span&gt;  font = Mono 10.5                                                            
&lt;&#x2F;span&gt;&lt;span&gt;  use_custom_command = True                                                   
&lt;&#x2F;span&gt;&lt;span&gt;  exit_action = hold                                                          
&lt;&#x2F;span&gt;&lt;span&gt;  custom_command = ssh -t -p XXXX me@myvps.net &amp;quot;screen -dr irc&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the &lt;code&gt;-t&lt;&#x2F;code&gt; flag to SSH forces it forces it to allocate a psuedo-TTY, preventing the &lt;code&gt;Must be connected to a terminal&lt;&#x2F;code&gt; error which would otherwise occur.&lt;&#x2F;p&gt;
&lt;p&gt;Adding the following lines to the start of &lt;code&gt;~&#x2F;.i3&#x2F;config&lt;&#x2F;code&gt; causes the desired behavior when i3 is started:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;exec --no-startup-id i3-msg &amp;#39;workspace 2; exec firefox&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;exec --no-startup-id i3-msg &amp;#39;workspace 1; exec terminator -p &amp;quot;irc&amp;quot;&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that I exec firefox first and terminator second, so that the terminal which needs my VPS password to be entered in order to reconnect to irc is in the active workspace immediately after startup.&lt;&#x2F;p&gt;
&lt;p&gt;As an added bonus, digging these commands out of my old config reminded me how to automatically set a background image: Just add the following to &lt;code&gt;~&#x2F;.13&#x2F;config&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;exec feh --bg-scale ~&#x2F;background.jpg
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;making-terminator-pick-the-right-font-size&quot;&gt;Making Terminator pick the right font size&lt;&#x2F;h1&gt;
&lt;p&gt;The default font size that Terminator has been using causes my screen to be a little under 160 characters wide. This causes the 80-character lines on which I standardize my writing to wrap annoyingly, and means I have to manually zoom out by one &lt;code&gt;ctrl-minus&lt;&#x2F;code&gt; in each pane when I split my terminal.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is two lines in &lt;code&gt;~&#x2F;.config&#x2F;terminator&#x2F;config&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[profiles]                                                                      
&lt;&#x2F;span&gt;&lt;span&gt;  [[default]]                                                                   
&lt;&#x2F;span&gt;&lt;span&gt;    ...        
&lt;&#x2F;span&gt;&lt;span&gt;    use_system_font = False                                                     
&lt;&#x2F;span&gt;&lt;span&gt;    font = Mono 10.5
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You have to manually override the default &lt;code&gt;use_system_font=True&lt;&#x2F;code&gt; setting for any font changes in your terminator config to apply. After hearing &lt;a href=&quot;https:&#x2F;&#x2F;jargonsummary.wordpress.com&#x2F;2010&#x2F;12&#x2F;25&#x2F;changing-font-size-and-family-of-terminator&#x2F;&quot;&gt;good things&lt;&#x2F;a&gt; about the font Inconsolata, I gave it a try, but found that it looks unpleasantly blurry at the small sizes that I prefer to use. I&#x27;m sure there&#x27;s a setting somewhere to fix that, but my needs are met equally well by switching back to the Mono font as they would be by shaving the font display yak.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-results&quot;&gt;The Results&lt;&#x2F;h2&gt;
&lt;p&gt;Based on IRC timestamps, it now takes me roughly 13 seconds and [ctrl+shift+e] + [laptop username] + [laptop password] + [vps password] of typing to kill X, log in, automatically connect to available wifi, and reconnect to IRC. This is an improvement over my previous process.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Please License Your Code</title>
        <published>2015-02-04T00:00:00+00:00</published>
        <updated>2015-02-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/04/please_license_your_code/"/>
        <id>https://edunham.net/2015/02/04/please_license_your_code/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/04/please_license_your_code/">&lt;p&gt;Code without a license isn&#x27;t &lt;a href=&quot;http:&#x2F;&#x2F;opensource.org&#x2F;osd&quot;&gt;open source&lt;&#x2F;a&gt;. It isn&#x27;t &lt;a href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;philosophy&#x2F;free-sw.html&quot;&gt;free software&lt;&#x2F;a&gt;, either.&lt;&#x2F;p&gt;
&lt;p&gt;Posting your code publicly doesn&#x27;t inherently apply a license to it.&lt;&#x2F;p&gt;
&lt;p&gt;Adding a license really isn&#x27;t hard -- it&#x27;s just a label that clarifies the owner&#x27;s intent to share it.&lt;&#x2F;p&gt;
&lt;p&gt;Imagine you come into the school cafeteria and there&#x27;s a plate of cookies just sitting on one of the tables. No sign on it, no indication of what they&#x27;re for, but they&#x27;re out there in public. Is it ok to take one?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;cookies-license-analogy.jpg&quot; alt=&quot;Plate of cookies as analogy for unlicensed code&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If you know the person who brought them, you can just ask if you can have one, and they&#x27;ll probably say yes. If some stranger brought them, then maybe they&#x27;re to share -- you can, and many people would, certainly take your chances and have one anyway.&lt;&#x2F;p&gt;
&lt;p&gt;But what if they were put there by someone with malicious intentions and access to unpleasant chemicals, or even just baked by someone well-meaning but with extremely bad kitchen hygiene?&lt;&#x2F;p&gt;
&lt;p&gt;Or maybe the art club has been making realistic cardboard cookies for an upcoming play, or the veterinary students decided to decorate some dog biscuits all festively, or... you get the idea.&lt;&#x2F;p&gt;
&lt;p&gt;You can, and many people would, just take your chances and have a cookie. Most of the time, they&#x27;re as wholesome and delicious as they look and were put there by a nice person with a clean kitchen who intended to put a sign on them but was just busy and forgot to, or assumed that everyone would know. But every once in a while, that isn&#x27;t the case, and then the aftermath involves some awkward conversations or serious discomfort or even lawyers.&lt;&#x2F;p&gt;
&lt;p&gt;Applying an appropriate license to your open source code is effectively just putting a label by that plate of tasty solutions you&#x27;ve brought to the internet, saying who they&#x27;re for.&lt;&#x2F;p&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;I am not a lawyer. I am not your lawyer. Please do not try to eat software, regardless of how it&#x27;s licensed.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>20 HabitRPG levels in 5 days</title>
        <published>2015-02-02T00:00:00+00:00</published>
        <updated>2015-02-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/02/20_habitrpg_levels_in_5_days/"/>
        <id>https://edunham.net/2015/02/02/20_habitrpg_levels_in_5_days/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/02/20_habitrpg_levels_in_5_days/">&lt;p&gt;I&#x27;ve been having &lt;a href=&quot;http:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;02&#x2F;01&#x2F;habitrpg.html&quot;&gt;fun with HabitRPG&lt;&#x2F;a&gt; lately, and discovered this One Neat Trick:&lt;&#x2F;p&gt;
&lt;p&gt;Checklists.&lt;&#x2F;p&gt;
&lt;p&gt;Checklists, at least the way I&#x27;ve been using them, are obscenely overpowered. I&#x27;ll have a medium-difficulty to-do, break it down into a dozen sub-tasks (because that&#x27;s what I usually do on to-do lists when I&#x27;m procrastinating), get them all checked off, and then reap a level and a half worth of &lt;a href=&quot;http:&#x2F;&#x2F;habitrpg.wikia.com&#x2F;wiki&#x2F;Experience_Points&quot;&gt;XP&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;According to that wiki page,&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Experience gain is determined by an algorithm from HabitRPG, which tracks how consistently a player completes a given task.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Cute theory. The thing is, I&#x27;ve never gotten more than maybe 20 XP from a habit or daily (the only types of tasks for which consistency makes sense as a metric), but I regularly get over 100 XP for the completion of a sufficiently detailed checklist.&lt;&#x2F;p&gt;
&lt;p&gt;Annoyingly, the wiki doesn&#x27;t point out where the relevant algorithm lives in the code. I&#x27;ve asked on IRC about how to find it, but for now, just try this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Make a to-do of a task that you&#x27;ll have to do today.&lt;&#x2F;li&gt;
&lt;li&gt;Add a checklist to the task, breaking it down into 8 or more necessary sub-items.&lt;&#x2F;li&gt;
&lt;li&gt;Accomplish your task, check each item, then check the main checkbox beside its title.&lt;&#x2F;li&gt;
&lt;li&gt;PROFIT!!&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This is also really broken in the amount of damage it does against bosses. I was cleaning my room yesterday with the checklist method and ticked maybe 20 HabitRPG checkboxes (emptied and discarded about 4 largeish cardboard boxes and 2 garbage bags of stuff I no longer need, so I don&#x27;t think I&#x27;m unfairly inflating points here), and look at this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;132dmg.png&quot; alt=&quot;132 damage to boss&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s a lot of damage to the boss. The calculator I used was &lt;a href=&quot;https:&#x2F;&#x2F;oldgods.net&#x2F;habitrpg&#x2F;habitrpg_user_data_display.html&quot;&gt;this awesome site&lt;&#x2F;a&gt;, in case you haven&#x27;t discovered it yet.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m a rogue, with all my points in INT as per the &lt;a href=&quot;http:&#x2F;&#x2F;habitrpg.wikia.com&#x2F;wiki&#x2F;Merfy%27s_Rogue_guide&quot;&gt;guide&lt;&#x2F;a&gt; on the subject! I&#x27;m not supposed to be able to hit things very hard! Sure, I&#x27;m level 25 right now with a &lt;a href=&quot;http:&#x2F;&#x2F;habitrpg.wikia.com&#x2F;wiki&#x2F;The_Perfect_Day&quot;&gt;perfect day&lt;&#x2F;a&gt; bonus from yesterday, but my stats still aren&#x27;t too great:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;str46.png&quot; alt=&quot;Stats with STR showing&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;&#x2F;strong&gt;: I scored a bunch of points in a silly game and conclucluded that it must be broken.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Gamifying Adulthood with HabitRPG</title>
        <published>2015-02-01T00:00:00+00:00</published>
        <updated>2015-02-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/01/habitrpg/"/>
        <id>https://edunham.net/2015/02/01/habitrpg/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/01/habitrpg/">&lt;p&gt;After creating an account and abandoning it some time before December 2013 (since I have never yet subscribed and yet had a &lt;a href=&quot;http:&#x2F;&#x2F;habitrpg.wikia.com&#x2F;wiki&#x2F;Trapper_Santa&quot;&gt;Trapper Santa&lt;&#x2F;a&gt; scroll in my inventory), I have returned to &lt;a href=&quot;http:&#x2F;&#x2F;habitrpg.com&#x2F;&quot;&gt;HabitRPG&lt;&#x2F;a&gt;. Here&#x27;s a quick examination of why I think I left and then came back.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-leave&quot;&gt;Why Leave?&lt;&#x2F;h1&gt;
&lt;p&gt;The short answer is that I never really &quot;got into it&quot;. I didn&#x27;t have a smartphone when I first created an account in the web UI, and it seemed at the time like using the app would be prerequisite to successful gameplay.&lt;&#x2F;p&gt;
&lt;p&gt;When I first got my phone, I installed the app, but I hadn&#x27;t touched the web UI in so long that I&#x27;d lost track of &lt;em&gt;why&lt;&#x2F;em&gt; the game was fun or rewarding. The app&#x27;s not super pretty, so when I wasn&#x27;t motivated to touch it by an understanding of the bigger-picture gameplay, I didn&#x27;t choose to.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, I started playing with the app at a time when I more or less already had my life under control. I didn&#x27;t have any recurringly time-wasting habits cutting into productive work that I was late on, my daily schedule worked out quite well with room for everything I wanted to get done, and so forth. Such is the beauty of &quot;grownup&quot; life, with a 9-5 job and not much else going on. At that point, Ingress was my &quot;I need to touch my phone a lot&quot; game of choice, and satisfied my vague desire for quantified&#x2F;gamified reality.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-return&quot;&gt;Why return?&lt;&#x2F;h1&gt;
&lt;p&gt;The game was brought to my attention in passing when I saw a friend playing it a couple months ago, and its usefulness clicked into place for me when I observed how much time I was spending on another online game called &lt;a href=&quot;http:&#x2F;&#x2F;www.dominusgame.net&#x2F;&quot;&gt;dominus&lt;&#x2F;a&gt;. It&#x27;s an open-source real time strategy type game, and the realtime aspect of gameplay (as opposed to the immsersive, lose-hours-on-end nature of my other bad habits like Minecraft) made me realize how easily and unobtrusively I can fit a 5-minutes-at-a-time game into my workflow.&lt;&#x2F;p&gt;
&lt;p&gt;Now, if only I could turn those little brain breaks into something useful. That&#x27;s where HabitRPG fits in.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-it-is&quot;&gt;What It Is:&lt;&#x2F;h1&gt;
&lt;p&gt;You should really go do the tutorial and read &lt;a href=&quot;http:&#x2F;&#x2F;habitrpg.wikia.com&#x2F;&quot;&gt;the wiki&lt;&#x2F;a&gt; for any kind of a comprehensive overview. In a hideously undersized nutshell, HabitRPG is a game where you get items, gold, XP, and mana points by completeing tasks. Tasks give you rewards (or harm you when incomplete) differently based on their type:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Habits&lt;&#x2F;em&gt; are behaviors that you want to get more consistent at. They can either be positive, negative, or have both positive and negative buttons. Consistently plus-ing a habit turns it slowly green then blue; consistently minus-ing it turns it orange then red. Habits stay in your habit list until you delete them, no matter how often they&#x27;re clicked.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Positive habits are &quot;good&quot; things and have only a &lt;code&gt;+&lt;&#x2F;code&gt; button, which you click when you do them. For example, &quot;eat a vegetable&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;Negative habits are things you&#x27;re trying to avoid doing, and have only a &lt;code&gt;-&lt;&#x2F;code&gt; button. For example, &quot;drinking soda&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;Some habits have both &lt;code&gt;+&lt;&#x2F;code&gt; and &lt;code&gt;-&lt;&#x2F;code&gt; buttons. For example, you might put both buttons on &quot;take the stairs instead of the elevator&quot;, and click on the &lt;code&gt;+&lt;&#x2F;code&gt; each time you take the stairs and the &lt;code&gt;-&lt;&#x2F;code&gt; each time you take the lift.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Dailies&lt;&#x2F;em&gt; are things that you want to do once every day, or on certain days of the week. Various game mechanics hurt you when you fail to complete all your daily tasks, or give you advantages when you complete all of them (called getting a &lt;em&gt;perfect day&lt;&#x2F;em&gt;). Dailies stick around until you delete them, and count the streak of how many times in a row you&#x27;ve gotten them.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;To-dos&lt;&#x2F;em&gt; are how you quantify all those one-off things that need to get done. For instance, &quot;Attend CS class&quot; might be a daily for Monday, Wednesday, and Friday, but &quot;Do CS class assignment 1&quot; would be a to-do.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;what-it-s-not&quot;&gt;What It&#x27;s Not&lt;&#x2F;h1&gt;
&lt;p&gt;HabitRPG is not a replacement for Google Calendar. Although recurring &quot;daily&quot; tasks can be set for certain days of the week, its deadlines feature just doesn&#x27;t get in your face the way a calendar email and notification will.&lt;&#x2F;p&gt;
&lt;p&gt;Although I&#x27;ll discuss the ways that its game mechanics reduce the likelihood of &quot;do it eventually&quot; tasks getting totally forgotten, I don&#x27;t find HabitRPG to match Google Calendar&#x27;s ability to make me do an important, time-sensitive, one-off task like attending an appointment.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s also not your mom. It can&#x27;t tell if you&#x27;re lying to it, and makes no effort to detect or punish &quot;cheating&quot;. While this game is a great way to channel a little bit of self-discipline into a lot of results, it cannot magically generate discipline and motivation for you if you start out with zero.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;personal-accountability-through-positive-tasks&quot;&gt;Personal accountability through positive tasks&lt;&#x2F;h1&gt;
&lt;p&gt;Part of the fun of this game is the challenge it presents in terms of learning to hack your own brain. One thing I&#x27;ve noticed is that I have a much easier time being honest on positive tasks than negative ones. For instance, a habit of &quot;make a public commit on GitHub&quot; is easy for me to recognize when I&#x27;ve fulfilled it, and it keeps the experience of using HabitRPG fun -- I&#x27;m motivated to open the site and record the action I took, because a good thing (XP and gold) will happen when I do.&lt;&#x2F;p&gt;
&lt;p&gt;Conversely, negative tasks are harder to be accountable for because I know that nothing good will happen if I bother to go so far out of my way as to open a HabitRPG tab and wait for it to load, and maybe if I just go do something else I&#x27;ll accidentally forget to record it, and... you get the idea.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;good-recurrence-model&quot;&gt;Good recurrence model&lt;&#x2F;h1&gt;
&lt;p&gt;Daily tasks fill a niche that Google Calendar and paper checklists can&#x27;t handle as well. There are many tasks that need to be done &lt;em&gt;sometime&lt;&#x2F;em&gt; every day, but it doesn&#x27;t matter exactly when. It&#x27;s hard to add something to a calendar without picking a particular time slot in advance (then easy to put it off till a different day if that time slot gets taken by something higher-priority). Paper to-do lists are fine for one-off tasks without a set time, but remembering to check a list every day (motivated only by &lt;em&gt;fear of failure&lt;&#x2F;em&gt;, rather than &lt;em&gt;hope for reward&lt;&#x2F;em&gt;) requires more willpower than I usually have to spare.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;partying&quot;&gt;Partying&lt;&#x2F;h1&gt;
&lt;p&gt;Getting together with friends to form a party and fight monsters is delightfully entertaining, and provides an element of reality to the game. Now, if I skip a daily task, it&#x27;s not only hurting me but it&#x27;s harming the cute little avatars of people I care about as well.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s also an incentive against cheating, since it&#x27;ll be obvious to everyone you&#x27;re with if you level too fast. (Though &quot;too fast&quot; is quite relative -- a min&#x2F;maxed Rogue between levels 10 and 20, in the hands of a checklist fanatic, can gain 2 or 3 levels on a productive day.)&lt;&#x2F;p&gt;
&lt;h1 id=&quot;complexity&quot;&gt;Complexity&lt;&#x2F;h1&gt;
&lt;p&gt;One trait of Minecraft which addicted me to the game was its complexity -- a player who&#x27;s read the entire wiki and memorized a lot of recipes and facts about the game can do interesting tricks which amaze less educated players, and enjoy teaching their friends. I feel like so far, the subtleties of HabitRPG&#x27;s game mechanics are hitting a similar sweet spot between being easy to start and difficult to master.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;moderation&quot;&gt;Moderation&lt;&#x2F;h1&gt;
&lt;p&gt;Sometimes I&#x27;ll get going on a project or task which makes me feel productive and then repeat it, when another less-satisfying task is more important to do at that particular time. Habits in HabitRPG fight this tendency by implementing a rule of diminishing returns: The more often you&#x27;ve done a particular habit, the less gold and XP you&#x27;ll get each time you engage in it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;notes-to-self&quot;&gt;Notes-To-Self&lt;&#x2F;h1&gt;
&lt;p&gt;I used to jot down ideas of things to try someday in a notebook; then I went through a phase of jotting them down in a git repository; then upon acquiring a smartphone I had moderate success with Google Keep. The problem with all of those media was that my cool ideas would get lost in them -- at times of boredom, I&#x27;d forget that my lists were out there.&lt;&#x2F;p&gt;
&lt;p&gt;Now, I&#x27;m throwing such ideas onto my to-do list in HabitRPG and they gradually percolate up into being the least onerous challenge available.&lt;&#x2F;p&gt;
&lt;p&gt;Since HabitRPG is a low-guilt site to check when I&#x27;m procrastinating on more useful things, it tend to remind me of my &quot;that might be neat&quot; ideas right when I have free cycles to actually do something about them. At the opposite extreme, Google Tasks puts a little list in my inbox and reminds me of things to do when I&#x27;m trying to compose email, and have the least free time right then.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;emergent-task-weighting&quot;&gt;Emergent Task Weighting&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ve noticed a tendency to throw the same unpleasant task into my to-do queue multiple times. or break it down into increasingly longer checklists in order to procrastinate on actually accomplishing it. In a classic to-do list this would just cause me to experience more guilt and fear about the task, but in HabitRPG, it slowly increases the amount of gold and XP that the task will be worth when I finally get it done and check off all of the associated items.&lt;&#x2F;p&gt;
&lt;p&gt;Although some perfectionists might consider multiple occurrences of a task in the to-do list to be cheating, I find that letting them pile up is a powerful technique for convincing myself to finally get it over with and do the task.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;improvements&quot;&gt;Improvements&lt;&#x2F;h1&gt;
&lt;p&gt;It would be nice if the interface displayed the gold, XP, and mana values for each task&#x27;s completion on the title of the task.&lt;&#x2F;p&gt;
&lt;p&gt;The to-do list has a button for moving an item to the top, but no button for moving an item to the bottom. Since newly created to-do items default to being at the top of the list, it can get tedious trying to keep older and more important items near the surface.&lt;&#x2F;p&gt;
&lt;p&gt;The interface which keeps track of daily tasks defaults to showing all tasks (even those which aren&#x27;t due on the current day), so every time you reload the page you have to click &quot;due&quot; to see only the relevant items. It would be nice if the interface remembered whether you prefer seeing &quot;due&quot; or &quot;all&quot; items.&lt;&#x2F;p&gt;
&lt;p&gt;The app lacks a few features which I find essential to normal gameplay, namely the ability to show only due dailies, and the ability to allocate points into various attributes when you level.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Trouble with Toctrees</title>
        <published>2015-02-01T00:00:00+00:00</published>
        <updated>2015-02-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/02/01/the_trouble_with_toctrees/"/>
        <id>https://edunham.net/2015/02/01/the_trouble_with_toctrees/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/02/01/the_trouble_with_toctrees/">&lt;p&gt;It&#x27;s a couple weeks and nearly a dozen posts into this Tinkerer experiment, I&#x27;m mostly delighted with it. It&#x27;s fulfilling its original promise of &quot;write RST, push button, get pretty blog&quot;... Mostly. There&#x27;s one problem, though. &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;site&#x2F;commit&#x2F;2d9f1115d63c7dde161278da692822d0183c3766&quot;&gt;I&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;site&#x2F;commit&#x2F;e676a58df8d6eff46f9176af089650b583c661c9&quot;&gt;constantly&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;site&#x2F;commit&#x2F;d043a4b666285ad55440969ca5806c17bda71697&quot;&gt;forget&lt;&#x2F;a&gt; to add master.rst when committing.&lt;&#x2F;p&gt;
&lt;p&gt;What happens when you forget to &lt;code&gt;git add master.rst&lt;&#x2F;code&gt; before committing a new post? The post&#x27;s identifier, which got added to the toctree in &lt;code&gt;master.rst&lt;&#x2F;code&gt; when you said &lt;code&gt;tinker --post&lt;&#x2F;code&gt;, doesn&#x27;t end up in the &lt;code&gt;master.rst&lt;&#x2F;code&gt; on the remote server! When you build a sphinx site, only those pages which exist in some toctree somewhere get links to them. This is the correct behavior, for if Sphinx tried to auto-detect new pages, it might not make the right guess about which toctree to include them in.&lt;&#x2F;p&gt;
&lt;p&gt;However, the inconvenient consequence is that &lt;code&gt;master.rst&lt;&#x2F;code&gt; has to be manually added to source control. I didn&#x27;t touch it to create the post, so I forget that I have to touch it to successfully push my changes. I firmly believe that files created by my tools and their sources created by me are fundamentally different -- the former should never live in version control and the latter should (almost) always. &lt;code&gt;master.rst&lt;&#x2F;code&gt;, with its changes coming from a side effect of my manually creating a file, has been in a nasty limbo between the two states.&lt;&#x2F;p&gt;
&lt;p&gt;The solution is a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&#x2F;site&#x2F;blob&#x2F;master&#x2F;build.py&quot;&gt;quick and dirty Python script&lt;&#x2F;a&gt; which regenerates &lt;code&gt;master.rst&lt;&#x2F;code&gt; whenever it&#x27;s called. The call to this script adds a single line to the chain of tools which constitutes my &quot;push button, get website update&quot; command, and will save me many cumulative hours of annoyance and troubleshooting over the lifetime of my blog.&lt;&#x2F;p&gt;
&lt;p&gt;Since automatic sorting would always put my pages into alphabetical order, I&#x27;ve solved the problem before it occurrs by renaming my pages into the form &lt;code&gt;01about.rst&lt;&#x2F;code&gt;, and configuring the script to sort them by casting the first 2 characters of the page name to an int.&lt;&#x2F;p&gt;
&lt;p&gt;For my use case, remembering to preface my few pages&#x27; filenames with the place in the list where they belong is a small price to pay for privilege of not feeling stupid due to forgetting to add an unrelated file every time I push a blog update.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Arbitrary Python versions on Flip</title>
        <published>2015-01-31T00:00:00+00:00</published>
        <updated>2015-01-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/31/arbitrary_python_versions_on_flip/"/>
        <id>https://edunham.net/2015/01/31/arbitrary_python_versions_on_flip/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/31/arbitrary_python_versions_on_flip/">&lt;p&gt;A question in the &lt;code&gt;#osu-lug&lt;&#x2F;code&gt; channel about running python 2.7 on a school server (which only has python 2.6.6) made me realize I should know how to do that but have never tried it. &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;1534210&#x2F;use-different-python-version-with-virtualenv&quot;&gt;Stackoverflow&lt;&#x2F;a&gt; provides instructions for solving a similar problem, so I&#x27;m testing them out to make sure they work for Python 2.7.7 and Python 3.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-setup&quot;&gt;The Setup&lt;&#x2F;h1&gt;
&lt;p&gt;At Oregon State University, students in the engineering department have access to a trio of servers which, for most purposes, are interchangeable. If you&#x27;re running a screen session for IRC, it&#x27;s important to log in to the same flip (&lt;code&gt;flip1.engr.oregonstate.edu&lt;&#x2F;code&gt;, &lt;code&gt;flip2.engr.oregonstate.edu&lt;&#x2F;code&gt;, or &lt;code&gt;flip3.engr.oregonstate.edu&lt;&#x2F;code&gt;) every time because the session exists in that particular server&#x27;s memory. If you only care about stuff stored to disk, you can just ssh to &lt;code&gt;flip.engr.oregonstate.edu&lt;&#x2F;code&gt; to be round-robined onto an available server. Your NFS home directory is shared between all the flip servers, which is why the same files are also available whenever you log in with your engineering account to a Windows, Linux, or Mac computer in the university&#x27;s computer.&lt;&#x2F;p&gt;
&lt;p&gt;All 3 flips are identical; I happen to be on flip1 at the start of this tutorial:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;-bash-4.1$ hostname
&lt;&#x2F;span&gt;&lt;span&gt;flip1.engr.oregonstate.edu
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s running CentOS 6.6 and has Python 2.6.6 and Virtualenv 1.10.1 installed:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ python --version
&lt;&#x2F;span&gt;&lt;span&gt;Python 2.6.6
&lt;&#x2F;span&gt;&lt;span&gt;$ virtualenv --version
&lt;&#x2F;span&gt;&lt;span&gt;1.10.1
&lt;&#x2F;span&gt;&lt;span&gt;$ cat &#x2F;etc&#x2F;redhat-release
&lt;&#x2F;span&gt;&lt;span&gt;CentOS release 6.6 (Final)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;tl;dr:&lt;&#x2F;strong&gt; Just &lt;code&gt;ssh&lt;&#x2F;code&gt; to &lt;code&gt;youronid@flip.engr.oregonstate.edu&lt;&#x2F;code&gt;. You only need to specify flip{1-3} if you&#x27;re doing IRC stuff.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;install-python-locally&quot;&gt;Install Python locally&lt;&#x2F;h1&gt;
&lt;p&gt;On Flip, run the following commands:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;wget http:&#x2F;&#x2F;www.python.org&#x2F;ftp&#x2F;python&#x2F;2.7.7&#x2F;Python-2.7.7.tgz
&lt;&#x2F;span&gt;&lt;span&gt;tar -zxvf Python-2.7.7.tgz
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;# then you wait. it may take a few minutes.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;cd Python-2.7.7
&lt;&#x2F;span&gt;&lt;span&gt;mkdir ~&#x2F;python27
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;# all 3 of the following steps may take awhile.
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;configure --prefix=$HOME&#x2F;python27
&lt;&#x2F;span&gt;&lt;span&gt;make
&lt;&#x2F;span&gt;&lt;span&gt;make install
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;use-python2-7-7&quot;&gt;Use Python2.7.7&lt;&#x2F;h1&gt;
&lt;p&gt;Instead of invoking &lt;code&gt;python&lt;&#x2F;code&gt; and getting the old version from your path, specify which python you want to run your program with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;~&#x2F;python27&#x2F;bin&#x2F;python my_awesome_file.py
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;or-make-a-virtualenv&quot;&gt;Or Make a Virtualenv&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ virtualenv -p ~&#x2F;python27&#x2F;bin&#x2F;python venv27
&lt;&#x2F;span&gt;&lt;span&gt;$ source venv27&#x2F;bin&#x2F;activate
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(venv27)-bash-4.1$ python --version
&lt;&#x2F;span&gt;&lt;span&gt;Python 2.7.7
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;errors&quot;&gt;Errors&lt;&#x2F;h1&gt;
&lt;p&gt;If you get a permission denied error, check that files actually ended up in your ~&#x2F;python27 directory. If the directory is empty, run &lt;code&gt;make; make install&lt;&#x2F;code&gt; in your Python-2.7.7 directory.&lt;&#x2F;p&gt;
&lt;p&gt;Re-installing solved the failure-to-install issue for me.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trying-it-with-python-3-too&quot;&gt;Trying it with Python 3, too&lt;&#x2F;h2&gt;
&lt;p&gt;Repeating the same process with Python 3.4.2 yields the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ImportError: No module named &amp;#39;_collections_abc&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;ERROR: The executable venv3&#x2F;bin&#x2F;python3 is not functioning
&lt;&#x2F;span&gt;&lt;span&gt;ERROR: It thinks sys.prefix is &amp;#39;&#x2F;nfs&#x2F;stak&#x2F;students&#x2F;d&#x2F;dunhame&amp;#39; (should be &amp;#39;&#x2F;nfs&#x2F;stak&#x2F;students&#x2F;d&#x2F;dunhame&#x2F;venv3&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;ERROR: virtualenv is not compatible with this system or executable
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So, Flip&#x27;s virtualenv version is too old. Fortunately, Python3 shipped with pyvenv:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ python3&#x2F;bin&#x2F;pyvenv-3.4 venv3
&lt;&#x2F;span&gt;&lt;span&gt;$ source venv3&#x2F;bin&#x2F;activate
&lt;&#x2F;span&gt;&lt;span&gt;(venv3)$ python --version
&lt;&#x2F;span&gt;&lt;span&gt;Python 3.4.2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For anyone who wants to copy and paste, the commands for installing Python 3.4.2 are:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;wget https:&#x2F;&#x2F;www.python.org&#x2F;ftp&#x2F;python&#x2F;3.4.2&#x2F;Python-3.4.2.tgz
&lt;&#x2F;span&gt;&lt;span&gt;tar -zxvf Python-3.4.2.tgz
&lt;&#x2F;span&gt;&lt;span&gt;cd Python-3.4.2
&lt;&#x2F;span&gt;&lt;span&gt;mkdir ~&#x2F;python3
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;configure --prefix=$HOME&#x2F;python3
&lt;&#x2F;span&gt;&lt;span&gt;make
&lt;&#x2F;span&gt;&lt;span&gt;make install
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Vim: Open file with cursor at the end</title>
        <published>2015-01-29T00:00:00+00:00</published>
        <updated>2015-01-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/29/vim_open_file_with_cursor_at_the_end/"/>
        <id>https://edunham.net/2015/01/29/vim_open_file_with_cursor_at_the_end/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/29/vim_open_file_with_cursor_at_the_end/">&lt;p&gt;As part of a recent quest to automate everything and learn more Vim tricks, I&#x27;ve been identifying patterns in my use of the editor and attempting to get them done with fewer keystrokes.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h1&gt;
&lt;p&gt;I have a file containing notes which I edit several times a day. This takes quite a few keystrokes:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;vim path&#x2F;to&#x2F;where&#x2F;the&#x2F;file&#x2F;is&#x2F;file.txt
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;shift&amp;gt; G $
&lt;&#x2F;span&gt;&lt;span&gt;i
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then I&#x27;m ready to type. Wouldn&#x27;t it be nice to have a short alias to do that for me instead?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;choose-your-own-adventure&quot;&gt;Choose Your Own Adventure&lt;&#x2F;h1&gt;
&lt;p&gt;You can either keep reading to find out the neat but ultimately irrelevant stuff I learned along the way, or skip down to &lt;a href=&quot;https:&#x2F;&#x2F;edunham.net&#x2F;2015&#x2F;01&#x2F;29&#x2F;vim_open_file_with_cursor_at_the_end&#x2F;#the-answer&quot;&gt;The Answer&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;techniques-available&quot;&gt;Techniques Available&lt;&#x2F;h1&gt;
&lt;p&gt;There seem to be 4 options to accomplish this sort of task, 3 of which might be good:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;vim.wikia.com&#x2F;wiki&#x2F;Modeline_magic&quot;&gt;Modeline magic&lt;&#x2F;a&gt; can potentially introduce security vulnerabilities by allowing arbitrary code to be executed, but on the positive side, it allows arbitrary configurations to be set on a per-file basis. To enable modeline hacks, add &lt;code&gt;set modeline&lt;&#x2F;code&gt; to your &lt;code&gt;.vimrc&lt;&#x2F;code&gt; then put a line of the form &lt;code&gt;# vim: set expandtab:&lt;&#x2F;code&gt; or similar in the file to which you want the command to apply.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;http:&#x2F;&#x2F;learnvimscriptthehardway.stevelosh.com&#x2F;chapters&#x2F;12.html&quot;&gt;Autocommands&lt;&#x2F;a&gt; allow Vim to execute specific code when it sees a particular event in the file to which they apply. To be honest, I&#x27;ve never played with autocommands before, and left them till last because they seemed to have the steepest of all 3 formidable learning curves.&lt;&#x2F;li&gt;
&lt;li&gt;Vim&#x27;s command line options include &lt;code&gt;-c {command}&lt;&#x2F;code&gt;, which runs the specified command after the first file has been read. Or &lt;code&gt;--cmd {command}&lt;&#x2F;code&gt; executes it before processing the &lt;code&gt;.vimrc&lt;&#x2F;code&gt; file. A shorter syntax for &lt;code&gt;-c&lt;&#x2F;code&gt; is &lt;code&gt;+{command}&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;The final option, which is a Wrong Answer(TM) but seems awfully tempting after the previously described ones fail a lot, is to accomplish the same purpose through circuitous bash tricks. For instance if &lt;code&gt;G&lt;&#x2F;code&gt; to jump to the end of the file fails when embedded in a command but &lt;code&gt;:20&lt;&#x2F;code&gt; works to jump to line 20, I could use the output of &lt;code&gt;wc -l&lt;&#x2F;code&gt; as a line number. Additionally, it might be easier to have Bash concatenate on some newlines than to convince Vim to insert them.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;trying-modelines&quot;&gt;Trying Modelines&lt;&#x2F;h1&gt;
&lt;p&gt;First, I added &lt;code&gt;set modeline&lt;&#x2F;code&gt; to my &lt;code&gt;.vimrc&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Then I added &lt;code&gt;# vim: set noexpandtab:&lt;&#x2F;code&gt; as the first line in my file. This is a convenient test that my command syntax is right, since my &lt;code&gt;.vimrc&lt;&#x2F;code&gt; defaults things to &lt;code&gt;expandtab&lt;&#x2F;code&gt;. It works, in that the tab key inserts hard tabs when I hit it in that particular file now.&lt;&#x2F;p&gt;
&lt;p&gt;Now, could a gesture possibly work? Switch it to &lt;code&gt;# vim: G:&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Error detected while processing modelines:
&lt;&#x2F;span&gt;&lt;span&gt;line    1:
&lt;&#x2F;span&gt;&lt;span&gt;E518: Unknown option: G
&lt;&#x2F;span&gt;&lt;span&gt;Press ENTER or type command to continue
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Unfortunately, the &lt;code&gt;:normal&lt;&#x2F;code&gt; command which I discovered later does not work in modelines -- it seems that, true to their name, they really only do take mode settings as arguments.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;trying-command-line-syntax&quot;&gt;Trying command line syntax&lt;&#x2F;h1&gt;
&lt;p&gt;I asked a few people how they solve this problem, and they suggested the &lt;code&gt;vim +999999 file.txt&lt;&#x2F;code&gt; trick. Closer examination of the man page reveals that this works because when the specified line number is absent, it defaults to the last line of the file. Although with enough 9&#x27;s it would work fine in practical applications, it seems icky to me because my notes file could hypothetically get very long. However, a quick test reveals that &lt;code&gt;vim + file.txt&lt;&#x2F;code&gt; does the same thing!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chaining-commands&quot;&gt;Chaining commands&lt;&#x2F;h1&gt;
&lt;p&gt;Now I know that &lt;code&gt;vim + file.txt&lt;&#x2F;code&gt; opens the file with the cursor at the first character of the final line. The man page contains a promising hint that I might be able to automate switching to insert mode as well:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;+{command}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;-c {command}
&lt;&#x2F;span&gt;&lt;span&gt;    {command} will be executed after the  first  file  has
&lt;&#x2F;span&gt;&lt;span&gt;    been read.  {command} is interpreted as an Ex command.
&lt;&#x2F;span&gt;&lt;span&gt;    If the {command} contains spaces it must  be  enclosed
&lt;&#x2F;span&gt;&lt;span&gt;    in  double  quotes  (this depends on the shell that is
&lt;&#x2F;span&gt;&lt;span&gt;    used).  Example: Vim &amp;quot;+set si&amp;quot; main.c
&lt;&#x2F;span&gt;&lt;span&gt;    Note: You can use up to 10 &amp;quot;+&amp;quot; or &amp;quot;-c&amp;quot; commands.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I need fewer than 10 commands chained, so this might work. When &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;11587124&#x2F;vim-why-doesnt-normal-i-enter-insert-mode&quot;&gt;looking up&lt;&#x2F;a&gt; how to express &quot;enter insert mode&quot; as a command line option, I discovered the &lt;code&gt;:normal&lt;&#x2F;code&gt; command. It&#x27;s the magic bullet for allowing gestures which you&#x27;d do in normal mode to be expressed on the command line. Let&#x27;s try this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;vim &amp;quot;+ normal G $&amp;quot; file.txt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;opens the file with the cursor at the last character of the last line! Success! However, &lt;code&gt;&quot;+ normal G $ i&quot;&lt;&#x2F;code&gt; does not put one into insert mode after moving to the last character of the file. Time to chain commands!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-answer&quot;&gt;The Answer&lt;&#x2F;h2&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;vim &amp;quot;+normal G$&amp;quot; +startinsert file.txt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Does precisely what I wanted all along!&lt;&#x2F;p&gt;
&lt;p&gt;Its final form actually lives in my &lt;code&gt;~&#x2F;.bashrc&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;alias list=&amp;#39;vim &amp;quot;+normal G$&amp;quot; +startinsert &#x2F;absolute&#x2F;path&#x2F;to&#x2F;list.txt&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;2 hours saving 10 seconds per file open twice a day is an egregious violation of the &lt;a href=&quot;http:&#x2F;&#x2F;xkcd.com&#x2F;1205&#x2F;&quot;&gt;xkcd rule&lt;&#x2F;a&gt;, but it was fun and I learned a lot about Vim automation in the process.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;&#x2F;strong&gt; &lt;a href=&quot;http:&#x2F;&#x2F;www.mythmon.com&#x2F;&quot;&gt;Mythmon&lt;&#x2F;a&gt; pointed out that the command can be golfed into:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;vim &amp;#39;+ normal GA&amp;#39; foo.txt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or to put yourself in insert mode in a new line at the end of the file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;vim &amp;#39;+normal Go&amp;#39; foo.txt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>What Makes a Good Mailing List Post?</title>
        <published>2015-01-28T00:00:00+00:00</published>
        <updated>2015-01-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/28/what_makes_a_good_post/"/>
        <id>https://edunham.net/2015/01/28/what_makes_a_good_post/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/28/what_makes_a_good_post/">&lt;p&gt;As part of my student club officer duties, I send a lot of emails. One game that makes this chore less onerous is to try to optimize each email&#x27;s quality. I do this by observing my own reaction to others&#x27; postings, and others&#x27; reaction to my posts. Here are a few trends I&#x27;ve noticed.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-the-email&quot;&gt;Why the email?&lt;&#x2F;h1&gt;
&lt;p&gt;What do you want your readers to do? Summarize the task in the email&#x27;s topic. This shows respect for your readers&#x27; time -- you allow them to immediately discern whether the message might be relevant to them without investing the energy to open it.&lt;&#x2F;p&gt;
&lt;p&gt;I doubt that I&#x27;m alone in feeling quite annoyed when an email with a vague title contains a request that&#x27;s irrelevant to me. If the title had been more specific, I could have saved the time it took to open and read it.&lt;&#x2F;p&gt;
&lt;p&gt;A few minutes invested in writing your message well will save time for tens or hundreds of list subscribers.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;get-to-the-point&quot;&gt;Get to the point...&lt;&#x2F;h1&gt;
&lt;p&gt;People skim their emails. Keep sentences short; complex ones only cause confusion. Long and chatty is great for personal conversations between friends, but frustrating and a waste of time for professional communication.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;but-include-all-the-necessary-information&quot;&gt;...But include all the necessary information.&lt;&#x2F;h1&gt;
&lt;p&gt;It disrespects your readers&#x27; time to make them look things up. &quot;We&#x27;ll meet at 6pm next Thursday (2&#x2F;5&#x2F;1015)&quot; is far more useful than &quot;we&#x27;ll meet on Thursday&quot;. The latter forces the reader to go to your website and look up the time, and possibly go find a calendar to confirm the date as well.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;know-your-audience&quot;&gt;Know your audience...&lt;&#x2F;h1&gt;
&lt;p&gt;If you&#x27;re writing a technical message to a wide range of knowledge levels, it might be insulting to the intermediate and advanced viewers if you spell out every technical detail. However, it doesn&#x27;t hurt to include enough detail to help a newer community member craft useful Google queries to get help.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re asking readers to take an action, give them a concise explanation of why they should. Hint: &quot;Because it&#x27;s cool&quot; might work if you have a lot of social status and a relatively simpleminded audience, but rarely convinces perceptive adults. However, if your audience thinks it&#x27;s cool to write code and you say &quot;Because you can write a lot of code if you take this action&quot;, they&#x27;ll independently draw the conclusion that taking the action is cool too.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;but-don-t-expect-too-much&quot;&gt;...But don&#x27;t expect too much.&lt;&#x2F;h1&gt;
&lt;p&gt;Most people only look at one request per message. If you&#x27;re requesting an action, make sure it sounds easy to accomplish, and you pre-emptively solve any problems they might run into along the way.&lt;&#x2F;p&gt;
&lt;p&gt;For example, if your message&#x27;s request is to have people add an event to their calendars, be sure you provide the date, start and end times, and location.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Those funny characters in Vim</title>
        <published>2015-01-27T00:00:00+00:00</published>
        <updated>2015-01-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/27/those_funny_characters_in_vim/"/>
        <id>https://edunham.net/2015/01/27/those_funny_characters_in_vim/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/27/those_funny_characters_in_vim/">&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;encoding_problem.png&quot; alt=&quot;vim showing garbled characters from encoding issues&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I copied and pasted some of the lines from a PDF, and now I have a problem which is nearly impossible to Google.&lt;&#x2F;p&gt;
&lt;p&gt;The first potential fix is to set the encoding so it tries to display the characters:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;:e ++enc=utf-8
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And it kind of works:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;encoding_fixed.png&quot; alt=&quot;vim with encoding fixed to UTF-8&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Better. When I re-open the file with the encoding set correctly, my red bar in the 80th character column is one piece instead of being broken by the incorrect displays.&lt;&#x2F;p&gt;
&lt;p&gt;Diamonds with question marks in them are equally difficult to search for, so I asked on IRC. My problem has 2 parts: how to find the un-display-able character I&#x27;m addressing, and how to refer to it in a regular expression.&lt;&#x2F;p&gt;
&lt;p&gt;To find the problem character&#x27;s value, place the cursor on it and use &lt;code&gt;ga&lt;&#x2F;code&gt;. According to the &lt;a href=&quot;http:&#x2F;&#x2F;vimdoc.sourceforge.net&#x2F;htmldoc&#x2F;various.html&quot;&gt;vimdoc&lt;&#x2F;a&gt;, you can also use &lt;code&gt;:ascii&lt;&#x2F;code&gt;, which might be easier to remember.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;character_value.png&quot; alt=&quot;vim ga command showing character hex value&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This tells me that the problem character has hex value &lt;code&gt;bf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;According to &lt;a href=&quot;https:&#x2F;&#x2F;durgaprasad.wordpress.com&#x2F;2007&#x2F;09&#x2F;25&#x2F;find-replace-non-printable-characters-in-vim&#x2F;&quot;&gt;this helpful post&lt;&#x2F;a&gt;, which didn&#x27;t show up until I googled &quot;vim regex octal value of character&quot;, one can specify octal values in regex by prefacing them with &lt;code&gt;\%x&lt;&#x2F;code&gt;. In my case, &lt;code&gt;:%s&#x2F;\%xbf&#x2F;-&#x2F;g&lt;&#x2F;code&gt; replaces all those questionmark symbols that used to be some kind of separators in the encoding from which I pasted them.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Searching a FOSS project&#x27;s history</title>
        <published>2015-01-21T00:00:00+00:00</published>
        <updated>2015-01-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/21/searching_foss_project_history/"/>
        <id>https://edunham.net/2015/01/21/searching_foss_project_history/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/21/searching_foss_project_history/">&lt;p&gt;I&#x27;m curious about whether anyone has tried to build a predictive analytics plugin for Heka before. To find out, I&#x27;m going to stalk the project&#x27;s entire recorded history. Since it&#x27;s a relatively young project (only in its third year of having a public mailing list), the history is small enough for basic Linux command-line utilities to handle in a timely manner.&lt;&#x2F;p&gt;
&lt;p&gt;Here are all the places one can look for project history, and how I used them.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;find-the-communication-channels&quot;&gt;Find the Communication Channels&lt;&#x2F;h1&gt;
&lt;p&gt;FOSS projects communicate on IRC, issue trackers, and mailing lists. Sometimes contributors also discuss things on the phone, via private messages, during video games, and in person. Although the latter type of interactions are rarely publicly documented, most of content that&#x27;s important to a project is shared with all of its members, and thus easy for even a new contributor to find.&lt;&#x2F;p&gt;
&lt;p&gt;When you want a particular piece of information, the question to ask is &quot;if I was the person who created that information, where would I have put it?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Find the project&#x27;s documentation on how to get involved and where to ask questions. On the front page of the &lt;a href=&quot;https:&#x2F;&#x2F;hekad.readthedocs.org&#x2F;en&#x2F;v0.8.2&#x2F;index.html&quot;&gt;Heka docs&lt;&#x2F;a&gt;, it suggests a mailing list, issue tracker, github project, and IRC channel.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;searching-the-mailing-list-archives&quot;&gt;Searching the Mailing List Archives&lt;&#x2F;h1&gt;
&lt;p&gt;Heka&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;mail.mozilla.org&#x2F;pipermail&#x2F;heka&#x2F;&quot;&gt;mailing list archives&lt;&#x2F;a&gt; don&#x27;t explicitly offer a search feature. Fortunately, the archives are not overwhelmingly large, so I can just download them and search them locally.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;downloading-the-archives&quot;&gt;Downloading the archives&lt;&#x2F;h2&gt;
&lt;p&gt;Examining the page source reveals that the &lt;code&gt;.txt.gz&lt;&#x2F;code&gt; archives have nicely standardized names, which allowed me to craft a slightly verbose wget command to retrieve them all:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;wget https:&#x2F;&#x2F;mail.mozilla.org&#x2F;pipermail&#x2F;heka&#x2F;201{3,4,5}-{January,February,March,April,May,June,July,August,September,October,November,December}.txt.gz
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I suspect there&#x27;s a recursive wget command which would grab all &lt;code&gt;.txt.gz&lt;&#x2F;code&gt; links from a specified index page, but I didn&#x27;t know it off the top of my head so I&#x27;ll save the rabbit hole of crafting the shortest possible incantation for later. The command above was fast to research and write, and got all the tarballs I wanted, so it accomplished its purpose.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unzip&quot;&gt;Unzip&lt;&#x2F;h2&gt;
&lt;p&gt;After that wget, I have a directory full of &lt;code&gt;.txt.gz&lt;&#x2F;code&gt; files. I unzip them:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;gzip -d *
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;grep&quot;&gt;Grep&lt;&#x2F;h2&gt;
&lt;p&gt;Now it&#x27;s easy to look for any keywords that might go with the information I&#x27;m seeking:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;grep -i &amp;quot;predict&amp;quot; *
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;searching-the-source-code&quot;&gt;Searching the Source Code&lt;&#x2F;h1&gt;
&lt;p&gt;Since the GitHub link was given in the &lt;a href=&quot;https:&#x2F;&#x2F;hekad.readthedocs.org&#x2F;en&#x2F;v0.8.2&#x2F;index.html&quot;&gt;heka docs&lt;&#x2F;a&gt;, it&#x27;s trivial to clone a copy and search for keywords throughout the source and comments:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;git clone git@github.com:mozilla-services&#x2F;heka.git
&lt;&#x2F;span&gt;&lt;span&gt;cd heka
&lt;&#x2F;span&gt;&lt;span&gt;git grep -i predict
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;searching-the-commit-history&quot;&gt;Searching the Commit History&lt;&#x2F;h1&gt;
&lt;p&gt;The &lt;code&gt;git grep&lt;&#x2F;code&gt; command used above is designed to search only in the tree and index, so Git&#x27;s metadata is left out. If you do a regular recursive grep on a Git repo, you can get a bunch of redundant or spurious matches from Git&#x27;s commit history.&lt;&#x2F;p&gt;
&lt;p&gt;When it&#x27;s time to intentionally search the commit history, Git has a tool for that too:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;git log -S&amp;quot;predict&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The output of the &lt;code&gt;git log&lt;&#x2F;code&gt; command will by default be a list of commits, with hash, author, date, and short message. For more detail on a commit, just &lt;code&gt;git show&lt;&#x2F;code&gt; the hash:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;git show ea2b3c9f12f7f046be9b3bc133ee3eda90e16306
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;searching-the-issue-tracker&quot;&gt;Searching the Issue Tracker&lt;&#x2F;h1&gt;
&lt;p&gt;Luckily, the terms I&#x27;m searching for are simple case-insensitive strings, so I can use the search box on the project&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mozilla-services&#x2F;heka&#x2F;issues&quot;&gt;GitHub issue tracker&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If I needed to search with a regular expression, or simply wanted a local copy of the issue database, my options would be to use a pre-made &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;joeyh&#x2F;github-backup&quot;&gt;GitHub backup tool&lt;&#x2F;a&gt; or the &lt;a href=&quot;https:&#x2F;&#x2F;developer.github.com&#x2F;v3&#x2F;issues&#x2F;&quot;&gt;API&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;seek-irc-history&quot;&gt;Seek IRC history&lt;&#x2F;h1&gt;
&lt;p&gt;Some channels are logged publicly. It&#x27;s always worth a try to join the channel and examine the &lt;code&gt;&#x2F;topic&lt;&#x2F;code&gt; for any hints. IRC channel topics are also good places to find a community-specific pastebin or etherpad, both of which could potentially contain information relevant to your search.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;also-google&quot;&gt;Also, Google.&lt;&#x2F;h1&gt;
&lt;p&gt;In my case, Google helped find several repositories of Heka plugins, on which I repeated the issue tracker and source code search steps. Google is also the best way of finding a project&#x27;s official blog, or simply individual blogs about the project, if those are available.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>ECE375: Using an Arduino Uno as a programmer</title>
        <published>2015-01-19T00:00:00+00:00</published>
        <updated>2015-01-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/19/ece375_using_an_arduino_uno_as_a_programmer/"/>
        <id>https://edunham.net/2015/01/19/ece375_using_an_arduino_uno_as_a_programmer/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/19/ece375_using_an_arduino_uno_as_a_programmer/">&lt;p&gt;I have an &lt;a href=&quot;http:&#x2F;&#x2F;eecs.oregonstate.edu&#x2F;education&#x2F;courses&#x2F;ece375&#x2F;&quot;&gt;atmega128 development board&lt;&#x2F;a&gt; for the ECE375 class at Oregon State University. I believe the board is good, because it runs the test program that it came with when I picked it up from TekBots. I also have an &lt;a href=&quot;https:&#x2F;&#x2F;www.olimex.com&#x2F;Products&#x2F;AVR&#x2F;Programmers&#x2F;AVR-ISP-MK2&#x2F;open-source-hardware&quot;&gt;Olimex AVR-ISP-MK2&lt;&#x2F;a&gt; programmer inherited from an ECE major friend, which I have come to conclude is bad, because despite testing and rebuilding all of the connections between it and my atmega128 board, despite passing avrdude all of the force and override-warnings flags at my disposal, it consistently refuses to program.&lt;&#x2F;p&gt;
&lt;p&gt;Since my assignment is due tomorrow, I am configuring an Arduino Uno to stand in as a programmer. Here&#x27;s how.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m following the &lt;a href=&quot;http:&#x2F;&#x2F;arduino.cc&#x2F;en&#x2F;Tutorial&#x2F;ArduinoISP&quot;&gt;ArduinoISP&lt;&#x2F;a&gt; tutorial to turn an Uno left over from another project into a programmer.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;add-a-capacitor&quot;&gt;Add a capacitor&lt;&#x2F;h1&gt;
&lt;p&gt;First, I scrounged a 10uF capacitor out of a failed attempt at building an audio amp. The capacitor goes on the Uno with its negative leg (the side with the stripe) connected to ground, and the other leg connected to the pin labeled reset.&lt;&#x2F;p&gt;
&lt;p&gt;This is necessary to prevent spurious resets. If noise or interference on the board caused the reset line to fall below a certain minimum voltage (the logic low threshhold), the Uno would reset when the user didn&#x27;t tell it to. The capacitor stores up electricity and can provide a tiny supply of power to keep the reset line high during fluctations, so that the board will only be reset if it&#x27;s being actively driven low for more than just a moment.&lt;&#x2F;p&gt;
&lt;p&gt;I asked a grad student in Electrical Engineering about the capacitor required by the tutorial, and he claimed that 10 uF is probably overkill -- a 0.1uF ceramic cap would do the job just as well. So if you&#x27;re following this tutotial and only have a small capacitor available, try it anyways, and the worst that could happen without it is an occasional board reset during programming.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;program-the-arduino-uno&quot;&gt;Program the Arduino Uno&lt;&#x2F;h1&gt;
&lt;p&gt;I installed the Arduino toolchain and IDE with &lt;code&gt;yaourt -S arduino-beta&lt;&#x2F;code&gt;, since the regular arduino package was failing to download at the time I tried.&lt;&#x2F;p&gt;
&lt;p&gt;I downloaded and unzipped the &lt;a href=&quot;https:&#x2F;&#x2F;code.google.com&#x2F;p&#x2F;mega-isp&#x2F;downloads&#x2F;&quot;&gt;mega-isp firmware&lt;&#x2F;a&gt;, then opened it in the Arduino ide with &lt;code&gt;sudo arduino ArduinoISP.pde&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When I attempted to compile and upload, it threw several errors:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ArduinoISP.pde:36:14: error: expected unqualified-id before numeric constant
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;usr&#x2F;share&#x2F;arduino&#x2F;hardware&#x2F;arduino&#x2F;avr&#x2F;variants&#x2F;standard&#x2F;pins_arduino.h:41:22: note: in expansion of macro &amp;#39;MOSI&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;static const uint8_t MOSI = 11;
&lt;&#x2F;span&gt;&lt;span&gt;                      ^
&lt;&#x2F;span&gt;&lt;span&gt;ArduinoISP.pde:35:14: error: expected unqualified-id before numeric constant
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;usr&#x2F;share&#x2F;arduino&#x2F;hardware&#x2F;arduino&#x2F;avr&#x2F;variants&#x2F;standard&#x2F;pins_arduino.h:42:22: note: in expansion of macro &amp;#39;MISO&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;static const uint8_t MISO = 12;
&lt;&#x2F;span&gt;&lt;span&gt;                      ^
&lt;&#x2F;span&gt;&lt;span&gt;ArduinoISP.pde:34:13: error: expected unqualified-id before numeric constant
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;usr&#x2F;share&#x2F;arduino&#x2F;hardware&#x2F;arduino&#x2F;avr&#x2F;variants&#x2F;standard&#x2F;pins_arduino.h:43:22: note: in expansion of macro &amp;#39;SCK&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;static const uint8_t SCK  = 13;
&lt;&#x2F;span&gt;&lt;span&gt;                      ^
&lt;&#x2F;span&gt;&lt;span&gt;Error compiling.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since &lt;code&gt;MISO&lt;&#x2F;code&gt;, &lt;code&gt;MOSI&lt;&#x2F;code&gt;, and &lt;code&gt;SCK&lt;&#x2F;code&gt; were having a namespace conflict with a header file, the easiest fix was to replace their strings with &lt;code&gt;MYMISO&lt;&#x2F;code&gt;, &lt;code&gt;MYMOSI&lt;&#x2F;code&gt;, and &lt;code&gt;MYSCK&lt;&#x2F;code&gt; throughout the ArduinoISP file. I&#x27;d guess they were added to the header files after the ArduinoISP software was published, since my version of the Arduino IDE is much more recent than the ISP&#x27;s publication date of 2009.&lt;&#x2F;p&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;When saving these changes, the IDE asks if it&#x27;s ok to change the extension from &lt;code&gt;.pde&lt;&#x2F;code&gt; to &lt;code&gt;.ino&lt;&#x2F;code&gt;. Just say yes; the file extension doesn&#x27;t make any real difference.&lt;&#x2F;p&gt;
&lt;p&gt;The next set of errors is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;usr&#x2F;share&#x2F;arduino&#x2F;hardware&#x2F;tools&#x2F;avr&#x2F;bin&#x2F;avrdude: error while loading shared
&lt;&#x2F;span&gt;&lt;span&gt;libraries: libtinfo.so.5: cannot open shared object file: No such file or
&lt;&#x2F;span&gt;&lt;span&gt;directory
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A quick Google for the error has the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;arduino&quot;&gt;Arch Wiki page&lt;&#x2F;a&gt; among the first hits, and the fix is straightforward:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cd &#x2F;usr&#x2F;lib
&lt;&#x2F;span&gt;&lt;span&gt;$ sudo ln -s libncurses.so.5 libtinfo.so.5
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After symlinking the libraries, my next error on attempting to program is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;avrdude: ser_open(): can&amp;#39;t open device &amp;quot;COM1&amp;quot;: No such file or directory
&lt;&#x2F;span&gt;&lt;span&gt;ioctl(&amp;quot;TIOCMGET&amp;quot;): Inappropriate ioctl for device
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This would normally mean you get to &lt;a href=&quot;http:&#x2F;&#x2F;arduino-er.blogspot.com&#x2F;2014&#x2F;08&#x2F;arduino-ide-error-avrdude-seropen-cant.html&quot;&gt;mess around with groups&lt;&#x2F;a&gt; or just invoke the IDE as root. Since I&#x27;m already running it with sudo, it&#x27;s a hint that Arch named the device something funny when it registered. In the Arduino IDE&#x27;s menus, clicking &lt;code&gt;Tools&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;Port&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;&#x2F;dev&#x2F;TTYACM0 (Arduino Uno)&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;My next error was:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;avrdude: stk500_getsync(): not in sync: resp=0x00
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At the suggestion of &lt;a href=&quot;http:&#x2F;&#x2F;www.parkansky.com&#x2F;arduino-error.htm&quot;&gt;this site&lt;&#x2F;a&gt;, I unplugged the capacitor from the &lt;code&gt;RESET&lt;&#x2F;code&gt; pin and tried again. It uploaded without error.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;wire-the-uno-up-to-the-atmega128&quot;&gt;Wire the Uno up to the ATMEGA128&lt;&#x2F;h1&gt;
&lt;p&gt;My ECE friend helped me read the ATMEGA board schematic and figure out what the pins in the 10-pin header do. Here&#x27;s my ugly ASCII art of the header (note that the notch faces toward the inside of the board):&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;-----------
&lt;&#x2F;span&gt;&lt;span&gt;|a b c d e|
&lt;&#x2F;span&gt;&lt;span&gt;|f g h i j|
&lt;&#x2F;span&gt;&lt;span&gt;----___----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Arduino Uno Pin&lt;&#x2F;th&gt;&lt;th&gt;Name&lt;&#x2F;th&gt;&lt;th&gt;Wire Color&lt;&#x2F;th&gt;&lt;th&gt;ATMEGA Pin&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;10&lt;&#x2F;td&gt;&lt;td&gt;reset&lt;&#x2F;td&gt;&lt;td&gt;gray&lt;&#x2F;td&gt;&lt;td&gt;h&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;11&lt;&#x2F;td&gt;&lt;td&gt;MOSI&lt;&#x2F;td&gt;&lt;td&gt;white&lt;&#x2F;td&gt;&lt;td&gt;f&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;12&lt;&#x2F;td&gt;&lt;td&gt;MISO&lt;&#x2F;td&gt;&lt;td&gt;black&lt;&#x2F;td&gt;&lt;td&gt;j&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;13&lt;&#x2F;td&gt;&lt;td&gt;SCK&lt;&#x2F;td&gt;&lt;td&gt;brown&lt;&#x2F;td&gt;&lt;td&gt;i&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;GND&lt;&#x2F;td&gt;&lt;td&gt;GND&lt;&#x2F;td&gt;&lt;td&gt;blue&lt;&#x2F;td&gt;&lt;td&gt;b,c,d,e&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;+5V&lt;&#x2F;td&gt;&lt;td&gt;+5V&lt;&#x2F;td&gt;&lt;td&gt;purple&lt;&#x2F;td&gt;&lt;td&gt;a&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Surprisingly, the ATMEGA128 runs its test program quite happily when powered off of only the 5V line from the Uno. Here&#x27;s a picture of the setup:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;pictures&#x2F;uno-atmega128-wiring.jpg&quot; alt=&quot;Arduino Uno wired to ATmega128 development board&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;program-the-atmega&quot;&gt;Program the ATMEGA&lt;&#x2F;h1&gt;
&lt;p&gt;A &lt;a href=&quot;http:&#x2F;&#x2F;playground.arduino.cc&#x2F;Code&#x2F;MegaISP&quot;&gt;writeup&lt;&#x2F;a&gt; on the Arduino Playground provides the outline for my avrdude incantation. To find the actual port in use, I unplug and replug the Uno then examine the output of &lt;code&gt;dmesg | tail&lt;&#x2F;code&gt; to see:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[28403.507844] cdc_acm 3-1:1.0: ttyACM1: USB ACM device
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This means that the Uno is showing up in &lt;code&gt;&#x2F;dev&#x2F;ttyACM1&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Note&lt;&#x2F;p&gt;
&lt;p&gt;With the capacitor plugged in, I get the error:&lt;&#x2F;p&gt;
&lt;p&gt;avrdude: stk500_recv(): programmer is not responding&lt;&#x2F;p&gt;
&lt;p&gt;so I&#x27;m unplugging the cap for now and hoping that it works.&lt;&#x2F;p&gt;
&lt;p&gt;For some code to test uploading with, I&#x27;m using Mythmon&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mythmon&#x2F;HelloDave&quot;&gt;Hello Dave&lt;&#x2F;a&gt; game.&lt;&#x2F;p&gt;
&lt;p&gt;The installation command will look something like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sudo avrdude -p m128 -c avrisp -P &#x2F;dev&#x2F;ttyACM1 -U flash:w:hellodave.hex
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: AVR device initialized and ready to accept instructions
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Reading | ################################################## | 100% 0.01s
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: Device signature = 0x000000
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: Yikes!  Invalid device signature.
&lt;&#x2F;span&gt;&lt;span&gt;        Double check connections and try again, or use -F to override
&lt;&#x2F;span&gt;&lt;span&gt;        this check.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When I pass the -F flag, it gets as far as a signature mismatch:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;sudo avrdude -p m128 -c avrisp -F -P &#x2F;dev&#x2F;ttyACM1 -U flash:w:hellodave.hex
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: AVR device initialized and ready to accept instructions
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Reading | ################################################## | 100% 0.01s
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: Device signature = 0x000000
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: Yikes!  Invalid device signature.
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
&lt;&#x2F;span&gt;&lt;span&gt;         To disable this feature, specify the -D option.
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: erasing chip
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: reading input file &amp;quot;hellodave.hex&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: input file hellodave.hex auto detected as Intel Hex
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: writing flash (2468 bytes):
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Writing | ################################################## | 100% 0.32s
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: 2468 bytes of flash written
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: verifying flash memory against hellodave.hex:
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: load data flash data from input file hellodave.hex:
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: input file hellodave.hex auto detected as Intel Hex
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: input file hellodave.hex contains 2468 bytes
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: reading on-chip flash data:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Reading | ################################################## | 100% 0.28s
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: verifying ...
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: verification error, first mismatch at byte 0x0080
&lt;&#x2F;span&gt;&lt;span&gt;         0xff != 0x10
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: verification error; content mismatch
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude: safemode: Fuses OK
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;avrdude done.  Thank you.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Don&#x27;t rename Tinkerer posts</title>
        <published>2015-01-15T00:00:00+00:00</published>
        <updated>2015-01-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/15/don_t_rename_tinkerer_posts/"/>
        <id>https://edunham.net/2015/01/15/don_t_rename_tinkerer_posts/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/15/don_t_rename_tinkerer_posts/">&lt;p&gt;Or if you must, at least do it correctly. Here&#x27;s how.&lt;&#x2F;p&gt;
&lt;p&gt;When you invoke &lt;code&gt;tinker --post&lt;&#x2F;code&gt;, it creates a file in &lt;code&gt;YYYY&#x2F;MM&#x2F;DD&#x2F;postname.rst&lt;&#x2F;code&gt;, and also adds YYYY&#x2F;MM&#x2F;DD&#x2F;postname to the toctree in &lt;code&gt;master.rst&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I learned what happens when you rename things improperly because my very first post to this blog started out entitled &quot;Blogging with Tinker&quot;. I then remembered that the framework is technically called &lt;code&gt;tinkerer&lt;&#x2F;code&gt; even though one always invokes it as &lt;code&gt;tinker&lt;&#x2F;code&gt;, so I changed the .rst file&#x27;s name.&lt;&#x2F;p&gt;
&lt;p&gt;If your &lt;a href=&quot;http:&#x2F;&#x2F;www.catb.org&#x2F;jargon&#x2F;html&#x2F;F&#x2F;suffix-fu.html&quot;&gt;sphinx-fu&lt;&#x2F;a&gt; is strong, the next part will be obvious. If you&#x27;ve finally carved out some time to play with your personal blog at a silly hour of the morning after a long day of schoolwork, it&#x27;ll waste a lot of time and googling before you figure out why &lt;strong&gt;the post disappeared&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When the name of the file does not precisely match the name in the toctree, you will not get a link to it in your sidebar. Sphinx does throw a helpful little warning:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;WARNING: document isn&amp;#39;t included in any toctree
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;but when that&#x27;s right in the middle of a wall of 38 other warnings about how Sphinx isn&#x27;t happy with Tinkerer&#x27;s post templates being only templates and thus not used anywhere, it&#x27;s not immediately obvious what changed since the build worked perfectly.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;should-tinkerer-have-a-rename-option&quot;&gt;Should Tinkerer have a &lt;code&gt;--rename&lt;&#x2F;code&gt; option?&lt;&#x2F;h1&gt;
&lt;p&gt;My first impression is no; the inconvenience of implementing and documenting &lt;code&gt;--rename&lt;&#x2F;code&gt;, plus the clunky syntax of spelling out the full paths to the old and new posts and getting the dates right, seems to be worse than just mentioning it here.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;my-tinkerer-post-disappeared&quot;&gt;My Tinkerer post disappeared!&lt;&#x2F;h1&gt;
&lt;p&gt;If your Tinkerer post is missing, make sure you didn&#x27;t make the same mistake I did by forgetting to update the name in &lt;code&gt;master.rst&lt;&#x2F;code&gt;. &lt;strong&gt;If you move the source file, you have to update the toctree too&lt;&#x2F;strong&gt;. Sphinx is just doing its job.&lt;&#x2F;p&gt;
&lt;p&gt;Yes, this section is redundant, but I wanted to make sure this post includes the same phrases I googled when wondering what was going on. It&#x27;s such an obvious error that I&#x27;m sure other people have made it, debugged it, gone &quot;duh, that was obvious!&quot;, and then not written anything down.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Floating-point Forth</title>
        <published>2015-01-14T00:00:00+00:00</published>
        <updated>2015-01-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/14/floating_point_forth/"/>
        <id>https://edunham.net/2015/01/14/floating_point_forth/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/14/floating_point_forth/">&lt;p&gt;The first &lt;a href=&quot;http:&#x2F;&#x2F;classes.engr.oregonstate.edu&#x2F;eecs&#x2F;winter2015&#x2F;cs480&#x2F;assignments&#x2F;MilestoneI.htm&quot;&gt;assignment&lt;&#x2F;a&gt; for CS480 (Translators) requests that we use Forth as a pocket calculator, rather than teaching the immensely powerful composition strategies for which it&#x27;s valued in the real world.&lt;&#x2F;p&gt;
&lt;p&gt;Since our first exposure to the language is a deep dive into the syntax of floating-point computation, no single tutorial on the web answers all the strange assortment of introductory and advanced questions that my classmates and I are running into.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s what I&#x27;ve learned so far:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Remember &lt;a href=&quot;http:&#x2F;&#x2F;www.mathsisfun.com&#x2F;operation-order-pemdas.html&quot;&gt;PEMDAS&lt;&#x2F;a&gt;. The quickest way to construct an expression tree for a given mathematical equation is to work from right to left in PEMDAS, then progress downward: The root of the tree is the first subtraction sign in the equation if there is one, otherwise the first addition sign, etc. Everything to the left of the sign which ended up as the root of the tree goes on its left side; everything to the right goes on the right. Then you build each little subtree, following the same rule.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Deep within the &lt;a href=&quot;http:&#x2F;&#x2F;www.forth.com&#x2F;starting-forth&#x2F;sf1&#x2F;sf1.html&quot;&gt;introduction&lt;&#x2F;a&gt; of the book Starting Forth, it explains what the comments mean. If you learn nothing else from the introduction, this little section will make lots of other docs make sense:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;To communicate stack effects in a visual way, Forth programmers conventionally use a special stack notation in their glossaries or tables of words. We&#x27;re introducing the stack notation now so that you&#x27;ll have it under your belt when you begin the next chapter.&lt;&#x2F;p&gt;
&lt;p&gt;Here is the basic form:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;( before -- after )
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The dash separates the things that should be on the stack (before you execute the word) from the things that will be left there afterwards. For example, here&#x27;s the stack notation for the word .:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;.   ( n -- )
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(The letter &quot;n&quot; stands for &quot;number.&quot;) This shows that . expects one number on the stack (before) and leaves no number on the stack (after).&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the stack notation for the word +.:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;+   ( n1 n2 -- sum )
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;The floating point stack is separate from the default, or integer, stack. It has different arithmetic &lt;a href=&quot;http:&#x2F;&#x2F;galileo.phys.virginia.edu&#x2F;classes&#x2F;551.jvn.fall01&#x2F;primer.htm#fp&quot;&gt;operators&lt;&#x2F;a&gt; than the integer stack -- typically same as the integer operator, but prefaced with an f.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;F@      ( adr --)       ( f: -- x)
&lt;&#x2F;span&gt;&lt;span&gt;F!      ( adr --)       ( f: x --)
&lt;&#x2F;span&gt;&lt;span&gt;F+                      ( f: x y -- x+y)
&lt;&#x2F;span&gt;&lt;span&gt;F-                      ( f: x y -- x-y)
&lt;&#x2F;span&gt;&lt;span&gt;F*                      ( f: x y -- x*y)
&lt;&#x2F;span&gt;&lt;span&gt;F&#x2F;                      ( f: x y -- x&#x2F;y)
&lt;&#x2F;span&gt;&lt;span&gt;FEXP                    ( f: x -- e^x)
&lt;&#x2F;span&gt;&lt;span&gt;FLN                     ( f: x -- ln[x])
&lt;&#x2F;span&gt;&lt;span&gt;FSQRT                   ( f: x -- x^0.5)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;s&amp;gt;f&lt;&#x2F;code&gt; moves the top value of the integer stack onto the floating point stack. For this assignment, it won&#x27;t make sense to ever move floating point values back to the integer stack.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Declaring variables is pretty straightforward:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;variable a  (initialize an intger named a)
&lt;&#x2F;span&gt;&lt;span&gt;23 a !      (assign the value 23 into a)
&lt;&#x2F;span&gt;&lt;span&gt;a @         (put a&amp;#39;s value onto the integer stack)
&lt;&#x2F;span&gt;&lt;span&gt;.s          (hey look, the integer stack has 23 on it!)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Remember that you have to preface everything with an &lt;code&gt;f&lt;&#x2F;code&gt; to make it apply to the floating-point stack:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;fvariable z     (initialize a floating-point variable named z)
&lt;&#x2F;span&gt;&lt;span&gt;69e z f!        (z gets the value 69)
&lt;&#x2F;span&gt;&lt;span&gt;z f@            (put value of z onto floating-point stack)
&lt;&#x2F;span&gt;&lt;span&gt;f.s             (Show the floating-point stack.)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;When an operator has both an integer and a float as its arguments, when do you put the int onto the float stack? According to a former instructor who used to teach this course, the correct solution is to insert a new node for the &lt;code&gt;s&amp;gt;f&lt;&#x2F;code&gt; conversion between the parent and its integer child in the expression tree, before the post-order traversal. If you try to do the conversion after the traversal and it occurrs at the wrong time, you could end up accidentally swapping the order of the operator&#x27;s children. This is harmless for addition and multiplication, but would break everything if it occurred with subtraction or division.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;There are two ways to do recursion in gforth. If you say &lt;code&gt;recursive&lt;&#x2F;code&gt; after the name of the word you&#x27;re creating, you are able to call the word by name in its definition. Alternately, you can say &lt;code&gt;recurse&lt;&#x2F;code&gt; instead of the name of the word whenever you need to call it from within itself.&lt;&#x2F;p&gt;
&lt;p&gt;Since both fibonacci and factorial are used in the homework, I&#x27;ll use a function that sums from 0 to the argument as an example. In Python, it&#x27;d look like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;def sum(i):
&lt;&#x2F;span&gt;&lt;span&gt;    if (i == 0):
&lt;&#x2F;span&gt;&lt;span&gt;        return 0
&lt;&#x2F;span&gt;&lt;span&gt;    return i + sum(i-1)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Using forth&#x27;s &lt;code&gt;recursive&lt;&#x2F;code&gt; command, it&#x27;ll look like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;: sum recursive
&lt;&#x2F;span&gt;&lt;span&gt;dup 
&lt;&#x2F;span&gt;&lt;span&gt;    ( top copy of the argument will get destroyed by comparison )
&lt;&#x2F;span&gt;&lt;span&gt;0=  
&lt;&#x2F;span&gt;&lt;span&gt;    ( take argument off the stack, replace it with true or false )
&lt;&#x2F;span&gt;&lt;span&gt;if
&lt;&#x2F;span&gt;&lt;span&gt;    ( we&amp;#39;ll only take this branch if the argument was == 0 )
&lt;&#x2F;span&gt;&lt;span&gt;    exit
&lt;&#x2F;span&gt;&lt;span&gt;else
&lt;&#x2F;span&gt;&lt;span&gt;    ( we have i on the stack, need to return i + the sum )
&lt;&#x2F;span&gt;&lt;span&gt;    dup
&lt;&#x2F;span&gt;&lt;span&gt;        ( extra copy, keep an i for adding later )
&lt;&#x2F;span&gt;&lt;span&gt;    1-
&lt;&#x2F;span&gt;&lt;span&gt;        ( decrement, now stack has i-1 on top to be the arg to next call )
&lt;&#x2F;span&gt;&lt;span&gt;    sum
&lt;&#x2F;span&gt;&lt;span&gt;        ( recursively call with that i-1 we made )
&lt;&#x2F;span&gt;&lt;span&gt;    +
&lt;&#x2F;span&gt;&lt;span&gt;        ( yay postfix! )
&lt;&#x2F;span&gt;&lt;span&gt;endif ;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we don&#x27;t use &lt;code&gt;recursive&lt;&#x2F;code&gt;, it looks almost identical except for the actual recursive call:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;: othersum 
&lt;&#x2F;span&gt;&lt;span&gt;dup 
&lt;&#x2F;span&gt;&lt;span&gt;0=  
&lt;&#x2F;span&gt;&lt;span&gt;if
&lt;&#x2F;span&gt;&lt;span&gt;    exit
&lt;&#x2F;span&gt;&lt;span&gt;else
&lt;&#x2F;span&gt;&lt;span&gt;    dup
&lt;&#x2F;span&gt;&lt;span&gt;    1-
&lt;&#x2F;span&gt;&lt;span&gt;    recurse 
&lt;&#x2F;span&gt;&lt;span&gt;        ( does exactly the same thing as the call to sum )
&lt;&#x2F;span&gt;&lt;span&gt;    +
&lt;&#x2F;span&gt;&lt;span&gt;endif ;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So testing them out will look something like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;3 sum 
&lt;&#x2F;span&gt;&lt;span&gt;. 6  ok
&lt;&#x2F;span&gt;&lt;span&gt;4 sum 
&lt;&#x2F;span&gt;&lt;span&gt;. 10  ok
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;3 othersum 
&lt;&#x2F;span&gt;&lt;span&gt;. 6  ok
&lt;&#x2F;span&gt;&lt;span&gt;4 othersum 
&lt;&#x2F;span&gt;&lt;span&gt;. 10  ok
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Was this helpful? Did I miss anything? For feedback, go find my email address on my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edunham&quot;&gt;github&lt;&#x2F;a&gt; profile and send me your thoughts! (I might set up comments or a reasonably bot-resistant email address disclosure here someday, but for now, I hope the simple IQ test of clicking an extra link is enough to weed out spammers).&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Blogging with Tinkerer</title>
        <published>2015-01-13T00:00:00+00:00</published>
        <updated>2015-01-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://edunham.net/2015/01/13/blogging_with_tinkerer/"/>
        <id>https://edunham.net/2015/01/13/blogging_with_tinkerer/</id>
        
        <content type="html" xml:base="https://edunham.net/2015/01/13/blogging_with_tinkerer/">&lt;p&gt;I had a &lt;a href=&quot;http:&#x2F;&#x2F;wok.mythmon.com&#x2F;&quot;&gt;wok&lt;&#x2F;a&gt; site here for a while, but I rarely (okay, never) updated it. My experience with blogging platforms has been limited to Wordpress (both self-hosted and on wordpress.com), Wok, Pelican, and an abomination of a Trac plugin that I&#x27;d prefer to forget.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how and why I am now trying &lt;a href=&quot;http:&#x2F;&#x2F;tinkerer.me&#x2F;index.html&quot;&gt;Tinkerer&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Mythmon wrote Wok to meet his projects&#x27; needs, which have customizabity as a high priority and assume that the user is already comfortable with graphic design, CSS, and JavaScript. Since Wok&#x27;s relatively small userbase means there&#x27;s no standard template with the look that I had in mind for my personal site, I had some fun adventures in graphic design before getting frustrated and setting the project aside indefinitely.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve worked on other projects that use Wok, such as the &lt;a href=&quot;http:&#x2F;&#x2F;lug.oregonstate.edu&#x2F;&quot;&gt;OSU LUG&lt;&#x2F;a&gt; website, and found that it&#x27;s easy to use as long as the artistic parts are handled by someone with more sophisticated aesthetic sensibilities than my own. Although I know an ugly website when I see it, my ideal user interface is a UNIX terminal, so I never managed to come up with a satisfying design for my personal site.&lt;&#x2F;p&gt;
&lt;p&gt;I revisited my site recently and, in true sysadmin fashion, decided that the obvious way to solve the &quot;human isn&#x27;t happy&quot; problem was to switch to a fancy new technology and see if that helped. &lt;a href=&quot;http:&#x2F;&#x2F;www.staticgen.com&#x2F;&quot;&gt;Staticgen.com&lt;&#x2F;a&gt; has an exhaustive list of site generators, which one can sort by language and rating on GitHub.&lt;&#x2F;p&gt;
&lt;p&gt;My criteria for a &quot;better&quot; site generator are as follows:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generate static sites&lt;&#x2F;strong&gt;. The purpose of my personal web site is just to disseminate a few pages of information, and the best way to accomplish that goal is a few static pages. I see no reason that my poor little DigitalOcean droplet should have to do more work than absolutely necessary for each visitor to my blog -- generating static pages only when they&#x27;re changed is like caching a dynamic site, taken to the extreme. Plus, refusing to run any code input by my site&#x27;s users (such as database queries, PHP scripts, etc.) on my server eliminates an entire category of security vulnerabilities.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Take .rst input&lt;&#x2F;strong&gt;. Whenever I have a choice, I prefer to write presentations, documentation, and curriculum in &lt;a href=&quot;http:&#x2F;&#x2F;docutils.sourceforge.net&#x2F;rst.html&quot;&gt;reStructuredText&lt;&#x2F;a&gt;. In my experience, RST strikes a good balance between being supported by most documentation-generation systems and facilitating easy links, tables, and image embedding. Markdown is RST&#x27;s main competitor and looks a bit simpler in plaintext, but if you want to embed an image or table, you&#x27;re out of luck. It&#x27;s &lt;em&gt;possible&lt;&#x2F;em&gt; to get tables and images in markdown, if you don&#x27;t mind writing raw HTML by hand, but it isn&#x27;t &lt;em&gt;pleasant&lt;&#x2F;em&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Free and Open Source&lt;&#x2F;strong&gt;. Duh; if I improve it, I want to be able to share. If I love it and its maintainer gives up on it, I want to be able to keep using it anyway.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Written in a language I know or want to learn&lt;&#x2F;strong&gt;. First, because I don&#x27;t want to learn to set up the dependencies for some language that I never intend to use again. Second, because I&#x27;ll inevitably end up getting an error message, or trying to hack in some new feature, and find myself digging around in the guts of my site generator. Althought the inner workings of these things are rarely pretty, it&#x27;s less painful if I know or at least appreciate the language they&#x27;re written in.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Minimize irrelevant features for my use case&lt;&#x2F;strong&gt;. If a program&#x27;s implementation and documentation are cluttered with features that I don&#x27;t expect to ever want, the time I spend sorting support for those features from support for the parts I need will be wasted.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Must ship with templates that I like&lt;&#x2F;strong&gt;. Yes, I&#x27;m lazy. After my misadventure trying to design a site theme from scratch, I&#x27;m rebounding toward the other extreme and trying to find something that Just Works how I want it to, right out of the box.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Neutral or positive reputation among people I respect&lt;&#x2F;strong&gt;. I know, it&#x27;s not &lt;em&gt;cool&lt;&#x2F;em&gt; to admit that peer pressure exists, but it does. If everyone whose preferences I usually agree with says bad things about a particular blogging platform, I expect that it won&#x27;t be very enjoyable for me to use either, and my time invested in learning it is likely to have been wasted. A popular and widely used SSG would be a safe bet, but I wouldn&#x27;t necessarily learn anything new by deploying it. However, gaining experience with a promising but previously unheard-of tool is advantageous to my social groups, because next time someone asks for opinions, I&#x27;ll have a non-redundant review to add.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Ultimately, the feature which set Tinkerer apart from the other SSGs which meet those criteria is that it feels like a thin layer on top of Sphinx, a versatile and delightful documentation tool with which I&#x27;m already very familiar. This has allowed me to leverage my existing knowledge and get a site up in only a couple of hours, rather than fighting with an unfamiliar design paradigm.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
