<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kestrel Development</title><description>Make. Break. Repeat.</description><link>https://kesdev.com/</link><language>en-us</language><item><title>Posting Images to Bluesky from TypeScript</title><link>https://kesdev.com/posting-images-to-bluesky-from-typescript</link><guid isPermaLink="true">https://kesdev.com/posting-images-to-bluesky-from-typescript</guid><description>Special thanks to Guillermo Esteves @allencompassingtrip.com, whose Rails photoblogging CMS features a Bluesky integration which was my inspiration to build this project. Since the demise of Twitter, I’ve been posting to Bluesky. The communities that have formed there are lovely, including lots of fellow photographers, and I wanted to start sharing photos from my Photolog to Bluesky on a daily bas…</description><pubDate>Tue, 24 Dec 2024 05:44:56 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Special thanks to
&lt;a href=&quot;https://bsky.app/profile/allencompassingtrip.com&quot;&gt;Guillermo Esteves @allencompassingtrip.com&lt;/a&gt;,
whose Rails photoblogging CMS features a
&lt;a href=&quot;https://github.com/gesteves/denali/blob/release/app/lib/bluesky.rb&quot;&gt;Bluesky integration&lt;/a&gt;
which was my inspiration to build this project.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since the demise of Twitter, I’ve been posting to
&lt;a href=&quot;https://bsky.app/profile/mplewis.com&quot;&gt;Bluesky&lt;/a&gt;. The communities that have
formed there are lovely, including lots of fellow photographers, and I wanted to
start sharing photos from my &lt;a href=&quot;https://photolog.mplewis.com/&quot;&gt;Photolog&lt;/a&gt; to
Bluesky on a daily basis.
&lt;a href=&quot;https://bsky.app/profile/mplewis.com/post/3ldxj6zwm2o2a&quot;&gt;Here’s what one of those posts looks like&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The Bluesky app works like Twitter – you can post small messages and attach
images, videos, or a website embed. It’s different from Twitter in that it is
based on the AT Protocol, which makes Bluesky more like an app that runs on top
of an open database. So, instead of making a REST request to the Bluesky API,
you “insert into the database” and your post shows up in the app.&lt;/p&gt;
&lt;p&gt;My goal was to write a text post, attach an image, and run this code daily with
a random photo. AT Protocol complicates things a little bit compared to a
&lt;code&gt;POST /posts/new&lt;/code&gt; REST API in that the AT Protocol is data-oriented rather than
task-oriented. This means you have to perform the intermediate steps to
construct the &lt;em&gt;parts of a post&lt;/em&gt; before assembling them into a completed post.&lt;/p&gt;
&lt;p&gt;Since I’m using &lt;a href=&quot;http://localhost:4321/how-i-build-frontend-apps&quot;&gt;Astro&lt;/a&gt; to post
my photos, it was easy enough to update my
&lt;a href=&quot;https://github.com/mplewis/photolog/blob/main/src/sitegen/src/pages/photos.json.ts&quot;&gt;template&lt;/a&gt;
to add a &lt;a href=&quot;https://photolog.mplewis.com/photos.json&quot;&gt;&lt;code&gt;photos.json&lt;/code&gt;&lt;/a&gt; file with
content for daily posts.&lt;/p&gt;
&lt;h1 id=&quot;how-to&quot;&gt;How To&lt;/h1&gt;
&lt;p&gt;The Bluesky team has some
&lt;a href=&quot;https://docs.bsky.app/docs/tutorials/creating-a-post#images-embeds&quot;&gt;documentation&lt;/a&gt;
on posting with images, but these docs use Python and are not terribly complete,
so I wanted to share my complete and working example of the
&lt;a href=&quot;https://github.com/mplewis/photolog/blob/60e6bf5b2de74da514b29f3efbe9469d97c5fe42/src/netlify/functions/index.mts#L103-L129&quot;&gt;Node.js TypeScript code&lt;/a&gt;
I use to post to Bluesky.&lt;/p&gt;
&lt;p&gt;Here are the steps from the above code described in detail:&lt;/p&gt;
&lt;h2 id=&quot;upload-image-data-to-bluesky&quot;&gt;Upload image data to Bluesky&lt;/h2&gt;
&lt;p&gt;This results in a &lt;strong&gt;blob&lt;/strong&gt;, a bit of structured data which points to the
uploaded image. You can’t upload an image of &gt; 1 MB and you have to strip the
image metadata yourself. At this time, these are both responsibilities of the
client.&lt;/p&gt;
&lt;p&gt;To upload content to Bluesky, you use the ATP Agent with a set of credentials
(username &amp;#x26; app-specific password). Under the hood, this is performing REST
requests for you.&lt;/p&gt;
&lt;h2 id=&quot;build-the-image-metadata&quot;&gt;Build the image metadata&lt;/h2&gt;
&lt;p&gt;Construct an &lt;code&gt;app.bsky.embed.images&lt;/code&gt; object by passing the image data blob. You
have to provide the aspect ratio of your image in this part, or the image won’t
be rendered properly in the official clients.&lt;/p&gt;
&lt;h2 id=&quot;build-the-post-content&quot;&gt;Build the post content&lt;/h2&gt;
&lt;p&gt;Assemble the &lt;strong&gt;rich text&lt;/strong&gt; for the post. To add stuff like inline links,
hashtags, or @ mentions, you provide the raw string alongside
&lt;a href=&quot;https://docs.bsky.app/docs/advanced-guides/post-richtext&quot;&gt;facets&lt;/a&gt;, bits of data
which indicate a range of string characters should point to a target thing.
Bluesky’s SDK provides a
&lt;a href=&quot;https://github.com/bluesky-social/atproto/tree/main/packages/api#rich-text&quot;&gt;helper&lt;/a&gt;
to turn raw text into rich text.&lt;/p&gt;
&lt;p&gt;Finally, build an &lt;code&gt;app.bsky.feed.post&lt;/code&gt; by combining the text, facets, image
embed, and created at date. You can actually back/forward date stuff as part of
how the protocol works. Some people use this to import their posts from other
social media sites onto ATProto.&lt;/p&gt;
&lt;h2 id=&quot;post-it&quot;&gt;Post it&lt;/h2&gt;
&lt;p&gt;Use the same ATP Agent we used in the image upload step to upload this post to
Bluesky, and you’re done!&lt;/p&gt;
&lt;h1 id=&quot;posting-daily&quot;&gt;Posting Daily&lt;/h1&gt;
&lt;p&gt;To run this task daily, I created a Netlify app with one
&lt;a href=&quot;https://docs.netlify.com/functions/scheduled-functions/&quot;&gt;scheduled function&lt;/a&gt;
configured to run at 09:00 Mountain Time. I want this script to try not to
repost images that have been posted recently, but I don’t want to maintain a
database of recent posts. So I implemented the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the date of the most recently added image (the &lt;strong&gt;basis date&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Shuffle the list of images using the basis date as the RNG seed&lt;/li&gt;
&lt;li&gt;Get the distance in days between the basis date and now&lt;/li&gt;
&lt;li&gt;Pick the photo to post from the shuffled images at index
&lt;code&gt;[daysSince % imageCount]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this scheme, the script will go through all images in order, only repeating
after each image has been posted once. When I add new images – a relatively
infrequent operation – the basis date changes, the images are reshuffled, and
the script starts posting sequentially from that point. This is the only time
that there’s a risk of posting the same image twice in a row, but I’m OK with
the low chance of that happening in exchange me not having to run a database for
this project.&lt;/p&gt;</content:encoded></item><item><title>How I Build Frontend Apps</title><link>https://kesdev.com/how-i-build-frontend-apps</link><guid isPermaLink="true">https://kesdev.com/how-i-build-frontend-apps</guid><description>I’ve written a bit about how I deliver software, and now I want to share some of the details around how I build the browser side of web applications when I’m in charge of the project. Language: TypeScript My main tool is TypeScript, which I use whenever it’s feasible. I think that writing your projects in TS is the obvious best decision for the vast majority of webapps for several reasons: Just us…</description><pubDate>Fri, 20 Dec 2024 21:27:34 GMT</pubDate><content:encoded>&lt;p&gt;I’ve written a bit about &lt;a href=&quot;/how-i-ship-it&quot;&gt;how I deliver software&lt;/a&gt;, and now I
want to share some of the details around how I build the browser side of web
applications when I’m in charge of the project.&lt;/p&gt;
&lt;h1 id=&quot;language-typescript&quot;&gt;Language: TypeScript&lt;/h1&gt;
&lt;p&gt;My main tool is &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt;, which I use
whenever it’s feasible. I think that writing your projects in TS is the obvious
best decision for the vast majority of webapps for several reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Just use one language.&lt;/strong&gt; TypeScript and npm packages work great on both
frontend and backend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Great frontend/backend interop story.&lt;/strong&gt; Frameworks like
&lt;a href=&quot;https://redwoodjs.com/&quot;&gt;RedwoodJS&lt;/a&gt; provide type safety across your frontend
clients and backend APIs, and they make it easy to define templates to be
rendered at various points in your app lifecycle: pre-rendered static bits,
dynamic on content load, etc. Full-stack TS frameworks do a much better job of
updating partially dynamic content in statically rendered HTML without
requiring the entire app to be a big blob of single-page JavaScript
application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Massive ecosystem.&lt;/strong&gt; The new data format you’re trying to work with probably
has an npm package. If you’re solving something that three or more developers
have ever had to solve – structured logging, ANSI terminal colors,
authentication, data validation – npm almost certainly has a package for you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TypeScript does have a couple of drawbacks which occasionally make Go the better
language to solve some problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Poor compute concurrency.&lt;/strong&gt; Node.js runs CPU-intensive tasks in a
single-threaded event loop. If you are doing a lot of parallel computation or
managing big stacks of deferred tasks, Goroutines solve your problems better.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex runtime.&lt;/strong&gt; Setting up a TypeScript project is non-trivial: you need
to configure tsc, set up &lt;code&gt;package.json&lt;/code&gt; scripts, decide between ESModules or
CommonJS, set up linting rules and formatters… and then to run your project
on a remote machine, you need Node.js installed. The Go language comes with
those tools out of the box and compiles your program into a static binary with
no dependency requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;components-astro--react&quot;&gt;Components: Astro &amp;#x26; React&lt;/h1&gt;
&lt;p&gt;For my static sites, &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; is my framework of choice. It
comes with the
&lt;a href=&quot;https://docs.astro.build/en/reference/astro-syntax/&quot;&gt;Astro component language&lt;/a&gt;,
as well as a bunch of other helpful components which work best in Astro. For
example, the
&lt;a href=&quot;https://docs.astro.build/en/guides/images/&quot;&gt;&lt;code&gt;&amp;#x3C;Image&gt;&lt;/code&gt; and &lt;code&gt;&amp;#x3C;Picture&gt;&lt;/code&gt; components&lt;/a&gt;
make it easy to drop your images into your repo and let the framework resize
your images to the appropriate size for your target browsers. For work that
doesn’t include a lot of frontend logic, Astro provides enough for you to build
an adequate compmonent-based architecture.&lt;/p&gt;
&lt;p&gt;When I need interactivity, I add &lt;a href=&quot;https://react.dev/&quot;&gt;React&lt;/a&gt; to my Astro apps. I
first started using React in 2013 when it was released, and shortly after that,
I discovered &lt;a href=&quot;https://v1.vuejs.org/guide/index.html&quot;&gt;Vue v1&lt;/a&gt; which I found was
more effective. Vue provided “batteries included” features like state management
and styling which React wasn’t really prepared to solve yet. But as React has
grown, it’s become a real competitor: its integrations with state and styling
libraries are now excellent. The main reason I like modern React is its
functional components with &lt;a href=&quot;https://react.dev/reference/react/hooks&quot;&gt;hooks&lt;/a&gt;.
With these tools, I find it very easy to reason about the data flow through a
function which returns a JSX blob which ends up in my page.&lt;/p&gt;
&lt;h1 id=&quot;state-management-jotai&quot;&gt;State Management: Jotai&lt;/h1&gt;
&lt;p&gt;Sometimes passing state up and down from component to parent and back gets
cluttered and unwieldy. &lt;a href=&quot;https://jotai.org/&quot;&gt;Jotai&lt;/a&gt; is a minimal library which
provides &lt;em&gt;atoms,&lt;/em&gt; singletons that encapsulate a piece of stateful data. Atoms
are defined globally and can be used throughout your app, allowing you to
teleport important data wherever it needs to appear in the DOM. It also supports
&lt;a href=&quot;https://jotai.org/docs/utilities/storage&quot;&gt;storage&lt;/a&gt;, so you can use the atomic
paradigm to persist data such as session tokens between page loads. Most
importantly, it works very well with React via the
&lt;a href=&quot;https://jotai.org/docs/core/use-atom&quot;&gt;&lt;code&gt;useAtom&lt;/code&gt; hook&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;styling-tailwind&quot;&gt;Styling: Tailwind&lt;/h1&gt;
&lt;p&gt;To make my UI usable, I use &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt;, a CSS
framework with great tooling based on utility classes. In the past, I’ve used
&lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt; to build my apps. It’s a great CSS framework with
opinions included, but eventually I find that I want more flexibility over my
design language, and Tailwind provides this for me.&lt;/p&gt;
&lt;p&gt;Using Tailwind for your styling brings many advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Comprehensive coverage.&lt;/strong&gt; An extensive
&lt;a href=&quot;https://tailwindcss.com/docs/installation&quot;&gt;docs library&lt;/a&gt; includes every
single CSS feature, from flexbox to grid to &lt;code&gt;max-width&lt;/code&gt; containers with
screen-sized breakpoints. With a well-selected library of
&lt;a href=&quot;https://tailwindcss.com/docs/customizing-colors&quot;&gt;named custom colors&lt;/a&gt;, you
may not even have to open a hex picker.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skip writing CSS.&lt;/strong&gt; No, really – 99% of what you need to do in your app can
be done by adding classes to an HTML element. You mostly don’t need to write
CSS by hand to implement a desired design.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Editor integration and linting.&lt;/strong&gt; VSCode can help you autocomplete valid
Tailwind classes and warn you when you’re making mistakes like overwriting
Y-axis padding values &lt;code&gt;py-3&lt;/code&gt; with &lt;code&gt;py-4&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theming and plugins.&lt;/strong&gt; Tailwind lets you customize the class names available
for use by configuring the &lt;a href=&quot;https://tailwindcss.com/docs/theme&quot;&gt;theme&lt;/a&gt;. Your
custom names are available across your class: creating &lt;code&gt;fontFamily.funky&lt;/code&gt;
automatically creates the &lt;code&gt;text-funky&lt;/code&gt; class, even in your IDE’s autocomplete.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performant builds.&lt;/strong&gt; Tailwind automatically optimizes its production build
to only include the class definitions which are directly relevant to your
application. If you don’t use a feature, it isn’t included in the final CSS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also adds a few complications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Component systems are required.&lt;/strong&gt; Copying &lt;code&gt;px-2 py-3 my-4&lt;/code&gt; into all of your
&lt;code&gt;&amp;#x3C;Button&gt;&lt;/code&gt;s is tedious at best and a source of mistakes at worst. If you’re
using a system that is primarily doing server-side HTML templating and makes
it hard for you to compose your app out of components, using only CSS utility
classes may create more problems than it solves.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build system integration.&lt;/strong&gt; You need to configure Tailwind to work with your
app’s build system. If you have to do this by hand, you might be in for pain.
Luckily,
&lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/tailwind/&quot;&gt;Astro&lt;/a&gt; and
&lt;a href=&quot;https://docs.redwoodjs.com/docs/tutorial/intermission/#using-your-current-codebase&quot;&gt;RedwoodJS&lt;/a&gt;
both include plugins for Tailwind support.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>How I Ship It</title><link>https://kesdev.com/how-i-ship-it</link><guid isPermaLink="true">https://kesdev.com/how-i-ship-it</guid><description>I just migrated my blog from Ghost to Astro. I realized I haven’t posted something new in two years, so I thought I’d take this opportunity to share how I work on software in my personal time. Preamble Until recently, I was hosting my blog using self-hosted Ghost on my personal Kubernetes cluster. I like Ghost a lot, but the reasons I picked it originally were: it has a web editor, and it’s not Wo…</description><pubDate>Thu, 19 Dec 2024 07:05:21 GMT</pubDate><content:encoded>&lt;p&gt;I just migrated my blog from &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt; to
&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. I realized I haven’t posted something new in two
years, so I thought I’d take this opportunity to share how I work on software in
my personal time.&lt;/p&gt;
&lt;h1 id=&quot;preamble&quot;&gt;Preamble&lt;/h1&gt;
&lt;p&gt;Until recently, I was hosting my blog using self-hosted Ghost on my personal
Kubernetes cluster. I like Ghost a lot, but the reasons I picked it originally
were: it has a web editor, and it’s not Wordpress. I originally paid for hosted
Ghost Pro just to get started, but eventually I set up a self-hosted MySQL
database via the &lt;a href=&quot;https://github.com/cybozu-go/moco&quot;&gt;moco operator&lt;/a&gt;, which let
me save some money on hosting.&lt;/p&gt;
&lt;p&gt;Moco is high-quality software, it works reliably (even backup and restore), and
the team writes high-quality documentation. But it’s pre-1.0 and I found myself
going through difficult upgrade processes more than once, so I’ve been looking
for an excuse to move back to a static site generator.&lt;/p&gt;
&lt;p&gt;These days, most of my personal web work is done in Astro, so that’s where I
migrated my blog content. Welcome to the new site!&lt;/p&gt;
&lt;h1 id=&quot;my-principles&quot;&gt;My Principles&lt;/h1&gt;
&lt;p&gt;These days, I feel like I already spend too much time maintaining and thinking
about my home servers. I want to reclaim some of my time by simplifying the way
I develop and ship software, and these principles help me get there.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automation saves time:&lt;/strong&gt; If you plug the repo straight into CI, you can push
to deploy. If you don’t automate this process, your deploy script will soon
break, and then you’ll have to fix that instead of deploying your feature.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shorten the minimum loop:&lt;/strong&gt; I only program for fun when a project is already
loaded into my brain. If I put down a high-friction project for a while, I
might never pick it back up. Choosing simpler frameworks means the minimum
loop – the amount of time it takes to pick up a project, write a minimal
feature, and deploy to production – stays shorter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stay frugal:&lt;/strong&gt; A project that consumes no resources produces no stress.
Modern platform hosts have generous free tiers within which you can easily
remain if you make the right technical decisions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use patterns:&lt;/strong&gt; Prefer the same sets of tools for the same tasks. Pick the
thing that works, not the different thing that’s shiny. If you solve a
problem, solve it categorically. If you see something three or more times,
it’s time to make it a template.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;my-tech-stack&quot;&gt;My Tech Stack&lt;/h1&gt;
&lt;p&gt;I have some redundancy in my tech stack, but not much. Let’s discuss:&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;#x22;src&amp;#x22;:&amp;#x22;../images/tech-stack.png&amp;#x22;,&amp;#x22;alt&amp;#x22;:&amp;#x22;Tech Stack&amp;#x22;,&amp;#x22;index&amp;#x22;:0}&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;infrastructure-as-code-pulumi&quot;&gt;Infrastructure as Code: Pulumi&lt;/h2&gt;
&lt;p&gt;I write a lot of Terraform in my day job and I don’t love it. The outcomes from
managing infrastructure using code are excellent, but defining complicated
configurations in HCL is a pain. &lt;a href=&quot;https://www.pulumi.com/&quot;&gt;Pulumi&lt;/a&gt; embraces the
typed programming language and allows you to define your software stack in
TypeScript. As a developer, this means you get high-quality linting in your
systems &lt;em&gt;before&lt;/em&gt; your apply operation fails. It’s also a natural fit for
&lt;em&gt;composing&lt;/em&gt; your stack together: it is trivial to extract an existing app’s
hosting config (i.e. compute + ingress + DNS) into a builder function which acts
as a &lt;em&gt;template&lt;/em&gt; you can use to host all your other apps in the same spot.&lt;/p&gt;
&lt;h2 id=&quot;domains-and-dns-porkbun&quot;&gt;Domains and DNS: Porkbun&lt;/h2&gt;
&lt;p&gt;After some poor experiences in the past with domain registrars that don’t
respect their customers, I’ve moved all my domains to
&lt;a href=&quot;https://porkbun.com/&quot;&gt;Porkbun&lt;/a&gt; where I’m very happy. Their prices are
competitive, their DNS service is reliable, and they are my favorite no-nonsense
place to buy a .com.&lt;/p&gt;
&lt;h2 id=&quot;code-hosting-and-ci-github&quot;&gt;Code hosting and CI: GitHub&lt;/h2&gt;
&lt;p&gt;GitHub is the unrivaled home of open source, and it’s where
&lt;a href=&quot;https://github.com/mplewis&quot;&gt;my software&lt;/a&gt; lives. GitHub Actions is perhaps not
the cheapest CI/CD solution, but it’s well-integrated and reasonably painless
for my automated testing and deployment needs. I stay in the free tier.&lt;/p&gt;
&lt;h2 id=&quot;static-and-serverless-hosting-netlify&quot;&gt;Static and serverless hosting: Netlify&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; has long been a straightforward way to turn
a Git repo into a hosted website, including any optional build step. But their
offerings have advanced dramatically since they first launched, and now they are
a formidable host for serverless apps. They repackage the reliable AWS Lambda at
very reasonable per-request prices with a generous free tier.&lt;/p&gt;
&lt;p&gt;Serverless platforms manage all of your compute infrastructure for you, charging
you per request and per execution second rather than charging you to rent a
server by the month. These days, if I can build my app into a serverless
paradigm, I do – they are extremely cheap to free on the low end and nearly
zero-maintenance. RedwoodJS and Astro are two frameworks that help me accomplish
this, and they both deploy natively to Netlify.&lt;/p&gt;
&lt;h2 id=&quot;dynamic-apps-redwoodjs&quot;&gt;Dynamic apps: RedwoodJS&lt;/h2&gt;
&lt;p&gt;Ruby on Rails has long been a powerful solution for booting up your new
startup’s codebase, but what if you’re trying to avoid running persistent
servers that need to be always-on to handle requests? And what if you’re looking
to take advantage of more modern paradigms for integrating your frontend and
backend code?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://redwoodjs.com/&quot;&gt;RedwoodJS&lt;/a&gt; is a framework that tackles the approximate
scope of vanilla Rails or Django: it has relational database models and
migrations, it renders HTML, and it ships JS to your browser. It also goes
beyond: the frontend and backend aspects are tightly integrated via
&lt;a href=&quot;https://docs.redwoodjs.com/docs/tutorial/chapter2/cells/&quot;&gt;Cells&lt;/a&gt;, React
components which handle the data lifecycle for you. And with
&lt;a href=&quot;https://redwoodjs.com/blog/rsc-now-in-redwoodjs&quot;&gt;RSC&lt;/a&gt; landing in the framework
soon, it will be even easier for you to simply ask for the data you need and
have it magically arrive in your UI.&lt;/p&gt;
&lt;p&gt;I am excited about the future of Redwood. The leadership is focused on building
features that help me be more productive. They have prioritized non-breaking
upgrade paths on their journey from v1 to v8, something that has caused me no
end of grief with Rails. And they are transparent about their roadmap and
development priorities, which gives me confidence that the app I build today can
be even better tomorrow.&lt;/p&gt;
&lt;h2 id=&quot;static-sites-astro&quot;&gt;Static sites: Astro&lt;/h2&gt;
&lt;p&gt;Not everything needs a SQL database. What if your project is, say,
&lt;a href=&quot;http://kesdev.com&quot;&gt;a blog&lt;/a&gt;? What if it’s a
&lt;a href=&quot;https://kqmunity.com/&quot;&gt;community hub&lt;/a&gt;? And what if you want to use tools like
React to build a component hierarchy without requiring your users to run some JS
just to see your text on a page?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; is a framework for building high-performance
static sites that also supports dynamic behavior. By default, anything you build
in Astro is rendered at &lt;em&gt;compile time&lt;/em&gt; into static HTML – no JS is sent to the
browser! But for interactive bits, you can add
&lt;a href=&quot;https://docs.astro.build/en/concepts/islands/&quot;&gt;islands&lt;/a&gt; of interactivity, and
you can even slot server-rendered content directly into an otherwise-static
page.&lt;/p&gt;
&lt;p&gt;With support for React, MDX, Tailwind, and RSS via first-party addons, the Astro
team has shown their priority is to give you the tools you need to build the
site you want. Astro is the best fit for most of my content today, and I am
excited to continue using it as it grows.&lt;/p&gt;
&lt;h2 id=&quot;serverless-friendly-db-neon&quot;&gt;Serverless-friendly DB: Neon&lt;/h2&gt;
&lt;p&gt;Postgres databases are expensive, especially if you’re not using the database
24/7. My small apps are well-served by &lt;a href=&quot;https://neon.tech/&quot;&gt;Neon&lt;/a&gt;, a “serverless
Postgres” platform. What’s a serverless database, exactly? This means that Neon
is happy to serve individual requests from my Netlify function requests, and
they manage the complexities of shared DB hosting for me, including the
pgbouncer instance.&lt;/p&gt;
&lt;p&gt;When a new request comes in, they quickly boot my database up and serve the
request, and when it’s idle for 10 minutes, they suspend it and stop charging
me. This makes Neon extremely cost-efficient for my side projects which may be
only occasionally used by a few people, but should still be highly avilable.
It’s a match made in heaven for dynamic apps hosted on Netlify.&lt;/p&gt;
&lt;h2 id=&quot;cloud-services-aws&quot;&gt;Cloud services: AWS&lt;/h2&gt;
&lt;p&gt;There are some categories of services I am not interested in hosting right now.
I don’t want to spend my time trudging toward a reliable, redundant setup for
blob storage, hosted databases with recovery, or reliable email delivery. When I
need these things, I am happy to pay AWS, the cloud provider that has
historically been excellent for long-term support. I can be confident that the
services I’m using today won’t be taken offline in six months.&lt;/p&gt;
&lt;h2 id=&quot;general-purpose-hosting-kubernetes&quot;&gt;General-purpose hosting: Kubernetes&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; is for everyone, including you! If your
goal is to build a production-quality service, featuring rolling deployments,
redundant compute hosting, and several nines of uptime, Kubernetes solves these
problems in a way that’s accessible if not immediately friendly.&lt;/p&gt;
&lt;h2 id=&quot;managed-kubernetes-and-dns-digitalocean&quot;&gt;Managed Kubernetes and DNS: DigitalOcean&lt;/h2&gt;
&lt;p&gt;DigitalOcean’s
&lt;a href=&quot;https://www.digitalocean.com/products/kubernetes&quot;&gt;managed Kubernetes&lt;/a&gt; offering
is reliable and extremely affordable. You pay the sticker price for data plane
Droplets in your cluster and the managed control plane is free.&lt;/p&gt;
&lt;p&gt;I use Pulumi to provision my k8s cluster, connect it to DO public load
balancers, set up DNS entries programmatically via DO networking, and request
SSL certificates for my apps using
&lt;a href=&quot;https://cert-manager.io/docs/&quot;&gt;Cert Manager&lt;/a&gt;. Now, putting a Docker image on
the public internet &lt;em&gt;is&lt;/em&gt; my happy path.&lt;/p&gt;</content:encoded></item><item><title>How to Ask a Good Question</title><link>https://kesdev.com/how-to-ask-a-good-question</link><guid isPermaLink="true">https://kesdev.com/how-to-ask-a-good-question</guid><description>When you have a problem with a software project or library, often the best way to ask for help is through text. Different projects offer support in different ways, such as email, chat rooms, or GitHub issues. Most of the time, the people helping you out are unpaid volunteers, so it&apos;s polite to respect their time by structuring your question well up front. Here&apos;s an example of a bad question:&amp;lt;ne…</description><pubDate>Thu, 08 Sep 2022 21:24:28 GMT</pubDate><content:encoded>&lt;p&gt;When you have a problem with a software project or library, often the best way to ask for help is through text. Different projects offer support in different ways, such as email, chat rooms, or GitHub issues. Most of the time, the people helping you out are unpaid volunteers, so it&apos;s polite to respect their time by structuring your question well up front. &lt;/p&gt;&lt;p&gt;Here&apos;s an example of a bad question:&lt;/p&gt;&lt;blockquote&gt;&amp;#x3C;newbiecoder123&gt; why doesnt this work??&lt;/blockquote&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/09/image-11.png&quot; class=&quot;kg-image&quot; alt=&quot;Screenshot of VSCode with two panes of code open and a terminal with an error message of some kind&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1269&quot;&gt;&lt;/figure&gt;&lt;p&gt;It&apos;s really hard to understand what&apos;s going on here! The user hasn&apos;t told us what they&apos;re trying to do or what the issue is, the screenshot is hard to read, and we don&apos;t have enough information to help them. It&apos;s going to be very hard to help this person figure out what&apos;s wrong.&lt;/p&gt;&lt;p&gt;Here&apos;s how to ask a good question:&lt;/p&gt;&lt;h1 id=&quot;structure&quot;&gt;Structure&lt;/h1&gt;&lt;h2 id=&quot;what-are-you-trying-to-do&quot;&gt;What are you trying to do?&lt;/h2&gt;&lt;p&gt;Start by summarizing your problem &lt;em&gt;briefly. &lt;/em&gt;Keep this to one sentence. For example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&quot;I&apos;m trying to make a POST request to a web server.&quot;&lt;/li&gt;&lt;li&gt;&quot;I&apos;m trying to run the application locally.&quot;&lt;/li&gt;&lt;li&gt;&quot;I&apos;m running into a bug and I&apos;m not sure if it&apos;s my fault.&quot;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Often, we forget to give people the full context of what we&apos;re trying to do. Saying this up front helps a volunteer understand what you want to do.&lt;/p&gt;&lt;h2 id=&quot;what-have-you-attempted&quot;&gt;What have you attempted?&lt;/h2&gt;&lt;p&gt;If you&apos;re inexperienced with a tool, it&apos;s quite possible you&apos;re using it wrong. Complex tools often have multiple ways to use them, or subtle ways in which they can be misconfigured. State what you&apos;ve tried so that the helpers know what they can skip past. For example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&quot;I used this example from the documentation:&quot; (with a link to the docs and example in question)&lt;/li&gt;&lt;li&gt;&quot;I ran this command in my terminal:&quot; (with the command you used)&lt;/li&gt;&lt;li&gt;&quot;Here is the code I am running:&quot; (with a code snippet)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;When you&apos;re asking for help with a programming language or library, make sure to &lt;strong&gt;include the code&lt;/strong&gt; that is causing the problem. The secret benefit of including readable code is that it &lt;a href=&quot;https://xkcd.com/356/&quot;&gt;nerd snipes&lt;/a&gt; people into mentally engaging with your problem, so they&apos;ll probably help you out sooner rather than later.&lt;/p&gt;&lt;p&gt;If you include a code snippet, try to cut it down to the smallest block of code which demonstrates your issue. If you post multiple pages of code, people tend to ignore you; if you only post ten lines, it&apos;s much easier for someone to see what you&apos;re trying to do.&lt;/p&gt;&lt;h2 id=&quot;what-happened&quot;&gt;What happened?&lt;/h2&gt;&lt;p&gt;Tell the volunteers what &lt;em&gt;actually happened&lt;/em&gt; when you tried to use this tool. This could be something like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;an error message from your terminal&lt;/li&gt;&lt;li&gt;a stack trace from a program crash&lt;/li&gt;&lt;li&gt;a screenshot of the issue (only if you can&apos;t copy and paste the text)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Try to give people the full error message, but omit extraneous information that you don&apos;t think is related to the crash.&lt;/p&gt;&lt;p&gt;When possible, prefer copying and pasting text into a code block rather than attaching a screenshot. Screenshots are not accessible – they do not render properly on some mobile devices, and they cannot be easily read by screen readers.&lt;/p&gt;&lt;h2 id=&quot;what-did-you-want-to-happen&quot;&gt;What did you want to happen?&lt;/h2&gt;&lt;p&gt;Now tell the volunteers your &lt;em&gt;expected behavior.&lt;/em&gt; This is what you want to happen, and what you believe should reasonably be happening given the code you posted. This could be that:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;you didn&apos;t expect an error to occur&lt;/li&gt;&lt;li&gt;you expected an action to complete&lt;/li&gt;&lt;li&gt;you expected a program to start sucecssfully&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Often, this is obvious, but sometimes you might be forgetting to include context because you take the goal for granted. If a volunteer doesn&apos;t know what you&apos;re trying to do, it&apos;s hard for them to help you get there. So make sure to include this part.&lt;/p&gt;&lt;h2 id=&quot;complete-example&quot;&gt;Complete example&lt;/h2&gt;&lt;p&gt;Here&apos;s what a well-asked question looks like:&lt;/p&gt;&lt;blockquote&gt;Hello! I&apos;m trying to start a Node web server using this code:&lt;br&gt;...snippet of TypeScript code...&lt;br&gt;&lt;br&gt;I have tried to start it by running &lt;code&gt;node index.js&lt;/code&gt; in my terminal.&lt;br&gt;&lt;br&gt;However, when I run &lt;code&gt;node index.js&lt;/code&gt; command to start the server, I get this error message:&lt;br&gt;...snippet of a stack trace...&lt;br&gt;&lt;br&gt;What I expect to happen is that my webserver starts on port 8000 and answers requests.&lt;br&gt;&lt;br&gt;Does anyone have experience with this error who could help me?&lt;/blockquote&gt;&lt;h1 id=&quot;etiquette&quot;&gt;Etiquette&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Don&apos;t demand help.&lt;/strong&gt; You&apos;re using free, open-source software – you get the support level that you pay for. Most of the time, authors and experts want to help you have a good experience with their software. They&apos;re volunteering to help you – they don&apos;t owe you anything.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Don&apos;t be bothersome.&lt;/strong&gt; Pings can be disruptive to peoples&apos; focus or work days. Often, volunteers are helping people out in their spare time between other important things in their life. If someone doesn&apos;t get back to you for a while, they may be away from their computer, or busy, or they may have gone to sleep. In general, asynchronous, slow notifications such as email or a GitHub @-tag are OK; instant pings such as a Slack or Discord @-tag are extremely rude.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Be polite and appreciative.&lt;/strong&gt; These are unpaid &lt;em&gt;volunteers,&lt;/em&gt; and you&apos;re getting &lt;em&gt;free help&lt;/em&gt; from them! To pay them back for the time and effort they&apos;re spending with you, speak politely and express gratitude. &lt;em&gt;Hello, please, &lt;/em&gt;and &lt;em&gt;thank you&lt;/em&gt; go a long way, and they help ensure other volunteers will want to help you in the future too.&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;tips&quot;&gt;Tips&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://dontasktoask.com/&quot;&gt;Don&apos;t ask to ask.&lt;/a&gt; &lt;a href=&quot;https://nohello.net/en/&quot;&gt;Just ask.&lt;/a&gt; &lt;/strong&gt;A help channel is full of folks who are ready to help. Simply go ahead and ask your question, and if you&apos;re not in the right place, folks will redirect you to the right place.&lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title>My blog now lives on Gemini, too</title><link>https://kesdev.com/my-blog-now-lives-on-gemini-too</link><guid isPermaLink="true">https://kesdev.com/my-blog-now-lives-on-gemini-too</guid><description>Outdated: Since writing this post, I’ve migrated my blog off Ghost onto Astro, a static site framework. I haven’t updated my Gemini integration to point to the new blog, and this post was a bit mangled in the migration process. But this content might still be useful to you, and if you want to chat about Gemini, email me! Here is the source code for this post. If you&apos;re already on Gemini, you can v…</description><pubDate>Tue, 23 Aug 2022 02:35:15 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;strong&gt;Outdated:&lt;/strong&gt; Since writing this post, I’ve &lt;a href=&quot;/how-i-ship-it&quot;&gt;migrated&lt;/a&gt; my blog
off Ghost onto Astro, a static site framework. I haven’t updated my Gemini
integration to point to the new blog, and this post was a bit mangled in the
migration process. But this content might still be useful to you, and if you
want to chat about Gemini, &lt;a href=&quot;mailto:matt@mplewis.com&quot;&gt;email me&lt;/a&gt;! Here is the
&lt;a href=&quot;https://github.com/mplewis/kesdev-blog/blob/main/src/content/posts/2022-08-22-my-blog-now-lives-on-gemini-too.md&quot;&gt;source code&lt;/a&gt;
for this post.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If you&apos;re already on Gemini, you can &lt;a href=&quot;gemini://kesdev.com&quot;&gt;visit my blog on Gemini&lt;/a&gt; right now!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gemini.circumlunar.space/&quot;&gt;Project Gemini&lt;/a&gt; (&lt;a href=&quot;gemini://gemini.circumlunar.space/&quot;&gt;Gemini link&lt;/a&gt;) is an exciting new project! It aims to bring together some of the best parts of Gopher and the web:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Content-focused: no JavaScript, no pop-ups, no &lt;em&gt;images&lt;/em&gt;&lt;/li&gt;&lt;li&gt;Privacy-oriented: no cookies, no non-consensual tracking&lt;/li&gt;&lt;li&gt;Mandatory TLS, with &lt;em&gt;client certificate support &lt;/em&gt;for identity management&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The &lt;a href=&quot;https://gemini.circumlunar.space/docs/specification.gmi&quot;&gt;specification&lt;/a&gt; (&lt;a href=&quot;gemini://gemini.circumlunar.space/docs/specification.gmi&quot;&gt;Gemini link&lt;/a&gt;) is rather small for a web protocol at only ~5200 words. I found the spec easy to read and understand. It declares an extremely basic content format which sort of looks like a stripped-down version of Markdown.&lt;/p&gt;&lt;p&gt;I&apos;m always excited for new global communication technologies, and Gemini seems like a friendlier version of the Internet – one where Facebook pixels aren&apos;t threatening to aggregate our activity at every turn. So I wanted to start hosting some interesting projects on Gemini, starting with this blog.&lt;/p&gt;&lt;h1 id=&quot;approach&quot;&gt;Approach&lt;/h1&gt;&lt;p&gt;The approach I took was to host my content on &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt;, my favorite lightweight blog platform which runs on Node and features an excellent WYSIWYG CMS. Ghost provides a &lt;a href=&quot;https://ghost.org/docs/content-api/&quot;&gt;Content API&lt;/a&gt; which exposes all of my blog&apos;s content and its metadata. I wrote an application called &lt;a href=&quot;https://github.com/mplewis/ghostini&quot;&gt;Ghostini&lt;/a&gt; to read data from that API and serve it. I used the &lt;a href=&quot;https://github.com/LukeEmmet/html2gemini&quot;&gt;html2gemini&lt;/a&gt; library to convert from my Ghost HTML to Gemini text.&lt;/p&gt;&lt;p&gt;I host my Ghost blog inside my personal Kubernetes cluster, which runs on &lt;a href=&quot;https://www.digitalocean.com/products/kubernetes&quot;&gt;DigitalOcean&lt;/a&gt;. Traffic enters my cluster via a DigitalOcean Load Balancer, and I route it using &lt;a href=&quot;https://traefik.io/solutions/kubernetes-ingress/&quot;&gt;Traefik&lt;/a&gt; to handle my Kubernetes ingress.&lt;/p&gt;&lt;h1 id=&quot;routing&quot;&gt;Routing&lt;/h1&gt;&lt;p&gt;Most of my apps are HTTP/REST apps, which Traefik and Kubernetes handle as first-party citizens. This means they can natively and easily route requests to the right host by reading the &lt;code&gt;Host&lt;/code&gt; HTTP header. However, Gemini doesn&apos;t use HTTP headers, or any headers at all. A Gemini request contains only the URL and looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;gemini://kesdev.com/&amp;#x3C;CR&gt;&amp;#x3C;LF&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To use Gemini properly, the Gemini server must terminate the TLS connection. This allows it to read the client certificate, which it needs if it wants to do anything with the client&apos;s identity. This means that we can&apos;t MITM the TLS connection, by having Traefik terminate and restarting it, without losing the client identity. We have to do &lt;a href=&quot;https://www.parallels.com/blogs/ras/ssl-passthrough/&quot;&gt;TLS passthrough&lt;/a&gt;, forwarding the encrypted connection directly to the Gemini server without reading it.&lt;/p&gt;&lt;p&gt;But if we can&apos;t read the message, we can&apos;t see the absolute hostname inside and know that this request is for &lt;code&gt;kesdev.com&lt;/code&gt;. So we have to take advantage of the TLS extension called &lt;a href=&quot;https://www.wikiwand.com/en/Server_Name_Indication&quot;&gt;Server Name Indication&lt;/a&gt; (SNI). Compatible clients can send the name of their target host (the Server Name) at the TLS &lt;code&gt;ClientHello&lt;/code&gt; step, which we&apos;re able to read before continuing with TLS negotiation. This lets our Traefik TCP router understand where this request should go and route it there without terminating TLS.&lt;/p&gt;&lt;p&gt;I&apos;m lightly familiar with TLS, but learning enough of this to make this work with my k8s + Traefik setup took me three entire days of work. So I&apos;d like to share a basic configuration that I wish I had had when I was starting this endeavor.&lt;/p&gt;&lt;h1 id=&quot;configuration&quot;&gt;Configuration&lt;/h1&gt;&lt;p&gt;My cluster uses Traefik 2.6.1. Below is a Kubernetes manifest which does the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;starts an &lt;code&gt;nginx&lt;/code&gt; deployment and service in your cluster&lt;/li&gt;&lt;li&gt;configures nginx to listen on port 443 and terminate TLS using a &lt;code&gt;localhost&lt;/code&gt; certificate&lt;/li&gt;&lt;li&gt;configures Traefik to route requests on the &lt;code&gt;websecure&lt;/code&gt; &lt;a href=&quot;https://doc.traefik.io/traefik/routing/entrypoints/&quot;&gt;entrypoint&lt;/a&gt; for &lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;example.com&lt;/code&gt; to the &lt;code&gt;nginx&lt;/code&gt; service using an &lt;a href=&quot;https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/#kind-ingressroutetcp&quot;&gt;IngressRouteTCP&lt;/a&gt; with &lt;a href=&quot;https://doc.traefik.io/traefik/routing/routers/#passthrough&quot;&gt;TLS passthrough&lt;/a&gt; enabled&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This should be enough to get you to a point where you can port-forward into your &lt;code&gt;traefik&lt;/code&gt; service on the &lt;code&gt;websecure&lt;/code&gt; port. Then you should be able to visit &lt;a href=&quot;https://localhost:1443&quot;&gt;https://localhost:1443&lt;/a&gt; to see a valid TLS connection (with a self-signed cert).&lt;/p&gt;&lt;h2 id=&quot;verification&quot;&gt;Verification&lt;/h2&gt;&lt;p&gt;Here&apos;s how I tested this using kubectl and &lt;a href=&quot;https://httpie.io/&quot;&gt;httpie&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;kubectl apply -f nginx.yaml
kubectl port-forward service/traefik 1443:443
https get localhost:1443 --verify=no&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can verify that Traefik is routing TCP with passthrough, not offloading, by visiting in your browser and checking that the server certificate signature in your browser matches the SHA256 signature for the certificate in the configmap:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/08/image.png&quot; class=&quot;kg-image&quot; alt=&quot;Screenshot of my Firefox server certificate inspector, showing that the SHA-256 fingerprint matches the expected fingerprint for nginx TLS termination&quot; loading=&quot;lazy&quot; width=&quot;840&quot; height=&quot;1143&quot;&gt;&lt;/figure&gt;&lt;pre&gt;&lt;code&gt;B1:71:61:6F:A9:62:44:4C:78:84:B0:A9:4D:6C:AB:51:4E:8B:EC:AB:06:A8:7C:F3:FC:C4:63:EE:71:1D:9E:A9&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;kubernetes-manifest&quot;&gt;Kubernetes manifest&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      volumes:
        - name: nginx-configs
          configMap:
            name: nginx-configs
        - name: nginx-certs
          configMap:
            name: nginx-certs
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - name: https
              containerPort: 443
          volumeMounts:
            - name: nginx-configs
              mountPath: /etc/nginx/conf.d
            - name: nginx-certs
              mountPath: /tmp/certs
&lt;hr&gt;
&lt;p&gt;apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 443
targetPort: 443&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: nginx
spec:
entryPoints:
- websecure
tls:
passthrough: true
routes:
- match: HostSNI(&lt;code&gt;localhost&lt;/code&gt;)
services:
- name: nginx
port: 443&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-certs
data:&lt;/p&gt;
&lt;h1 id=&quot;openssl-x509--noout--fingerprint--sha256--inform-pem--in-localhostcrt&quot;&gt;openssl x509 -noout -fingerprint -sha256 -inform pem -in localhost.crt&lt;/h1&gt;
&lt;h1 id=&quot;sha256-fingerprintb171616fa962444c7884b0a94d6cab514e8becab06a87cf3fcc463ee711d9ea9&quot;&gt;SHA256 Fingerprint=B1:71:61:6F:A9:62:44:4C:78:84:B0:A9:4D:6C:AB:51:4E:8B:EC:AB:06:A8:7C:F3:FC:C4:63:EE:71:1D:9E:A9&lt;/h1&gt;
&lt;p&gt;localhost.crt: |
-----BEGIN CERTIFICATE-----
MIIBSzCB8qADAgECAhEAxReivp6Xurv1VFFia/KbrTAKBggqhkjOPQQDAjAUMRIw
EAYDVQQDEwlsb2NhbGhvc3QwHhcNMjIwMjAzMDUyODQ3WhcNMzIwMjAzMDUyODQ3
WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AARtJ3ZPKaPpmyUz4Lt5r7UgDUsa5vjiDKQeh3UX0DIlIKywO1S5k0IUrnFOlrdf
RLmBK4BqpEi8IMAHOGhwwQ5WoyUwIzAhBgNVHREEGjAYgglsb2NhbGhvc3SCCyou
bG9jYWxob3N0MAoGCCqGSM49BAMCA0gAMEUCIGTSuMSaShxZ4HQLnN7cQz+s/vG5
uyTmMI0WZL+MDLsoAiEA/TYIzjxzbFVPkU8+uD2TXlidlk1kib+eGcZ45DObPc0=
-----END CERTIFICATE-----
localhost.key: |
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQglzpLGw4kdm8tCuoe
LgmkatGPo7p0DXgy4szovoYEswChRANCAARtJ3ZPKaPpmyUz4Lt5r7UgDUsa5vji
DKQeh3UX0DIlIKywO1S5k0IUrnFOlrdfRLmBK4BqpEi8IMAHOGhwwQ5W
-----END PRIVATE KEY-----&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configs
data:
https-only.conf: |
server {
listen 443 ssl http2;
ssl_certificate /tmp/certs/localhost.crt;
ssl_certificate_key /tmp/certs/localhost.key;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    server_name  localhost;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    location / {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        root   /usr/share/nginx/html;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        index  index.html index.htm;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    error_page   500 502 503 504  /50x.html;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    location = /50x.html {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        root   /usr/share/nginx/html;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&amp;#x3C;/code&gt;&amp;#x3C;/pre&gt;&amp;#x3C;h1 id=&quot;conclusion&quot;&gt;Conclusion&amp;#x3C;/h1&gt;&amp;#x3C;p&gt;I spent a long time trying to get this to work because I wanted the ability to host Gemini apps in my Kubernetes cluster. I&apos;m very happy to have it working, and I hope that what I&apos;ve learned saves you some time if you try this for yourself! I look forward to hosting more public Gemini apps.&amp;#x3C;/p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Building multi-architecture Docker images on CircleCI</title><link>https://kesdev.com/building-multi-architecture-docker-images-on-circleci</link><guid isPermaLink="true">https://kesdev.com/building-multi-architecture-docker-images-on-circleci</guid><description>I spent a couple of hours today working to build my Docker images for ARM and x64 machines in my hosted CI provider, CircleCI. Here is the solution I came up with. I hope this saves you some time if you have to do the same thing in the future.This solution:builds ARM and x64 imagesruns on commits on the main branch of your repopushes your_app:latest and your_app:&amp;lt;1234567&amp;gt; image tags to Docke…</description><pubDate>Sat, 20 Aug 2022 22:35:42 GMT</pubDate><content:encoded>&lt;p&gt;I spent a couple of hours today working to build my Docker images for ARM and x64 machines in my hosted CI provider, CircleCI. Here is the solution I came up with. I hope this saves you some time if you have to do the same thing in the future.&lt;/p&gt;&lt;p&gt;This solution:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;builds ARM and x64 images&lt;/li&gt;&lt;li&gt;runs on commits on the &lt;code&gt;main&lt;/code&gt; branch of your repo&lt;/li&gt;&lt;li&gt;pushes &lt;code&gt;your_app:latest&lt;/code&gt; and &lt;code&gt;your_app:&amp;#x3C;1234567&gt;&lt;/code&gt; image tags to Docker Hub (where &lt;code&gt;&amp;#x3C;1234567&gt;&lt;/code&gt; are the first 7 characters of the commit SHA)&lt;/li&gt;&lt;li&gt;if the commit is tagged, pushes &lt;code&gt;your_app:&amp;#x3C;commit_tag&gt;&lt;/code&gt; as well&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;kg-card kg-code-card&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: 2.1
&lt;p&gt;jobs:
build:
# We must use a machine image to get multi-arch support;
# this doesn’t work inside a Docker image
resource_class: medium
machine:
image: ubuntu-2204:2022.07.1
steps:
- checkout
- run:
# Configure the “Docker Hub” for your organization, and set the DOCKER_LOGIN and DOCKER_PASSWORD env vars
name: Sign in to Docker Hub
command: docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
- run:
# required for multi-arch builds
name: Create Docker builder
command: docker buildx create —use
- run:
name: Build and push image
command: bin/build-and-push-image
environment:
IMAGE_NAME: your_username/your_image_name&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code class=&quot;language-yaml&quot;&gt;workflows:
build:
jobs:
- build:
context:
- Docker Hub
filters:
branches:
only: main&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;figcaption&gt;Contents of &lt;code&gt;.circleci/config.yml&lt;/code&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure class=&quot;kg-card kg-code-card&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash&lt;p&gt;&lt;/p&gt;
&lt;p&gt;if [ -z “$CI” ]; then # protective magick
echo “This script must be run inside a CI environment.”
exit 0
fi&lt;/p&gt;
&lt;p&gt;export BUILD_CMD=“docker buildx build —progress plain —platform linux/amd64,linux/arm64 —push”
BUILD_SHA=”$(echo “$CIRCLE_SHA1” | cut -c1-7)”
export BUILD_SHA&lt;/p&gt;
&lt;p&gt;set -euxo pipefail&lt;/p&gt;
&lt;p&gt;$BUILD_CMD -t “$IMAGE_NAME:latest” .
$BUILD_CMD -t “$IMAGE_NAME:$BUILD_SHA” .&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code class=&quot;language-bash&quot;&gt;set +u
if [ -n “$CIRCLE_TAG” ]; then
$BUILD_CMD -t “$IMAGE_NAME:$CIRCLE_TAG” .
fi
set -u&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;figcaption&gt;Contents of &lt;code&gt;bin/build-and-push-image&lt;/code&gt; script&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;</content:encoded></item><item><title>Designing Data-Intensive Applications Cheat Sheet</title><link>https://kesdev.com/designing-data-intensive-applications-cheat-sheet</link><guid isPermaLink="true">https://kesdev.com/designing-data-intensive-applications-cheat-sheet</guid><description>This is a summary I wrote of each chapter of the excellent Designing Data-Intensive Applications (affiliate link) book. I use this to prepare for remote job interviews in infrastructure and devops. Please enjoy!Chapter 1Reliability: Systems must work correctly even when faults occurScalability: Systems have strategies for maintaining performance, even when load increasesResponse time percentiles c…</description><pubDate>Thu, 04 Aug 2022 22:06:05 GMT</pubDate><content:encoded>&lt;p&gt;This is a summary I wrote of each chapter of the excellent &lt;a href=&quot;https://amzn.to/3oSZjun&quot;&gt;Designing Data-Intensive Applications (affiliate link)&lt;/a&gt; book. I use this to prepare for remote job interviews in infrastructure and devops. Please enjoy!&lt;/p&gt;&lt;h1 id=&quot;chapter-1&quot;&gt;Chapter 1&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;Reliability: Systems must work correctly even when faults occur&lt;/li&gt;&lt;li&gt;Scalability: Systems have strategies for maintaining performance, even when load increases&lt;/li&gt;&lt;li&gt;Response time percentiles can measure performance&lt;/li&gt;&lt;li&gt;Maintainability: Makes life better for eng/ops teams who work sith the system&lt;/li&gt;&lt;li&gt;Good abstractions reduce complexity and make the system easier to modify and adapt&lt;/li&gt;&lt;li&gt;Good operability means visibility into system health and having ways to manage it&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-2&quot;&gt;Chapter 2&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;Document DBs target use cases where data is self-contained and rarely related&lt;/li&gt;&lt;li&gt;Graph DBs target use cases where all data is deeply interlinked&lt;/li&gt;&lt;li&gt;Schema can be explicit (enforced on write) or implicit (enforced on read)&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-3&quot;&gt;Chapter 3&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;OLTP:&lt;/strong&gt; Optimized for transaction processing&lt;/p&gt;&lt;ul&gt;&lt;li&gt;User-facing, large volume of requests&lt;/li&gt;&lt;li&gt;Small record count per query&lt;/li&gt;&lt;li&gt;Key-indexed lookup, dependent on disk seek time&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;strong&gt;Log-structured:&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Append-only to files; delete obsolete files; do not update a written file&lt;/li&gt;&lt;li&gt;Random-access writes are turned into sequential writes on disk, enabling higher write throughput on HDD/SSD&lt;/li&gt;&lt;li&gt;LevelDB, Cassandra, HBase, Lucene&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Update-in-place: &lt;/strong&gt;disk is a set of fixed-size pages that can be overwritten&lt;/li&gt;&lt;li&gt;B-trees, used in all major relational DBs&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;OLAP:&lt;/strong&gt; Optimized for analytics processing&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Used by biz analysts, not end users&lt;/li&gt;&lt;li&gt;Lower query volume, but they demand millions of records scanned&lt;/li&gt;&lt;li&gt;Disk bandwidth is the bottleneck&lt;/li&gt;&lt;li&gt;One solution: column-oriented storage&lt;/li&gt;&lt;li&gt;Data warehouses: when your queries require sequential scans, indexes matter a lot less&lt;/li&gt;&lt;li&gt;It’s more important to encode data compactly to minimize the amount of data that a query must read from disk&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-4&quot;&gt;Chapter 4&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;Rolling upgrades allow new versions of a service to be released without downtime&lt;/li&gt;&lt;li&gt;This promotes frequent small releases over rare big releases&lt;/li&gt;&lt;li&gt;This derisks deployments by allowing faulty releases to be rolled back before large user impact&lt;/li&gt;&lt;li&gt;This improves &lt;strong&gt;evolvability, &lt;/strong&gt;the ease of making changes to an app&lt;/li&gt;&lt;li&gt;During rolling upgrades, different versions of the app are running at the same time&lt;/li&gt;&lt;li&gt;Encoding must be backward-compatible (new code reading old data) and forward-compatible (old code reading new data)&lt;/li&gt;&lt;li&gt;JSON, XML, CSV have optional schemas and are vague about datatypes (e.g. numbers)&lt;/li&gt;&lt;li&gt;Binary schema formats (Thrift, Protobuf, Avro, gRPC) provide compact, efficient encoding, with explicit forward- and backward-compatibility semantics, but are not human-readable&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-5&quot;&gt;Chapter 5&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;High availability:&lt;/strong&gt; Keep system running even if 1+ machines goes down&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Disconnected operation:&lt;/strong&gt; App keeps working even if network unavailable&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Latency: &lt;/strong&gt;Place data closer to users so they can use it faster&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; Handle higher volume than any one machine could using read replicas&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Replication approaches:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Single-leader: &lt;/strong&gt;Clients send all writes to a &lt;strong&gt;leader&lt;/strong&gt; which streams data change events to &lt;strong&gt;followers; &lt;/strong&gt;reads from followers may be stale.&lt;br&gt;Easy to understand, no conflict resolution&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multi-leader: &lt;/strong&gt;Clients send a write to any leader node; leaders stream events to each other and any followers&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Leaderless replication: &lt;/strong&gt;Clients send each write to several nodes and read from several nodes in parallel to detect stale data.&lt;br&gt;Can be more robust to faulty nodes, network outage, latency, but harder to reason about, weak consistency guarantees&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Replication lag causes issues:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Read-after-write: Users must always see data they submitted&lt;/li&gt;&lt;li&gt;Monotonic reads: Users must always see data in chronological order (not see earlier point-in-time data)&lt;/li&gt;&lt;li&gt;Consistent prefix reads: Users must always see data in a state that makes causal sense: a question is followed by its reply&lt;/li&gt;&lt;li&gt;In multi-leader and leaderless schemes, conflicts may occur and must be resolved&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-6&quot;&gt;Chapter 6&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;Partitioning is necessary when data cannot fit onto a single machine&lt;/li&gt;&lt;li&gt;Goal is to spread data and query load evenly among multiple machines, avoiding hot spots&lt;/li&gt;&lt;li&gt;Must choose a partition scheme that fits data, and rebalance partitions when nodes are added or removed&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Approaches:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;strong&gt;&lt;strong&gt;Key range partitioning:&lt;/strong&gt; e.g. node owns keys from A through F&lt;/strong&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;May cause hot spots if application frequently accesses keys that are close together in the sorted order&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hash partitioning:&lt;/strong&gt; keys are assigned to a node corresponding to their hashed value&lt;/li&gt;&lt;li&gt;Distributes load evenly but destroys ordering of keys, so range queries are inefficient&lt;/li&gt;&lt;li&gt;Common approach: Create a fixed number of partitions in advance, assign several to a node, and move entire partitions when a node is added/removed&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Secondary indices must also be partitioned:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;strong&gt;&lt;strong&gt;Document-partitioned (local):&lt;/strong&gt; secondary indices are stored in same partition as primary K/V&lt;/strong&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Only update a single partition on write; read of secondary index requires scatter/gather across all partitions&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;strong&gt;&lt;strong&gt;Term-partitioned (global): &lt;/strong&gt;secondary indices are partitioned separately using indexed values&lt;/strong&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Entry in secondary index may include records from all partitions of the primary key&lt;/li&gt;&lt;li&gt;Write updates several partitions; reads from a single partition&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-7&quot;&gt;Chapter 7&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;Tranasactions allow an app to pretend that some concurrency problems and SW/HW faults don’t exist – lots of errors become “transaction aborts”&lt;/li&gt;&lt;li&gt;Txns hugely reduce the number of potential error cases you need to worry about&lt;/li&gt;&lt;li&gt;Without txns, hardware errors (power outage, disk crash) cause various data inconsistencies – hard to reason about effects of concurrent access&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Race conditions include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Dirty reads: Client sees another client’s writes before they are committed. Solvable with read-committed isolation level.&lt;/li&gt;&lt;li&gt;Dirty writes: Client overwrites another client’s data that has been written but not committed. Solvable with snapshot isolation.&lt;/li&gt;&lt;li&gt;Read skew: Client sees different parts of the DB at different points in time&lt;/li&gt;&lt;li&gt;Lost updates: Two clients perform a concurrent read-modify-write (e.g. bank balance problem). Solvable with snapshot isolation.&lt;/li&gt;&lt;li&gt;Write skew: Txn reads something, makes decision, writes decision – by the time the write is made, the premise is no longer true. Solvable with serializable isolation.&lt;/li&gt;&lt;li&gt;Phantom reads: Txn reads objects matching a search condition; someone else writes data that modifies those search results. Write skew issues may require index-range locks.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Approaches to implementing serializable transactions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Literally executing transactions in serial order on a single CPU core&lt;/li&gt;&lt;li&gt;Two-phase locking: standard approach; may have poor performance&lt;/li&gt;&lt;li&gt;Serializable snapshot isolation: optimistically allow txns to proceed without blocking; commits are aborted if not serializable&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-8&quot;&gt;Chapter 8&lt;/h1&gt;&lt;p&gt;Some kinds of partial failures in distributed systems:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Network: Packets may be lost or artificially delayed&lt;/li&gt;&lt;li&gt;Time: Node clock may jump forward or backward and be out of sync with other nodes&lt;/li&gt;&lt;li&gt;Pauses: GC may pause a process; other nodes declare it dead; it resumes and is unaware it was paused&lt;/li&gt;&lt;li&gt;Any software that interacts with other nodes may fail, go slow, or time out&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Detecting faults:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Most systems con’t know if a node has failed&lt;/li&gt;&lt;li&gt;Most distributed algorithms rely on timeouts to detect node failure&lt;/li&gt;&lt;li&gt;But timeouts might be network issues, not node failures&lt;/li&gt;&lt;li&gt;A limping node might cause more issues than a dead one&lt;/li&gt;&lt;li&gt;Once a fault is detected, info must flow over unreliable network between nodes – we rely on quorum protocols to make decisions&lt;/li&gt;&lt;li&gt;Distributed systems enable scalability, fault tolerance, and low latency&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;chapter-9&quot;&gt;Chapter 9&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;Linearizability makes a database behave like an atomic variable, but is slow, especially across high-latency networks&lt;/li&gt;&lt;li&gt;Causality imposes an ordering on events, based on cause and effect&lt;/li&gt;&lt;li&gt;Weaker consistency model&lt;/li&gt;&lt;li&gt;Some things can be concurrent – branching and merging&lt;/li&gt;&lt;li&gt;Less sensitive to network issues than linearizability; lacks the coordination overhead of linearizability&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;strong&gt;Consensus&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Solves atomic problems in causal models, e.g. signing up for a username requires that username to not already be taken&lt;/li&gt;&lt;li&gt;All nodes must agree on what was decided, irrevocably&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Consensus decision problems include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Linearizable compare-and-set registers: set a register based on a parameter&lt;/li&gt;&lt;li&gt;Atomic transaction commit: commit or abort&lt;/li&gt;&lt;li&gt;Total order broadcast: decide on order to deliver messages&lt;/li&gt;&lt;li&gt;Locks and leases: only one client can grab a lock&lt;/li&gt;&lt;li&gt;Membership/coordination: decide which nodes are alive and dead&lt;/li&gt;&lt;li&gt;Uniqueness constraint: which txn is allowed and failed due to constraint violation&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Single-leader failure resolutions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Wait for leader to recover&lt;/li&gt;&lt;li&gt;Human does manual failover&lt;/li&gt;&lt;li&gt;Algorithm chooses new leader&lt;/li&gt;&lt;li&gt;Even if a leader can be chosen algorithmically, we still need &lt;strong&gt;consensus &lt;/strong&gt;to select the new leader&lt;/li&gt;&lt;li&gt;Leaderless and multi-leader replication systems don’t use global consensus&lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title>My Tips for Traveling Light</title><link>https://kesdev.com/traveling-light</link><guid isPermaLink="true">https://kesdev.com/traveling-light</guid><description>Disclaimer: I use Amazon referral links in this post. Using links on this page to purchase items may result in me receiving an affiliate bonus. I have not been otherwise paid to write this post, and these are my personal views alone.Now that I have the flexibility in my remote-work career and the income to buy plane tickets more often, I&apos;m making the most of it. I love to travel, but I hate haulin…</description><pubDate>Sun, 10 Jul 2022 09:38:23 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Disclaimer: I use Amazon referral links in this post. Using links on this page to purchase items may result in me receiving an affiliate bonus. I have not been otherwise paid to write this post, and these are my personal views alone.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Now that I have the flexibility in my remote-work career and the income to buy plane tickets more often, I&apos;m making the most of it. I love to travel, but I hate hauling all my shit from place to place just to live somewhere new for a bit. Here&apos;s what I&apos;ve learned in my efforts to travel light while keeping my creature comforts (read: my computers) with me.&lt;/p&gt;&lt;h1 id=&quot;essentials&quot;&gt;Essentials&lt;/h1&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/07/essentials-1.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;1065&quot;&gt;&lt;figcaption&gt;Left to right: &lt;a href=&quot;https://amzn.to/3Ruo5Oh&quot;&gt;24 oz ThermoFlask&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3Rw2IMu&quot;&gt;25000 mAh USB-C powerbank&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3yDqXjd&quot;&gt;hanging toiletry bag&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;As I&apos;ve gone beyond my twenties, I&apos;ve realized that my mom, with her big shoulder bags full of water bottles, wet wipes, and snacks, was right: the worst thing that can happen to you as a full-grown adult is to &lt;strong&gt;be caught lacking.&lt;/strong&gt; So the core of my packing strategy is self-sufficiency. I should never find myself running around to find something I need which I could have brought with me. My big needs are &lt;strong&gt;water &lt;/strong&gt;and &lt;strong&gt;power.&lt;/strong&gt; &lt;/p&gt;&lt;p&gt;I love my &lt;a href=&quot;https://amzn.to/3Ruo5Oh&quot;&gt;insulated 24 oz ThermoFlask&lt;/a&gt; which can keep my water ice-cold overnight, and these days it doesn&apos;t leave my side – doubly so when I&apos;m abroad. Any day that I need to pop into a shop to grab a bottle of water is a day I haven&apos;t prepared properly.&lt;/p&gt;&lt;p&gt;These days, between my boarding pass, my music, and my movies for the flight, my phone is critical to me too. Life is too short to live on Low-Power Mode, so I keep a &lt;a href=&quot;https://amzn.to/3Rw2IMu&quot;&gt;25000 mAh USB-C powerbank&lt;/a&gt; on me for longer trips. This thing has enough power to keep my phone &lt;em&gt;and&lt;/em&gt; (M1 Air) laptop going for days, and I&apos;ve never regretted hauling it with me. I&apos;m religious about keeping all of my batteries topped up.&lt;/p&gt;&lt;h2 id=&quot;toiletries&quot;&gt;Toiletries&lt;/h2&gt;&lt;p&gt;To hold my toiletries, I use a &lt;a href=&quot;https://amzn.to/3yDqXjd&quot;&gt;hanging bag&lt;/a&gt; with plenty of space for everything I could need, including medication and mouthwash. For shorter trips or one-bag travel, I use a &lt;a href=&quot;https://amzn.to/3RuoiB3&quot;&gt;smaller, lighter hanging bag&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;drugs&quot;&gt;Drugs&lt;/h2&gt;&lt;p&gt;The drugs I typically bring with me are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Melatonin: &lt;/strong&gt;for solving jet lag by helping me get to sleep on schedule to arrive in a new time zone. Aligning my sleep schedule to avoid losing a day to travel is my favorite party trick, and it never fails to impress anyone over 30.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Benadryl: &lt;/strong&gt;for occasional allergies, and it doubles as a much stronger (occasional) sleep aid than melatonin. Great for when you&apos;ve been out later than you intended but you still need to catch a 7am train tomorrow morning.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Aleve: &lt;/strong&gt;never leave home without painkillers.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;sleep&quot;&gt;Sleep&lt;/h2&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/07/sleep.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;892&quot;&gt;&lt;figcaption&gt;Left to right, top to bottom: &lt;a href=&quot;https://www.amazon.com/trtl-Pillow-Plus-Travel-Accessories/dp/B07QHDRCZ3&quot;&gt;Trtl pillow&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3O2TqVi&quot;&gt;SleepSloth eye mask&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3nVXNqH&quot;&gt;Laser Lite earplugs&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3P4WGAZ&quot;&gt;Sony WH-1000XM3 noise-cancelling headphones&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I&apos;m a person who can fall asleep anywhere if I&apos;m tired enough, but sometimes I still need a little help. So these are always in my carry-on:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Earplugs: &lt;/strong&gt;My favorites are the &lt;a href=&quot;https://amzn.to/3nVXNqH&quot;&gt;Howard Leight Laser Lite&lt;/a&gt; disposable foam earplugs. They&apos;re extremely comfortable and cheap, they&apos;re reusable for a few days if you keep your ears clean of wax, and they block out a ton of sound if inserted properly.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Eye mask: &lt;/strong&gt;I love this &lt;a href=&quot;https://amzn.to/3O2TqVi&quot;&gt;SleepSloth eye mask&lt;/a&gt;, which blocks out all light and doesn&apos;t put any pressure on my eyes. You can even comfortably open your eyes underneath it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Trtl pillow: &lt;/strong&gt;This &lt;a href=&quot;https://www.amazon.com/trtl-Pillow-Plus-Travel-Accessories/dp/B07QHDRCZ3&quot;&gt;adjustable neckscarf-style pillow&lt;/a&gt; provides support to keep you from &quot;nodding on&quot; as your head tips over sideways. It&apos;s also incredibly compact compared to a big fluffy neck pillow.I&apos;ve slept dozens of hours with this strapped to my neck on long trips, and it only makes you look a little silly. (They&apos;re mostly just jealous of you.)&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Noise-cancelling headphones: &lt;/strong&gt;The &lt;a href=&quot;https://amzn.to/3P4WGAZ&quot;&gt;Sony WH1000-XM series&lt;/a&gt; provide incredible noise cancelling, even if you&apos;re frugal like me and find a deal on an older generation. I used to be a die-hard Bose QuietComfort fan, but I had two pairs go out on me out of warranty and I no longer trust the brand&apos;s quality.&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;clothes&quot;&gt;Clothes&lt;/h1&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/07/clothes.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;948&quot;&gt;&lt;figcaption&gt;Left to right, top to bottom: &lt;a href=&quot;https://amzn.to/3c9jIbx&quot;&gt;Under Armour technical shirt&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3P3lVDQ&quot;&gt;tabi slippers&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3uJpOpd&quot;&gt;synthetic stretch chinos&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3ogqT4x&quot;&gt;Fun Toes merino wool socks&lt;/a&gt;, &lt;a href=&quot;https://www.timberland.com/shop/mens-mt-maddsen-waterproof-hiking-boots-brown-tan-2730r242&quot;&gt;Timberland Mt. Maddsen hiking boots&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Never skimp on anything that separates you from the ground. It&apos;s critical to bring a pair of shoes that won&apos;t let you down when you&apos;re away from home. Shoes are heavy and bulky, so if you bring one pair that will get you through everything, you won&apos;t have to fit a second pair into your luggage.&lt;/p&gt;&lt;p&gt;Look for something durable and hard-wearing. Outdoor hiking boots are a great option, and high-quality brands (with varying levels of everyday style) include Keen, Merrell, Red Wing, and Teva. I am extremely partial to the &lt;a href=&quot;https://www.timberland.com/shop/mens-mt-maddsen-waterproof-hiking-boots-brown-tan-2730r242&quot;&gt;Timberland Mt. Maddsen boots&lt;/a&gt;, which look amazing and are fully waterproof – I&apos;ve even brought these on canoe trips. I typically wear these with &lt;a href=&quot;https://amzn.to/3ogqT4x&quot;&gt;cheap merino wool socks&lt;/a&gt;, which are comfortable in hot and cold conditions as well as breathable to keep sweat off your skin.&lt;/p&gt;&lt;p&gt;When you&apos;re lounging indoors, you&apos;ll want some shoes to grip the dirty floors and protect your nice socks from them. Look into &lt;a href=&quot;https://amzn.to/3P3lVDQ&quot;&gt;tabi-style slippers&lt;/a&gt;, which are lightweight, easy to wash, and fold flat into your luggage.&lt;/p&gt;&lt;p&gt;To stay comfortable when you&apos;re spending several hours sweating into your seat on a bus, an airplane, or in an airport chair, I recommend you pack &lt;em&gt;only&lt;/em&gt; high-wicking synthetic technical shirts. They double as great shirts for hiking or walking around a city, they don&apos;t wrinkle, and they move the sweat off your skin. Anything will do, so find a brand that you find comfortable which fits you well. (I like these shirts by &lt;a href=&quot;https://amzn.to/3c9jIbx&quot;&gt;Under Armour&lt;/a&gt;.)&lt;/p&gt;&lt;p&gt;Pick shirts that are all around the same color – coordiating your outfits is much easier when all your shirts and sweaters match all your pants. If you&apos;re going somewhere sunny, look for long-sleeve technical shirts with built-in SPF protection.&lt;/p&gt;&lt;p&gt;Science has made incredible advances in stylish, wrinkle-free, everyday pants. &lt;a href=&quot;https://amzn.to/3uJpOpd&quot;&gt;Synthetic stretch chinos&lt;/a&gt; are so comfortable you&apos;ll forget you&apos;re stuck in a plastic chair for seven hours during your layover, and they&apos;re stylish enough to shame everyone else showing up to the pub. I love these so much that next time, I&apos;ll bring a second pair instead of my denim jeans.&lt;/p&gt;&lt;h1 id=&quot;devices&quot;&gt;Devices&lt;/h1&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/07/devices.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;608&quot;&gt;&lt;figcaption&gt;Left to right: &lt;a href=&quot;https://amzn.to/3O0x0UY&quot;&gt;Fire Stick 4K Max&lt;/a&gt;, &lt;a href=&quot;https://www.peakdesign.com/products/tech-pouch&quot;&gt;Peak Design Tech Pouch&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3uH2qbS&quot;&gt;Anker PowerExtend adapter&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I spend a lot of my downtime on vacation catching up on my favorite TV shows and websites, so it&apos;s important to me that getting power isn&apos;t a pain in the ass. It&apos;s critical if I&apos;m working remotely!&lt;/p&gt;&lt;h2 id=&quot;power&quot;&gt;Power&lt;/h2&gt;&lt;p&gt;An outlet extension strip is an incredible thing to have with you when power outlets are limited. No need to snipe an outlet or wait for one to free up – just piggyback their adapters onto yours and you can plug in too. This &lt;a href=&quot;https://amzn.to/3uH2qbS&quot;&gt;Anker PowerExtend adapter&lt;/a&gt; triples as an extension cord, a power strip, and a USB-C PD charger for my MacBook, which makes it totally indispensable to me.&lt;/p&gt;&lt;p&gt;If you&apos;re going somewhere with different plugs, don&apos;t forget to bring an outlet adapter. A 240V -&gt; 120V adapter is often huge and heavy, so if you can avoid bringing one of these, don&apos;t bring one! Odds are your USB adapter (e.g. the Anker listed above) supports anywhere from 100 to 240 volts AC – you can find out for sure by reading the fine print on the back.&lt;/p&gt;&lt;p&gt;And of course, don&apos;t forget the USB power bank I mentioned under the Essentials section.&lt;/p&gt;&lt;h2 id=&quot;cables&quot;&gt;Cables&lt;/h2&gt;&lt;p&gt;Triple-check to make sure you have the correct cables on you for all your devices before you leave. It&apos;s never a great feeling to leave on your trip only to have to hunt down an overpriced Lightning cable at the airport. And if you&apos;re planning on using your iPhone as a mobile tethering modem for your MacBook, make sure you bring that pesky 3ft USB-C to Lightning cable too.&lt;/p&gt;&lt;p&gt;If you carry as many cables and devices as I do, you&apos;ll want to organize them better than coiling them all up and tossing them into a big pocket together. The &lt;a href=&quot;https://www.peakdesign.com/products/tech-pouch&quot;&gt;Peak Design Tech Pouch&lt;/a&gt; is the gold standard for organizing lots of little cables, but of course, it&apos;s just a pouch with pockets! There are plenty of $10 options which do the job just fine.&lt;/p&gt;&lt;h2 id=&quot;media&quot;&gt;Media&lt;/h2&gt;&lt;p&gt;I love catching up on my TV and movie backlog while I&apos;m on vacation, but I hate showing up to a rental apartment and finding out their smart TV is all goofed up, or the HDMI cable is broken. So these days I bring my &lt;a href=&quot;https://amzn.to/3O0x0UY&quot;&gt;Fire Stick&lt;/a&gt; with me on trips, along with its power adapter and cables, in a little accessory bag. It stays signed into my streaming accounts and makes it easy to put on whatever I want when I arrive.&lt;/p&gt;&lt;p&gt;Amazon devices like the Fire Stick regularly go on sale during holidays and Prime Day, so don&apos;t buy one at full price.&lt;/p&gt;&lt;h1 id=&quot;bags&quot;&gt;Bags&lt;/h1&gt;&lt;p&gt;A great trip ends when your luggage goes missing. The best way to prevent this is to never let your bags leave your side – that means no checked luggage! There are two main schools of thought:&lt;/p&gt;&lt;h2 id=&quot;1-one-bag&quot;&gt;1. One-bag&lt;/h2&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/07/backpacks.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;743&quot;&gt;&lt;figcaption&gt;Left to right: &lt;a href=&quot;https://www.ospreyeurope.com/shop/ie_en/farpoint-40-14&quot;&gt;Osprey Farpoint 40&lt;/a&gt;, &lt;a href=&quot;https://www.aersf.com/travel-pack-3-black&quot;&gt;Aer Travel Pack&lt;/a&gt;, &lt;a href=&quot;https://www.peakdesign.com/collections/travel-bags/products/travel-backpack?variant=11530908172332&quot;&gt;Peak Design Travel Backpack&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;em&gt;Pack all your stuff into a single bag.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Aim for a 40L backpack intended for carry-on travel. Bags of this volume will fit on airplanes, either below the seat or in the overhead compartment. (In my experience, they even fit onto Ryanair.) A bag listed as a &lt;em&gt;travel pack &lt;/em&gt;will typically unzip all the way around so that you can pack it like a suitcase, rather than stuffing in from the top like a hiking pack. My favorites are the &lt;a href=&quot;https://www.ospreyeurope.com/shop/ie_en/farpoint-40-14&quot;&gt;Osprey Farpoint 40&lt;/a&gt;, the &lt;a href=&quot;https://www.aersf.com/travel-pack-3-black&quot;&gt;Aer Travel Pack&lt;/a&gt;, and the &lt;a href=&quot;https://www.peakdesign.com/collections/travel-bags/products/travel-backpack?variant=11530908172332&quot;&gt;Peak Design Travel Backpack&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;If you only bring one bag, your one bag never leaves your sight (or your arms) so it&apos;s at reduced risk of being left behind or stolen. Airlines love you – many offer priority boarding for folks with no overhead carry-on, and you&apos;re never at risk of having to check your second bag when the plane is full. However, you will have less space for all your items.&lt;/p&gt;&lt;p&gt;Folks who are willing to pack light can absolutely do a trip of any length with a very minimal set of clothing:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;3-4 shirts, sets of underwear, and pairs of socks&lt;/li&gt;&lt;li&gt;1 pair of versatile pants&lt;/li&gt;&lt;li&gt;1 pair of comfortable pants (or shorts) for sleeping and lounging&lt;/li&gt;&lt;li&gt;1 all-purpose down jacket&lt;/li&gt;&lt;li&gt;1 versatile sweater layer&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This gives you enough clothing to wash your dirty laundry in a sink and hang dry every couple of days. You can bring &lt;a href=&quot;https://amzn.to/3yDs3eP&quot;&gt;travel detergent packets&lt;/a&gt;, or you can just put a bit of your own detergent into your &lt;a href=&quot;https://amzn.to/3AKZgb5&quot;&gt;travel shampoo bottles&lt;/a&gt; – it&apos;s the same stuff. &lt;/p&gt;&lt;h2 id=&quot;2-backpack-and-carry-on&quot;&gt;2. Backpack and carry-on&lt;/h2&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/07/carryons.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;668&quot;&gt;&lt;figcaption&gt;Left to right: &lt;a href=&quot;https://eu.travelpro.com/collections/carry-on-luggage/products/maxlite%C2%AE-5-55-expandable-carry-on-spinner&quot;&gt;Travelpro Maxlite 5&lt;/a&gt;, &lt;a href=&quot;https://briggs-riley.co.uk/collections/carry-on-luggage/products/domestic-carry-on-expandable-spinner&quot;&gt;Briggs and Riley Domestic 56cm&lt;/a&gt;, &lt;a href=&quot;https://www.peakdesign.com/products/everyday-backpack?variant=29743300771884&quot;&gt;Peak Design Everyday Backpack&lt;/a&gt; strapped to a carry-on handle&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;em&gt;Pack your clothes into your carry-on bag and your devices into your backpack.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;A one-bag travel pack can turn you into a tortoise, and they&apos;re not ideal for bringing your laptop to work from a coffee shop. If you choose to bring a carry-on, you can bring most of the clothes you want, and you can bring a backpack that&apos;s more ergonomic for daily use. Plus, you&apos;ll have more cargo space for bringing souvenirs for friends back home.&lt;/p&gt;&lt;p&gt;If you go this route, bring your favorite daily backpack and a good &lt;em&gt;softshell &lt;/em&gt;carry-on. Mine is the &lt;a href=&quot;https://www.peakdesign.com/products/everyday-backpack?variant=29743300771884&quot;&gt;Peak Design Everyday Backpack&lt;/a&gt;, which comes with a handy built-in strap that fits over a carry-on bag&apos;s handle. You do &lt;em&gt;not&lt;/em&gt; need a hardshell carry-on – hardshells are most useful for protection against airline employees chucking your bag into the cargo hold, and softshells allow you to use more space and expand the bag&apos;s capacity with a zipper.&lt;/p&gt;&lt;p&gt;Pick a high-quality &lt;em&gt;spinner &lt;/em&gt;(four universal wheels) carry-on bag. It should either come with a lifetime warranty (&lt;a href=&quot;https://briggs-riley.co.uk/collections/carry-on-luggage/products/domestic-carry-on-expandable-spinner&quot;&gt;Briggs and Riley&lt;/a&gt;) or be cheap enough to replace when the wheels go out in a few years (&lt;a href=&quot;https://eu.travelpro.com/collections/carry-on-luggage/products/maxlite%C2%AE-5-55-expandable-carry-on-spinner&quot;&gt;Travelpro Maxlite 5&lt;/a&gt;). Don&apos;t forget that American brands have a &quot;domestic&quot; size (larger for American plane cabins) and an &quot;international&quot; size (smaller for European plane cabins), so size according to the airline you usually fly.&lt;/p&gt;&lt;h2 id=&quot;other-bag-tips-and-tricks&quot;&gt;Other bag tips and tricks&lt;/h2&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2022/07/bags.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;588&quot;&gt;&lt;figcaption&gt;Left to right: &lt;a href=&quot;https://amzn.to/3AKXM0k&quot;&gt;Gonex compression packing cubes&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3yXc0tU&quot;&gt;crossbody sling&lt;/a&gt;, &lt;a href=&quot;https://amzn.to/3z0Ay5d&quot;&gt;4Monster 16L packable daypack&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I am deathly afraid of being made a target when on vacation, especially when I&apos;m out drinking. I&apos;m also horribly scatterbrained and liable to lose anything not physically attached to me. So I keep my passport, wallet, and cash glued to my body using a &lt;a href=&quot;https://amzn.to/3yXc0tU&quot;&gt;crossbody bag&lt;/a&gt;. (It has enough space for an N95 mask and my AirPods as well.) I like this bag enough that I&apos;ve started using it for my dailies at home, rather than transferring all my items between my pairs of pants every morning.&lt;/p&gt;&lt;p&gt;Sometimes I just need a bag to hold my down jacket and water bottle, or a tote for groceries, and it&apos;s inappropriate to bring my nice laptop backpack. So I always bring a &lt;a href=&quot;https://amzn.to/3z0Ay5d&quot;&gt;16L packable daypack&lt;/a&gt; in my luggage. It packs down into a tiny bundle that I can shove into any free corner of space, and it&apos;s ultralight when empty.&lt;/p&gt;&lt;p&gt;Whichever bags you use, you can get more volume from it if you use packing cubes to store your clothes. These &lt;a href=&quot;https://amzn.to/3AKXM0k&quot;&gt;Gonex compression cubes&lt;/a&gt; come with a zipper for the contents and a second zipper to compress the air out of the clothes inside. This way you don&apos;t have to worry about carefully rolling your clothes together – just fold them flat, put as much as you can into the bag, and zip it down to compress.&lt;/p&gt;&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;&lt;p&gt;These tips aren&apos;t intended for anyone in particular – they&apos;re just items and practices that I&apos;ve found which help me make a good trip great. I hope some of these help you on your future trips, and I&apos;d love to hear what you like to use when you travel.&lt;/p&gt;</content:encoded></item><item><title>SRE for Backpackers</title><link>https://kesdev.com/site-reliability-engineering-for-backpac</link><guid isPermaLink="true">https://kesdev.com/site-reliability-engineering-for-backpac</guid><description>Last week, I took some time away from work to travel back to Minnesota and camp in the Boundary Waters Canoe Area (BWCA) with my friends. When I was growing up in Wisconsin, my dad took my brothers and me to the BWCA with an outfitter guide. These days I feel confident going out on my own without a guide.Our party was six people, all experienced backpackers. Everyone had been to the BWCA on a cano…</description><pubDate>Mon, 23 May 2022 16:35:18 GMT</pubDate><content:encoded>&lt;p&gt;Last week, I took some time away from work to travel back to Minnesota and camp in the Boundary Waters Canoe Area (BWCA) with my friends. When I was growing up in Wisconsin, my dad took my brothers and me to the BWCA with an outfitter guide. These days I feel confident going out on my own without a guide.&lt;/p&gt;&lt;p&gt;Our party was six people, all experienced backpackers. Everyone had been to the BWCA on a canoe camping trip before. Still, some things went wrong, and since it&apos;s my job to keep things from going wrong, I wanted to write about how the lessons I learned as a Site Reliability Engineer apply to the world outside computers.&lt;/p&gt;&lt;h1 id=&quot;pack-light&quot;&gt;Pack light&lt;/h1&gt;&lt;p&gt;Each of us carried a hiking pack with our personal stuff – clothes, toiletries, sleeping bag and pad. We split the shared goods – 2-person tent, water filter, rain tarp – among bags evenly. But the spread of weight was drastically different from person to person. On the low end, James and I packed ultralight Osprey Atmos packs weighing in fully loaded around 30 lb (14 kg), while Dinh and Jared packed wider, larger &quot;portage packs&quot; clocking in around 50 lb (23 kg).&lt;/p&gt;&lt;p&gt;Carrying bigger, heavier packs means you can bring more. It also means you have to &lt;strong&gt;portage&lt;/strong&gt; them to get to your campsite. Our campsite was separated from the initial put-in lake by three other lakes, each separated by land. We had to pull the canoes out three times, portage the canoes to the other side, haul the bags over, load the bags, and put the canoes back in. The process is lengthy and strenuous: our longest portage was about 0.7 mi (1.12 km) of hilly, rocky trail. We rented light Kevlar canoes, but a &quot;light&quot; canoe still weighs 50 lb (23 kg) – a significant load for your shoulders.&lt;/p&gt;&lt;p&gt;Our canoe – light packers – had the option to haul in the following configuration in one trip:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A: carry a pack and the canoe&lt;/li&gt;&lt;li&gt;B: carry a pack and help balance the nose of the canoe&lt;/li&gt;&lt;li&gt;C: carry two packs&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The other canoe – heavy packers – did not have this option because their packs were so heavy, and had to make two trips between the entry and exit point for every portage. The discrepancy in active portage tiplanme made it harder to keep our party together because the &quot;light&quot; canoe would be in the water waiting while the &quot;heavy&quot; canoe was still moving down the trail.&lt;/p&gt;&lt;p&gt;When we unpacked at camp, I noticed lots of ways that our heavy campers could have saved weight:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Someone brought a blue hardware store tarp that weighed about 4 lb (2 kg). Modern ultralight tarps can come in as low as 8 oz (224g), pack smaller, and serve the same function.&lt;/li&gt;&lt;li&gt;Someone brought two Cliq camp chairs, each of which weighed 3.7 lb (1.7 kg). They&apos;re nice chairs, but we didn&apos;t strictly need chairs &lt;em&gt;with legs. &lt;/em&gt;Every campsite had logs configured around the firepit grate as seating, and we always sat around the fire anyway. A great alternative would have been the folding foam canoe chairs – these doubled as secure, comfortable seats in the canoes when we paddled, and when we arrived at camp, we unclipped them from the canoes, brought them up to the campfire sitting logs, and used them as seats. Any equipment that pulls double duty is worth its weight!&lt;/li&gt;&lt;li&gt;Someone brought a bulky 32 oz (900 ml) plastic canteen with a built-in LifeStraw. The LifeStraw doesn&apos;t weigh much, but it adds volume to the water bottle, and it&apos;s redundant when we&apos;re already hauling water filters for the lake water. My hydration bladder fit nicely along the inside flat back of my hiking pack and took up much less volume.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In my work as an infrastructure engineer, I see lots of ways that we misuse tools and add extraneous redundancy and overhead to daily processes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Approval and sign-off workflows in our shared repos demand code review and approval from people who are not only outside of the codebase, but outside of the &lt;em&gt;domain&lt;/em&gt; – the mandatory reviewers don&apos;t understand the problem that&apos;s being solved in the pull request.&lt;/li&gt;&lt;li&gt;Overly-complex domain tools are chained together in redundant ways: using Terragrunt to template out Terraform charts that deploy using Helm charts under the hood makes it incredibly hard to understand how a parameter turns into a value, and makes the system inscrutable to new engineers. It doesn&apos;t add value, either: Terraform can do all of this without additional tooling.&lt;/li&gt;&lt;li&gt;Sometimes, less is more. Many of our shared infrastructure modules define categories of databases, storage systems, or workloads to be used in production environments. But sometimes this work to define domain problems as infra modules is premature, and users find it easier to simply write the verbose infrastructure by hand (&quot;going full YAML&quot;) to get what they need. Rich Hickey&apos;s &lt;a href=&quot;https://www.infoq.com/presentations/Simple-Made-Easy/&quot;&gt;Simple Made Easy&lt;/a&gt; is a wonderful talk about how to understand your domain to write the &lt;em&gt;correct&lt;/em&gt; abstraction for your users without overcomplicating.&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;plan-backups-for-your-backups&quot;&gt;Plan backups for your backups&lt;/h1&gt;&lt;figure class=&quot;kg-card kg-embed-card&quot;&gt;&lt;iframe width=&quot;200&quot; height=&quot;150&quot; src=&quot;https://www.youtube.com/embed/vsUdMsistpM?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/figure&gt;&lt;p&gt;Throughout the trip, we had a series of redundant systems to get where we needed to go without getting lost in the wilderness. We had two canoes, and one person brought a pair of FRS/GMRS radios so we could communicate between parties. We had two waterproof maps of the area, detailing the location of campsites and length of portages. And we had a GPS in each boat as a quick reference and backup to the maps.&lt;/p&gt;&lt;p&gt;On our last day, we had chosen a campsite a stone&apos;s throw away from our entry point. We camped on Alton Lake, one lake over from Sawbill Lake via an 0.1 mile (0.15 km) portage. We planned to get back to the parking lot one hour after we shoved off into the water. The heavy boat launched first while we finished loading the light boat. But as our boat headed south along the east shore toward the east portage landing, we saw the other boat head south along the &lt;em&gt;west&lt;/em&gt; shore and blow straight past the portage. Worried, we landed our boat at the portage, moved to the exit point, and gave the other party five minutes to catch up. We tried to figure out what happened while we waited, but we only got the full story after we got back to the parking lot:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The person with the radios had decided to pack them both up, reasoning that it was a short, straightforward trip and we wouldn&apos;t need them. &lt;/li&gt;&lt;li&gt;The person in the other boat had folded their map up into their tent by mistake and couldn&apos;t access it when they loaded into the canoe.&lt;/li&gt;&lt;li&gt;No one in the other party had reviewed the course closely before shoving off.&lt;/li&gt;&lt;li&gt;Someone in the other party had mistaken the location of the portage, telling the party it was at the south end of the lake rather than halfway down the east bank.&lt;/li&gt;&lt;li&gt;The person in the other boat with the GPS didn&apos;t review the route as they paddled.&lt;/li&gt;&lt;li&gt;I had asked the other crew to keep our boats together in a single convoy, but they opted to set off immediately so they could get back to civilization faster.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To anyone who has ever participated in a high-pressure production outage that&apos;s gone poorly, this sequence of failures likely looks familiar. Disasters are usually not caused by a single event, but rather a &lt;a href=&quot;https://www.wikiwand.com/en/Chain_of_events_(accident_analysis)&quot;&gt;series of systemic failures that &lt;em&gt;chain&lt;/em&gt;&lt;/a&gt;&lt;em&gt; &lt;/em&gt;to cause an unwanted outcome. An example of this is the &lt;a href=&quot;https://www.wikiwand.com/en/Tenerife_airport_disaster&quot;&gt;Tenerife airport disaster&lt;/a&gt;, in which two 747 passenger jets collided on a runway, resulting in massive loss of life. Major factors in this incident included:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Pressure on one of the pilots to take off ASAP to remain in compliance with his airline&apos;s duty-time regulations&lt;/li&gt;&lt;li&gt;Pressure to take off immediately to avoid further delays from deterioration of weather conditions&lt;/li&gt;&lt;li&gt;Sudden fog which greatly limited visibility, preventing plane crews from seeing each other and tower crew members from seeing either plane&lt;/li&gt;&lt;li&gt;Radio interference which made it difficult for plane crews to understand tower instructions&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;When you build practices for daily operation – or incident management – into your organization, take time to think through the human factors. By working to answer these questions, you will have a better understanding of how your system will fail in ways you didn&apos;t design it to. You can use these answers to build stronger processes that mitigate broken links in the disaster chain.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Are your engineers typically working on code and production changes as a team, or are they independent cowboys shipping solo?&lt;/li&gt;&lt;li&gt;How do your team members typically communicate when something is going right – or wrong?&lt;/li&gt;&lt;li&gt;How do they behave when under stress, as opposed to during business as usual?&lt;/li&gt;&lt;li&gt;What conflicts exist in your organization map? What teams don&apos;t fully trust one another, and are likely to withhold information in a critical period?&lt;/li&gt;&lt;li&gt;Which teams are motivated to uphold the integrity of the system as a whole, and which teams are directly motivated to uphold the integrity of &lt;em&gt;their deliverables&lt;/em&gt; above other teams&apos; work?&lt;/li&gt;&lt;li&gt;How are engineers being pressured by executive leadership? Which teams are being overloaded into taking on more work than they can reasonably support? What kinds of tech debt will definitely result in future incidents if not addressed?&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;rethink-your-knots&quot;&gt;Rethink your knots&lt;/h1&gt;&lt;p&gt;When camping in the northwoods of the midwestern United States, the most common concern is black bears. Black bears in Minnesota average from 154 lb (70 kg) to 275 lb (125 kg) and are known to be persistent and intelligent about acquiring food. Although they rarely approach campers and direct encounters are uncommon, they become more dangerous when acclimated to human food, learning that human campsites have food which can be foraged.&lt;/p&gt;&lt;p&gt;To minimize bear attacks, park staff have drilled into campers the utmost importance of making food totally inaccessible and removing it from campsites. Before you go to bed in your tent every night, you have to take the following precautions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Remove all scent from your campsite. Store food and toiletries (e.g. deodorant sticks, toothpaste) in sealed dry bags. Scoop any loose food debris into the fire pit and either burn it or bury it in ash.&lt;/li&gt;&lt;li&gt;Wash dishes in a bucket. Bring the bucket of dirty water to the woods several hundred feet from your campsite (usually near the latrine) before dumping it. Brush your teeth and spit out here.&lt;/li&gt;&lt;li&gt;Hang a bear bag. Attach a rope between two trees at least 12 feet (4m) apart. Rig a pulley system with carabiners. Attach your food bag to the center of the rope between the trees and pull it up. Secure the working end of the pulley rope to the tree.&lt;/li&gt;&lt;li&gt;Watch out for common pitfalls. Bears can figure out how to shred an improperly-hung rope, dropping the bag right into their claws. Bears can stand on their hind legs and shred bags that hang lower than 12 feet above the ground (the dreaded &quot;bear bag pinata&quot;).&lt;/li&gt;&lt;li&gt;Do this every night. Do not skimp. Do not skip one night because it&apos;s late, the sun is down, and you&apos;re cozy in your tent.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;All of us in the group have hung bear bags before, with varying degrees of ease. The first night, we hung our bear bags between two trees in the simplest configuration: two ropes joined in the middle by a carabiner, hauled up by hand. We hung the bag successfully, but we had trouble lifting it: everyone&apos;s food and toiletries were reasonably heavy at the start of the trip, before we had eaten all the food, and it took three people to successfully pull the bag to hanging height. We also used a &lt;a href=&quot;http://ropewiki.com/Toggle-type_device&quot;&gt;rope toggle&lt;/a&gt; to secure the free ends.&lt;/p&gt;&lt;p&gt;I had brought along a copy of &lt;a href=&quot;https://www.amazon.com/dp/B01CN2N77A/ref=dp_kinw_strp_1&quot;&gt;The Little Book of Incredibly Useful Knots&lt;/a&gt;, a wonderful book that goes into detail on various sorts of knots, bends, hitches, and more. It gave me some great ideas on how we could minimize the amount of hardware we needed to use and improve the hanging setup using only the rope we brought with us.&lt;/p&gt;&lt;p&gt;The next night, I offered to improvise a hanging method that would be simpler, use fewer carabiners, and accomplish the same security for our bear bags:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Two ropes joined in the middle by a &lt;a href=&quot;https://www.animatedknots.com/zeppelin-bend-knot&quot;&gt;Zeppelin bend&lt;/a&gt;, eliminating the need for a carabiner&lt;/li&gt;&lt;li&gt;Use of the now-free carabiner to form a pulley, easing the burden of lifting the heavy bags through a 2:1 mechanical advantage&lt;/li&gt;&lt;li&gt;Securing the working ends of the ropes to the tree using a &lt;a href=&quot;https://www.animatedknots.com/round-turn-two-half-hitches-knot&quot;&gt;round turn and two half hitches&lt;/a&gt; to clean up the slack and minimize un/winding work&lt;/li&gt;&lt;li&gt;Hanging the bags from a &lt;a href=&quot;https://www.wikiwand.com/en/Butterfly_loop&quot;&gt;butterfly loop&lt;/a&gt; to secure their position along the line between the trees&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We had success with most of these techniques, but struggled with tossing our rope over the branches in the trees available to us. We ended up skipping the pulley and muscling the bags up to height – which wasn&apos;t so bad in the end, because by the last day, we had eaten most of the food and the bags were much lighter. But the techniques I got to practice made me much more comfortable with my own ability, and I&apos;m now confident that I can nail down a two-tree pulleyed bear bag system on my own next time. (Outside of hanging the bear bags, I also learned some great knots for tying guylines to support tarps and tents!)&lt;/p&gt;&lt;p&gt;In systems engineering, especially in software, it&apos;s important to return to your assumptions and your working system frequently. Often, you&apos;ll find a live production system has evolved organically to support business needs, and developers have bolted functionality on like campers bolt carabiners to their backpacks. If you take a big step back and audit the system holistically, you can often find ways to rearchitect these legacy applications to simplify them and make them easier to work with.&lt;/p&gt;&lt;p&gt;For example, I&apos;ve worked on two major &quot;data mover&quot; systems whose purpose was to move data from one big database to another on a regular basis. They effectively served as &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/data-guide/relational-data/etl&quot;&gt;ETL&lt;/a&gt; applications: &lt;em&gt;extract &lt;/em&gt;data from the source, &lt;em&gt;transform&lt;/em&gt; it to fit the form the consumers want, &lt;em&gt;load&lt;/em&gt; it into the destination. But their business logic code had grown organically without a design document or, seemingly, a plan, and both systems were quickly becoming unmaintainable.&lt;/p&gt;&lt;p&gt;I wrote a project proposal to completely overhaul each system from first principles. The proposal included the time to fully audit each system&apos;s business responsibilities and source code, diagram and document each system, build a drop-in replacement, migrate business logic to the new app, and perform a switchover of the daily job. &quot;Big bang&quot; rewrites are often seen as productivity poison by project managers because engineers love to spend time refactoring code that they see as &quot;ugly&quot; or &quot;sub-optimal,&quot; but if you put the right plan into place beforehand, you can build your way toward success from the start. I was able to execute both of these &quot;big bang&quot; rewrites successfully by spending my time getting buy-in from my managers and team leads so that they understood how supporting this project would end up directly supporting their work.&lt;/p&gt;&lt;h1 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h1&gt;&lt;p&gt;The more time I spend inside large systems, the more I tend to think about the world around me as a large system. Even though I type glyphs into a screen all day for a living, I still find that the lessons I learn about reliability and resilience from the digital world apply to the analog one. By packing light, planning backups for my backups, and rethinking my knots, I find that my systems improve in all parts of my life. As a site reliability engineer, you&apos;re in an excellent place to help your fellow humans by sharing your favorite techniques, discussing novel approaches, and reworking systems that have been taken for granted. It&apos;s your duty to make the world a safer, more reliable place!&lt;/p&gt;</content:encoded></item><item><title>Required Viewing: Tech Talks</title><link>https://kesdev.com/required-viewing-tech-talks</link><guid isPermaLink="true">https://kesdev.com/required-viewing-tech-talks</guid><description>I recommend these tech talks to everyone I know in software. They&apos;ve been deeply influential in my engineering practices, and I think about them often when I&apos;m working to write high-quality code.Thank you to everyone who contributed to these talks – I couldn&apos;t do it without you.Rich Hickey: Simple Made EasyHow do we build systems that are ergonomic to work with without producing systems bloated wi…</description><pubDate>Thu, 16 Dec 2021 20:33:41 GMT</pubDate><content:encoded>&lt;p&gt;I recommend these tech talks to everyone I know in software. They&apos;ve been deeply influential in my engineering practices, and I think about them often when I&apos;m working to write high-quality code.&lt;/p&gt;&lt;p&gt;Thank you to everyone who contributed to these talks – I couldn&apos;t do it without you.&lt;/p&gt;&lt;h1 id=&quot;rich-hickey-simple-made-easy&quot;&gt;Rich Hickey: Simple Made Easy&lt;/h1&gt;&lt;p&gt;How do we build systems that are ergonomic to work with without producing systems bloated with complexity?&lt;/p&gt;&lt;figure class=&quot;kg-card kg-bookmark-card&quot;&gt;&lt;a class=&quot;kg-bookmark-container&quot; href=&quot;https://www.infoq.com/presentations/Simple-Made-Easy/&quot;&gt;&lt;div class=&quot;kg-bookmark-content&quot;&gt;&lt;div class=&quot;kg-bookmark-title&quot;&gt;Simple Made Easy&lt;/div&gt;&lt;div class=&quot;kg-bookmark-description&quot;&gt;Rich Hickey emphasizes simplicity’s virtues over easiness’, showing that while many choose easiness they may end up with complexity, and the better way is to choose easiness along the simplicity path.&lt;/div&gt;&lt;div class=&quot;kg-bookmark-metadata&quot;&gt;&lt;img class=&quot;kg-bookmark-icon&quot; src=&quot;https://cdn.infoq.com/statics_s2_20211214-0231/apple-touch-icon.png&quot; alt=&quot;&quot;&gt;&lt;span class=&quot;kg-bookmark-author&quot;&gt;InfoQ&lt;/span&gt;&lt;span class=&quot;kg-bookmark-publisher&quot;&gt;Rich Hickey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;kg-bookmark-thumbnail&quot;&gt;&lt;img src=&quot;https://res.infoq.com/presentations/Simple-Made-Easy/en/mediumimage/rich-hickey-big.jpg&quot; alt=&quot;&quot;&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;h1 id=&quot;richard-feldman-making-impossible-states-impossible&quot;&gt;Richard Feldman: Making Impossible States Impossible&lt;/h1&gt;&lt;p&gt;Your language&apos;s type system is a powerful tool. Learn how to leverage it to reduce the number of runtime errors in your software by encoding your domain problem into compiler-checked types.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-embed-card&quot;&gt;&lt;iframe width=&quot;356&quot; height=&quot;200&quot; src=&quot;https://www.youtube.com/embed/IcgmSRJHu_8?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/figure&gt;&lt;h1 id=&quot;brie-bunge-adopting-typescript-at-scale&quot;&gt;Brie Bunge: Adopting TypeScript at Scale&lt;/h1&gt;&lt;p&gt;Learn how Airbnb migrated from JavaScript to TypeScript. My favorite fact from this video: 38% of Airbnb production incidents could have been prevented with TypeScript&apos;s compile-time validation.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-embed-card&quot;&gt;&lt;iframe width=&quot;356&quot; height=&quot;200&quot; src=&quot;https://www.youtube.com/embed/P-J9Eg7hJwE?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/figure&gt;</content:encoded></item><item><title>On The User Interview</title><link>https://kesdev.com/on-the-user-interview</link><guid isPermaLink="true">https://kesdev.com/on-the-user-interview</guid><description>As engineers, we often have grand ideas of fantastic things we could build to solve everyone&apos;s problems. We spend a couple of months building and deploying a prototype that we&apos;re proud of, only to discover that everyone hates our solution. The tool we built is the wrong shape for the user, adds friction to their workflow, and makes their problems worse.I often forget that I should start by asking …</description><pubDate>Wed, 03 Nov 2021 01:32:50 GMT</pubDate><content:encoded>&lt;p&gt;As engineers, we often have grand ideas of fantastic things we could build to solve everyone&apos;s problems. We spend a couple of months building and deploying a prototype that we&apos;re proud of, only to discover that everyone hates our solution. The tool we built is the wrong shape for the user, adds friction to their workflow, and makes their problems worse.&lt;/p&gt;&lt;p&gt;I often forget that I should start by &lt;em&gt;asking people &lt;/em&gt;what they need before I build anything. If you ask the right questions, the users will tell you exactly what you need to build – even if &lt;em&gt;they&lt;/em&gt; don&apos;t yet know what they want.&lt;/p&gt;&lt;p&gt;A structured user interview will give you the unbiased answers you need to build the right thing and make your people happy. Here are my tips on how to conduct user interviews successfully.&lt;/p&gt;&lt;h1 id=&quot;don-t-over-specify-your-solution&quot;&gt;Don&apos;t over-specify your solution&lt;/h1&gt;&lt;p&gt;Your manager has given you the time and space to build a product that solves someone&apos;s problem. You probably have an idea in your head of what a solution might be. But if you&apos;re not the user, your idea of a solution is probably &lt;em&gt;very&lt;/em&gt; wrong.&lt;/p&gt;&lt;p&gt;If you start building out your idea immediately, you&apos;ll probably end up building something that:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Introduces too much friction and frustration&lt;/li&gt;&lt;li&gt;Interferes with an existing workflow&lt;/li&gt;&lt;li&gt;Isn&apos;t suited for your users&apos; skill level&lt;/li&gt;&lt;li&gt;Solves a problem that exists, but isn&apos;t the one your users &lt;em&gt;want&lt;/em&gt; you to solve&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So it&apos;s important to structure your process around what the &lt;em&gt;user&lt;/em&gt; wants, not what you &lt;em&gt;think&lt;/em&gt; they want. Here&apos;s an example of an over-specified early solution to building a CI/CD tool:&lt;/p&gt;&lt;blockquote&gt;We are building a deployment pipeline that takes a user&apos;s Docker container, turns it into a Kubernetes Deployment with Service and Ingress, and gets it online at a public URL.&lt;/blockquote&gt;&lt;p&gt;To focus on the user&apos;s needs first, simplify:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&quot;takes a user&apos;s Docker container:&quot; What if the user uses LXC or .deb packages to deploy their software? Instead, say &quot;takes a user&apos;s &lt;em&gt;software.&lt;/em&gt;&quot;&lt;/li&gt;&lt;li&gt;&quot;turns it into a Kubernetes Deployment with Service and Ingress:&quot; What if they&apos;re deploying their app to a different system, like Fargate? Is supporting Fargate deployments within your team&apos;s scope? If not, do you have a plan to help the team into Kubernetes and make sure they understand how it works? Instead, say &quot;deploys it to cloud compute.&quot;&lt;/li&gt;&lt;li&gt;&quot;gets it online at a public URL:&quot; Is this a public app? Does the user &lt;em&gt;want&lt;/em&gt; a public URL to access their app? Are there security concerns if you take their internal/VPN apps and put them online? Instead, say &quot;exposes it so the team can use it.&quot;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;After de-specifying this solution, it sounds something like this:&lt;/p&gt;&lt;blockquote&gt;We are building a deployment pipeline that takes a user&apos;s &lt;strong&gt;software&lt;/strong&gt; and &lt;strong&gt;deploys it to cloud compute&lt;/strong&gt;, then &lt;strong&gt;exposes it so the team can use it.&lt;/strong&gt;&lt;/blockquote&gt;&lt;p&gt;Great! Now you have an open-ended shape of a solution, rather than a concrete solution. As you perform your interviews, you have an idea of what &lt;em&gt;kind of thing&lt;/em&gt; you&apos;re building, and the feedback from your users will help you refine this idea.&lt;/p&gt;&lt;h1 id=&quot;write-your-questions-up-front&quot;&gt;Write your questions up front&lt;/h1&gt;&lt;p&gt;We all carry personal bias into everything we do. When we speak to people, the conversation changes based on the other person&apos;s appearance, mood, and perceived experience with the subject at hand.&lt;/p&gt;&lt;p&gt;When we perform user interviews, our bias can prevent us from getting to the important parts of a conversation we want to have. We might assume a backend developer knows all about load balancing, only to find out later that they&apos;ve never heard of it. We might not ask a data scientist about a deployment pipeline because we assume one of their peers handles that for them. It might turn out that they&apos;re the only person maintaining the production deploys for the team.&lt;/p&gt;&lt;p&gt;To avoid your bias keeping you from discovering what you need to know, write your questions &lt;em&gt;before&lt;/em&gt; a user interview starts. Some of my most successful user interview questions look like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Tell me about how you deploy software into production today.&lt;/li&gt;&lt;li&gt;How is your team&apos;s morale when it comes to deploying software? Why?&lt;/li&gt;&lt;li&gt;How does your team like the existing process?&lt;/li&gt;&lt;li&gt;What pain points do you encounter with this process?&lt;/li&gt;&lt;li&gt;If you could wave a magic wand and instantly fix three things in your system, what would they be?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I find that people are not often able to come up with a concrete solution to their problems and &lt;em&gt;how&lt;/em&gt; to fix them. But most people do a great job of telling you &lt;em&gt;what&lt;/em&gt; is wrong. When someone encounters a point of friction every day for a year, it starts to burn itself into their memory. They&apos;re usually ecstatic to find someone with an open ear, ready to write down their complaints. I always make sure to include open-ended questions about what a user finds annoying, frustrating, or painful.&lt;/p&gt;&lt;p&gt;A couple of other tips:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Keep your questions open-ended so that the user can tell you how they feel.&lt;/li&gt;&lt;li&gt;Avoid simple questions that lead to yes/no answers.&lt;/li&gt;&lt;li&gt;Don&apos;t write questions that focus on a specific piece of infrastructure or technology, or on a specific solution you have in mind.&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;follow-don-t-lead-the-subject&quot;&gt;Follow, don&apos;t lead, the subject&lt;/h1&gt;&lt;p&gt;Finally, it&apos;s important to remember that a user interview is about the &lt;strong&gt;user,&lt;/strong&gt; not your team&apos;s mandate or goals. You&apos;re here to discover what pains them so that you can empathize with them and build something to solve their pain. Remember this during your user interview. Try to stick to your question list when you need to move onto a new topic.&lt;/p&gt;&lt;p&gt;This shouldn&apos;t prevent you from following their lead. If a user has strong feelings about a piece of infrastructure, it&apos;s a great idea to ask them to elaborate. Ask them why and how often they feel that way. If you pick up on an unusual emotion—say, anger or despair—consider the context, and consider adding a question to your list to help you explore these feelings with future interview subjects.&lt;/p&gt;&lt;p&gt;As long as you&apos;re asking open-ended questions, starting with a common base to expose how users truly feel, and working to discover the thoughts of your users in an unbiased way, you&apos;re doing a great job.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;Best of luck with your user interviews! I wish I had more resources to share on this topic – my experience with user interviewing is mostly practical in my work, alongside a couple of UX classes in college. If you have resources that you found helpful for conducting user interviews, I&apos;d love for you to share them with me at &lt;a href=&quot;mailto:matt@mplewis.com&quot;&gt;matt@mplewis.com&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>My Site Reliability Resources</title><link>https://kesdev.com/my-site-reliability-resources</link><guid isPermaLink="true">https://kesdev.com/my-site-reliability-resources</guid><description>This is my list of resources for folks who want to learn more about site reliability engineering, embracing risk, and building observable systems.Last updated 2020-08-27.Site Reliability Engineering: Embracing Risk: The Site Reliability Engineering book outlines the Google Way for keeping the lights on. It&apos;s an excellent book and you should read all of it if you&apos;re interested in building reliable …</description><pubDate>Fri, 27 Aug 2021 19:48:09 GMT</pubDate><content:encoded>&lt;p&gt;This is my list of resources for folks who want to learn more about site reliability engineering, embracing risk, and building observable systems.&lt;/p&gt;&lt;p&gt;Last updated 2020-08-27.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://sre.google/sre-book/embracing-risk/&quot;&gt;Site Reliability Engineering: Embracing Risk&lt;/a&gt;: The Site Reliability Engineering book outlines the Google Way for keeping the lights on. It&apos;s an excellent book and you should read all of it if you&apos;re interested in building reliable software. This chapter is a great introduction to the idea of &quot;one to zero:&quot; understanding that components of a system are inherently &lt;strong&gt;unreliable,&lt;/strong&gt; rather than assuming the building blocks you put together will always work.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://charity.wtf/2019/05/01/friday-deploy-freezes-are-exactly-like-murdering-puppies/&quot;&gt;Friday Deploy Freezes Are Exactly Like Murdering Puppies&lt;/a&gt;: Charity Majors is the &lt;a href=&quot;https://www.honeycomb.io/teammember/charity-majors/&quot;&gt;CTO of Honeycomb,&lt;/a&gt; where they work hard to make your systems observable so you can make them reliable. &lt;a href=&quot;https://twitter.com/mipsytipsy&quot;&gt;(Follow her on Twitter.)&lt;/a&gt; This blog post discusses why deployments are the heartbeat of your company, and why good judgment matters more than prescriptive policies.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://charity.wtf/2019/10/28/deploys-its-not-actually-about-fridays/&quot;&gt;Deploys: It’s Not Actually About Fridays&lt;/a&gt;: Charity writes about how system reliability and rapid, frequent deployments are tightly linked, and how a culture of observability-driven development is key to the success of your systems and your organization.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://charity.wtf/2018/08/19/shipping-software-should-not-be-scary/&quot;&gt;Shipping Software Should Not Be Scary&lt;/a&gt;: Charity discusses ownership and agency surrounding code and systems, why your senior engineers must be able to deploy and debug their own code, and why &lt;strong&gt;nothing is production except production.&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://itrevolution.com/the-phoenix-project/&quot;&gt;The Phoenix Project&lt;/a&gt;: In this novel, an IT manager is promoted to CTO just in time to take the flak for a big-bang system rewrite and catastrophic production deploy. This book is required reading for anyone looking to manage an engineering organization. It discusses the ways in which your project flow can be seen as an assembly line in a factory, and how disruptions to your flow make it difficult to see the true causes behind your group&apos;s failure to deliver. &lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title>Burner Phones for Activists</title><link>https://kesdev.com/burner-phones-for-activists</link><guid isPermaLink="true">https://kesdev.com/burner-phones-for-activists</guid><description>If you participate in street activism and attend actions, you probably understand the value of having a smartphone on you with an active data plan. The Signal secure messenger runs purely on data networks, and it can be useful to have a live mapping app in case you&apos;re unfamiliar with the area or need to find a way home.Many activists recommend you do not bring your everyday phone with you to actio…</description><pubDate>Sun, 17 Jan 2021 23:03:01 GMT</pubDate><content:encoded>&lt;p&gt;If you participate in street activism and attend actions, you probably understand the value of having a smartphone on you with an active data plan. The Signal secure messenger runs purely on data networks, and it can be useful to have a live mapping app in case you&apos;re unfamiliar with the area or need to find a way home.&lt;/p&gt;&lt;p&gt;Many activists recommend you do &lt;strong&gt;not&lt;/strong&gt; bring your everyday phone with you to actions. If you do, you run the risk of law enforcement having a &lt;a href=&quot;https://www.wikiwand.com/en/Stingray_phone_tracker&quot;&gt;Stingray&lt;/a&gt; device on site. If the action is deemed unlawful, LEOs might cross-reference the list of serial numbers detected in the area with lists of known cell phone subscribers. This data might be used to start building a case against you.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Learn more about Stingray phone surveillance:&lt;/em&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://theintercept.com/2020/07/31/protests-surveillance-stingrays-dirtboxes-phone-tracking/&quot;&gt;How Cops Can Secretly Track Your Phone&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://www.eff.org/deeplinks/2020/06/quick-and-dirty-guide-cell-phone-surveillance-protests&quot;&gt;A Quick and Dirty Guide to Cell Phone Surveillance at Protests&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://arstechnica.com/tech-policy/2016/08/to-find-suspect-city-cops-ran-stingray-for-hours-then-called-in-fbi/&quot;&gt;FBI’s stingray quickly found suspect after local cops’ device couldn’t&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A &lt;strong&gt;burner phone&lt;/strong&gt; is a device that is not tied to your identity that you may not plan to keep forever. Since this is for the purpose of modern activism, we&apos;ll look for a smartphone that runs Signal. Here&apos;s what you&apos;ll need to do:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Buy a prepaid phone SIM&lt;/li&gt;&lt;li&gt;Buy a smartphone&lt;/li&gt;&lt;li&gt;Activate your phone plan&lt;/li&gt;&lt;li&gt;Install Signal&lt;/li&gt;&lt;/ol&gt;&lt;h1 id=&quot;buy-a-prepaid-phone-sim&quot;&gt;Buy a prepaid phone SIM&lt;/h1&gt;&lt;p&gt;When you buy stuff and you don&apos;t want to have it associated with your identity, the golden rule is &lt;strong&gt;use cash. &lt;/strong&gt;You want to avoid a paper trail, so go to your favorite no-fee ATM, withdraw some cash, and only use cash for the following purchases.&lt;/p&gt;&lt;p&gt;I recommend you select &lt;strong&gt;&lt;a href=&quot;https://www.tracfone.com/&quot;&gt;Tracfone&lt;/a&gt; &lt;/strong&gt;as your wireless carrier. They&apos;re inexpensive, run on a variety of wireless networks, and are almost definitely compatible with the smartphone you will buy. (My SIM card kit came with three different SIM cards for different networks.)&lt;/p&gt;&lt;p&gt;Tracfone SIM cards are sold as a &lt;strong&gt;BYOP (Bring Your Own Phone)&lt;/strong&gt; kit. You can buy a smartphone/SIM card bundle, but I recommend against this as the phones are generally much more expensive when purchased through Tracfone. (Tracfone also sells odd models of Android that almost certainly will not receive security updates after too long.)&lt;/p&gt;&lt;p&gt;Go to your local tech superstore (e.g. Best Buy), walk over to the Tracfone section, and purchase a BYOP SIM card kit for $1. You&apos;re done.&lt;/p&gt;&lt;h1 id=&quot;buy-a-smartphone&quot;&gt;Buy a smartphone&lt;/h1&gt;&lt;p&gt;When buying a smartphone, consider the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Does this do what I want?&lt;/strong&gt; The device should run an up-to-date operating system (iOS, Android). Google it and make sure it was made within the last five years.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Is it secure?&lt;/strong&gt; Before you buy your device, make sure you can still update it. Android phones typically last two years before they stop receiving security updates from Google and the manufacturer. iPhones typically last four years as Apple puts a higher focus on keeping their devices running for longer. (This is why I recommend iPhones over Android devices.)&lt;br&gt;&lt;br&gt;If you are a power user, you might be able to flash a clean updated ROM onto your Android; make sure it&apos;s compatible before you buy.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Is it in good shape?&lt;/strong&gt; Sure, you won&apos;t use this thing every day, but you want the screen to be free of cracks, the battery to be in good condition, and all the components in working order. (And don&apos;t forget to buy a case to protect it from drops while you&apos;re on the move.)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Here are three places you could buy a used smartphone:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Locally on Craigslist.&lt;/strong&gt; Craigslist people take cash, which is ideal, and you can inspect the phone in person before taking it home. But you might not be able to find a wide variety of devices in your area.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;In a bundle with your SIM card.&lt;/strong&gt; But odds are this is much more expensive than purchasing a similar phone in a personal transaction.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;On eBay.&lt;/strong&gt; This is not ideal, because you&apos;ll have to pay via PayPal. But the prices and selection will be better than shopping locally.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;After you receive your phone, make sure to wipe it completely before setup, and update the OS as soon as possible.&lt;/p&gt;&lt;h1 id=&quot;activate-your-phone-plan&quot;&gt;Activate your phone plan&lt;/h1&gt;&lt;p&gt;Your Tracfone plan has &lt;strong&gt;service days&lt;/strong&gt; as well as &lt;strong&gt;minutes&lt;/strong&gt; and &lt;strong&gt;data.&lt;/strong&gt; Service days correspond to how long your phone number and service will remain activated. Minutes and data are how much of your prepaid quota you have available for use. When your plan runs low on any of these, you purchase Tracfone prepaid cards to &lt;em&gt;top-up&lt;/em&gt; your plan.&lt;/p&gt;&lt;p&gt;There are two ways to top-up your plan without leaving a paper trail:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Buy a Tracfone prepaid card with cash&lt;/li&gt;&lt;li&gt;Buy a Visa/MasterCard debit gift card with cash, and use that to top-up on the Tracfone website&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Tracfone cards are available at many gas stations, pharmacies, and grocery stores. They look like this:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2021/01/tfn.jpg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;877&quot;&gt;&lt;figcaption&gt;The packaging for a Tracfone retail gift card&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;But if you choose to buy a Tracfone prepaid card with cash, you may run into some issues:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;These cards usually come with service days and minutes, but not data. You need data to use Signal.&lt;/li&gt;&lt;li&gt;The &quot;pick-your-own-size&quot; cards (e.g. the card itself has barcodes for various denominations of &quot;virtual top-up cards&quot;) must be activated at the register. The clerk will ask you for your phone number and name, and you don&apos;t want your name on record here.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I recommend the following process:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;To &lt;strong&gt;activate&lt;/strong&gt; your plan for the first time, purchase the cheapest Tracfone card you can find that includes plan minutes. (Again, use cash.)&lt;/li&gt;&lt;li&gt;To &lt;strong&gt;top-up&lt;/strong&gt; your plan with data, purchase a $100 prepaid debit card. Then, go to the Tracfone website and refill your account using the debit card. Tracfone supports prepaid debit cards as a method of payment, and you don&apos;t have to provide your real name to complete the checkout process. (Your debit card may take some time to activate after you purchase it, so don&apos;t worry; just wait two hours and try again.)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;At the time of writing, Tracfone offers the following refill on their site:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2021/01/tfn15.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;295&quot;&gt;&lt;figcaption&gt;$15: 30 days service, 500 minutes, 500 texts, 500 MB data&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;If you refill with three of these, you&apos;ll get 90 days of service and 1.5 GB of data total for ~$50 after tax. This means you can get six months of service out of your $100 debit card. And as long as you set your phone up and install apps on wifi, and only use data for messenger apps, that data should last you a while.&lt;/p&gt;&lt;p&gt;Don&apos;t forget: you&apos;ll need to top up your phone to keep your service active, so pick up debit cards with cash before you need them.&lt;/p&gt;&lt;h1 id=&quot;install-signal&quot;&gt;Install Signal&lt;/h1&gt;&lt;p&gt;Now that your phone is set up, it&apos;s time to install Signal so you can communicate with your comrades. If you&apos;re on Android, you can &lt;a href=&quot;https://signal.org/android/apk/&quot;&gt;download the APK directly&lt;/a&gt; so that you don&apos;t have to sign into the Google Play Store with a real account.&lt;/p&gt;&lt;p&gt;If you&apos;re on iPhone, you will need an Apple account to download Signal. So don&apos;t use your everyday account here. Create a new email address for an alternate identity of yours through &lt;a href=&quot;https://protonmail.com/&quot;&gt;ProtonMail&lt;/a&gt;, a privacy-focused email provider with free plans available. Then use that email and identity to create an Apple account. Finally, sign into the App Store with this email and install Signal. &lt;em&gt;Don&apos;t do anything with these accounts on any other device!&lt;/em&gt;&lt;/p&gt;&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;&lt;p&gt;At this point, you have an activated smartphone, with a data plan and Signal, that are not tied to your true identity. The Intercept has a great article on true anonymity and privacy that you should read before you set up your device: &lt;a href=&quot;https://theintercept.com/2020/06/15/protest-tech-safety-burner-phone/&quot;&gt;Protesters, Here’s How to Set Up a Cheap Burner Phone&lt;/a&gt;. In particular, read the section titled &lt;strong&gt;A Note About Anonymity, &lt;/strong&gt;as it raises important concerns about how you use your new device.&lt;/p&gt;&lt;p&gt;Be safe out there, and keep your comrades safe!&lt;/p&gt;</content:encoded></item><item><title>I&apos;m Scared of Learning Hard Stuff</title><link>https://kesdev.com/im-scared-of-learning-hard-stuff</link><guid isPermaLink="true">https://kesdev.com/im-scared-of-learning-hard-stuff</guid><description>At Gusto, I work with engineers from diverse backgrounds of experience. Some of us came from the typical Silicon Valley companies like Google and Facebook, while others joined Gusto as their first startup. I started in a consulting shop in Minneapolis fresh out of college. It gave me the confidence to learn more on my own, but also taught me to learn in a way that I’m still trying to recover from.…</description><pubDate>Tue, 01 May 2018 16:32:04 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;At &lt;a href=&quot;https://gusto.com&quot;&gt;Gusto&lt;/a&gt;, I work with engineers from diverse backgrounds of experience. Some of us came from the typical Silicon Valley companies like Google and Facebook, while others joined Gusto as their first startup. I started in a consulting shop in Minneapolis fresh out of college. It gave me the confidence to learn more on my own, but also taught me to learn in a way that I’m still trying to recover from. With the support of my colleagues and mentors, I’m learning how to learn in a way that will make me a better engineer and leader.&lt;/p&gt;
&lt;p&gt;In my first job, I built mobile and web software for connected IoT devices that used Bluetooth. I worked on very small teams and had near-total ownership over my projects. This meant I got to learn in a way I really enjoyed: picking a greenfield approach, learning iOS or Android, and building with the shiniest new tools. In consulting, we weren’t often concerned with the legacy of our code – if the customer accepted delivery, we had finished our project and could move on.&lt;/p&gt;
&lt;h1 id=&quot;myapproachfallsapart&quot;&gt;My Approach Falls Apart&lt;/h1&gt;
&lt;p&gt;When I moved to Gusto, I entered a gauntlet of new and foreign technology. Gusto&apos;s engineers are full-stack, and my mentor asked me to start where I was weakest: a JavaScript frontend app built with React, Backbone, and Bootstrap. I did my best to adopt the organization&apos;s best practices around testing and code review. I became comfortable in the dev tools as well as the Rails console.&lt;/p&gt;
&lt;p&gt;But as I moved into the thicker parts of the payroll domain, my team asked me to debug thornier issues. I had to figure out why a payment wasn&apos;t sent as expected, or why a feature I wrote was locking customers out of the app. This meant I had to dig into the five-year-old Gusto codebase. In contrast, I&apos;d written the majority of my past codebases from scratch, so I knew about every dark corner harboring bugs. It was a shock to jump into Gusto and find out I knew nothing about the mechanisms I needed to fix. The codebase was complex and many of the authors had left the company. There was a lot I didn&apos;t know!&lt;/p&gt;
&lt;p&gt;I coped with my inexperience by avoiding the problem. When we assigned tasks during sprint planning, I asked for the ones within my comfort zone. When fires happened in production or I needed to make important design decisions, I deferred to anyone who was confident about tackling the problem.&lt;/p&gt;
&lt;iframe src=&quot;https://giphy.com/embed/v0eHX3n28wvoQ&quot; width=&quot;480&quot; height=&quot;324&quot; frameborder=&quot;0&quot; class=&quot;giphy-embed&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;Pictured: My reaction when asked to make hard decisions and work with scary code.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My coworkers noticed that I often spent time avoiding hard problems by working on other problems I enjoyed more. They saw me as someone who wrote excellent tests but became distracted when it came to building entire features. In my performance review, my PE (People Empowerer -- what we call a manager at Gusto) and I discussed my peer feedback:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“You delegated a key decision on this Payroll Engineering story to an engineer that is your junior.”&lt;/p&gt;
&lt;p&gt;“You spent a couple of days optimizing the build process, but you didn’t finish your stories for the week.”&lt;/p&gt;
&lt;p&gt;“Your teammates don’t feel you’re focusing on the work you’re given, and they get the impression you may not be pulling your weight.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When everything added up, I found myself in the middle of the pack at Gusto. I wasn&apos;t yet reliable enough to be a senior-level engineer.&lt;/p&gt;
&lt;p&gt;I wanted to do better. I thought about how working in a codebase with history and complexity made me feel. I discussed my feelings about code and engineering with my PE frankly and openly. I found that I&apos;m scared of learning hard things.&lt;/p&gt;
&lt;h1 id=&quot;figuringoutwhatswrong&quot;&gt;Figuring Out What’s Wrong&lt;/h1&gt;
&lt;p&gt;At my old gig, I could almost always build a project from scratch. Because I built them myself, I understood everything about how they worked. Gusto presented me with intense problem domains, dozens of engineers committing at once, and a complex series of interlocked systems with side effects. It was petrifying to work with this codebase. What if I made a mistake? I could annoy my coworkers, upset our customers, and cost the company real money.&lt;/p&gt;
&lt;p&gt;I found it easier to change only the parts I knew. But that caused me to leave projects without a deep understanding of their domain. I couldn&apos;t apply that knowledge to new projects I joined, even ones adjacent to my past work. And I felt less effective coming back to fix bugs in code I had written before.&lt;/p&gt;
&lt;p&gt;My trepidation over working in an established codebase had led to a phobia. Our primary repo has over five hundred thousands of lines of Ruby code, interfaces with dozens of state and federal agencies, and has thousands of edge cases. I needed to make conscious decisions to get over my fear of the unknown and build my knowledge of the scary, cobwebbed corners of our payroll codebase. So my PE and I came up with actions I could consciously take to drive my independent study and help me learn through my work.&lt;/p&gt;
&lt;iframe src=&quot;https://giphy.com/embed/3R9LDINpbGX2o&quot; width=&quot;480&quot; height=&quot;265&quot; frameborder=&quot;0&quot; class=&quot;giphy-embed&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;Pictured: I investigate tactical approaches to independent study and the growth-oriented mindset.&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&quot;changingmyapproach&quot;&gt;Changing My Approach&lt;/h1&gt;
&lt;p&gt;I aimed for the hard stuff on purpose. For example, I preferred stories that dealt with our automated tax filing system, rather than stories around building the frontend components I had grown comfortable with. During sprint planning meetings, I chose to take the tasks I knew nothing about. I hoped to learn more about the unknowns and gain a better understanding of our systems as a whole.&lt;/p&gt;
&lt;p&gt;I spent more time investigating before asking for help. I used to spend about ten minutes investigating a difficult problem before asking for guidance. To change that, I came up with my personal Two-Hour Rule: I had to work at a problem for two hours before deciding it was time to ask someone for help.&lt;/p&gt;
&lt;p&gt;I chose to avoid asking for help until I could describe my problem in detail. I used to ask for help after a brief period of not understanding the code I was looking at. To remedy this, I spent more time observing and thinking, then describing my understanding of the codebase on paper. I jotted down diagrams in a small notebook to help organize my thoughts and my mental models. For example, when I worked on a feature that interacted with our complex Historical Payroll model, I wrote pages of notes and diagrams that I eventually synthesized into a page of documentation for our company wiki.&lt;/p&gt;
&lt;p&gt;To me, this was the road less traveled, but I found it made an immediate difference in the quality of my work. When I chose to stick with a difficult bug instead of asking for help, I often found I learned a lot about how the bug happened and how to fix it. In the end, I realized I didn’t need help after all. When I did need to ask for help, I could describe the problem completely, accurately, and point at the exact bits of code I didn&apos;t understand. When I left a project, I was able to write clear, concise documentation about what the code did and why – even if I hadn&apos;t written it originally.&lt;/p&gt;
&lt;iframe src=&quot;https://giphy.com/embed/UyPpKZScnl7na&quot; width=&quot;480&quot; height=&quot;270&quot; frameborder=&quot;0&quot; class=&quot;giphy-embed&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;Pictured: My new approach to diving deep and documenting my work yields results.&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&quot;lookingforward&quot;&gt;Looking Forward&lt;/h1&gt;
&lt;p&gt;As I move forward, I want to learn and grow in my abilities.  It’s only been a short time since I adopted my new strategies, but I’ve already seen improvement in my ability to grok complex problems in my projects. I want to become a great engineer at Gusto, I want to grow as a skilled engineer in general, and I want to learn how to learn so I can continue my trajectory. I look forward to seeing how the skills I’m teaching myself will help me become a better leader at Gusto and wherever I go.&lt;/p&gt;
&lt;p&gt;Gusto has been an incredible place for me to learn the skills around working with a large team on complex problems. I&apos;ve received nothing but support as I grow in my abilities and develop my strengths. Come work with me to create a world where work empowers a better life! &lt;a href=&quot;https://gusto.com/about/careers&quot;&gt;Check out our open engineering positions in San Francisco and Denver.&lt;/a&gt;&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>An Opinionated Guide to Fixie Commuters</title><link>https://kesdev.com/an-opinionated-guide-to-fixie-commuters</link><guid isPermaLink="true">https://kesdev.com/an-opinionated-guide-to-fixie-commuters</guid><description>Two months ago, I picked up a basic little road bike and turned it into a fixie commuter. What&apos;s so great about a fixie? They&apos;re simple Fixed-gear bikes are way simpler than geared bikes. Reducing the number of moving parts on your bike means you have less stuff to maintain. Less stuff is liable to break. When stuff does break, you have less cascading failures to fix before you can get back on the…</description><pubDate>Fri, 04 Nov 2016 04:52:03 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;Two months ago, I picked up a basic little road bike and turned it into a fixie commuter.&lt;/p&gt;
&lt;h1 id=&quot;whatssogreataboutafixie&quot;&gt;What&apos;s so great about a fixie?&lt;/h1&gt;
&lt;h2 id=&quot;theyresimple&quot;&gt;They&apos;re simple&lt;/h2&gt;
&lt;p&gt;Fixed-gear bikes are way simpler than geared bikes. Reducing the number of moving parts on your bike means you have less stuff to maintain. Less stuff is liable to break.&lt;/p&gt;
&lt;p&gt;When stuff does break, you have less cascading failures to fix before you can get back on the road. The last time I tuned up a hybrid bike, I had to clean the bottom bracket, replace and jerry-rig a new front chainring, and recalibrate the rear derailleur to get it working again. The last time I worked on my fixie, I replaced the rear cog and lubed the chain.&lt;/p&gt;
&lt;p&gt;When you turn a road bike into a fixie, here&apos;s what you&apos;ll replace:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rear cassette → Single cog&lt;/li&gt;
&lt;li&gt;Rear derailleur → Nothing!&lt;/li&gt;
&lt;li&gt;Three front chainrings → One front chainring&lt;/li&gt;
&lt;li&gt;Rear brake → Nothing! (Aesthetics! If you want.)&lt;/li&gt;
&lt;li&gt;Front brake → Nothing! (Don&apos;t do this. You should &lt;strong&gt;really&lt;/strong&gt; have a brake.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;theyreromantic&quot;&gt;They&apos;re romantic&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I still feel that variable gears are only for people over forty-five. Isn&apos;t it better to triumph by the strength of your muscles than by the artifice of a derailer?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://www.sheldonbrown.com/fixeda.html&quot;&gt;There is an almost mystical connection&lt;/a&gt; between a fixed-gear cyclist and bicycle: it feels like an extension of your body to a greater extent than does a freewheel-equipped machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Riding a fixie is a different experience. Pedal faster to go faster. You&apos;ll feel if your rear wheel slips on gravel because your feet will slip too. Get ready to spin your legs when you hit the downhill!&lt;/p&gt;
&lt;h1 id=&quot;howtoconvertaroadbike&quot;&gt;How to Convert a Road Bike&lt;/h1&gt;
&lt;p&gt;The easiest way to start riding a fixie is to buy an already-built fixie. The second easiest way is to buy a cheap vintage road bike and take off most of the parts.&lt;/p&gt;
&lt;p&gt;You&apos;ll be looking for a bike with horizontal or mostly-horizontal dropouts. That&apos;s because you&apos;re removing the derailleur, which tensions the chain, so the only way to tension the chain is to move the rear wheel forward and back in the frame.&lt;/p&gt;
&lt;p&gt;Here are the types of dropouts that will work and won&apos;t work to tension your chain:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.sheldonbrown.com/gloss_dr-z.html#dropout&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2018/07/Sheldon_Brown_s_Bicycle_Glossary_Dr_-_z.png&quot; alt=&quot;Types of Dropouts&quot; loading=&quot;lazy&quot;&gt; From Sheldon Brown&apos;s site&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;stripthenotfixieparts&quot;&gt;Strip the not-fixie parts&lt;/h2&gt;
&lt;p&gt;There are a lot of parts to strip! You&apos;ll need to remove these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rear wheel (the entire thing)&lt;/li&gt;
&lt;li&gt;Front and rear derailleur&lt;/li&gt;
&lt;li&gt;Shifter levers&lt;/li&gt;
&lt;li&gt;Shifter cables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And optionally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rear brake (if you want less stuff on your bike)&lt;/li&gt;
&lt;li&gt;Front brake (again, please don&apos;t. Keep the front brake.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take off these parts, one by one, and put them in a bin to donate to your favorite community bike shop. I gave mine to &lt;a href=&quot;http://bikestogether.org/&quot;&gt;Bikes Together&lt;/a&gt;, an awesome Denver non-profit.&lt;/p&gt;
&lt;h2 id=&quot;resizethechain&quot;&gt;Resize the chain&lt;/h2&gt;
&lt;p&gt;Your old chain is too long for your fixie. You need to shorten it so that your chain is tight between your chainring and rear cog.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=GrJSWY9FWFk&quot;&gt;Here&apos;s how to shorten your chain.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;And &lt;a href=&quot;https://www.youtube.com/watch?v=QuLgpvrQzcI&quot;&gt;here&apos;s how to easily tension your chain.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&apos;d like to replace the chain, now is a great time. If you choose to do this, &lt;a href=&quot;https://bicycles.stackexchange.com/a/13080/13928&quot;&gt;here&apos;s how to tell what width chain you have.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;replaceoneorbothwheels&quot;&gt;Replace one or both wheels&lt;/h2&gt;
&lt;p&gt;Your geared road bike&apos;s rear wheel has a cassette. This lets you change gears using the derailleur. It slides onto a &lt;a href=&quot;http://www.parktool.com/blog/repair-help/freehub-service&quot;&gt;freehub&lt;/a&gt; which lets you coast. You want neither of these things – you don&apos;t want your fixie to coast!&lt;/p&gt;
&lt;p&gt;Instead, you want a rear wheel with a fixed hub and a single cog on each side. You&apos;ll often see these wheels called &quot;flip-flop,&quot; &quot;fixed-free&quot;, or &quot;fixed-fixed.&quot; Flip-flop and fixed-free mean the same thing: one side has a fixed cog that moves the pedals with the wheel, and one side has a cog that lets you coast. Fixed-fixed wheels have a fixed cog on both sides.&lt;/p&gt;
&lt;p&gt;If your bike has its original parts, it might still have 27&quot; wheels. It&apos;s a lot harder to find modern tubes and tires for a 27&quot; wheel than a standard road bike 700c wheel. So now is a great time to upgrade to a 700c rear wheel. And as long as you&apos;re at it, you might as well replace the front wheel as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&apos;t forget long-reach brake calipers:&lt;/strong&gt; The radius of a 700c wheel is &lt;a href=&quot;http://bicycles.stackexchange.com/questions/10170/how-do-i-know-if-i-can-replace-the-27-inch-wheels-with-700c-wheels-on-my-bike&quot;&gt;4mm smaller&lt;/a&gt; than that of a 27&quot; wheel. This means your brake pads will have to reach 4mm farther to make proper contact with the rims.&lt;/p&gt;
&lt;p&gt;Your existing calipers might not have 4mm of wiggle room to work with your new wheels. Make sure to check before you buy. If you need more reach, there are &lt;a href=&quot;http://www.sheldonbrown.com/harris/brake-calipers.html&quot;&gt;several options available&lt;/a&gt; for long-reach brake calipers.&lt;/p&gt;
&lt;h2 id=&quot;getnewtiresandtubes&quot;&gt;Get new tires and tubes&lt;/h2&gt;
&lt;p&gt;Hooray! Your new wheels are here. Now you get to experience the candy shop that is tire selection.&lt;/p&gt;
&lt;p&gt;There are a couple of factors that all trade off against each other when you&apos;re buying tires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flat protection:&lt;/strong&gt; Heavier, more armored tires can roll over sharper stuff without damaging the tube inside.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rolling resistance:&lt;/strong&gt; Heavier tires have more rolling resistance. This saps all your precious forward momentum and means you spend more energy going the same distance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agility:&lt;/strong&gt; Some tires are more nimble around corners and in slick conditions than others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight:&lt;/strong&gt; More weight generally correlates to more durable and longer-lasting. It also makes you slower.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Price:&lt;/strong&gt; You get what you pay for. Cheap tires won&apos;t be good at anything listed above. But even expensive tires will wear out down the line.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To find a tire that&apos;s best for you, check out &lt;a href=&quot;https://www.bicyclerollingresistance.com/road-bike-reviews&quot;&gt;Rolling Resistance reviews&lt;/a&gt;. They cover all the tradeoffs of every road bike tire you should consider.&lt;/p&gt;
&lt;h2 id=&quot;replacetheancientbrakeleversandbartape&quot;&gt;Replace the ancient brake levers and bar tape&lt;/h2&gt;
&lt;p&gt;Vintage brake levers and cables look like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bikeforums.net/classic-vintage/681558-non-aero-cable-routing-question.html&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2018/07/routing-vintage.jpg&quot; alt=&quot;Vintage cable routing&quot; loading=&quot;lazy&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If your bike comes with these, they are probably original and they probably really suck at translating hand pull into braking action. And if you&apos;re like me, you want to minimize the exposed cables on your clean frame.&lt;/p&gt;
&lt;p&gt;I like it when my cables pop out from under the bar tape:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.velominati.com/2013/09/la-vie-velominatus-cable-obsession/&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2018/07/modern-routing.jpg&quot; alt=&quot;Modern cable routing&quot; loading=&quot;lazy&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Consider buying &lt;a href=&quot;https://smile.amazon.com/Tektro-RL340-Brake-Levers-Black/dp/B003U9ROKC/&quot;&gt;road bike brake levers&lt;/a&gt; so you can safely ride &lt;a href=&quot;http://lovelybike.blogspot.com/2012/06/drop-bar-hand-positions-introduction.html&quot;&gt;on the hoods&lt;/a&gt;, and &lt;a href=&quot;https://smile.amazon.com/gp/product/B001JI8SKG/&quot;&gt;a crosstop lever&lt;/a&gt; if you ride on the tops a lot.&lt;/p&gt;
&lt;p&gt;Once you have those, you need to &lt;a href=&quot;https://www.youtube.com/watch?v=geSok-YQN-U&quot;&gt;install the levers&lt;/a&gt;, then &lt;a href=&quot;https://www.youtube.com/watch?v=5MzIiv7pewE&quot;&gt;wrap the bars over the brake cables.&lt;/a&gt; Although it can be nervewracking, this is actually a lot of fun. Find a color you like at your LBS or online and buy twice as many rolls as you need. That way, if you screw up, you can just try again.&lt;/p&gt;
&lt;h2 id=&quot;retentyofoots&quot;&gt;Retent yo foots&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.iamlivingit.com/cycling/clipless-pedals&quot;&gt;&lt;img src=&quot;/ghost-images/content/images/2018/07/clipless-bike-pedal1504783675.jpg&quot; alt=&quot;clipless-bike-pedal1504783675&quot; loading=&quot;lazy&quot;&gt; From &lt;em&gt;Cycling Basics: How to Cycle with Clipless Pedals&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Foot retention does two important things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keeps your feet glued to the pedals.&lt;/strong&gt; You can&apos;t coast on a fixie. Your pedals move when your wheels do. If your feet slip off the pedals while you&apos;re moving fast, it will be really hard to get your feet back onto the pedals. That&apos;s a recipe for a crash.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lets you pull up for more power.&lt;/strong&gt; You only have one gear now – time to make it count. When your feet are bound to the pedals, you can pull up with one foot while you press down with the other. This lets you apply more force and means you don&apos;t need a higher gear as much.&lt;/p&gt;
&lt;p&gt;Going up a hill? Focus on the pull of your back leg – your body will naturally place weight on your front foot. It&apos;s like a magic sixth gear you never knew you had.&lt;/p&gt;
&lt;p&gt;Pulling up and cranking hard with both feet also helps you start from a dead stop. It&apos;s very convenient when you&apos;re in the left lane at a light that just turned green and you want to keep the drivers behind you happy.&lt;/p&gt;
&lt;h3 id=&quot;whatsacliplesspedal&quot;&gt;What&apos;s a clipless pedal?&lt;/h3&gt;
&lt;p&gt;Confusingly, a clipless pedal uses a metal clip mounted to your shoe to hold your foot to the pedal. The pedals are shaped so that they latch onto the clip in your shoe.&lt;/p&gt;
&lt;p&gt;To clip in, you simply step into the pedal with the clip in your shoe. Slide the toe side in, then step down until you hear the click. To clip out, rotate your ankle outward away from your bike.&lt;/p&gt;
&lt;p&gt;There are &lt;a href=&quot;http://www.cyclorama.net/viewArticle.php?id=352&quot;&gt;two main types of clipless systems.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Off-road systems&lt;/strong&gt; use small clips that make it easy to clip out. Lots of casual shoes offer recessed mounts that let you walk around without crunching your clips against asphalt.&lt;/p&gt;
&lt;p&gt;Shimano SPD is the most popular off-road system. Other competitors are Crank Brothers, with the egg-beater design, and Speedlite with the hockey puck pedals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Road systems&lt;/strong&gt; use large clips. They are intended to retain your feet with more force. Their larger contact area lets you transfer power more efficiently to the pedals for long rides and races. Popular road systems include SPD-SL, Look, and Time.&lt;/p&gt;
&lt;p&gt;If you don&apos;t know what you need, start with the &lt;a href=&quot;https://smile.amazon.com/Shimano-Unisex-PD-M520-MTB-Pedal/dp/B004L1CLSS&quot;&gt;Shimano SPD M520 pedals&lt;/a&gt;. Then search for some &lt;a href=&quot;https://smile.amazon.com/Giro-Road-Republic-Shoes-Black/dp/B00NEQQEXY&quot;&gt;classy, walkable SPD shoes&lt;/a&gt; and go from there.&lt;/p&gt;
&lt;h3 id=&quot;whataremyotheroptions&quot;&gt;What are my other options?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Toe clips&lt;/strong&gt; are metal or plastic cages that surround your toes. You can use normal shoes in toe clips. You just slip the front of your shoes in and pedal normally.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://bicycles.stackexchange.com/questions/1504/what-is-the-point-of-pedal-straps&quot;&gt;&lt;strong&gt;Toe straps&lt;/strong&gt;&lt;/a&gt; are velcro straps that fit around the front of your pedals. Slide your feet through them, pull them tight, and velcro them down. They&apos;re more secure than toe clips, but they&apos;re harder to bail out of in a crash or at a stop light.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Platform pedals&lt;/strong&gt; are what you find on most peoples&apos; bikes. They&apos;re flat on both sides. They don&apos;t come with straps, clips, or cages on their own. I don&apos;t recommend you use platform pedals without additional foot retention.&lt;/p&gt;
&lt;h1 id=&quot;enjoyyournewoldbike&quot;&gt;Enjoy your new old bike!&lt;/h1&gt;
&lt;p&gt;Practice safely! Learn to clip in, clip out, and spin before you take this on your daily commute. Everyone has the embarassing sideways slow fall when they come to a full stop but forget to unclip in time. Try to get that out of the way at a park before you end up doing it at a stoplight.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>How to get stuff from Craigslist at ridiculous prices</title><link>https://kesdev.com/how-to-get-stuff-from-craigslist-at-ridiculous-prices</link><guid isPermaLink="true">https://kesdev.com/how-to-get-stuff-from-craigslist-at-ridiculous-prices</guid><description>Here&apos;s my strategy to getting the used stuff I want at insane prices. I&apos;ve used this to buy bicycles, motorcycles, home furniture, and kitchen equipment. If the thing you&apos;re looking for isn&apos;t one-of-a-kind, I think you&apos;ll have luck with this approach. As a brief introduction, here&apos;s a story about something I bought recently. I love two-wheeled vehicles. Ever since I moved to Denver, I&apos;ve been gett…</description><pubDate>Mon, 29 Aug 2016 03:31:44 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;meta property=&quot;og:image&quot; content=&quot;http://mplewis.com/files/blog_assets/nishiki.jpg&quot;&gt;
&lt;p&gt;Here&apos;s my strategy to getting the used stuff I want at insane prices. I&apos;ve used this to buy bicycles, motorcycles, home furniture, and kitchen equipment. If the thing you&apos;re looking for isn&apos;t one-of-a-kind, I think you&apos;ll have luck with this approach.&lt;/p&gt;
&lt;p&gt;As a brief introduction, here&apos;s a story about something I bought recently.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I love two-wheeled vehicles. Ever since I moved to Denver, I&apos;ve been getting to work on my &lt;a href=&quot;https://www.wikiwand.com/en/Kawasaki_KLR650&quot;&gt;dual-sport motorcycle&lt;/a&gt;, and more recently, my &lt;a href=&quot;http://www.bikesdirect.com/products/gravity/g29ss.htm&quot;&gt;cheap mountain bike&lt;/a&gt;. While it&apos;s fun to spin my legs at 120 RPM and slam my suspension over curbs, my single-speed, knobby-tire mountain bike isn&apos;t quite the right tool for a commute.&lt;/p&gt;
&lt;p&gt;I was keeping my eye out for a cheap, &lt;a href=&quot;http://tomsbiketrip.com/touring-bike-faq-3-steel-or-aluminium-frames-or-something-else/&quot;&gt;steel-frame&lt;/a&gt; bicycle for a couple of weeks. Last week, I found this beauty on Craigslist and snapped her up. Meet my new (1989-ish) Nishiki Performance Equipe Sport:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.pedalroom.com/bike/nishiki-performance-equipe-sport-30102#photo237424&quot;&gt;&lt;img src=&quot;http://mplewis.com/files/blog_assets/nishiki.jpg&quot; alt=&quot;Nishiki bicycle&quot; loading=&quot;lazy&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I found this two-wheeled friend listed for about 70% less than comparable bikes on Craigslist. Everything is in great shape. Wheels are true, rust is minimal, and the previous owner upgraded the &lt;a href=&quot;http://biketouringnews.com/components-touring-bicycles/down-tube-shifters/&quot;&gt;suicide shifters&lt;/a&gt; to indexed shifters on the bars! In short, I love my bike and I got it for a steal.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Enough about me – here&apos;s how you can find what you want and spend less than you planned.&lt;/p&gt;
&lt;h1 id=&quot;getobsessed&quot;&gt;Get obsessed&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/UDGKJdRBbLmGA/giphy.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you know me, you know I get obsessed about something I want.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two years ago, it was scuba diving.&lt;/li&gt;
&lt;li&gt;One year ago, it was motorcycling.&lt;/li&gt;
&lt;li&gt;This year, it was my road bike.&lt;/li&gt;
&lt;li&gt;My entire life, I&apos;m going to be obsessed with planes and gliders.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&apos;re obsessed with the thing you want, you&apos;ll be spending all your time doing research. Search for what you want on Craigslist, on Reddit, and on thing-specific forums. There are awesome communities out there with FAQs and glossaries of all the technical terms you&apos;ll need to know when you buy something used.&lt;/p&gt;
&lt;p&gt;Here are a few of the search terms I found useful during my hunt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how to buy used road bike&lt;/li&gt;
&lt;li&gt;vintage bikes subreddit&lt;/li&gt;
&lt;li&gt;how wheel sizing works&lt;/li&gt;
&lt;li&gt;steel vs aluminum bike frame&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You won&apos;t know everything by the time you buy, but you&apos;ll keep building on your working knowledge as you go. Just make sure you keep learning and keep an open mind.&lt;/p&gt;
&lt;h1 id=&quot;watchthemarket&quot;&gt;Watch the market&lt;/h1&gt;
&lt;p&gt;If you&apos;re like me, you&apos;ve already had dreams about bicycles at this point. You should be fully invested and want to buy off some rando on Cragislist RIGHT NOW!! But &lt;em&gt;don&apos;t do it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s time for you to channel your obsession. You&apos;re going to watch the market with the goal of figuring out what you want and what it&apos;s generally priced at.&lt;/p&gt;
&lt;p&gt;My first step was to go to Craigslist and search for &lt;em&gt;road bike&lt;/em&gt;. Turns out there are a lot of these. So I started looking through the results like a kid in a candy shop and crossing off the bikes that I didn&apos;t want. Either they didn&apos;t fit me, or they were too pricey, or they were from a cheap low-quality manufacturer.&lt;/p&gt;
&lt;p&gt;After about a week of drooling over used bikes, I started seeing a pattern. You will too. You&apos;ll notice that seeing something specific will make you reject the listing completely, or you&apos;ll associate a certain brand with a specific price point and quality.&lt;/p&gt;
&lt;h1 id=&quot;setyourstandards&quot;&gt;Set your standards&lt;/h1&gt;
&lt;p&gt;Once you start seeing a pattern, you&apos;ve started to &lt;em&gt;know&lt;/em&gt; what you want instead of just guessing. Now you&apos;re going to define exactly what you want.&lt;/p&gt;
&lt;p&gt;Set your standards high and be specific. &lt;em&gt;This is where you save your money!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You know what the average price for your thing is – your max should be 75% of that or less. Tons of people on Craigslist want to get rid of their perfectly good stuff immediately, and they&apos;re willing to cut a deep discount to do so.&lt;/p&gt;
&lt;p&gt;You know what makes the thing you want quality. In my case, specific Japanese brands – Nishiki, Panasonic, Univega – made awesome bikes in the 80s and 90s that often get overlooked today. This means their prices go way down.&lt;/p&gt;
&lt;p&gt;Here&apos;s what I thought I wanted going into my search:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Road bike. Thin wheels. Frame that fits me. Working.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s what I &lt;em&gt;knew&lt;/em&gt; I wanted after I watched listings on Craigslist:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Road bike. Thin wheels, reasonably slick, preferably 28mm. 50-54cm vintage Japanese frame, cromoly steel. Drop bars. Minimal rust. Non-suicide shifters. Working. $150 or less.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;getnotified&quot;&gt;Get notified&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;http://i.imgur.com/w8hopto.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now you know exactly what you want. You know you want your high-quality thing at a low price. Unfortunately, this is exactly what other people want too. You&apos;ll have to beat them to the punch.&lt;/p&gt;
&lt;p&gt;Start by building your Craigslist search. Go to your Craigslist site, enter your search query, and run it. Here are some tips that served me well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the OR &lt;code&gt;|&lt;/code&gt; and NOT &lt;code&gt;-&lt;/code&gt; operators to &lt;a href=&quot;https://www.craigslist.org/about/help/search&quot;&gt;specify multiple keywords you want&lt;/a&gt; and filter ones you don&apos;t. Here&apos;s an example Craigslist query for Nishiki, Panasonic, and Univega bicycles that aren&apos;t mountain bikes:&lt;br&gt;
&lt;code&gt;nishiki|panasonic|univega -mountain&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set a minimum price limit. Jokesters list stuff as $1 to ensure it appears in searches with upper price bounds. You know no one will just give your thing away for $20 – so set your minimum to $20 to filter the noise.&lt;/li&gt;
&lt;li&gt;Set the maximum price limit to that price you set above. It&apos;s important for you to stick to your guns on this one, even if you have FOMO for search results. You&apos;ll find a deal eventually.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Great! You&apos;ve got your search dialed in. Now, copy the Craigslist URL for your search. Use it to set up an IFTTT recipe to &lt;a href=&quot;https://ifttt.com/recipes/79-get-an-email-when-a-new-craigslist-post-matches-your-search&quot;&gt;email you when a new item pops up on Craigslist for a specific search&lt;/a&gt;. You can also use push notifications or SMS – they&apos;re a bit more urgent.&lt;/p&gt;
&lt;h1 id=&quot;jumponit&quot;&gt;Jump on it&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Why is the notifier important?&lt;/em&gt; Because if you&apos;ve found a good deal, you need to jump on that immediately. Good deals on Craigslist don&apos;t last three days. They rarely last one or two days. When you see that notification, excuse yourself from your meeting, lunch, or therapy session, and call the lister immediately.&lt;/p&gt;
&lt;p&gt;Craigslist people tend to have a specific code of honor. If you&apos;re the first person to call them and set up a meeting, they&apos;ll usually tell other interested parties that they have to wait until you swing by and take a look. The quicker you contact, the more likely you are to get the thing. But to take advantage of this, you need to show up that day, so swing by the seller right after work and leave your kids late at day care. (Kidding. Probably.)&lt;/p&gt;
&lt;h1 id=&quot;profit&quot;&gt;Profit!&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/k92kDp9.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you&apos;re doing this right, you might get the thing of your dreams in one to two days. You should find something you love in two weeks, unless the market you&apos;re searching is unusually small. And you know that you&apos;ll love it because you&apos;ve been looking for something very specific at a specific price. Congratulations!&lt;/p&gt;
&lt;h1 id=&quot;whataboutnegotiating&quot;&gt;What about negotiating?&lt;/h1&gt;
&lt;p&gt;I&apos;m gonna be honest. I suck at Craigslist negotiating. If I&apos;m saving less than $100, negotiating makes me feel uncomfortable enough that I don&apos;t bother. And at the end of my search, I&apos;ve usually found an item that&apos;s way, way under market value, so I feel good enough about the price already.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in driving a hard bargain, there are &lt;a href=&quot;http://recraigslist.com/2013/09/how-to-negotiate-on-craigslist/&quot;&gt;lots&lt;/a&gt; &lt;a href=&quot;http://lifehacker.com/5974807/how-to-get-anything-you-want-with-minimal-negotiation&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;http://www.pretendtobepoor.com/craigslist/&quot;&gt;other&lt;/a&gt; &lt;a href=&quot;http://www.wolfofcraigslist.com/how-to-get-a-25-price-reduction-on-craigslist-in-10-seconds/&quot;&gt;resources&lt;/a&gt; &lt;a href=&quot;http://www.secrets2money.com/7-Tricks-to-Negotiate-Better-on-Craigslist.html&quot;&gt;to&lt;/a&gt; &lt;a href=&quot;https://thebillfold.com/how-to-win-at-craigslist-9b8baf58c38f#.o3u1s26tk&quot;&gt;teach&lt;/a&gt; &lt;a href=&quot;http://www.apartmenttherapy.com/secrets-of-craigslist-tons-of-133405&quot;&gt;you&lt;/a&gt; better than I can.&lt;/p&gt;
&lt;h1 id=&quot;goodluck&quot;&gt;Good luck!&lt;/h1&gt;
&lt;p&gt;I hope this guide helps you get the thing you want at a price you love. If any of these tips help you, or if you have other methods you use to find great deals, &lt;a href=&quot;mailto:matt@mplewis.com&quot;&gt;drop me a line&lt;/a&gt; or leave comment on this post. I love hearing from readers!&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Finding a Career You Love: Lessons Learned From My Job Hunt</title><link>https://kesdev.com/finding-a-career-you-love-lessons-learned-from-my-job-hunt</link><guid isPermaLink="true">https://kesdev.com/finding-a-career-you-love-lessons-learned-from-my-job-hunt</guid><description>I&apos;m writing this article for someone who works in tech and is looking for a new job, but wants to be somewhat selective and build stuff they believe in. If that describes you, read on! On June 3, I left my job at Punch Through, a hardware consulting firm in Minneapolis and San Francisco. My wife and I moved to Denver, CO. She&apos;s attending pharmacy school in the fall and I was looking for something …</description><pubDate>Tue, 19 Jul 2016 21:58:58 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;I&apos;m writing this article for someone who works in tech and is looking for a new job, but wants to be somewhat selective and build stuff they believe in. If that describes you, read on!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;On June 3, I left my job at &lt;a href=&quot;http://punchthrough.com&quot;&gt;Punch Through&lt;/a&gt;, a hardware consulting firm in Minneapolis and San Francisco. My wife and I moved to Denver, CO. She&apos;s attending pharmacy school in the fall and I was looking for something new in my career.&lt;/p&gt;
&lt;p&gt;Before I left Minneapolis, I started looking for a new job in Denver. Here&apos;s what I learned and what I would do differently.&lt;/p&gt;
&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What I Wanted:&lt;/strong&gt; An overview of my target career and why I was so picky.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How I Searched:&lt;/strong&gt; The boards and networks I used to find my dream job in Colorado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How I Applied:&lt;/strong&gt; How I got my foot in the door and how I kept the conversation going.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;General Tips:&lt;/strong&gt; Everything else that I think will help people searching for an awesome place to work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How It Turned Out:&lt;/strong&gt; The results. How many jobs I applied for, how many companies turned me down, and how many offers I received.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;whatiwanted&quot;&gt;What I Wanted&lt;/h1&gt;
&lt;p&gt;I was in the market for&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a product manager role,&lt;/li&gt;
&lt;li&gt;or if that wasn&apos;t available, an engineering role with room to grow into management,&lt;/li&gt;
&lt;li&gt;with a team that cared about quality&lt;/li&gt;
&lt;li&gt;and worked on a product I believed in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I loved my coworkers and the projects at Punch Through. I worked there for three years during and after college as a software developer. My primary work was on iOS, Android, and web software for &lt;a href=&quot;http://punchthrough.com/bean&quot;&gt;LightBlue Bean&lt;/a&gt;, a Bluetooth Low Energy development board for makers and product designers.&lt;/p&gt;
&lt;p&gt;Our team was small – about 16 people when I left – and we all had lots of ownership over the things we built. I enjoyed the responsibility of ownership and liked managing the Scrum backlog. I made it my short-term career goal to get into a position where I was formally responsible for a product&apos;s direction. Since my experience was in engineering, my friends and coworkers advised me to consider taking a dev role with the possibility of growth into management.&lt;/p&gt;
&lt;p&gt;When I searched for companies to apply to, the specific product and industry were very important to me. If the best company in the world were hiring, but they weren&apos;t building a product I could be proud of, I would never apply for a job there. I avoided companies doing marketing and building social networks, and I refused to work in advertising tech.&lt;/p&gt;
&lt;h1 id=&quot;howisearched&quot;&gt;How I Searched&lt;/h1&gt;
&lt;p&gt;I searched for leads through a few channels:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Personal network and friends of friends&lt;/li&gt;
&lt;li&gt;Job sites: &lt;a href=&quot;http://glassdoor.com&quot;&gt;Glassdoor&lt;/a&gt;, &lt;a href=&quot;http://jobs.stackoverflow.com&quot;&gt;Stack Overflow Careers&lt;/a&gt;, &lt;a href=&quot;http://linkedin.com&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Community: &lt;a href=&quot;http://denverdevs.org/&quot;&gt;Denver Devs&lt;/a&gt;, &lt;a href=&quot;http://www.gettechfriends.com/&quot;&gt;TechFriends Front Range&lt;/a&gt;, &lt;a href=&quot;http://www.builtincolorado.com/built-brews&quot;&gt;Built In Brews&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Recruiters: &lt;a href=&quot;http://dice.com&quot;&gt;Dice&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;personalnetwork&quot;&gt;Personal Network&lt;/h2&gt;
&lt;p&gt;I reached out to my coworkers from Punch Through and friends living in Colorado and asked if anyone knew anyone building anything cool. Everyone was happy to pass along what they knew. I got very few leads through my personal network, but the ones I did get were all high quality – no fussing about, let&apos;s meet up for coffee and chat.&lt;/p&gt;
&lt;h2 id=&quot;jobsites&quot;&gt;Job Sites&lt;/h2&gt;
&lt;p&gt;I searched job sites for the keywords &lt;em&gt;Product Manager&lt;/em&gt;, &lt;em&gt;Developer&lt;/em&gt; and &lt;em&gt;Engineer&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://glassdoor.com&quot;&gt;Glassdoor&lt;/a&gt; has excellent data on job satisfaction which helped me get a quick idea of whether a team&apos;s culture was healthy. When most people are complaining about management not listening, not communicating, and hiring in managers from outside, that&apos;s a big set of red flags.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://jobs.stackoverflow.com&quot;&gt;Stack Overflow Careers&lt;/a&gt; has less jobs listed, but most of them are high quality. People posting jobs on SO are encouraged to list the job&apos;s salary range, which is a breath of fresh air.&lt;/p&gt;
&lt;p&gt;LinkedIn has everything but it&apos;s incredibly hard to find relevant postings or info. This is a last resort. What you &lt;strong&gt;should&lt;/strong&gt; be using LinkedIn for is to find the name of a hiring manager at Acme Corp, figure out their email, and send them a nice cover letter directly. More on that later.&lt;/p&gt;
&lt;h2 id=&quot;community&quot;&gt;Community&lt;/h2&gt;
&lt;p&gt;All three of the communities I listed were excellent leads for awesome companies. The &lt;a href=&quot;http://denverdevs.org/&quot;&gt;Denver Devs&lt;/a&gt; Slack channel has a weekly Gigs Day: employers post about open jobs and individuals post if they&apos;re looking for a job. It&apos;s a great way to skip the recruiting site and ask some quick questions to see if you&apos;re a good fit.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.builtincolorado.com/built-brews&quot;&gt;Built In Brews&lt;/a&gt; is a fantastic networking event. Show up at an awesome office, have some food and drinks, and chat with other awesome people. I got in touch with a couple of excellent recruiters who set me up with great companies. Their parent site, Built in Colorado, also has a &lt;a href=&quot;http://www.builtincolorado.com/jobs&quot;&gt;job posting board&lt;/a&gt; with lots of great leads.&lt;/p&gt;
&lt;h2 id=&quot;recruiters&quot;&gt;Recruiters&lt;/h2&gt;
&lt;p&gt;The easiest way to get recruiters to do the legwork for you is to put your resume on &lt;a href=&quot;http://dice.com&quot;&gt;Dice&lt;/a&gt; and list your relevant skills. You&apos;ll get calls and emails for the next month with stuff that is – surprisingly – mostly relevant! The quality is mixed but there are a couple of diamonds in the rough. For example, many of the recruiters that reached out were filling Contract or Contract to Hire roles, even though I was seeking full-time exclusively. But the calls I got for full-time roles were mostly real jobs where my experience was relevant.&lt;/p&gt;
&lt;p&gt;One recruiter was a complete asshole. They called me once to see if I was interested in the role and the industry. I was, so they sent over the job details. I liked them, so I scheduled a Skype call. Here&apos;s how that call went once we got past small talk:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Them: &quot;How much did you make at your last job?&quot;&lt;br&gt;
Me: &quot;Sorry, I prefer not to say.&quot;&lt;br&gt;
Them: &quot;I really need to know.&quot;&lt;br&gt;
Me: &quot;Sorry, I don&apos;t want to cap my next salary by stating that. But I can tell you what I&apos;m targeting.&quot;&lt;br&gt;
Them: &quot;Let me worry about that. What did you make at your last job?&quot;&lt;br&gt;
Me: &quot;...I really can&apos;t give you that number for several reasons.&quot;&lt;br&gt;
Them: &quot;I understand. Sorry this didn&apos;t work out. Have a nice day.&quot; &lt;strong&gt;Skype hangup&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&apos;t think I could fix that recruiter&apos;s broken behavior. There are so many things wrong with what they said and did. Between the blatant disrespect, lack of professionalism, and the Skype equivalent of slamming the phone down, I&apos;m amazed that Asshole Recruiter moves forward with anyone in the hiring process.&lt;/p&gt;
&lt;p&gt;Pro tip – don&apos;t say &lt;em&gt;Let me worry about [your salary],&lt;/em&gt; because &lt;strong&gt;I&lt;/strong&gt; will worry about my salary. Thanks.&lt;/p&gt;
&lt;h1 id=&quot;howiapplied&quot;&gt;How I Applied&lt;/h1&gt;
&lt;p&gt;OK, I have a big pile of job listings. How do I turn those into interviews?&lt;/p&gt;
&lt;p&gt;Here are the approaches I used, from most to least successful:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get a personal introduction from a friend of a friend and send them a nice cover letter&lt;/li&gt;
&lt;li&gt;Find the hiring manager&apos;s work email and send them a nice cover letter&lt;/li&gt;
&lt;li&gt;Apply for the job online with a nice cover letter&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice anything in common?&lt;/p&gt;
&lt;h2 id=&quot;thecoverletter&quot;&gt;The Cover Letter&lt;/h2&gt;
&lt;p&gt;This is the cover letter skeleton I used for 90% of my job applications. I think it struck a balance of giving them information about me, letting them know what I wanted, telling them why I&apos;d be a great pick, and asking to move forward in the process.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hello! My name is $NAME. [Why I&apos;m looking for a job right now.] My last position was as a $ROLE at $COMPANY, a $TYPE_OF_COMPANY in $PLACE. I wanted to get in touch with you because I’m looking for a position building $TYPE_OF_PRODUCT, and I really like the sound of $COMPANY. I think your company is a great place to $PROFESSONAL_GOAL.&lt;/p&gt;
&lt;p&gt;[Brief history of my time at my last company. Any of my relevant experience with impressive KPIs attached.]&lt;/p&gt;
&lt;p&gt;I want to work with $COMPANY because $COMPANY_CULTURE. [Short blurb about stuff I saw on their site or read about the company that would make me happy to work there.]&lt;/p&gt;
&lt;p&gt;I feel my experience in $RELEVANT_EXPERIENCE will fit right in at $COMPANY. [Sentence about how my personality fits with the company culture. Sentence about how my experience is relevant. Sentence about how I can start adding value as soon as I&apos;m hired.]&lt;/p&gt;
&lt;p&gt;I’d like to chat with you sometime. I want to learn more about $COMPANY&apos;s needs and let you find out if there’s a place I would fit in.&lt;/p&gt;
&lt;p&gt;Thank you for your time!&lt;/p&gt;
&lt;p&gt;$NAME&lt;br&gt;
$WEBSITE&lt;br&gt;
$PHONE_NUMBER&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Please feel free to steal this template and use it yourself. This letter is brief, to the point, and professional. If your paragraphs start looking longish, consider shortening them. It&apos;s a cover letter, and the goal is to get the hiring manager on the phone so they can ask about what they really care about.&lt;/p&gt;
&lt;p&gt;It&apos;s important that this letter has your voice in it. Think business casual, not T-shirt and shorts. Some excitement (!) and some talk about your personal reasons for looking for a new job is OK. Emoji is not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always ask for the job!&lt;/strong&gt; It&apos;s important to ask for the job throughout the process. It&apos;s not aggressive – it&apos;s professional. Asking for the job makes it clear that you&apos;re interested and want to be put at the top of their list.&lt;/p&gt;
&lt;p&gt;After an email, ask when they&apos;re available for a call. At the end of a phone call, ask what the next steps are. After an interview, ask when you should expect to hear back from them on a decision.&lt;/p&gt;
&lt;p&gt;Great – you have a cover letter. How should you get it to the hiring manager?&lt;/p&gt;
&lt;h2 id=&quot;personalintroduction&quot;&gt;Personal Introduction&lt;/h2&gt;
&lt;p&gt;If you can get someone to CC you into a conversation with the person responsible for hiring, you have just saved yourself weeks of waiting for them to check the inbound resumes. Ask your friends if they would be comfortable with introducing you to hiring at their company, and nudge them until they send the email.&lt;/p&gt;
&lt;h2 id=&quot;emailthehiringmanager&quot;&gt;Email the Hiring Manager&lt;/h2&gt;
&lt;p&gt;There are a couple of tricks to this one. You can often leverage LinkedIn to help you find the hiring manager. Once you know who they are, you can usually guess their email based on the email addresses of other people at the company. &lt;a href=&quot;https://www.themuse.com/advice/how-to-hunt-down-a-hiring-managers-email-address&quot;&gt;Here&apos;s a good set of tips.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;justapplyonline&quot;&gt;Just Apply Online&lt;/h2&gt;
&lt;p&gt;When you apply online, assume your resume will go to the glorious digital circle file and never be seen again. I consider it a happy accident when someone actually responds to my online application. Don&apos;t forget to write a cover letter for online apps – it goes a long way.&lt;/p&gt;
&lt;h1 id=&quot;generaltips&quot;&gt;General Tips&lt;/h1&gt;
&lt;p&gt;Here&apos;s stuff I wish I had known when I started my job hunt.&lt;/p&gt;
&lt;h2 id=&quot;planforalonghunt&quot;&gt;Plan For A Long Hunt&lt;/h2&gt;
&lt;p&gt;My job hunt took ten weeks total. When you start your search, plan to be looking for at least a few weeks.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A few weeks?!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, a few weeks. The longer you hunt, the more you apply, and the later you wait, the better your odds of finding the job you really want. Unfortunately, that gets a bit stressful when you don&apos;t have steady income coming in.&lt;/p&gt;
&lt;p&gt;I wish I had had a job lined up by the time I moved to Denver. If I were to do this all again, I&apos;d start hunting a month earlier.&lt;/p&gt;
&lt;h2 id=&quot;setclearexpectations&quot;&gt;Set Clear Expectations&lt;/h2&gt;
&lt;p&gt;In my first few interviews, I told the interviewer I wanted a position in management even though I was applying for a dev role. I told them that I would be happy to grow into a new role. This spooked my interviewers – they were worried I would be dissatisfied with my job and leave if I didn&apos;t get a promotion to management quickly.&lt;/p&gt;
&lt;p&gt;I honed my approach as I went through more interviews. Instead of saying &quot;I want to move to a mangement role sometime,&quot; I asked about the company roles and culture. I asked if the company was big enough to hire product managers, and if they tended to hire from inside. I asked questions like &quot;If I were to work here and I liked it a lot, is there room to grow?&quot;&lt;/p&gt;
&lt;p&gt;Those questions helped me understand what roles I could move into without making them think I wouldn&apos;t be happy with an engineering role.&lt;/p&gt;
&lt;h2 id=&quot;besososoorganized&quot;&gt;Be So, So, So Organized&lt;/h2&gt;
&lt;p&gt;Use iCal. Use Google Calendar. Use &lt;a href=&quot;https://flexibits.com/fantastical&quot;&gt;Fantastical&lt;/a&gt;, my favorite Mac and iOS calendar app. It doesn&apos;t matter what tool you use – just use some tool. When you apply for five to ten jobs a day, you will need a calendar to tell you who you&apos;re talking to and when.&lt;/p&gt;
&lt;p&gt;Show up early. 30 minutes early is acceptable. 15 minutes early is ideal. If you arrive on time, you are late. When you&apos;re trying to find parking in downtown Denver, you&apos;ll pass the lot you want twice. Then you won&apos;t be able to find the office. (Not that I know from experience.)&lt;/p&gt;
&lt;p&gt;If you screw up, completely flub an interview time, and don&apos;t show up, you might be screwed. But you can usually salvage it if you are &lt;strong&gt;very&lt;/strong&gt; apologetic and polite and &lt;strong&gt;extremely&lt;/strong&gt; thankful that they gave you another shot.&lt;/p&gt;
&lt;h2 id=&quot;alwayssaythankyou&quot;&gt;Always Say Thank You&lt;/h2&gt;
&lt;p&gt;Any time you finish a phone call, a meeting over coffee, or an in-person interview, send that person a thank you note the next day. It doesn&apos;t have to be anything fancy. Just email them saying you appreciated their time and mention what you liked about the last interaction. For example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I was happy to learn about the role and my day-to-day responsibilities.&lt;/p&gt;
&lt;p&gt;I liked meeting Sam and getting a feel for the engineering culture at E Corp.&lt;/p&gt;
&lt;p&gt;I enjoyed learning about the roles you have available and I&apos;d like to learn more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sending the thank you note the next day keeps your interaction fresh in their mind. &lt;strong&gt;Don&apos;t forget to ask for the job!&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;bepolitelypushy&quot;&gt;Be Politely Pushy&lt;/h2&gt;
&lt;p&gt;You&apos;ve gotten in touch with hiring. Now you&apos;re sitting and waiting for them to get back to you. What&apos;s the etiquette on bugging someone you hardly know after they don&apos;t email you for weeks?&lt;/p&gt;
&lt;p&gt;My rule is: &lt;strong&gt;Wait three to five business days and ping them again.&lt;/strong&gt; You need a job and being too polite will hurt your chances. If someone hasn&apos;t returned your email or scheduled a call with you, send them one of these:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey $NAME,&lt;/p&gt;
&lt;p&gt;Just wanted to check in and make sure I&apos;m still on your radar. I&apos;d like to move forward at $COMPANY. Are you available for a call this week?&lt;/p&gt;
&lt;p&gt;Thanks,&lt;br&gt;
Matt&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&apos;s a polite reminder that you&apos;re still interested but do need a response soon. You can send a few of these in a row if you wait a few days and still don&apos;t hear back. If you send three of these and don&apos;t get anything back, assume they ghosted you and aren&apos;t interested.&lt;/p&gt;
&lt;h2 id=&quot;setafakedeadline&quot;&gt;Set A (Fake) Deadline&lt;/h2&gt;
&lt;p&gt;I started applying for jobs on May 2. I accepted a job on July 16. This was way longer than I thought I would spend job searching.&lt;/p&gt;
&lt;p&gt;Once I got my first offer from a Boulder dev shop, I asked them for just over a week to consider the offer. Then I told all the other companies I was still interviewing with that I had an offer on the table. Once I told them I had a deadline, the managers &lt;strong&gt;hustled&lt;/strong&gt; to get me through the process. One mid-sized company flew me to SF in the morning and back at night the day before my deadline, while another got me a last-minute on-site appointment to finish up the process with two engineers and a hiring manager.&lt;/p&gt;
&lt;p&gt;The lesson? Most places can lift heaven and earth to move you through the process – but they aren&apos;t incentivized to unless you tell them they have to. The next time I seek a job, I&apos;m setting a deadline to encourage companies to move it.&lt;/p&gt;
&lt;h1 id=&quot;howitturnedout&quot;&gt;How It Turned Out&lt;/h1&gt;
&lt;p&gt;I applied to &lt;strong&gt;26 companies&lt;/strong&gt;. This doesn&apos;t include recruiter gigs that fell through or positions I wasn&apos;t seriously interested in. Here&apos;s how those turned out:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9 companies&lt;/strong&gt; dropped out because I couldn&apos;t get in touch with them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;4:&lt;/strong&gt; No response. I applied online and no one got back to me.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5:&lt;/strong&gt; Ghosted. I started talking with a recruiter or hiring manager and they refused to respond after I pinged them several times.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;13 companies&lt;/strong&gt; didn&apos;t give me an offer for one reason or another.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2:&lt;/strong&gt; Situation mismatch. They were looking for something totally different. One manager wanted to hire someone like me but couldn&apos;t get a budget in time. One team found a lead developer in Pennsylvania and wanted me to relocate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3:&lt;/strong&gt; I declined to continue the process. I didn&apos;t have time to complete their code test, or they got back to me too late in the process and I had to accept an offer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8:&lt;/strong&gt; Job not offered. I wasn&apos;t a good fit for the position or they found someone they preferred.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4 companies&lt;/strong&gt; got back to me with an offer. I turned down three offers and accepted one.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I feel OK about the job hunt. Four offers out of 23 serious applications sounds low, but at each step along the way I learned more about the companies and the roles I was applying to. It was occasionally disappointing to be turned away, but I&apos;m glad I didn&apos;t end up working somewhere where I would be a poor fit for the role.&lt;/p&gt;
&lt;p&gt;I&apos;m happy where I ended up: a Project Management position at a connected device company. That&apos;s exactly what I was looking for when I started my job hunt, and I&apos;m glad I found a growing company that was looking for someone like me.&lt;/p&gt;
&lt;p&gt;I don&apos;t look forward to searching for a job, but I&apos;m confident I will be able to navigate the interviews and hiring process much more easily next time.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>You got LaTeX in my Markdown!</title><link>https://kesdev.com/you-got-latex-in-my-markdown</link><guid isPermaLink="true">https://kesdev.com/you-got-latex-in-my-markdown</guid><description>Working title: You got Markdown in my LaTeX! I hate writing documents in Microsoft Word and Apple Pages. I spend way too much time fiddling with formatting, page layout, and typesetting before everything looks the way I want it. And writing math equations, even with Word&apos;s GUI equation editor, is tedious and takes too much clicking. I&apos;m a programmer—just let me use LaTeX&apos;s equation format instead!…</description><pubDate>Tue, 21 Oct 2014 06:51:05 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Working title: You got Markdown in my LaTeX!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I hate writing documents in Microsoft Word and Apple Pages. I spend way too much time fiddling with formatting, page layout, and typesetting before everything looks the way I want it. And writing math equations, even with Word&apos;s GUI equation editor, is tedious and takes too much clicking. I&apos;m a programmer—just let me use LaTeX&apos;s equation format instead!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;tl;dr&lt;/code&gt; If you want to write documents in Markdown, embed LaTeX, and generate PDFs, use &lt;a href=&quot;http://johnmacfarlane.net/pandoc/&quot;&gt;Pandoc&lt;/a&gt;.&lt;br&gt;
Skip down to &lt;em&gt;Kung Fu Pandoc&lt;/em&gt; to get started!&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;sojustuselatextheysaiditllbefuntheysaid&quot;&gt;So just use LaTeX, they said. It&apos;ll be fun, they said.&lt;/h1&gt;
&lt;p&gt;I like typesetting tools, and I like LaTeX... with reservations. Some of my complaints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The boilerplate needed to get a TeX document up and running is &lt;a href=&quot;https://www.google.com/search?q=latex%20boilerplate&quot;&gt;pretty massive&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;LaTeX is a &lt;strong&gt;pain in the ass&lt;/strong&gt; to write. There is so much character overhead behind basic formatting.
&lt;ul&gt;
&lt;li&gt;Bold? &lt;code&gt;\textbf{bolded text}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Inline code? &lt;code&gt;\texttt{for (int i = 0; i &amp;#x3C; 42; i++)}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Basic tables? &lt;a href=&quot;http://en.wikibooks.org/wiki/LaTeX/Tables&quot;&gt;Here&apos;s a short reference.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Holy reserved characters, Batman! Here are things you can&apos;t write in LaTeX without escaping:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# $ % ^ &amp;#x26; _ { } ~ \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s not like I&apos;d ever want to use a dollar sign in my budget report or an underscore in my C code.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you don&apos;t escape a character properly, you&apos;ll probably get a syntax error. LaTeX&apos;s syntax errors are cryptic and terrifying:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Missing $ inserted&lt;/code&gt;: could mean just about anything, such as: you put too many lines inside an equation block, or you forgot to close a curly bracket.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;No line here to end&lt;/code&gt;: means that you tried to continue a line when LaTeX &quot;didn&apos;t expect it&quot;. &lt;a href=&quot;http://www.tex.ac.uk/cgi-bin/texfaq2html?label=noline&quot;&gt;When does LaTeX expect it? God only knows.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Not confusing enough for you? &lt;a href=&quot;http://tex.stackexchange.com/questions/125399/how-to-trace-latex-errors-efficiently&quot;&gt;There are more errors where those came from.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;wouldntitbeniceif&quot;&gt;Wouldn&apos;t it be nice if...&lt;/h1&gt;
&lt;p&gt;This semester, I tried to write an assignment for class and found out my LaTeX build chain in Sublime broke. Instead of trying to troubleshoot it, I started thinking about what I&apos;d like to use instead.&lt;/p&gt;
&lt;p&gt;Here&apos;s why I love LaTeX:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It lets me write equations quickly. Its equation syntax makes sense to me, and it&apos;s easy to &lt;s&gt;steal&lt;/s&gt; import equations from Wikipedia pages.&lt;/li&gt;
&lt;li&gt;It&apos;s a typesetting program, not a WYSIWYG. No more screwing around with weird margin sliders and page layout tools and header settings: simply throw in your text, edit the settings once, and watch all your text reflow into place.&lt;/li&gt;
&lt;li&gt;It&apos;s easy to generate a PDF from a LaTeX file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&apos;s why I prefer Markdown as a language to write formatted text:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&apos;s SUPER lightweight. &lt;code&gt;**bold**&lt;/code&gt;, &lt;code&gt;*italic*&lt;/code&gt;, ```inline code` ``.&lt;/li&gt;
&lt;li&gt;GitHub-flavored Markdown is super cool. It adds support for syntax-highlighted code blocks and tables! I like it so much I built a tool to &lt;a href=&quot;https://github.com/mplewis/csvtomd&quot;&gt;generate Markdown tables from CSV files&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You probably already know its syntax if you use GitHub or Reddit frequently.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I asked myself: &quot;Self, wouldn&apos;t it be great if there were a typesetting tool that let you write documents in Markdown, embed LaTeX where you needed it, and generate PDFs?&quot; And it turns out that already exists.&lt;/p&gt;
&lt;h1 id=&quot;kungfupandoc&quot;&gt;Kung-Fu Pandoc&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;If you need to convert files from one markup format into another, pandoc is your swiss-army knife.&lt;/p&gt;
&lt;p&gt;—&lt;cite&gt;John MacFarlane, author of pandoc&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We&apos;ll use this Swiss Army knife called Pandoc to compile and export our Markdown-LaTeX docs. Let&apos;s try it out!&lt;/p&gt;
&lt;h2 id=&quot;installpandocandlatex&quot;&gt;Install Pandoc and LaTeX&lt;/h2&gt;
&lt;p&gt;You&apos;ll need Pandoc and pdfTeX installed. If you can run &lt;code&gt;pandoc&lt;/code&gt; and &lt;code&gt;pdflatex&lt;/code&gt; from a terminal, you&apos;re probably good to go.&lt;/p&gt;
&lt;p&gt;If you&apos;re on a Mac, install Pandoc using &lt;a href=&quot;http://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; (&lt;code&gt;brew install pandoc&lt;/code&gt;) and &lt;a href=&quot;https://tug.org/mactex/&quot;&gt;install MacTeX&lt;/a&gt; using their &lt;code&gt;.pkg&lt;/code&gt; installer.&lt;/p&gt;
&lt;h2 id=&quot;getyoursourcedoc&quot;&gt;Get Your Source Doc&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://mplewis.com/files/pandoc-md-latex/example.md&quot;&gt;Here&apos;s a sample Markdown-plus-LaTeX document you can try to compile.&lt;/a&gt; Save that file somewhere as &lt;code&gt;example.md&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;compileyourdoc&quot;&gt;Compile Your Doc&lt;/h2&gt;
&lt;p&gt;Here&apos;s the command I used to compile &lt;code&gt;example.md&lt;/code&gt; —&gt; &lt;code&gt;example.pdf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pandoc -o example.pdf example.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See? It&apos;s that easy. Now open your PDF and you should see something like this:&lt;/p&gt;
&lt;img alt=&quot;Markdown as PDF&quot; src=&quot;http://mplewis.com/files/pandoc-md-latex/example.png&quot; width=&quot;400&quot; style=&quot;border: 1px solid #ccc&quot;&gt;
&lt;p&gt;Awesome. You&apos;re one step closer to well-documented world domination.&lt;/p&gt;
&lt;h1 id=&quot;automatethatishwithsublimetextsbuildsystems&quot;&gt;Automate That Ish with Sublime Text&apos;s Build Systems&lt;/h1&gt;
&lt;p&gt;Great! Now you can build Pandoc Markdown docs manually from the command line.&lt;/p&gt;
&lt;p&gt;If you use Sublime Text like me, you might use the &lt;strong&gt;Build&lt;/strong&gt; command (&lt;code&gt;F7&lt;/code&gt; or &lt;code&gt;Cmd+B&lt;/code&gt;) to build and run your scripts. When I used &lt;a href=&quot;https://github.com/SublimeText/LaTeXTools&quot;&gt;LaTeXTools&lt;/a&gt;, I got really used to checking how my LaTex looked when it was rendered by hitting &lt;code&gt;Cmd+B&lt;/code&gt;. The LaTeX build command rendered my source document and opened a PDF viewer with the results.&lt;/p&gt;
&lt;p&gt;I duplicated this in Sublime Text. Here&apos;s how you can too!&lt;/p&gt;
&lt;h2 id=&quot;createanewsublimebuildfile&quot;&gt;Create a new Sublime-Build file&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Sublime-Build&lt;/strong&gt; files tell Sublime Text how you want to build a document when you hit Ctrl-B.&lt;/p&gt;
&lt;p&gt;Create a new Sublime-Build file by selecting &lt;code&gt;Tools —&gt; Build System —&gt; New Build System...&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You&apos;ll get a document named &lt;code&gt;untitled.sublime-build&lt;/code&gt; that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;shell_cmd&quot;: &quot;make&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;writeyourpandocbuildcommand&quot;&gt;Write Your Pandoc Build Command&lt;/h2&gt;
&lt;p&gt;Replace the contents of your new document with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;shell_cmd&quot;: &quot;pandoc -o \&quot;$file.pdf\&quot; \&quot;$file\&quot; &amp;#x26;&amp;#x26; open -a Preview \&quot;$file.pdf\&quot;&quot;,
    &quot;selector&quot;: &quot;text.html.markdown&quot;,
    &quot;path&quot;: &quot;/usr/texbin:$PATH&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then change a few things:&lt;/p&gt;
&lt;h3 id=&quot;ifyourenotonamacremovetheopenacommand&quot;&gt;If you&apos;re not on a Mac, remove the &lt;code&gt;open -a&lt;/code&gt; command.&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;open -a Preview \&quot;$file.pdf\&quot;&quot;&lt;/code&gt; is just for Macs—it opens the rendered file in Preview.app.&lt;br&gt;
If you&apos;re not on a Mac, strip off the &lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt; characters and everything after them. Your &lt;code&gt;shell_cmd&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;shell_cmd&quot;: &quot;pandoc -o \&quot;$file.pdf\&quot; \&quot;$file\&quot;&quot;,
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;removethepathsettingifyoudontwantiteasierjustleaveitin&quot;&gt;Remove the &lt;code&gt;path&lt;/code&gt; setting if you don&apos;t want it. Easier: just leave it in.&lt;/h3&gt;
&lt;p&gt;I found my Sublime install couldn&apos;t find &lt;code&gt;pdflatex&lt;/code&gt; unless I added this directory to the build system&apos;s path directly. You can probably leave it in—it shouldn&apos;t do any harm as long as Sublime can find &lt;code&gt;pdflatex&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;saveyournewbuildcommand&quot;&gt;Save Your New Build Command&lt;/h2&gt;
&lt;p&gt;The default save location will be &lt;code&gt;Sublime Text 3/Packages/User&lt;/code&gt;. Save the file you just created into that directory as &lt;code&gt;Markdown to PDF.sublime-build&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;setbuildsystemtoautomatic&quot;&gt;Set &quot;Build System&quot; to &quot;Automatic&quot;&lt;/h2&gt;
&lt;p&gt;Select &lt;code&gt;Tools —&gt; Build System —&gt; Automatic&lt;/code&gt; to ensure Sublime picks your new Markdown build system.&lt;/p&gt;
&lt;h2 id=&quot;buildamarkdownfile&quot;&gt;Build a Markdown File!&lt;/h2&gt;
&lt;p&gt;Open the &lt;code&gt;example.md&lt;/code&gt; file you downloaded earlier in Sublime, then hit &lt;code&gt;F7&lt;/code&gt; or &lt;code&gt;Cmd-B&lt;/code&gt;. The Sublime Text console should open as it starts compiling your document, then print something like this when it&apos;s done:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Finished in 2.4s]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the directory holding &lt;code&gt;example.md&lt;/code&gt; and look for a brand-new &lt;code&gt;example.md.pdf&lt;/code&gt; file!&lt;/p&gt;
&lt;p&gt;You did it! I am very proud of you.&lt;/p&gt;
&lt;h1 id=&quot;bonusroundcustomlatextemplates&quot;&gt;Bonus Round: Custom LaTeX Templates&lt;/h1&gt;
&lt;p&gt;You&apos;re a cool guy who knows cool stuff. Want to make your LaTeX docs even cooler? Add your own custom template.&lt;/p&gt;
&lt;p&gt;Say I hate the default LaTeX monospace font and I want to add &lt;code&gt;\usepackage{inconsolata}&lt;/code&gt; to fancy up my code blocks. Here&apos;s how I can do that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Save a LaTeX template somewhere on my computer&lt;/li&gt;
&lt;li&gt;Edit that template and add the LaTeX packages I want&lt;/li&gt;
&lt;li&gt;Ask Pandoc to use that template every time I compile my documents&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getalatextemplate&quot;&gt;Get a LaTeX template&lt;/h2&gt;
&lt;p&gt;I used &lt;a href=&quot;https://github.com/jgm/pandoc-templates/blob/master/default.latex&quot;&gt;this &lt;code&gt;default.latex&lt;/code&gt; template&lt;/a&gt; provided by &lt;a href=&quot;https://github.com/jgm&quot;&gt;jgm&lt;/a&gt;. Seems to work great!&lt;/p&gt;
&lt;p&gt;I saved mine as &lt;code&gt;~/.pandoc/default.latex&lt;/code&gt; because I think that&apos;s where Pandoc templates usually go.&lt;/p&gt;
&lt;h2 id=&quot;editthattemplate&quot;&gt;Edit that template&lt;/h2&gt;
&lt;p&gt;At &lt;a href=&quot;https://github.com/jgm/pandoc-templates/blob/ec057f0ad3d191d18a2842e6a2fd41c471c6d97d/default.latex#L134&quot;&gt;line 134&lt;/a&gt;, I added some text to remind me where I can safely put &lt;code&gt;\usepackage&lt;/code&gt; commands, as well as my custom command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ADD YOUR \usepackage{...} COMMANDS HERE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
&lt;/code&gt;&lt;p&gt;&lt;code&gt;\usepackage{inconsolata}
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2 id=&quot;makepandocusethattemplate&quot;&gt;Make Pandoc use that template&lt;/h2&gt;
&lt;p&gt;I edited my &lt;code&gt;shell_cmd&lt;/code&gt; inside my &lt;code&gt;.sublime-build&lt;/code&gt; file from the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;shell_cmd&quot;: &quot;pandoc -o \&quot;$file.pdf\&quot; \&quot;$file\&quot; &amp;#x26;&amp;#x26; open -a Preview \&quot;$file.pdf\&quot;&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added a command-line argument pointing Pandoc to my default template as shown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;shell_cmd&quot;: &quot;pandoc --template=\&quot;/Users/mplewis/.pandoc/default.latex\&quot; -o \&quot;$file.pdf\&quot; \&quot;$file\&quot; &amp;#x26;&amp;#x26; open -a Preview \&quot;$file.pdf\&quot;&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now Pandoc uses my new template and makes my typewriter text not suck. Hooray!&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Building an iOS Cocoapod that uses the Dropbox Sync API</title><link>https://kesdev.com/building-an-ios-cocoapod-that-uses-the-dropbox-sync-api</link><guid isPermaLink="true">https://kesdev.com/building-an-ios-cocoapod-that-uses-the-dropbox-sync-api</guid><description>One of the frameworks we&apos;re building at Punch Through Design links to Dropbox to help users access their files from both iOS and their computer. Dropbox provides a very useful Sync SDK for iOS that makes it easy to access Dropbox accounts. Installing the Dropbox SDK can be complicated, so I simply used Cocoapods to install the Dropbox iOS Sync SDK for use with our framework. I wanted the framework…</description><pubDate>Fri, 08 Aug 2014 23:29:03 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;One of the frameworks we&apos;re building at Punch Through Design links to Dropbox to help users access their files from both iOS and their computer. Dropbox provides a very useful &lt;a href=&quot;https://www.dropbox.com/developers/sync&quot;&gt;Sync SDK for iOS&lt;/a&gt; that makes it easy to access Dropbox accounts.&lt;/p&gt;
&lt;p&gt;Installing the Dropbox SDK can be complicated, so I simply used Cocoapods to install the &lt;a href=&quot;http://cocoadocs.org/docsets/Dropbox-Sync-API-SDK/3.0.2/&quot;&gt;Dropbox iOS Sync SDK&lt;/a&gt; for use with our framework.&lt;/p&gt;
&lt;p&gt;I wanted the framework to be easy to install into any project, so I made efforts to distribute it as a Cocoapod. This caused problems: I needed to write a Podspec that included the Dropbox SDK as a framework dependency, and the &lt;a href=&quot;http://guides.cocoapods.org/syntax/podspec.html&quot;&gt;Podspec documentation&lt;/a&gt; is sorely lacking. I couldn&apos;t figure out how to write a podspec that included the Dropbox SDK as a pod dependency AND linked the Dropbox framework so Xcode could find all its headers.&lt;/p&gt;
&lt;p&gt;We ended up stealing some lines of code from the &lt;a href=&quot;https://github.com/overcommitted/ParcelKit/blob/master/ParcelKit.podspec&quot;&gt;ParcelKit podspec&lt;/a&gt;—they also list the iOS Dropbox SDK as a Cocoapod dependency, and they seem to have it figured out.&lt;/p&gt;
&lt;p&gt;Here are the important lines of code from our Podspec file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Pod::Spec.new do |s|
  # ...your code goes here
  s.frameworks   = &apos;Dropbox&apos;
  s.dependency &apos;Dropbox-Sync-API-SDK&apos;, &apos;~&gt; 3.0.2&apos;
  s.xcconfig     = { &apos;FRAMEWORK_SEARCH_PATHS&apos; =&gt; &apos;&quot;${PODS_ROOT}/Dropbox-Sync-API-SDK/dropbox-ios-sync-sdk-3.0.2&quot;&apos; }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I believe you&apos;ll need to change the framework search path if the Dropbox SDK version changes. This code helped me successfully link our Podfile with Dropbox, minimizing the amount of damage we had to do to our &lt;code&gt;.xcodeproj&lt;/code&gt; file.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Submitting a Python CLI Tool to PyPI</title><link>https://kesdev.com/submitting-a-python-cli-tool-to-pypi</link><guid isPermaLink="true">https://kesdev.com/submitting-a-python-cli-tool-to-pypi</guid><description>Last week, I built a command-line tool, csvtomd, to help me write documentation for my personal and work projects. Building Markdown tables by hand can be tedious and often involves lots of manual spacing tweaks to make the source code look just as good as the rendered table. With csvtomd, you can build a table in Excel or Numbers, export it as CSV, and convert the CSV to a Markdown table. I wrote…</description><pubDate>Mon, 21 Jul 2014 00:19:15 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;Last week, I built a command-line tool, &lt;a href=&quot;https://github.com/mplewis/csvtomd&quot;&gt;csvtomd&lt;/a&gt;, to help me write documentation for my personal and work projects. Building Markdown tables by hand can be tedious and often involves lots of manual spacing tweaks to make the source code look just as good as the rendered table. With csvtomd, you can build a table in Excel or Numbers, export it as CSV, and convert the CSV to a Markdown table.&lt;/p&gt;
&lt;p&gt;I wrote this tool in Python 3 and wanted to make it available to other developers. The best way to distribute Python 3 packages is to upload your package to the &lt;a href=&quot;https://pypi.python.org/pypi&quot;&gt;Python Package Index, or PyPI&lt;/a&gt;. Once a package is on PyPI, other developers can install your package with a simple command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip3 install csvtomd
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;helpfulhelp&quot;&gt;Helpful Help&lt;/h1&gt;
&lt;p&gt;Here are a few of the articles that helped me put my package online:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://guide.python-distribute.org/creation.html&quot;&gt;The Hitchhiker&apos;s Guide to Packaging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://coderwall.com/p/qawuyq&quot;&gt;Use Markdown READMEs in Python modules&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most useful tutorial I found was &lt;a href=&quot;https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/&quot;&gt;Sharing Your Labor of Love: PyPI Quick And Dirty&lt;/a&gt; by &lt;a href=&quot;http://hynek.me&quot;&gt;Hynek Schlawack&lt;/a&gt;. It&apos;s a great, simple introduction to getting your Python code ready for PyPI and uploading it as a standalone package.&lt;/p&gt;
&lt;h1 id=&quot;pypiandcliscripts&quot;&gt;PyPI and CLI Scripts&lt;/h1&gt;
&lt;p&gt;One of the hiccups I discovered while trying to install my CLI app was that all the tutorials I found involved creating packages for use in other Python scripts. I had to figure out how to set the entry point to my application so that I could run it properly from the command line.&lt;/p&gt;
&lt;p&gt;My solution was to add this &lt;code&gt;entry_points&lt;/code&gt; line to my &lt;code&gt;setup.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;setup(
    ...
    entry_points={
        &apos;console_scripts&apos;: [
            &apos;csvtomd = csvtomd:main&apos;
        ]
    },
    ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make this entry point work, I had to modify my &lt;code&gt;csvtomd.py&lt;/code&gt; script. Originally, my script&apos;s main module code—parsing arguments, running the conversion—was located outside a function and ran in the body of the main script. I had to move that code into a &lt;code&gt;main()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;For convenience, I added a runner for executing the script standalone:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;pypiandmarkdown&quot;&gt;PyPI and Markdown&lt;/h1&gt;
&lt;p&gt;Will McKenzie came up with &lt;a href=&quot;https://coderwall.com/p/qawuyq&quot;&gt;a nifty solution&lt;/a&gt; for using Markdown READMEs in PyPI packages. Here&apos;s the problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PyPI uses ReStructuredText to render READMEs&lt;/li&gt;
&lt;li&gt;GitHub uses Markdown to render READMEs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Will&apos;s solution was to do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a wrapper called &lt;code&gt;register.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Have &lt;code&gt;register.py&lt;/code&gt; convert &lt;code&gt;README.md&lt;/code&gt; (Markdown) to &lt;code&gt;README.txt&lt;/code&gt; (ReStructuredText)&lt;/li&gt;
&lt;li&gt;Have &lt;code&gt;setup.py&lt;/code&gt; read the &lt;code&gt;README.txt&lt;/code&gt; file as its &lt;code&gt;long_description&lt;/code&gt; property on PyPI&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I had problems with the pyandoc library, so I took Will&apos;s idea and rebuilt it slightly differently into &lt;code&gt;setup_wrap.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
import os
import subprocess
import pypandoc
&lt;p&gt;with open(‘README.rst’, ‘w’) as dest:
long_description = pypandoc.convert(‘README.md’, ‘rst’)
dest.write(long_description)&lt;/p&gt;
&lt;p&gt;args = [‘python3’, ‘setup.py’] + sys.argv[1:]
subprocess.call(args)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code class=&quot;language-python&quot;&gt;os.remove(‘README.rst’)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setup_wrap.py&lt;/code&gt; does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Converts &lt;code&gt;README.md&lt;/code&gt; to &lt;code&gt;README.rst&lt;/code&gt; using &lt;a href=&quot;https://github.com/bebraw/pypandoc&quot;&gt;pypandoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;python3 setup.py&lt;/code&gt; and passes along its own arguments&lt;/li&gt;
&lt;li&gt;Deletes &lt;code&gt;README.rst&lt;/code&gt; after &lt;code&gt;setup.py&lt;/code&gt; is done&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Additionally, I added the following to &lt;code&gt;setup.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with open(&apos;README.rst&apos;) as f:
    long_description = f.read()
&lt;/code&gt;&lt;p&gt;&lt;code class=&quot;language-python&quot;&gt;setup(
…
long_description=long_description,
…
)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;So now, instead of running &lt;code&gt;python3 setup.py sdist&lt;/code&gt;, just run &lt;code&gt;python3 setup_wrap.py sdist&lt;/code&gt; and you&apos;ll have a very pretty README on both GitHub and PyPI.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Week One with iOS</title><link>https://kesdev.com/week-one-with-ios</link><guid isPermaLink="true">https://kesdev.com/week-one-with-ios</guid><description>It&apos;s really not that bad. I think I might prefer it to Android soon. Interface Builder and Storyboards are nicer than Android&apos;s XML layouts, but you have the learning curve of the message-passing component linking GUI. I was really annoyed with iOS for using IBOutlets and delegates to programatically link code and UI until I realized that Android does the exact same thing under a different name. I…</description><pubDate>Sat, 14 Jun 2014 06:43:58 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;It&apos;s really not that bad. I think I might prefer it to Android soon.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interface Builder and Storyboards are nicer than Android&apos;s XML layouts, but you have the learning curve of the message-passing component linking GUI. I was really annoyed with iOS for using IBOutlets and delegates to programatically link code and UI until I realized that Android does the exact same thing under a different name.&lt;/li&gt;
&lt;li&gt;It&apos;s much easier to build a good-looking iOS app out of the box thanks to their great UI elements and thought put into design. Android apps look like trash out of the box and they&apos;re hard to make pretty, especially if you try them on more than one phone.&lt;/li&gt;
&lt;li&gt;I didn&apos;t like how I had to subclass UIViewController for every single individual screen until I realized that I have to do literally the same thing in Java. Android does have Activities though, and those were really nice—it was clear how to pass parameters from one view to another. There&apos;s probably just as good of a way to do this in the view constructor.&lt;/li&gt;
&lt;li&gt;I was really afraid of memory management but it turns out iOS has ARC which is pretty neat.&lt;/li&gt;
&lt;li&gt;I still don&apos;t know what half the keywords in Obj-C mean. Here&apos;s an example: &lt;code&gt;@property (nonatomic, retain) IBOutlet UIButton *myButton;&lt;/code&gt;—I know &lt;code&gt;retain&lt;/code&gt; has something to do with not letting ARC discard it, but how does &lt;code&gt;IBOutlet&lt;/code&gt; &quot;decorate&quot; it? What&apos;s &lt;code&gt;nonatomic&lt;/code&gt; mean? What does an &lt;code&gt;@&lt;/code&gt; symbol specify, because I&apos;ve been using it for like ten things already today.&lt;/li&gt;
&lt;li&gt;Why are there three ways to register something into Interface Builder, and why does the most common one involve setting a return type that gets thrown away and left unused?&lt;/li&gt;
&lt;li&gt;It took me 30 minutes today to realize that my &lt;code&gt;Configs/app_ids.plist&lt;/code&gt; file was being statically linked into the root when I ran my app, keeping me from importing it from the &lt;code&gt;Configs&lt;/code&gt; folder I specified. Aaagh. Why?&lt;/li&gt;
&lt;li&gt;This week, Objective-C has felt like a mesh between the readability of Haskell, the parens of Lisp, and the elegant naming styles of Java.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I will probably keep developing for iOS and Android, but I think I like iOS a bit more now—it lets me work my chops as a designer a bit more than Android, and it&apos;s not that different after banging your head into the system for a week.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>LED crowd visualizations suck, and here&apos;s how to make them better</title><link>https://kesdev.com/led-crowd-visualizations-suck</link><guid isPermaLink="true">https://kesdev.com/led-crowd-visualizations-suck</guid><description>This post is a WIP outline. Feel free to read it if you&apos;d like. People love attending fireworks events and laser light shows. It&apos;s awe-inspiring to see coordinated events happen on a scale so much larger than a single person. It&apos;s even more incredible to become a part of the performance yourself. In 2007, Graffiti Research Lab launched a project called L.A.S.E.R Tag that let users spray large-scal…</description><pubDate>Wed, 21 May 2014 04:16:03 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;&lt;em&gt;This post is a WIP outline. Feel free to read it if you&apos;d like.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;People love attending fireworks events and laser light shows. It&apos;s awe-inspiring to see coordinated events happen on a scale so much larger than a single person.&lt;/p&gt;
&lt;p&gt;It&apos;s even more incredible to become a part of the performance yourself. In 2007, Graffiti Research Lab launched a project called &lt;a href=&quot;http://www.graffitiresearchlab.com/blog/projects/laser-tag/&quot;&gt;L.A.S.E.R Tag&lt;/a&gt; that let users spray large-scale digital graffiti on the sides of massive buildings using a laser and a high-power projector.&lt;/p&gt;
&lt;iframe width=&quot;700&quot; height=&quot;525&quot; src=&quot;//www.youtube.com/embed/EFWcAkxzkv4&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Giving people a chance to be a part of the show grants them a feeling of empowerment. It feels good to be part of something bigger, especially when you can personally see hundreds or thousands of other people invested in the same idea.&lt;/p&gt;
&lt;h1 id=&quot;crowdvisualizationisexciting&quot;&gt;Crowd visualization is exciting.&lt;/h1&gt;
&lt;p&gt;In 2012, Coldplay gave out LED wristbands called &lt;a href=&quot;http://en.wikipedia.org/wiki/Xyloband&quot;&gt;&lt;strong&gt;Xylobands&lt;/strong&gt;&lt;/a&gt; to people attending their Moto Xyloto tour concerts. This was the first time Xylobands were used in a large-scale performance.&lt;/p&gt;
&lt;iframe width=&quot;700&quot; height=&quot;394&quot; src=&quot;//www.youtube.com/embed/QKcxhQZxvdw&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;The Xylobands were given out for free to concert attendees. They sport LEDs that light up in sync with the performance. &lt;strong&gt;[FIXME]&lt;/strong&gt; It&apos;s really cool to see the entire crowd flash with the beat of the music.&lt;/p&gt;
&lt;p&gt;In June 2012, Disney rolled out their &lt;strong&gt;Glow with the Show&lt;/strong&gt; ears at select Disney parks.&lt;/p&gt;
&lt;iframe width=&quot;700&quot; height=&quot;394&quot; src=&quot;//www.youtube.com/embed/qVe93Vhbpxk&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;While the Xylobands flash, blink, and glow in a single color determined by the plastic casing, the Disney ears are translucent white and have RGB LEDs built into the body, allowing them to glow in any color. In addition, the Disney ears also support more sophisticated patterns than the Xylobands.&lt;/p&gt;
&lt;p&gt;Some of the Disney ears patterns shown in the video include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;blending from one color to another,&lt;/li&gt;
&lt;li&gt;fading on and off smoothly, and&lt;/li&gt;
&lt;li&gt;displaying independent colors and fades on each ear.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;wecandobetter&quot;&gt;We can do better.&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Crowds aren&apos;t blobs&lt;/li&gt;
&lt;li&gt;They&apos;re people&lt;/li&gt;
&lt;li&gt;Screens (2D pixel arrays) are cool&lt;/li&gt;
&lt;li&gt;Displaying coherent patterns on free-form blobs of pixels creates an incredible effect&lt;/li&gt;
&lt;/ul&gt;
&lt;iframe width=&quot;700&quot; height=&quot;394&quot; src=&quot;//www.youtube.com/embed/0bKNnXUMQ9k&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h1 id=&quot;techspecs&quot;&gt;Tech Specs&lt;/h1&gt;
&lt;p&gt;Here&apos;s a quick overview of&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Glow with the Show ears&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Translucent plastic enclosure&lt;/li&gt;
&lt;li&gt;IR blaster at front of stage sends signal to &lt;strong&gt;Vishay TSMP6000&lt;/strong&gt; IR receiver&lt;/li&gt;
&lt;li&gt;Each device repeats IR from receiver on front to emitter on back&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TI MSP430G2553&lt;/strong&gt; microcontroller contros RGB LEDs in ears&lt;/li&gt;
&lt;li&gt;Runs on 3 AAA batteries&lt;/li&gt;
&lt;li&gt;All devices flash the same patte (!!)rn during the show&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$25 (!!)&lt;/strong&gt; at park kiosks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Coldplay Xyloband&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Opaque colored plastic enclosure mounted on nylon wristband strap&lt;/li&gt;
&lt;li&gt;Each device has its own color&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Atmel ATmega48PA&lt;/strong&gt; (according to &lt;a href=&quot;http://hackaday.com/2012/02/19/ask-hackaday-did-you-catch-the-grammys/&quot;&gt;Hackaday&lt;/a&gt;) or &lt;strong&gt;Silicon Labs 8-bit µC&lt;/strong&gt; (according to &lt;a href=&quot;https://www.youtube.com/watch?v=5s2NQnBgIEw&quot;&gt;CarlsTechShed&lt;/a&gt;) is probably the microcontroller being used&lt;/li&gt;
&lt;li&gt;Operator can select to pulse all bands or select colors independently from one another&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SI4313&lt;/strong&gt; RF receiver &lt;a href=&quot;http://mplewis.com/files/si4313.pdf&quot;&gt;(PDF datasheet)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don&apos;t forget to &lt;a href=&quot;https://www.youtube.com/watch?v=5s2NQnBgIEw&quot;&gt;check out the teardown&lt;/a&gt; of the Xyloband—thanks, &lt;a href=&quot;https://www.youtube.com/user/CarlsTechShed&quot;&gt;CarlsTechShed&lt;/a&gt;!&lt;/p&gt;
&lt;h1 id=&quot;whydotheysuck&quot;&gt;Why do they suck?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Single setting for the entire crowd&lt;/li&gt;
&lt;li&gt;No way to treat the crowd like a screen&lt;/li&gt;
&lt;li&gt;Devices aren&apos;t interactive—users don&apos;t give back to the show&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;devicerequirements&quot;&gt;Device Requirements&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Devices need to:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be cool&lt;/li&gt;
&lt;li&gt;Be light&lt;/li&gt;
&lt;li&gt;Be wearable&lt;/li&gt;
&lt;li&gt;Have an RGB LED&lt;/li&gt;
&lt;li&gt;Receive data from a base station&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be individually addressable&lt;/strong&gt;—this is the one thing these devices should do but don&apos;t&lt;/li&gt;
&lt;li&gt;Be cheap enough that people will buy them at a gift shop or that the price can be factored into a show ticket ($10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Devices could:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have an accelerometer&lt;/li&gt;
&lt;li&gt;Translate live accelerometer data into visualizations&lt;/li&gt;
&lt;li&gt;Return accelerometer data to the base station&lt;/li&gt;
&lt;li&gt;Bounce signals back to other devices&lt;/li&gt;
&lt;li&gt;Be controlled by DMX via an adapter at the base station for use in shows with DMX setups&lt;/li&gt;
&lt;/ul&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Analyzing 8 years of my music history with SQL</title><link>https://kesdev.com/analyzing-8-years-of-my-music-history-with-sql</link><guid isPermaLink="true">https://kesdev.com/analyzing-8-years-of-my-music-history-with-sql</guid><description>Ever heard an album or a song for the first time and loved it so much you played it on repeat for three days straight? I wanted to dig into my listening history to find out which songs I loved, got sick of, and forgot about for years. Good thing I use Last.fm obsessively. Last.fm is a service that &quot;scrobbles&quot;—keeps track of—every song you listen to past the halfway mark. I&apos;ve been using Last.fm si…</description><pubDate>Thu, 10 Apr 2014 05:34:20 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;&lt;strong&gt;Ever heard an album or a song for the first time and loved it so much you played it on repeat for three days straight?&lt;/strong&gt; I wanted to dig into my listening history to find out which songs I loved, got sick of, and forgot about for years.&lt;/p&gt;
&lt;p&gt;Good thing I use Last.fm obsessively. Last.fm is a service that &lt;em&gt;&quot;scrobbles&quot;&lt;/em&gt;—keeps track of—every song you listen to past the halfway mark. I&apos;ve been using Last.fm since 2006 and have accumulated a &lt;em&gt;seriously&lt;/em&gt; extensive scrobble log.&lt;/p&gt;
&lt;p&gt;Last.fm provides users with an API for accessing their scrobble history. I figured that if I scraped my entire listening history into a SQL database, I could better analyze my listening history.&lt;/p&gt;
&lt;h2 id=&quot;scrapingthelastfmapiwithpython&quot;&gt;Scraping the Last.fm API with Python&lt;/h2&gt;
&lt;p&gt;I wrote some Python to claw my data out of the internals of Last.fm, one page at a time. It uses the &lt;a href=&quot;http://docs.python-requests.org/en/latest/&quot;&gt;Requests&lt;/a&gt; and &lt;a href=&quot;http://docs.python-requests.org/en/latest/&quot;&gt;Dataset&lt;/a&gt; packages. I used &lt;a href=&quot;http://ipython.org/notebook.html&quot;&gt;iPython Notebook&lt;/a&gt; to assemble a &lt;strong&gt;docs-and-code mashup of doom.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.mplewis.com/files/lastfm-scraper.html&quot;&gt;Check out my writeup on scraping Last.fm data here!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(If you have iPython Notebook, you can &lt;a href=&quot;http://www.mplewis.com/files/lastfm-scraper.ipynb&quot;&gt;download the notebook file here&lt;/a&gt; and run it yourself.)&lt;/p&gt;
&lt;h2 id=&quot;whatchuknowboutme&quot;&gt;Whatchu know &apos;bout me?&lt;/h2&gt;
&lt;p&gt;Once my data was scraped into my DB, I ran some queries to search for songs I played obsessively after I discovered them, and I found several of my music-listening phases, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;that period during which I listened exclusively to &quot;Weird Al&quot; Yankovic,&lt;/li&gt;
&lt;li&gt;the landmark date in 2006 when I listened to Daft Punk&apos;s &lt;em&gt;Discovery&lt;/em&gt; for the first time, and&lt;/li&gt;
&lt;li&gt;that time I saw &lt;em&gt;Wicked&lt;/em&gt; with my high school band and listened to the soundtrack for a week straight.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is awesome&lt;/strong&gt;. I&apos;m a stats nerd and I love the idea of a quantified self. Thank you, 14-year-old Matt, for signing up for Last.fm 8 years ago.&lt;/p&gt;
&lt;p&gt;There&apos;s plenty more to find out. Like, &lt;a href=&quot;http://rd.io/x/QWTS1Ddd7GBS/&quot;&gt;this has been my favorite song so far this week&lt;/a&gt; and &lt;a href=&quot;http://www.rdio.com/artist/Basement_Jaxx_Vs._Metropole_Orkest/album/Basement_Jaxx_Vs._Metropole_Orkest/&quot;&gt;this is my favorite newly-discovered album&lt;/a&gt;. And &lt;a href=&quot;http://rd.io/x/QWTS1DFtBiM/&quot;&gt;this is probably my favorite new artist by far&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;okthisisgettingkindacreepy&quot;&gt;OK, this is getting kinda creepy&lt;/h2&gt;
&lt;p&gt;But don&apos;t take my word for it—I&apos;ve put my entire scrobble log online for you. Want to know on which day I fell asleep listening to Ellie Goulding in 2012? Want to dig up that evidence that I&apos;m actually a closet Ke$ha fan? &lt;strong&gt;&lt;a href=&quot;http://www.mplewis.com/files/lastfm-trickybeta-20140410.zip&quot;&gt;Download my SQLite database and learn what you will about 8 years of Matt&apos;s musical taste.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As always, email me at &lt;a href=&quot;mailto:matt@mplewis.com&quot;&gt;matt@mplewis.com&lt;/a&gt; or tweet me at &lt;a href=&quot;https://twitter.com/mplewis&quot;&gt;@mplewis&lt;/a&gt; if you do something cool with this! I always love hearing when someone takes something I helped build and makes it even more interesting.&lt;/p&gt;
&lt;p&gt;And if you do find out I like Ke$ha, please keep it to yourself maybe?&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Throwback Thursday: My First Sorting Algorithm</title><link>https://kesdev.com/throwback-thursday-my-first-sorting-algorithm</link><guid isPermaLink="true">https://kesdev.com/throwback-thursday-my-first-sorting-algorithm</guid><description>I just rediscovered some code I wrote for the University of Minnesota&apos;s &quot;Structure of Computer Programming I&quot;, a course I took three years ago. CSCI 1901 focused mostly on learning Scheme with everyone&apos;s favorite Scheme textbook, the venerable SICP from MIT. To get better at writing Scheme, I practiced by writing two versions of the bubble sort algorithm. One sorted a list of integers into increas…</description><pubDate>Thu, 03 Apr 2014 09:31:12 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;I just rediscovered some code I wrote for the University of Minnesota&apos;s &lt;em&gt;&quot;Structure of Computer Programming I&quot;&lt;/em&gt;, a course I took three years ago. CSCI 1901 focused mostly on learning Scheme with everyone&apos;s favorite Scheme textbook, the venerable &lt;a href=&quot;http://mitpress.mit.edu/sicp/&quot;&gt;SICP from MIT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get better at writing Scheme, I practiced by writing two versions of the bubble sort algorithm. One sorted a list of integers into increasing order, while another sorted a list of Cartesian tuples (x, y coordinates) into increasing distance from the origin. I think these sorts are even stable!&lt;/p&gt;
&lt;p&gt;Building a bubblesort algorithm from scratch is one of the most rewarding things I&apos;ve ever done as a programmer. To this day I&apos;m still proud of these programs.&lt;/p&gt;
&lt;p&gt;I even commented (one of) them. Enjoy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/mplewis/9952091&quot;&gt;bubblesort-int-list.scm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/mplewis/9952068&quot;&gt;bubblesort-point-list.scm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Released under &lt;a href=&quot;http://opensource.org/licenses/ISC&quot;&gt;the ISC License&lt;/a&gt;. Go crazy.)&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>So You Want To Make Your Images Responsive</title><link>https://kesdev.com/so-you-want-to-make-your-images-responsive</link><guid isPermaLink="true">https://kesdev.com/so-you-want-to-make-your-images-responsive</guid><description>Your website looks like it&apos;s from &apos;06. Your images look blurry when those kids pull up your site on their fancy new Retina MacBooks and iPad Airs. What do? Wait for srcset to come out Implement srcset-polyfill with code that isn&apos;t production-ready Send your users images at 2X resolution, even if they&apos;re on mobile Use BBC News&apos; imager.js You should be using imager.js. Here&apos;s why: It&apos;s production-re…</description><pubDate>Wed, 11 Dec 2013 00:19:29 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;Your website looks like it&apos;s from &apos;06. Your images look blurry when those kids pull up your site on their fancy new Retina MacBooks and iPad Airs. What do?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wait for &lt;a href=&quot;http://www.w3.org/html/wg/drafts/srcset/w3c-srcset/&quot;&gt;&lt;code&gt;srcset&lt;/code&gt;&lt;/a&gt; to come out&lt;/li&gt;
&lt;li&gt;Implement &lt;a href=&quot;https://github.com/borismus/srcset-polyfill&quot;&gt;&lt;code&gt;srcset-polyfill&lt;/code&gt;&lt;/a&gt; with code that isn&apos;t production-ready&lt;/li&gt;
&lt;li&gt;Send your users images at 2X resolution, even if they&apos;re on mobile&lt;/li&gt;
&lt;li&gt;Use BBC News&apos; &lt;a href=&quot;https://github.com/BBC-News/Imager.js&quot;&gt;&lt;code&gt;imager.js&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should be using &lt;code&gt;imager.js&lt;/code&gt;. Here&apos;s why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&apos;s production-ready—the BBC uses this on their sites&lt;/li&gt;
&lt;li&gt;It&apos;s documented very, very well.&lt;/li&gt;
&lt;li&gt;It&apos;s &lt;em&gt;smart&lt;/em&gt;—it lazy loads specific images based on the viewport properties, saving your mobile users time and bandwidth&lt;/li&gt;
&lt;li&gt;Your users will love you for having a fast-loading site (with pretty Retina images, if they&apos;re into that).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/BBC-News/Imager.js&quot;&gt;Go check out some examples and get started!&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;appendixokimusingimagerjsnowwhat&quot;&gt;Appendix: &lt;em&gt;OK, I&apos;m using imager.js. Now what?&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Start generating your images automatically from source. Ain&apos;t nobody got time for doing that by hand.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/andismith/grunt-responsive-images/&quot;&gt;&lt;code&gt;grunt-responsive-images&lt;/code&gt;&lt;/a&gt; will do the heavy lifting for you. Yay for automation!&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>How To Make An EE Cry With One Animated GIF</title><link>https://kesdev.com/how-to-make-an-ee-cry-with-one-animated-gif</link><guid isPermaLink="true">https://kesdev.com/how-to-make-an-ee-cry-with-one-animated-gif</guid><description>Auto route, yo. (Check out the HTML5 video here.)</description><pubDate>Mon, 11 Nov 2013 03:02:44 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;&lt;a href=&quot;http://gfycat.com/AffectionateUniformBettong&quot;&gt;Auto route, yo. (Check out the HTML5 video here.)&lt;/a&gt;&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Deploying an Octopress blog from a VPS to NearlyFreeSpeech.net</title><link>https://kesdev.com/deploying-an-octopress-blog-from-a-vps-to-nearlyfreespeech-net</link><guid isPermaLink="true">https://kesdev.com/deploying-an-octopress-blog-from-a-vps-to-nearlyfreespeech-net</guid><description>Author&apos;s note: This was migrated from an Octopress blog. I think the data is still relevant to anyone trying to run Octopress and deploy to NearlyFreeSpeech, so I&apos;ve kept the post online in its old form. Enjoy! Hello! I&apos;m Matthew Lewis, and this is my Octopress blog. I&apos;d like to use this blog for projects, and I suppose the first project I should discuss is this blog itself. Octopress is a bloggin…</description><pubDate>Fri, 08 Nov 2013 19:10:36 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Author&apos;s note: This was migrated from an Octopress blog. I think the data is still relevant to anyone trying to run Octopress and deploy to NearlyFreeSpeech, so I&apos;ve kept the post online in its old form. Enjoy!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hello! I&apos;m Matthew Lewis, and this is my Octopress blog.&lt;/p&gt;
&lt;p&gt;I&apos;d like to use this blog for projects, and I suppose the first project I should discuss is this blog itself.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://octopress.org/&quot;&gt;Octopress&lt;/a&gt; is a blogging framework. It&apos;s based on &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;jekyll&lt;/a&gt;, a static site generator.&lt;/p&gt;
&lt;p&gt;Frankly, Octopress is amazing. It&apos;s easy to install and modify themes and layouts, and I can run a single &lt;code&gt;rake&lt;/code&gt; command to deploy my blog from my VPS to my &lt;a href=&quot;http://www.nearlyfreespeech.net&quot;&gt;NearlyFreeSpeech&lt;/a&gt; site.&lt;/p&gt;
&lt;p&gt;In this post, I&apos;ll detail how to generate and deploy your Octopress blog straight to your NearlyFreeSpeech.net hosting account in one command.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h1 id=&quot;deploymentstack&quot;&gt;Deployment Stack&lt;/h1&gt;
&lt;p&gt;I&apos;m running on a Debian 32-bit VPS hosted on &lt;a href=&quot;http://www.bigscoots.com&quot;&gt;BigScoots&lt;/a&gt; and hosting www.mplewis.com on NearlyFreeSpeech.net.&lt;/p&gt;
&lt;h1 id=&quot;whyuseavpsandawebhost&quot;&gt;Why use a VPS and a web host?&lt;/h1&gt;
&lt;p&gt;I want to host my blog on NFS.net because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NFS can handle much more traffic than my dev VPS (my VPS is uber cheap)&lt;/li&gt;
&lt;li&gt;NFS is seriously cheap for static sites, which makes using Octopress on NFS very attractive&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have to use a VPS to deploy because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NFS doesn&apos;t have the proper Ruby or bundles or anything I need to generate my Octopress site using Jekyll&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;sohowsthedeploywork&quot;&gt;So, how&apos;s the deploy work?&lt;/h1&gt;
&lt;p&gt;The process behind deploying an Octopress blog is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;rake generate&lt;/code&gt; to generate the static files behind the blog posts&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;rake deploy&lt;/code&gt; to rsync the static files to a web server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&apos;s it. The config is documented in &lt;a href=&quot;http://octopress.org/docs/deploying/rsync/&quot;&gt;the Octopress docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And if you&apos;re lazy like me, use the combined &lt;code&gt;rake&lt;/code&gt; command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;rake gen_deploy&lt;/code&gt; to generate the blog and deploy it to your server. Hooray!&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;andthatsitthateasy&quot;&gt;And that&apos;s it? That easy?&lt;/h1&gt;
&lt;p&gt;Yes... and no.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rake deploy&lt;/code&gt; wants a public key to connect to your web server.&lt;/p&gt;
&lt;p&gt;NearlyFreeSpeech.net will let you use a public key to authenticate. However, you have to file a free assistance ticket and wait a couple of hours for the key to be set up on your account.&lt;/p&gt;
&lt;p&gt;More information can be found &lt;a href=&quot;https://members.nearlyfreespeech.net/mplewis/support/faq?q=SSHKeys&amp;#x26;keywords=key&amp;#x26;form=1#SSHKeys&quot;&gt;here&lt;/a&gt; on NFS.net&apos;s FAQ, but it&apos;s a very simple process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click the link titled &quot;assistance request&quot; to submit a free assistance request.&lt;/li&gt;
&lt;li&gt;Copy your &lt;code&gt;id_rsa.pub&lt;/code&gt; and paste it in the text field, and ask them to add that pub key to your account.&lt;/li&gt;
&lt;li&gt;Wait two hours.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And you&apos;re done! Your dev server will now be able to deploy straight to any of your NFS sites without a password.&lt;/p&gt;
&lt;p&gt;I hope this information helps anyone looking for info on how to deploy to NearlyFreeSpeech.net via an external server.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>I&apos;ll be watching you: Stalk me in real time via the web</title><link>https://kesdev.com/ill-be-watching-you-stalk-me-in-real-time-via-the-web</link><guid isPermaLink="true">https://kesdev.com/ill-be-watching-you-stalk-me-in-real-time-via-the-web</guid><description>Author&apos;s note: Google took down Google Latitude and Locality is no longer online. But my code is still around, and I&apos;ll leave this up in case anyone finds anything of use! I&apos;ve always been fascinated by the idea of real-time location data. The first Google Glass video showed a person checking where his friend was with a real-time location service. I always liked the idea of the Weasley family cloc…</description><pubDate>Fri, 08 Nov 2013 19:07:21 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Author&apos;s note: Google took down Google Latitude and Locality is no longer online. But my code is still around, and I&apos;ll leave this up in case anyone finds anything of use!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;ve always been fascinated by the idea of real-time location data. &lt;a href=&quot;http://www.youtube.com/watch?v=JSnB06um5r4&quot;&gt;The first Google Glass video&lt;/a&gt; showed a person checking where his friend was with a real-time location service. I always liked the idea of &lt;a href=&quot;&quot;&gt;the Weasley family clock from Harry Potter&lt;/a&gt;, which the mother of the large Weasley family used to keep tabs on her kids and husband. So I tried to build a Weasley clock of my own with HTML, CSS, and a little JavaScript magic.&lt;/p&gt;
&lt;p&gt;I named my web application &lt;a href=&quot;http://www.mplewis.com/locality/&quot;&gt;&lt;strong&gt;Locality&lt;/strong&gt;&lt;/a&gt;. &lt;a href=&quot;http://www.mplewis.com/locality/&quot;&gt;Check it out right now,&lt;/a&gt; and read on to find out how it works.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;locality&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Noun&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The position or site of something.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An area or neighborhood, esp. as regarded as a place occupied by certain people or as the scene of particular activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Step one is figuring out where I am at any given time. The easiest way to get my own real-time location data is with an Android smartphone that I already have. It&apos;s got GPS and 4G, so it&apos;ll try to sync my location to &lt;a href=&quot;http://latitude.google.com/&quot;&gt;Google Latitude&lt;/a&gt; whenever I move around.&lt;/p&gt;
&lt;p&gt;Getting the data out of Google Latitude is slightly tricky. Latitude offers &quot;site badges&quot;, HTML drop-ins that put a little map and an icon on the map to show people where you are. But I want the coordinates, and Latitude offers a JSON feed to get that data out too. The URL for any given user&apos;s JSON feed is &lt;code&gt;https://latitude.google.com/latitude/apps/badge/api?user=YOUR_USER_ID_HERE&amp;#x26;type=json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;ve ever worked with JSON before, you know that making a JSON request from one domain to another is likely to fail because of &lt;a href=&quot;&quot;&gt;HTTP access control (CORS)&lt;/a&gt;. So I used &lt;a href=&quot;http://developer.yahoo.com/yql/&quot;&gt;Yahoo&apos;s YQL&lt;/a&gt; to import a JSON feed, read the data, and spit it back out as JSONP -- which isn&apos;t governed by CORS.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// URL for Latitude data in JSON form
var latitudeUrl = &apos;https://latitude.google.com/latitude/apps/badge/api?user=MY_USER_ID&amp;#x26;type=json&apos;;
// use Yahoo YQL to proxy the JSON into JSONP
var yahooYqlUrl = &apos;http://query.yahooapis.com/v1/public/yql?callback=?&apos;
&lt;/code&gt;&lt;p&gt;&lt;code class=&quot;language-javascript&quot;&gt;$.getJSON(yahooYqlUrl, {
q: ‘select * from json where url=”’ + latitudeUrl + ’”’,
format: ‘json’
}, function(response) {
var data = response.query.results.json;
// do something with data here
});
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s a bit of a hacky solution but it works very well. &lt;a href=&quot;http://stackoverflow.com/a/8579158/254187&quot;&gt;Credit where credit is due to Stack Overflow user hippietrail.&lt;/a&gt; Apparently there&apos;s also a cool little service called &lt;a href=&quot;http://jsonp.ru/&quot;&gt;jsonp.ru&lt;/a&gt; that does the same thing, but I trust Yahoo more with my data.&lt;/p&gt;
&lt;p&gt;Once I&apos;ve got the Matt coordinates, I need to figure out where Matt is. I started by constructing a list of location objects with data on each location of which I wanted to keep track. I grabbed the coordinates from Google Maps by using the &quot;right-click to drop coordinates&quot; Labs addon and manually typing the coordinates for each location into my code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var locations = [
    {
        name: &quot;Stadium View&quot;,
        coordinates: {
            latitude: 44.971599,
            longitude: -93.221807
        }
    },
    {
        name: &quot;Kenneth H. Keller Hall&quot;,
        coordinates: {
            latitude: 44.974650,
            longitude: -93.232460
        }
    },
    // ... put more locations here
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, so now I have the coordinates of locations and the coordinates for myself. I need to be able to tell how far away I am from each location. It turns out this is a reasonably well-solved problem -- I found some fantastic scripts from &lt;a href=&quot;http://www.movable-type.co.uk/scripts/latlong.html&quot;&gt;Chris Veness at movable-type.co.uk&lt;/a&gt; that do just what I want: simple, accurate calculation of distances between two latitude/longitude pairs.&lt;/p&gt;
&lt;p&gt;I wrote a little function that uses &lt;code&gt;LatLon.js&lt;/code&gt; to calculate the distance in miles between two pairs of coordinates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function distMiLatLon(lat1, lon1, lat2, lon2) {
    var rMi = 3963.1676; // earth&apos;s radius in miles
    var p1 = new LatLon(lat1, lon1, rMi);
    var p2 = new LatLon(lat2, lon2, rMi);
    return p1.distanceTo(p2); // in miles
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For my application, I decided if I was within 500 feet of a known location, I should be checked in. 500 feet == .094697 miles, so I set &lt;code&gt;var maxDistToValidateLoc = .094697&lt;/code&gt; in my code. After Locality gets the coordinates for locations and for me, it iterates down the list of locations and calculates a distance to each one. If a distance is less than .094697 miles, it registers me as being present at that location. Otherwise, it puts me in the &quot;no man&apos;s land&quot; category of &quot;somewhere else&quot;.&lt;/p&gt;
&lt;p&gt;That&apos;s the JavaScript and data handling side covered. I&apos;ll write more about the HTML/CSS layout in another post. For now, check out the app at &lt;a href=&quot;http://www.mplewis.com/locality/&quot;&gt;mplewis.com/locality&lt;/a&gt; and tell me what you think!&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Downloading Your Google Location History With The Google API Python Client Library</title><link>https://kesdev.com/downloading-your-google-location-history-with-the-google-api-python-client-library</link><guid isPermaLink="true">https://kesdev.com/downloading-your-google-location-history-with-the-google-api-python-client-library</guid><description>tl;dr If you want to get started with your Latitude data right away, check out my gist at the bottom. I&apos;ve started a new project! I&apos;m investigating ways to share my life with others electronically, with little to no conscious effort on my part. One of the things I&apos;d like to share is my location history. I&apos;m not sure how much or how little information I want to give to a total stranger, but I belie…</description><pubDate>Fri, 08 Nov 2013 19:06:34 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;tl;dr If you want to get started with your Latitude data right away, check out my gist at the bottom.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&apos;ve started a new project! I&apos;m investigating ways to share my life with others electronically, with little to no conscious effort on my part.&lt;/p&gt;
&lt;p&gt;One of the things I&apos;d like to share is my location history. I&apos;m not sure how much or how little information I want to give to a total stranger, but I believe that if I want to share a significant amount of my life with everyone on the Internet, context is key -- and location makes a huge deal when it comes to taking events in my life into context. Right now I live in San Francisco, but two months ago I lived in Minneapolis, and somewhere in between I spent time with my family in Wausau. Knowing this is key to understanding what I want to share with the world.&lt;/p&gt;
&lt;p&gt;I&apos;m being intentionally vague about the actual project I&apos;m working on, but that&apos;s okay -- this post is about one of the key elements, and I&apos;ll post more later when I start assembling other parts.&lt;/p&gt;
&lt;p&gt;Read on to find out how to get your super-interesting real-time location data out of everyone&apos;s favorite location-tracking Google app.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h1 id=&quot;howtogeolog101&quot;&gt;How to Geolog 101&lt;/h1&gt;
&lt;p&gt;What&apos;s the best, most convenient, cheapest way to tell people where I&apos;ve been in the past? There are plenty of ways to log my location, and most of them involve the use of GPS to lock onto my location, and some kind of data logging device to put the data into a format I can use.&lt;/p&gt;
&lt;p&gt;I could build a device from scratch involving a GPS module, a microcontroller and a data logger, or I could just use my Android phone!&lt;/p&gt;
&lt;p&gt;Android, as we all know, is developed by Google, and Google puts out tons of other cool web products. One of those products is Google Latitude.&lt;/p&gt;
&lt;p&gt;From Google&apos;s website:&lt;/p&gt;
&lt;p&gt;{% blockquote %}&lt;br&gt;
Google Latitude is a location-aware mobile app developed by Google as a successor to its earlier SMS-based service Dodgeball. Latitude allows a mobile phone user to allow certain people to view their current location. Via their own Google Account, the user&apos;s cell phone location is mapped on Google Maps.&lt;br&gt;
{% endblockquote %}&lt;/p&gt;
&lt;p&gt;Perfect! Plus -- if I use my cell phone, it uses &lt;a href=&quot;http://www.skyhookwireless.com/&quot;&gt;Skyhook&lt;/a&gt; to approximate my location even better with the use of wifi hotspots and cell phone towers.&lt;/p&gt;
&lt;p&gt;So, Google Latitude will log my location over time in Location History. All I need to do is carry my cell phone on me and keep it turned on. Easy.&lt;/p&gt;
&lt;p&gt;My next goal, then, is to get my data out of Location History and into a form I can use.&lt;/p&gt;
&lt;h1 id=&quot;prerequisiteswhatchuneed&quot;&gt;Prerequisites (&lt;a href=&quot;https://www.youtube.com/watch?v=s8S5PN1FnFk&quot;&gt;Whatchu Need&lt;/a&gt;)&lt;/h1&gt;
&lt;p&gt;To be able to follow along and extract your own Location History data, you&apos;ll need the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Location History data in your Google account&lt;/li&gt;
&lt;li&gt;A Python 2.7+ installation&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://code.google.com/p/google-api-python-client/&quot;&gt;Google API Python Client&lt;/a&gt; -- I used &lt;code&gt;pip install google-api-python-client&lt;/code&gt; to install this.&lt;/li&gt;
&lt;li&gt;A Google account.&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://developers.google.com/console/help/&quot;&gt;Google API project&lt;/a&gt; with access to the Latitude API. See &lt;a href=&quot;https://developers.google.com/console/help/#creatingdeletingprojects&quot;&gt;Creating a project&lt;/a&gt; for information on creating a new API project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After you create a project, you&apos;ll want to go to the &lt;code&gt;API Access&lt;/code&gt; tab and create an OAuth 2.0 Client ID. Make sure you set the application type to &lt;code&gt;Installed application&lt;/code&gt; and the installed application type to &lt;code&gt;Other&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;accessingyourdatausingapoorlydocumentedbutwellwrittenapilibrary&quot;&gt;Accessing Your Data Using A Poorly-Documented (But Well-Written) API Library&lt;/h1&gt;
&lt;h2 id=&quot;authentication&quot;&gt;Authentication&lt;/h2&gt;
&lt;p&gt;The Google Location History API runs on OAuth 2. It&apos;s finnicky because of that. OAuth 2 is finnicky. Avoid writing an OAuth 2 client from scratch if you can -- you&apos;ll regret it. Use the tools you&apos;re given by the developers who know what they&apos;re doing.&lt;/p&gt;
&lt;p&gt;It took me about four hours to figure out the basics of the API because the documentation is a bit lacking. You should still check out the docs though -- &lt;a href=&quot;https://code.google.com/p/google-api-python-client/source/browse/samples/latitude/latitude.py&quot;&gt;here&apos;s Google&apos;s only Latitude API sample&lt;/a&gt;, and &lt;a href=&quot;https://code.google.com/p/google-api-python-client/wiki/SampleApps&quot;&gt;here&apos;s some samples for other applications that might help you out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hopefully I can save you some time by explaining how the script I wrote to authenticate with the Google OAuth endpoint via the Python API works and why it works that way.&lt;/p&gt;
&lt;p&gt;Here&apos;s the code you&apos;ll need to authenticate with Google:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import httplib2
from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
&lt;p&gt;CLIENT_ID = ‘YOUR_CLIENT_ID’
CLIENT_SECRET = ‘YOUR_CLIENT_SECRET’
REQUESTED_SCOPE = ‘&lt;a href=&quot;https://www.googleapis.com/auth/latitude.all.best&quot;&gt;https://www.googleapis.com/auth/latitude.all.best&lt;/a&gt;’&lt;/p&gt;
&lt;p&gt;flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, REQUESTED_SCOPE)&lt;/p&gt;
&lt;p&gt;storage = Storage(‘credentials.dat’)
credentials = storage.get()
if credentials == None or credentials.invalid:
credentials = run(flow, storage)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code class=&quot;language-python&quot;&gt;http = httplib2.Http()
http = credentials.authorize(http)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the above example, you need to replace &lt;code&gt;CLIENT_ID&lt;/code&gt; and &lt;code&gt;CLIENT_SECRET&lt;/code&gt; with real values. Fill those in with the values found in the application you created above in the Google API Console.&lt;/p&gt;
&lt;p&gt;When you run this code, Python will open your web browser to an authorization page. If you&apos;re not logged into a Google account, you&apos;ll be asked to log in. You&apos;ll then be asked to allow access to your location history.&lt;/p&gt;
&lt;p&gt;After you grant access to your application to access your location history, you can close your browser window. The Python script will continue by saving your login credentials in &lt;code&gt;credentials.dat&lt;/code&gt; as shown in the script above -- if you want to change that, just provide a different path to &lt;code&gt;Storage()&lt;/code&gt;. Once you have &lt;code&gt;credentials.dat&lt;/code&gt; saved, you won&apos;t have to log in again until your OAuth token expires.&lt;/p&gt;
&lt;p&gt;Great! Now you have authorization to access your Location History data. How do you actually access it?&lt;/p&gt;
&lt;h2 id=&quot;accessingyourdata&quot;&gt;Accessing Your Data&lt;/h2&gt;
&lt;p&gt;This uses some of the code Google provides in their API acess examples. This code follows immediately after the authentication above and assumes you have a valid &lt;code&gt;http&lt;/code&gt; instance available from &lt;code&gt;credentials.authorize()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;latitude = build(&apos;latitude&apos;, &apos;v1&apos;, http=http)
&lt;/code&gt;&lt;p&gt;&lt;code class=&quot;language-python&quot;&gt;try:
# access Location History data via the “latitude” object; put your code here!
except AccessTokenRefreshError:
print ‘Access token refresh error. Please restart the application to reauthorize.’
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;latitude&lt;/code&gt; object indicates you want to access the Latitude API, version 1, using the &lt;code&gt;credentials.authorize()&lt;/code&gt; &lt;code&gt;http&lt;/code&gt; object to make the requests.&lt;/p&gt;
&lt;p&gt;Inside that &lt;code&gt;try&lt;/code&gt; section you&apos;ll need to use the &lt;code&gt;latitude&lt;/code&gt; object to access your data. Here&apos;s the syntax you need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;response = latitude.location().list(granularity=&apos;best&apos;).execute()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, Google assumes that you want the latest results. Specifying the keyword argument &lt;code&gt;granularity=&apos;best&apos;&lt;/code&gt; indicates you want the best available location granularity as well. You&apos;re allowed to do this because earlier, when you asked the user for permission, you told Google you wanted permission to use the best-available location data (REQUESTED_SCOPE = &apos;&lt;a href=&quot;https://www.googleapis.com/auth/latitude.all.best&quot;&gt;https://www.googleapis.com/auth/latitude.all.best&lt;/a&gt;&apos;).&lt;/p&gt;
&lt;p&gt;When you request this data, you&apos;ll get the 100 latest data points. You can access them by looping through the object &lt;code&gt;response[&apos;items&apos;]&lt;/code&gt;. If the &lt;code&gt;response&lt;/code&gt; object has no key &lt;code&gt;items&lt;/code&gt;, then the Google API returned no results.&lt;/p&gt;
&lt;p&gt;Since Latitude likes to take one point a minute, you almost certainly won&apos;t get all your data with one query. So, how do you cycle through this data?&lt;/p&gt;
&lt;h2 id=&quot;iteratingthroughyourdata&quot;&gt;Iterating Through Your Data&lt;/h2&gt;
&lt;p&gt;You can specify other arguments to the &lt;code&gt;latitude&lt;/code&gt; object inside the &lt;code&gt;location().list()&lt;/code&gt; function as well. One option you can specify is &lt;code&gt;max_time&lt;/code&gt;, which indicates that you want no objects with the milliseconds timestamp later than the one you provide in the &lt;code&gt;max_time&lt;/code&gt; argument.&lt;/p&gt;
&lt;p&gt;Since the &lt;code&gt;latitude.location().list().execute()&lt;/code&gt; function returns the 100 latest location items that match your query, you can cycle through the list by doing the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run a &lt;code&gt;latitude.location().list(granularity=&apos;best&apos;).execute()&lt;/code&gt; and save that data.&lt;/li&gt;
&lt;li&gt;Take the least-recent data point in that set of data, look for the key &lt;code&gt;timestampMs&lt;/code&gt;, and subtract 1 millisecond.&lt;/li&gt;
&lt;li&gt;Take that new lower timestamp and use it in the query as follows: &lt;code&gt;latitude.location().list(granularity=&apos;best&apos;, max_time=LOWER_TIMESTAMP_HERE).execute()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since you asked the API to return no results with a timestamp greater than the one you provided, you&apos;ll get a new page of the next-100-most-recent Latitude results. Yay!&lt;/p&gt;
&lt;p&gt;If you keep doing that, someday you&apos;ll get a &lt;code&gt;response&lt;/code&gt; back that doesn&apos;t have an &lt;code&gt;items&lt;/code&gt; key. This means you don&apos;t have any results left. You could either check for an &lt;code&gt;items&lt;/code&gt; key with &lt;code&gt;if items in response&lt;/code&gt;, or you could just &lt;code&gt;try&lt;/code&gt; to read &lt;code&gt;response[items]&lt;/code&gt; and catch the Exception with &lt;code&gt;except KeyError:&lt;/code&gt;. The second one is &quot;more Pythonic&quot; if you want to get super pedantic and PEP-8ic.&lt;/p&gt;
&lt;h1 id=&quot;workingsample&quot;&gt;Working Sample&lt;/h1&gt;
&lt;p&gt;Here&apos;s a working sample. I&apos;m using this with my application&apos;s OAuth2 ID and secret and it saves all my data as a big pile of JSON files. Hopefully this gets you up and running!&lt;/p&gt;
&lt;p&gt;{% gist 5993157 google_latitude_api_access.py %}&lt;/p&gt;
&lt;h1 id=&quot;alternativestolatitudeforgeologging&quot;&gt;Alternatives to Latitude for Geologging&lt;/h1&gt;
&lt;p&gt;If you don&apos;t want to use Google Latitude to log your GPS location constantly (which is probably a good idea, because &lt;a href=&quot;https://support.google.com/gmm/answer/3001634?p=maps_android_latitude&amp;#x26;rd=1&quot;&gt;the API is shutting down in less than a month&lt;/a&gt;), then you can log the data on your phone with an app instead. &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.flashlight.ultra.gps.logger&quot;&gt;Ultra GPS Logger&lt;/a&gt; is the best one I&apos;ve found in the Play Store, and at $5ish it&apos;s much cheaper than buying a dedicated hardware device.&lt;/p&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;In this post, I detailed how to use the Google API library for Python with the Google Latitude API to get your personal location history data out of Google Latitude and onto your computer in a versatile data format.&lt;/p&gt;
&lt;p&gt;In my next post, I&apos;ll be writing about the purpose for which I&apos;m using this location history data, and why it&apos;s so important to have accurate location data!&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item><item><title>Installing Kaleidoscope Integration (KSdiff build 89) With Kaleidoscope 2.0.0</title><link>https://kesdev.com/installing-kaleidoscope-integration-ksdiff-build-89-with-kaleidoscope-2-0-0</link><guid isPermaLink="true">https://kesdev.com/installing-kaleidoscope-integration-ksdiff-build-89-with-kaleidoscope-2-0-0</guid><description>The other day I was using the fantastic file diff application Kaleidoscope, and I wanted to integrate the diff functionality with my SVN client. Kaleidoscope provides a terminal tool called KSdiff that integrates the GUI Kaleidoscope application with other programs. However, after installing KSdiff from the Kaleidoscope website as directed, KSdiff build 111 told me that my version of Kaleidoscope …</description><pubDate>Fri, 08 Nov 2013 19:05:57 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;The other day I was using the fantastic file diff application &lt;a href=&quot;http://www.kaleidoscopeapp.com/&quot;&gt;Kaleidoscope&lt;/a&gt;, and I wanted to integrate the diff functionality with my SVN client. Kaleidoscope provides a terminal tool called KSdiff that integrates the GUI Kaleidoscope application with other programs.&lt;/p&gt;
&lt;p&gt;However, after installing KSdiff from the Kaleidoscope website as directed, KSdiff build 111 told me that my version of Kaleidoscope was too low and that I needed to upgrade my application to use Kaleidoscope&apos;s integration features. In other words, the version of KSdiff I had installed was too high for my current installation of the application.&lt;/p&gt;
&lt;p&gt;I didn&apos;t want to upgrade Kaleidoscope right away, so I Googled around for an older version of KSdiff that would work with my out-of-date Kaleidoscope.&lt;/p&gt;
&lt;p&gt;I found &lt;a href=&quot;http://www.mplewis.com/files/ksdiff-89.zip&quot;&gt;ksdiff-89.zip&lt;/a&gt; on the Kaleidoscope CDN. That file did what I was looking for!&lt;/p&gt;
&lt;p&gt;If you&apos;re having the same problem I am and want to integrate Kaleidoscope without upgrading from version 2.0.0, &lt;a href=&quot;http://www.mplewis.com/files/ksdiff-89.zip&quot;&gt;download and install ksdiff-89.zip&lt;/a&gt; and you&apos;ll have integration with Kaleidoscope in your favorite version control clients.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded></item></channel></rss>