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

 <title>Mike Kasberg's Blog</title>
 <link href="https://www.mikekasberg.com/feed.xml" rel="self" type="application/atom+xml"/>
 <link href="https://www.mikekasberg.com" rel="alternate" type="text/html"/>
 <updated>2026-01-31T20:00:00-07:00</updated>
 <id>https://www.mikekasberg.com</id>
 <author>
   <name>Mike Kasberg</name>
   <email>mike@mikekasberg.com</email>
 </author>

 
 <entry>
   <title>Dotfiles Secrets in Chezmoi, Without Password Headaches</title>
   
   <link href="https://www.mikekasberg.com/blog/2026/01/31/dotfiles-secrets-in-chezmoi.html" type="text/html" title="Dotfiles Secrets in Chezmoi, Without Password Headaches"/>
   
   <published>2026-01-31T20:00:00-07:00</published>
   <updated>2026-01-31T20:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2026/01/31/dotfiles-secrets-in-chezmoi</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2026/01/31/dotfiles-secrets-in-chezmoi.html">&lt;p&gt;I’ve been &lt;a href=&quot;/blog/2021/05/12/my-dotfiles-story.html&quot;&gt;managing my dotfiles with Chezmoi&lt;/a&gt; for nearly five years now, and I love it!
Chezmoi fundamentally changed my approach to dotfiles with templating, making it
easy to keep my dotfiles working on multiple machines and even multiple
operating systems. And Chezmoi’s integration with password managers made it
easy to keep important identity files like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id_rsa&lt;/code&gt; synced and backed up while
remaining secure. Overall I’ve been really happy with my dotfiles setup, but in
the last couple months I started noticing some pain points. Every time I synced
my dotfiles, I got prompted for my Bitwarden password. For a long time this
didn’t bother me, but I found myself getting increasingly annoyed at having
to authenticate to my password manager, and sometimes even re-authenticate with
2-factor authentication, when I knew my secrets hadn’t changed. I realized I
was actually &lt;em&gt;avoiding&lt;/em&gt; updating my dotfiles because I didn’t want to deal with
the password manager – which is exactly the opposite of what I want.  So I
finally decided to do something about it.&lt;/p&gt;

&lt;h2 id=&quot;the-password-manager&quot;&gt;The Password Manager&lt;/h2&gt;

&lt;p&gt;I’ve been &lt;a href=&quot;/blog/2018/11/20/keepass-vs-bitwarden.html&quot;&gt;using Bitwarden&lt;/a&gt; as
my personal password manager for more than seven years! Back when I started, it
was a simple, no-nonsense, open-source password manager with an intuitive
interface. I like Bitwarden a lot and I’m very happy with it overall, even
though it’s lost some of its original charm as it’s become more popular over the
years. But the CLI is terrible.&lt;/p&gt;

&lt;p&gt;The CLI doesn’t really manage any vault state for you, so the only way to keep
it unlocked is to set an env variable. There might be some reasons for designing
things to work that way, but from a UX perspective it &lt;em&gt;sucks&lt;/em&gt;. It means
something as simple as unlocking the vault to run a series of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bw get&lt;/code&gt; commands
in your terminal involves copying and pasting a key from the unlock command. The
UX is bad enough that I &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/a2464d0c6306c29a2ee39f9f49c9729d9a36ad27/dot_zshrc#L149-L152&quot;&gt;wrote my own
alias&lt;/a&gt;
(yes, that’s a link to my dotfiles 😄) to unlock the vault for the current
session in a single command. But, frustratingly, even this doesn’t work half the
time! If my OTP has expired after 30 days (coincidentally, about the frequency I
update my dotfiles), the CLI just exits with an unhelpful error message. While
I’m glad Bitwarden at least offers a CLI, the interface is laughably bad for my
usage. And the fact that I end up having to redo 2-factor OTP auth every other
time I update my dotfiles is just icing on the cake for a very bad UX.&lt;/p&gt;

&lt;p&gt;(Now that it’s 2026, I should probably just vibe-code a much more robust script
for myself to login with more graceful error handling. Maybe I will! But that’s
a topic for another blog post. &lt;em&gt;This&lt;/em&gt; post is about a better password management
UX for Chezmoi, so let’s get on with it!)&lt;/p&gt;

&lt;h2 id=&quot;passwords-and-chezmoi&quot;&gt;Passwords and Chezmoi&lt;/h2&gt;

&lt;p&gt;One of the most important and useful features of Chezmoi is dotfile templates
(using &lt;a href=&quot;https://pkg.go.dev/text/template&quot;&gt;Go text/template&lt;/a&gt;). This is what
allows you to customize dotfiles per-machine from the same source.  It also
allows you to reference secrets from your password manager using a template
function. When I started with Chezmoi, the appeal of integrating with Bitwarden
was a big draw for me. I became enchanted by the idea of completely &lt;a href=&quot;https://www.chezmoi.io/user-guide/advanced/install-your-password-manager-on-init/&quot;&gt;setting up a
new machine with a single
command&lt;/a&gt;,
and Chezmoi’s Bitwarden integration was an important building block. Chezmoi is
an excellent dotfiles manager, and with all the different tools and hooks for
customization (templates, settings, chezmoidata, hooks, etc) it’s nearly
infinitely configurable. This flexibility is both good and bad. It makes Chezmoi
a good solution to a wide variety of needs, but it also removes the simplicity
offered by an opinionated view. And definitely gives you all the tools you need
to shoot yourself in the foot.&lt;/p&gt;

&lt;p&gt;In my case, I shot myself in the foot by setting up my dotfiles to unnecessarily
require my Bitwarden password every time they’re applied, which in turn makes it
more toilsome to update them. I only recently started to realize that this
integration was making me go through the song and dance of unlocking my vault to
sync my dotfiles (including OTP), and that was adding enough toil that updating
my dotfiles became a burden. This isn’t entirely Chezmoi’s fault; if anything the
Bitwarden CLI UX is mostly to blame. But in hindsight, I think Chezmoi makes it
a little too easy to create a setup where you do need to unlock your password
vault every time you sync your dotfiles, and I &lt;em&gt;do&lt;/em&gt; think we can do better than
that. So let’s get to it.&lt;/p&gt;

&lt;p&gt;My desired UX was simple: I wanted to be able to sync my dotfiles without
unlocking my password manager. I started thinking about this problem from first
principles, with all options on the table.&lt;/p&gt;

&lt;p&gt;I considered getting rid of any secrets managed by my dotfiles. That’s not a bad
option. I really only have a small handful of secrets in my dotfiles, and it
wouldn’t be terrible to manually copy them out of my password manager on a
one-off basis. This would be a fine option, but let’s see if we can do better.&lt;/p&gt;

&lt;p&gt;I considered using a different password manager. Perhaps one of the fifteen
options Chezmoi supports is better than Bitwarden, and has a CLI with a better
UX that doesn’t constantly log me out. I only considered this option briefly.
Aside from the CLI, I’m happy with Bitwarden, and I don’t want to migrate all my
passwords to a solution that might not be that much better. This option doesn’t
really solve the root problem of my dotfiles setup prompting me for a password
every time I run it.&lt;/p&gt;

&lt;p&gt;I considered encrypting my secrets and storing them in my (public) dotfiles git
repo. This is actually a really appealing option, and was a strong contender.
Especially since Chezmoi has a guide to &lt;a href=&quot;https://www.chezmoi.io/user-guide/frequently-asked-questions/encryption/&quot;&gt;only request a passphrase the first
time chezmoi is
run&lt;/a&gt;,
and that’s basically exactly what I wanted to do. In fact, this became a source of
inspiration for the solution I finally landed on. But ultimately, I decided
&lt;em&gt;not&lt;/em&gt; to pursue this option because I don’t like the idea of making my secrets
publicly available in encrypted form. Even if the encryption is very good, it
feels like you’re asking for someone to try to break it. And if you make a
mistake configuring something, you’re toast.&lt;/p&gt;

&lt;p&gt;I considered configuring or modifying Chezmoi to avoid requesting a passphrase
every time dotfiles are updated/applied. &lt;em&gt;That’s the root problem&lt;/em&gt;, and this is
the behavior I really wanted. But as I thought about how this might work, I
realized there’s a fundamental flaw to a solution that screws with Chezmoi’s
template behavior like that. Chezmoi’s job is to ensure the target state matches
the source state when you apply your dotfiles.  If it’s skipping files, it’s not
really doing it’s job. Sure, hypothetically, you could design something where a
configuration setting makes it ignore any files with a certain function in the
template, but that’s not an elegant solution. It breaks Chezmoi’s operating
model. That’s a dirty hack that would probably be ugly to implement. But what if
you could achieve similar behavior without breaking chezmoi’s assumptions?&lt;/p&gt;

&lt;h2 id=&quot;a-better-approach-to-secrets-in-chezmoi&quot;&gt;A Better Approach to Secrets in Chezmoi&lt;/h2&gt;

&lt;p&gt;What if, instead of &lt;em&gt;skipping&lt;/em&gt; files that call Bitwarden functions in their
template, you could just replace the Bitwarden functions with a function call
that didn’t require a password? What if you could actually pre-fetch any secrets
that you need, and find a way to cache them, and provide them to Chezmoi when it
runs? That’s exactly what I ended up doing, using functionality that already
exists in Chezmoi!&lt;/p&gt;

&lt;p&gt;Chezmoi has supported a
&lt;a href=&quot;https://www.chezmoi.io/reference/special-directories/chezmoidata/&quot;&gt;.chezmoidata&lt;/a&gt;
directory for a long time.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If any .chezmoidata/ directories exist in the source state, all files within
them are interpreted as structured static data in the given formats. This data
can then be used in templates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The files can be JSON, JSONC, TOML, or YAML. Importantly, they &lt;em&gt;don’t&lt;/em&gt; actually
need to be checked in to your dotfiles repository! They just need to exist in
that source state directory when Chezmoi is applying templates. This is a
&lt;em&gt;perfect&lt;/em&gt; solution for storing secrets that change infrequently since it makes
the data available for Chezmoi without a password prompt. I had a little
hesitation at first about putting secrets in a plain text file like this, but
there’s really no reason to worry about that. Chezmoi is generating dotfiles, so
any secrets it’s using in templates will obviously be visible in plain text to
the owner of those dotfiles. It’s perfectly fine to store them in a YAML file in
the dotfiles source directory.&lt;/p&gt;

&lt;p&gt;I wrote a &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/a2464d0c6306c29a2ee39f9f49c9729d9a36ad27/bin/executable_kasm-secrets&quot;&gt;short
script&lt;/a&gt;
that fetches secrets from Bitwarden CLI and writes them to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;secrets.yml&lt;/code&gt; file
in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.chezmoidata&lt;/code&gt;. I checked the script in to my dotfiles, and also added
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;secrets.yml&lt;/code&gt; to my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; to ensure I never accidentally commit the
secrets. Now I only need to authenticate to Bitwarden once, and the secrets will
be cached where Chezmoi can use them so I don’t need to authenticate again
unless the secrets change (which is pretty infrequent). If the secrets do
change, I’ll notice (because something won’t authenticate correctly), and I’ll
just run the script again to install the latest secrets.  Once the secrets are
installed, I can access them using normal Chezmoi templates. (I “namespaced”
mine with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kasm&lt;/code&gt; because I felt like it, but that’s not really required.) The
solution feels nice and elegant because instead of working against Chezmoi,
we’re using it’s existing framework to let it do what it’s already good at –
sync the target state to match the source.&lt;/p&gt;

&lt;p&gt;I’ve been running this setup for about a week, and the improvement from not
needing to authenticate every time I update my dotfiles is real. I not only
removed a little bit of toil, I removed a friction point to making changes to my
dotfiles, and I’m using them more effectively because of it!&lt;/p&gt;

&lt;p&gt;It might seem counter-intuitive at first that having a separate command is
actually better, but after experiencing the difference I’m quite confident that
it is, at least for me. While there’s definitely an appeal to having a single
command (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chezmoi update&lt;/code&gt;) that puts your machine in exactly the correct state,
including not only dotfiles but also secrets and apps and other things, in some
ways that’s an illusion that doesn’t account for the complexity and toil of
typing in a password to perform updates, or dealing with failed authentication.&lt;/p&gt;

&lt;p&gt;If you like what I’ve built, I hope you steal it for your own dotfiles and
you’re welcome to do so! Attribution is always appreciated but not strictly
required. &lt;a href=&quot;https://github.com/mkasberg/dotfiles/&quot;&gt;My dotfiles&lt;/a&gt; are public, so
you can see my &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/a2464d0c6306c29a2ee39f9f49c9729d9a36ad27/bin/executable_kasm-secrets&quot;&gt;secrets
script&lt;/a&gt;
and
&lt;a href=&quot;https://github.com/mkasberg/dotfiles/commit/a2464d0c6306c29a2ee39f9f49c9729d9a36ad27&quot;&gt;commit&lt;/a&gt;
where I implemented everything. If you’re not using
&lt;a href=&quot;https://bitwarden.com/&quot;&gt;Bitwarden&lt;/a&gt;, it should be trivial to update the script
to call any other CLI tool to get secrets. And if you’re not already using
&lt;a href=&quot;https://www.chezmoi.io/&quot;&gt;Chezmoi&lt;/a&gt;, I think you should check it out! But a
similar approach will probably work with a wide variety of dotfiles tools.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/dotfiles-secrets-in-chezmoi.jpg"/>
 </entry>
 
 <entry>
   <title>Vibe Coding in The World&apos;s Largest Hackathon: Building GPXTrack.xyz</title>
   
   <link href="https://www.mikekasberg.com/blog/2025/06/29/vibe-coding-in-the-worlds-largest-hackathon.html" type="text/html" title="Vibe Coding in The World&apos;s Largest Hackathon: Building GPXTrack.xyz"/>
   
   <published>2025-06-29T12:00:00-06:00</published>
   <updated>2025-06-29T12:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2025/06/29/vibe-coding-in-the-worlds-largest-hackathon</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2025/06/29/vibe-coding-in-the-worlds-largest-hackathon.html">&lt;p&gt;I got pretty excited when &lt;a href=&quot;https://bolt.new&quot;&gt;Bolt.new&lt;/a&gt; announced the
&lt;a href=&quot;https://hackathon.dev&quot;&gt;World’s Largest Hackathon&lt;/a&gt;. I saw not only an
opportunity to build something cool, but also – and perhaps more importantly –
an opportunity to gain experience building something with AI tools. A
hackathon’s the perfect place to explore the capabilities of AI tools because it
let me lean in to vibe coding experience. I used the AI for many more kinds of
tasks that I normally would, and I let it take control of things much more than I
normally would. As a result, I think I was able to discover and push the
limits of AI in my own workflow much faster than I otherwise would have, so it
was an excellent learning experience!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://bolt.new&quot;&gt;Bolt.new&lt;/a&gt; is &lt;a href=&quot;https://stackblitz.com/&quot;&gt;StackBlitz’s&lt;/a&gt; new
AI-first coding platform that lets you use AI chat and codegen to build web
applications without leaving the browser. And while I’m not completely sold on
the in-browser IDE, it’s actually grown on me a little over the course of the
hackathon. I was happy to give the platform a try since Bolt.new was offering
10M tokens to anyone who wanted to participate in the World’s Largest Hackathon.
Fundamentally, the tool is just an agent that’s using Claude to generate and
edit code, so I think many of the approaches and techniques I’ve picked up are
equally applicable to Bolt.new and to other agents, like &lt;a href=&quot;https://www.anthropic.com/claude-code&quot;&gt;Claude
Code&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;inspiration&quot;&gt;Inspiration&lt;/h2&gt;

&lt;div class=&quot;not-prose flex justify-center&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Recording the 3D printing show today &lt;a href=&quot;https://t.co/op9qzyvyn6&quot;&gt;pic.twitter.com/op9qzyvyn6&lt;/a&gt;&lt;/p&gt;&amp;mdash; Wes Bos (@wesbos) &lt;a href=&quot;https://twitter.com/wesbos/status/1912171185875321145?ref_src=twsrc%5Etfw&quot;&gt;April 15, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt; 
&lt;/div&gt;

&lt;p&gt;Several months ago, &lt;a href=&quot;https://wesbos.com/&quot;&gt;Wes Bos&lt;/a&gt; was getting into 3D printing
and sharing his journey &lt;a href=&quot;https://x.com/wesbos/status/1912171185875321145&quot;&gt;on X&lt;/a&gt;.
I’ve been interested in 3D printing for &lt;a href=&quot;/blog/2023/01/11/3-months-of-3d-printing.html&quot;&gt;a while&lt;/a&gt;, so I was eager to follow along. As Wes
got deeper into 3D printing, he was searching for ways to integrate &lt;a href=&quot;https://x.com/wesbos/status/1896582757322518891&quot;&gt;programming
with 3D printing&lt;/a&gt; and found
&lt;a href=&quot;https://manifoldcad.org/&quot;&gt;ManifoldCAD&lt;/a&gt;, a tool to programmatically generate 3D
models using javascript. I think Wes was attracted to ManifoldCAD for many of
the same reasons that I like &lt;a href=&quot;https://openscad.org/&quot;&gt;OpenSCAD&lt;/a&gt;. I’ve been pretty
&lt;a href=&quot;/blog/2023/03/22/3d-printing-with-openscad.html&quot;&gt;happy using OpenSCAD&lt;/a&gt; for
a long time, but I was a little surprised at all the things Wes was able to do
when I saw &lt;a href=&quot;https://bracket.engineer&quot;&gt;Bracket.Engineer&lt;/a&gt; for the first time!  Wes
took ManifoldCAD to the next level by using it as a library, in combination with
Three.js, to deploy a website that lets you customize a parametric model in your
web browser as a GUI, with the complete capabilities of javascript.  I thought
this was awesome, and the ability to easily integrate code from another language
was a capability that I’d been missing in OpenSCAD!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/vibe-coding-in-the-worlds-largest-hackathon/openscad.png&quot; alt=&quot;Mini GPX Figurines in OpenSCAD&quot; /&gt;&lt;/p&gt;

&lt;p&gt;More than a year ago, I worked on a project to generate a 3D printable model
from a GPX track using OpenSCAD. Because I needed a more powerful programming
language than OpenSCAD itself to import data from a GPX file, I wrote some code
in Ruby that would parse the GPX file and render OpenSCAD code using a template.
The approach worked, and is still available at
&lt;a href=&quot;https://github.com/mkasberg/3d-gpx-figurines&quot;&gt;github.com/mkasberg/3d-gpx-figurines&lt;/a&gt;,
but it was an ugly approach and I wished there was a cleaner way to do it. I
realized when I saw &lt;a href=&quot;https://bracket.engineer&quot;&gt;Bracket.Engineer&lt;/a&gt; that the tight
integration Wes had achieved between JS code and a ManifoldCAD model would
provide a much cleaner way to generate a 3D model from a GPX activity track, and
the World’s Largest Hackathon seemed like the perfect place to experiment with
the approach. It was a perfect combination of events!&lt;/p&gt;

&lt;h2 id=&quot;the-ai-prompt-learning-curve&quot;&gt;The AI Prompt Learning Curve&lt;/h2&gt;

&lt;p&gt;Without initially knowing what Bolt.new was capable of, or even what kind of
prompts worked best, I took a naive approach and dove right in. I forked the
&lt;a href=&quot;https://github.com/wesbos/bracket.engineer&quot;&gt;Bracket.Engineer repo&lt;/a&gt; to use as a
starting point, and started thinking about all the work to be done. First and
foremost, I needed to get my OpenSCAD GPX path model to work in the ManifoldCAD
library, or this whole project would hit a dead-end very quickly! I tried to
just zero-shot it, with relatively little context, by pasting the entire
OpenSCAD file into the prompt and adding a sentence or two asking Bolt to port
the code to ManifoldCAD. And when that was done, I wrote another short prompt
asking it to update the code to reference my new GPX model instead of the
bracket model from the original repo. To my surprise and delight, it did a
reasonably good job! It didn’t work perfectly on the first attempt, but it got
close enough that I knew this was going to be possible!&lt;/p&gt;

&lt;p&gt;Although my first prompt went surprisingly well, I quickly got a taste of the
many ways AI can fail as I worked with the AI in frustration to make half a
dozen different flavors of “fix it” commits. Most of them only partially worked,
and some didn’t work at all. There were a few fundamental problems with the
ported 3D model (rendering in broken ways) that the AI seemed unable to fix, so
I had to dig into myself and do some real (human) debugging work. I soon
realized that the AI had repeatedly made the same error with order of operations
as it was porting code from OpenSCAD to JS. OpenSCAD is a functional-inspired
language, and commonly uses code like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translate(rotate(cube))&lt;/code&gt;. The LLM had
ported many of these operations to JS like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cube.translate().rotate()&lt;/code&gt;, which is
&lt;em&gt;not the same&lt;/em&gt;. (Order of operations is important when you’re translating things
and rotating them about the origin.) I ended up fixing several of these errors
manually, which was actually reasonably quick once I recognized the failure
pattern.&lt;/p&gt;

&lt;p&gt;I ended my first day of “vibe coding” optimistic about the possibilities, but
also frustrated about the reality that the AI had written lots of bugs and left
other tasks incomplete, and the process to fix the bugs and get things working
was incredibly slow so far. Before starting the next day, I had time to think
about the approach I’d taken on the first day, what had worked, and what had
not. I noticed that Bolt was actually creating a new git commit every time I
asked it to change something (which led to a lot of “fix something” commits). I
also explored the Bolt UI more deeply and noticed that it supported working on
different branches. For my second day of vibe coding, I decided to try doing all
my work on a branch and opening a pull request with the changes, which would
give me a better way to review code &lt;em&gt;before&lt;/em&gt; it hit my main branch.&lt;/p&gt;

&lt;h2 id=&quot;ai-troubleshooting-techniques&quot;&gt;AI Troubleshooting Techniques&lt;/h2&gt;

&lt;p&gt;OpenSCAD supports 3D text, and ManifoldCAD does not, but I knew that 3D text
was an important part of my project and I wanted to find a way to implement it.
I started a new branch in Bolt. I had no idea how we were going to make this
work, so I started asking the LLM questions. I think this was my first use of
the “Discussion” mode in Bolt. I asked it if there were any common techniques
for using 3D text in ManifoldCAD. (There weren’t, as ManifoldCAD doesn’t have
that feature.) So I asked it, at a higher level, what the most common approach
was to create 3D text. It outlined a process at a high level that exports
contours from a 2D font and extrudes those contours into a 3D model. Then I
asked it to make a plan that would allow us to use extruded font contours in our
own project. It made a fairly detailed plan to use opentype.js to extract
contours from a web font, send the contours to our model in the right format,
and extrude them as 3D text. It was working well above my knowledge of fonts,
contours, and 3D rendering here. I could have maybe figured all this out on my
own, but it would have been &lt;em&gt;days&lt;/em&gt; of reading arcane things about how fonts
work. I learned a lot quickly just by watching the LLM think, and I asked it to
implement the plan.&lt;/p&gt;

&lt;p&gt;It wrote a lot of code, and it seemed mostly correct to me at first glance.
There were a couple errors, and the AI was able to work through them and fix
them. But then the errors were gone, and we were stuck. Lots of code that I
didn’t really understand, and no errors, but no 3D text. I was stumped, and I
took a break to think. I didn’t want to debug all this font code myself, but the
AI was unable to make progress. Asking it to fix the problem, without a clear
indication of what was wrong and no error message, made it start hallucinating
issues and breaking things further as it tried to fix the wrong thing. What
would &lt;em&gt;I&lt;/em&gt; do, if I were the AI in this situation? &lt;strong&gt;I’d want logs.&lt;/strong&gt; Back to
basics. Print statement debugging. And that’s exactly what I asked the AI to do.
I told it that we have a problem because there are no errors, but the text isn’t
showing up, and we need to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log&lt;/code&gt; to validate our assumptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I was blown away by the results.&lt;/strong&gt; It added &lt;em&gt;beautiful&lt;/em&gt; log messages in more
than a dozen different places throughout the complicated code it had just
written, and logged exactly the right thing in a nice readable form at every
location. It probably would have taken me an hour to do that myself, and the
logs wouldn’t look as nice. This was great! I started up the app, got dozens of
lines of logs, and was just starting to get sad about reading through them all
when it occurred to me to just try pasting all the log output back to the LLM.
So I did. I pasted more than 50 lines of the logs it had just written itself and
I asked it to interpret the results. &lt;em&gt;And I was blown away again.&lt;/em&gt; It drew
correct conclusions from all of it’s log messages, showed its reasoning in
bullet form, and stated quite confidently that we had correct contours all the
way through our pipeline but weren’t getting 3D shapes and it was probably
because our “winding order” was backwards. Okay, I told it to fix the winding
order. It &lt;a href=&quot;https://github.com/mkasberg/gpxtrack.xyz/commit/5683668ab7f0e6ee10aaa0ff036d118a05df34dc&quot;&gt;added a
.reverse()&lt;/a&gt;
in the right spot, and I had mostly-working 3D text in ManifoldCAD! I was
ecstatic! All this work had been done on a branch, so I asked it to remove the
logs it had added and clean up the code for a PR. I didn’t closely review all of
the new font-rendering code, but &lt;em&gt;I knew it worked before I merged it&lt;/em&gt; because I
had the opportunity to test it, and that was good enough for me!&lt;/p&gt;

&lt;h2 id=&quot;moving-faster&quot;&gt;Moving Faster&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/vibe-coding-in-the-worlds-largest-hackathon/bolt.jpg&quot; alt=&quot;Building gpxtrack.xyz in Bolt.new&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As I used the LLM more, I became much more confident with it. I was developing
a sense of its abilities. I knew what it did well, and I knew what it would
struggle with. I started crafting better prompts to guide it around the parts I
knew would be tricky. My prompts grew more precise, and the generated code
became more precise as a result. One trick I learned was that it &lt;em&gt;always&lt;/em&gt; helped
to discuss the plan before generating any code. With this technique, I could be
a little lazy and provide a mediocre prompt with a sentence or two about what we
needed to do, and the LLM would generate a short paragraph and several bullets
about what needed to happen. If the plan was bad, I had a chance to revise it
before we started going down the wrong path in code. And if the plan was good,
the LLM always performed better edits using its own detailed plan than it did
using my short and vague description. Using this technique, I got to the point
where on several occasions the LLM generated a PR on the first try that I was
able to merge with no edits! And it felt awesome, because I knew I was moving
&lt;em&gt;fast&lt;/em&gt;. When things went smoothly, I could generate, review, and merge a PR in
minutes that would probably have taken an hour or more without AI help.&lt;/p&gt;

&lt;p&gt;The confidence I gained solving the font problem and the other techniques I’d
acquired along the way made me ambitious enough to try tackling bigger problems.
I had the LLM &lt;a href=&quot;https://github.com/mkasberg/gpxtrack.xyz/pull/13&quot;&gt;move our model generation to a web
worker&lt;/a&gt; to improve the
performance and got great results! If I weren’t using an AI code assistant, I
might not even try that refactoring – I’d have needed to spend a lot of time
learning new things and fixing problems that were difficult to debug, and it
probably wouldn’t seem worthwhile to me since the performance without this
optimization wasn’t unbearable. But with AI help, I felt like I’d be able to try
something quickly to see if it would work, even using technologies I didn’t have
a lot of experience with. (I initially tried a &lt;a href=&quot;https://github.com/mkasberg/gpxtrack.xyz/pull/12&quot;&gt;different
approach&lt;/a&gt;, and discarded it
pretty quickly when it wasn’t working well. AI enabled me to experiment very
quickly, and pivot very quickly when it wasn’t working.)&lt;/p&gt;

&lt;h2 id=&quot;looking-ahead&quot;&gt;Looking Ahead&lt;/h2&gt;

&lt;p&gt;I’m really happy with &lt;a href=&quot;https://gpxtrack.xyz&quot;&gt;gpxtrack.xyz&lt;/a&gt;! (And if you’re a
runner, hiker, or cyclist, you should go check it out!) Not only did I learn a
ton about building web applications with AI, but I built something really cool
along the way. It’s open source on
&lt;a href=&quot;https://github.com/mkasberg/gpxtrack.xyz&quot;&gt;GitHub&lt;/a&gt;, so you can check out the
code if you’re interested!&lt;/p&gt;

&lt;p&gt;At the end of this experience, what I think I’m most excited about is seeing AI
lower the cost of trying things. Adam Wathan has already shared &lt;a href=&quot;https://x.com/adamwathan/status/1928538629074190478&quot;&gt;an
example&lt;/a&gt; of a project he
and his team were able to finish that they might not have even tried without AI
help. The future is exciting, but I think LLM technology is still so new that
most engineers using it have no idea what it’s capable of. I think the fastest
way for us to get there is to build new things and try new things, and the
World’s Largest Hackathon was a great way to do that. Some of my most important
takeaways are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Don’t let the AI wander too much on its own. It’ll start hallucinating
things, duplicating functionality, and doing other things that are terrible
for the long-term health of a codebase.&lt;/li&gt;
  &lt;li&gt;To prevent that, review the generated code regularly – either right when the
AI generates it, or with a PR, or with some other process.&lt;/li&gt;
  &lt;li&gt;Discuss problems and make a detailed plan before writing code. Let the AI
write a detailed prompt for itself. The AI is better at writing prompts for
itself than you are, and this gives you an opportunity to review the plan
before it’s done in code.&lt;/li&gt;
  &lt;li&gt;AIs are great at troubleshooting &lt;em&gt;if they have the tools and context they
need&lt;/em&gt;. Let them add logs, like a human would.&lt;/li&gt;
  &lt;li&gt;AIs shift costs around to make things like experimentation much cheaper (in
terms of developer time) than they otherwise would have been.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI technology continues to evolve so rapidly that it can feel hard to keep up,
but I’m excited to see what the future holds!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/vibe-coding-in-the-worlds-largest-hackathon.jpg"/>
 </entry>
 
 <entry>
   <title>Optimizing Zsh Init with ZProf (and Switching to Mise)</title>
   
   <link href="https://www.mikekasberg.com/blog/2025/05/29/optimizing-zsh-init-with-zprof.html" type="text/html" title="Optimizing Zsh Init with ZProf (and Switching to Mise)"/>
   
   <published>2025-05-29T21:00:00-06:00</published>
   <updated>2025-05-29T21:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2025/05/29/optimizing-zsh-init-with-zprof</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2025/05/29/optimizing-zsh-init-with-zprof.html">&lt;p&gt;I’ve been noticing for a while that on my personal laptop, new terminal
tabs/windows were very slow to start (more than a second). It bugged me a lot
since I knew something seemed wrong, but I’d been putting off digging into the
problem for a long time.  A few days ago, I finally took some time to
investigate! I’m glad I did, because I found some low hanging fruit that
ended up saving more than two seconds on zsh (Oh-My-Zsh) init!&lt;/p&gt;

&lt;h2 id=&quot;zprof&quot;&gt;ZProf&lt;/h2&gt;

&lt;p&gt;I knew zsh was slow to start up, but I didn’t know why. Fortunately, there’s a
tool that can easily profile this! Zsh comes with a tool called
&lt;a href=&quot;https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fzprof-Module&quot;&gt;zprof&lt;/a&gt;
that can profile zsh startup. To use it, you just add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zmodload zsh/zprof&lt;/code&gt; as
the first line in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt; (to cause the following lines to be profiled),
and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zprof&lt;/code&gt; as the last line in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt; (to print the profile results).
With those lines in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;, opening a new terminal will print profiling
information! Here’s the important bits of what my first profile showed.&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1        1693.34  1693.34   55.59%   1693.34  1693.34   55.59%  load-nvmrc
 2)    2         587.73   293.87   19.29%    286.15   143.07    9.39%  nvm
 3)    1         264.29   264.29    8.68%    264.29   264.29    8.68%  compdump
 4)    1         238.42   238.42    7.83%    207.30   207.30    6.80%  nvm_ensure_version_installed
 5)    2         692.22   346.11   22.72%    182.89    91.44    6.00%  compinit
 6)  845         176.20     0.21    5.78%    176.20     0.21    5.78%  compdef
 7)    4          70.58    17.65    2.32%     70.58    17.65    2.32%  compaudit
 ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s an obvious optimization here. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load-nvmrc&lt;/code&gt; is responsible for more than
half of my zsh init time, taking more than 1.5 seconds! There &lt;em&gt;might&lt;/em&gt; have been some
kind of misconfiguration or reason for the poor nvm performance. (I see 2 calls to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm&lt;/code&gt;, which seems wrong.) But I actually
didn’t bother to troubleshoot nvm any further. I’d been interested in trying
&lt;a href=&quot;https://mise.jdx.dev/&quot;&gt;mise-en-place&lt;/a&gt; for a while, and this seemed like a great
excuse to give it a go (since it can replace nvm). I &lt;a href=&quot;https://github.com/mkasberg/dotfiles/commit/693111880e80b6a4a3157f30426a53444b84d06a&quot;&gt;ripped out the nvm
Oh-My-Zsh
plugin&lt;/a&gt;
and saw immediate improvement in my init time!&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    2         499.94   249.97   40.72%    499.94   249.97   40.72%  compdump
 2) 1674         327.03     0.20   26.63%    327.03     0.20   26.63%  compdef
 3)    2        1193.22   596.61   97.18%    300.31   150.15   24.46%  compinit
 4)    4          67.30    16.83    5.48%     67.30    16.83    5.48%  compaudit
 5)    1          19.11    19.11    1.56%     19.11    19.11    1.56%  is_update_available
 ...

-----------------------------------------------------------------------------------

 3)    2        1193.22   596.61   97.18%    300.31   150.15   24.46%  compinit
       2/4        67.30    33.65    5.48%      0.78     0.39             compaudit [4]
    1662/1674    325.67     0.20   26.52%    325.67     0.20             compdef [2]
       2/2       499.94   249.97   40.72%    499.94   249.97             compdump [1]

-----------------------------------------------------------------------------------

       2/2       499.94   249.97   40.72%    499.94   249.97             compinit [3]
 1)    2         499.94   249.97   40.72%    499.94   249.97   40.72%  compdump

-----------------------------------------------------------------------------------

    1662/1674    325.67     0.20   26.52%    325.67     0.20             compinit [3]
 2) 1674         327.03     0.20   26.63%    327.03     0.20   26.63%  compdef

-----------------------------------------------------------------------------------

       2/4        67.30    33.65    5.48%      0.78     0.39             compinit [3]
       2/4        66.52    33.26    5.42%     66.52    33.26             compaudit [4]
 4)    4          67.30    16.83    5.48%     67.30    16.83    5.48%  compaudit
       2/4        66.52    33.26    5.42%     66.52    33.26             compaudit [4]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But something still seemed off. Although I’d saved more than 1.5 seconds of
init time by ripping out nvm, nearly 0.5 seconds still felt abnormally slow to
initialize a shell. I didn’t know what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compdump&lt;/code&gt; was, but I decided to find
out! LLMs are &lt;em&gt;excellent&lt;/em&gt; for helping to debug things like this. I’ve found that
they particularly excel at providing detailed explanations of problems from
relatively short error or trace output – particularly for common problems in open
source tooling that are well-documented. I pasted the above output into Gemini
2.5 Pro and asked it to interpret the zprof output and provide recommendations
to improve my init time.&lt;/p&gt;

&lt;p&gt;Gemini noted (correctly) that there were &lt;em&gt;two&lt;/em&gt; calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compinit&lt;/code&gt;, and that this
function should normally only be called once during zsh init. It also let me
know that Oh-My-Zsh calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compinit&lt;/code&gt; for me when sourcing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ZSH/oh-my-zsh.sh&lt;/code&gt;, and
recommended that I make sure I’m not calling compinit elsewhere in my
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;. As it turns out, &lt;em&gt;I was calling compinit elsewhere in my
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;&lt;/em&gt;. A long time ago, I’d pasted a few lines that added some completions to my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fpath&lt;/code&gt;
and called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compinit&lt;/code&gt;. I could avoid calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compinit&lt;/code&gt; twice by just modifying
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fpath&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; sourcing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oh-my-zsh.sh&lt;/code&gt;! I &lt;a href=&quot;https://github.com/mkasberg/dotfiles/commit/766601070434341903e977f849752535bd174362&quot;&gt;edited my ~/.zshrc to do
that&lt;/a&gt;, and saved
another 0.25 seconds by removing the duplicate call!&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1         250.01   250.01   42.55%    250.01   250.01   42.55%  compdump
 2)  843         146.91     0.17   25.00%    146.91     0.17   25.00%  compdef
 3)    1         552.65   552.65   94.05%    137.29   137.29   23.36%  compinit
 ...

-----------------------------------------------------------------------------------

 3)    1         552.65   552.65   94.05%    137.29   137.29   23.36%  compinit
       1/2        19.79    19.79    3.37%      0.28     0.28             compaudit [5]
     831/843     145.57     0.18   24.77%    145.57     0.18             compdef [2]
       1/1       250.01   250.01   42.55%    250.01   250.01             compdump [1]

-----------------------------------------------------------------------------------

       1/1       250.01   250.01   42.55%    250.01   250.01             compinit [3]
 1)    1         250.01   250.01   42.55%    250.01   250.01   42.55%  compdump

-----------------------------------------------------------------------------------

     831/843     145.57     0.18   24.77%    145.57     0.18             compinit [3]
 2)  843         146.91     0.17   25.00%    146.91     0.17   25.00%  compdef
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is progress! I’d already saved 1.75 seconds of init time, but I was hungry
to keep going since all the fixes so far had been easy and obvious (in
hindsight). Still in the Gemini session, I asked the LLM to analyze my new
profile and explain what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compdump&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compinit&lt;/code&gt; do. Gemini explained that
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compdump&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compinit&lt;/code&gt; are used to set up completions, but it also noted that
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compdump&lt;/code&gt; is used to build a cache file, which &lt;em&gt;shouldn’t be slow every time&lt;/em&gt;
(even though it was for me). It suggested to try removing the cache files at
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zcompdump*&lt;/code&gt; to see if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compdump&lt;/code&gt; is then faster after rebuilding the cache
once.&lt;/p&gt;

&lt;p&gt;Before removing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zcompdump*&lt;/code&gt;, I looked at the files I was about to remove, and
noticed the reason why zsh was trying to rebuild the compdump cache each time!&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ls -l .zcompdump*                                
...
-r--r--r-- 1 mkasberg mkasberg 122128 Dec 28  2022 .zcompdump-mkasberg-latitude-e7450-5.9.zwc
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Apparently, something I’d done in the distant past (probably due to a laptop
migration) had triggered the creation of this compdump file that was readonly,
and the existence of this readonly compdump file caused compdump to silently
fail, or at least to retry on every shell init rather than only when something
had changed. I deleted all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zcompdump*&lt;/code&gt; files, including the readonly one
(since they’re just cache files that can be easily regenerated anyway), and
after that the zsh init process generated a single cache file on the first run
and then stopped regenerating them! This fix shaved an additional 200ms off
my zsh init time, and we’re in very good territory now! &lt;strong&gt;In total, I shaved
nearly 2 seconds off the startup time for opening a new terminal window or
tab!!!&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1          47.03    47.03   56.59%     25.72    25.72   30.94%  compinit
 2)    2          21.31    10.66   25.65%     21.31    10.66   25.65%  compaudit
 3)    1          20.19    20.19   24.30%     20.19    20.19   24.30%  is_update_available
 4)    1          27.75    27.75   33.39%      7.56     7.56    9.10%  (anon) [/home/mkasberg/.oh-my-zsh/tools/check_for_upgrade.sh:126]
 5)    1           2.05     2.05    2.47%      2.05     2.05    2.47%  detect-clipboard
 6)    1           1.56     1.56    1.88%      1.56     1.56    1.88%  colors
 7)   12           1.47     0.12    1.76%      1.47     0.12    1.76%  compdef
 8)    3           1.13     0.38    1.36%      1.13     0.38    1.36%  is-at-least
 9)    2           0.86     0.43    1.04%      0.86     0.43    1.04%  add-zsh-hook
10)    1           0.78     0.78    0.94%      0.78     0.78    0.94%  regexp-replace
11)   10           0.29     0.03    0.35%      0.29     0.03    0.35%  is_plugin
12)    2           0.09     0.04    0.11%      0.09     0.04    0.11%  is_theme
13)    2           0.06     0.03    0.07%      0.06     0.03    0.07%  env_default
14)    1           0.02     0.02    0.03%      0.02     0.02    0.03%  bashcompinit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;test-driving-mise&quot;&gt;Test Driving Mise&lt;/h2&gt;

&lt;p&gt;With NVM ripped out, I still needed to setup Mise to replace it. I opted to
install Mise with apt on Ubuntu since that would provide an easy mechanism to
keep Mise itself up to date. And with Mise installed, setting up a default
global Node version was a piece of cake! I used the &lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh/tree/95ef2516697aa764d1d4bb93ad3490584cc118ec/plugins/mise&quot;&gt;Oh-My-Zsh Mise
plugin&lt;/a&gt;
to init Mise on shell startup, and it’s only adding about 60ms to startup time
(compared to over 1500ms for NVM)!&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;num  calls                time                       self            name
-----------------------------------------------------------------------------------
 2)    1          58.35    58.35   18.07%     58.35    58.35   18.07%  _mise_hook
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So far, I’m very happy with Mise. It’s going to replace not only nvm for me, but
also rbenv and probably a couple other tools. It’s fast, it has a nice
interface, and it’s easy to configure. I really like the way it stores its
user-level configuration at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.config/mise/config.toml&lt;/code&gt;, and I’ve &lt;a href=&quot;https://github.com/mkasberg/dotfiles/commit/cee3d99b02ecccc190cc94f9a6153858af8f22f5&quot;&gt;set mine
up&lt;/a&gt;
to respect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ruby-version&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.nvmrc&lt;/code&gt; files in my projects.&lt;/p&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;

&lt;p&gt;So, what did I learn along the way?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ZProf is a great tool to profile slow &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt; init time.&lt;/li&gt;
  &lt;li&gt;When adding Oh-My-Zsh plugins or making other changes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;, consider
running a profile to see if the changes made the init process significantly
slower.&lt;/li&gt;
  &lt;li&gt;Mise is a great tool, and can replace multiple other tools with something
faster!&lt;/li&gt;
&lt;/ul&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/optimizing-zsh-init-with-zprof.jpg"/>
 </entry>
 
 <entry>
   <title>How to Dual-Boot Ubuntu 25.04+ and Windows 11 with Encryption</title>
   
   <link href="https://www.mikekasberg.com/blog/2025/05/19/dual-boot-ubuntu-25-04-and-windows-with-encryption.html" type="text/html" title="How to Dual-Boot Ubuntu 25.04+ and Windows 11 with Encryption"/>
   
   <published>2025-05-19T20:00:00-06:00</published>
   <updated>2025-05-19T20:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2025/05/19/dual-boot-ubuntu-25-04-and-windows-with-encryption</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2025/05/19/dual-boot-ubuntu-25-04-and-windows-with-encryption.html">&lt;p&gt;I’ve been writing about how to dual-boot Ubuntu and Windows with encryption for
a long time. I first &lt;a href=&quot;/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html&quot;&gt;wrote about it back in 2020&lt;/a&gt;, where I described a four-part
process with 19 total steps to get dual-boot working with encryption. The
process was very complicated, involving manual setup of LUKS. In Ubuntu
24.04, the process &lt;a href=&quot;/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html&quot;&gt;got significantly easier&lt;/a&gt; due to a quirk
in the installer that allowed you to select both the dual-boot and the LUKS
encryption options in the new Ubuntu installer GUI. &lt;strong&gt;Now, in Ubuntu 25.04+, the
GUI installer has full support for installing Ubuntu alongside windows with
encryption!&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;Compatibility&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;This guide is compatible with &lt;b&gt;Windows 10&lt;/b&gt; or &lt;b&gt;Windows 11&lt;/b&gt;, and the
process is nearly identical with either version.&lt;/p&gt;
&lt;p&gt;This guide was written for &lt;b&gt;Ubuntu 25.04+&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;For Ubuntu 23.04 - 24.10, see &lt;a href=&quot;/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu (24.04 - 24.10) and Windows with Encryption&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For Ubuntu versions earlier than 23.04, see &lt;a href=&quot;/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu (16.04 - 22.10) and Windows with
Encryption&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;instructions-tldr&quot;&gt;Instructions (Tl;dr)&lt;/h2&gt;

&lt;p&gt;Installing Ubuntu 25.04+ with dual-boot encryption is very easy because the
Ubuntu GUI installer now has built-in support for doing so!&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Windows should already be installed, and BitLocker should be disabled.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Run the Ubuntu installer. Go through the initial installer screens to choose
your language and set up internet access. Choose an interactive install process,
and select the software you want to install as you continue through the
installer wizard.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When you get to the “How do you want to install Ubuntu?” screen, choose to
“Install Ubuntu alongside Windows Boot Manager”.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption-25-04/how-do-you-want-to-install-ubuntu.png&quot; alt=&quot;&amp;quot;How do you want to install Ubuntu?&amp;quot; screenshot&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When choosing “Encryption and file system”, choose to “Encrypt with a
passphrase” (using LUKS and LVM).&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption-25-04/encryption-and-file-system.png&quot; alt=&quot;&amp;quot;Encryption and file system&amp;quot; screenshot&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Finish the installer, completing the steps to choose a password and setup
your user account. Here’s what my summary screen looks like at the end.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption-25-04/ready-to-install.png&quot; alt=&quot;&amp;quot;Ready to install&amp;quot; screenshot&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;After Ubuntu finishes installing, you have an encrypted Ubuntu partition.
Boot into Windows and enable BitLocker to encrypt the Windows partition.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Congratulations, you have a dual-boot system where Ubuntu is
encrypted with LVM (LUKS) and Windows is encrypted with BitLocker. It’s awesome
that in Ubuntu 25.04+, the GUI installer includes the option to set up dual boot
and simultaneously encrypt the Ubuntu partition with LUKS – this is much less
tedious than it used to be, and I’m grateful for the work of everyone who
contributed to the new Ubuntu installer!&lt;/p&gt;

&lt;h2 id=&quot;important-notes&quot;&gt;Important Notes&lt;/h2&gt;

&lt;p&gt;The process is really straight-forward since the Ubuntu installer does all the
hard parts. Still, there are a few places you might get stuck along the way.
Hopefully these notes help.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;As with any new OS installation, you should back up any important data before
proceeding. &lt;strong&gt;These instructions will modify partitions on your hard disk and
could result in data loss.&lt;/strong&gt; Proceed at your own risk; I’m not responsible for
any damage or data loss.&lt;/li&gt;
  &lt;li&gt;Your BIOS must be configured to boot with UEFI.&lt;/li&gt;
  &lt;li&gt;Windows BitLocker should be turned off when Ubuntu is installed. The primary
reason for this is the Ubuntu installer can’t shrink an encrypted Windows
partition.&lt;/li&gt;
  &lt;li&gt;When you enable BitLocker, make sure you save your BitLocker recovery key
before rebooting, and consider selecting the checkbox to test BitLocker when
setting it up. There’s a chance your boot sequence will prevent BitLocker from
automatically unlocking the drive and you’ll need the recovery key.&lt;/li&gt;
  &lt;li&gt;If BitLocker repeatedly requires the recovery key after rebooting (or fails to
the system test before starting encryption), try booting directly into Windows
from your UEFI BIOS boot menu (e.g. on a Dell, hit F12 during boot to bring up
the boot menu) rather than using Ubuntu’s Grub bootloader when booting
Windows. Inserting Grub into the boot sequence can mess with BitLocker.&lt;/li&gt;
  &lt;li&gt;If you’re using your BIOS (rather than Grub) to boot Windows, you should also
be able to adjust the boot order in your BIOS if you prefer to boot Windows by
default.&lt;/li&gt;
  &lt;li&gt;Likewise, if you expect Grub to be the default and it is not, you should be
able to configure that in your UEFI BIOS settings.&lt;/li&gt;
  &lt;li&gt;If you want to access the Windows BitLocker drive from Linux, this should be
possible with the recovery key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;additional-details&quot;&gt;Additional Details&lt;/h2&gt;

&lt;p&gt;If you’re comfortable with all the above, you might not need any additional
information. But I think the details below will help some people who either need
additional help with some steps, or are curious about some of the more advanced
details.&lt;/p&gt;

&lt;h3 id=&quot;bios-setup&quot;&gt;BIOS Setup&lt;/h3&gt;

&lt;p&gt;Before starting, ensure your computer is running the latest BIOS available. This
is important because an out-of-date BIOS can have bugs, and those bugs sometimes
affect things like UEFI, non-Windows operating systems, or other components
we’ll be touching.&lt;/p&gt;

&lt;p&gt;You should also ensure your BIOS &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boot List Option&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UEFI&lt;/code&gt;. While
you’re looking at BIOS settings, it’s worth noting that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TPM Security&lt;/code&gt; is
required for BitLocker in Windows.&lt;/p&gt;

&lt;h3 id=&quot;installing-windows&quot;&gt;Installing Windows&lt;/h3&gt;

&lt;p&gt;If Windows is already installed, just do the steps outlined above. The Ubuntu
installer is capable of shrinking the Windows partition, and I’d recommend you
just let it do so.&lt;/p&gt;

&lt;p&gt;If Windows isn’t installed, you should install Windows first. Leaving extra
space on the hard drive for Linux would be fine, but for simplicity I think it’s
easiest to just do a normal Windows install followed by the steps above. The
Windows partition will shrink quickly if it’s mostly empty. If you need a
Windows USB stick, the easiest way to make one is to use the &lt;a href=&quot;https://www.microsoft.com/software-download/&quot;&gt;Windows Media
Creation Tool&lt;/a&gt; from a computer
that’s already running Windows.&lt;/p&gt;

&lt;h3 id=&quot;ubuntu-usb-stick&quot;&gt;Ubuntu USB Stick&lt;/h3&gt;

&lt;p&gt;The easiest way to make a bootable Ubuntu USB stick is to &lt;a href=&quot;https://ubuntu.com/download/desktop&quot;&gt;download the
ISO&lt;/a&gt; and use the Startup Disk Creator on a
computer that’s already running Ubuntu. If you don’t already have a computer
running Ubuntu, you can use &lt;a href=&quot;https://etcher.balena.io/&quot;&gt;balenaEtcher&lt;/a&gt; to flash
the image to the USB stick.&lt;/p&gt;

&lt;h3 id=&quot;final-partition-scheme&quot;&gt;Final Partition Scheme&lt;/h3&gt;

&lt;p&gt;As a reference, here’s the final state of my hard drive after allowing the
installer to shrink my Windows partition and installing Ubuntu with LVM and
encryption. Note that your partition sizes might be different than mine based on
the size of your disk and the way you split it up, but the number of partitions
and their types will probably be the same. In the scheme below, partition (3) is
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:&lt;/code&gt; in Windows. Partitions (2) and (4) are used by Windows. Partition (5) is
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt; for Ubuntu and partition (6) is the encrypted LVM partition for Ubuntu.&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo sgdisk --print /dev/sda
Disk /dev/sda: 976773168 sectors, 465.8 GiB
 
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         2099199   1024.0 MiB  EF00  EFI
   2         2099200         2131967   16.0 MiB    0C01  Microsoft reserved ...
   3         2131968       157882367   75.2 GiB    0700  Basic data partition
   4       975652864       976771071   546.0 MiB   2700  
   5       157882368       162076671   2.0 GiB     8300
   6       162076672       975652863   387.9 GiB   8300
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;why-encryption-is-important&quot;&gt;Why encryption is important&lt;/h2&gt;

&lt;p&gt;I began using encrypted storage on all my personal computers more than ten years
ago after noticing that all the companies I’d worked for required it, and had
good reason to. Laptops get lost and stolen all the time. They’re high-value
items that are small and easy to carry. And when a thief gets your laptop,
there’s tons of valuable information on it that they can use or sell. Even if
you use a password to login, it’s easy for an attacker to gain access to your
data if your disk isn’t encrypted – for example, by using a live USB stick. And
once they have that data, they might get access to online accounts, bank
statements, emails, and tons of other data. For me, an encrypted hard disk isn’t
optional anymore – it’s a necessity.&lt;/p&gt;

&lt;h2 id=&quot;congratulations&quot;&gt;Congratulations!&lt;/h2&gt;

&lt;p&gt;Congratulations, you’ve created a dual-boot system with Ubuntu 25.04 and Windows
11 with all your data encrypted! I hope you found this guide useful! I’m excited
to see that full-disk encryption with Ubuntu has become &lt;em&gt;much easier, with full
support in the GUI installer&lt;/em&gt; since I wrote my &lt;a href=&quot;/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html&quot;&gt;original dual-boot with
encryption&lt;/a&gt; guide back in 2020!
I’d love to hear from you if you validate these steps on your own computer (or
if you notice any problems with the guide itself). The best way to reach me is
&lt;a href=&quot;https://twitter.com/mike_kasberg&quot;&gt;on Twitter&lt;/a&gt; or via email.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/dual-boot-encryption-24-04.jpg"/>
 </entry>
 
 <entry>
   <title>How to Install Ubuntu Linux on a Nimo Laptop</title>
   
   <link href="https://www.mikekasberg.com/blog/2025/04/27/how-to-install-linux-on-a-nimo-laptop.html" type="text/html" title="How to Install Ubuntu Linux on a Nimo Laptop"/>
   
   <published>2025-04-27T21:00:00-06:00</published>
   <updated>2025-04-27T21:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2025/04/27/how-to-install-linux-on-a-nimo-laptop</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2025/04/27/how-to-install-linux-on-a-nimo-laptop.html">&lt;p&gt;In my previous blog post, I &lt;a href=&quot;https://www.nimopc.com/products/nimo-15-6-n153-fhd-r5-6600h-laptop?ref=glvzophi&amp;amp;variant=46948420452603&quot;&gt;reviewed a Nimo N153 R5 6600H
Laptop&lt;/a&gt;.
In the review, I mentioned that I installed &lt;a href=&quot;https://ubuntu.com/desktop&quot;&gt;Ubuntu
Linux&lt;/a&gt; on the laptop. As a follow-up, I wanted to
write a quick tutorial about how to install Linux on a Nimo laptop. I believe
this guide should work on &lt;em&gt;any&lt;/em&gt; Nimo laptop, from the lightweight
&lt;a href=&quot;https://www.nimopc.com/products/nimo-15-6-n151-fhd-laptop-for-home-and-school?ref=glvzophi&amp;amp;variant=46960521675003&quot;&gt;N151&lt;/a&gt;
to the Beefy
&lt;a href=&quot;https://www.nimopc.com/products/nimo-17-3-fhd-n172-amd-r9-6950h?ref=glvzophi&quot;&gt;N172&lt;/a&gt;
or even the mini PC
&lt;a href=&quot;https://www.nimopc.com/products/nimo-minipc-dnb20-in-macaron-intel-n100-processor?ref=glvzophi&amp;amp;variant=47004999287035&quot;&gt;DNB20&lt;/a&gt;.
The N151 is very inexpensive and could be a great way to test the waters with a
Linux laptop, and the DNB20 might make a great home server to play with!&lt;/p&gt;

&lt;h2 id=&quot;installation-process&quot;&gt;Installation Process&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Download and install &lt;a href=&quot;https://etcher.balena.io/&quot;&gt;balenaEtcher&lt;/a&gt;. We’ll use
this tool to create a bootable USB drive from Windows.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Download an Ubuntu ISO from &lt;a href=&quot;https://ubuntu.com/desktop&quot;&gt;ubuntu.com/desktop&lt;/a&gt;.
I’ll be using Ubuntu 25.04 in this guide, but the steps should be pretty much
the same for any version.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Use balenaEtcher to write the Ubuntu ISO image to the USB stick.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/how-to-install-linux-on-a-nimo-laptop/balenaEtcher.png&quot; alt=&quot;balenaEtcher creating a bootable USB stick&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Boot the laptop from the USB stick. Tapping &lt;strong&gt;F12&lt;/strong&gt; while powering the
machine on will show the boot disk menu on a Nimo laptop.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/how-to-install-linux-on-a-nimo-laptop/boot-menu.jpg&quot; alt=&quot;Nimo laptop boot menu&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;After booting the USB stick, the installer will open automatically. Follow
the prompts in the wizard to install Ubuntu. You can choose to erase the disk and
install Ubuntu, or you can choose to dual-boot Ubuntu and Windows so you have
the option to choose what you want to use each time the computer boots.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/how-to-install-linux-on-a-nimo-laptop/install-ubuntu.png&quot; alt=&quot;Ubuntu installation wizard&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you chose the dual-boot option, you may need to edit the boot preferences
in the UEFI BIOS. By default, it will always boot Windows (even after Ubuntu is
installed). You can change this by configuring the BIOS to use Ubuntu as Boot
Option #1, and Ubuntu’s Grub Bootloader will let you choose Ubuntu or Windows
each time.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/how-to-install-linux-on-a-nimo-laptop/bios-boot-setup.jpg&quot; alt=&quot;Nimo laptop BIOS setup&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Installing Ubuntu on a Nimo Laptop is actually pretty easy. I think
Ubuntu makes a &lt;a href=&quot;/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html&quot;&gt;great desktop OS&lt;/a&gt;, and I use Ubuntu as my daily
driver. Even if you’re not ready to take that step, Ubuntu makes a great
developer environment, and installing Ubuntu on a spare laptop or mini PC set up
as a home server is a great way to learn!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Tutorials"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-to-install-linux-on-a-nimo-laptop.jpg"/>
 </entry>
 
 <entry>
   <title>Laptop Review: Nimo N153 R5 6600H</title>
   
   <link href="https://www.mikekasberg.com/blog/2025/04/21/laptop-review-nimo-n153.html" type="text/html" title="Laptop Review: Nimo N153 R5 6600H"/>
   
   <published>2025-04-21T21:00:00-06:00</published>
   <updated>2025-04-21T21:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2025/04/21/laptop-review-nimo-n153</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2025/04/21/laptop-review-nimo-n153.html">&lt;p&gt;If you’ve read some of my past blog posts (like &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;My $500 Developer Laptop&lt;/a&gt; or &lt;a href=&quot;/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html&quot;&gt;The Best Computer You can
Buy for $100&lt;/a&gt;),
you might already know I enjoy finding good technology on a budget. So when
&lt;a href=&quot;https://www.nimopc.com/?ref=glvzophi&quot;&gt;Nimo&lt;/a&gt; reached out to me to see if I’d be
interested in reviewing one of their laptop models, I was really excited! Nimo
sells laptops primarily online, and takes pride in offering lower prices than
many mainstream brands. I was especially interested to try their N153 laptop
with a Ryzen 5 processor to see how it compares to the Intel processors I’m used
to.&lt;/p&gt;

&lt;h2 id=&quot;unboxing--first-impressions&quot;&gt;Unboxing &amp;amp; First Impressions&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/laptop-review-nimo-n153/unboxing.jpg&quot; alt=&quot;Unboxing a Nimo N153 laptop&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When the laptop arrived, I was excited to open up the box to see what it looked
like. The box includes the laptop and a USB-C charger, along with a short
instruction manual. The USB-C charger is nice since most of my devices are
compatible with it these days! And since this charger was made for a computer,
it’s powerful enough to charge pretty much anything. (I used it to charge my
MacBook Pro a couple times and it worked just fine!) The laptop itself looks
great, and I appreciated both the simplicity of the designs and a few small
touches like the engraved logo on the back of the screen.&lt;/p&gt;

&lt;h2 id=&quot;specs&quot;&gt;Specs&lt;/h2&gt;

&lt;p&gt;The laptop I’m reviewing is a &lt;a href=&quot;https://www.nimopc.com/products/nimo-15-6-n153-fhd-r5-6600h-laptop?ref=glvzophi&amp;amp;variant=46948420452603&quot;&gt;Nimo N153 Ryzen 5
6600H&lt;/a&gt;.
The RAM and SSD capacity are configurable – I like that since you can tweak the
specs to fit your needs!&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;CPU&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Ryzen 5 6600H&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;GPU&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Integrated Radeon 660M&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;RAM&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;16GB DDR5-4800 (Configurable)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;SSD&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;1TB (Configurable)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Resolution&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;1920x1080&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Battery&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;54 Wh&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Size&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;15.6 in&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Weight&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;4.45 lbs&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;I think the available ports are an important consideration when buying a
computer that’s sometimes overlooked. The N153 has pretty much exactly what I’d
want, with multiple USB-3.0 and USB-C ports, an HDMI port, and a MicroSD slot.
The laptop also comes with a &lt;strong&gt;backlit keyboard&lt;/strong&gt; and a &lt;strong&gt;webcam&lt;/strong&gt;, but more
notable than the webcam itself is it comes with a &lt;em&gt;physical cover&lt;/em&gt; to disable
the webcam! I was stoked to see this since it’s an important security feature
that many brands ignore! In addition, it has a &lt;strong&gt;fingerprint reader&lt;/strong&gt;. I
appreciate that so I don’t have to type my password or pin every time I log in!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/laptop-review-nimo-n153/webcam.jpg&quot; alt=&quot;The physical cover on the webcam&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;I wanted to compare the performance of the N153 to some of the other computers I
own. The &lt;a href=&quot;https://www.nimopc.com/products/nimo-15-6-n153-fhd-r5-6600h-laptop?ref=glvzophi&amp;amp;variant=46948420452603&quot;&gt;N153 is currently listed for $469.99
(new)&lt;/a&gt;
(as of April 14, 2025). (Nimo takes pride in offering lower prices than
mainstream brands.) Let’s see how it does compared to my &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;$500 (used) developer
laptop&lt;/a&gt; that I previously
wrote about. I used &lt;a href=&quot;https://www.geekbench.com/&quot;&gt;Geekbench 6&lt;/a&gt; to compare the
performance of these two computers.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Laptop Model&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Single-core Score&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Multi-core Score&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Dell Latitude&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1438&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4036&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nimo N153&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1968&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;7399&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The Ryzen 5 processor in the Nimo 153 is simply a better processor than the one
in the laptop I’ve been using for the past couple years. The N153 performs
noticeably faster for processor intensive tasks like file compression or photo
rendering. Since this laptop is &lt;em&gt;faster&lt;/em&gt; than the one I’ve been using, I’m
already pretty confident I won’t have any problems with it’s performance, but
let’s keep going through some more specs.&lt;/p&gt;

&lt;p&gt;I also wanted to see how the Nimo N153 would fare with some 3D games. I’m not
really a hardcore gamer, but I enjoy playing Starcraft II with friends from time to
time. I don’t have a gaming PC; instead I’ve been using a Dell Precision laptop
running Windows. The Precision has a Nvidia Quadro 1000M graphics card, which is
better for games than the integrated Intel UHD graphics in the Dell Latitude I
mentioned above. Even though the Nimo N153 isn’t really a gaming PC, I wondered
if it would perform better than my Dell Precision for casual gaming. I ran
3DMark Night Raid and Time Spy on both computers to compare.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Laptop Model&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;3DMark Night Raid&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;3DMark Time Spy&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Dell Precision&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;12,019&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,090&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nimo N153&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;13,475&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,216&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The N153 performs better than my Dell Precision in both specs! The N153 &lt;em&gt;isn’t&lt;/em&gt;
a gaming laptop with a dedicated graphics card, and most games &lt;em&gt;won’t&lt;/em&gt; run on
their highest level settings. But it &lt;em&gt;is&lt;/em&gt; good enough to run Starcraft II on
medium settings, and that’s good enough for me to have some fun playing
occasional games with my friends!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/laptop-review-nimo-n153/starcraft.jpg&quot; alt=&quot;Starcraft II on a Nimo N153&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;hardware--repairability&quot;&gt;Hardware &amp;amp; Repairability&lt;/h2&gt;

&lt;p&gt;A laptop review wouldn’t be complete without a look at the hardware, so I opened
up the N153 to see what it would be like to work on it. The back panel pops off
easily after removing all the screws. With the back cover off, there’s easy
access to the battery, SSD, and RAM. We can see the 54 Wh battery, AData RAM,
and Kingston SSD. I really appreciated how easy it would be to work on this
machine and swap some components since all the important components are easily
accessible with the back cover off. Opening the computer up felt similar to a
Dell, which I consider to be very easy to work on!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/laptop-review-nimo-n153/hardware.jpg&quot; alt=&quot;The motherboard of a Nimo N153&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;linux-compatibility&quot;&gt;Linux Compatibility&lt;/h2&gt;

&lt;p&gt;The N153 came with Windows 11, but of course I prefer using Ubuntu Linux. Ubuntu
25.04 just came out on April 17, 2025. I used Balena Etcher to create an Ubuntu
USB stick from Windows, and hit F12 to boot the USB stick on the N153.
Installation went smoothly, and everything works great out of the box. I didn’t
even need to install any additional drivers! The display works, the graphics
card works, the sound works, the touchpad works, the webcam works, and the
function keys work.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/laptop-review-nimo-n153/ubuntu.jpg&quot; alt=&quot;Ubuntu on the Nimo N153&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As I’ve written about before, Ubuntu is a &lt;a href=&quot;/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html&quot;&gt;great desktop OS&lt;/a&gt;. I love the simple
functionality Ubuntu offers compared to the bloat of Windows. And as a
programmer, I love using Ubuntu because I feel right at home with all the
Linux-oriented open source tools I use on a daily basis. I’ve been using Ubuntu
on the N153 as my daily driver for about a week, and it works flawlessly!&lt;/p&gt;

&lt;h2 id=&quot;but-can-it-run-an-ai-llm&quot;&gt;But can it run an AI LLM?&lt;/h2&gt;

&lt;p&gt;I wanted to push the limits of the N153 a little bit while also trying to do
something interesting. LLMs are exploding in popularity and getting better every
day, so it seemed like a fun idea to see if I could run an LLM locally on the
N153. I installed &lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama&lt;/a&gt; using the installer script from
their website.&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -fsSL https://ollama.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After installing Ollama, we can run a model. I chose a relatively small model
designed to be runnable on a laptop.&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ollama run gemma:2b
&amp;gt;&amp;gt;&amp;gt; Who are you?
I am a large language model, trained by Google. I am a conversational AI
that can assist with a wide range of tasks, including language generation,
language translation, question answering, and more.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ollama runs Gemma 2B just fine! You don’t necessarily need to run an AI locally
when you can just use ChatGPT or Gemini for free, but some people might prefer
to run something locally for offline use or for privacy reasons, and it’s a fun
way to test the capabilities of a laptop!&lt;/p&gt;

&lt;h2 id=&quot;nimo-n153-review&quot;&gt;Nimo N153 Review&lt;/h2&gt;

&lt;p&gt;I didn’t know about &lt;a href=&quot;https://www.nimopc.com/?ref=glvzophi&quot;&gt;Nimo PC&lt;/a&gt; until
recently, but I was glad to learn about them because it’s cool to see a company
building good laptops at affordable prices. The N153 that I reviewed is a
mid-range laptop that’s great for work and entertainment, but Nimo offers a
range of laptops to fit your needs. And they stand behind their products with a
2 year warranty and 90 day return policy.&lt;/p&gt;

&lt;p&gt;Overall, the Nimo N153 is a very good laptop at a great price! Its seamless
Linux compatibility makes it a good laptop for web development and programming.
It would likewise perform well at a wide variety of day-to-day tasks most people
face like email, web browsing, and spreadsheets. And it handles playing 1080p
video or editing photos with ease. I’m happy with the laptop as a daily driver
for web browsing and light programming, and I’m writing this blog post on it
right now! I recommend the Nimo N153 – particularly if you want a new laptop
for a better price than you might find from a mainstream brand.&lt;/p&gt;

&lt;hr /&gt;

&lt;p class=&quot;text-sm&quot;&gt;This laptop review is sponsored by Nimo PC, but the review is my own. In
exchange for writing the review, I was allowed to keep the reviewed product.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Reviews"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/laptop-review-nimo-n153.jpg"/>
 </entry>
 
 <entry>
   <title>I Fought the IRS for Over $12K – and WON!</title>
   
   <link href="https://www.mikekasberg.com/blog/2025/04/13/i-fought-the-irs-for-over-12k-and-won.html" type="text/html" title="I Fought the IRS for Over $12K – and WON!"/>
   
   <published>2025-04-13T18:30:00-06:00</published>
   <updated>2025-04-13T18:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2025/04/13/i-fought-the-irs-for-over-12k-and-won</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2025/04/13/i-fought-the-irs-for-over-12k-and-won.html">&lt;p&gt;This is a story about how I overpaid my 2021 taxes due to an error in TurboTax
and spent nearly three years trying to recover more than $12,000 from the IRS!
I began writing this back in 2022, in the midst of frustration with TurboTax and
the IRS, but waited to finish writing and publish it until I knew the ending.
And I’m happy to say that although I had nearly given up hope, I successfully
recovered my $12K tax refund, &lt;em&gt;with interest&lt;/em&gt;, in 2025! I want to share the
story not only because I think it’s fascinating, but because I also hope it
helps anyone else who finds themself in a similar situation.&lt;/p&gt;

&lt;h2 id=&quot;the-error&quot;&gt;The Error&lt;/h2&gt;

&lt;p&gt;The story begins in back in March, 2022. Although I’d always disliked Intuit and
knew they &lt;a href=&quot;https://www.propublica.org/article/inside-turbotax-20-year-fight-to-stop-americans-from-filing-their-taxes-for-free&quot;&gt;lobby to make taxes
worse&lt;/a&gt;,
I’d been using TurboTax to file my taxes for many years. Every time tax season
rolled around, I just wanted a quick and cheap solution to get my taxes done.
TurboTax was always the easy answer and, like always, I filed my 2021 taxes with
TurboTax.&lt;/p&gt;

&lt;p&gt;My 2021 tax situation was a little unique because I chose to exercise some
Incentive Stock Options (ISOs). When you exercise ISOs, the bargain element is
reported as a special kind of income on your taxes, and there are a bunch of
complicated rules about how it gets taxed. In some cases, a rule called the
alternative minimum tax (AMT) is triggered, which causes all your taxes for the
year to be calculated using a completely different set of rules. This &lt;em&gt;can be
dangerous&lt;/em&gt; if you’re not expecting it, as you might owe much more than you
anticipated at the end of the year if you’re not careful. Of course, I was aware
of this and worked with a financial professional to understand roughly what I
would owe.&lt;/p&gt;

&lt;p&gt;TurboTax &lt;em&gt;does handle ISOs&lt;/em&gt;, and my financial advisor even recommended I keep
using TurboTax since I’d used it before and was comfortable with it. When I did
my 2021 taxes, TurboTax didn’t ask me about ISOs in the income section. I wanted
to be proactive about making sure I reported my income correctly, so I used
TurboTax Help to search for ISOs and AMT. The TurboTax Help article said ISOs
need to be reported on Form 6251 to calculate AMT, and provided a link to enter
the data in TurboTax. I used the link to fill out data for Form 6251 in TurboTax
and continued working through TurboTax. After completing most of the sections,
it asked me a question about “odd tax situations”, including whether I’d
exercised ISOs. I was slightly annoyed to re-enter some info since I’d already
done this once after reading the help article, but I was also reassured TurboTax
would handle things correctly since it was actually asking about this, so I
entered all the info it asked for. Doing so made my tax refund change from a
small refund to about $10K &lt;em&gt;owed&lt;/em&gt;. That number was a little higher than I
expected, but I had expected to owe &lt;em&gt;some&lt;/em&gt; additional tax due to my ISOs, so I
shrugged and paid my tax bill. It felt great to be done with my taxes, and filed
them electronically in early March, 2022.&lt;/p&gt;

&lt;h2 id=&quot;finding-the-error&quot;&gt;Finding the Error&lt;/h2&gt;

&lt;p&gt;A few days later, something didn’t seem right to me about the extra $10K I’d
paid on my taxes. While I was aware that I might owe some extra tax, $10K was
larger than I’d estimated I would owe with some simple spreadsheets. I’d also
worked with my financial planner to avoid triggering &lt;em&gt;too much&lt;/em&gt; AMT and, though
our estimations weren’t exact, a $10K difference was more than either of us had
expected. In general, AMT doesn’t get triggered until a certain amount of
income, and I’d planned to use my ISOs in a way that wouldn’t trigger it (or
might barely trigger it depending on my other income).  So the $10K change in
TurboTax due to AMT felt wrong. I looked closer at &lt;em&gt;the actual tax forms
TurboTax produces and sends to the IRS&lt;/em&gt; (the PDF) and found that the numbers
were wrong. TurboTax had produced a Form 6251 that showed &lt;em&gt;exactly double&lt;/em&gt; my
ISO income!&lt;/p&gt;

&lt;p&gt;After noticing that the ISO income TurboTax reported on Form 6251 was exactly
double what it should be, I immediately suspected I knew what the problem was.
(I’m a programmer; I know how these things work.) I’d entered my ISO info in two
different forms in TurboTax – once after searching TurboTax Help for ISO and
AMT, and again when TurboTax asked me about ISOs near the end. I’d originally
assumed it was just validating or overwriting the numbers I’d already put in,
but it was actually creating duplicates! (C’mon, how does software like TurboTax
not validate against duplicate entry???) Bummer. I’d already filed my taxes and
overpaid about $10K via direct deposit, and now I needed to get my money back. I
actually didn’t think it would be too hard. I just needed to file an amended
return showing my correct income and I’d get my refund. TurboTax has a built in
process to amend your return, and they try to make it easy. (Oh, how naive!)&lt;/p&gt;

&lt;p&gt;I used TurboTax to amend my return, and was careful to make sure I removed the
duplicate ISO information. I looked at the amended 1040-X TurboTax generated. My
ISO income was now so low that I didn’t actually trigger the AMT – as I’d
originally intended, and my overall tax was reduced by more than $12K! My 1040-X
showed that the IRS owed me a $12K refund on line 22. I was satisfied that
TurboTax calculated things correctly this time, so I hit the submit button and
TurboTax e-filed my amended return, just a few days after filing the original
return.&lt;/p&gt;

&lt;h2 id=&quot;waiting&quot;&gt;Waiting&lt;/h2&gt;

&lt;p&gt;When you e-file and you owe money, the IRS is happy to take your money
immediately via credit card or ACH withdrawal. Unfortunately, they’re not so
quick when &lt;em&gt;they owe you&lt;/em&gt; money. My original tax return was accepted in a few
days, so I originally anticipated waiting about a week to get my refund. I
waited… And waited… And waited some more. A month went by and I still hadn’t
received my refund, nor had my return even been processed. As it turns out, the
IRS was &lt;a href=&quot;https://www.taxpayeradvocate.irs.gov/news/national-taxpayer-advocate-releases-fiscal-year-2023-objectives-report/&quot;&gt;months behind on processing amended returns in
2022&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;At the end of May, the agency had a backlog of 21.3 million unprocessed paper
tax returns, an increase of 1.3 million over the same time last year.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apparently, I was at the back of the line. &lt;strong&gt;I e-filed my amended return in
early March, 2022, and it was not processed by the IRS until late March, 2023 –
more than a year later.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/i-fought-the-irs-and-won/2023-01-15_IRS-Covid-Delay.png&quot; alt=&quot;Processing delay notice on IRS website&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.irs.gov/filing/wheres-my-amended-return&quot;&gt;IRS website&lt;/a&gt; says
processing for amended returns can take up to 16 weeks. In my case, it took
&lt;strong&gt;more than 52&lt;/strong&gt;! While I was waiting for my return to be processed, I
repeatedly tried to check in with the IRS. The online &lt;a href=&quot;https://www.irs.gov/filing/wheres-my-amended-return&quot;&gt;Where’s My Amended
Return&lt;/a&gt; tool showed that my
return had been accepted in early March, 2022, and provided a phone number to
call if it had been more than 16 weeks since the IRS accepted my return. I
called this phone number several times over many months, each time waiting on
hold for an hour only to be hung up on or told that the agent couldn’t tell me
anything more than the website. My entire experience with the IRS was incredibly
frustrating.  They weren’t adhering to &lt;em&gt;their own timelines and rules&lt;/em&gt;, but I
had no recourse other than to wait. I considered hiring legal representation or
a tax professional, but ultimately decided it wasn’t worthwhile yet – I figured
they probably couldn’t do anything, and their fees might cut significantly into
the $12K I was trying to recover.&lt;/p&gt;

&lt;p&gt;One day around March 2023, I checked the IRS website and noticed that my refund
was finally marked as completed! I waited to receive something in the mail…
And waited… And waited some more. I called after a week or so to say that I
hadn’t received anything in the mail even though my refund was marked as
completed, and was told that I should wait longer since the IRS systems can be
slow and the mail can be slow. The IRS Where’s My Amended Return tool provides a
number to call if your return was completed and you hadn’t received a notice. I
called this number multiple times without getting anywhere. In just one example
of the complete incompetence I dealt with, I called and said my return was
marked as complete but I hadn’t received anything in the mail and was expecting
a refund. I was told I’d called the wrong number and transferred to the refunds
department. The refunds department told me there had been no refund issued and
that they couldn’t give me any other info. The amount of “That’s not my
department” I had to deal with was astounding, and an enormous waste of time.&lt;/p&gt;

&lt;p&gt;I thought it was odd that the IRS agent told me no refund had been issued when
my 1040-X, which was marked as “completed” on their website, showed a refund of
$12K on line 22. So I looked into it more. I did some research, and I found out
that you can request a transcript of your IRS records by logging in to the &lt;a href=&quot;https://www.irs.gov/payments/your-online-account&quot;&gt;IRS
Online Account&lt;/a&gt; page. I
downloaded my transcript and compared it with my original return and my amended
return. I noticed a line on my transcript, labeled “Form 6251”, that still
showed the incorrect number from my original return. I dug into this more, and
realized that the amended return generated by TurboTax didn’t include a Form
6251! That’s a smoking gun, if I’ve ever seen one! The &lt;a href=&quot;https://www.irs.gov/faqs/irs-procedures/amended-returns-form-1040x&quot;&gt;IRS 1040-X
FAQ&lt;/a&gt; says,
“Include copies of any forms and/or schedules that you’re changing”, and
TurboTax &lt;em&gt;did not include Form 6251 in my amended return&lt;/em&gt; – so the IRS never
changed my income from ISOs to the correct number. The duplicate entries
produced by TurboTax on my original return were a bad user experience, but this
was a straight-up bug.&lt;/p&gt;

&lt;h2 id=&quot;the-turbotax-guarantee&quot;&gt;The TurboTax Guarantee?&lt;/h2&gt;

&lt;p&gt;With the evidence that my 1040-X was incorrect because it was missing for 6251,
I got on the phone with TurboTax. Luckily, as it was now mid-summer, tax season
had passed and I was able to reach TurboTax Help pretty quickly. The TurboTax
agent (a tax accountant I guess?) had me share my screen, and I presented what
I’d found, suggesting that TurboTax should have included a Form 6251 with my
amended return but they did not. He told me it didn’t matter – the IRS would
use the value reported on my 1040-X, and Form 6251 didn’t need to be included
since I didn’t owe any AMT on the amended return. (As it turns out, he didn’t
know what he was talking about. 😂) When I pressed him that I hadn’t received my
refund from the IRS, he told me that the IRS &lt;em&gt;must&lt;/em&gt; either issue a refund (per
line 22 of my 1040-X) or send me a notice to say that my amended return was
incorrect, and he couldn’t do anything without this notice.  Ugh. OK, fine. I’d
call the IRS and ask for the notice.&lt;/p&gt;

&lt;p&gt;Back on the phone, again, to dial the IRS number on the “Where’s My Amended
Return” page. But this time I was armed with the info the TurboTax accountant
gave me – the IRS either owed me a refund or owed me a notice to say why my
amended return is incorrect. After another hour on hold, I spoke with perhaps
the only competent agent in the IRS. (Or maybe I was just using the right words
now – saying, “I never received a notice in the mail,” instead of, “I never
received a refund.” If so, it’s a major flaw of the IRS that the response to “I
never received a refund from my amended return,” is, “Let me transfer you to the
refund department,” rather than, “Did you receive a notice?” or, “What info have
you received?”) In any case, the agent was able to tell me that a notice had
been mailed to me in March, 2023. To this day, I don’t know what happened to
that notice.  Most likely, I suppose it was lost in the mail and never
delivered. The agent also, to my &lt;em&gt;utter astonishment&lt;/em&gt;, was able to describe what
the notice said. He said that basically, the IRS rejected my amended return
because I didn’t provide Form 6251. He also mentioned that he could resend a
copy of the notice, which I gladly accepted.&lt;/p&gt;

&lt;p&gt;A couple weeks later, I did receive Notice 916-C in the mail. And, as the agent
I spoke with suggested, it said that my amended return wasn’t accepted because
it was missing Form 6251. Additionally, they wanted me to submit copies of Form
3921 showing my ISO income. I called TurboTax &lt;em&gt;again&lt;/em&gt;, armed with the notice
this time, but they continued to be utterly useless. They implied the rejection
of the amended return was &lt;em&gt;my fault&lt;/em&gt; since I didn’t include the right forms
(even though TurboTax generated a Form 6251 in the original return and &lt;em&gt;didn’t&lt;/em&gt;
generate one in the amended return). If that’s my fault, what am I even paying
them for?!? The support rep on the phone gave me the URL of a page where I could
request a refund because of my dissatisfaction. I requested a refund, including
the above info about their incorrect return and lack of support resolving the
problem. They did not offer me a refund, and basically said that once their
software generates a return they don’t offer refunds anymore, though they were
happy to give me 30% off next year if I use them again. So much for a “biggest
refund guarantee”.&lt;/p&gt;

&lt;h2 id=&quot;the-bug-in-turbotax&quot;&gt;The Bug in TurboTax&lt;/h2&gt;

&lt;p&gt;As a software engineer, the bug(s) in TurboTax seem obvious to me. The first
problem is that I was able to enter my ISO info twice, in two separate parts of
the UI, and they weren’t checked for duplication. It’s easy to see how their app
could evolve over time to get to that place, but it should also be a relatively
easy fix to remove one form or the other, and/or to check for unintentional
duplicate entries. Whether or not we call that a bug, it was a terrible user
experience. The second bug happened when I amended my return. On the original
return, TurboTax automatically included Form 6251 for AMT. But then, when my
edits made my income fall below the AMT threshold, TurboTax did not include Form
6251 or 3921. While this would have been correct on an original tax return, &lt;strong&gt;it
is incorrect to not file From 6251 on an amended return if it had already been
filed on the original return and had changed since then.&lt;/strong&gt; Though it would be
easy to introduce this bug in software by using the same logic to generate the
amended return as was used for the original one. Although TurboTax support was
unhelpful, I remain convinced that the problems with my amended return were due
to a clear bug in their software.&lt;/p&gt;

&lt;h2 id=&quot;amending-my-own-return&quot;&gt;Amending My Own Return&lt;/h2&gt;

&lt;p&gt;$10,000 is a weird amount of money when you need tax help. It’s big enough that
it’s definitely worth a lot of time to try to recover it. At the same time, it’s
not a &lt;em&gt;huge&lt;/em&gt; amount of money. It felt like if I got professional tax help, I’d
probably pay about $1-2K, and I’d be spending up to 20% of my refund money just
to get the help I needed.&lt;/p&gt;

&lt;p&gt;Since TurboTax wouldn’t provide the help I needed, I decided to just amend my
return myself. Having my online tax transcript, Notice 916-C, and the 1040-X
generated by TurboTax gave me confidence that I knew what was wrong and that I
would be able to fix it. But I didn’t really want to start a new 1040-X from
scratch. Fortunately, I didn’t have to! I realized the 1040-X TurboTax generated
had all the right numbers, it was just missing extra documentation that TurboTax
neglected to include. I found a blank Form 6251 online and followed the
instructions to fill in the numbers, cross-referencing it with the original
(doubled) Form 6251. As I expected, the corrected form showed $0 AMT on line 11.
I printed out the amended 1040-X from TurboTax and my copies of Form 3921. I put
these all together in the right order and mailed it to the IRS per their
instructions for submitting an amended return.&lt;/p&gt;

&lt;p&gt;This time, I knew what to expect… I waited… And waited… And waited some
more. I’d mailed my return using Priority Mail with tracking, and the package
sat somewhere (probably an IRS sorting facility or something) for nearly a month
before tracking finally showed it’d been delivered. And of course, once it had
been delivered, I was right back where I’d been a year earlier. Checking the
“Where’s My Amended Return” website, like before, showed that my return was
received but not processed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/i-fought-the-irs-and-won/2023-01-24_IRS-Take-Action.png&quot; alt=&quot;IRS Website - Take action&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After waiting more than a year (again), I became frustrated that my return still
hadn’t been accepted! I did some more searching, and found a couple &lt;a href=&quot;https://www.reddit.com/r/IRS/comments/1c6kx6d/reach_out_to_your_congressional_representatives/&quot;&gt;posts on
Reddit&lt;/a&gt;
that recommended reaching out to my congressional representative about my
problems with the IRS. As it turns out, this works! I sent a short email
explaining my situation to my representative on Jan 12, 2025 and heard back from
their office the following day. On Feb 24, I got an email from my representative
to notify me that the IRS was processing my case. And on Mar 26, I got a check
in the mail from the IRS for my tax refund, plus interest for the extra three
years they’d held my money!&lt;/p&gt;

&lt;h2 id=&quot;lessons-and-observations&quot;&gt;Lessons and Observations&lt;/h2&gt;

&lt;p&gt;I’m really happy I got everything sorted out in the end, and I want to share
some of the things I learned along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t overpay your taxes.&lt;/strong&gt; It may be exceptionally difficult and slow to get
your refund if you need to file an amended return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get help from your congressional representative if you have a difficult
problem with the IRS.&lt;/strong&gt; I experienced much, much better customer service from my
congressional representative than from the IRS themselves. Your representative
probably isn’t the right place to go for simple questions, but they may be very
helpful if you have a problem that’s been difficult to resolve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I don’t recommend TurboTax.&lt;/strong&gt; I know, I know, it’s still the easiest way to do
your taxes for most normal tax situations. But as you can see, the guarantees
all over their website seem somewhat meaningless. And they &lt;em&gt;do&lt;/em&gt; lobby congress
to make your taxes worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider doing your taxes yourself&lt;/strong&gt; (instead of using TurboTax). Because of
my frustration with TurboTax, &lt;a href=&quot;/blog/2024/03/15/i-did-my-own-taxes-by-hand-and-you-can-too.html&quot;&gt;I started doing my own taxes by hand&lt;/a&gt;, without TurboTax or
any other tax software. I’ve done this two years in a row now, and it’s been a
really positive experience. It’s free, I know I won’t run into any bugs in
TurboTax, and I know I’m not giving money to Intuit. It’s actually a lot easier
to do your own taxes than most people think it is, and now that I’ve done it I
have a much better understanding of how my taxes are calculated. It’s easy to
get started, and it feels a lot like a standardized test – just follow the
instructions for Form 1040.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The government should make all this easier.&lt;/strong&gt; The US is one of the only
countries in the world where people have to report their own income to the
government every year (and a big reason for that is the companies like TurboTax
that make so much money from it). In almost all cases, &lt;em&gt;the government already
knows how much tax you owe before you tell them anything&lt;/em&gt;. If fixing this was a
priority for our politicians, none of us would have to file taxes ever again!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/i-fought-the-irs-and-won.jpg"/>
 </entry>
 
 <entry>
   <title>Recovering Data from a 2015 Mac SSD with a $5 Adapter from AliExpress</title>
   
   <link href="https://www.mikekasberg.com/blog/2025/01/11/recovering-data-from-a-2015-mac.html" type="text/html" title="Recovering Data from a 2015 Mac SSD with a $5 Adapter from AliExpress"/>
   
   <published>2025-01-11T21:00:00-07:00</published>
   <updated>2025-01-11T21:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2025/01/11/recovering-data-from-a-2015-mac</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2025/01/11/recovering-data-from-a-2015-mac.html">&lt;p&gt;I worked on a fun little project recently to recover data from my wife’s 2015
MacBook Air. Although that might not sound terribly interesting, the way I ended
up using an adapter is one of the most ridiculous things I’ve ever done with a
computer. So of course, I had to write a blog post about it.&lt;/p&gt;

&lt;p&gt;Several weeks ago, my wife’s 2015 MacBook Air stopped working. It wouldn’t turn
on, even when connected to power, and the screen was just blank. I don’t know
exactly what went wrong with it, but probably the motherboard or
some other internal component failed.&lt;/p&gt;

&lt;p&gt;Since the computer was nearly ten years old, it was well past time to replace it with
a new one anyway. But my wife was a little worried about data that wasn’t backed
up. She does back up her computer occasionally, but hadn’t done so for a while
and didn’t know exactly what data she might have lost. (There’s probably a
lesson about backups there…) She didn’t think she’d lost any critical data,
but wanted me to try to recover the data for peace of mind if it was easy and
cheap. Fortunately for her, all the evidence suggested that her disk was fine!
(If the disk had gone bad, I’d expect the computer to boot the BIOS and display
an error message, not fail to turn on completely.) I decided I’d try to recover
the data from the SSD!&lt;/p&gt;

&lt;p&gt;Normally, recovering data from a functional disk is pretty easy.
Just put the disk in a working computer, or attach it to a working computer with
a USB adapter, and copy whatever you need. Not much harder than copying data off
a USB stick. For this 2015 MacBook Air, however, it turned out to be much more
difficult (and harder than I originally anticipated)! Almost all computer hard
drives have standardized connector – SATA for most older 2.5” disks, and M.2
for newer SSDs.&lt;/p&gt;

&lt;p&gt;Unfortunately, a 2015 MacBook Air uses neither SATA nor M.2. It uses a
proprietary Apple “12+16” pin connector. This is incredibly annoying – it not
only inhibits the upgradability of these machines, but also makes it much harder
to recover data from them! The obvious approach to recover data from a 12+16
drive would be to install it in a working Apple computer with that kind of
connector. But my wife’s MacBook was dead and I didn’t want to buy another ten
year old laptop just for this data recovery.&lt;/p&gt;

&lt;p&gt;The other obvious option for data recovery is to find an adapter, and that’s
what I did. I wanted to find 12+16 to USB-A adapter to plug the old Apple drive
in like a USB disk, but I couldn’t find any adapters cheaper than a full disk
enclosure for $60+. I was, however, able to find a 12+16 pin (female) to M.2
(male) adapter for just $4.11 (as of Dec, 2024) on
&lt;a href=&quot;https://www.aliexpress.us/item/3256805613964883.html&quot;&gt;AliExpress&lt;/a&gt;. I knew it
was a bit of a gamble, but I decided to give it a go! I still wanted to plug
this in like a USB disk to grab the data off it, so I also ordered a cheap M.2
USB enclosure for $6.88 from
&lt;a href=&quot;https://www.aliexpress.us/item/3256805739692606.html&quot;&gt;AliExpress&lt;/a&gt;. That led to
the first ridiculous attempt to recover data from this drive, with 2 adapters
between the Apple SSD and my laptop, and with rubber bands holding everything
together.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/recovering-data-from-a-2015-mac/usb.jpg&quot; alt=&quot;A 12+16 Apple SSD connected to an Apple-to-M.2 adapter connected to an
M.2-to-USB adapter connected to a computer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The adapters did light up, and even showed up under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsusb&lt;/code&gt; in Linux, but
something in the chain wasn’t working right and the disk reported a size of
zero. Can’t say I’m terribly surprised.&lt;/p&gt;

&lt;p&gt;I knew it was a gamble ordering sub-$10 adapters from AliExpress (and even more
of a gamble chaining two of them together), and I was ready to give up on the
project when it occurred to me that I could still try taking one of the adapters
out of the chain by plugging the Apple adapter directly into a laptop’s M.2
slot. My &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;old Precision 5510&lt;/a&gt; (which has become my
Windows machine) had an empty M.2 slot that looked like it would work so
I decided to give it a go! As it turns out, the 12+16 to M.2 adapter from
AliExpress was slightly too wide and also too long to fit properly in my laptop.
Would it read the drive (just long enough to copy the data off it) if it wasn’t
seated properly? I booted the Windows laptop from a Live Ubuntu USB stick so I
could work in Linux.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/recovering-data-from-a-2015-mac/crooked_drive.jpg&quot; alt=&quot;A partially installed M.2 adapter in a running laptop&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It WORKED!!!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/recovering-data-from-a-2015-mac/working.jpg&quot; alt=&quot;A running laptop with a M.2 adapter hanging off the back&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was a little surprised, and something in me felt an odd happiness at
succeeding with this ridiculous setup. I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dd&lt;/code&gt; to make an image of the Apple
disk:&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dd if=/dev/sdb of=~/Desktop/backup.img bs=4M status=progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since I was using Linux to run this backup, I didn’t even try to read the files.
I just dumped the whole disk to a .img file. I transferred that file to my NAS,
and from there to my wife’s new M1 MacBook.&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hdiutil attach ~/Desktop/backup.img
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The disk image mounted just fine on the new Mac, and we were able to recover all
the files off the old drive. We copied the ones we cared about to the NAS, and
then discarded the huge disk image file. So, if you ever find yourself needing
to recover data from an old Apple 12+16 pin SSD, you might be able to do it for
under $5 with an adapter from China! (But of course, do so at your own risk –
especially when using a $5 adapter from China.)&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/recovering-data-from-a-2015-mac.jpg"/>
 </entry>
 
 <entry>
   <title>Ghostty on Ubuntu: First Impressions and How to Install</title>
   
   <link href="https://www.mikekasberg.com/blog/2024/12/26/ghostty-on-ubuntu.html" type="text/html" title="Ghostty on Ubuntu: First Impressions and How to Install"/>
   
   <published>2024-12-26T20:30:00-07:00</published>
   <updated>2024-12-26T20:30:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2024/12/26/ghostty-on-ubuntu</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2024/12/26/ghostty-on-ubuntu.html">&lt;p&gt;&lt;a href=&quot;https://ghostty.org&quot;&gt;Ghostty&lt;/a&gt; came out today! I’ve been following the
development on &lt;a href=&quot;https://mitchellh.com/writing&quot;&gt;Mitchell Hashimoto’s Blog&lt;/a&gt; for a
while, and I was excited to give the terminal a try.&lt;/p&gt;

&lt;h2 id=&quot;first-impressions&quot;&gt;First Impressions&lt;/h2&gt;

&lt;p&gt;Ghostty is great! My first impression was that it looked exactly like Gnome
Terminal, with a slightly different color scheme. That’s not a criticism, that’s
actually high praise! Mitchell has written &lt;a href=&quot;https://mitchellh.com/writing/ghostty-is-coming&quot;&gt;on his
blog&lt;/a&gt; about the importance of
Ghostty being native while also being cross-platform. That is to say, although
it works on both macOS and Linux, it uses native windows, menu bars, and tabs.
After trying Ghostty for a little bit, I have to say I agree, and he nailed it!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ghostty-on-ubuntu/ghostty.png&quot; alt=&quot;Ghostty terminal on Ubuntu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was really pleased to see that everything just worked out of the box (and I
know Mitchell’s put in a lot of effort here too). I’ve been using Gnome Terminal
as my daily driver on Ubuntu for more than ten years, and I knew within a few
minutes of trying Ghostty that I’d be using Ghostty as my daily driver going
forward. I’ve briefly tried other terminals before, like Kitty or Alacritty, but
I always came back to Gnome Terminal – specifically because I don’t like the
non-native look and feel of others. I make heavy use of native tabs, so the
non-native tabs (or complete lack of tabs) and non-native UI of other Terminals
kept me using the default Gnome Terminal, despite knowing the others were
faster. With Ghostty, I can have my cake and eat it too!&lt;/p&gt;

&lt;p&gt;Ghostty is simple (it really does look very similar to Gnome Terminal), but it’s
fast and has advanced configuration if you need it. I haven’t needed it yet,
though I plan to experiment with different themes in the near future. I’m
finishing this blog post in vim in Ghostty, and so far, Ghostty has been
everything I hoped it would be. I highly recommend it! Mitchell, congrats on a
great Terminal and a great launch! I appreciate all your work (and the work of
the rest of the community)!&lt;/p&gt;

&lt;h2 id=&quot;how-to-install-ghostty-on-ubuntu&quot;&gt;How to Install Ghostty on Ubuntu&lt;/h2&gt;

&lt;div class=&quot;message&quot;&gt;
I&apos;m working on an &lt;b class=&quot;not-prose&quot;&gt;&lt;a href=&quot;https://github.com/mkasberg/ghostty-ubuntu&quot;&gt;
unofficial Ubuntu package for Ghostty&lt;/a&gt;&lt;/b&gt;! This should be easier to install
than building from source as outlined below. Follow the instructions on
GitHub, and open an issue there if you run into problems!
&lt;/div&gt;

&lt;p&gt;There’s no official (or unofficial) Ubuntu package yet, so the only way to get
Ghostty running on Ubuntu right now is to build it yourself. Fortunately, this
isn’t too hard! The build instructions are documented in the Ghostty docs, but
I’ll lay out some step-by-step instructions for Ubuntu here.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You’ll need Zig 0.13. There are several ways to install this; I installed it
by downloading a binary release. Download the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tar.xz&lt;/code&gt; file for your
architecture from the &lt;a href=&quot;https://ziglang.org/download/&quot;&gt;Zig Downloads&lt;/a&gt; page.
You probably want the x86_64 version. Make sure you get version 0.13 for Linux.&lt;/li&gt;
  &lt;li&gt;Extract the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tar.xz&lt;/code&gt; file to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt&lt;/code&gt; directory. (Any directory would be
fine, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt&lt;/code&gt; is conventional for stuff like this and is what I chose.)
    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo tar -xf zig-linux-x86_64-0.13.0.tar.xz -C /opt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Make sure you have the required dependencies to compile Ghostty, as
documented in the &lt;a href=&quot;https://ghostty.org/docs/install/build#linux-installation-tips&quot;&gt;Ghostty docs&lt;/a&gt;.
    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt install libgtk-4-dev libadwaita-1-dev git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Download Ghostty. The easiest way is to just clone the git repo. Then you can
check out a specific tag, or just build main.
    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/ghostty-org/ghostty.git
cd ghostty
git checkout v1.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Build Ghostty. Again, following the &lt;a href=&quot;https://ghostty.org/docs/install/build#installation-directory&quot;&gt;docs&lt;/a&gt;.
    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/opt/zig-linux-x86_64-0.13.0/zig build -p $HOME/.local -Doptimize=ReleaseFast
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update-desktop-database&lt;/code&gt; to refresh the application list so you get
the Ghostty icon in your launcher. (If that command fails, try it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt;.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! By default, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.local/bin&lt;/code&gt; should be in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$PATH&lt;/code&gt;, so the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ghostty&lt;/code&gt;
command should now be available. The Ghostty build process should also have
installed a launcher file to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$HOME/.local/share/applications&lt;/code&gt;, so you should be
able to find Ghostty in your normal application launcher menu without any extra
effort!&lt;/p&gt;

&lt;p&gt;For the time being, I think this is the easiest way to install Ghostty on
Ubuntu. It will take some time for Ghostty to make it into the official Debian
or Ubuntu repositories, but I hope to see some other installation options like
Flatpak, Snap, Linux binaries, or Homebrew (on Linux) become available in the
coming weeks.&lt;/p&gt;

&lt;p&gt;I’m looking forward to using Ghostty as my default shell, and I hope this helps
you get it set up on your own machine!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/ghostty-on-ubuntu.jpg"/>
 </entry>
 
 <entry>
   <title>How to Dual-Boot Ubuntu 24.04 - 24.10 and Windows (10 or 11) with Encryption</title>
   
   <link href="https://www.mikekasberg.com/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html" type="text/html" title="How to Dual-Boot Ubuntu 24.04 - 24.10 and Windows (10 or 11) with Encryption"/>
   
   <published>2024-05-20T07:30:00-06:00</published>
   <updated>2024-05-20T07:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html">&lt;p&gt;Dual-booting Ubuntu and Windows with encryption for both has been possible for a
long time, but has always been difficult. Until recently, the Ubuntu installer
supported encrypting Ubuntu (with LVM) &lt;em&gt;or&lt;/em&gt; dual-booting with Windows, but never
supported automatic partitioning for encrypted dual-boot – and therefore
required manual LVM partition setup to achieve encrypted dual-boot. I wrote a
long blog post back in 2020 (&lt;a href=&quot;/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu and Windows
with Encryption&lt;/a&gt;) that describes the complicated steps necessary to set up an encrypted LVM
partition for encrypted dual-boot. &lt;strong&gt;In Ubuntu 24.04+, things are much easier&lt;/strong&gt;
because the installer supports dual-booting and using encryption simultaneously
without resorting to manual partitioning!&lt;/p&gt;

&lt;p&gt;I wrote that original blog post because I couldn’t find any good documentation
about how to dual-boot Ubuntu and Windows with encryption, and even though the
new installer makes the process easier I think the lack of documentation is
still a problem I can help solve. So I’m completely re-writing my guide here to
document the process for the new installer in Ubuntu 24.04+. If you’re familiar
with my original guide, you’ll find that a few bits are the same, but the
overall process is substantially shorter and simpler because the Ubuntu 24.04
installer can now handle most partitioning for you! I hope this tutorial blog
post will help you achieve dual-boot Windows (10 or 11) and Ubuntu (24.04+),
with both encrypted. With the right instructions, this isn’t difficult – only a
little bit harder than a normal (non-encrypted) dual-boot. Like my previous
dual-boot guide, I tested this on a Dell Latitude e7450, but I expect it to work
on pretty much any computer with UEFI.&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;Compatibility&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;This guide is compatible with &lt;b&gt;Windows 10&lt;/b&gt; or &lt;b&gt;Windows 11&lt;/b&gt;, and the
process is nearly identical with either version.&lt;/p&gt;
&lt;p&gt;This guide was written for &lt;b&gt;Ubuntu 24.04 - 24.10&lt;/b&gt;. I&apos;ve confirmed that it works
with these versions. I suspect it will work with Ubuntu 23.04+ as
long as you use the new installer (not the legacy installer), but I haven&apos;t
confirmed this. (If you&apos;re successful with Ubuntu 23.04 or 23.10, let me know
via email or Twitter and I&apos;ll update this note!)&lt;/p&gt;
&lt;p&gt;For Ubuntu &lt;b&gt;25.04+&lt;/b&gt;, see &lt;a href=&quot;/blog/2025/05/19/dual-boot-ubuntu-25-04-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu (25.04+) and Windows with
Encryption&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For Ubuntu versions
&lt;b&gt;earlier than 23.04&lt;/b&gt;, see &lt;a href=&quot;/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu (16.04 - 22.10) and Windows with
Encryption&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;tldr-instructions&quot;&gt;Tl;Dr (Instructions)&lt;/h2&gt;

&lt;p&gt;In Ubuntu 24.04+, the installer is (finally) capable of automatically shrinking
a Windows partition and installing Ubuntu to an LVM encrypted partition
automatically.  &lt;strong&gt;But&lt;/strong&gt; the installer UI is misleading, and we’re only able to
achieve this via a quirk in the installer. It’s unclear to me at this point in
time if we’re using intentional functionality (with terrible UI design) or
unintentional functionality that happens to work. In either case, here’s the
procedure for getting encrypted dual-boot with the Ubuntu installer.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Windows should already be installed.&lt;/li&gt;
  &lt;li&gt;Run the Ubuntu installer. Go through the initial installer screens to choose
your language and set up internet access. Choose an interactive install process,
and select the software you want to install as you continue through the
installer wizard.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When you get to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Disk setup&lt;/code&gt; step, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Erase disk and install
Ubuntu&lt;/code&gt;. Hit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Advanced features...&lt;/code&gt; button, and select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Use LVM and
encryption&lt;/code&gt;, and hit OK to close the pop-up window. &lt;strong&gt;Now change your selection
back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Install Ubuntu alongside Windows Boot Manager&lt;/code&gt;&lt;/strong&gt;. (Note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Use
LVM and encryption&lt;/code&gt; remains listed in the UI even though the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Advanced
features...&lt;/code&gt; button is disabled. Through this UI quirk, we’re able to use
the LVM encryption advanced feature &lt;em&gt;and&lt;/em&gt; select the dual-boot radio button,
and the installer will do what we want despite the odd UI.)&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption-24-04/installer_radio_buttons.png&quot; alt=&quot;Install Ubuntu alongside Windows Boot Manager with Use LVM and encryption selected&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Next&lt;/code&gt; and finish the installer, completing the steps to choose a
password and setup your user account.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption-24-04/installer_ready.png&quot; alt=&quot;The installer confirmation screen showing encrypted dual-boot&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;After Ubuntu finishes installing, you have an encrypted Ubuntu partition.
Boot into Windows and enable BitLocker to encrypt the Windows partition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Congratulations, you have a dual-boot system where Ubuntu is
encrypted with LVM (LUKS) and Windows is encrypted with BitLocker. Compared to
previous versions of Ubuntu this process is much less tedious, and I’m excited
and grateful for the work of everyone who contributed to the new Ubuntu
installer!&lt;/p&gt;

&lt;h2 id=&quot;important-notes&quot;&gt;Important Notes&lt;/h2&gt;

&lt;p&gt;The process is really straight-forward since the Ubuntu installer does all the
hard parts. Still, there are a few places you might get stuck along the way.
Hopefully these notes help.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;As with any new OS installation, you should back up any important data before
proceeding. &lt;strong&gt;These instructions will modify partitions on your hard disk and
could result in data loss.&lt;/strong&gt; Proceed at your own risk; I’m not responsible for
any damage or data loss.&lt;/li&gt;
  &lt;li&gt;Your BIOS must be configured to boot with UEFI.&lt;/li&gt;
  &lt;li&gt;Windows BitLocker must be turned off when Ubuntu is installed. The primary
reason for this is the Ubuntu installer can’t shrink an encrypted Windows
partition, but the installer won’t let you proceed, even if it doesn’t need to
shrink any partitions, if BitLocker is enabled.&lt;/li&gt;
  &lt;li&gt;Make sure you save your BitLocker recovery key before rebooting, and consider
selecting the checkbox to test BitLocker when setting it up. There’s a chance
your boot sequence will prevent BitLocker from automatically unlocking the drive
and you’ll need the recovery key.&lt;/li&gt;
  &lt;li&gt;If BitLocker repeatedly requires the recovery key after rebooting (or fails to
the system test before starting encryption), try booting directly into Windows
from your UEFI BIOS boot menu (e.g. on a Dell, hit F12 during boot to bring up
the boot menu) rather than using Ubuntu’s Grub bootloader when booting
Windows. Inserting Grub into the boot sequence can mess with BitLocker.&lt;/li&gt;
  &lt;li&gt;If you’re using your BIOS (rather than Grub) to boot Windows, you should also
be able to adjust the boot order in your BIOS if you prefer to boot Windows by
default.&lt;/li&gt;
  &lt;li&gt;If you want to access the Windows BitLocker drive from Linux, this should be
possible with the recovery key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;additional-details&quot;&gt;Additional Details&lt;/h2&gt;

&lt;p&gt;If you’re comfortable with all the above, you might not need any additional
information. But I think the details below will help some people who either need
additional help with some steps, or are curious about some of the more advanced
details.&lt;/p&gt;

&lt;h3 id=&quot;bios-setup&quot;&gt;BIOS Setup&lt;/h3&gt;

&lt;p&gt;Before starting, ensure your computer is running the latest BIOS available. This
is important because an out-of-date BIOS can have bugs, and those bugs sometimes
affect things like UEFI, non-Windows operating systems, or other components
we’ll be touching.&lt;/p&gt;

&lt;p&gt;You should also ensure your BIOS &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boot List Option&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UEFI&lt;/code&gt;. While
you’re looking at BIOS settings, it’s worth noting that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TPM Security&lt;/code&gt; is
required for BitLocker in Windows.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;For this tutorial, your BIOS must support UEFI!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Most modern computers support this, but if yours doesn&apos;t this tutorial won&apos;t
work for you. You might consider these alternatives:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Installing only Linux with encryption using the graphical installer.&lt;/li&gt;
  &lt;li&gt;OR Installing only Windows with encryption.&lt;/li&gt;
  &lt;li&gt;OR Dual-booting Linux and Windows without encryption using Ubuntu&apos;s graphical installer.&lt;/li&gt;
  &lt;li&gt;OR Finding another tutorial or figuring out how to do this with an MBR disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;h3 id=&quot;installing-windows&quot;&gt;Installing Windows&lt;/h3&gt;

&lt;p&gt;If Windows is already installed, just do the steps outlined above. The Ubuntu
installer is capable of shrinking the Windows partition, and I’d recommend you
just let it do so.&lt;/p&gt;

&lt;p&gt;If Windows isn’t installed, you should install Windows first. Leaving extra
space on the hard drive for Linux would be fine, but for simplicity I think it’s
easiest to just do a normal Windows install followed by the steps above. The
Windows partition will shrink quickly if it’s mostly empty. If you need a
Windows USB stick, the easiest way to make one is to use the &lt;a href=&quot;https://www.microsoft.com/software-download/&quot;&gt;Windows Media
Creation Tool&lt;/a&gt; from a computer
that’s already running Windows.&lt;/p&gt;

&lt;h3 id=&quot;ubuntu-usb-stick&quot;&gt;Ubuntu USB Stick&lt;/h3&gt;

&lt;p&gt;The easiest way to make a bootable Ubuntu USB stick is to &lt;a href=&quot;https://ubuntu.com/download/desktop&quot;&gt;download the
ISO&lt;/a&gt; and use the Startup Disk Creator on a
computer that’s already running Ubuntu. If you don’t already have a computer
running Ubuntu, you can use &lt;a href=&quot;https://etcher.balena.io/&quot;&gt;balenaEtcher&lt;/a&gt; to flash
the image to the USB stick.&lt;/p&gt;

&lt;h3 id=&quot;final-partition-scheme&quot;&gt;Final Partition Scheme&lt;/h3&gt;

&lt;p&gt;As a reference, here’s the final state of my hard drive after allowing the
installer to shrink my Windows partition and installing Ubuntu with LVM and
encryption. Note that your partition sizes might be different than mine based on
the size of your disk and the way you split it up, but the number of partitions
and their types should be the same. In the scheme below, partition (3) is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:&lt;/code&gt;
in Windows. Partitions (2) and (4) are used by Windows. Partition (5) is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt;
for Ubuntu and partition (6) is the encrypted LVM partition for Ubuntu.&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo sgdisk --print /dev/sda
Disk /dev/sda: 976773168 sectors, 465.8 GiB
 
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048          206847   100.0 MiB   EF00  EFI system partition
   2          206848          239615   16.0 MiB    0C01  Microsoft reserved ...
   3          239616       157882367   75.2 GiB    0700  Basic data partition
   4       975652864       976771071   546.0 MiB   2700  
   5       157882368       162076671   2.0 GiB     8300
   6       162076672       975652863   387.9 GiB   8300
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;why-encryption-is-important&quot;&gt;Why Encryption is Important&lt;/h2&gt;

&lt;p&gt;I began using encrypted storage on all my personal computers about ten years ago
after noticing that all the companies I’d worked for required it, and had good
reason to. Laptops get lost and stolen all the time. They’re high-value items
that are small and easy to carry. And when a thief gets your laptop, there’s
tons of valuable information on it that they can use or sell. Even if you use a
password to login, it’s easy for an attacker to gain access to your data if
your disk isn’t encrypted – for example, by using a live USB stick. And once
they have that data, they might get access to online accounts, bank statements,
emails, and tons of other data. For me, an encrypted hard disk isn’t optional
anymore – it’s a necessity.&lt;/p&gt;

&lt;h2 id=&quot;congratulations&quot;&gt;Congratulations!&lt;/h2&gt;

&lt;p&gt;Congratulations, you’ve created a dual-boot system with Ubuntu 24.04 and Windows
11 with all your data encrypted! I hope you found this guide useful, and I hope
full-disk encryption with Ubuntu becomes more popular and better-supported as a
result! If you’ve found this helpful and it’s saved you some time, perhaps you’d
like to &lt;a href=&quot;https://www.buymeacoffee.com/mkasberg&quot;&gt;buy me a coffee&lt;/a&gt;? No pressure,
just a little way to say thanks if you feel like it 😊. Also, I’d love to hear
from you if you validate these steps on different hardware (or if you notice any
problems with the guide itself). The best way to reach me is &lt;a href=&quot;https://twitter.com/mike_kasberg&quot;&gt;on
Twitter&lt;/a&gt; or via email.&lt;/p&gt;

&lt;p&gt;I also owe some thanks to &lt;a href=&quot;https://twitter.com/_C_King_123&quot;&gt;@_C_King_123&lt;/a&gt;, who
&lt;a href=&quot;https://twitter.com/_C_King_123/status/1791537395424219196&quot;&gt;pointed out to me&lt;/a&gt;
that it is possible to do an encrypted dual-boot installation with the Ubuntu
24.04 installer, as I first saw documented
&lt;a href=&quot;https://techtalkblog.ch/ubuntu-24-04-lts-fde-alongside-windows-installation/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/dual-boot-encryption-24-04.jpg"/>
 </entry>
 
 <entry>
   <title>Better Related Posts in Jekyll Using AI</title>
   
   <link href="https://www.mikekasberg.com/blog/2024/04/23/better-related-posts-in-jekyll-using-ai.html" type="text/html" title="Better Related Posts in Jekyll Using AI"/>
   
   <published>2024-04-23T15:30:00-06:00</published>
   <updated>2024-04-23T15:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2024/04/23/better-related-posts-in-jekyll-using-ai</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2024/04/23/better-related-posts-in-jekyll-using-ai.html">&lt;p&gt;On any blog, it’s really common to link to related posts near the end of an
article. It keeps readers on your website by linking to another post they might
be interested in, and it can help with SEO. For a long time, Jekyll has provided
&lt;a href=&quot;http://jekyllrb.com/docs/variables/#site-variables&quot;&gt;site.related_posts&lt;/a&gt; as a
convenient way to link to related posts. Unfortunately, the default
implementation just lists the ten most recent posts (which might not actually be
that closely related). Jekyll does offer a better implementation using Latent
Semantic Indexing (LSI) with
&lt;a href=&quot;https://jekyll.github.io/classifier-reborn/&quot;&gt;classifier-reborn&lt;/a&gt;. This plugin
tries to populate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;related_posts&lt;/code&gt; with posts that are actually related, but it’s
difficult to install and doesn’t always produce the best results.&lt;/p&gt;

&lt;p&gt;I was aware of many of the problems and challenges with the existing approach in
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;classifier-reborn&lt;/code&gt; since I &lt;a href=&quot;/blog/2022/07/12/how-i-updated-jekyll-classifier-reborn-for-ruby-3.html&quot;&gt;updated classifier-reborn for Ruby 3&lt;/a&gt; back in 2022.
Classifier-reborn isn’t too bad (and was useful enough to me that I updated it
for Ruby 3), but I’ve wished for a long time it was easier to use and produced
better results. More recently, with the rapid growth of ChatGPT and LLMs, I’ve
been wanting to try a personal project that could make use of modern AI. I read
an interesting blog post from Hacker News about how &lt;a href=&quot;https://bawolf.substack.com/p/embeddings-are-a-good-starting-point&quot;&gt;Embeddings are a good
starting
point&lt;/a&gt;, and
it occurred to me that embeddings from OpenAI would be a great way to get better
related post functionality into Jekyll! I decided to try building &lt;a href=&quot;https://github.com/mkasberg/jekyll_ai_related_posts&quot;&gt;my own Jekyll
plugin for related posts&lt;/a&gt;
to see if AI would work well here, and I got some really great results!&lt;/p&gt;

&lt;h2 id=&quot;solution-design&quot;&gt;Solution Design&lt;/h2&gt;

&lt;p&gt;OpenAI offers an &lt;a href=&quot;https://platform.openai.com/docs/guides/embeddings&quot;&gt;Embeddings
API&lt;/a&gt; that’s very easy to
use. You provide some input text, and the API returns a vector embedding from
OpenAI’s LLM. These vectors can be compared outside an LLM using simple vector
similarity algorithms like cosine similarity. With a little searching, I found
a &lt;a href=&quot;https://github.com/asg017/sqlite-vss&quot;&gt;SQLite plugin&lt;/a&gt; that extends SQLite with
vector database functionality. This seemed like a great solution to me! I could
cache vector embeddings from OpenAI in a small SQLite database, and use the same
database (with the plugin) to perform a vector similarity search to find related
posts!&lt;/p&gt;

&lt;p&gt;Jekyll has a rich plugin ecosystem, and provides hooks that plugins can use to
integrate with various steps in the build process. I designed my plugin as a
&lt;a href=&quot;http://jekyllrb.com/docs/plugins/generators/&quot;&gt;generator plugin&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Generators run after Jekyll has made an inventory of the existing content, and
before the site is generated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When my plugin gets called during site generation, the first thing it does is
ensure that we’ve cached a vector embedding for every post. If any posts are
missing an embedding, we make a request to the OpenAI API to get it. Then, with
embeddings for all posts in our database, we perform a vector similarity search
for each post in SQLite, making this data available to use in the post itself
(via a Liquid template) as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ai_related_posts&lt;/code&gt; in the page data. The approach is
very simple, and turned out to work great!&lt;/p&gt;

&lt;h2 id=&quot;accuracy&quot;&gt;Accuracy&lt;/h2&gt;

&lt;p&gt;One of my biggest concerns when designing this plugin was accuracy. Could I
design a solution that would produce better results than classifier-reborn?&lt;/p&gt;

&lt;p&gt;I think I was successful. Let’s look at some examples from my own blog,
&lt;a href=&quot;https://www.mikekasberg.com&quot;&gt;mikekasberg.com&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;example-1&quot;&gt;Example 1&lt;/h3&gt;

&lt;p&gt;Here’s an example from one of my recent blog posts, &lt;a href=&quot;/blog/2023/07/17/3d-printing-map-figurines-with-gps.html&quot;&gt;3D Printing Map Figurines
with GPS&lt;/a&gt;. The
table below shows the related posts produced by each approach.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;classifier-reborn&lt;/th&gt;
      &lt;th&gt;ai_related_posts&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2023/01/11/3-months-of-3d-printing.html&quot;&gt;3 Months of 3D Printing&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2023/04/27/3d-printing-the-strava-logo.html&quot;&gt;3D Printing the Strava Logo&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu (20.04 - 23.10) and Windows (10 or 11) with Encryption&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2023/01/11/3-months-of-3d-printing.html&quot;&gt;3 Months of 3D Printing&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2024/03/15/i-did-my-own-taxes-by-hand-and-you-can-too.html&quot;&gt;I Did My Own Taxes By Hand (and You Can Too!)&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2023/03/22/3d-printing-with-openscad.html&quot;&gt;3D Printing with OpenSCAD&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;It seems obvious to me that the posts on the right are much better related
posts. All the posts generated by ai_related_posts are about 3D printing. Very
relevant! In contrast, classifier-reborn only produced one related post about 3D
printing. I’m sure there’s some reason the LSI approach thought the posts on the
left might be related, but they seem somewhat random!&lt;/p&gt;

&lt;h3 id=&quot;example-2&quot;&gt;Example 2&lt;/h3&gt;

&lt;p&gt;Let’s look at another example, &lt;a href=&quot;/blog/2023/12/22/home-wifi-upgrades-adding-an-access-point-with-wired-backhaul.html&quot;&gt;Home WiFi Upgrades: Adding an Access Point with Wired Backhaul&lt;/a&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;classifier-reborn&lt;/th&gt;
      &lt;th&gt;ai_related_posts&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2024/04/12/learning-to-solder-a-wled-project.html&quot;&gt;Learning to Solder: A WLED Project&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2023/07/28/how-to-test-and-optimize-your-home-wifi.html&quot;&gt;How to Test and Optimize Your Home Wifi Coverage&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2023/07/28/how-to-test-and-optimize-your-home-wifi.html&quot;&gt;How to Test and Optimize Your Home Wifi Coverage&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2022/12/22/i-installed-my-own-coax-cable.html&quot;&gt;I Installed My Own Coax Cable for My Internet Modem (and You Can Too)&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2024/02/04/buying-used-computers-a-story-and-some-advice.html&quot;&gt;Buying Used Computers: A Story and Some Advice&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2024/04/12/learning-to-solder-a-wled-project.html&quot;&gt;Learning to Solder: A WLED Project&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;These results are interesting because two out of the three results are the same
(but in a different order), and I don’t think either set of results is &lt;em&gt;bad&lt;/em&gt;.
But I &lt;em&gt;do&lt;/em&gt; think the results on the right are, again, definitely better than
those on the left. The most closely related article to “Adding an Access Point
with Wired Backhaul” is “How to Test and Optimize Your Home Wifi Coverage”, and
the AI plugin got this right! I also think the article about installing coax
cable is indeed the next most closely related article, and the AI plugin got
this right too while classifier-reborn missed this completely!&lt;/p&gt;

&lt;p&gt;With evidence like the above, it seems clear to me that my AI plugin’s producing
good results – much more accurate than classifier-reborn, which I was
previously using on my blog. I could find many other examples where the AI
approach produced better results, but I think examples above illustrate the
point.&lt;/p&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;Another concern I had was performance. LSI is compute intensive, but when it
uses computing libraries like Numo (a Ruby interface for LAPACK) it works
fairly quickly. A Jekyll build on my machine using LSI with Numo averages about
3.5 seconds.&lt;/p&gt;

&lt;p&gt;When I tested my AI plugin, the first Jekyll site build was very slow. But this
was expected since it needed to fetch embeddings for every post for the first
time. My blog currently has 84 posts, and this took 40 seconds (or about 0.5s
per post). While not ideal, this is fine for a first run, and because we cache
the embeddings the performance is much better after that. Any subsequent run
takes about 4 seconds total. (Even with the cached embeddings, we perform a
vector similarity search for each post on every build, for now.) So the
performance isn’t &lt;em&gt;faster&lt;/em&gt; than LSI, but it’s at least not noticeably slower.
At only 4 seconds for a full site build with nearly 100 posts, I’m happy with
the performance and it feels like a win to get better results than
classifier-reborn in about the same amount of time!&lt;/p&gt;

&lt;h2 id=&quot;cost&quot;&gt;Cost&lt;/h2&gt;

&lt;p&gt;Classifier-reborn is a open source plugin, so it’s free to use. My AI related
posts plugin is also open source and free to use, but requires calling OpenAI
APIs, which aren’t free. Fortunately, since we only need to call the API once
per post and we cache the results, the costs are minimal. I paid $5 for OpenAI
API access to get off the free plan and get higher rate limits. It turns out I
might not have even needed to do this – I got embeddings for all 84 posts in my
blog for $0.00 in API fees, using 1,277 tokens on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-embedding-3-small&lt;/code&gt;
model. So while you do need an API key, it doesn’t seem like cost will be a
prohibitive factor to using the AI plugin. For most blogs, you can get
embeddings for all your posts from the OpenAI API for a few pennies!&lt;/p&gt;

&lt;h2 id=&quot;onward-&quot;&gt;Onward 🚀&lt;/h2&gt;

&lt;p&gt;I’m excited that relatively new AI technologies allowed me to build a plugin,
with relatively little code, that produces better related posts than the LSI
plugin that’s been used with Jekyll for a long time. And I’m excited to already
be using it to make the related posts on my own blog better!&lt;/p&gt;

&lt;p&gt;The plugin is &lt;a href=&quot;https://github.com/mkasberg/jekyll_ai_related_posts&quot;&gt;open source on
GitHub&lt;/a&gt;, and I’d love to
see others start using it. I’d also like to collaborate to make it better! While
it already produces great results, I think there’s potential to make the results
even better, and to add integrations with other models and APIs besides OpenAI.
(The approach should work with any model that can produce an embedding vector.)
It’s exciting to see advancements coming out rapidly in the AI field and to
think about how we might use them in the future!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/better-related-posts-in-jekyll-using-ai.jpg"/>
 </entry>
 
 <entry>
   <title>Learning to Solder: A WLED Project</title>
   
   <link href="https://www.mikekasberg.com/blog/2024/04/12/learning-to-solder-a-wled-project.html" type="text/html" title="Learning to Solder: A WLED Project"/>
   
   <published>2024-04-12T22:00:00-06:00</published>
   <updated>2024-04-12T22:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2024/04/12/learning-to-solder-a-wled-project</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2024/04/12/learning-to-solder-a-wled-project.html">&lt;p&gt;I’ve been thinking for a while that it would be fun to add some kind of
color-changing LED mood lighting to my home office. As I was researching
different products, I quickly stumbled across the &lt;a href=&quot;https://kno.wled.ge/&quot;&gt;WLED&lt;/a&gt;
project. WLED was immediately appealing to me because of my love for open source
software. It’s highly praised on various blogs and YouTube videos, and I was
confident that the software quality I’d get with WLED (and its ability to
integrate with things like apps and Home Assistant) would be better than almost
anything I could find off the shelf. I was excited to build something with WLED,
but I ran into a small stumbling block – most of the hardware that’s compatible
with WLED requires soldering electronics components together.&lt;/p&gt;

&lt;p&gt;Though I’ve been programming since &lt;a href=&quot;/blog/2017/12/04/my-first-program.html&quot;&gt;before I was ten&lt;/a&gt;, I never learned to solder or work with
electronics components. Sure, I’m comfortable changing the RAM in a laptop, but
that’s pretty much the extent of my hardware knowledge. I’ve always felt a
little bit limited in the kinds of projects I could tackle because I’ve always
been too intimidated to work with electronics components (like LEDs, GPIO,
Arduinos, etc) and DC circuits. Looking back, I wish I’d taken some kind of
hardware-focused electronics course in high school or college to give me the
foundational knowledge I need to explore more on my own. In any case, I’ve
learned over the years that the best way to get comfortable with something is to
just try to do it. You might make some mistakes along the way, but the knowledge
gained far outweighs the mistakes and there’s no better way to learn something!
So I decided to dive in head-first and figure out how to make a WLED-compatible
LED strip, teaching myself to solder along the way.&lt;/p&gt;

&lt;h2 id=&quot;research--shopping&quot;&gt;Research &amp;amp; Shopping&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/learning-to-solder/parts.jpg&quot; alt=&quot;Parts for building an LED strip and controller&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With little knowledge of DC circuits, I began reading &lt;em&gt;a lot&lt;/em&gt;, and started
shopping for parts. The first thing I shopped for, naturally, was an LED strip.
This in itself is a confusing task. I knew I wanted an addressable LED strip
since the full power of WLED requires changing the color of each LED
independently for cool effects. The &lt;a href=&quot;https://kno.wled.ge/basics/compatible-led-strips/&quot;&gt;WLED
Compatibility&lt;/a&gt; page lists
&lt;em&gt;more than a dozen&lt;/em&gt; different kinds of addressable LED strips, but doesn’t link
anywhere and doesn’t really provide any info about which ones to use or where to
buy them. After a couple days of research, I settled on WS2812Bs – mainly
because they seemed very common.  They also happen to be the underlying tech for
Adafruit’s NeoPixels. Though I wasn’t planning to work with NeoPixels
specifically, I found the &lt;a href=&quot;https://learn.adafruit.com/adafruit-neopixel-uberguide/the-magic-of-neopixels&quot;&gt;NeoPixel
Überguide&lt;/a&gt;
very informative as I was teaching myself about LEDs.&lt;/p&gt;

&lt;p&gt;You can type “WS2812B” into Amazon and find a variety of different sellers.
Fundamentally, all WS2812Bs are the same, but different brands and sellers might
have varying degrees of quality. Perhaps more important than the brand is
the water resistance rating (&lt;a href=&quot;https://en.wikipedia.org/wiki/IP_code&quot;&gt;IP&lt;/a&gt;), LED
spacing, and length. The LED strips on Amazon aren’t a terrible deal, but you
can buy the exact same thing for less on AliExpress if you’re willing to wait
for slower shipping. I bought a 5m WS2812B strip with 60 LEDs/m, IP30 for $9.15 with
$1.00 shipping on AliExpress from &lt;a href=&quot;https://www.aliexpress.com/store/1100777574&quot;&gt;BTF
Lighting&lt;/a&gt; (which seems to have a
good reputation for quality LEDs). The same product would
have been about $20 on Amazon. I won’t use the full 5m, but you can cut these
anywhere and use the rest later.&lt;/p&gt;

&lt;p&gt;Now, you can’t buy an LED strip like this and just plug it into the wall. The
LED strip has leads for power and a data signal. I needed
a controller to run the WLED software and control the lights. Shopping for a
controller is probably &lt;em&gt;more confusing than shopping for an LED strip&lt;/em&gt;! The &lt;a href=&quot;https://kno.wled.ge/basics/compatible-controllers/&quot;&gt;WLED
Controllers Wiki&lt;/a&gt; lists more
than 35 different options with WLED pre-installed, as well as additional DIY
options. I skimmed through many of these options, but I found most of the
controllers to be confusing, poorly documented, overpriced, or unavailable. The
diagram at the top of the &lt;a href=&quot;https://kno.wled.ge/basics/compatible-controllers/&quot;&gt;WLED Controllers
Wiki&lt;/a&gt; showed a simple setup
with an ESP8266 dev board, and I began researching this option instead of a
pre-installed controller. If I’m going to use WLED and learn as I go, I might as
well get the full DIY experience! Much like LED strips, you can find ESP8266 dev
boards on Amazon, but I found them much cheaper on AliExpress. I ordered two
&lt;a href=&quot;https://www.aliexpress.us/item/3256805591350059.html&quot;&gt;WeMos D1 Mini ESP8266 dev
boards&lt;/a&gt; for $5.11 total.
(Ordering two got me free shipping!) I later found out that this board, the
WeMos D1 Mini, is &lt;a href=&quot;https://github.com/Aircoookie/WLED-Docs/blob/cc4921953035e59dcc6c9ccde91bde93145d1200/docs/basics/compatible-controllers.md?plain=1#L126&quot;&gt;Aircoookie’s
recommendation&lt;/a&gt;
(the maintainer of WLED) if you want to use an ESP8266 board, so I guess I did
my research well!&lt;/p&gt;

&lt;p&gt;Besides the dev board and LED strip, I also needed some some other parts and
tools. Electronics hobbyists might have this stuff laying around, but I didn’t
have &lt;em&gt;anything&lt;/em&gt; since this was my first foray into tinkering with electronics. I
bought some &lt;a href=&quot;https://www.aliexpress.com/item/2251832088900868.html&quot;&gt;18 AWG wire&lt;/a&gt; and a
&lt;a href=&quot;https://www.aliexpress.us/item/2251832624591733.html&quot;&gt;5V/6A power supply&lt;/a&gt; from
AliExpress. To calculate the size of power supply and wire that I’d need, I used
this &lt;a href=&quot;https://wled-calculator.github.io/&quot;&gt;WLED Power Calculator&lt;/a&gt;, and rounded up
where I was unsure, to be safe. The WLED Power Calculator shows diagrams that
include a fuse, but this &lt;a href=&quot;https://quinled.info/2019/02/18/use-fuses-for-increased-safety/&quot;&gt;QuinLED fuses
page&lt;/a&gt; suggested
fuses might not be necessary when the power produced by my power supply is close
to the power used by the system (since the power supply itself should act as a
fuse if the system draws too much current). For this relatively small project, I
opted not to add additional fuses to my circuit.&lt;/p&gt;

&lt;p&gt;As I was gathering all the supplies I’d need, I realized I didn’t really have a
good plan for connecting all the components. I’d originally imagined I’d just
solder some wires directly to the board, but doing so would make it difficult to
experiment and hard to fix any mistakes I made. I noticed that some people were
using breadboards in the &lt;a href=&quot;https://www.youtube.com/watch?v=ZFO_QOBG9Bs&quot;&gt;YouTube
videos&lt;/a&gt; I was watching, and I
decided to go with this approach myself after realizing I could buy a breadboard
for a couple dollars. Using a breadboard with jumper cables would allow me to
test things before making permanent connections. While learning about
breadboards, I also discovered the MB102 power module, which would give me a
great way to connect my power supply to the bread board and the rest of my
components. I bought a &lt;a href=&quot;https://www.aliexpress.us/item/3256801135286073.html?spm=a2g0o.order_list.order_list_main.5.43441802eHi6AM&amp;amp;gatewayAdapt=glo2usa&quot;&gt;MB102 breadboard
kit&lt;/a&gt;
from AliExpress that came with a small breadboard, jumper wires, and a MB102
power module, all for about $4.&lt;/p&gt;

&lt;p&gt;Running LED strips with WLED is a deep, deep rabbit hole. Over and over again,
I’d start feeling comfortable with my design, only to discover several new
factors I hadn’t previously considered. One such experience was learning about
voltage level shifters. While the basic wiring diagram with an ESP8266 dev board
on the &lt;a href=&quot;https://kno.wled.ge/basics/compatible-controllers/&quot;&gt;WLED Wiki&lt;/a&gt; omits
level shifters, my research suggested that LED strips with longer distances
between the LED strip and board could suffer from flickering (or just not work
at all) because the ESP8266 produces a 3.3V data signal but the LED expects a 5V
data signal. At short distances, the signal produced by the board is within the
tolerance and works fine, but at longer distances the voltage drop from the wire
can put the data signal strength out of tolerance. Fortunately, I also
discovered an easy solution to this problem that uses a &lt;a href=&quot;https://www.youtube.com/watch?v=ZFO_QOBG9Bs&amp;amp;t=765s&quot;&gt;sacrificial
pixel&lt;/a&gt;, and I decided to try
that solution before doing anything more complicated. (It turned out to work
great!)&lt;/p&gt;

&lt;p&gt;Having learned a lot about LED strips, I was feeling increasingly confident in
assembling working LEDs with all the right components, but I was nervous about
my ability to solder. Even with a bread board, I’d need to solder to connect
header pins to my D1 Mini, or to splice some wires together. I wanted to
practice soldering on a smaller project before working on my LEDs.&lt;/p&gt;

&lt;h2 id=&quot;learning-to-solder&quot;&gt;Learning to Solder&lt;/h2&gt;

&lt;p&gt;I spent a little bit of time learning about soldering irons, and I decided to
purchase a &lt;a href=&quot;https://pine64.com/product/pinecil-smart-mini-portable-soldering-iron/&quot;&gt;Pine64
Pinecil&lt;/a&gt;
This soldering iron was not only highly recommended, but also priced reasonably.
As an added bonus (for me), it runs open source firmware. In addition to the
soldering iron, I bought a &lt;a href=&quot;https://www.aliexpress.us/item/3256805768927263.html&quot;&gt;silicone work
mat&lt;/a&gt; from AliExpress and
some &lt;a href=&quot;https://www.amazon.com/dp/B071JHLT8Q&quot;&gt;MG Chemicals 60/40 Rosin Leaded
Solder&lt;/a&gt; from Amazon.&lt;/p&gt;

&lt;p&gt;I watched a couple YouTube videos &lt;a href=&quot;https://www.youtube.com/watch?v=6rmErwU5E-k&quot;&gt;like this
one&lt;/a&gt; to learn the
fundamentals of soldering. To find a small board to practice on, I typed “solder
practice board” into AliExpress and came across a practice board called a Weevil
Eye. For a few dollars, it included a little PCB, some LEDS, and other
electrical components to solder together for practice. (I later realized that
SparkFun, a US company, sells the &lt;a href=&quot;https://www.sparkfun.com/products/10723&quot;&gt;Weevil
Eye&lt;/a&gt; and I’d actually bought a
clone on AliExpress – I’d encourage you to buy from SparkFun if you want to try
this project yourself.) The practice board was exactly what I needed to try
soldering for the first time on a low-stakes project.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/learning-to-solder/soldering-weevil-eye.jpg&quot; alt=&quot;Soldering Weevil Eye&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My Weevil Eye turned out great! I learned most of what I know about soldering
from &lt;a href=&quot;https://www.youtube.com/watch?v=6rmErwU5E-k&quot;&gt;wermy’s YouTube Crash
Course&lt;/a&gt;, so I’d highly recommend it
if you want to learn to solder yourself. My connections were solid, looked
clean, and worked flawlessly! Soldering requires a little coordination and
practice, but I think anyone who’s motivated can learn to do it pretty quickly!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/learning-to-solder/weevil-eye-complete.jpg&quot; alt=&quot;Completed Weevil Eye&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;wled-with-an-esp8266-dev-board&quot;&gt;WLED with an ESP8266 Dev Board&lt;/h2&gt;

&lt;p&gt;With my practice project complete, and feeling much more confident about
soldering, I was excited to start my real project –
getting WLED working on an ESP8266 dev board with WS2812B LEDs! I installed WLED
to my D1 Mini dev board using the &lt;a href=&quot;https://install.wled.me/&quot;&gt;WLED Web
Installer&lt;/a&gt;. This was really easy, but I did need to
install a CH340 driver to get my computer to recognize the device. The online
WLED installer tool configures the WiFi connection for the device as part of the
installation, so right after installing the software I was able to visit my
device at its IP address and see the WLED interface. Seeing the WLED interface
on my computer confirmed that the D1 Mini was powered on, that WLED was running
correctly, and that the network was set up right! I was pleasantly surprised at
how painless the whole process was!&lt;/p&gt;

&lt;p&gt;With the WLED software installed on the D1 Mini, it was time to wire things up.
Even with a breadboard, some soldering was required. I soldered some header pins
to the D1 Mini so I could use it on the breadboard. I also soldered some wires
to a single LED (cut from my strip), and tinned
the other ends of the wires to use them with the breadboard. This LED would
ultimately become my sacrificial pixel, but I first wanted to use it to test my
setup with just a single LED. I attached everything to the breadboard and used
jumper wires to make the connections, setting up a circuit like the one
described on the &lt;a href=&quot;https://kno.wled.ge/basics/compatible-controllers/&quot;&gt;WLED
Wiki&lt;/a&gt;. It was really
exciting to turn this on and see my LED light up, confirming that I was
able to power the device using my 5V power supply (rather than a USB cable),
connect to the web interface via WiFi, and change the colors of my LED!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/learning-to-solder/working-led.jpg&quot; alt=&quot;An LED on a breadboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After confirming that my prototype worked with a single LED, the only thing left
to do was connect a full LED strip. I left my single LED in place as a
sacrificial pixel and soldered longer wires to its output to connect my full LED
strip. Everything works, and the LEDs look great along the back of my desk,
lighting up the wall! Now that I’ve confirmed the setup works, I’ll probably
buy some aluminum channels to preserve the longevity of my LEDs (by managing
heat) and increase the safety of the setup.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/learning-to-solder/led-strip.jpg&quot; alt=&quot;A working LED strip&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Overall, this was a really fun project! I was initially overwhelmed by
information as I discovered that LEDs and DC circuits are actually way more
complicated that I imagined, but it was fun to learn. I had a great time
learning to solder, and I feel more confident in my ability to tackle projects
like this in the future. But I also know I’ve barely scratched the surface of
working with DC electronics and I have a lot more to learn! Knowing what I know
now, I’d only recommend working with an ESP8266 dev board like I did if you have
a desire to build your own circuit from scratch (and learn a lot along the way).
If you just want the easiest solution to power and control an LED strip with
WLED, I think some pre-built hardware like the
&lt;a href=&quot;https://quinled.info/quinled-dig2go/&quot;&gt;dig2go&lt;/a&gt; from QuinLED is much easier (if
you can find it in stock), and I might go with something more like that on my
next project depending what my needs are. I love having WLED-powered LEDs at my
desk that I can control from my computer, and it’s fun to know that I built them
from scratch myself!&lt;/p&gt;

&lt;h2 id=&quot;bill-of-materials&quot;&gt;Bill of Materials&lt;/h2&gt;

&lt;p&gt;Want to build your own WLED setup? Here’s a list of the parts I ended up using.
Of course, some parts might be slightly different for your project – you should
run your own power calculations to determine the best size power supply and
wires, for example.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.aliexpress.us/w/wholesale-WeMos-D1-Mini.html&quot;&gt;WeMos D1 Mini&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.aliexpress.us/item/2251801850504415.html&quot;&gt;BTF Lighting WS2812B LED Strip, 5m 60 IP30&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.aliexpress.us/item/2251832088900868.html&quot;&gt;3 pin wire&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.aliexpress.us/item/2251832624591733.html&quot;&gt;5V Power Supply&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.aliexpress.us/item/3256801135286073.html&quot;&gt;White MB102 Breadboard Kit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/learning-to-solder-a-wled-project.jpg"/>
 </entry>
 
 <entry>
   <title>I Did My Own Taxes By Hand (and You Can Too!)</title>
   
   <link href="https://www.mikekasberg.com/blog/2024/03/15/i-did-my-own-taxes-by-hand-and-you-can-too.html" type="text/html" title="I Did My Own Taxes By Hand (and You Can Too!)"/>
   
   <published>2024-03-15T14:00:00-06:00</published>
   <updated>2024-03-15T14:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2024/03/15/i-did-my-own-taxes-by-hand-and-you-can-too</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2024/03/15/i-did-my-own-taxes-by-hand-and-you-can-too.html">&lt;p&gt;The US is one of the only countries in the world where everyone wastes a dozen
hours every year doing their taxes. (The IRS
&lt;a href=&quot;https://www.taxpayeradvocate.irs.gov/wp-content/uploads/2023/01/ARC22_MSP_Glance.pdf&quot;&gt;estimates&lt;/a&gt;
that it takes an individual &lt;strong&gt;13 hours&lt;/strong&gt; to prepare their taxes!!!) Like many
others, I’ve been using TurboTax to do my taxes for years. I’ve wanted for a
long time to try doing my own taxes, by hand, without tax preparation software,
but I always felt too intimidated by all the tax forms (and the sheer volume of
information) to actually do it, and didn’t know where to start. This year, I
finally did it!  And it actually wasn’t nearly as difficult as I imagined it to
be – it almost felt &lt;em&gt;easy&lt;/em&gt;. Once I got started, I got the hang of it very
quickly, and I successfully filed my own tax return this year, without using
TurboTax! (And in well &lt;em&gt;under&lt;/em&gt; 13 hours!)&lt;/p&gt;

&lt;h2 id=&quot;why-do-your-own-taxes&quot;&gt;Why Do Your Own Taxes?&lt;/h2&gt;

&lt;p&gt;It probably seems crazy to some people that I’d even consider doing my own
taxes. Why would anybody waste time doing their own taxes when it only costs
$50 to buy the software that does it for you? For me there are several reasons.
One is because I’m frugal, and I hate shelling out $50-$100 every year to pay
for Intuit TurboTax. Perhaps more importantly, &lt;strong&gt;Intuit is evil&lt;/strong&gt;, and I hate
paying them money that I know they’ll ultimately use to make my life worse.
Intuit &lt;a href=&quot;https://www.propublica.org/article/inside-turbotax-20-year-fight-to-stop-americans-from-filing-their-taxes-for-free&quot;&gt;lobbies
congress&lt;/a&gt;
to make filing taxes more difficult and they &lt;a href=&quot;https://www.propublica.org/article/turbotax-just-tricked-you-into-paying-to-file-your-taxes&quot;&gt;use dark
patterns&lt;/a&gt;
to trick users into paying for products they don’t need. Finally, I wanted to
experience doing my own taxes first-hand, to get a better understanding of how
easy or hard it actually is and to learn more about the process.&lt;/p&gt;

&lt;p&gt;It’s certainly possible to avoid using Intuit TurboTax without resorting to
doing your own taxes by hand, but most of the alternatives also have their
pitfalls. TurboTax &lt;em&gt;is&lt;/em&gt;, in my opinion, the easiest tax software to use, and if
you’re going to pay for tax software it’s important that it gets your taxes done
quickly and correctly. Still, there are some cheaper alternatives that come
close, like &lt;a href=&quot;https://www.taxhawk.com/&quot;&gt;TaxHawk&lt;/a&gt;.  But ultimately, any tax
preparation software company has the same incentives as Intuit to make it
difficult for people to do their own taxes without help. There’s some open
source tax software, like &lt;a href=&quot;https://ustaxes.org/start&quot;&gt;UsTaxes.org&lt;/a&gt;, that seems
like it &lt;em&gt;could be an option&lt;/em&gt;. But I never felt completely confident in the
accuracy of software like that, and it wasn’t nearly as user-friendly as other
options. (When the homepage starts with a list of the 10+ tax forms they
support, and I don’t even know what half those forms mean, that turns me off to
the product.) Ultimately, I realized that doing my taxes myself, by hand, with
the IRS forms was the best way to avoid paying for tax software while still
feeling confident in the accuracy of my taxes. And I’m happy to report that it
turned out to be much easier and faster than I anticipated, and I learned a
little along the way!&lt;/p&gt;

&lt;h2 id=&quot;could-you-do-your-own-taxes&quot;&gt;Could You Do Your Own Taxes?&lt;/h2&gt;

&lt;p&gt;If you’re reading this, I’m sure you’re wondering how difficult you’d find it to
do your own taxes, by hand, like I did. Let me assure you: If you’ve read this
far, you can! I’m not an accountant, but I &lt;em&gt;am&lt;/em&gt; a software engineer. So while I
don’t have formal training in accounting or taxes, I’ve always been pretty good at
math, and a big part of my job as a software engineer is figuring out how to
break down complex problems. But after actually doing my taxes by hand, I think
all that’s &lt;strong&gt;mostly irrelevant&lt;/strong&gt;. I’ll admit that doing your own taxes is
&lt;em&gt;complicated enough&lt;/em&gt; that some people couldn’t, or shouldn’t, or wouldn’t do it.
But if you’ve read this far into &lt;em&gt;a blog post about filing taxes&lt;/em&gt; I don’t think
that applies to you. I’m quite confident that most people could be successful at
filing their own taxes by hand with the IRS tax forms if they just knew where to
start, or had someone show them how to do it the first time. And that’s what I
intend to do here!&lt;/p&gt;

&lt;p&gt;Filing your own taxes doesn’t require any special skills in accounting, reading,
or math. In fact, I don’t think any math beyond addition, subtraction, and
multiplication is even required! Still, taxes can seem a little confusing.
Doing my own taxes felt a lot like a standardized test from my childhood:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;John has 4 apples, Sally has 6 oranges, and Bob has 7 bananas. If Bob has more
bananas than John has apples, enter how many oranges Sally has on the line
below. Otherwise, enter 0.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While that might seem ridiculous, I’m honestly not exaggerating that much. It’s
simple stuff, but you might need to read it twice to make sure you don’t get
confused. It feels a little bit like a puzzle or game, or maybe a riddle from
an escape room. And, if it helps with your motivation, I recommend thinking
about it that way!  You have all the information you need right in front of you,
you just need to read the instructions and enter the information on the right
line. And while the &lt;a href=&quot;https://www.irs.gov/pub/irs-pdf/i1040gi.pdf&quot;&gt;instructions&lt;/a&gt;
might seem overwhelming (yes, that’s a 114-page PDF), you don’t need to read
them all line by line. You can &lt;em&gt;ignore&lt;/em&gt; most of them, skim some of them, and read
&lt;em&gt;a page or two&lt;/em&gt; in detail when necessary. (Obviously, this isn’t legal advice.
LOL.)&lt;/p&gt;

&lt;p&gt;You might be worried about how long it would take to do your own taxes by hand.
Isn’t it much faster to just use TurboTax and be done with it? Actually, no, it
isn’t. At least not in my case. In previous years, I’ve typically spent about
2-3 hours doing my taxes in TurboTax, and that’s about how long it took me this
year to do my taxes by hand, without TurboTax. So TurboTax wasn’t actually any
faster for me.&lt;/p&gt;

&lt;h2 id=&quot;how-to-do-your-own-taxes&quot;&gt;How To Do Your Own Taxes&lt;/h2&gt;

&lt;p&gt;I did my own taxes using &lt;a href=&quot;https://www.freefilefillableforms.com&quot;&gt;Free File Fillable
Forms&lt;/a&gt;, and that’s what I’d recommend to
you too. This is &lt;strong&gt;not&lt;/strong&gt; tax software like TurboTax. It’s a service &lt;a href=&quot;https://www.irs.gov/e-file-providers/free-file-fillable-forms&quot;&gt;run by the
IRS&lt;/a&gt; that
basically gives you empty PDF forms you can type into, does a little bit of
light math for you, and lets you e-file so you don’t need to mail your federal
return.&lt;/p&gt;

&lt;h3 id=&quot;free-file-fillable-forms-overview&quot;&gt;Free File Fillable Forms Overview&lt;/h3&gt;

&lt;p&gt;After you create your account, Free File Fillable Forms will drop you onto a
page with a blank 1040. This is intimidating if you’ve never filled out a 1040
manually before – but don’t panic!&lt;/p&gt;

&lt;p&gt;The first thing you should know is that there’s a save button in the upper
right. Hit that early and often because it doesn’t auto-save and you don’t want
to lose your work! There’s also a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Do the Math&lt;/code&gt; button at the bottom. This
won’t do anything until you have some numbers, but you can hit it any time you
want. It will do most of the addition and multiplication for you!&lt;/p&gt;

&lt;p&gt;The next thing I want to draw your attention to is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Instructions For This
Form&lt;/code&gt; button at the bottom. Use this button any time you’re not sure what to
do! It’ll take you to an (enormous and intimidating) PDF, but don’t read the
whole PDF. Skip ahead in the PDF to the line number from the form, and read the
instructions for that line. Typically, this is a couple short paragraphs. Even
for the most complicated lines, it’s almost never more than a page. This
strategy helped me a ton in one or two places that were confusing.&lt;/p&gt;

&lt;p&gt;Finally, let’s talk about adding other forms. Most tax payers won’t &lt;em&gt;just&lt;/em&gt; file
a 1040, you’ll also have to attach other forms, like Schedule 1 for additional
income, Schedule A for itemized deductions, or Form 2441 for child care
expenses. You might be worried (like I was) that you don’t even know all the
forms you need, but don’t worry – we’ll get there in a bit. For now, you should
just know that there’s two ways to add forms and they both do basically the same
thing. On the form 1040, starting around line 1e, you’ll see some blue &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add&lt;/code&gt;
buttons.  Clicking any of these buttons adds the form to calculate the
corresponding line.  (You might need a couple of these but probably don’t need
most of them – they aren’t typically necessary when the line is 0 for you.) You
can also add forms using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add Form&lt;/code&gt; button in the bottom left. This does
the same thing, and is faster if you know which form you need but don’t know
where it shows up in the 1040.&lt;/p&gt;

&lt;h3 id=&quot;doing-your-own-taxes&quot;&gt;Doing Your Own Taxes&lt;/h3&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;Warning &amp;amp; Disclaimer&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;I am not a lawyer! Nor am I an accountant. I&apos;m just writing about my own
experience, and this is not legal advice. As always, do your own research and
consult with a professional if you need additional help.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Now that you have that quick overview of Free File Fillable Forms, let’s
actually start doing your taxes!&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Read about this year’s changes. Since we’ll be using last year’s tax return
as a reference, it’s important to know about anything that’s different this
year. The IRS summarizes changes in &lt;a href=&quot;https://www.irs.gov/pub/irs-pdf/p17.pdf&quot;&gt;Tax Guide For Individuals (Pub
17)&lt;/a&gt;. You can find a “What’s New”
section near the beginning, and this is worth skimming. It’s only about a page.
Aside from that, you can refer back to the IRS Tax Guide if you run into any
questions along the way. (It’s also worth noting that the instructions for
individual tax forms, like the &lt;a href=&quot;https://www.irs.gov/pub/irs-pdf/i1040gi.pdf&quot;&gt;1040
Instructions&lt;/a&gt;, sometimes also
contain their own “What’s New” section. It’s probably worth skimming there too
if you notice it.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Find a copy of your tax return from last year. For most people, taxes are
roughly the same each year, so we’ll use this as a reference and sanity check.
As I did my taxes, I relied on cross-checking last year’s return (a PDF from
TurboTax in my case) more heavily than any other resource!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Gather all the tax forms you received in the mail. It’s best to have all of
these ready so you know which forms you have, and so you don’t need to stop what
you’re doing to look for the info you need later.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Begin filling out the top of Form 1040. This is the easy stuff like your name
and address!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When you get to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Income&lt;/code&gt; section of Form 1040 (line 1a), start
cross-checking last year’s return as you fill out data. Most lines will be very
similar from year to year. If you notice any big discrepancies, make sure you
understand why (e.g. because you made more money this year than last, or bought
a house, etc.). I noticed a discrepancy (compared to last year) in my own return
on Schedule 1, line 1, and found after reading the instructions more closely
(with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Instructions For This Form&lt;/code&gt; button) that I needed to do a
calculation for that line rather than just entering a raw number from my 1099-G.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Make sure you’ve entered data from all the tax forms you got in the mail
(W-2, 1099, etc). If you haven’t entered data from one, it’s a good sign you’re
missing something. (But note that there are a couple forms that don’t need to be
entered, like form 1095-C.) A quick Google search can tell you where to enter data
from the forms you received.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Do a final check. There are three ways we’re going to double-check our return.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Compare to last year’s return, as you’ve been doing all along.&lt;/li&gt;
      &lt;li&gt;Make sure you used all the tax forms you got in the mail. If you didn’t use
one of your tax forms, it might indicate you forgot something.&lt;/li&gt;
      &lt;li&gt;Gut check. Do your taxes seem correct to you, in the context of your life
this year? Did you owe significantly more or less than you expected? If so, try
to make sure you didn’t forget anything.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When all that’s done, you can e-file the return for free on Free File Fillable
Forms, using the second tab at the top. It will ask you to double-check a couple
numbers, and it will ask you for info to verify your identity and pay. It will
also run a few automated checks to find common errors (like missing required
data). If everything checks out, your return will be filed for free and you’re
done with your federal taxes!&lt;/p&gt;

&lt;p&gt;Unfortunately, this is only your federal tax return. You’ll have to figure out
the state return on your own. In most cases, that &lt;em&gt;should&lt;/em&gt; be easy since it
should use a subset of the info from your federal return. In my case, it was
very easy – Colorado offers &lt;a href=&quot;https://tax.colorado.gov/file-individual-income-tax-online&quot;&gt;free online tax
filing&lt;/a&gt;. No matter
where you live, a lot of the strategies above probably apply.&lt;/p&gt;

&lt;h2 id=&quot;taxes-done&quot;&gt;Taxes Done!&lt;/h2&gt;

&lt;p&gt;Using the method I outlined above, I was able to complete my own taxes in about
two hours, which is actually about how long it usually takes me with TurboTax. I
was surprised to find that it wasn’t actually much slower than TurboTax to
simply follow the instructions on the IRS tax forms. I’m incredibly glad I tried
doing my own taxes. Not only did I save &lt;strong&gt;some some money&lt;/strong&gt; by not paying for
TurboTax, I also think I now have a &lt;strong&gt;better understanding&lt;/strong&gt; of the tax system
than I ever had when I was using tax software. Back when I used TurboTax, if I
wanted to figure out how some life event might impact my taxes next year, I
wouldn’t have even known where to look for that info. Now, after getting a
little experience with how the IRS tax forms work, I’d know exactly how to find
the relevant section of the 1040 and look at the math for how additional income,
another child, or a new house would impact my taxes. If you’ve ever considered
doing your own taxes by hand, I &lt;strong&gt;highly recommend&lt;/strong&gt; giving it a go, and I hope
the outline of my experience above helps you feel less overwhelmed and gets you
off to a great start. I plan on doing my own taxes by hand for the foreseeable
future. Maybe if enough people do their taxes this way we can remove some
momentum from companies like Intuit and add some momentum to simplify the
process overall!&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Tutorials"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/i-did-my-own-taxes.jpg"/>
 </entry>
 
 <entry>
   <title>Buying Used Computers: A Story and Some Advice</title>
   
   <link href="https://www.mikekasberg.com/blog/2024/02/04/buying-used-computers-a-story-and-some-advice.html" type="text/html" title="Buying Used Computers: A Story and Some Advice"/>
   
   <published>2024-02-04T14:00:00-07:00</published>
   <updated>2024-02-04T14:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2024/02/04/buying-used-computers-a-story-and-some-advice</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2024/02/04/buying-used-computers-a-story-and-some-advice.html">&lt;p&gt;I’m a big fan of used computers! I often recommend them to friends and
relatives, and I’ve bought several myself. I’ve written in the past about &lt;a href=&quot;/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html&quot;&gt;the
best computer you can buy for $100&lt;/a&gt;, &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;my $500 developer
laptop&lt;/a&gt;, and &lt;a href=&quot;/blog/2021/08/12/dell-latitudes-are-great-laptops.html&quot;&gt;used Dell
Latitudes&lt;/a&gt;. But
recently, I had a sub-par experience with refurbished computers. Having gone
through that, I have some thoughts about the current state of the used computer
market and some advice about buying a used computer in 2024.&lt;/p&gt;

&lt;h2 id=&quot;the-dud&quot;&gt;The Dud&lt;/h2&gt;

&lt;p&gt;I’ll begin with a story, and this story actually picks up where I left off when
buying &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;my $500 developer laptop&lt;/a&gt;. I’d ordered a refurbished Latitude 7490 for $436. There was a minor problem
getting the right SSD, but I worked it out with the seller and felt happy overall
with my purchase. Still, the “refurbished” quality of this computer was pretty
poor when I received it. When I installed my new SSD, I noticed that the
previous SSD had been installed incorrectly. (It functioned, but was &lt;em&gt;clearly&lt;/em&gt;
insecure and poor quality workmanship from whoever installed it.) Aside from
that, there was a big gash on the back of the computer. The computer was
described as follows.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Laptop is in good condition and may only show slight wear from previous use.
Overly worn parts such as touchpad and keyboard are replaced entirely. Laptop
screen will be free of defects. Laptop has been thoroughly tested and the
functionality of all components are guaranteed to be in perfect working order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Calling the gash on the laptop I received “slight wear from previous use” is a
big stretch, but I didn’t complain. I was able to look past it and still felt
like I paid a great price for the performance I got – which is what I primarily
care about.&lt;/p&gt;

&lt;p&gt;I’ve owned that 7490 for several months now, and I’ve been working sporadically
to troubleshoot a persistent and annoying problem I was having with the fan in
Linux. The fan seemed to run excessively and loudly, even when the computer was
cool. I assumed this was a problem with Linux controlling the fan, and I tried a
wide variety of solutions like BIOS updates,
&lt;a href=&quot;https://github.com/vitorafsr/i8kutils&quot;&gt;i8kutils&lt;/a&gt;, and other random GitHub
repositories for Dell fan control. Nothing seemed to work, and I got really
frustrated trying to figure out why. Finally, I figured out the problem using a
BIOS diagnostic utility. It found a problem with the fan!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/buying-used-computers-a-story-and-some-advice/alert.jpg&quot; alt=&quot;Diagnostic Alert: Fan is not responding&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was actually really happy to see this alert – it meant that my problem was
&lt;strong&gt;not&lt;/strong&gt; a software problem with Linux controlling the fan, but was instead a
hardware problem with the fan itself! (This was good news because I now
understood the problem and why none of my previous solutions worked, so I could
figure out how to fix it.) Based on the symptoms, it seemed like the fan was
damaged in a way that allowed it to spin but prevented the speed from being
properly controlled.  The fan would start and stop, but would frequently run at
8K+ RPMs rather than slower, quieter speeds. This wasn’t a problem for
performance, but it did make the laptop run very loud.&lt;/p&gt;

&lt;p&gt;Though I was happy to understand the problem, I was bummed that it couldn’t be
fixed with software. The refurbished computer I’d bought several months earlier
had come with a slightly-damaged fan, and I didn’t notice (or at least didn’t
correctly diagnose the problem) for &lt;em&gt;more than three months&lt;/em&gt;. I &lt;em&gt;could&lt;/em&gt; try to
return it, but that seemed unlikely given the standard 30-day return policy. So
I was left with a 7490 laptop that not only had a bad gash on the back but also
a bad fan (possibly damaged at the same time, now that I think about it).&lt;/p&gt;

&lt;p&gt;I decided that I didn’t trust this 7490 for long-term use as my personal
computer, and though replacing the fan &lt;em&gt;might&lt;/em&gt; solve the problem, I didn’t want
to dive into that fix without knowing how difficult it would be, how expensive
it would get, or what else I would find.  I wanted to buy a different computer
with a working fan (and hopefully no cosmetic damage either). I considered a few
options, including buying a new $1,000+ laptop or a used ThinkPad T480 (as
&lt;a href=&quot;https://maxrozen.com/getting-your-own-good-enough-laptop-for-under-500&quot;&gt;recommended by Max
Rozen&lt;/a&gt;),
but ultimately decided to double down and try buying &lt;em&gt;another&lt;/em&gt; Latitude 7490,
hopefully with better luck this time.&lt;/p&gt;

&lt;h2 id=&quot;the-refurbished-market&quot;&gt;The Refurbished Market&lt;/h2&gt;

&lt;p&gt;I’m disappointed with the current state of the computer refurbishing industry
and the used computer market in general. Maybe I’ve just been lucky in the past,
but back in 2018 I bought a &lt;em&gt;beautiful&lt;/em&gt; refurbished e7450 from a seller on
Newegg. An i5-5300U with 8GB DDR3, three years old at the time, for $330.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/buying-used-computers-a-story-and-some-advice/e7450.jpg&quot; alt=&quot;Dell e7450 Laptop&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In total, I’ve bought at least five refurbished computers in the last ten years
(not all for myself), and this most recent experience with the 7490 was the only
&lt;em&gt;bad&lt;/em&gt; experience I’ve had. I &lt;em&gt;want&lt;/em&gt; refurbished computers to be a great,
reliable option, but it doesn’t feel like they are right now.&lt;/p&gt;

&lt;p&gt;As I was shopping for a replacement laptop for my slightly-broken 7490, I
thought harder about where to shop for a really clean used computer. I’ve had
great success with
&lt;a href=&quot;https://www.newegg.com/Laptops-Notebooks/SubCategory/ID-32&quot;&gt;Newegg&lt;/a&gt; in the
past, but I was disappointed with their selection of 7490s. In the past, I’ve
often bought “Grade A” refurbished machines, typically either used very lightly
or with replaced keyboards and trackpads, and almost no cosmetic damage. But this
time the available selection of refurbished 7490s was either “Grade B” or not
even graded. Buying a Grade A laptop (from a reliable seller) feels like getting
a great deal on a nearly-new machine with an open box. But buying a Grade B
laptop feels more like a crap shoot, buying a mystery machine that definitely
has some cosmetic damage, and might or might not have &lt;em&gt;bad&lt;/em&gt; cosmetic damage or
other problems. Since I didn’t find any Grade A 7490s available on Newegg, I
searched some other places.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.ebay.com/b/PC-Laptops-Netbooks/177/bn_317584&quot;&gt;Ebay’s&lt;/a&gt; used computer
selection is interesting. I saw some Ebay sellers listing refurbished
laptops, very similar to what I found on Newegg. Still, none of the available
7490s with the specs I wanted were marked as Grade A, and I didn’t feel good
about buying a B-grade refurbished laptop sight-unseen. But it occurred to me
that on Ebay, &lt;em&gt;I didn’t have to&lt;/em&gt;. Some Ebay sellers put many refurbished laptops
under one listing with a generic picture of a new machine.  But other sellers
simply list “used” laptops, and the pictures on Ebay are &lt;em&gt;actual pictures of the
machine you’re buying&lt;/em&gt;. &lt;strong&gt;This is great!&lt;/strong&gt; You can buy a computer with “light
cosmetic damage” and understand &lt;em&gt;before you buy it&lt;/em&gt; whether it has a scratch, a
gash, or a keyboard with all the letters worn off.&lt;/p&gt;

&lt;p&gt;There are also other advantages to buying a used computer on Ebay. Dell laptops
in particular have a “Service Tag” on a sticker on the bottom of the laptop. You
can type the service tag in at &lt;a href=&quot;https://www.dell.com/support/home/en-us&quot;&gt;Dell
Support&lt;/a&gt; to get a detailed description
of the original system Dell sold. Some listings have pictures of the service tag
on the bottom of the laptop, and some sellers even include the service tag in
the description. This is an &lt;strong&gt;amazing&lt;/strong&gt; tool for used computer shopping. You can
find product details omitted from the listing! Of course, the computers might
have been upgraded or modified since they were manufactured at Dell. It’s not
uncommon to see a computer that Dell sold with 8GB RAM listed on Ebay with 16GB
RAM – but now you know that it probably has non-OEM RAM in it. On the other
hand, if the Ebay RAM size matches what was sold by Dell, odds are it’s OEM RAM!
And some components are built into the motherboard and can’t be easily changed.&lt;/p&gt;

&lt;p&gt;As I was shopping for my replacement 7490, I knew to look for a couple specific
configuration options. Dell sells (sold, I guess) the 7490 with an option to
upgrade the USB-C port to Thunderbolt 3. They also offer ten different display
options with various resolution/camera/touch options, and two different battery
sizes. (I know all this because I read &lt;a href=&quot;https://www.dell.com/support/manuals/en-us/latitude-14-7490-laptop/latitude_7490_om/technical-specifications?guid=guid-bf51648f-378d-45af-818e-6931ba59d73e&amp;amp;lang=en-us&quot;&gt;Dell’s 7490 Tech
Specs&lt;/a&gt;
while doing research before shopping for this laptop.) Sellers rarely document
all these options correctly in their online listing. I’ve even found a couple
Ebay listings that were listed &lt;em&gt;very cheaply&lt;/em&gt; because the seller seemed unaware
of the difference between a baseline 7490 and a 7490 &lt;em&gt;with Thunderbolt 3 and a
60 Whr battery&lt;/em&gt; – listing both as simply, “i7-8650U, 8GB DDR4, 256GB SSD”. If
sellers don’t know what they’re selling but you do, use that to your advantage!
Ultimately, I used Ebay to find a &lt;em&gt;gorgeous&lt;/em&gt; 7490 with the configuration I
wanted at an incredible price! But sadly I got &lt;em&gt;another&lt;/em&gt; dud.&lt;/p&gt;

&lt;h2 id=&quot;laptop-repair&quot;&gt;Laptop Repair&lt;/h2&gt;

&lt;p&gt;The new 7490 I bought seemed almost perfect, but I soon discovered a hidden
issue. The keyboard would intermittently miss key presses on some keys. Every
key worked, but some keys simply didn’t register if you pressed them too
quickly. (Apparently this can sometimes happen after spilling something on the
computer.) It was incredibly annoying, and made fast typing nearly impossible. I
considered returning the laptop, but decided against it for two reasons. First,
it might have been difficult to prove the laptop was defective since the
keyboard problem was intermittent. Second, and more importantly, I was now in
possession of &lt;em&gt;two&lt;/em&gt; Dell 7490s, each with minor but &lt;em&gt;different&lt;/em&gt; problems. My
Ebay laptop had a &lt;em&gt;pristine&lt;/em&gt; chassis and a working fan! It was spotless other than
the keyboard problem, and I didn’t want to let go of the laptop &lt;em&gt;knowing&lt;/em&gt; it
worked flawlessly other than the keyboard. I decided to keep the laptop and
combine all the best parts from my two systems. Doing so involved a fairly
complex keyboard swap to move the good keyboard into the best chassis, but it
was worth it to me! (And it was kind of fun.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/buying-used-computers-a-story-and-some-advice/keyboard-swap.jpg&quot; alt=&quot;Swapping a keyboard to a different laptop chassis&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I also rebuilt the system with the bad fan and keyboard – it runs Windows fine,
and I plan to sell it on Ebay, but I’ll explicitly call out the known issues
with the fan and keyboard.  In doing so, I’ll take a hit on the price (selling
for less than what I paid), but I just consider that part of the price I paid
for the pristine system I put together.&lt;/p&gt;

&lt;h2 id=&quot;advice-for-buying-a-used-laptop-in-2024&quot;&gt;Advice for Buying a Used Laptop in 2024&lt;/h2&gt;

&lt;p&gt;This whole experience made me realize a few things about the refurbished
computer market. First of all, I still think refurbished computers are a great
deal, and I’d still buy a Grade A refurbished machine from a reputable seller,
even without pictures of the machine I’m buying (as long as it’s returnable).
&lt;strong&gt;But&lt;/strong&gt;, for any lower-grade machine, from now on I’ll want to see pictures &lt;em&gt;of
the machine I’m actually buying&lt;/em&gt; before I buy it. And in any case, I’ll run some
diagnostic tests right after getting the machine. I’m tired of people marking
beat-up computers as “Grade B Refurbished” without actually doing anything to
them besides wiping the drive. I’m tired of not knowing what I’m getting because
they sell hundreds of different used computers in varying degrees of “cosmetic
damage” under the same listing. As one way for buyers to get more info, I do
think service tags and similar serial numbers or labels are an excellent way to
validate the original config of used laptops, and I’d prefer to buy from any
listing that includes them. I hope it’ll become much more common for sellers and
marketplaces to include this kind of info in their listings.&lt;/p&gt;

&lt;p&gt;It’s also worth talking about batteries. Rechargeable batteries go bad after
several years of use, so you should expect that the battery in any used computer
you buy is nearing the end of it’s useful life. Sometimes, you’ll get lucky.
Maybe the previous owner only ever used their laptop plugged in and the battery
is excellent. More often, you’ll get a battery that holds a 1-hour charge on a
good day. Some sellers will show battery health screenshots, which are somewhat
reliable but not always exactly correct. Other sellers might replace the battery
entirely, which is great if it’s replaced with a new OEM battery and otherwise a
bit of a crap shoot. Ultimately, you should either be okay with the idea of a
less-than-perfect battery, or be prepared to buy an OEM battery yourself
if you get a dud.&lt;/p&gt;

&lt;p&gt;Based on everything I learned through this experience, here are some tips for
buying a used computer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Decide what you want. I often steer people toward business grade laptops
like Dell Latitudes or Lenovo ThinkPads, but anything you want is fine. Most
importantly, at this stage, make sure you look at the tech specs for the
computer you want to buy and understand all the configurations it was sold with.
The last thing you want is to think you’re getting a great deal only to find
that you bought a computer with 1366x768 resolution instead of 1080p.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Look locally. This is typically a long-shot, but if you can buy used laptops
from your work, or your friend’s work, or from your uncle Bob, you can be much
more confident in the quality than you would be buying the same thing over the
internet. Just make sure you’re buying something you actually want (or getting
an amazing deal), and not just buying a relative’s old junk.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Check &lt;a href=&quot;https://www.newegg.com/Laptops-Notebooks/SubCategory/ID-32&quot;&gt;Newegg&lt;/a&gt;’s
used/refurbished marketplace. I &lt;em&gt;am&lt;/em&gt; a little wary of buying refurbished from
Newegg, but I’d still start my search there. I still think Newegg has the best
search tools (for CPU/RAM/SSD/resolution/etc) and the most accurate listings.
This will give you a feel for your price point. I probably &lt;em&gt;wouldn’t&lt;/em&gt; buy a
B-grade refurbished laptop without photos from Newegg, but I think I’d still buy
a Grade A refurbished machine from a highly-rated seller.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Check &lt;a href=&quot;https://www.ebay.com/b/PC-Laptops-Netbooks/177/bn_317584&quot;&gt;Ebay&lt;/a&gt;.
Specifically, look for used computers with actual photos of the computer you’re
buying. If possible, look for a service tag (or the equivalent from a different
manufacturer) and confirm the specs. If you can find something you want on Ebay,
with pictures of the actual computer you’re buying, with detailed and accurate
specs, at a reasonable price, buy it!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Consider combining two broken laptops like I did. This is only a viable option
if you’re confident you can work on the laptop hardware (watch a YouTube video
first).  But you might get a great deal by, for example, buying one laptop with
a broken screen and another of the same model with a bad motherboard, but good
screen.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;After receiving your laptop, upgrade the BIOS and drivers, and run some
diagnostic tests. Make sure everything works right when you receive it so you
can return it if there’s a problem.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope that helps you shop for your next new, used computer! Despite my recent
troubles with used computers, I found a solution and ended on a good note! (I’m
writing this blog post on my reassembled 7490.) And I’ll probably buy more used
computers in the future.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/buying-used-computers-a-story-and-some-advice.jpg"/>
 </entry>
 
 <entry>
   <title>Scalaing Challenge Leaderboards for Millions of Users</title>
   
   <link href="https://medium.com/strava-engineering/scaling-challenge-leaderboards-for-millions-of-athletes-9ab09ef01381" type="text/html" title="Scalaing Challenge Leaderboards for Millions of Users"/>
   
   <published>2024-01-18T10:00:00-07:00</published>
   <updated>2024-01-18T10:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2024/01/18/scaling-challenge-leaderboards-for-millions-of-athletes</id>
   
   <content type="html" xml:base="https://medium.com/strava-engineering/scaling-challenge-leaderboards-for-millions-of-athletes-9ab09ef01381">&lt;p&gt;&lt;a href=&quot;https://www.strava.com/challenges&quot;&gt;Strava challenges&lt;/a&gt; offer a fun way for
athletes to compete against themselves and others! Back in 2020, our legacy
challenge leaderboard system was running into bottlenecks and scalability
problems on a regular basis, and we often found ourselves putting out fires to
keep the system stable. In late 2020 and early 2021, I worked on a project to
replace the old leaderboard system with a new one that could handle a much
larger number of athletes competing in challenges. This blog post is about that
project. I drafted most of this post when the project wrapped up in 2021, but
didn’t get it published before I went on paternity leave – and then I forgot
about it. I think the project was interesting and worth sharing, so I’m glad I
finally remembered my draft (three years later 🫣) and found some time to put in
the finishing touches and get it published! Enjoy!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="The Strava Engineering Blog"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/strava-blog/scaling-challenge-leaderboards-for-millions-of-users.jpg"/>
 </entry>
 
 <entry>
   <title>Home WiFi Upgrades: Adding an Access Point with Wired Backhaul</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/12/22/home-wifi-upgrades-adding-an-access-point-with-wired-backhaul.html" type="text/html" title="Home WiFi Upgrades: Adding an Access Point with Wired Backhaul"/>
   
   <published>2023-12-22T15:00:00-07:00</published>
   <updated>2023-12-22T15:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/12/22/home-wifi-upgrades-adding-an-access-point-with-wired-backhaul</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/12/22/home-wifi-upgrades-adding-an-access-point-with-wired-backhaul.html">&lt;p&gt;When we bought our house several years ago, our home wifi consisted of a cable
modem and wireless router on the floor of our living room (where we happened to
have a coax outlet). Over time, I’ve made a few improvements! Last year, I
&lt;a href=&quot;/blog/2022/12/22/i-installed-my-own-coax-cable.html&quot;&gt;installed my own coax cable&lt;/a&gt; so I could move our internet
equipment out of the living room. This was a great improvement overall, but led
to bad coverage and slow speeds on my deck. To try to fix that, I got an
&lt;a href=&quot;https://www.tp-link.com/us/home-networking/range-extender/re450/&quot;&gt;RE-450&lt;/a&gt; wifi
range extender. The range extender improved the wifi on the deck, but didn’t
work seamlessly with the rest of my network. So I planned some more
improvements. I just finished installing some Cat-6 ethernet cable so I could
add a wifi access point with a wired backhaul. I want to write this blog about
how I did it, what kind of performance I got, and what I learned along the way.
Maybe you can try something similar if you have wifi coverage problems in your
own home!&lt;/p&gt;

&lt;h2 id=&quot;wifi-problems&quot;&gt;WiFi Problems&lt;/h2&gt;

&lt;p&gt;To get a thorough understanding of the problems I was trying to solve, I think
it’s important to understand a little about the layout of my house. We have a
mostly-finished basement with an unfinished 10’x10’ mechanical room (with our
furnace, water heater, and storage). Last year, I &lt;a href=&quot;/blog/2022/12/22/i-installed-my-own-coax-cable.html&quot;&gt;moved my modem and wifi
router&lt;/a&gt; to this
mechanical room, right next to my basement office. Our main level has a kitchen
and a TV room, and the internet was great there. Our second story has only
bedrooms. The internet was just okay in the master bedroom. It worked, but it
was noticeably slower than other parts of the house. The other bedrooms were
worse, with barely-working wifi. Our deck had similarly bad wifi, with the
signal dropping intermittently.&lt;/p&gt;

&lt;p&gt;I tried to fix these problems several months ago with the range extender.
Perhaps it was just a problem with this particular model, but I was never able
to get the range extender working well. Whenever I’d set it up to use the same
SSID as my primary wifi network, clients would seem to get mixed up and
connections would drop. Eventually, I gave up on using the same SSID. The range
extender &lt;em&gt;did&lt;/em&gt; work on a separate SSID, and I used it this way to get wifi on my
deck. Unfortunately, though the range extender improved the overall coverage
area, the speed was still mediocre, and I didn’t like the need to change wifi
networks on the deck.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/access-point-with-wired-backhaul/wireless-backhaul.png&quot; alt=&quot;Installing Cat-6 cable through a wall&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;planning-a-solution&quot;&gt;Planning a Solution&lt;/h2&gt;

&lt;p&gt;I suspected the reason I was experiencing dropped connections had to do with all
the traffic between my computer, the range extender (with wireless backhaul),
and my primary router. I also knew that (at least in theory) it was possible to
set up a seamless wireless network with multiple access points, where clients
would automatically choose the best access point – this is how corporate
networks have great reception throughout a huge office building. A major
difference between my range extender and those corporate networks was the
“backhaul” (connection back to the primary router). My range extender used a
wireless backhaul (because homeowners love the simplicity of just plugging in a
device to fix their problems, even if it doesn’t work that well), but
professional networks use wired backhaul for better performance and reliability.
As far as I know, it’s very uncommon for a “professional” wifi network to
use a wireless “mesh” or range extenders – it just isn’t cost-effective or
practical compared to the simplicity of access points with wired backhaul.
I decided to try setting up an access point with wired backhaul on my home
network.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/access-point-with-wired-backhaul/wired-backhaul.png&quot; alt=&quot;Installing Cat-6 cable through a wall&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, my range extender (an
&lt;a href=&quot;https://www.tp-link.com/us/home-networking/range-extender/re450/&quot;&gt;RE-450&lt;/a&gt;) can
also function in access point mode! All I needed to do was run an ethernet cable
to it! This is easier said than done. Our home isn’t wired with ethernet, and
with a mostly-finished basement we have very limited access to run cables
through the walls (since we can’t just fish them down through an unfinished
basement). I needed to figure out where I could put a wifi access point and run
ethernet to it. After mulling over the problem for several weeks, an idea
occurred to me to run ethernet &lt;strong&gt;through our garage&lt;/strong&gt;. Ethernet cable is low voltage,
so it can be exposed (and it wouldn’t bother me to have a wire along the wall of
my garage; we already had a couple others). My garage shares a small section of
wall with my basement mechanical room. My garage has an available power outlet
(which is necessary since my RE-450 isn’t PoE). And, luckily, my garage is
somewhat centrally located near the part of the house with bad coverage – under
my master bedroom and near the deck. I decided to run a Cat-6 ethernet cable
from the wifi router in my basement mechanical room to a wifi access point on
the far side of our garage!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/access-point-with-wired-backhaul/installing-cable.jpg&quot; alt=&quot;Installing Cat-6 cable through a wall&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;installing-cat-6-cable&quot;&gt;Installing Cat-6 Cable&lt;/h2&gt;

&lt;p&gt;I had very limited experience running ethernet cable. (Though I did have an
“internship” fifteen years ago terminating patch panels.) But coming off a
success &lt;a href=&quot;/blog/2022/12/22/i-installed-my-own-coax-cable.html&quot;&gt;installing my own coax cable&lt;/a&gt; last year, I decided to give it a
go. I opted for Cat-6 cable over Cat-5. For about 30% difference in
price, it made sense to me to be more future-proof so I don’t need to rewire
this in five years. I went to Home Depot and bought my supplies:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.homedepot.com/p/Southwire-100-ft-Blue-23-4-Solid-CU-CAT6-CMR-Riser-Data-Cable-56918943/202316437&quot;&gt;100 ft Cat-6 twisted pair
cable&lt;/a&gt; ($32)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.homedepot.com/p/Klein-Tools-Twisted-Pair-Installation-Tool-Set-with-Zipper-Pouch-VDV026-212/304087142&quot;&gt;Klein Tool
Set with crimper&lt;/a&gt;
($50)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.homedepot.com/p/IDEAL-CAT-6-Single-Piece-Modular-Plug-25-Pack-85-367/308990482&quot;&gt;RJ-45 Cat-6
plugs&lt;/a&gt; ($1/ea)&lt;/li&gt;
  &lt;li&gt;(Optional) &lt;a href=&quot;https://www.homedepot.com/p/Klein-Tools-LAN-Explorer-Data-Cable-Tester-with-Remote-VDV526-100/309925931&quot;&gt;Klein LAN Explorer cable
tester&lt;/a&gt;
($30)&lt;/li&gt;
  &lt;li&gt;(Optional) &lt;a href=&quot;https://www.homedepot.com/p/Gardner-Bender-1-4-in-Polyethylene-Clip-On-Category-3-and-5-Data-Cable-Staple-Blue-100-Pack-PTP-100BX/315205659&quot;&gt;Data cable staples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I bought the Klein Tool set above, though I didn’t need the punch tool since
I wasn’t installing any jacks. I also opted for the traditional plugs rather than
pass-through connectors. Either are fine – just make sure your plugs match the
tool you use. At a fundamental level, all you need is Cat-6 cable, a plug for
each end, and a crimping tool to terminate the ends. Depending on your
particular needs, you might also want a drill (to route the cable through walls)
or data cable staples (to attach the cable to walls). I also bought a cable
tester to make sure I had a good connection to the plug with the ends in the
correct order, but this isn’t strictly necessary.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/access-point-with-wired-backhaul/terminating.jpg&quot; alt=&quot;Installing Cat-6 cable through a wall&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Terminating ethernet cable with a crimping tool might seem intimidating if
you’ve never done it before, but it was actually one of the easier parts of this
project! Terminating cable is nothing compared to figuring out how to route it
through walls to the right spot. Just watch a five-minute YouTube video and make
sure you put the wires in the correct order. If I figured this out, you can too!&lt;/p&gt;

&lt;h2 id=&quot;results-and-lessons&quot;&gt;Results and Lessons&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/access-point-with-wired-backhaul/access-point.jpg&quot; alt=&quot;Installing Cat-6 cable through a wall&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I &lt;a href=&quot;/blog/2023/07/28/how-to-test-and-optimize-your-home-wifi.html&quot;&gt;tested&lt;/a&gt;
the changes I made to my wifi network by running this SSH command (to time the
transfer of a 1GB file) from various points around my house before and after the
changes.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ssh 192.168.0.1 &apos;dd if=/dev/zero bs=1GB count=1 2&amp;gt;/dev/null&apos; | dd of=/dev/null status=progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My results are summarized in the table below. “Router” represents the wifi
router in my basement (without the range extender). “Wired AP” is my new wifi
access point with wired backhaul. And the deltas compare the new setup to the
previous setups.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Client Location&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Router&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Range Extender&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Wired AP&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Δ (vs Router)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Δ (vs Extender)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Basement Office&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;48 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;N/A&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;48 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1x&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;N/A&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Master Bedroom&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;28 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;27 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;55 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.9x&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Far Bedroom&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;14* MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;25 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;46 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;3.3x&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.8x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Deck&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2* MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;19 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;38 MB/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;19x&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2x&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p class=&quot;text-sm&quot;&gt;*Connection unstable&lt;/p&gt;

&lt;p&gt;I couple things jump out at me. It’s worth noting that while the range extender
improved the connection in areas where it was previously unstable, it didn’t
really make the connection any faster where the connection was already stable
(in the master bedroom). But the same RE-450 in access point mode is almost
twice as fast across the board to any area it can reach. (It also works great,
without dropping connections, on the same SSID is my primary wifi router. Yay!)&lt;/p&gt;

&lt;p&gt;I learned several things from this experience, and I think some of the lessons I
learned will apply to anyone thinking about how to improve their home wifi.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A range extender (without a wired backhaul) is a simple solution that &lt;em&gt;can&lt;/em&gt;
work to make wifi available where it wasn’t before. For example, wifi didn’t
work reliably on my deck without a range extender but did work with one. (I
needed to use a separate SSID; YMMV depending on your equipment.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;An access point with a wired backhaul is almost always better than a wireless
range extender. A wired AP doesn’t “waste” half its wireless bandwidth
communicating with the primary wireless router.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;My deck and master bedroom are roughly the same distance from my basement
wifi router. But the master bedroom achieved 28 MB/s while the deck only got
about 2 MB/s on the router alone. I think a big factor here is interference.
There are only a couple walls and floors obstructing the signal to the bedroom,
but the signal to the deck is obstructed (partially) by the ground and a
concrete foundation. So when planning your wifi, you might need access points
closer together than you think if there’s a lot of obstruction.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It’s worth knowing that while my ranger extender requires a power outlet even
in access point mode, this isn’t always necessary. If you don’t have a power
outlet available, some wireless access points can work via power over ethernet
(PoE). If you use this, your router needs to provide PoE power, or you can
inject power near the router.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you ever have the opportunity to install ethernet wiring in the walls of
your house (either as a new build or during remodeling), &lt;strong&gt;do it!&lt;/strong&gt; Even if
most devices are wireless, the ability to put a wired access point in various
rooms would be a huge improvement to your network’s performance!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re not satisfied with your home wifi speed and coverage, I think a wired
access point should be high on your list of improvements to consider. While it
might not be feasible in every situation, it can provide very good speed and
coverage if there’s a way to run an ethernet cable back to the router. And if
you have enough DIY chops to install the cable yourself, the wired access point
can be very cost-effective!&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;

&lt;p&gt;I’m pretty darn happy with my home wifi now – I have fast coverage everywhere,
including very fast wifi in my bedroom and on my deck. 40+ MB/s (megabytes per
second) is fast enough that my ISP is the limiting factor (at roughly 150 Mbps
– megabits per second, or ~18 MB/s) rather than my home wifi. Still, I’m sure
I’ll want to upgrade more in the future as new technology comes out. I’ve heard
great things about Ubiquity, and would love to upgrade my router and access
points to Ubiquity hardware one day!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/access-point-with-wired-backhaul.jpg"/>
 </entry>
 
 <entry>
   <title>Analyzing Static Website Logs with AWStats</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/12/09/analyzing-static-website-logs-with-awstats.html" type="text/html" title="Analyzing Static Website Logs with AWStats"/>
   
   <published>2023-12-09T14:00:00-07:00</published>
   <updated>2023-12-09T14:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/12/09/analyzing-static-website-logs-with-awstats</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/12/09/analyzing-static-website-logs-with-awstats.html">&lt;p&gt;It’s really common to add Google Analytics to your site to gain valuable
insights about who’s visiting. I think most companies use Google Analytics, and
because it’s free and easy many solo developers and smaller sites do too. But
recently, I think a lot of people have been questioning the value. For many
small or independent blogs and websites, Google Analytics is an
overly-complicated product that’s difficult to use and navigate. Moreover, using
Google Analytics requires loading their relatively large, bloated javascript on
your site. When Google Analytics competitor Plausible was starting out a few
years ago, they highlighted a bunch of &lt;a href=&quot;https://plausible.io/blog/remove-google-analytics&quot;&gt;other reasons you might not want to use
Google Analytics&lt;/a&gt;. In short,
there’s gotta be a better analytics tool for small websites right?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog&lt;/em&gt; is a small website, and &lt;em&gt;I used to use Google Analytics&lt;/em&gt;. (&lt;strong&gt;I don’t
anymore!&lt;/strong&gt;) I removed Google Analytics from this blog back in 2021. I agree with
most of the criticisms in the article linked above – I don’t want to force my
visitors to be tracked, I don’t want a bloated script potentially slowing down
my site, and Google Analytics is way overkill for what I need. All I really care
about is occasionally checking how many people are visiting my site, seeing
which pages they look at, and watching for errors. Everything else is bonus, as
long as it’s easy to use and understand. For a year or two, I operated without
any analytics at all. I used &lt;a href=&quot;https://search.google.com/search-console&quot;&gt;Google Search
Console&lt;/a&gt; to gain a little insight into
which pages people were visiting. Search Console is actually &lt;em&gt;great&lt;/em&gt;, and I’m
still using it today. It provides a simple view of how people find your site
from Google, which is a good approximation for which pages are popular anyway.
But Search Console was never a true representation of all my traffic (because
some people don’t come to my site from Google), and therefore isn’t a perfect
replacement for Google Analytics.&lt;/p&gt;

&lt;p&gt;I wanted to find a better tool for analyzing traffic to my website. I started
looking for alternatives. &lt;a href=&quot;https://plausible.io/&quot;&gt;Plausible&lt;/a&gt; seemed really
appealing, and I liked a lot of their selling points. Simple analytics that are
easy to understand. Lightweight script. No cookies. Open source. Unfortunately,
I didn’t like their price tag. As of December 2023, their &lt;a href=&quot;https://plausible.io/#pricing&quot;&gt;cheapest
plan&lt;/a&gt; is $9/mo. While that’s probably reasonable
for most customers, it just doesn’t make sense for me. This blog is a statically
generated site that I operate for &lt;em&gt;dollars&lt;/em&gt; per year. And while I &lt;em&gt;could&lt;/em&gt; afford
to pay $9/mo, I just don’t want to. I don’t think I’d get $108 dollars of annual
value from analytics on my personal blog that doesn’t make any money. On top of
that, I’ve come to enjoy operating this blog &lt;em&gt;really frugally&lt;/em&gt;. I take some pride
in keeping things simple and cheap, and in knowing that you &lt;em&gt;can&lt;/em&gt; run a website
really well on a very low budget using open source software.&lt;/p&gt;

&lt;p&gt;So, if not Plausible Analytics, where should I turn? I was intrigued by the idea
of server-side analytics. I shouldn’t need clients to run javascript to
know who’s visiting my website, I should be able to tell who’s connecting to my
server and what page they’re requesting! Even though my blog is a static site, my
hosting provider uses Apache, and they offer access to the logs. That should be all
I need for basic analytics right?!?&lt;/p&gt;

&lt;h2 id=&quot;awstats&quot;&gt;AWStats&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://awstats.sourceforge.io/&quot;&gt;AWStats&lt;/a&gt; is a great tool! Despite the first
three letters, AWStats has nothing to do with AWS. In fact, it was created six
years &lt;em&gt;before&lt;/em&gt; AWS, in May, 2000. A time when PHP, ASP, Perl, ColdFusion, and
JSP were the some of the most popular server-side languages. AWStats stands for
Advanced Web Statistics. It’s a log analyzer for Apache (and nginx and other)
log files. Whether you’re serving static HTML through Apache or using something
like mod_php, AWStats will parse your server log files and provide info and
statistics about your web requests and visitors. It’s designed to be run on your
web server and hosted by Apache – sort of like an admin panel for your web
site. It has a separate process that can run on a cron schedule to ingest new
log files, and it uses Apache to serve the reports as web pages. I wondered if I
could use AWStats to analyze the Apache log files for my static site!&lt;/p&gt;

&lt;p&gt;Normally, AWStats expects to be run on a web server. It expects to save its
configuration in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/awstats/awstats.mysite.conf&lt;/code&gt; and it expects to create a
database in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/awstats&lt;/code&gt;. I &lt;em&gt;could&lt;/em&gt; run AWStats on my hosting provider,
but I’d prefer not to. For one, I’m currently only hosting static pages, and
don’t want to set up (or pay for) AWStats to run CGI Perl scripts. Also, because
I pay (pennies) for (megabytes of) server storage, and in the spirit of doing
things frugally, I don’t want to leave all my logs on the server indefinitely.
And finally, I’m the only one that needs to see the reports. I don’t want them
available on the internet, and don’t want to deal with locking down my
configuration to prevent public access. As an alternative to all this, I could
install AWStats on my laptop. This would be easy since I use Ubuntu – just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo
apt install awstats&lt;/code&gt; and I’m done. But I don’t really like that solution either.
AWStats expects to be on a web server, and I don’t want to deal with a bunch of
special (and tricky to reproduce) web server config on my laptop. Docker to the
rescue!&lt;/p&gt;

&lt;p&gt;With Docker, I can mount my log files into a (temporary) AWStats container
running on my laptop to parse my log files and serve reports. I mount the log
files into the container and run the AWStats script to ingest log files. I can
save state between runs by persisting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/awstats&lt;/code&gt; in a Docker volume. And
I can run Apache from the container to view my reports! (AWStats &lt;em&gt;can&lt;/em&gt; generate
static HTML to be viewed without running Apache, but only for a single month at
a time so I find it much nicer to let Apache run the CGI script and change the
time frame as I please.) I started using this AWStats Docker container to view
my server statistics a few weeks ago, and I’m really happy with it! AWStats
produces really useful reports, and I have all the info I need about traffic to
my site without running any analytics javascript on clients! Perhaps more
importantly, it includes a section showing statistics about my error status codes
and which URLs produced the error.&lt;/p&gt;

&lt;h2 id=&quot;awstats-docker-setup--configuration&quot;&gt;AWStats Docker Setup &amp;amp; Configuration&lt;/h2&gt;

&lt;p&gt;Here’s my current setup. For a long time, I’ve used a Rakefile (a Makefile, but
with Ruby) to build and deploy my site. I added a task to my Rakefile to grab
the access logs off the server and save them to my laptop (where I keep them and
back them up). I also added tasks to ingest the logs into the Docker container
(with the AWStats database persisted on a Docker volume) and a task to serve the
reports. (I’m using the &lt;a href=&quot;https://github.com/pabra/docker_awstats/&quot;&gt;pabra&lt;/a&gt;
AWStats Docker image, which appears to be the most well-maintained an
up-to-date.) Here’s what that looks like.  (Most of this is just bash, so could
be easily ported to a Makefile or another script.)&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Fetch logs from the server&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:logs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;======== FETCH LOGS ========&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bold&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;scp mikekasberg.com:/home/logs/access_log &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dir__&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/logs/access.log&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dir__&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/logs/access.log&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10000000&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 10MB&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Size exceeds 10MB. Rotating log file to local only.&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;date_string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getutc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;iso8601&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;FileUtils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dir__&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/logs/access.log&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dir__&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/logs/access_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date_string&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.log&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ssh mikekasberg.com &apos;rm /home/logs/access_log&apos;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Ingest logs for AWStats Docker&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:awstats_ingest&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;========== INGEST ==========&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bold&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;docker volume create awstats-db-mike-jekyll&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;docker run --rm -e AWSTATS_CONF_LOGFILE=&apos;logresolvemerge.pl /logs/*.log |&apos; -e AWSTATS_CONF_LOGFORMAT=1 -e AWSTATS_CONF_SITEDOMAIN=www.mikekasberg.com -e AWSTATS_CONF_HOSTALIASES=&apos;localhost 127.0.0.1&apos; -e AWSTATS_CONF_ALLOWFULLYEARVIEW=3 -v ${PWD}/logs/:/logs/:ro -v awstats-db-mike-jekyll:/var/lib/awstats pabra/awstats:7.9 awstats.pl -config=awstats  -update&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Run AWStats Interface&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:awstats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:awstats_ingest&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;========== AWStats ==========&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bold&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;xdg-open &quot;http://localhost:8080/awstats.pl&quot;&apos;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;docker run --rm -it -p 8080:80 -e AWSTATS_CONF_SITEDOMAIN=www.mikekasberg.com -e AWSTATS_CONF_HOSTALIASES=&apos;localhost 127.0.0.1&apos; -e AWSTATS_CONF_ALLOWFULLYEARVIEW=3 -v awstats-db-mike-jekyll:/var/lib/awstats pabra/awstats:7.9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Grabbing logs from my server is as easy as running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake logs&lt;/code&gt;, and I do that
at least every couple months. And viewing my website statistics is as easy as
running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake awstats&lt;/code&gt;. (The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awstats&lt;/code&gt; task depends on the ingestion task, so it
will ingest any new logs first.) I even added a line that will automatically
open the reports in my web browser so I don’t need to remember the URL!
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdg-open&lt;/code&gt; works on Linux, on macOS you could just use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt;.) What can you do
with AWStats? It does the basics well (visitors and page views). Beyond that,
it’s great for looking at the most common error URLs, and it’s also good for
looking at referrers.&lt;/p&gt;

&lt;p&gt;AWStats is really old technology, but it’s really good at what it does and it’s
reliable! There are lots of potential problems with javascript analytics but
perhaps the most important is this: Users don’t like it and many try to block
it. I think server-side analytics is a great (obvious?) alternative to
client-side analytics that’s less intrusive, and could be a great fit for small
websites and blogs. Running AWStats in a temporary Docker container is a great
way to get web statistics from server logs!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/analyzing-static-website-logs-with-awstats.jpg"/>
 </entry>
 
 <entry>
   <title>Catching Mew: A Playable Game Boy Quote</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/09/26/catching-mew-a-playable-gameboy-quote.html" type="text/html" title="Catching Mew: A Playable Game Boy Quote"/>
   
   <published>2023-09-26T07:30:00-06:00</published>
   <updated>2023-09-26T07:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/09/26/catching-mew-a-playable-gameboy-quote</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/09/26/catching-mew-a-playable-gameboy-quote.html">&lt;p&gt;Last week I went to &lt;a href=&quot;https://thestrangeloop.com/&quot;&gt;Strange Loop 2023&lt;/a&gt;. There were
many great sessions, but my favorite was &lt;a href=&quot;https://www.youtube.com/watch?v=z9JYOZWLMlo&quot;&gt;Playable Quotes for Game Boy
Games&lt;/a&gt;.  Joël and Adam presented an
idea for &lt;em&gt;playable&lt;/em&gt; Game Boy “quotes” that should be (legally) shareable. (It’s
worth reading &lt;a href=&quot;https://joel.franusic.com/playable_quotes_for_game_boy&quot;&gt;How We Made Playable Quotes for the Game
Boy&lt;/a&gt; on Joël’s Blog.)
Their solution’s both clever and elegant, and uses only a few hundred lines of
code (aside from existing emulators). The playable quote truly removes all the
unnecessary parts of the ROM, which is important for legal reasons.  Their
implementation is also practical and future-proof, embedding everything that’s
necessary to play the game in a single distributable file.  It’s also pretty
clever, using steganography to embed data into a screenshot of the game.  (This
revelation produced an outburst of applause at the live presentation.) While I
watched this presentation at Strange Loop, I couldn’t help but think that a
playable quote like this would be a great way to experience catching Mew in the
original Pokémon Game Boy games.&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;b&gt;Skip Ahead:&lt;/b&gt; &lt;a href=&quot;#how-to-catch-mew&quot;&gt;How to Catch Mew&lt;/a&gt;
&lt;/div&gt;

&lt;h2 id=&quot;the-legend-of-mew&quot;&gt;The Legend of Mew&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://bulbapedia.bulbagarden.net/wiki/Mew_(Pok%C3%A9mon)&quot;&gt;Mew&lt;/a&gt; is my favorite
Pokémon. Not because it’s a cool, rare Pokémon with special abilities that’s
hard to catch, but because of the &lt;strong&gt;incredible programming story&lt;/strong&gt; of how it
came to be. In fact, this might be my &lt;em&gt;favorite&lt;/em&gt; programming story. It’s on par
with &lt;a href=&quot;https://web.mit.edu/jemorris/humor/500-miles&quot;&gt;We can’t send e-mail more than 500
miles&lt;/a&gt; and some of the other
wonderful stories listed &lt;a href=&quot;http://beza1e1.tuxen.de/lore/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have fond memories of playing Pokémon as a child, but I never caught Mew.
Still, I remember all the rumors that I’d talk about with my friends. Someone
heard a rumor that you could find Mew next to a truck by the S.S. Anne after
beating the Elite Four. Another friend thought that you could only get Mew with
a GameShark.  Someone else heard you could only get Mew at official Nintendo
events where they gave it out. As a young kid (without access to Nintendo events
or a GameShark), all these rumors added to the mystery and intrigue of Mew, and
they hype around Mew probably contributed to the success of Pokémon.&lt;/p&gt;

&lt;p&gt;As it turns out, the real story behind Mew is a programming tale. I find this
story fascinating, and it makes me like Mew even more than the legends I heard
in my childhood. Mew was originally created by Shigeki Morimoto, a programmer
for Game Freak. And he snuck it into the code! It was a sort of Easter egg,
originally intended only for Game Freak staff. Morimoto added it to the game at
the last minute, after the removal of some debug features in preparation for the
final release freed up enough space to add Mew. He didn’t add any way to catch
or encounter it – I guess he meant to use his developer tools or some
GameShark-like device to let people he knew get Mew in their game. All
programmers love a good Easter egg, and this might be the most famous Easter egg
I’ve ever heard of (even if most people now know Mew as a regular Pokémon and
don’t realize it was originally just an Easter egg). It’s worth reading a full
account of the story on &lt;a href=&quot;https://en.wikipedia.org/wiki/Mew_(Pok%C3%A9mon)#Concept_and_creation&quot;&gt;Mew’s Wikipedia
Page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Programmers love Easter eggs, but we also love interesting bugs and glitches. If
Mew was originally intended to be an unobtainable Easter egg, the fact that
&lt;strong&gt;it’s actually possible to catch a Mew in an unmodified game&lt;/strong&gt; by exploiting a
glitch makes it even more alluring! Pokémon Game Boy games use a pseudo-random
number generator (PRNG) to randomize the Pokémon you encounter. By manipulating
this PRNG, it’s possible to predictably and reliably encounter a wild Mew after
a series of specific actions! Exploiting the glitch is a little tricky to pull
off, and requires a series of specific actions at specific points in the game.
This particular glitch requires a couple hours of play time to get to the right
part of the game, and isn’t usable once you’ve passed it (by defeating the
trainers).&lt;/p&gt;

&lt;p&gt;This seems like a perfect application of the playable quotes I learned about at
Strange Loop, so I spent a couple hours over my weekend creating them!&lt;/p&gt;

&lt;h2 id=&quot;how-to-catch-mew&quot;&gt;How to Catch Mew&lt;/h2&gt;

&lt;div class=&quot;message&quot;&gt;
All the images below are &lt;a href=&quot;https://joel.franusic.com/playable_quotes_for_game_boy&quot;&gt;playable Game Boy quotes&lt;/a&gt;. Click on them
to see the animation or play the game yourself!
&lt;/div&gt;

&lt;p&gt;The earliest point you can catch Mew in Pokémon Red/Blue is in Cerulean city.
You need to defeat the five trainers on Nugget bridge, but you need to be very
careful with trainer battles after that because the glitch we’re going to
exploit requires battles with a couple of the upcoming trainers.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Catch an &lt;a href=&quot;https://bulbapedia.bulbagarden.net/wiki/Abra_(Pok%C3%A9mon)&quot;&gt;Abra&lt;/a&gt;. You can encounter a wild Abra in the grass North of Nugget
Bridge on Route 24. Abra is difficult to catch because the only move it knows is
Teleport, and it will use Teleport to escape if it gets the opportunity. The
best way to catch it is to immediately put it to sleep, perhaps with
Butterfree’s Sleep Powder.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/00-catch-abra.png&quot;&gt;
  &lt;img src=&quot;/images/catching-mew/00-catch-abra.png&quot; class=&quot;img-responsive img-blog-content&quot; alt=&quot;A playable Game Boy quote. Making Abra fall asleep.&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Visit the Pokémon Center in Cerulean City. This is important because you’ll
teleport to the most recent Pokémon Center you visited. You should also prepare
your party at this point. You’ll want 1-2 strong Pokémon to win a trainer
battle, and perhaps a couple weaker Pokémon or Pokémon with special abilities to
use to weaken and catch Mew.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Save your game. Don’t save again until you’ve caught mew. This way you can
try again if you make a mistake.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk down the grassy area West of Nugget Bridge right next to the water. You
must press start quickly after moving South but before you finish moving. Your
goal is to pause the game such that when you unpause, the trainer will see you
and trigger a battle. Rather than unpausing, use Teleport. The trainer will spot
you, but you’ll Teleport away. This can be difficult to describe, so perhaps a
playable quote will help!&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/01-nugget-bridge-teleport.png&quot;&gt;
  &lt;img src=&quot;/images/catching-mew/01-nugget-bridge-teleport.png&quot; class=&quot;img-responsive img-blog-content&quot; alt=&quot;A playable Game Boy quote. Teleporting away from the trainer battle West of Nugget Bridge.&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk back up Nugget Bridge and battle a specific trainer on Route 24. It is important that you are
a few steps away so the trainer has to walk to you. Win the battle.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/02-beat-trainer.png&quot;&gt;
  &lt;img src=&quot;/images/catching-mew/02-beat-trainer.png&quot; class=&quot;img-responsive img-blog-content&quot; alt=&quot;A playable Game Boy quote. Battling the trainer on Route 24.&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Immediately after the battle ends, use Teleport.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/03-teleport-2.png&quot;&gt;
  &lt;img src=&quot;/images/catching-mew/03-teleport-2.png&quot; class=&quot;img-responsive img-blog-content&quot; alt=&quot;A playable Game Boy quote. Teleporting away from the trainer on Route 24.&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk back up Nugget Bridge. Upon leaving Cerulean City, the game will
glitch and the menu will come up even though you didn’t press
&lt;kbd&gt;START&lt;/kbd&gt;. Exit the menu and &lt;strong&gt;you will encounter Mew!&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/04-catching-mew.png&quot;&gt;
  &lt;img src=&quot;/images/catching-mew/04-catching-mew.png&quot; class=&quot;img-responsive img-blog-content&quot; alt=&quot;A playable Game Boy quote. Catching Mew!&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I find it fascinating that you can exploit a glitch in Pokémon to catch Mew, the
uncatchable Pokémon! It brought me a lot of joy to listen to the &lt;a href=&quot;https://www.youtube.com/watch?v=z9JYOZWLMlo&quot;&gt;Playable
Quotes for Game Boy Games&lt;/a&gt; session
at Strange Loop, it was fun to build these playable quotes, and I hope Joël and
Adam enjoy seeing their quotes in use here on my blog! I also hope that these
playable quotes help create one of the most approachable, easy-to-understand
tutorials for catching Mew. It’s worth noting that the quotes above contain a
complete save-state, and it’s possible to continue playing if you drop a
complete ROM onto the quote player. It should also be possible to export the
save-state from any quote in a widely-compatible .sav or .srm format, but doing
so is left as an exercise for the reader, for now.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/catching-mew.png"/>
 </entry>
 
 <entry>
   <title>My $500 Developer Laptop</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/09/09/my-500-developer-laptop.html" type="text/html" title="My $500 Developer Laptop"/>
   
   <published>2023-09-09T07:30:00-06:00</published>
   <updated>2023-09-09T07:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/09/09/my-500-developer-laptop</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/09/09/my-500-developer-laptop.html">&lt;p&gt;I’m not the first person to write about a $500 developer laptop. In fact, I was
inspired by Max Rozen’s &lt;a href=&quot;https://maxrozen.com/replacing-my-macbook-m1-with-thinkpad-t480&quot;&gt;Replacing my MacBook Air M1 with a ThinkPad
T480&lt;/a&gt; and
&lt;a href=&quot;https://maxrozen.com/getting-your-own-good-enough-laptop-for-under-500&quot;&gt;Getting your own good enough laptop for under
$500&lt;/a&gt;.
Like Max, I’m not only writing a blog about this – I’m actually using the $500
laptop I’m writing about as my personal daily driver. You don’t need a $2,000
computer to have a great machine for web development! The laptop I chose is a
great alternative to the ThinkPad T480. (ThinkPads are great, but they’re not
the only way to get an incredibly capable and pragmatic laptop on a budget.)
I’ve been a fan of Dell for many years, and my $500 developer laptop is a
&lt;strong&gt;Latitude 7490&lt;/strong&gt;. I recently bought one to replace the Latitude e7450 I was
using. Let’s see how it stacks up to the T480, and how well it works as a daily
driver.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;Related posts about finding the perfect computer:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2019-05-03: &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;Computer Shopping: The Ultimate Developer Laptop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2020-05-24: &lt;a href=&quot;/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html&quot;&gt;Why I Love Ubuntu As a Desktop OS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2021-08-12: &lt;a href=&quot;/blog/2021/08/12/dell-latitudes-are-great-laptops.html&quot;&gt;Dell Latitudes are Great Laptops (and they run Ubuntu well)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2022-05-07: &lt;a href=&quot;/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html&quot;&gt;The Best Computer You Can Buy For $100&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;h1 id=&quot;the-laptop&quot;&gt;The Laptop&lt;/h1&gt;

&lt;p&gt;Like the ThinkPad T480, the Latitude 7490 is a business class laptop that’s
several years old. (The xx90 indicates it came out in 2019.) That means there
are tons of them on the used marked, they’re easy to work on, and replacement
parts are easy to find. You can buy a 7490 with a wide range of specs; let’s
look at the one I found:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Intel i7 8650U CPU&lt;/li&gt;
  &lt;li&gt;32GB RAM&lt;/li&gt;
  &lt;li&gt;1920x1080 screen&lt;/li&gt;
  &lt;li&gt;60 Wh battery&lt;/li&gt;
  &lt;li&gt;Windows 11 Pro installed (to be replaced with Ubuntu Linux)&lt;/li&gt;
  &lt;li&gt;1TB NVMe SSD&lt;/li&gt;
  &lt;li&gt;WiFi: Intel Dual Band Wireless-AC 8265&lt;/li&gt;
  &lt;li&gt;1280x720 webcam&lt;/li&gt;
  &lt;li&gt;Ports:
    &lt;ul&gt;
      &lt;li&gt;USB-C / Thunderbolt 3&lt;/li&gt;
      &lt;li&gt;HDMI&lt;/li&gt;
      &lt;li&gt;3x USB 3.1&lt;/li&gt;
      &lt;li&gt;Gigabit Ethernet&lt;/li&gt;
      &lt;li&gt;SD Card Reader&lt;/li&gt;
      &lt;li&gt;3.5mm Headset jack&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I originally found the laptop spec’ed out above listed for $436 (including
shipping) on
&lt;a href=&quot;https://www.newegg.com/p/pl?N=100006740&amp;amp;SrchInDesc=7490%2CLatitude&quot;&gt;Newegg&lt;/a&gt;,
but after I ordered it the seller contacted me because the 1TB disks were out of
stock. I accepted their offer of a lower price with a smaller hard drive, and
upgraded to a 1TB NVMe drive myself with the savings!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/my-500-developer-laptop/ssd.jpg&quot; alt=&quot;Installing a new SSD in my Latitude 7490&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The laptop was refurbished, so all I received is the laptop and a charger in a
plain box with some bubble wrap. There were a few minor scuffs on the laptop. No
big deal, it works great!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/my-500-developer-laptop/packaging.jpg&quot; alt=&quot;Unwrapping my Latitude 7490&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Compared to the
&lt;a href=&quot;https://maxrozen.com/replacing-my-macbook-m1-with-thinkpad-t480&quot;&gt;T480&lt;/a&gt;, I think
this latitude is perhaps &lt;em&gt;slightly better&lt;/em&gt;?!? The processor is slightly faster,
the SSD is larger, and I didn’t need to upgrade the display or RAM. The T480
comes out way ahead on battery life though, with 92 Wh compared to the
Latitude’s 60 Wh. Overall, these are actually very similar machines, and the
main point is you can find either of them very cheaply – a really good computer
(that runs Linux well) for less than $500.&lt;/p&gt;

&lt;h2 id=&quot;is-this-really-a-good-computer&quot;&gt;Is this really a good computer?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, I think the Latitude 7490 really is a good computer&lt;/strong&gt;, and I really enjoy
using it. I prefer Ubuntu Linux over macOS or Windows. After using Linux for a
long time, &lt;a href=&quot;/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html&quot;&gt;Windows feels like a slow, bloated joke to me&lt;/a&gt;, with advertisements plastered
all over the Start menu. macOS is much better, but still feels bloated compared
to Ubuntu. I use vanilla Gnome 3, and it provides a simple, unobtrusive
interface that gets out of my way and lets me get work done. Dell Latitudes run
Ubuntu better than any other computer I’ve ever used, and Ubuntu on a 7490 is a
prefect fit for me.&lt;/p&gt;

&lt;p&gt;I use an M1 Max at work, and I actually prefer my personal Latitude
7490 for most tasks. It’s technically slower, but it’s not &lt;em&gt;noticeably&lt;/em&gt; slower.
To say that a little differently, my 7490 doesn’t feel any slower for most tasks
than the fastest laptop money can buy! The battery lasts a long time. And it
comes with the perfect variety of ports (USB-C, USB-A, HDMI, headset).&lt;/p&gt;

&lt;p&gt;Back in 2019 I wrote about &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;The Ultimate Developer Laptop&lt;/a&gt;. That blog post
was primarily about the Dell Precision 5510 I’d just bought, and why I chose it.
I still stand by that post – the Precision 5510 is a great laptop – but I’ve
found that a few of my preferences have shifted slightly since I wrote it.  I’ve
come to prefer slightly smaller laptops with longer battery life. Back in 2019,
I most often worked at my home desk and rarely used battery power, so the
Precision 5510’s meager battery (I only get 1-2 hours) didn’t bother me much.
Since then, I got married, moved to a bigger house, and now spend much more time
using my laptop away from my desk – whether working outside on my deck or
browsing the web from my couch. I found myself frustrated by the 5510’s short
battery life and relatively heavy weight. In comparison, the Latitude sips power
because it doesn’t have a graphics card, and has a battery that lasts 5+ hours.&lt;/p&gt;

&lt;p&gt;In 2021, I wrote about how well &lt;a href=&quot;/blog/2021/08/12/dell-latitudes-are-great-laptops.html&quot;&gt;Dell Latitudes run Linux&lt;/a&gt;, and that was actually the
catalyst that set me on the path toward the 7490 I just bought. I wrote that
2021 post from my own Latitude e7450. I originally bought it as a spare computer
(and a lighter one for travel) with 8GB RAM and a 256GB SSD, but I often
found myself choosing to use it over my Precision 5510 despite the meager specs
because of its small size and longer battery life. I spent like $40 to upgrade
the RAM and disk, I began using my e7450 almost exclusively as my personal daily
driver. To be clear, I’m talking about an e7450 with an i7-5600U, 500GB SSD, and
16GB RAM. The CPU is from 2015, the SSD uses SATA (relatively
slow), and it has integrated graphics.  The fact that I was happier using that
computer than my Precision 5510 just further drives home the point that you can
get a “good enough” computer for dirt cheap! (You can buy a similar e7450 for
under $200 today.)&lt;/p&gt;

&lt;p&gt;Is my $500 Latitude 7490 the perfect web developer machine? Probably not, but I
think it’s darn close. I’d love to have an M1 MacBook Air that runs Ubuntu,
though the keyboard layout would probably drive me nuts. &lt;a href=&quot;https://asahilinux.org/&quot;&gt;Asahi
Linux&lt;/a&gt; appears to be usable for some people, but I’m
not sure it’s stable enough to be my own primary machine yet. The &lt;a href=&quot;https://system76.com/laptops/lemur&quot;&gt;System76 Lemur
Pro&lt;/a&gt; looks like a great option with a 73Wh
battery, and is probably what I’d buy with an unlimited budget, but it’s hard
for me to justify that much difference in price for minor performance
improvements. The M1 or the latest Intel processor might make sense for an
enterprise codebase that takes 20 minutes to compile, but probably doesn’t make
a ton of difference for the average web developer (or any other computer user).&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;Battery Life Update (Nov 2023)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;When I originally bought this laptop, I wasn&apos;t completely sure how good the battery life would be. It depends not only on the size of the battery (60 Wh), but also the power consumption of the CPU, display, and other components. After two months of use, I can say I&apos;m very pleased! If I charge the laptop overnight, it can go a full weekend day (6+ hours active use, sleeping in between) without charging and still have some battery left over. I&apos;d guess I could get 8 hours normal use on a full charge!&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Long story short: I really like my new (used) Dell Latitude 7490. It runs Ubuntu
flawlessly, it has great battery life, it has a great keyboard, it’s
lightweight, and it’s easy and cheap to upgrade and repair. I recommend both
ThinkPads and Latitudes to anyone who wants a great deal on a Linux computer
that they’ll probably fall in love with. Want to shop for your own? See my &lt;a href=&quot;/blog/2021/08/12/dell-latitudes-are-great-laptops.html&quot;&gt;Dell
Latitudes are Greal Laptops&lt;/a&gt; post for some tips about what to
look for and where to find them.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/my-500-developer-laptop.jpg"/>
 </entry>
 
 <entry>
   <title>How to Test and Optimize Your Home Wifi Coverage</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/07/28/how-to-test-and-optimize-your-home-wifi.html" type="text/html" title="How to Test and Optimize Your Home Wifi Coverage"/>
   
   <published>2023-07-28T17:00:00-06:00</published>
   <updated>2023-07-28T17:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/07/28/how-to-test-and-optimize-your-home-wifi</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/07/28/how-to-test-and-optimize-your-home-wifi.html">&lt;p&gt;I’m sure everyone’s had a frustrating experience trying to optimize their home
wifi network. You put the router in a great spot for the TV, but you have bad
wifi on your deck. You move it somewhere to provide better coverage for your
deck, but it causes intermittent problems streaming movies to your TV. Figuring
out the ideal wifi setup is tricky, and poorly documented. (But I’m hoping to
provide some better documentation in this blog post!) I recently optimized my
home wifi network to provide better coverage throughout my house, and I
discovered some cool techniques along the way that I want to share.&lt;/p&gt;

&lt;p&gt;Last year I &lt;a href=&quot;/blog/2022/12/22/i-installed-my-own-coax-cable.html&quot;&gt;moved my Wifi router to the basement&lt;/a&gt;. This was a great improvement! It
gave me a lot more space to put additional devices near my router (like my
Pi-Hole and NAS). It also moved my wifi router closer to my basement office,
improving the wireless connection I use all day at work. But there were some
downsides too. I realized that after moving my router to the basement, my wifi
would intermittently lose the connection on my deck and in my yard, and the
speed from my bedroom became slower. (Who could’ve predicted that???) I bought a
wireless range extender (a TP-Link RE450) to improve my wireless coverage, but I
wanted to find the optimal location for the range extender, and I also wanted to
measure how much difference it made. (And I wanted something more accurate than
counting the bars on my phone, which never seem that accurate anyway!)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/how-to-test-and-optimize-your-wifi/re450.jpg&quot; alt=&quot;A TP-Link RE450 range extender&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can &lt;em&gt;sort of&lt;/em&gt; use internet speed tests (like
&lt;a href=&quot;https://www.speedtest.net/&quot;&gt;speedtest.net&lt;/a&gt;) for this, but that’s not &lt;em&gt;really&lt;/em&gt;
measuring what you want or getting the most accurate measurement. Internet speed
tests measure the speed of your &lt;em&gt;internet&lt;/em&gt;, which is limited by your ISP, is
probably slower than your wifi, and could be affected by a wide range of
factors. If you’ve tried measuring your wifi speed using this method before and
you measured the same speed pretty much everywhere, it’s probably because you’re
measuring your &lt;em&gt;internet&lt;/em&gt; speed rather than your &lt;em&gt;wifi&lt;/em&gt; speed. Not all wifi
usage is a transfer from the internet – I often transfer large files to or from
my NAS, and that transfer isn’t limited my my internet speed. Aside from that,
measuring &lt;em&gt;wifi&lt;/em&gt; speed instead of &lt;em&gt;internet&lt;/em&gt; speed uses a more specific and more
sensitive measurement that ultimately results in a better understanding of my
home’s wifi – which is something we can optimize!&lt;/p&gt;

&lt;p&gt;There are some tools available that are supposed to be able to generate a
heatmap image of the wifi coverage in your house (or any other area) like
&lt;a href=&quot;https://github.com/jantman/python-wifi-survey-heatmap&quot;&gt;python-wifi-survey-heatmap&lt;/a&gt;.
When I first started trying to optimize my wifi setup, I looked into a bunch of
these tools, but I never ended up actually using them. I found that they all
required much more effort than I wanted to invest. (To start with, they all
require a floor plan of your house, which I don’t have.) This might be exactly
what you’re looking for if you want to invest many hours into building a
detailed wifi heat map, but I’m guessing that, like me, you want something
faster and easier. Let me share what I came up with.&lt;/p&gt;

&lt;h2 id=&quot;how-to-test-your-wifi-coverage&quot;&gt;How to Test Your Wifi Coverage&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;You need to be able to access another device on your network with ssh.
Ideally, this device is connected to your router with an ethernet cable (not
wifi). Some wifi routers allow ssh access, and that would be ideal if yours
supports it. I used my NAS. If you don’t have either of those, any macOS or
Linux computer will work if you &lt;a href=&quot;https://osxdaily.com/2022/07/08/turn-on-ssh-mac/&quot;&gt;run an ssh
server&lt;/a&gt; (temporarily) on the
computer. You could plug the computer into your router with an ethernet cable,
or use wifi and put it directly next to the router. Note the IP address of the
device you’ll be connecting to – we’ll call this device A.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Using a different laptop (our test computer), stand near your router. We’re
going to run an ssh command to test the network speed from device A to your
laptop. Run this command on your laptop, replacing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192.168.0.1&lt;/code&gt; with the IP
of device A from step (1):&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ssh 192.168.0.1 &apos;dd if=/dev/zero bs=1GB count=1 2&amp;gt;/dev/null&apos; | dd of=/dev/null status=progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;This command runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dd if=/dev/zero bs=1GB&lt;/code&gt; on device A to produce a gigabyte
of zeroes. That data is sent over the network to your laptop, and piped into
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dd of=/dev/null&lt;/code&gt;, while measuring the progress. You’ll see a live report
that includes the transfer speed. (If your wifi is ridiculously fast,
you can change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; to send more than 1GB.) When the transfer finishes (typically 10-60
seconds), you’ll see a report of the average transfer speed in MB/s. Make
note of this, and the location you were in. (Since you were right next to your
router, we’ll use this as a baseline – the fastest speed we expect to see.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Repeat step (2) at various locations around your house. Test all the spots
you might want to use your computer. Your office, the TV room, your bedroom, the
kitchen, the deck, etc. Note the speed at each location. You’ll quickly
develop a sense of where your wifi quality is great and where it’s low.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Make a change and re-test to see if things got better or worse. Here are some
things you might consider changing to improve your wifi quality:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Move your router&lt;/li&gt;
      &lt;li&gt;Change your wifi channel&lt;/li&gt;
      &lt;li&gt;Change your wifi band (2.4Ghz/5Ghz)&lt;/li&gt;
      &lt;li&gt;Add a wifi range extender&lt;/li&gt;
      &lt;li&gt;Replace your router&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! As you can see, by testing the speed to transfer 1GB of data over
wifi from various locations, we can easily get a sense for where the wifi is
good and where it’s bad. And by running the same test before and after a change,
we can get a sense for how much the change helped or hurt the wifi performance.&lt;/p&gt;

&lt;p&gt;If none of that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt; stuff made sense to you, or if you don’t have a second
device with ssh access to test with, you &lt;em&gt;can&lt;/em&gt; run an internet speed test at
&lt;a href=&quot;https://www.speedtest.net/&quot;&gt;speedtest.net&lt;/a&gt; instead of the command in step (2)
above. Just be aware that you’re measuring internet speed – not wifi speed –
so it’s likely to be the same everywhere except near the edges of your coverage. 
I hope this helps you get a great wifi setup in your own house!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-to-test-and-optimize-your-home-wifi.jpg"/>
 </entry>
 
 <entry>
   <title>3D Printing Map Figurines with GPS</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/07/17/3d-printing-map-figurines-with-gps.html" type="text/html" title="3D Printing Map Figurines with GPS"/>
   
   <published>2023-07-17T20:00:00-06:00</published>
   <updated>2023-07-17T20:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/07/17/3d-printing-map-figurines-with-gps</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/07/17/3d-printing-map-figurines-with-gps.html">&lt;p&gt;Learning to &lt;a href=&quot;/blog/2023/03/22/3d-printing-with-openscad.html&quot;&gt;create 3D models with OpenSCAD&lt;/a&gt; has opened a world of possibilities for
me. Once I was comfortable with the basics, I wondered what it would look like
to push the boundaries of programmatic 3D printing even further. In my day job
as a software engineer at Strava, I work with GPS data from running and cycling
activities. What if there was a way to bring that data into OpenSCAD to use it
in a 3D model?&lt;/p&gt;

&lt;p&gt;As I thought about this, I realized there’s actually a pretty easy solution –
it wouldn’t be terribly difficult to parse GPS data from a GPX file and print
that data as an array into a scad file. Distance and elevation can be
represented as an array of numbers in scad, and position (lat/lng) can be
represented as an array of 2D points. I decided it would be a fun project to try
creating an OpenSCAD model that includes GPS data.&lt;/p&gt;

&lt;p&gt;It would be pretty easy to parse a GPX file and produce an array for OpenSCAD in
almost any programming language, but I chose Ruby for the task. Aside from the
fact that I like Ruby and I’m familiar with it, it’s well-suited for the task
because it works as a scripting language, it has a nice XML library
(&lt;a href=&quot;https://nokogiri.org/#&quot;&gt;Nokogiri&lt;/a&gt;) for parsing GPX files (which are really
just XML), and it has a templating language
(&lt;a href=&quot;https://rubyapi.org/3.2/o/erb&quot;&gt;ERB&lt;/a&gt;) that I thought I might use for writing
data back to SCAD files.&lt;/p&gt;

&lt;p&gt;My initial process was somewhat manual. I hacked together a script to parse
elevation data out of a GPX file (using Nokogiri to iterate the correct nodes)
and dump it to the console as a JSON array. I manually copied and pasted this
array into an OpenSCAD file and then worked exclusively in OpenSCAD for a while
to design the model. (It’s fortunate that OpenSCAD array syntax is identical to
JSON – that kept things really simple 😅.) It was finicky at first – too few
data points and the model looked terrible, but too many data points also looked
odd and made rendering unreasonably slow in OpenSCAD. After experimenting a
little I found that about 100 data points looked decent with most of my samples.&lt;/p&gt;

&lt;p&gt;Getting latitude and longitude working was a little trickier than elevation, but
not bad. There are a variety of different projections I could’ve used (to
transform latitude and longitude to a 2D surface), but for simplicity I went
with an equirectangular projection (treating longitude as x and latitude as y).
In OpenSCAD, I represented these points as an array of arrays.&lt;/p&gt;

&lt;p&gt;Getting GPX data into an OpenSCAD file is one thing, but making a useful model
with it is another. GPX data (lat/lng combined with elevation) is basically a
sequence of 3D points, and I needed to assign volume to those points to create a
3D object. For this figurine model, I wanted to represent the GPX path as a line
(with changing height to represent elevation). To achieve this, I break the path
down to a series of line segments (between two consecutive points). For each segment, I
create a quadrilateral prism with the correct height at opposite ends, then
translate and rotate that prism into the correct relative position for the
model. This approach yielded pretty good results but had some oddities when the angle
between any two points was too far from 180. To make that look better, I add a
cylinder at each GPX point to fill the gaps, and trim the prism at the ends to
avoid odd intersections. The final result isn’t perfectly smooth, but looks
great from any reasonable distance away.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3d-printing-map-figurines/3d-printer.webp&quot; alt=&quot;A 3D printer creating a GPX figurine&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With a working prototype, I cleaned things up and made all the tooling nicer to
work with. I tweaked the script to use an ERB template to generate a scad file
with the arrays of GPX points, and made a nicer CLI interface for the script.
Want to try it yourself? I made the code open source and it’s available on
GitHub:
&lt;a href=&quot;https://github.com/mkasberg/3d-gpx-figurines&quot;&gt;mkasberg/3d-gpx-figurines&lt;/a&gt;. I
hope you enjoy printing your own GPX figurines or riffing on my work to create
your own cool 3D project!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="3D Printing"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/3d-printing-map-figurines.jpg"/>
 </entry>
 
 <entry>
   <title>3D Printing the Strava Logo</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/04/27/3d-printing-the-strava-logo.html" type="text/html" title="3D Printing the Strava Logo"/>
   
   <published>2023-04-27T19:30:00-06:00</published>
   <updated>2023-04-27T19:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/04/27/3d-printing-the-strava-logo</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/04/27/3d-printing-the-strava-logo.html">&lt;p&gt;I just added a new wall decoration to my home office, and I think it turned out
great!&lt;/p&gt;

&lt;p&gt;I got the idea to 3D print the Strava logo a few weeks ago after seeing Martin
Woodward’s &lt;a href=&quot;https://github.com/martinwoodward/octolamp&quot;&gt;Octolamp&lt;/a&gt;. I designed
and printed my own Strava logo, and it turned out even better than I expected.
It’s actually really easy for 3D printers to create shapes like this because
it’s just an extruded 2D shape. Vertical walls with no overhangs are a piece of
cake to print. And while it looks like it uses a lot of material, it actually
doesn’t – the inside is mostly air, with a small percent of infill material for
support. And I got lucky with the Strava logo – I printed each part separately,
allowing me to print it twice as big as I otherwise would have been able to on
my Prusa MINI. I completed a fun project, got a great wall decoration for my
home office (fitting since I work for Strava), and the whole thing only cost a
couple dollars in filament!&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;&lt;img src=&quot;/images/3d-printing-the-strava-logo/decoration.jpg&quot; alt=&quot;The Strava Logo hanging on my wall&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-i-did-it&quot;&gt;How I Did It&lt;/h2&gt;

&lt;p&gt;I’ve been &lt;a href=&quot;/blog/2023/03/22/3d-printing-with-openscad.html&quot;&gt;getting into OpenSCAD&lt;/a&gt; lately, and I wanted to continue
building my skills by using OpenSCAD for this project. The only tricky part
would be getting the logo into OpenSCAD. In hindsight, I probably could have
found an SVG and imported it, but I came up with a different solution before I
realized I the SVG would be easier. My approach was actually very simple; I just
manually entered the coordinates for the vertices of the Strava Logo (measured
with an image editor) in my OpenSCAD script. This was a little toilsome, but not
bad since there aren’t that many points.&lt;/p&gt;

&lt;p&gt;With a path representing the perimeter of the logo in my script, the rest was a
piece of cake. I converted the path to a 2D polygon, then extruded the polygon
to create a 3D object. I even added a little hole to the back to hang it on a
nail. The print turned out as good as I hoped it would, and it’s now hanging in
my home office!&lt;/p&gt;

&lt;p&gt;OpenSCAD (a 3D scripting tool) turned out to be a great tool to use for this
project because it makes it easy to re-create the model with &lt;em&gt;any&lt;/em&gt; SVG. By
replacing the SVG file, maybe tweaking a couple parameters, and re-running the
OpenSCAD script you can generate a printable 3D model of any logo or image!&lt;/p&gt;

&lt;h2 id=&quot;create-your-own-3d-logo&quot;&gt;Create Your Own 3D Logo&lt;/h2&gt;

&lt;p&gt;I can’t share the source for the Strava Logo specifically since it’s a
copyrighted logo, but I &lt;em&gt;can&lt;/em&gt; share similar OpenSCAD source code for the GitHub
logo. (The GitHub logo is also copyrighted, but this is allowed by their &lt;a href=&quot;https://github.com/logos&quot;&gt;usage
guidelines&lt;/a&gt;), and Martin Woodward (VP of Developer
Relations at GitHub) even shared his own &lt;a href=&quot;https://github.com/martinwoodward/octolamp&quot;&gt;similar
project&lt;/a&gt;. Here’s my &lt;a href=&quot;https://github.com/mkasberg/3d-logo&quot;&gt;&lt;strong&gt;3D Logo
GitHub Repo&lt;/strong&gt;&lt;/a&gt; that you can use to create a
3D print of any logo or image. (And here it is on
&lt;a href=&quot;https://www.printables.com/model/464836-print-any-logo-svg-in-3d&quot;&gt;Printables&lt;/a&gt;.)
If you want to print the &lt;strong&gt;GitHub Octocat logo&lt;/strong&gt;, that’s all you’ll need. And if
you want to print &lt;strong&gt;any other logo&lt;/strong&gt;, just replace the GitHub logo SVG with the
SVG you want to print!&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="3D Printing"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/3d-printing-the-strava-logo.jpg"/>
 </entry>
 
 <entry>
   <title>3D Printing with OpenSCAD</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/03/22/3d-printing-with-openscad.html" type="text/html" title="3D Printing with OpenSCAD"/>
   
   <published>2023-03-22T18:30:00-06:00</published>
   <updated>2023-03-22T18:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/03/22/3d-printing-with-openscad</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/03/22/3d-printing-with-openscad.html">&lt;p&gt;As I mentioned in &lt;a href=&quot;/blog/2023/01/11/3-months-of-3d-printing.html&quot;&gt;my last blog post&lt;/a&gt;, I’ve been getting into 3D printing
recently. The deeper I get into the 3D printing world, the more I become
interested in designing my own 3D models. It’s cool to print a model that
someone else created, but for me it’s way more fun to print something that I
created. It scratches my itch to “build something out of nothing” in a very
physical way.&lt;/p&gt;

&lt;p&gt;There are a wide variety of 3D modeling applications, and I experimented with
several as I learned more about 3D printing. Some are CAD programs that are
ideal for designing precise parts, others come with more artistic features for
designing life-like models. For a while I was using
&lt;a href=&quot;https://www.tinkercad.com/&quot;&gt;Tinkercad&lt;/a&gt;, and I still think that’s a great place
to start for anyone new to 3D printing. But more recently, I’ve developed a
strong affection for &lt;a href=&quot;https://openscad.org&quot;&gt;OpenSCAD&lt;/a&gt;. OpenSCAD is CAD-oriented
software that describes itself as a “3D compiler”. Rather than using an
interactive modeling UI, you write a script that produces a 3D model.  As such,
it’s well-suited to models that contain patterns and geometric shapes, since
these are relatively easy to produce with a script compared to a UI.  OpenSCAD
is also well-suited to “parametric” and open source models – the model is just
a text file, so you can even use git for version control! As someone with a
programming background, I found it very natural and fun to work with.&lt;/p&gt;

&lt;p&gt;If you’re curious about OpenSCAD, I’d encourage you to give it a try. The &lt;a href=&quot;https://openscad.org/cheatsheet/index.html&quot;&gt;cheat
sheet&lt;/a&gt; has everything you’ll need to
get started. There are commands that generate geometric primitives (like cubes
and spheres) and commands that add or subtract one shape from another. Once you
know about six commands (translate, difference, rotate, and a few primitives),
you already know enough to build some really intricate models. There aren’t many
commands to learn, and once you get a feel for how OpenSCAD works it’s really
intuitive!&lt;/p&gt;

&lt;h2 id=&quot;customizable-hex-coaster&quot;&gt;Customizable Hex Coaster&lt;/h2&gt;

&lt;p&gt;The first project I modeled with OpenSCAD with it made me fall in love with it.
I wanted to add a few 3D printed decorations around my desk, and I though a
coaster would be a good place to start. A 3D printed coaster is a useful item
that’s also simple enough that I could design it myself from sctatch. I came up
with an idea to use a tiled hexagonal pattern, and the project seemed like a
great fit for OpenSCAD. It was simple enough to be an achievable goal for
someone new to the program, but hard enough to be interesting. My completed
&lt;a href=&quot;https://www.printables.com/model/385391-customizable-hex-coaster&quot;&gt;Customizable Hex
Coaster&lt;/a&gt; model
is available on Printables (and
&lt;a href=&quot;https://github.com/mkasberg/hex-coaster&quot;&gt;GitHub&lt;/a&gt;) if you want to check it out
for yourself.&lt;/p&gt;

&lt;p&gt;Building the model in OpenSCAD was a lot of fun! I was a little surprised that I
ended up using a decent amount of high-school level trigonometry to make
everything fit right, and while that might sound daunting I actually really
enjoyed it. It felt a lot like solving a crossword or sudoku, using a muscle I
hadn’t exercised in a while. And it was cool to see a physical application of
trig.  After doing trig with hexagons for a couple hours, they’ve become my new
favorite shape! Here are a few interesting properties I discovered along the
way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The length of an edge of a hexagon is exactly half the length of it’s longest centerline&lt;/li&gt;
  &lt;li&gt;You can break a hexagon down into 6 equilateral triangles, and those can be further broken down into 30-60-90 right triangles&lt;/li&gt;
  &lt;li&gt;You can use trig to find the proportion of a hexagon’s height to its width, or length of an edge to height&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/3d-printing-with-openscad/Hexagon.svg&quot; alt=&quot;A hexagon&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For me, there’s something very satisfying about getting the numbers exactly
right so that you can create a repeating pattern of hexagon tiles. It’s trickier
than you might initially expect! For example, here are some of the challenges I
faced along the way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Figuring out how to make a hexagon in OpenSCAD&lt;/li&gt;
  &lt;li&gt;Calculating the height of a hexagon from its width&lt;/li&gt;
  &lt;li&gt;Accounting for the thickness of the walls in the pattern&lt;/li&gt;
  &lt;li&gt;Figuring out how to tile a hexagon pattern within a hexagon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notably, I didn’t Google for the solution to any of these, which was refreshing
– I had all the tools I needed to figure this out between some basic trig
knowledge and the OpenSCAD cheat sheet.&lt;/p&gt;

&lt;h2 id=&quot;onward&quot;&gt;Onward!&lt;/h2&gt;

&lt;p&gt;Building the hexagon coaster was fun, and it was a great intro to OpenSCAD. I
have the coaster sitting on my desk, and I use it almost every day! But I have
some even cooler projects coming down the pipeline. I’ve been experimenting more
with OpenSCAD, and I’m working on an idea to incoporate real world data into my
OpenSCAD scripts. So hopefully I’ll have more cool projects to share in a couple
months!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="3D Printing"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/3d-printing-with-openscad.jpg"/>
 </entry>
 
 <entry>
   <title>3 Months of 3D Printing</title>
   
   <link href="https://www.mikekasberg.com/blog/2023/01/11/3-months-of-3d-printing.html" type="text/html" title="3 Months of 3D Printing"/>
   
   <published>2023-01-11T18:30:00-07:00</published>
   <updated>2023-01-11T18:30:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2023/01/11/3-months-of-3d-printing</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2023/01/11/3-months-of-3d-printing.html">&lt;p&gt;I got my first 3D printer about three months ago, and now that I’ve had it for a
little while I wanted to write about my experience with it so far. Learning 3D
printing has been a blast, but I faced some surprises and challenges along the
way. It felt a bit like I was thrown into the deep end when I got started, but
after about three months I finally feel like a have a good handle on the basics,
and I’ve finshed several different kinds of projects. If you’re curious about
3D printing or thinking about getting into it yourself, maybe I can provide some
inspiration and insight.&lt;/p&gt;

&lt;h2 id=&quot;thrown-into-the-dep-end&quot;&gt;Thrown Into The Dep End&lt;/h2&gt;

&lt;p&gt;My wife got me a &lt;a href=&quot;https://www.prusa3d.com/category/original-prusa-mini/&quot;&gt;Prusa MINI+ 3D
Printer&lt;/a&gt; for my last
birthday. She got me the unassembled kit (which I asked for), so my first
experience with 3D printing was figuring out how to build the darn thing. When I
opened the box and saw hundreds of parts sorted into multiple stages of packets,
I knew I had my work cut out for me. I’d imagined spending about an hour putting
it together but this was going to take &lt;em&gt;days&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Got a &lt;a href=&quot;https://twitter.com/Prusa3D?ref_src=twsrc%5Etfw&quot;&gt;@Prusa3D&lt;/a&gt; Mini printer kit! Excited to get this put together! &lt;a href=&quot;https://t.co/YDY01jTW0J&quot;&gt;pic.twitter.com/YDY01jTW0J&lt;/a&gt;&lt;/p&gt;&amp;mdash; Mike Kasberg (@mike_kasberg) &lt;a href=&quot;https://twitter.com/mike_kasberg/status/1566851202859446272?ref_src=twsrc%5Etfw&quot;&gt;September 5, 2022&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;I assembled the printer in about six hours total, spread over the course of a
week. I like to say this was my first experience with 3D printing because I’ve
found that a lot of the things you do and challenges you face when you’re 3D
printing are basically the same as when you’re assembling a precise machine from
a bunch of small plastic parts. It seems quick and easy at a surface level but
you quickly learn about all the minute details that are important. Perhaps
another reason this assembly experience felt so representative of the 3D
printing world is that the Prusa MINI itself is made largely from 3D printed
parts. That’s right – this printer can (partially) print itself. (Its origins
trace back to a project called &lt;a href=&quot;https://reprap.org/wiki/Prusa_i3_MK2&quot;&gt;RepRap&lt;/a&gt;,
whose goal was to create a 3D printer that could clone itself.) So when you
assemble a Prusa printer from a kit, you’re literally assembling 3D printed
parts. And that’s exactly what you spend a lot of your time doing when you get
into 3D printing.&lt;/p&gt;

&lt;p&gt;Although I was initially overwhelmed by the sheer number of parts to assemble, I
made progress by taking things one step at a time, and finishing the assembly
began to feel more achievable once I got started. I learned several tricks along
the way (like how to get metal nuts in the correct position inside plastic
parts), and leaned heavily on my experience with hand tools (and a feel for
things like screw and belt tension that can only really come from experience).
In the end, it felt great to finish the assembly and know that I’d built
something with my own hands. It felt a lot like building Ikea furniture, but
more precise and complex.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3-months-of-3d-printing/prusa-mini.jpg&quot; alt=&quot;My completed Prusa MINI+ printer&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;fun-easy-prints&quot;&gt;Fun, Easy Prints&lt;/h2&gt;

&lt;p&gt;After getting the printer assembled and calibrated, I found some relatively
simple prints to try. (This is probably where most people start if they buy an
assembled printer.) I printed a &lt;a href=&quot;https://www.printables.com/model/178035-cute-mini-octopus&quot;&gt;cute mini
octopus&lt;/a&gt; and a &lt;a href=&quot;https://github.com/DrLex0/print3D-FlexiRex&quot;&gt;Flexi
rex&lt;/a&gt; to give to my toddler. These
were great prints to help me get acquainted with my printer since they’re
print-in-place models. (No assembly required!) They printed relatively quickly
and easily. It was cool to see how the printer is able to create a model that
can have joints in it. And small, relatively simple prints like this were a
great way to develop a deeper understanding my 3D printer and some of its
quirks.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3-months-of-3d-printing/simple-prints.jpg&quot; alt=&quot;Some simple 3D prints&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;assembly-required&quot;&gt;Assembly Required&lt;/h2&gt;

&lt;p&gt;When I first got my 3D printer, I didn’t know exactly what I’d be able to print
but I was excited to find things to try. I’d heard that you could make 3D
printed Nerf guns, and I thought it would be fun to give that a shot.
Unfortunately, I quickly learned that it’s not as easy as it sounds. When
someone says, “you can make a 3D printed Nerf gun,” you might (naievly) think
you can just download a model from the internet, press print, and have a working
gun several hours later. In reality, things are much more complex.&lt;/p&gt;

&lt;p&gt;Even finding models to print was somewhat challenging. A quick search for “Nerf
gun” on &lt;a href=&quot;https://www.printables.com/search/all?q=nerf%20gun&quot;&gt;Printables.com&lt;/a&gt;
yielded few good results.  I found a mix of rubber band guns, Nerf gun mods
(like scopes), and other accessories (like targets), but no 3D printable
functional Nerf guns like I’d imagined. In my search, I learned about several
other websites where you can download 3D models.
&lt;a href=&quot;https://www.thingiverse.com/search?q=nerf+gun&amp;amp;page=1&amp;amp;type=things&amp;amp;sort=relevant&quot;&gt;Thingiverse.com&lt;/a&gt;
seemed to have a wider selection of models than
&lt;a href=&quot;https://www.printables.com/&quot;&gt;Printables.com&lt;/a&gt;, but still yielded few good
results for Nerf guns. Lots of Google and YouTube searches helped me figure out
where to find the models and information I was really looking for.  There really
are great 3D-printable high-performance Nerf guns out there if you know where to
look. Captainslug has several great designs available on
&lt;a href=&quot;http://www.captainslug.com/&quot;&gt;captainslug.com&lt;/a&gt; and
&lt;a href=&quot;https://www.thingiverse.com/captainslug/designs&quot;&gt;Thingiverse&lt;/a&gt;. There are other
creators as well – &lt;a href=&quot;https://www.frontlinefoam.com/free-stl-file-download/&quot;&gt;Frontline Foam’s STL file
list&lt;/a&gt; shows many of the
available designs. Ultimately, I chose to print and build a &lt;a href=&quot;https://github.com/118design/ZINC&quot;&gt;Zinc
[2.0]&lt;/a&gt; from &lt;a href=&quot;https://118.design/&quot;&gt;118.design&lt;/a&gt;
since it seemed relatively simple compared to many of the bigger guns, but also
very well designed.&lt;/p&gt;

&lt;p&gt;Building the Zinc Nerf blaster was an experience that felt a lot like assembling
the Prusa MINI printer. Everything took longer and was more difficult than I’d
anticipated. The gun is made from 14 printed parts. I rarely did more than one
print per day (and some days didn’t have time to do any), so it took me more
than two weeks just to print the parts I needed. Once all my parts were printed
and I began assembling them, I discovered the assembly was more complicated than
I imagined, and often required sanding parts or adding grease to make things fit
right and operate smoothly. And this is true for 3D printing in general – many
parts require sanding, tweaking, and assembly after they come off the printer.
In the end, I probably spent nearly a month printing and assembling my Zinc
blaster, but it did turn out &lt;em&gt;really&lt;/em&gt; cool. It operates well and shoots faster
than any Nerf gun you can buy off the shelf. It was a fun experience, but was
also a huge time investment. I might print another Nerf gun one day, but next
time I’ll prepare myself for a much bigger time commitment.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3-months-of-3d-printing/zinc.jpg&quot; alt=&quot;My Zinc [2.0] dart blaster&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;replacement-parts&quot;&gt;Replacement Parts&lt;/h2&gt;

&lt;p&gt;Printable replacement parts tend to come up pretty often in conversations about
3D printing. It’s easy to imagine a world where a part for coffee machine breaks
and you fix it with a 3D printed part without ever leaving your home. But in my
experience, like a lot of other 3D printing ideas, this doesn’t work as well in
practice as it does in your imagination. In practice, few parts are actually
available. Random companies don’t just publish 3D files for all their parts. So
unless you happen to be really lucky, you probably can’t find a file for the part
you need. You might be able to design a part yourself, but (again) that’s a
bigger time investment than you might expect.&lt;/p&gt;

&lt;p&gt;A few weeks ago, I designed and printed a 3D part from scratch for my first
time. A little plastic clip had broken on one of my toddler’s toys, and I
thought I might be able to make a replacement. The part was relatively simple,
and modelling it from scratch seemed like a great way to try printing my own 3D
model for the first time. I didn’t know where to start – I didn’t even know
what software I could use to make files that would work with my printer. But I
did some Googling and learned as I went. I ended up using
&lt;a href=&quot;https://www.tinkercad.com/&quot;&gt;Tinkercad&lt;/a&gt; to model my part, and it worked great. I
think Tinkercad is a great program to start with because it’s simple and easy to
learn but has just enough features that it can be pretty powerful too. (And of
course, it’s free.) In Tinkercad, things work very much like they do in the
real world, which makes it intuitive. You can combine simple shapes to make more
complex shapes, and you can also remove material using simple shapes. I also
recently discovered &lt;a href=&quot;https://openscad.org/&quot;&gt;OpenSCAD&lt;/a&gt;, and plan on trying it
next time I model something, but I only think I’ll be comfortable with it
because of my programming background and it might be more difficult for
non-programmers to work with.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3-months-of-3d-printing/wagon.jpg&quot; alt=&quot;A wagon with a 3D printed replacement clip&quot; /&gt;&lt;/p&gt;

&lt;p&gt;While a lot of early adopters of 3D printing technology are technical enough to
have the knowledge (and desire) to make their own models, I think this’ll become
less true as 3D printing becomes more common. The average consumer will want to
download a file and print it without thinking about the technical aspects, and
might not have the knowledge (or desire to learn) to create their own models. So
while it’s fun to imagine a world where you can download and print a replacement
part for anything that breaks, I think that’s probably not likely any time
soon. Still, we’re making progress in that direction, and I’m really excited to
see how the brand profiles on
&lt;a href=&quot;https://blog.prusa3d.com/starting-a-3d-printing-revolution-introducing-official-brand-profiles-on-printables-com_73408/&quot;&gt;Printables.com&lt;/a&gt;
make it easier to find “open source” parts.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;3D printing doesn’t (yet) fulfill the sci-fi fantasies of printing anything you
want quickly and easily, without any extra work. But it still lets you do really
cool things. With only 3 months of 3D printing experience, I’ve already created
some really cool stuff.  And I’m most excited that owning a 3D printer unlocks
the ability for me to physically make anything I can imagine (as long as I’m
willing to put in the work).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3-months-of-3d-printing/tinkercad.jpg&quot; alt=&quot;A 3D printed part in front of the model in Tinkercad&quot; /&gt;&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="3D Printing"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/3-months-of-3d-printing.jpg"/>
 </entry>
 
 <entry>
   <title>I Installed My Own Coax Cable for My Internet Modem (and You Can Too)</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/12/22/i-installed-my-own-coax-cable.html" type="text/html" title="I Installed My Own Coax Cable for My Internet Modem (and You Can Too)"/>
   
   <published>2022-12-22T11:30:00-07:00</published>
   <updated>2022-12-22T11:30:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/12/22/i-installed-my-own-coax-cable</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/12/22/i-installed-my-own-coax-cable.html">&lt;p&gt;Like in many houses, our cable modem was an eye sore on a shelf in our living
room for many years. After all, that’s where the coax outlet for a TV always is
right? The location wasn’t ideal. People tend to put their internet cable modem
near their TV because that’s where the coax outlet happens to be, &lt;em&gt;not&lt;/em&gt; because
that’s where they actually want their internet modem. I wanted to move our cable
modem and wifi router to our basement mechanical room to get them out of the
living room. Moving the internet equipment to our basement would give me more
room for additional equipment near the router. But our basement didn’t have any
coax outlets! I learned to &lt;strong&gt;run coax cable myself&lt;/strong&gt; so I could move all our
networking equipment to the basement. I’m glad I did, because I love the new
setup and it wasn’t hard to do!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/i-installed-my-own-coax-cable/basement.jpg&quot; alt=&quot;My router on a shelf in the basement&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s really nice having our networking equipment in the basement mechanical
room. It might not be a big deal to have a single modem/router combo on a shelf
somewhere, but I’ve already added some additional equipment to my home network
since I moved the modem, and I want to add more in the near future. I currently
have a cable modem, a separate TP-Link wifi router, and a Pi-hole. And I’m
planning to add a NAS. And probably more equipment I haven’t thought of yet.
Having a dedicated space for all this networking equipment in the mechanical
room in the basement is a big benefit because it gives me plenty of room to
expand and makes our living room look less cluttered.&lt;/p&gt;

&lt;p&gt;I was a little intimidated by running coax cable myself for the first time, but
it turned out to be very easy to do, and you don’t need many special tools. I
think any homeowner comfortable with a drill could do it! The hardest part for
me was finding a good location to route the cable. Ideally, you’d run the cable
through an attic, crawlspace, or unfinished basement. I didn’t have access to
any of these from our (small) basement mechanical room, so I chose to run the
coax cable along the house exterior. This is a pretty common practice in the
area where I live, and our house already had some other exterior coax so adding
one more exterior cable wasn’t a big deal. I’m happy with the result!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/i-installed-my-own-coax-cable/tools.jpg&quot; alt=&quot;Coax cable tools&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here are the tools and supplies I used:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.homedepot.com/p/Southwire-100-ft-18-RG6-Quad-Shield-CU-CATV-CM-CL2-Coaxial-Cable-in-Black-56918443/202316436&quot;&gt;100 ft. RG6 Quad Shield Coax Cable&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.homedepot.com/p/Gardner-Bender-1-4-in-Coaxial-Cable-Clamp-RG-6-20-Pack-PEC-31525WA/205588202&quot;&gt;Coax Cable Clamps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.homedepot.com/p/Klein-Tools-Coax-Cable-Installation-Tool-Set-with-Zipper-Pouch-VDV026-211/304087097&quot;&gt;Klein Coax Installation Tool
Set&lt;/a&gt;
(cutters, a stripper, and a crimper)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.homedepot.com/p/Klein-Tools-Universal-F-Compression-Connector-for-RG6-6Q-10-Pack-VDV812-606/203578626&quot;&gt;Compression
Connectors&lt;/a&gt;
(included in the tool set)&lt;/li&gt;
  &lt;li&gt;a drill&lt;/li&gt;
  &lt;li&gt;zip ties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was fortunate that I didn’t have to drill directly through an exterior wall,
but you might have to. If you do, there are several good YouTube videos that can
teach you how to drill through an exterior wall and seal the hole properly to
prevent water intrusion. The rest is pretty easy. I used a cable clamp every
several feet to hold the cable in place. I left a service loop (a few feet of
extra cable) at both ends in case I need it later, and then I cut the cable and
crimped a connector on both ends.  I chose not to use a wall plate in my
mechanical room – fewer connections along the cable means less potential signal
degradation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/i-installed-my-own-coax-cable/crimping.jpg&quot; alt=&quot;Crimping coax cable&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Learning to crimp the cable was also easier than I would have expected it to be.
The coax cable stripper gets the cable stripped to exactly the right length, and
then you just peel back the braid and crimp the compression connector. You can
&lt;a href=&quot;https://www.youtube.com/watch?v=qo8KLJcrYiU&quot;&gt;learn to crimp coax&lt;/a&gt; in 5 minutes
on YouTube if you’ve never done it before.&lt;/p&gt;

&lt;p&gt;I happened to have a free port on an existing splitter near my exterior cable
box, so I just connected my new cable to that. If you don’t have any free ports,
you’d need to either disconnect an existing cable or get a bigger splitter to
add your new connection.&lt;/p&gt;

&lt;p&gt;I wanted to make sure my new coax cable didn’t have any signal problems and was
at least as fast as my existing setup, so I ran a &lt;a href=&quot;https://www.speedtest.net/&quot;&gt;speed
test&lt;/a&gt; with my cable modem plugged directly into my
laptop (to test without wifi slowdown!) on my old and new cables. My new cable
showed nearly identical speeds to my old one, confirming that there weren’t any
problems along the new cable. Hooray!&lt;/p&gt;

&lt;p&gt;Installing my own coax cable was a great experience that turned out to be easier
than I thought it would be, and I’m glad I decided to do this myself. My new
setup is nicer, I know I did quality work, and it was actually pretty cheap to
do! While this blog post isn’t a detailed step-by-step guide to installing your
own coax cable in your specific situation, I hope it can be an encouragement to
you if you’re thinking about trying. There should be enough information here
to give you a really good overview of the process, and you should be able to
fill in any missing details with some YouTube videos.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/i-installed-my-own-coax-cable.jpg"/>
 </entry>
 
 <entry>
   <title>How to Fix &quot;Pending update of firefox snap&quot;</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/12/21/how-to-fix-pending-update-of-firefox-snap.html" type="text/html" title="How to Fix &quot;Pending update of firefox snap&quot;"/>
   
   <published>2022-12-21T14:30:00-07:00</published>
   <updated>2022-12-21T14:30:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/12/21/how-to-fix-pending-update-of-firefox-snap</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/12/21/how-to-fix-pending-update-of-firefox-snap.html">&lt;p&gt;Many Ubuntu users have recently become frustrated by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pending update of
&quot;firefox&quot; snap&lt;/code&gt; message they sometimes see when using Firefox. The message
usually says something like “Close the app to avoid disruptions (13 days left)”.
This is a big annoyance for users who might not want to close the app or install
an update on that timeline, and would rather do it on their own schedule.
Fortunately, there’s an easy solution to completely avoid this problem on
Ubuntu.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/how-to-fix-firefox-crashes-on-ubuntu/update-notice.jpg&quot; alt=&quot;Close the app to avoid disruptions (13 days left)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As I wrote in a &lt;a href=&quot;/blog/2022/03/21/how-to-fix-firefox-crashes-on-ubuntu-21-10.html&quot;&gt;previous blog post&lt;/a&gt;, Ubuntu 21.10+ changed
the distribution method for Firefox from an apt package to a snap package. The
snap version of Firefox includes this forced upgrade mechanism that many users
don’t like. If you want to completely avoid the forced upgrade problem, the best
way to do so is to uninstall the Firefox snap package and install Firefox via a
different method.&lt;/p&gt;

&lt;p&gt;I think installing Firefox using the self-contained zip method (provided
directly by Mozilla) is currently the best way to install Firefox on Ubuntu.
This method will perform auto-updates using the same mechanism it uses on
Windows, installing the updates when Firefox starts (but never forcing you to
shut down a running browser). Alternatively, if you want to install Firefox
using apt, you can do so if you by adding a PPA that provides Firefox as an apt
package instead of snap. (As of Ubuntu 22.04, the official Ubuntu Firefox apt
package just installs the snap package, so you need a PPA if you want to
actually install Firefox as a .deb with apt.)&lt;/p&gt;

&lt;p&gt;Here’s how you can remove the Firefox snap package an use either method to
install a non-snap version of Firefox:&lt;/p&gt;

&lt;h2 id=&quot;remove-firefox-snap&quot;&gt;Remove Firefox Snap&lt;/h2&gt;

&lt;p&gt;Regardless which method you choose, here’s how to remove the snap:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Backup your bookmarks, extensions, and settings, if you care. For me, this
happened automatically because I was signed in to &lt;a href=&quot;https://www.mozilla.org/en-US/firefox/sync/&quot;&gt;Firefox
Sync&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Remove the snap. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo snap remove --purge firefox&lt;/code&gt; (Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--purge&lt;/code&gt; will
remove the app data, which is probably what you want since the apt package
stores its data in a different location.)&lt;/li&gt;
  &lt;li&gt;You might want to clean up after the snap version a little more – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -r
~/snap/firefox&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;install-firefox-from-self-contained-zip&quot;&gt;Install Firefox from Self-Contained Zip&lt;/h2&gt;

&lt;p&gt;Mozilla provides a &lt;a href=&quot;https://www.mozilla.org/en-US/firefox/linux/&quot;&gt;version of
Firefox&lt;/a&gt; that can run on most
Linux distributions simply by unzipping it. You can use this to install Firefox
outside Ubuntu’s package manager (using neither snap nor apt-get). As such,
Firefox should be able to update itself like it does on Windows. Detailed
instructions for this method can be found in this &lt;a href=&quot;https://support.mozilla.org/en-US/kb/install-firefox-linux#w_install-firefox-from-mozilla-builds-for-advanced-users&quot;&gt;Firefox help
article&lt;/a&gt;.
You’ll unzip Firefox to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt&lt;/code&gt;, install a symlink in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/bin&lt;/code&gt;, and add a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.desktop&lt;/code&gt; file for your desktop environment.&lt;/p&gt;

&lt;h2 id=&quot;install-firefox-with-apt&quot;&gt;Install Firefox with apt&lt;/h2&gt;

&lt;p&gt;Installing Firefox from apt can also work well – Firefox will continue working
and updating like any other apt package. Ubuntu 22.04 removed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; from
the repositories so you’ll need to add a PPA.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add the Mozilla Team PPA: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo add-apt-repository ppa:mozillateam/ppa&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Install Firefox with apt: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install firefox&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need more detailed instructions for this method, see these &lt;a href=&quot;https://www.omgubuntu.co.uk/2022/04/how-to-install-firefox-deb-apt-ubuntu-22-04&quot;&gt;OMG!
Ubuntu!&lt;/a&gt;
instructions.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I’ve been running Firefox from a self-contained zip for almost a year now, and
I haven’t had any problems with it. It’s been much more stable for me than the
snap version (and it doesn’t display annoying warnings telling me I have 13 days
left to update). I hope this method works as well for you as it does for me.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-to-fix-firefox-crashes-on-ubuntu.jpg"/>
 </entry>
 
 <entry>
   <title>Monitoring Gross Income with Lunch Money</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/09/19/monitoring-gross-income-with-lunch-money.html" type="text/html" title="Monitoring Gross Income with Lunch Money"/>
   
   <published>2022-09-19T19:30:00-06:00</published>
   <updated>2022-09-19T19:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/09/19/monitoring-gross-income-with-lunch-money</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/09/19/monitoring-gross-income-with-lunch-money.html">&lt;p&gt;&lt;a href=&quot;https://lunchmoney.app/?fp_ref=mike27&quot;&gt;Lunch Money&lt;/a&gt; is a great tool for
tracking your personal finances. I wrote about it a couple months ago in my
&lt;a href=&quot;#&quot;&gt;comparison of personal finance tools&lt;/a&gt;. However, the way most people use it,
it will track finances from your &lt;em&gt;net income&lt;/em&gt;. That is, it will track your
finances for the portion of your paycheck that hits your bank account – because
it uses your bank and credit card transaction history to track your finances.
And that’s fine for a lot of people, but it won’t give you a complete view of
your finances. In particular, if you’re only tracking finances from your net
paycheck, you’re probably &lt;em&gt;not&lt;/em&gt; tracking your 401(k) contributions, taxes, and
other paycheck deductions.&lt;/p&gt;

&lt;p&gt;It might not seem terribly important to track your paycheck deductions at first,
but in reality you could be blind to a large chunk of your financial picture if
you don’t. You won’t be able to track your true savings rate over time if you
don’t track how much you contribute to your 401(k). And you won’t have a
complete view of your finances if you don’t know how much money you’re spending
on taxes and other deductions. So, how can you track your gross paycheck in
Lunch Money or similar financial tools (like Mint)? The concept is relatively
simple. Each month, in addition to your net paycheck (that you’re already
tracking via deposit into your bank account), you want to add these
transactions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Each paycheck deduction (401(k) contribution, HSA contribution, etc.), as an
expense&lt;/li&gt;
  &lt;li&gt;The sum of all the above deductions, as income&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By adding these transactions each time you receive a paycheck, you can track
paycheck deductions the same way you track all your other expenses. You’ll see
your gross income in Lunch Money (or other financial app), and you’ll be able to
track things like 401(k) contributions and taxes even though they never went
through one of your automatically tracked acounts. To illustrate, let’s work
through an example. Suppose you get paid twice a month on the first and the
fifteenth and your paycheck looks like this:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Transaction&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Amount&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Gross Pay&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$3,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tax Withholding&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$(450)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;401(k) Contribution&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$(300)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Net Pay&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$2,250&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;You’d create these transactions (manually) on the first and fifteenth:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Transaction&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Amount&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Tax Withholding&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$(450)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;401(k) Contribution&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$(300)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Paycheck Deductions Income&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$750&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Manually creating those transactions each month is a little tedious, but we can
work around that. In Lunch Money, I use recurring transactions to make it easier
to recreate these transactions each paycheck. Unfortunately the process isn’t
fully automated, but recurring transactions make it quick to manually add
transactions each time I get a paycheck. To do this, I set up a recurring
transaction as described above (one for each expense, and one income transaction
for the sum of all the deductions) in Lunch Money.  Then, each time I get a
paycheck, I select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Create From Recurring&lt;/code&gt; from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add To Cash&lt;/code&gt; dropdown menu
on the Transactions page in Lunch Money, and I check the boxes for my paycheck
transactions. It would be great if Lunch Money could automatically create these
each month, but using recurring transactions isn’t too bad.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/monitoring-gross-income-with-lunch-money/example.jpg&quot; alt=&quot;An example of recurring transactions in Lunch Money&quot; /&gt;&lt;/p&gt;

&lt;p&gt;By adding these transactions to Lunch Money, I’m able to track my &lt;em&gt;gross
income&lt;/em&gt;. That’s great because it helps me build a complete understanding of how
much I’m saving (including 401(k) contributions) and how much I’m spending
(including on things like health insurance and taxes).&lt;/p&gt;

&lt;hr /&gt;

&lt;p class=&quot;text-sm&quot;&gt;I wasn’t compensated to write this article, and the views and opinions expressed
above are my own. As I’ve said before on this blog, I just enjoy writing about
great software. For this particular article, however, I &lt;em&gt;am&lt;/em&gt; using referral
links for Lunch Money above. Lunch Money is great software that I actually use
myself, and the referral links help offset some of the cost of running this
blog!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/monitoring-gross-income-with-lunch-money.jpg"/>
 </entry>
 
 <entry>
   <title>How I Updated jekyll/classifier-reborn for Ruby 3</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/07/12/how-i-updated-jekyll-classifier-reborn-for-ruby-3.html" type="text/html" title="How I Updated jekyll/classifier-reborn for Ruby 3"/>
   
   <published>2022-07-12T19:00:00-06:00</published>
   <updated>2022-07-12T19:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/07/12/how-i-updated-jekyll-classifier-reborn-for-ruby-3</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/07/12/how-i-updated-jekyll-classifier-reborn-for-ruby-3.html">&lt;p&gt;A couple months ago, I discovered that the
&lt;a href=&quot;https://github.com/jekyll/classifier-reborn&quot;&gt;classifier-reborn&lt;/a&gt; gem (a popular
Jekyll plugin to group related posts) was essentially incompatible with Ruby 3.
I use Jekyll to build some of my own websites (including this one), and I wanted
to be able to upgrade to Ruby 3 while continuing to use classifier-reborn.
Although I was initially frustrated that classifier-reborn didn’t yet support
Ruby 3 out of the box, I realized that I might be able to implement Ruby 3
support myself and contribute back to the project since classifier-reborn is,
after all, open source software. The pull requests I submitted turned out to be
some of my biggest open-source contributions yet, and I’m proud of the work I
did!&lt;/p&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;

&lt;p&gt;Classifier-reborn is a gem that populates Jekyll’s
&lt;a href=&quot;https://jekyllrb.com/docs/variables/#site-variables&quot;&gt;related_posts&lt;/a&gt; using
&lt;a href=&quot;https://en.wikipedia.org/wiki/Latent_semantic_analysis#Latent_semantic_indexing&quot;&gt;latent semantic
indexing&lt;/a&gt;.
It works well, but performing LSI requires computationally intensive linear
algebra, and that’s very slow in pure Ruby. Fortunately, it can be made much
faster (by a factor of 100x or more) using a linear algebra implementation in C.
Using classifier-reborn with Jekyll in pure Ruby has always been prohibitively
slow, but if you installed the &lt;a href=&quot;https://www.gnu.org/software/gsl/&quot;&gt;Gnu Scientific
Library&lt;/a&gt; and the
&lt;a href=&quot;https://github.com/SciRuby/rb-gsl&quot;&gt;gsl&lt;/a&gt; gem, LSI could be performed in only a
couple seconds for small- to medium-sized blogs.&lt;/p&gt;

&lt;p&gt;Unfortunately, the &lt;a href=&quot;https://github.com/SciRuby/rb-gsl&quot;&gt;gsl&lt;/a&gt; gem has been
&lt;a href=&quot;https://github.com/SciRuby/rb-gsl/issues/67&quot;&gt;unmaintained&lt;/a&gt; for several years.
Someone merged a pull request that makes it &lt;a href=&quot;https://github.com/SciRuby/rb-gsl/pull/66&quot;&gt;compatible with Ruby
3&lt;/a&gt;, but no version including those
changes has ever been released (nearly a year later), so there’s no way to use
the gem with Ruby 3 unless you point to an unreleased commit hash on GitHub.
And the absence of a Ruby version of the gsl gem is problematic for
classifier-reborn because LSI is unusably slow without the speedup provided by
the gem.&lt;/p&gt;

&lt;p&gt;Of course, the simple solution is “just keep using Ruby 2”, but that’s an
increasingly difficult option. Ruby 2.7 is scheduled to reach its &lt;a href=&quot;https://endoflife.date/ruby&quot;&gt;end of
life&lt;/a&gt; in March 2023. It’s also increasingly
difficult to build Ruby 2 on modern systems.  Ruby 2 depends on OpenSSL 1.1,
which reaches end of life in September 2023. And OpenSSL 1.1 has already been
removed from the default Ubuntu repositories for 22.04 LTS. All of this means
that you can’t even build Ruby 2 on Ubuntu 22.04 without first finding and
installing the OpenSSL 1.1 library. In short, it’s increasingly important that
Jekyll and its dependencies work with Ruby 3 if it’s going to be easy to use on
the most current operating systems.&lt;/p&gt;

&lt;h2 id=&quot;exploration&quot;&gt;Exploration&lt;/h2&gt;

&lt;p&gt;I wanted to use Jekyll with classifier-reborn in Ruby 3 so I started
looking for a solution. I enjoy working in Ruby and I’m very comfortable in that
language because I’ve used it professionally for years. At first, I looked into
optimizing the Ruby implementation of LSI in classifier-reborn. But I soon
realized this approach would be untenable – the slow part of the code was the
part that performs singular value decomposition on a matrix, and the best way
to optimize that is to use a fast linear algebra library like GSL. But the gem
that interfaces with GSL is unmaintained. So I started looking for alternatives
to the unmaintained gsl gem that would be compatible with Ruby 3.&lt;/p&gt;

&lt;p&gt;I discoverd that the gsl gem depends on the
&lt;a href=&quot;https://github.com/masa16/narray&quot;&gt;narray&lt;/a&gt; gem, but the narray gem is
deprecated. It recommends using
&lt;a href=&quot;https://github.com/ruby-numo/numo-narray&quot;&gt;numo-narray&lt;/a&gt; instead. Reading about
this gem led me to the &lt;a href=&quot;https://github.com/ruby-numo/numo-linalg&quot;&gt;numo-linalg&lt;/a&gt;
gem, which provides a fast linear algebra implementation that’s compatible with
Ruby 3! I wondered if it would be possible to modify classifier-reborn to use
numo-linalg instead of the gsl gem…&lt;/p&gt;

&lt;p&gt;I opened the source code for classifer-reborn and started poking around.  My
goal was to find all the usages of the gsl gem and see if they could be replaced
with numo-linalg. Lucky for me, there weren’t too many places to update. I
convinced myself that it was probably possible to replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SV_decomp&lt;/code&gt; (provided
by the gsl) with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Numo::Linalg.svd&lt;/code&gt; (provided by numo-linalg) since both
accepted similar input parameters and produced similar outputs.&lt;/p&gt;

&lt;p&gt;If I was going to put in the effort to tackle this problem, I wanted to make
sure I’d be able to share my work with others. I decided it would be best to
start collaborating before writing too much code rather than trying to convince
someone to merge code I’d already written, so I asked for some input and support
&lt;a href=&quot;https://github.com/jekyll/classifier-reborn/issues/192#issuecomment-1116870759&quot;&gt;in this GitHub
issue&lt;/a&gt;
(where another developer had already asked about the possibility of using Numo
in classifier-reborn). I tried to communicate where I was headed (update
CI/tests, implement numo-linalg option, and release a new classifier-reborn
version) so there wouldn’t be any surprises. I was happy to receive a quick
reply from one of the Jekyll maintainers to let me know that he’d help get my
code merged and released!&lt;/p&gt;

&lt;h2 id=&quot;writing-code&quot;&gt;Writing Code&lt;/h2&gt;

&lt;p&gt;I wanted to be cautious with my development and I planned to rely heavily on
tests to let me know if I broke anything along the way. Unfortunately,
classifier-reborn hadn’t been heavily worked on since about 2017, so the CI
setup was a little outdated. It was testing against EOL versions of Ruby and
still used TravisCI config from before they &lt;a href=&quot;https://news.ycombinator.com/item?id=25338983&quot;&gt;stopped providing free open source
builds&lt;/a&gt;. To get CI running again,
so my &lt;a href=&quot;https://github.com/jekyll/classifier-reborn/pull/195&quot;&gt;first PR&lt;/a&gt; migrated
off TravisCI to GitHub Actions. In that PR, my goal was just to get the tests
running on current versions of Ruby to validate the changes I planned to make. I
followed that up with a &lt;a href=&quot;https://github.com/jekyll/classifier-reborn/pull/196&quot;&gt;second
PR&lt;/a&gt; to test both the pure
Ruby and GSL code paths – previously, you could run tests using GSL locally but
they didn’t run in CI. After those two PRs merged, I had some guard rails in
place and I was ready to try implementing LSI with numo-linalg.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jekyll/classifier-reborn/pull/198&quot;&gt;This PR&lt;/a&gt; contains the
code changes that make classifier-reborn work with Numo. I began by adding
code to load the numo-linalg gem if it’s installed. Classifier-reborn already
had a mechanism to try to load the gsl gem before falling back to pure Ruby, so I
followed a similar pattern to try to load numo-linalg before falling back to gsl and
then pure Ruby. With the numo-linalg gem being loaded, I implemented a code
branch to replace the GSL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SV_decomp&lt;/code&gt; call with a call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Numo::Linalg.svd&lt;/code&gt;.
Once that was done, I relied heavily on the existing tests. Several tests failed
when I first ran the suite with the numo-linalg gem, and the failing tests
highlighted all the areas of the code that needed to be updated. The existing
tests had great coverage, which I was thankful for. I used Ruby’s debugging
tools to help me understand and fix the failing test cases one by one.
Ultimately, the linear algebra operations were pretty well encapsulated, so I
only had to change about a hundred lines of code!&lt;/p&gt;

&lt;h2 id=&quot;jekyll-with-classifier-reborn-and-numo&quot;&gt;Jekyll with classifier-reborn and Numo&lt;/h2&gt;

&lt;p&gt;My efforts were successful, and &lt;a href=&quot;https://rubygems.org/gems/classifier-reborn/versions/2.3.0&quot;&gt;classifier-reborn
2.3.0&lt;/a&gt; was relased
on July 12, 2022 with support for Ruby 3 via the numo-linalg gem! Of course,
open source usually isn’t a solo effort, and I owe some thanks to
&lt;a href=&quot;https://github.com/mattr-&quot;&gt;mattr-&lt;/a&gt;, who helped get my code merged and released,
as well as to everyone who built Jekyll, classifier-reborn, Numo, and LAPACK.&lt;/p&gt;

&lt;p&gt;Want to use Jekyll and classifier-reborn with Ruby 3 yourself? Before you can
install and use the numo-linalg gem, you’ll need to install
&lt;a href=&quot;http://www.netlib.org/lapack/&quot;&gt;LAPACK&lt;/a&gt; on your system. On Ubuntu, you can do
this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt-get install liblapacke-dev libopenblas-dev&lt;/code&gt;. Once that’s
done, just add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numo-narray&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numo-linalg&lt;/code&gt; to your Gemfile. If your Gemfile
contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;classifier-reborn&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numo-narray&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numo-linalg&lt;/code&gt;,
classifier-reborn will use Numo to perform singular value decomposition, and
you’ll be able to use Jekyll with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;related_posts&lt;/code&gt; in Ruby 3!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/jekyll-classifier-reborn-ruby-3.jpg"/>
 </entry>
 
 <entry>
   <title>Mint, YNAB, Personal Capital, and Lunch Money: A Comparison of Personal Finance Tools</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/07/05/mint-ynab-personal-capital-and-lunch-money-a-comparison.html" type="text/html" title="Mint, YNAB, Personal Capital, and Lunch Money: A Comparison of Personal Finance Tools"/>
   
   <published>2022-07-05T20:00:00-06:00</published>
   <updated>2022-07-05T20:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/07/05/mint-ynab-personal-capital-and-lunch-money-a-comparison</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/07/05/mint-ynab-personal-capital-and-lunch-money-a-comparison.html">&lt;p&gt;For years, &lt;a href=&quot;https://mint.intuit.com/&quot;&gt;Mint&lt;/a&gt; was the first thing people would
recommend to anyone looking for a budgeting or personal finance tool.  More
recently, competitors like &lt;a href=&quot;https://www.ynab.com/&quot;&gt;You Need A Budget
(YNAB)&lt;/a&gt;, &lt;a href=&quot;https://www.personalcapital.com/&quot;&gt;Personal
Capital&lt;/a&gt;, and &lt;a href=&quot;https://lunchmoney.app/?fp_ref=mike27&quot;&gt;Lunch
Money&lt;/a&gt; have grown in popularity. Over
the last couple years, I’ve spent some time using all of these apps because I
wanted to explore the alternatives to Mint. Let’s explore the similarities and
differences to see what might work best for you.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#mint&quot; id=&quot;markdown-toc-mint&quot;&gt;Mint&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#who-should-use-mint&quot; id=&quot;markdown-toc-who-should-use-mint&quot;&gt;Who should use Mint?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#personal-capital&quot; id=&quot;markdown-toc-personal-capital&quot;&gt;Personal Capital&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#who-should-use-personal-capital&quot; id=&quot;markdown-toc-who-should-use-personal-capital&quot;&gt;Who should use Personal Capital?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#you-need-a-budget-ynab&quot; id=&quot;markdown-toc-you-need-a-budget-ynab&quot;&gt;You Need A Budget (YNAB)&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#who-should-use-ynab&quot; id=&quot;markdown-toc-who-should-use-ynab&quot;&gt;Who should use YNAB?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#lunch-money&quot; id=&quot;markdown-toc-lunch-money&quot;&gt;Lunch Money&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#who-should-use-lunch-money&quot; id=&quot;markdown-toc-who-should-use-lunch-money&quot;&gt;Who should use Lunch Money?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tldr&quot; id=&quot;markdown-toc-tldr&quot;&gt;Tl;dr&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;mint&quot;&gt;Mint&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/mint-ynab-pc-lunch-money/mint.jpg&quot; alt=&quot;Mint screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I used &lt;a href=&quot;https://mint.intuit.com/&quot;&gt;Mint&lt;/a&gt; for a long time, starting around 2013.
Mint was the &lt;em&gt;original&lt;/em&gt; online budgeting and personal finance tool (or at least
the first one to become popular), and it developed a large following. In large
part, I think its popularity was due to the fact that it could automatically
pull credit card transactions and keep them up to date. In the early days of
personal finance tools, that was &lt;em&gt;revolutionary&lt;/em&gt;. It was a huge improvement over
what most people had been doing before, which might have involved some kind of
manually-updated spreadsheet or physical envelopes.&lt;/p&gt;

&lt;p&gt;Mint’s core feature is the ability to aggregate transactions from different
financial institutions and categorize them into a budget. You can configure Mint
to automatically assign categories based on the merchant name, and you can use
these categories to create a monthly budget. Mint will show you how your
spending compares to your budget for all the categories in the current month.
Aside from budgeting features, Mint also has some basic reporting features
(graphs). You can break down your spending, income, assets, and debts in a few
different ways like by category or over time. Mint can also track your net
worth, though the graph is rather simplistic, showing assets and debts but with
no further breakdown.&lt;/p&gt;

&lt;p&gt;Aside from the features mentioned above, Mint also has some “tools” for finding
credit cards or loans, doing your taxes, buying stocks, or viewing your credit
score.  I put “tools” in quotes here because in my opinion, these are basically
just ads for services offered by Intuit or someone advertising with them. One of
the reasons Mint is available for free is because it contains these ads. If you
sign up for a credit card or loan through Mint, Mint will probably get some
revenue for advertising it.&lt;/p&gt;

&lt;p&gt;Today, Mint is more than 15 years old and is actually still very good at all the
things it was originally good at. Although it’s changed a little over the years,
it’s remained largely the same. Some people might like the fact that it doesn’t
change too much, but it’s also one of the things some people complain about.
Intuit, after purchasing Mint in 2009, seems not to have invested much in adding
new functionality to the app (aside from ads and connections to other Intuit
products). Lots of people wish Mint was owned by a company that appeared to care
more about improving it. Instead, it’s owned by Intuit – a company that many
people hate because they lobby to make tax laws more complicated (and probably
use profits from Mint users to do so). (In case you don’t know how Intuit
lobbies to make your life more difficult, you can read about it
&lt;a href=&quot;https://www.propublica.org/article/inside-turbotax-20-year-fight-to-stop-americans-from-filing-their-taxes-for-free&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;h3 id=&quot;who-should-use-mint&quot;&gt;Who should use Mint?&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://mint.intuit.com/&quot;&gt;Mint&lt;/a&gt; might be best for you if you don’t want to pay
for a personal finance app or if you’re just getting started with personal
finance apps and want simple, basic features. It’s easy to use Mint to
categorize transactions from different banks, assign them to categories, build a
budget, and view simple reports. But in exchange for the free service, you’re
basically giving away your financial data, so Mint might not be the right choice
if you’re privacy-minded. Also, Mint lacks advanced features, so you might want
to consider other apps if you want things like advanced categorization rules,
zero based budgeting, or more detailed reports.&lt;/p&gt;

&lt;h2 id=&quot;personal-capital&quot;&gt;Personal Capital&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/mint-ynab-pc-lunch-money/personal-capital.jpg&quot; alt=&quot;Personal Capital screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I started using &lt;a href=&quot;https://www.personalcapital.com/&quot;&gt;Personal Capital&lt;/a&gt; after
reading the &lt;a href=&quot;https://www.madfientist.com/personal-capital/&quot;&gt;recommendation&lt;/a&gt; on
the Mad Fientist blog. Like him, I wanted to get a better picture of my personal
financial portfolio to watch my net worth grow over time and to track the
performance of my investments in a single place. Personal Capital is a great
tool for this! While Mint is focused primarily on transactions and budgeting,
Personal Capital is focused primarily on investments and net worth.&lt;/p&gt;

&lt;p&gt;Features for tracking investments and net worth make Personal Capital stand out
from other tools. For example, you can break down your net worth over time by
investment type or account to see how it’s affected by your investments, loans,
cash, and other assets. Personal Capital also has useful features for stock
investors. You can compare your performance to different indexes. You can view
your asset class allocation across all your accounts to get a better
understanding of your total portfolio. And you can use a fee analyzer to see how
much your investments might be losing to fees.&lt;/p&gt;

&lt;p&gt;Personal Capital offers some pretty great investment tracking features, and you
might be wondering how they can offer the product for free… In some ways,
their business model is similar to Mint, though they seem a little more
restrained in their use of ads. Primarily, you’ll see unobtrusive ads that offer
you the services of a financial advisor. But there’s little doubt that they’re
using your data to understand what kind of value you might bring as a client.&lt;/p&gt;

&lt;h3 id=&quot;who-should-use-personal-capital&quot;&gt;Who should use Personal Capital?&lt;/h3&gt;

&lt;p&gt;I’d recommend &lt;a href=&quot;https://www.personalcapital.com/&quot;&gt;Personal Capital&lt;/a&gt; to someone
who wants to track the performance of their investments and net worth in a
single place. And it doesn’t have to be the only service you use! Mint and
Personal Capital work quite well together since they’re both free. Mint is
better for budgeting while Personal Capital has more features for investments.
But I’d also note that because Personal Capital’s a free service, you should
assume they’re going to use your data (e.g. for ads) – even if their ads are
primarily for their own investment products and advisors. Like Mint, Personal
Capital might not be the right choice for you if you’re privacy concious.&lt;/p&gt;

&lt;h2 id=&quot;you-need-a-budget-ynab&quot;&gt;You Need A Budget (YNAB)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/mint-ynab-pc-lunch-money/ynab.jpg&quot; alt=&quot;You Need A Budget screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A couple years ago, I began to &lt;a href=&quot;/blog/2020/10/23/unsubscribe-me.html&quot;&gt;care a little more about privacy&lt;/a&gt;. I realized that most of the time, when companies
give something away for free, it’s because they want my time, money, and
attention, and they want to use my data to target me with advertising. (And if
the company is Intuit, they’re probably using profits from my data to make my
taxes more complicated.) From that perspective, I’d prefer to pay a small fee
for access to a product or service rather than getting it for free. If I’m
paying to use the product, the developers should be incentivized to make the
product best for me rather than to make the product best for advertisers. So I
became more interested in personal finance apps that require a subscription, and
I found that YNAB was one of the most frequently recommended.&lt;/p&gt;

&lt;p&gt;People love &lt;a href=&quot;https://www.ynab.com/&quot;&gt;YNAB&lt;/a&gt;. It has an almost cult-like
following. So I was excited to try it and see what all the hype was about. I
started a free trial, but the software seemed a little confusing and tedious.
YNAB takes a highly opinionated view of budgeting that follows a couple rules,
as outlined in their &lt;a href=&quot;https://www.youtube.com/watch?v=kcf7NDdhFsk&quot;&gt;intro
videos&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Give every dollar a job&lt;/li&gt;
  &lt;li&gt;Embrace your true expenses&lt;/li&gt;
  &lt;li&gt;Roll with the punches&lt;/li&gt;
  &lt;li&gt;Age your money&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They call this concept &lt;a href=&quot;https://www.ynab.com/blog/what-is-a-zero-based-budget&quot;&gt;zero based
budgeting&lt;/a&gt;.
Essentially, that means you don’t allocate dollars until you have them in your
account, and you allocate every dollar you have in your account.&lt;/p&gt;

&lt;p&gt;I understand why that approach makes sense, but it wasn’t for me. After giving
up on my first trial, I thought maybe I just needed to give YNAB more time, so I
bought a subscription and tried it for several more months. I still didn’t like
it much. I think part of the reason is that &lt;em&gt;I didn’t really want to budget&lt;/em&gt;. At
least, not in the serious way YNAB requires. I primarily wanted to see where my
spending was going so I could think about whether I was saving enough and where
I might be spending too much. I was able to get that information from YNAB, but
I was also spending a lot of time allocating my money to different budget
categories (giving every dollar a job), and it felt unnecessary and tedious
because I wasn’t changing my behavior or budget based on that info. I was making
up my budget as I went. YNAB budgeting would have been a great way to rein in
overspending, but I didn’t have an overspending problem, so it just felt
toilsome.&lt;/p&gt;

&lt;h3 id=&quot;who-should-use-ynab&quot;&gt;Who should use YNAB?&lt;/h3&gt;

&lt;p&gt;I do think that &lt;a href=&quot;https://www.ynab.com/&quot;&gt;YNAB&lt;/a&gt; is a really great tool
for someone who is serious about budgeting. If you need to get your spending
under control, I think YNAB will probably help you do that better than any other
budgeting tool available, and I think the thousands of happy YNAB users you can
find online are a testament to this. On the other hand, if you’re like me and
you want to track your expenses without spending a lot of time learning and
applying YNAB’s budgeting approach, it might not be the best tool for you.&lt;/p&gt;

&lt;h2 id=&quot;lunch-money&quot;&gt;Lunch Money&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/mint-ynab-pc-lunch-money/lunch-money.jpg&quot; alt=&quot;Lunch Money screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://lunchmoney.app/?fp_ref=mike27&quot;&gt;Lunch Money&lt;/a&gt; is a pretty new personal
finance tool. I first found out about it from a friend (who’s also a software
developer). It’s a great startup story – it was created by a
&lt;a href=&quot;https://lunchmoney.app/about&quot;&gt;solopreneur&lt;/a&gt; to solve her own personal finance
needs, and it won the &lt;a href=&quot;https://www.indiehackers.com/product/lunch-money/won-jamstack-confs-web-app-of-the-year-award--MJlL_lXAZQjHkIlOgFR&quot;&gt;Jamstack Conf Web App of the
Year&lt;/a&gt;
award in 2020.&lt;/p&gt;

&lt;p&gt;Like YNAB, I also tried Lunch Money twice. The first time I tried it, I started
a free trial and decided after a couple days that it felt like a glorified
spreadsheet and wasn’t really what I wanted. But when I gave Lunch Money a
longer try, I became increasingly impressed with it as I discovered more
features. &lt;strong&gt;I realized Lunch Money was &lt;em&gt;exactly&lt;/em&gt; what I was looking for&lt;/strong&gt;, and
was more powerful than any spreadsheet I would ever build. It does a great job
finding and matching recurring transactions, which is a pretty cool and unique
feature. It also allows you to build more advanced rules than most other
services, and I appreciate that I can do things like categorize a transaction
based on the account it belongs to. As I spent more time with Lunch Money and
got more data into the system, I kept finding more useful features. I think it
does a decent job tracking net worth (though doesn’t track stocks in the same
detail as Personal Capital), and I like the stats and trends it exposes. I find
the “Overview” to be a really good high-level representation of this month’s
spending, and I really like that it shows recurring transactions that are
expected but haven’t happened yet.&lt;/p&gt;

&lt;p&gt;I think a big reason why Lunch Money does such a great job is because it was
built by a single person who must have been frustrated with existing tools and
knew exactly what she wanted to build. Lunch Money still feels a little bit like
a startup product, but I actually like that a lot. Sometimes you run into
something that’s not quite perfect, but you can also tell that the product is
being worked on and improved. And I like that by supporting Lunch Money, I’m
supporting a solopreneur and an independent product that isn’t controlled by a
large corporation.&lt;/p&gt;

&lt;h3 id=&quot;who-should-use-lunch-money&quot;&gt;Who should use Lunch Money?&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://lunchmoney.app/?fp_ref=mike27&quot;&gt;Lunch Money&lt;/a&gt; provides budgeting tools
that are on-par with or better than Mint, and also provides good net worth tools
and reports. Perhaps most importantly, it has unique features like detecting
recurring transactions and showing them on the monthly overview. I think that
Lunch Money is the best tool to gain a deeper understanding of your own personal
finances. Lunch Money isn’t free, but I actually think that’s a good thing.
Because it requires a subscription, &lt;em&gt;you’re the customer&lt;/em&gt; and it’s focused on
providing value to you rather than profiting off your data. If you want a strict
budget to get your spending under control, YNAB might be better for you. But if
you want a more flexible budget or just want the best tool to track your
spending, Lunch Money is the way to go!&lt;/p&gt;

&lt;h2 id=&quot;tldr&quot;&gt;Tl;dr&lt;/h2&gt;

&lt;p&gt;I’m currently using &lt;a href=&quot;https://lunchmoney.app/?fp_ref=mike27&quot;&gt;Lunch Money&lt;/a&gt; to
analyze my own personal finances, and it’s the first product I’d recommend to
others since I had such a great experience with it. But if you feel like your
budget’s out of control and you really need to rein in your spending, YNAB
might be better for you. Finally, Mint and/or Personal Capital might be good if
you want a free tool, but beware that if you’re not paying for the product,
there’s a good chance the product is more focused on providing value to
companies and advertisers than providing value to you.&lt;/p&gt;

&lt;hr /&gt;

&lt;p class=&quot;text-sm&quot;&gt;I wasn’t compensated to write this article, and the views and opinions expressed
above are my own. As I’ve said before on this blog, I just enjoy writing about
great software. For this particular article, however, I &lt;em&gt;am&lt;/em&gt; using referral
links for Lunch Money above. If you choose to subscribe to lunch money using my
referral link, &lt;strong&gt;you’ll get a free month&lt;/strong&gt; and I’ll get a small referral bonus.
Lunch Money is great software that I actually use myself, and the referral links
help offset some of the cost of running this blog!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/mint-ynab-pc-lunch-money.jpg"/>
 </entry>
 
 <entry>
   <title>The Best Computer You Can Buy For $100</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html" type="text/html" title="The Best Computer You Can Buy For $100"/>
   
   <published>2022-05-07T18:30:00-06:00</published>
   <updated>2022-05-07T18:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/05/07/the-best-computer-you-can-buy-for-100</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html">&lt;p&gt;It might seem impossible to find a good computer under $100, but you actually
have more options than you probably realize. Between a Raspberry Pi, Chromebook,
or used laptop, you can probably find something that you like and that suits
your needs! Let’s take a look at some options.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;Related posts about finding the perfect computer:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2019-05-03: &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;Computer Shopping: The Ultimate Developer Laptop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2020-05-24: &lt;a href=&quot;/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html&quot;&gt;Why I Love Ubuntu As a Desktop OS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2021-08-12: &lt;a href=&quot;/blog/2021/08/12/dell-latitudes-are-great-laptops.html&quot;&gt;Dell Latitudes are Great Laptops (and they run Ubuntu well)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-09-09: &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;My $500 Developer Laptop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;h2 id=&quot;raspberry-pi&quot;&gt;Raspberry Pi&lt;/h2&gt;

&lt;p&gt;Currently priced at exactly $100 at Micro Center, the &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-400/?variant=raspberry-pi-400-us-kit&quot;&gt;Raspberry Pi
400&lt;/a&gt;
might be exactly what you’re looking for! Of course, that Pi 400 does not
include a monitor (though it does include a power cable, HDMI cable, and mouse),
so you’d have to find a monitor for free or cheap for this to be a viable option.
You could, of course, get a Pi 4 (just the board) instead of a Pi 400 (with
keyboard, etc), but if you get a complete Pi 4 kit (like the one from
&lt;a href=&quot;https://www.canakit.com/raspberry-pi-4-starter-kit.html&quot;&gt;CanaKit&lt;/a&gt;) with cables
and an SD card, and maybe a keyboard an mouse, the price difference isn’t much.
You could also buy an older Pi (like the 3B+ perhaps) to have more money for a
monitor and peripherals. In any case, the Pi 400 is a very reasonable option for
a $100 computer (assuming you have a monitor).&lt;/p&gt;

&lt;p&gt;What can you do with a Pi 400 (or most other Pis)? It will run &lt;a href=&quot;https://www.raspberrypi.com/software/&quot;&gt;Pi
OS&lt;/a&gt;, which is based on Debian – the same
Linux distribution Ubuntu is based on. You won’t be able to run macOS or
Windows, but this is much less of a problem in 2022 than it was ten years ago.
Most of the tasks you need to do are probably online anyway (Google Docs, email,
YouTube, etc). The Pi would work fine for web browsing, email, and programming,
and should even be able to play video, so it will be the thing to beat when we
consider some other options.&lt;/p&gt;

&lt;h2 id=&quot;chromebook&quot;&gt;Chromebook&lt;/h2&gt;

&lt;p&gt;Chromebooks are another good option for a sub-$100 computer. For example, you
can get this refurbished &lt;a href=&quot;https://www.walmart.com/ip/Refurbished-Samsung-11-6-Chromebook-4-32GB-XE310XBA-K01US/844979866?athbdg=L1600&quot;&gt;Samsung
Chromebook&lt;/a&gt;
for $99 from Walmart, or a refurbished &lt;a href=&quot;https://www.newegg.com/lenovo-n23/p/2S3-0005-00296?Item=9SIAMDGG6D6815&quot;&gt;Lenovo N23
Chromebook&lt;/a&gt;
for $77 from Newegg (as of May 2022). With 4GB memory, the Lenovo chromebook seems roughly
comparable to the Pi 400 as far as what the hardware should be capable of. And
of course, Chromebooks have the monitor built into the laptop so you don’t need
to worry about purchasing a monitor separately.  With these Chromebooks, you’re
somewhat limited to using ChromeOS. Which is totally fine if all you need is
internet, email, Google Docs, and other simple apps.  While it’s
&lt;a href=&quot;https://support.google.com/chromebook/answer/9145439&quot;&gt;possible&lt;/a&gt; to get a Linux
environment installed on a Chromebook like this, it’s a little tricky and some
things might not work quite right. So the Pi is probably more versatile with
software (because it runs Linux natively), and while any Chromebook limits you
to ChromeOS, it does come with a monitor and provides great portability.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;Updated August 2023:&lt;/b&gt; If you&apos;re interested in using Linux (or any other OS) on a chromebook,
check out the &lt;a href=&quot;https://chrultrabook.github.io/docs/&quot;&gt;Chrultrabook Docs&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;ebay&quot;&gt;eBay&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ebay.com/sch/i.html?_oaa=1&amp;amp;_dcat=177&amp;amp;_udhi=100&amp;amp;Operating%2520System=Linux%7CNot%2520Included%7CWindows%252010%7CWindows%252010%2520Pro%7CWindows%25207%7CWindows%2520Vista%7CWindows%2520XP%7CWindows%25207%2520Professional%7CWindows%252010%2520S%7CWindows%25202000%7CWindows%25202003&amp;amp;LH_TitleDesc=0&amp;amp;_fsrp=1&amp;amp;_sacat=0&amp;amp;_nkw=laptop&amp;amp;LH_BIN=1&amp;amp;_from=R40&amp;amp;rt=nc&quot;&gt;eBay&lt;/a&gt;
is another place you might be able to find a sub-$100 computer, and a used
computer (like from eBay) is probably the only way to find real computers for
under $100 instead of Pis and Chromebooks. Of course, ordering from eBay is a
little more risky than ordering a brand-new Raspberry Pi, so read the
descriptions carefully and check the seller’s rating before you buy anything.
But if you find a good deal, you might find something better than the Pi 400 and
more capable than a Chromebook. To start the search, I’d search for “laptop” on
eBay and look for “buy it now” items. You might look for a desktop if you
already have a monitor, but let’s assume we want a laptop for now (which will
include all peripherals in our cost). You’ll probably find a lot of chromebooks,
but we already knew you could find some chromebooks in this price range. I’d
also limit my search to “Not included”, “Linux”, or any version of “Windows” in
the OS filter.  Now we’re getting some good results!&lt;/p&gt;

&lt;p&gt;Right off the bat, you’ll notice a couple older Dell Latitudes and ThinkPads for about
$100. I like Dell Latitudes and ThinkPads because they’re well-built business
laptops that are very common, so it should be easy to find parts for them and
they should last. In this price range, &lt;em&gt;cosmetic&lt;/em&gt; damage is your friend. It’s
great if you can buy a faster laptop cheaper because it has some scratches and
cracks in the plastic.&lt;/p&gt;

&lt;h2 id=&quot;how-to-compare-performance&quot;&gt;How to Compare Performance&lt;/h2&gt;

&lt;p&gt;Comparing performance of computers like this can be tricky. CPU Ghz is &lt;a href=&quot;https://www.howtogeek.com/177790/why-you-cant-use-cpu-clock-speed-to-compare-computer-performance/&quot;&gt;basically
meaningless&lt;/a&gt;,
so it’s hard to know if one option is actually faster than another. But with
some other easy-to-find stats, you can still get a pretty good rough comparison.&lt;/p&gt;

&lt;p&gt;Start with the memory (RAM). More RAM does make the computer feel faster (it can
do more at once and needs to read/write from disk less often), but it’s also a
useful indicator of overall system performance. Nobody puts 8GB RAM in a laptop
with a crap CPU, and nobody limits the performance of a great CPU with only 1 or
2 GB RAM. For $100, I wouldn’t buy anything with less than 4GB RAM and I’d look
for 6GB or 8GB if I could find it in my price range.&lt;/p&gt;

&lt;p&gt;After memory, look at the hard disk. You want an SSD, not an HDD. SSDs were a
huge breakthrough in making systems feel faster because a system with an SSD
spends significantly less time loading the OS and software from the disk.
Assuming you don’t need tons of storage space, I’d prefer even a 64GB SSD over a
500GB HDD when I’m on a budget.&lt;/p&gt;

&lt;p&gt;Finally, after comparing RAM and disk, look at the CPUs. (We’re treating the CPU
as more of a tie-breaker here). As I said, you can’t necessarily compare CPU
speed in Ghz from one CPU to another. Instead, you need to look for CPU
benchmark scores. Simply google, for example, “i5-5200U vs Atom N455”, or
whatever CPUs you want to compare. It shouldn’t be hard to find some
benchmarking sites that compare the two you’re interested in, and this will give
you a rough idea how much faster one might be than the other.&lt;/p&gt;

&lt;p&gt;In addition to or in lieu of CPU comparison, you might also look at the monitor
specs and the year the systems were build. Newer systems will generally use less
power, have faster components, and be lighter weight. A system with 4GB RAM
built in 2016 is probably better than a similar-looking system with 4GB RAM
built in 2012.&lt;/p&gt;

&lt;h2 id=&quot;software-for-cheap-computers&quot;&gt;Software for Cheap Computers&lt;/h2&gt;

&lt;p&gt;If you’re really lucky, maybe you can find a cheap computer that is only a few
years old and runs Windows 10 well, and you’re happy with that. On the other
hand, it’s more likely that you got a computer with a wiped hard disk – no
Operating System (OS) and no license for Windows. And even if you have a
license, the computer might not be fast enough to run Windows well anyway. For
an old/slow computer, installing a lightweight version of Linux or ChromeOS
would probably be best, and you might be surprised how &lt;em&gt;fast&lt;/em&gt; this can make the
old computer feel!  So if you can’t use Windows, or if Windows is very slow,
here are some alternative operating systems that you might want to try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ubuntu&lt;/strong&gt; - The first thing I’d recommend trying is &lt;a href=&quot;https://ubuntu.com/download/desktop&quot;&gt;Ubuntu
Desktop&lt;/a&gt;. There are plenty of tutorials
online for how to install it. Ubuntu Desktop is a very popular Linux
distribution, which means it should work well and it should be easy to find help
online if you need it. But it isn’t particularly lightweight - at least 4GB
memory (RAM) is recommended. If your computer isn’t fast enough to run Ubuntu,
fear not! There are other options you can try below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lubuntu&lt;/strong&gt; - If your computer feels slow running Ubuntu (which recommends 4GB
RAM), &lt;a href=&quot;https://lubuntu.net/&quot;&gt;Lubuntu&lt;/a&gt; (which recommends only 1GB RAM) will
probably work much better!  Lubuntu is based on Ubuntu – so you have the same
foundation and access to applications – but it uses the LXDE desktop
environment, which uses fewer resources. I’d expect Lubuntu to run on nearly
anything, but if even Lubuntu seems too slow you could try
&lt;a href=&quot;https://lxle.net/sponsor/&quot;&gt;LXLE&lt;/a&gt; or &lt;a href=&quot;http://www.damnsmalllinux.org/&quot;&gt;Damn Small
Linux&lt;/a&gt;, both of which are even more optimized
for old machines than Lubuntu is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chromebook OS&lt;/strong&gt; - &lt;a href=&quot;https://www.neverware.com/freedownload#intro-text&quot;&gt;CloudReady&lt;/a&gt;
is a distribution of Chrome OS that you can install on older computer hardware
to turn it into a Chromebook. Like Ubuntu Linux, CloudReady would replace the
original Windows operating system. New Chromebooks are sold and marketed as
lightweight, low-cost computers that are great for doing things on the web.
Because even new Chromebooks don’t generally have the fastest hardware, and
because the OS is lightweight, CloudReady can usually run pretty well even on
older computer hardware. Because of this, it might be a really good option to
bring new life to an old computer if you don’t like Ubuntu, or if you just like
ChromeOS better or if it runs better on your hardware.&lt;/p&gt;

&lt;p&gt;If you can find cheap hardware and install a lightweight OS, you can bring life
back into a computer that someone else wanted to get rid of. And while this
computer probably won’t run the latest computer games with amazing graphics,
it’s almost certainly good enough to do some web browsing, word processing,
email, and maybe even some programming. So hopefully it can meet your basic
computer needs and be a stepping stone to something better!&lt;/p&gt;

&lt;h2 id=&quot;so-which-computer-is-best&quot;&gt;So Which Computer is Best?&lt;/h2&gt;

&lt;p&gt;Of course, it depends on your needs. I’d get a refurbished Chromebook &lt;em&gt;only if&lt;/em&gt;
lightweight portability is your highest priority and your needs are very basic
(so you can use ChromeOS). Otherwise, I think the Pi or an eBay laptop would be
a better deal. If it’s important to you to buy something brand new and you don’t
need the portability of a laptop, the Pi is a very good option. Personally
though, I think an eBay laptop is the best deal. Rather than running an ARM
processor on an SD card, you get a complete, portable system with a monitor
based on a normal x64 processor, and this is the most flexible and most powerful
option. It’s also the only option that stands a chance at running Windows, if
that’s important to you.&lt;/p&gt;

&lt;p&gt;In my view, the best system you can buy for under $100 is a used Dell Latitude
or Lenovo ThinkPad. From the eBay search results, it looks like you can find
something with at least 4GB RAM and an SSD, and an i3 or i5 processor from about 2013.
You can probably even find one that comes with Windows 10 and is fast enough to
be usable with that OS. My personal pick in this price range would be a &lt;strong&gt;Dell
Latitude&lt;/strong&gt;, and I’d put Ubuntu on it as described above.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/the-best-computer-you-can-buy-for-100.jpg"/>
 </entry>
 
 <entry>
   <title>How to Fix Firefox Crashes on Ubuntu 21.10+</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/03/21/how-to-fix-firefox-crashes-on-ubuntu-21-10.html" type="text/html" title="How to Fix Firefox Crashes on Ubuntu 21.10+"/>
   
   <published>2022-03-21T12:00:00-06:00</published>
   <updated>2022-03-21T12:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/03/21/how-to-fix-firefox-crashes-on-ubuntu-21-10</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/03/21/how-to-fix-firefox-crashes-on-ubuntu-21-10.html">&lt;p&gt;If you use Firefox on Ubuntu, you might have noticed that Firefox seems less
stable in Ubuntu 21.10+ (including Firefox 22.04 LTS!). By default, Ubuntu
21.10+ installs Firefox as a snap rather than as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; package.
Unfortunately, the snap version of Firefox currently suffers from several
problems that can lead to a bad user experience – crashes and slow startup time
are among the top complaints.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/how-to-fix-firefox-crashes-on-ubuntu/upgrade-screenshot.png&quot; alt=&quot;New releases of Firefox are only available through snap&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here are some of the problems currently plaguing the snap version of Firefox (as
of March, 2022):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Slow startup. It’s a well-known issue that snaps can take longer to start
than their non-snap equivalents, and the snap version of Firefox takes
noticeably longer to start than the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; version.&lt;/li&gt;
  &lt;li&gt;Crashes. When I was using the snap version of Firefox, I experienced
seemingly random crashes at least once a day. Snaps automatically update
themselves in the background, and apparently &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1737810&quot;&gt;the update process can cause
crashes&lt;/a&gt;. From the bug
discussion, it appears this might be more of a problem with snapd than with
Firefox itself. Either way, it’s a horrible user experience.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bugs.launchpad.net/ubuntu/+source/chromium-browser/+bug/1741074&quot;&gt;Inability to install Gnome Extensions&lt;/a&gt; via the browser plugin.&lt;/li&gt;
  &lt;li&gt;Other random issues, like &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1740963&quot;&gt;this
one&lt;/a&gt; where typing
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt; in the URL bar doesn’t work without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; explicitly prepended.&lt;/li&gt;
  &lt;li&gt;There’s &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=snap&quot;&gt;a snap meta bug&lt;/a&gt;
with links to various other problems with the snap distribution of Firefox.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what can you do if you’re affected by these problems? At least for now, the
best thing to do is to simply stop using the snap version of Firefox. Instead,
you can install the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; package using apt or you can download and install a
self-contained zip version built by Mozilla. After installing Ubuntu 22.04, I
personally switched to the zip version since the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; is no longer available
in the official Ubuntu repositories. The zip version is self-updating (if
installed with the right permissions) and doesn’t require adding a PPA.&lt;/p&gt;

&lt;h2 id=&quot;remove-firefox-snap&quot;&gt;Remove Firefox Snap&lt;/h2&gt;

&lt;p&gt;Regardless which method you choose, here’s how to remove the snap:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Backup your bookmarks, extensions, and settings, if you care. For me, this
happened automatically because I was signed in to &lt;a href=&quot;https://www.mozilla.org/en-US/firefox/features/sync/&quot;&gt;Firefox
Sync&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Remove the snap. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo snap remove --purge firefox&lt;/code&gt; (Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--purge&lt;/code&gt; will
remove the app data, which is probably what you want since the apt package
stores its data in a different location.)&lt;/li&gt;
  &lt;li&gt;You might want to clean up after the snap version a little more – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -r
~/snap/firefox&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;install-firefox-from-self-contained-zip&quot;&gt;Install Firefox from Self-Contained Zip&lt;/h2&gt;

&lt;p&gt;One non-snap method of installing Firefox is to use Mozilla’s binaries. Mozilla
provides a &lt;a href=&quot;https://www.mozilla.org/en-US/firefox/linux/&quot;&gt;version of Firefox&lt;/a&gt;
that can run on most Linux distributions simply by unzipping it. You can use
this to install Firefox outside Ubuntu’s package manager (using neither snap nor
apt-get). As such, Firefox should be able to update itself like it does on
Windows. Detailed instructions for this method can be found in this &lt;a href=&quot;https://support.mozilla.org/en-US/kb/install-firefox-linux#w_install-firefox-from-mozilla-builds-for-advanced-users&quot;&gt;Firefox
help
article&lt;/a&gt;.
You’ll unzip Firefox to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt&lt;/code&gt;, install a symlink in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/bin&lt;/code&gt;, and add a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.desktop&lt;/code&gt; file for your desktop environment.&lt;/p&gt;

&lt;h2 id=&quot;install-firefox-with-apt&quot;&gt;Install Firefox with apt&lt;/h2&gt;

&lt;p&gt;Installing Firefox from apt can also work well – Firefox will continue working
and updating as it always has for you in the past. This is easy to do on Ubuntu
21.10, but 22.04 removed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; from the repositories so you’ll need to add
a PPA.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;Update:&lt;/b&gt; As of Ubuntu 22.04, Firefox is no longer available as a .deb
package in the official repositories. To install Firefox as a .deb with apt in
22.04+, you&apos;ll first need to add the Mozilla Team PPA: &lt;code&gt;sudo
add-apt-repository ppa:mozillateam/ppa&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;To install Firefox from apt, just run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install
firefox&lt;/code&gt; after ensuring you’ve added the PPA.&lt;/p&gt;

&lt;p&gt;If you need more detailed instructions for this method, see these &lt;a href=&quot;https://www.omgubuntu.co.uk/2022/04/how-to-install-firefox-deb-apt-ubuntu-22-04&quot;&gt;OMG!
Ubuntu!&lt;/a&gt;
instructions.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;It’s a little unfortunate that snap is the default installation method for
Firefox in Ubuntu when there are still problems that make it difficult to use as
a daily driver (though I’m sure there are many people who are hard at work
making the experience better). It’s unclear (to me) at this point if the snap
version of Firefox will be the default in 22.04 LTS, but I hope the default
experience is better than it is today, whether that’s with the snap version or
some other approach. (And thank you to everyone working on these problems.) In
the mean time, I hope my notes above are helpful if you’re currently
experiencing problems with Firefox and need it to be more stable &lt;em&gt;today&lt;/em&gt;.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-to-fix-firefox-crashes-on-ubuntu.jpg"/>
 </entry>
 
 <entry>
   <title>How to Find a Humidifier that Actually Works</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/02/15/how-to-find-a-humidifier-that-actually-works.html" type="text/html" title="How to Find a Humidifier that Actually Works"/>
   
   <published>2022-02-15T21:00:00-07:00</published>
   <updated>2022-02-15T21:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/02/15/how-to-find-a-humidifier-that-actually-works</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/02/15/how-to-find-a-humidifier-that-actually-works.html">&lt;p&gt;Unfortunately, it’s really hard to find good advice about humidifiers on the
internet. A quick google search for something like “humidifier advice” seems to
find mostly SEO spam – things like “5 Tips When Buying a Humidifier” that
contain useless tips and affiliate links to generate ad revenue. On top
of that, it’s pretty easy to find complaints about problems with various
humidifiers (e.g. in reviews), but hard to find solutions. Humidifiers produce
dust, they’re impossible to clean, they grow mold, or they don’t actually
improve humidity. Even some of the most frequently recommended humidifiers,
like the Honeywell HCM-350, &lt;a href=&quot;https://angelalashbrook.medium.com/whats-wrong-with-the-honeywell-humidifier-f165284aeaa4&quot;&gt;are
despised&lt;/a&gt;
by some consumers.&lt;/p&gt;

&lt;p&gt;I live in Colorado, a state known for its dry air at 5,280 ft elevation. And I
like to use a humidifier at night to keep my nose and throat from getting dry
when I sleep. Over the course of the past two or three years, I’ve done a decent
amount of research (including &lt;a href=&quot;https://www.youtube.com/watch?v=oHeehYYgl28&quot;&gt;this YouTube
video&lt;/a&gt;) and I’ve tried a few
different humidifiers. I think I finally have a sense of what works, and I’m
hoping to make this information easier to find by sharing it here! If you want
to use a humidifier but don’t know where to start, or if you’ve been
disappointed with problems from a humidifier you’ve tried, maybe I can help you
find something that works!&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;humidifiers-that-dont-really-work&quot;&gt;Humidifiers that &lt;strong&gt;Don’t&lt;/strong&gt; Really Work&lt;/h2&gt;

&lt;p&gt;There are a few different types of humidifiers, and each type uses a different
mechanism to produce humidity. While there could be some special circumstances
that warrant a certain type of humidifier, I think some types are almost always
&lt;strong&gt;bad&lt;/strong&gt; for normal users at home. You should be aware of these, and understand what makes them bad, so you can avoid them.&lt;/p&gt;

&lt;h3 id=&quot;ultrasonic-humidifiers&quot;&gt;Ultrasonic Humidifiers&lt;/h3&gt;

&lt;p&gt;Somewhat confusingly, ultrasonic humidifiers are sometimes also referred to as
“cool mist” humidifiers, but they’re not to be confused with the wicking kind.
(More on that below.) Ultrasonic humidifiers essentially work by vibrating the
water at a high frequency. This shoots tiny droplets of water into the air,
which then evaporate and make the air more humid. These humidifiers are
advertised as cool mist humidifiers that don’t need a filter and are quieter
than a fan. And while that’s true, they have a &lt;strong&gt;major downside&lt;/strong&gt;. Because of the
way they shoot droplets of water into the air, they also shoot everything that’s
&lt;em&gt;in&lt;/em&gt; the water into the air – including tiny particles of dirt and dissolved
minerals. That means ultrasonic humidifiers produce a substance infamously known
as “white dust” when you use them with tap water. The minerals dissolved in the
water fall back to the floor as dust when the tiny droplets of water evaporate.
This kind of humidifier will coat a whole room with a visible layer of fine dust
particles after only a day or two (assuming you fill it with tap water).
Interestingly, the problem goes away if you use distilled water, but that’s
infeasible for most people since you can’t easily/cheaply distill that much
water yourself, and you don’t want to purchase multiple gallons of distilled
water at the store every week. In short, don’t buy an ultrasonic humidifier.&lt;/p&gt;

&lt;h3 id=&quot;warm-mist-humidifiers&quot;&gt;Warm Mist Humidifiers&lt;/h3&gt;

&lt;p&gt;Warm mist humidifiers are another type of humidifier. They operate, more or less,
by slowly boiling water. This produces steam (“warm mist”) that adds humidity to
the room. There are various methods to heat the water and make the steam, but any
warm mist humidifier does ultimately produce steam from heat. Because the
humidity comes from steam, there’s no white dust to worry about. (The minerals
dissolved in the water are left behind in the tank when the water evaporates as
steam.) But warm mist humidifiers also have some downsides. They are the least
energy efficient type of humidifier (because they basically boil water) and they
also tend to be loud (because boiling water isn’t that quiet). This type of
humidifier can sometimes be pretty cheap – for example, the &lt;a href=&quot;https://www.vickshumidifiers.com/vicks-warm-steam-vaporizer&quot;&gt;Vicks Warm Steam
Vaporizer&lt;/a&gt;. But
that humidifier (vaporizer) uses electrolysis to produce steam, and after
extended use minerals will build up inside the humidifier and it won’t work as
well. If you want a humidifier that works well for daily use, a warm mist
humidifier probably isn’t the best choice.&lt;/p&gt;

&lt;h2 id=&quot;humidifiers-that-actually-do-work-well&quot;&gt;Humidifiers that Actually Do Work Well&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/humidifiers/filter.jpg&quot; alt=&quot;A humidifier with a wicking filter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The only kind of humidifier that’s well-suited for regular home use to humidify
a room (at least in my opinion) is an evaporative humidifier with a wick (sometimes
called an evaporative cool mist humidifier). This kind of humidifier uses a wick
(sometimes called a filter) that sits in water and absorbs it. A fan blows air
against the wick, and the air becomes more humid as moisture from the wick
evaporates into the air. That’s it.  It’s not complicated, and it shouldn’t be
expensive. Some examples of humidifiers in this category are the &lt;a href=&quot;https://www.honeywellstore.com/store/products/germ-free-cool-mist-humidifier-hcm-350.htm&quot;&gt;Honeywell
HCM-350&lt;/a&gt;,
&lt;a href=&quot;https://www.vickshumidifiers.com/shop/humidifiers/vicks-filtered-cool-mist-with-invisible-moisture-quiet-operation-humidifier&quot;&gt;Vicks
VEV400&lt;/a&gt;,
and &lt;a href=&quot;https://aircareproducts.com/humidifiers/companion-cm330dwht/&quot;&gt;Aircare
Companion&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many people try an evaporative humidifier and end up &lt;a href=&quot;https://angelalashbrook.medium.com/whats-wrong-with-the-honeywell-humidifier-f165284aeaa4&quot;&gt;hating it&lt;/a&gt; because it
develops an odor or grows mold quickly. Indeed, I experienced this myself for
several months. Of course, replacing the filter solves the problem, but I often
found myself replacing the filter every week or two, and scrubbing my hands raw
while trying to get the humidifier thoroughly clean so it didn’t smell for the
next week.&lt;/p&gt;

&lt;p&gt;As it turns out, most of that is unnecessary – there’s a simple solution that
can prevent odor and mold, and will allow you to use the same filter for months,
with normal tap water. The key is to treat your water with a &lt;em&gt;bacteriostatic
treatment&lt;/em&gt;, often called simply “bacteriostat”. I use &lt;a href=&quot;https://bestairproducts.com/shop/treatment/bacteriostatic-32-fl-oz-2/&quot;&gt;BestAir
Bacteriostatic&lt;/a&gt;,
available cheaply at many local stores including Walmart. You can use any brand
you want – Aircare makes their own similar
&lt;a href=&quot;https://aircareproducts.com/accessories/1970-bacteriostat/&quot;&gt;Bacteriostat&lt;/a&gt;. In
any case, adding a few drops of this to the tap water in your evaporative
humidifier prevents mold and bacteria from growing. Problem solved! No mold, no
smell! You’ll still want to clean the humidifier from time to time, and you’ll
still need to change the filter every month or two, but with an evaproative
humidifier and bacteriostat it actually &lt;em&gt;is&lt;/em&gt; possible to use a humidifier daily
but only replace the filter every couple months. It makes your humidifier work like you would have expected it to in the first place.&lt;/p&gt;

&lt;p&gt;So, if you want to buy an evaporative humidifier, what do I recommend? Honestly,
anything that’s on sale at your local store will probably work great as long as
it uses a wick. And the cheapest bacteriostat treatment you can find will
probably get the job done. I have a &lt;a href=&quot;https://www.honeywellstore.com/store/products/germ-free-cool-mist-humidifier-hcm-350.htm&quot;&gt;Honeywell
HCM-350&lt;/a&gt;
and a &lt;a href=&quot;https://www.vickshumidifiers.com/shop/humidifiers/vicks-filtered-cool-mist-with-invisible-moisture-quiet-operation-humidifier&quot;&gt;Vicks
VEV400&lt;/a&gt;,
and they both work well. It &lt;em&gt;is&lt;/em&gt; difficult to clean the fan on my Honeywell, but
it doesn’t smell because I use bacteriostat, and I do a deep cleaning once or twice a year. Still, if you’re comparing models,
the most important thing I’d look for is a simple design that’s easy to clean.
And while I’ve never personally tried them, some
&lt;a href=&quot;https://aircareproducts.com/&quot;&gt;Aircare&lt;/a&gt; models are frequently recommended and
seem like they’d perform well.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I wasn’t compensated in any way for writing this article, and none of the links
above are affiliate links. I just enjoy writing and sharing information on my
blog, and I hope I can save someone some trouble by sharing what I’ve learned.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Reviews"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/humidifiers.jpg"/>
 </entry>
 
 <entry>
   <title>Solving Wordle with Programming</title>
   
   <link href="https://www.mikekasberg.com/blog/2022/01/11/solving-wordle-with-programming.html" type="text/html" title="Solving Wordle with Programming"/>
   
   <published>2022-01-11T17:20:00-07:00</published>
   <updated>2022-01-11T17:20:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2022/01/11/solving-wordle-with-programming</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2022/01/11/solving-wordle-with-programming.html">&lt;p&gt;&lt;a href=&quot;https://www.powerlanguage.co.uk/wordle/&quot;&gt;Wordle&lt;/a&gt; is one of the latest crazes to
hit Twitter. You might have noticed it – it’s the thing everyone’s posting with
the yellow and green boxes. Wordle is a game where you have to guess the word of
the day. Each time you guess, it’ll tell you if each letter appears at the spot
you guessed, somewhere else in the word, or nowhere in the word. You win by
guessing the correct word in 6 guesses or less.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;⚠️ Spoiler Alert!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;This blog post contains spoilers! It dissects the stragegy of Wordle, links
to code that could help you cheat, and might ruin your fun. You&apos;ve been
warned!&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Wordle is a fun problem to solve with your brain, but it’s also a fun problem to
solve with a computer! It’s actually a great “intro to cryptography” type of
problem, which is part of the reason I thought of trying to solve it with a
computer in the first place. You see, Wordle is susceptible to a concept known
in cryptography as &lt;a href=&quot;https://en.wikipedia.org/wiki/Frequency_analysis&quot;&gt;frequency
analysis&lt;/a&gt;. Some letters in the
English language appear more frequently than others, and we can use that
information to make better guesses in Wordle.&lt;/p&gt;

&lt;h2 id=&quot;how-do-we-turn-this-into-a-computer-program&quot;&gt;How do we turn this into a computer program?&lt;/h2&gt;

&lt;p&gt;Well, we start with a word list. I grabbed one from &lt;a href=&quot;https://github.com/dwyl/english-words&quot;&gt;this GitHub
repository&lt;/a&gt;. Once I had the word list, I
wrote a quick script to filter it so that it only contained 5-letter words. (All
words used in Wordle must be 5 letters.) That’s a pretty good starting point,
but we can actually do better than that. I updated my script so that it would
pre-sort the word list based on the combined frequency of the letters the word
contains. This way, when we make our guesses, we can prioritize words that
contain the most common letters.&lt;/p&gt;

&lt;p&gt;Armed with this sorted word list, I wrote a script to solve Wordle. The script
is pretty simple – about 100 lines of code! It always uses the same word to
start, and this is actually the best strategy to use even if you’re solving a
Wordle puzzle yourself with your brain. Spoiler alert! That word is
&lt;strong&gt;&lt;a href=&quot;https://www.merriam-webster.com/dictionary/serai&quot;&gt;serai&lt;/a&gt;&lt;/strong&gt; – it contains the
5 most common letters in English. From there, using our word list, the script
will eliminate words that don’t match, prioritize words with letters that do
match but are in the wrong spot (yellow), and recommend another guess. The
script tells you what to guess, you tell it the results, and it loops until you
win (or lose).&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ./solve.rb 
Welcome to the Wordle solver!
Use the following legend to enter the colored tiles.

Gray:   X
Green:  G
Yellow: Y

E.g. enter &apos;XXYGX&apos; for a row that is gray, gray, yellow, green, gray.
Use &apos;NNNNN&apos; if the solver suggests an invalid word.

Try this word: serai
Enter the Wordle result: XXYXY
Try this word: ronin
Enter the Wordle result: YXYYX
Try this word: iring
Enter the Wordle result: XGGGX
Try this word: print
Enter the Wordle result: XGGGX
Try this word: crink
Enter the Wordle result: NNNNN
Try this word: drink
Enter the Wordle result: GGGGG
Congratulations, you won!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think there are probably a few small optimizations that could be made to the
way it guesses words, but as it stands now I think I’ve already gotten &lt;a href=&quot;https://en.wikipedia.org/wiki/Pareto_principle&quot;&gt;80% of
the value with 20% of the work&lt;/a&gt;,
and that’s good enough for me. I’ve only tested this with a handful of words so
far, but it’s been able to solve each one. There are probably a few edge cases
that will break something, but I can fix those as they pop up. Is this cheating?
Yes, probably, but it was fun to build and that’s really the point. Don’t use it
to beat your friends if you don’t want an unfair advantage. Want to see my code?
It’s on &lt;a href=&quot;https://github.com/mkasberg/wordle-solver&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/solving-wordle-with-programming.jpg"/>
 </entry>
 
 <entry>
   <title>Iterating on Club Leaderboards</title>
   
   <link href="https://medium.com/strava-engineering/iterating-on-club-leaderboards-f67862cef178" type="text/html" title="Iterating on Club Leaderboards"/>
   
   <published>2021-12-17T09:00:00-07:00</published>
   <updated>2021-12-17T09:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/12/17/iterating-on-club-leaderboards</id>
   
   <content type="html" xml:base="https://medium.com/strava-engineering/iterating-on-club-leaderboards-f67862cef178">&lt;p&gt;Iteration is an important part of my development workflow, and it’s an important
part of the way we work at Strava. Over the course of my own career, I’ve
learned to really value the process of incremental development. By shipping
relatively small changes quickly, we can gather feedback, observe important
metrics, and continue the cycle with targeted improvements. Ultimately, this
helps us continually deliver athlete value on a rapid timeline.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="The Strava Engineering Blog"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/strava-blog/iterating-on-club-leaderboards.jpg"/>
 </entry>
 
 <entry>
   <title>What are Dev Containers, What are GitHub Codespaces, and Why Should You Care? A Practical Intro</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/11/06/what-are-dev-containers.html" type="text/html" title="What are Dev Containers, What are GitHub Codespaces, and Why Should You Care? A Practical Intro"/>
   
   <published>2021-11-06T11:30:00-06:00</published>
   <updated>2021-11-06T11:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/11/06/what-are-dev-containers</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/11/06/what-are-dev-containers.html">&lt;p&gt;&lt;a href=&quot;https://github.blog/2021-08-11-githubs-engineering-team-moved-codespaces/&quot;&gt;GitHub
Codespaces&lt;/a&gt;
made waves when GitHub introduced the feature several months ago. Codespaces
promises a lot. It solves configuration headaches, it creates disposable
environments, it can provide better performance than the laptop you’re working
on, and it can better enable remote collaboration. I was interested to dip my
toes in the water and see what it’s like to work with Codespaces, but that can
be a little tricky to do if you don’t have access to a GitHub org that’s paying
for Codespaces. Fortunately, as it turns out, there’s a very accessible
alternative that’s free and easy to try, and it’s a great way to get a taste of
what the Codespaces experience might be like.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; has a feature called &lt;a href=&quot;https://code.visualstudio.com/docs/remote/containers&quot;&gt;Dev
Containers&lt;/a&gt;, and it works
almost exactly like GitHub Codespaces, but it runs locally in Docker rather than in
the cloud. Of course, this means you don’t get the performance benefits that are
possible with Codespaces, but most of the other benefits still apply. In
particular, a dev container provides a disposable development environment with
all the right tools ready to go. In fact, GitHub Codespaces &lt;a href=&quot;https://docs.github.com/en/codespaces/customizing-your-codespace/configuring-codespaces-for-your-project&quot;&gt;use the
same&lt;/a&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devcontainer.json&lt;/code&gt; file a local dev container would use – a Codespace is really
just a dev container in the cloud. More recently,
&lt;a href=&quot;https://www.jetbrains.com/help/idea/connect-to-devcontainer.html&quot;&gt;IntelliJ&lt;/a&gt; and
other IDEs have also added dev container support.&lt;/p&gt;

&lt;h2 id=&quot;what-is-a-dev-container&quot;&gt;What is a Dev Container?&lt;/h2&gt;

&lt;p&gt;To really understand dev containers (or Codespaces) and the benefits they
provide, I think it’s important to understand the problem they’re trying to
solve. Just for fun, I maintain a small open source project called &lt;a href=&quot;https://mkasberg.github.io/script-seed/&quot;&gt;Script
Seed&lt;/a&gt;. It isn’t anything terribly
exciting – it’s just a collection of sample “Hello World” scripts in various
scripting languages. But because that project uses 12+ different scripting
languages, it really shows the usefulness of a dev container. I don’t have all
12+ languages from that project installed on my laptop, so I use Docker to test
it.&lt;/p&gt;

&lt;p&gt;Even before dev containers were popular, I included a
&lt;a href=&quot;https://github.com/mkasberg/script-seed/blob/364724d61ff957473502ead5a49875e390cacd58/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt;
and a
&lt;a href=&quot;https://github.com/mkasberg/script-seed/blob/364724d61ff957473502ead5a49875e390cacd58/Makefile&quot;&gt;Makefile&lt;/a&gt;
in Script Seed. The Dockerfile was an Ubuntu container that had &lt;em&gt;all&lt;/em&gt; the
supported scripting languages installed, so you could test any script in the
project. And the Makefile provided a user-friendly syntax for running the
container. This Dockerfile was, essentially, a dev container. It provided anyone
who wanted to develop Script Seed a pre-configured, disposable workspace inside
the container with all the tools needed to develop and run the project. A true
VS Code dev container takes that same principle to the next level by adding some
conventions, improving the experience, and integrating tightly with the IDE.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-a-vs-code-dev-container-or-codespace&quot;&gt;Setting up a VS Code Dev Container (or Codespace)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/what-are-dev-containers/vs-code.png&quot; alt=&quot;VS Code running in a dev container&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Recently, I took that same project (&lt;a href=&quot;https://mkasberg.github.io/script-seed/&quot;&gt;Script
Seed&lt;/a&gt;) and migrated my old homegrown
Dockerfile to a true VS Code dev container. In setting up this small example
project with a dev container, I learned some things along the way that I’ll be
able to apply when setting up larger projects with dev containers or Codespaces.
I think a small project like this that’s easy to understand provides a perfect
playground to learn about dev containers.  Here’s the
&lt;a href=&quot;https://github.com/mkasberg/script-seed/pull/74&quot;&gt;PR&lt;/a&gt; where I added the dev
container to Script Seed. I’ll walk you through what I did, which will be great
to see if you want to learn how to set up your own dev container or Codespace on
your own project.&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;https://github.com/mkasberg/script-seed/pull/74/commits/0ca88ae1517909c2b7673f14584065f29f16773f&quot;&gt;first
commit&lt;/a&gt;,
I add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devcontainer.json&lt;/code&gt; to the to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.devcontainer&lt;/code&gt;
folder and I add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/server&lt;/code&gt; script. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/server&lt;/code&gt; script is just a helper
copied from my old docker entrypoint, so let’s focus on the other two files. The
best way to begin setting up a dev container is to let VS Code generate the
files for you by selecting &lt;strong&gt;Remote-Containers: Add Development Container
Configuration Files…&lt;/strong&gt; from the command palette, as documented in &lt;a href=&quot;https://code.visualstudio.com/docs/remote/create-dev-container&quot;&gt;Create a
development
container&lt;/a&gt;.  If
there’s already a Dockerfile in your project, you’ll be prompted to start from
that Dockerfile or from a predefined configuration definition. Although I first
tried to use my existing Dockerfile, I went back and did the process again using
a predefined container, and had a better first-time experience with the
predefined container. The VS Code team has done a really nice job setting up
these predefined containers, so I’d recommend trying one of them so you can
understand all the benefits they include before trying to build your own
container. In particular, these dev containers provide a non-root user, provide
a nice Bash prompt, and install many tools you might want.&lt;/p&gt;

&lt;p&gt;Starting from the predefined &lt;a href=&quot;https://github.com/microsoft/vscode-dev-containers/tree/v0.194.3/containers/ubuntu&quot;&gt;Ubuntu
dev container&lt;/a&gt;,
I installed all the languages I needed (as I had in my other Dockerfile) and was
ready to go! I made a couple light customizations to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devcontainer.json&lt;/code&gt;
including naming my container and forwarding a port for the HTTP server to run
on.&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;https://github.com/mkasberg/script-seed/pull/74/commits/1df1e1278e1fdc36ade994b7c098f0e3b1f8f5f7&quot;&gt;second
commit&lt;/a&gt;
in my PR, I update my old Makefile to use the new dev container. Primarily, I
want to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make test&lt;/code&gt; to run tests in CI (with GitHub Actions), but this is
also a nice pattern to allow anyone who isn’t using VS Code to interact with the
dev container. Finally, in the &lt;a href=&quot;https://github.com/mkasberg/script-seed/pull/74/commits/00851264210a7ddff93aba1d2fa527a1401d25ec&quot;&gt;third
commit&lt;/a&gt;,
I update my documentation to describe the new dev container.&lt;/p&gt;

&lt;p&gt;Once the dev container is set up, using it is easy. Just open the project in VS
Code with the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers&quot;&gt;Remote
Containers&lt;/a&gt;
extension installed, and it will prompt you to re-open the project inside the
container. (You can try this yourself by cloning &lt;a href=&quot;https://github.com/mkasberg/script-seed&quot;&gt;Script
Seed&lt;/a&gt; if you want.) The first run will
be a little slow as it pulls and builds the Docker container. Subsequent starts
will be faster as long as the Docker image doesn’t need to be rebuilt, and there
are some advanced options I haven’t played with yet to improve startup time by
pre-building containers. Inside the IDE, you’ll be able to interact with tools
installed in the container, including opening a terminal in the IDE that will
get you to a shell in the container. You can specify extensions to install in
your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devcontainer.json&lt;/code&gt; file and those extensions will be automatically
installed when you open the container in the IDE. You can do things like run and
debug code inside the container using tools from the IDE. If your project
includes a web server, you can run it inside the container (in my case, the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/server&lt;/code&gt; script) and open the site in a browser on the host computer via a
forwarded port.&lt;/p&gt;

&lt;h2 id=&quot;customization&quot;&gt;Customization&lt;/h2&gt;

&lt;p&gt;One thing you might be wondering about is customization. If you’re working
primarily inside the container, how do you interact with your usual tools like
git, ssh, or vim, and how do you customize those tools if the config for the container
is shared? Fortunately, VS Code has already thought about this and has some good
solutions for dev containers. For example, there’s a mechanism to &lt;a href=&quot;https://code.visualstudio.com/docs/remote/containers#_sharing-git-credentials-with-your-container&quot;&gt;share Git
credentials&lt;/a&gt;
with the container either with the git credentials helper or with SSH agent
forwarding. VS Code Dev Containers also provides a way to &lt;a href=&quot;https://code.visualstudio.com/docs/remote/containers#_personalizing-with-dotfile-repositories&quot;&gt;customize the
container with your own
dotfiles&lt;/a&gt;.
I haven’t played with this too much yet, but I’ve started modifying &lt;a href=&quot;https://github.com/mkasberg/dotfiles&quot;&gt;my own
dotfiles&lt;/a&gt; to work better with this kind of
setup. Perhaps I’ll write more about that experience later.&lt;/p&gt;

&lt;p&gt;In summary, dev containers provide a ton of value and are relatively easy to set
up – particularly if you’re already familiar with Docker. Normally, a lot of
the friction you experience when you open a new project for the first time is
getting your development environment set up. You might need several languages,
compilers, and tools installed on your system, and you might need new extensions
installed in your IDE. That could take hours to set up, and using a dev
container removes a lot of that friction and provides a great way to manage a
consistent, disposable development environment that’s more inviting for new
users. And GitHub Codespaces take it to the next level by hosting that
environment in the cloud.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/what-are-dev-containers.jpg"/>
 </entry>
 
 <entry>
   <title>How I Modified a Thule Bike Trailer Hitch for an E-Bike with a 12mm Axle</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/09/10/how-i-modified-a-thule-bike-trailer-hitch-for-an-ebike-with-a-12mm-axle.html" type="text/html" title="How I Modified a Thule Bike Trailer Hitch for an E-Bike with a 12mm Axle"/>
   
   <published>2021-09-10T10:00:00-06:00</published>
   <updated>2021-09-10T10:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/09/10/how-i-modified-a-thule-bike-trailer-hitch-for-an-ebike-with-a-12mm-axle</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/09/10/how-i-modified-a-thule-bike-trailer-hitch-for-an-ebike-with-a-12mm-axle.html">&lt;p&gt;Thule makes several popular bike trailers, including the
&lt;a href=&quot;https://www.thule.com/en-us/bike-trailers/multisport-trailers/thule-cadence-_-1684635&quot;&gt;Cadence&lt;/a&gt;
and
&lt;a href=&quot;https://www.thule.com/en-us/strollers/double-jogging-strollers/thule-chariot-lite-2-_-10203022&quot;&gt;Chariot&lt;/a&gt;.
The trailers come with the &lt;a href=&quot;https://www.thule.com/en-us/accessories/thule-axle-mount-ezhitcht-cup-with-quick-release-skewer-_-20100796&quot;&gt;Thule
ezHitch&lt;/a&gt;
to attach easily to most quick-release or solid axle wheels, and Thule also
sells an adapter to make the ezHitch fit on thru-axle wheels. This covers most
common bicycle setups, but it leaves out many e-bikes, which often use 12mm
solid rear axles. And that’s really unfortunate because towing a trailer is a
great way to use an e-bike!  I figured out how to modify a Thule ezHitch to fit
on my e-bike with a 12mm solid axle, and I’ll show you how I did it below.&lt;/p&gt;

&lt;p&gt;The Thule ezHitch is compatible with most solid axle bicycles, but most solid
axle bicycles use a 3/8” (~10mm) axle. Larger rear axles are common on e-bikes
because many e-bike designs apply torque against the rear axle and therefore
rely on a sturdy solid axle, often with additional measures (such as anti-spin
washers) to prevent the axle from rotating against the frame. For example, my
&lt;a href=&quot;https://www.juicedbikes.com/products/crosscurrentx&quot;&gt;Juiced CrossCurrent&lt;/a&gt; e-bike
has an &lt;a href=&quot;https://support.juicedbikes.com/hc/en-us/articles/360056450892-Rear-Wheel-Removal-and-Installation-CC-Service-Guide&quot;&gt;M12x1.25 rear
axle&lt;/a&gt;
and some Rad Power Bikes also have a 12mm rear axle.  Unfortunately, Thule
doesn’t offer any adapter that will fit a 12mm axle, so Thule trailers are
incompatible with many e-bikes – unless you modify the hitch to make it work!&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;Do this at your own risk!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;The following is not officially supported by Thule. It will almost certainly
void your warranty, and could result in serious injury or death. I&apos;m not
recommending that you do this or that it&apos;s safe to do; I&apos;m just showing you what
I did.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Well, if the hole on the Thule ezHitch is too small, the obvious solution is to
make the hole a little bigger – and that’s actually pretty easy to do. You can
drill through stainless steel using a cobalt drill bit and an ordinary drill. I
picked up a &lt;a href=&quot;https://www.homedepot.com/p/Milwaukee-1-2-in-Cobalt-Red-Helix-Twist-Drill-Bit-48-89-2329/203115299&quot;&gt;1/2” cobalt drill
bit&lt;/a&gt;
for about $25 at Home Depot. 1/2” is 12.7mm, so this is slightly larger than the
axle – but 1/2” is a common drill bit size and the extra 0.7mm gives just a
little extra room for error. When researching this idea, I found a YouTube
video on the &lt;a href=&quot;https://www.youtube.com/watch?v=ExNQ1ICba8M&quot;&gt;RadCity eBike Vlog&lt;/a&gt;
that shows a similar approach for a different trailer brand – you could watch
that if you want to see what to expect.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/how-i-modified-a-thule/drilling.jpg&quot; alt=&quot;Drilling a larger hole in a Thule ezHitch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When drilling steel, you should go slower than you would if you were drilling
wood. Running the drill slowly will reduce the amount of heat that builds up,
which will reduce damage to the drill bit. It took several minutes of drilling
for me to get through the layer of steel. While you &lt;em&gt;might&lt;/em&gt; be able to use a
drill bit that isn’t cobalt, it will probably damage the drill bit. My cobalt
drill bit seems to have held up great!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/how-i-modified-a-thule/finished.jpg&quot; alt=&quot;A close up of the ezHitch on an e-bike&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When you’re done drilling, the project is finished. The whole thing only takes
about 5 minutes and you’re ready to install the hitch on your e-bike! The axle
on my Juiced CrossCurrent was long enough that the extra space for the hitch
wasn’t a problem. My hitch now fits perfectly on my e-bike, and pulling my son
around in the trailer is a blast! It’s really nice to have the extra power from
the e-bike when pulling the trailer up a hill or travelling longer distances –
the e-bike with a trailer is a great combo and I’m glad I found a solution to
make my e-bike work with a Thule trailer!&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;2024 Update&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Some people are curious to know how this has held up for me
over time. Well, it&apos;s been more than two years (probably 40+ rides), and the
trailer hitch on the e-bike continues to work great for me!&lt;/p&gt;
&lt;/div&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Tutorials"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-i-modified-a-thule.jpg"/>
 </entry>
 
 <entry>
   <title>Dell Latitudes are Great Laptops (and they run Ubuntu well)</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/08/12/dell-latitudes-are-great-laptops.html" type="text/html" title="Dell Latitudes are Great Laptops (and they run Ubuntu well)"/>
   
   <published>2021-08-12T20:30:00-06:00</published>
   <updated>2021-08-12T20:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/08/12/dell-latitudes-are-great-laptops</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/08/12/dell-latitudes-are-great-laptops.html">&lt;p&gt;I’ve written before about my own search for &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;The Ultimate Developer
Laptop&lt;/a&gt; back in 2019. I chose to buy a Dell Precision 5510, but I also mentioned a
few other laptop models that work really well with Ubuntu. Laptop series like Dell
XPS, Dell Latitude, and Lenovo ThinkPad. Well, I actually own a &lt;a href=&quot;https://www.dell.com/support/kbdoc/en-us/000125555/dell-latitude-e7450-visual-guide&quot;&gt;Dell Latitude
E7450&lt;/a&gt;,
and I’ve recently started using it a lot more often. It’s a small, lightweight
laptop made with high-quality parts, and it runs Ubuntu flawlessly. Mine doesn’t
have a discrete graphics card either, so I don’t have to bother with graphics
drivers in Ubuntu. The overall experience is super-smooth, and it puts a smile
on my face to use this computer!&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;Related posts about finding the perfect computer:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2019-05-03: &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;Computer Shopping: The Ultimate Developer Laptop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2020-05-24: &lt;a href=&quot;/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html&quot;&gt;Why I Love Ubuntu As a Desktop OS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2022-05-07: &lt;a href=&quot;/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html&quot;&gt;The Best Computer You Can Buy For $100&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-09-09: &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;My $500 Developer Laptop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;If you’ve ever asked yourself, “What laptop should I buy to run Ubuntu?”, any
Dell Latitude is probably a great choice. Whether you want to spend thousands of
dollars on the newest hardware or get a cheap laptop to experiment with Ubuntu,
there’s probably some variant of a Dell Latitude that’s exactly what you’re
looking for. I specifically recommend Dell Latitude 7xxx series computers (more
on that below), but any Latitude made in the last 8 years or so will probably
run Ubuntu very well.&lt;/p&gt;

&lt;h2 id=&quot;why-are-dell-latitude-7xxx-laptops-great-for-ubuntu&quot;&gt;Why are Dell Latitude 7xxx laptops great for Ubuntu?&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Official Support&lt;/strong&gt; – Dell supports using Linux on most Latitude laptops. New Latitude laptops are listed
on &lt;a href=&quot;https://www.dell.com/en-us/work/shop/overview/cp/linuxsystems&quot;&gt;Dell’s Linux systems
page&lt;/a&gt;. Moreover,
many Dell Latitude laptops are listed as being &lt;a href=&quot;https://ubuntu.com/certified?q=Latitude&amp;amp;limit=20&amp;amp;category=Laptop&amp;amp;vendor=Dell&amp;amp;limit=20&quot;&gt;certified with
Ubuntu&lt;/a&gt;.
And even if a model isn’t officially certified, most Latitude models work flawlessly
anyway because they share most hardware with other models that &lt;em&gt;are&lt;/em&gt; certified.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Intel Graphics&lt;/strong&gt; – You can get a Latitude with Intel graphics, and I’d
recommend doing so. Avoid Nvidia and AMD. Nvidia and AMD graphics are both a headache with Linux,
and Intel graphics (i.e. embedded graphics with no discrete graphics card) work
flawlessly, with no special configuration required. The usual Linux graphics
card headaches disappear, and it’s a much better experience as long as you
don’t really need the graphics card for gaming or something.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Everything &lt;em&gt;just works&lt;/em&gt;&lt;/strong&gt; – As long as you don’t have a graphics card, you don’t
need to install any special drivers. Wifi, Bluetooth, audio, the SD card
reader – everything should work on Ubuntu without any additional setup.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Battery Life&lt;/strong&gt; – Using Intel graphics makes the battery life significantly
better. (Are you noticing a trend about Intel graphics?) Most of my
experience with Ubuntu is on laptops with Nvidia graphics, and the battery is
usually dead in an hour or two because the laptop always runs the graphics
card. (Yes, I’m aware of Nvidia prime, but it’s also a PITA.) In contrast, I 
get 6+ hours of battery life on my E7450 (with a 6-year-old battery), which
is by far the longest I’ve ever experienced with Ubuntu.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Size&lt;/strong&gt; – The 7xxx series of Dell Latitude represents Dell’s
premium/ultrabook line. These laptops are only a little bigger than a MacBook
Air, so they’re light to carry and they fit into any bag.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Quality&lt;/strong&gt; – Latitude laptops are business-grade machines, made to last through years of
use. They feel well-built and they’re nice to work with. It’s also easy to
perform basic maintenance and replace certain parts like RAM.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Ubiquitous&lt;/strong&gt; – Dell Latitude laptops are common (widely used in business), so it’s easy to
find support. When you Google a problem (even on Ubuntu), it’s likely someone
else has experienced the same problem and found a solution. Also, it’s easy to
find replacement parts if you ever need them.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Value&lt;/strong&gt; – Latitude laptops can be relatively cheap (especially if you buy used or
refurbished), and you get great value for your money. I’m writing this
on an E7450 from 6 years ago. And even though the computer’s 6 years old, it
feels snappy and runs Ubuntu well.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;about-dell-latitudes&quot;&gt;About Dell Latitudes&lt;/h2&gt;

&lt;p&gt;“Latitude” is a line of business-grade laptops from Dell. They’ve been around
forever – Dell was making Latitudes in the ’90s. But as I said, I’d recommend
specifically looking at the 7xxx series. Dell started making this line of
laptops in 2013 with the E7440. The number scheme of Dell’s Latitudes tells you
about the computer. The first number (7xxx) tells you that this is a
premium/ultrabook laptop. The second number (x4xx) tells you that this is a 14”
laptop. You’ll also see 12” (x2xx) and 13” (x3xx) laptops, and they’re all great
(and otherwise mostly identical) – pick the size you like best. The last two
digits (xx40) tell you the model year (though it only roughly corresponds with
the year). Dell went all the way up to the E7490 in 2018, then went back to 00
with the 7400 in 2019 (though they dropped the “E”). There’s a really great
&lt;a href=&quot;https://en.wikipedia.org/wiki/Dell_Latitude&quot;&gt;Dell Latitude Wikipedia page&lt;/a&gt;
where you can find more information about the specific laptop you might be
interested in, and what changed from year to year. One of the most important
changes to note is the addition of a USB-C port on the E7x80 models.&lt;/p&gt;

&lt;h2 id=&quot;where-to-buy&quot;&gt;Where to Buy&lt;/h2&gt;

&lt;p&gt;If you want a brand-new Dell Latitude, you can order a new one directly from
Dell or from your favorite computer store. However, there’s also a great used
market for these laptops. Because they’re business-grade laptops, many
businesses upgrade their whole fleet at once (or on a rolling rotation) so
there’s a lot of 2-4 year old laptops on the used/refurbished market at great
prices!&lt;/p&gt;

&lt;p&gt;You can find used Latitudes at most of the places you’d guess, including
&lt;a href=&quot;https://www.ebay.com/&quot;&gt;eBay&lt;/a&gt;. However, I particularly like
&lt;a href=&quot;https://www.newegg.com/&quot;&gt;Newegg&lt;/a&gt; for used/refurbished laptops because of their
great search tools. For example, &lt;a href=&quot;https://www.newegg.com/p/pl?N=100006740%20601283908%20601286756%20601283909%20601283905%20601303704%20601333386%20601343209%20601283904%20601283903%20601283906%20601283907%20601303722%20601323887%20601343206%20600004343%2050010772%20601296065%20601296066%20601296059%20601183897&amp;amp;SrchInDesc=latitude&amp;amp;Order=1&quot;&gt;this
search&lt;/a&gt;
for a Dell Latitude with a 4th-gen or newer i5 or i7 processor returns tons of
good results. (I’m focused on the 7xxx series here, but the 5xxx series in those
results are also good laptops.) For under $300, you can buy a laptop with 8GB
RAM and an SSD. And while you probably won’t be playing modern video games on
it, that laptop should run Ubuntu very well and would be fine for web browsing,
email, programming, and office or school work. And in the $550 price range, you
can get a more modern laptop with a USB-C port and a faster i7 processor. Of
course, I can’t personally vouch for all these Newegg vendors, but I’ve bought
refurbished machines off Newegg a couple times in the past and I’ve always had a
good experience.&lt;/p&gt;

&lt;h2 id=&quot;how-to-install-ubuntu&quot;&gt;How to Install Ubuntu&lt;/h2&gt;

&lt;p&gt;Installing Ubuntu on a Latitude laptop is easy. You’ll want to use a USB stick
since your laptop probably doesn’t have a DVD drive. First, create a USB stick
from &lt;a href=&quot;https://ubuntu.com/tutorials/create-a-usb-stick-on-windows&quot;&gt;Windows&lt;/a&gt;,
&lt;a href=&quot;https://ubuntu.com/tutorials/create-a-usb-stick-on-macos&quot;&gt;macOS&lt;/a&gt;, or
&lt;a href=&quot;https://ubuntu.com/tutorials/create-a-usb-stick-on-ubuntu&quot;&gt;Linux&lt;/a&gt;. Then, boot
from the USB stick and &lt;a href=&quot;https://ubuntu.com/tutorials/install-ubuntu-desktop&quot;&gt;install
Ubuntu&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;I think I’ve become a little bit of a Dell fanboy in the past several years…
But if I have, it’s only because I always seem to have consistently great
experiences with Dell laptops and Ubuntu. Using Ubuntu on these machines makes
me happy. 😊  This blog post wasn’t sponsored by anyone, and I didn’t receive
any form of compensation for writing it – I just like writing about Ubuntu and
technology.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/dell-latitudes-are-great-laptops.jpg"/>
 </entry>
 
 <entry>
   <title>Docks and Ubuntu</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/08/09/docks-and-ubuntu.html" type="text/html" title="Docks and Ubuntu"/>
   
   <published>2021-08-09T18:30:00-06:00</published>
   <updated>2021-08-09T18:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/08/09/docks-and-ubuntu</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/08/09/docks-and-ubuntu.html">&lt;p&gt;Laptop docks are great! As I wrote about in &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;The Ultimate Developer Laptop&lt;/a&gt; and &lt;a href=&quot;/blog/2020/04/10/my-home-office-setup.html&quot;&gt;My
Home Office Setup&lt;/a&gt;, I’ve been
using a &lt;a href=&quot;https://www.dell.com/support/home/en-us/product-support/product/dell-thunderbolt-dock-tb16/overview&quot;&gt;Dell
TB16&lt;/a&gt;
thunderbolt dock for about two years now, and I really love it. The dock makes
it so much quicker and easier to connect my laptop to all the peripherals on my
desk. And that’s important to me because it makes it feel easy to take my work to
another part of the house or go work on the deck – I don’t feel tied down to my
desk.&lt;/p&gt;

&lt;p&gt;But recently, I’ve found myself wishing my dock could do more. In particular,
I’ve found myself wanting to be able to connect my dock to a wider variety of
computers. For example, I have a Dell Latitude E7450 that doesn’t have a
thunderbolt port, and sometimes I want to use that computer at my desk. Doing so
requires moving the cables for my keyboard, mouse, monitor, speakers, etc. from
my dock to the laptop, completely negating the purpose of the dock! My TB16 dock
works great with the laptop I bought it for (my &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;Dell Precision
5510&lt;/a&gt;),
but leaves something to be desired with other laptops where it’s less compatible
(or not compatible at all). I was curious to find out if there were any docks
compatible with Ubuntu that would allow me to connect a wider variety of
computers. And while researching different docks and testing some out, I think I
developed a better understanding of which kinds of docks work best with Ubuntu
in different circumstances.&lt;/p&gt;

&lt;h2 id=&quot;types-of-docks&quot;&gt;Types of Docks&lt;/h2&gt;

&lt;p&gt;Fundamentally, there are at least four major categories of docks available today.&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;As of May 2024, I&apos;ve updated this list to include Thunderbolt Monitors as a
subsection of Thunderbolt Docks!&lt;/p&gt;
&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#oem-docks&quot;&gt;OEM Docks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#displaylink-docks&quot;&gt;DisplayLink Docks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#thunderbolt-docks&quot;&gt;Thunderbolt Docks&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#thunderbolt-monitors&quot;&gt;Thunderbolt Monitors&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#usb-hubs&quot;&gt;USB Hubs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;oem-docks&quot;&gt;OEM Docks&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/docks-and-ubuntu/dell-dock.jpg&quot; alt=&quot;Dell OEM Dock&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OEM docks are the old-fashioned dock you probably remember from the early 2010s.
They are usually specific to a single laptop model or series, and they usually
connect to a special port on the bottom of the laptop.  These were common on
older Dell Latitudes or IBM/Lenovo ThinkPads. You’d set your laptop down on top
of the dock and by doing so connect it to the mouse, keyboard, monitor, and
other peripherals plugged into the back.  The primary problem with these docks,
of course, is that they use a custom port that’s only compatible with one model
or series of laptop, and they aren’t even really used anymore on modern
computers (they’ve basically been superseded by Thunderbolt docks). If you’re
curious about an OEM dock’s compatibility with Ubuntu, I think they’re mostly
compatible but you’ll need to look up the specific information for the laptop
and dock you care about.&lt;/p&gt;

&lt;h3 id=&quot;displaylink-docks&quot;&gt;DisplayLink Docks&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/docks-and-ubuntu/displaylink.jpg&quot; alt=&quot;A DisplayLink dock&quot; /&gt;&lt;/p&gt;

&lt;p&gt;DisplayLink Docks Are USB-2.0 or USB-3.0 docks that use the
&lt;a href=&quot;https://www.displaylink.com/&quot;&gt;DisplayLink&lt;/a&gt; protocol to provide video over USB.
In addition to providing video over USB, they also provide additional USB ports
and sometimes other ports like ethernet. Thus, you can connect all your monitors
and peripherals at once over a single USB cable! Living the dream! Of course,
there are always downsides, and for DisplayLink docks there are two major
compromises. First, the dock doesn’t typically provide power, so you’ll still
have to connect your laptop’s charger. Second, DisplayLink compresses the video
to send it over USB, so it doesn’t do well with high framerates like videos or
games. The compression algorithm optimizes for changes, so you don’t actually
notice any stutter with mouse movements, typing, or other changes where a small
screen area is updating, but you do start to notice some visual artifacts when
you try to play a fullscreen video at 1080p, for example. Overall, these are
minimal compromises in many use cases – the dock is particularly well suited
for an office or developer setting.&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more about how DisplayLink docks work with
Ubuntu, I wrote a separate blog post about &lt;a href=&quot;/blog/2021/08/07/displaylink-docks-and-ubuntu.html&quot;&gt;DisplayLink Docks and Ubuntu&lt;/a&gt; with more details.&lt;/p&gt;

&lt;h3 id=&quot;thunderbolt-docks&quot;&gt;Thunderbolt Docks&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/docks-and-ubuntu/thunderbolt-dock.jpg&quot; alt=&quot;A Thunderbolt dock&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Thunderbolt Docks use a Thunderbolt port to send video to the dock, and can
also connect other peripherals over the same connection – much like a USB-3.0
DisplayLink dock. The difference is that Thunderbolt can support native video,
so there’s no need for a DisplayLink driver (nor its compression algorithm). In
some cases (i.e. if your dock and computer both support it), you can also charge
the computer over the same Thunderbolt cable, so there’s truly only a single
cable to plug in! In many cases, this is probably the best kind of dock, but it’s
also the most expensive and requires a modern computer and dock. In my
experience, they are a little finnicky – my Dell dock won’t charge my Lenovo
laptop, though USB and video still work fine.&lt;/p&gt;

&lt;p&gt;It’s worth noting that there are some weird sub-categories of USB-C docks that
might be confused with Thunderbolt docks. (Although Thunderbolt 3 and
USB-C use the same connector, they support different things. Technically,
Thunderbolt 3 is a superset of USB-C, a faster connection over the same kind of
port.) There are some docks that support DisplayPort over USB-C but aren’t
Thunderbolt docks.  I’m sure you can also find a USB-C dock that’s neither
DisplayPort nor Thunderbolt. For example, there are some docks that use the
DisplayLink protocol over a USB-C connection. A post on the Dell community
forums about &lt;a href=&quot;https://www.dell.com/community/Laptops-General-Read-Only/Demystifying-USB-C-and-Thunderbolt-i-e-adapter-and-dock/td-p/5153006&quot;&gt;Demystifying USB-C and
Thunderbolt&lt;/a&gt;
recently made it to the front page of &lt;a href=&quot;https://news.ycombinator.com/item?id=29447216&quot;&gt;Hacker
News&lt;/a&gt; and is worth the read if
you want more detailed technical information. The post goes into detail about
how USB-C, DisplayPort, and Thunderbolt allocate lanes of data over a USB-C
connector and how docks can use those lanes differently, resulting in better or
worse video quality. It also looks at how video quality over a USB-C connection
might vary across different hardware due to other factors, like whether you
laptop has a GPU wired to the USB-C port. In general, your display quality will
downgrade to the &lt;em&gt;worst&lt;/em&gt; component in the system between your computer, monitor,
dock, and cables. Suffice it to say, looking at the connector alone won’t
confirm what kind of connection you actually have with the dock and monitor,
which can make the Thunderbolt docking experience a little confusing.&lt;/p&gt;

&lt;p&gt;But the good news is that in general, I think Thunderbolt docks tend to work
well with Ubuntu. In my personal experience, the Dell TB16 Thunderbolt dock
works flawlessly with Ubuntu on my Dell Precision 5510 laptop. Of course, since
these are both Dell products, they were designed to work well together, so your
experience might vary and you should research the compatibility of the dock and
the laptop with Ubuntu. With that being said, I think most Thunderbolt docks use
the same underlying technology and don’t typically require any additional
drivers (at least on the latest version of Ubuntu), so it would be reasonable to
expect that they work well with Linux. My general recommendation would be to get
a laptop with documented Linux support and then look for docks that are
specifically compatible with that Laptop. Even better if the dock has documented
support for Linux.&lt;/p&gt;

&lt;p&gt;I do think a Thunderbolt dock provides the smoothest experience as long as you
plan to only use one laptop with the dock (or if all the laptops you’ll use
support that Thunderbolt dock). Thunderbolt docks will probably continue to grow
in popularity well into the future.&lt;/p&gt;

&lt;h4 id=&quot;thunderbolt-monitors&quot;&gt;Thunderbolt Monitors&lt;/h4&gt;

&lt;p&gt;I originally omitted Thunderbolt monitors from this blog post, but updated it in
May, 2024 to include them. I think they’re popular enough to warrant a mention
here, and are worth considering as an option if you’re searching for a
single-cable solution that’s compatible with Linux.&lt;/p&gt;

&lt;p&gt;A Thunderbolt monitor connects directly to your computer using a DisplayPort
signal over Thunderbolt. A Thunderbolt monitor is functionally similar to a
Thunderbolt dock in that both transmit a DisplayPort signal over Thunderbolt,
and allow you to connect a monitor in addition to other (USB) accessories. The
biggest difference, of course, is that a Thunderbolt monitor eliminates the dock
entirely (and creates more free space on your desk). Some monitors can even
supply 65W power over the Thunderbolt connection to the computer, allowing you
to achieve the same single-cable experience as a Thunderbolt dock without the
dock itself! In my experience, Thunderbolt monitors are broadly compatible with
Linux (since Linux is compatible with the underlying DisplayPort, USB, and
Thunderbolt technologies), but the caveats of Thunderbolt docks I discussed
above still apply. In particular, you might run into problems when pushing
devices to their limits (i.e. max supported resolution or number of monitors),
like &lt;a href=&quot;https://x.com/dhh/status/1788220471810662615&quot;&gt;DHH did in this thread on
X&lt;/a&gt;. But as these monitors become
more popular and the hardware continues to improve, I’d expect the linux
experience to get better soon.&lt;/p&gt;

&lt;p&gt;Overall, I think a Thunderbolt monitor may be &lt;strong&gt;the best choice&lt;/strong&gt; for a
single-cable solution that connects your monitor, keyboard, mouse, speakers,
power, and other accessories to your computer if you have a modern computer and
are willing to pay for an expensive monitor. &lt;strong&gt;But&lt;/strong&gt; you need to do research to
make sure the products you use (laptop graphics card, display, and cables) are
compatible and support the required technologies (like the DisplayPort version).
(Or buy a monitor with a great return policy in case it doesn’t work.) As with
Thunderbolt docks, reading &lt;a href=&quot;https://www.dell.com/community/Laptops-General-Read-Only/Demystifying-USB-C-and-Thunderbolt-i-e-adapter-and-dock/td-p/5153006&quot;&gt;Demystifying USB-C and
Thunderbolt&lt;/a&gt;,
and then understanding the data lanes your setup will use and what resolutions
and speed are supported by that setup will help you ensure everything is
compatible.&lt;/p&gt;

&lt;h3 id=&quot;usb-hubs&quot;&gt;USB Hubs&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/docks-and-ubuntu/usb-hub.jpg&quot; alt=&quot;A USB hub&quot; /&gt;&lt;/p&gt;

&lt;p&gt;USB Hubs hardly count as docks because they don’t typically handle display
output. (Though some can pass a DisplayPort connection through them.)
Still, they’re worth a brief mention because you can use a USB hub as a cheap and
effective dock. You can connect your keyboard, mouse, other peripherals, and
sometimes even ethernet to a USB hub. Then, you’d probably only have 3 cables to
plug in to your computer – the USB hub, power, and video. Another benefit of
USB hubs is they don’t typically require any drivers. They should &lt;em&gt;just work&lt;/em&gt;
with Ubuntu, or with anything else for that matter, because it’s really just a
standard USB connection.&lt;/p&gt;

&lt;h2 id=&quot;which-dock-should-you-use&quot;&gt;Which dock should you use?&lt;/h2&gt;

&lt;p&gt;If your laptop has a Thunderbolt 3 port and you can afford to buy a thunderbolt
3 dock or monitor, that’s probably your best option. And if you’re shopping for
a new laptop and want a dock to go with it, Thunderbolt 3 is what you should
look for.  Thunderbolt docks are the most expensive docking option (and also the
newest), and they obviously won’t work with laptops that don’t have a
Thunderbolt 3 port, but they provide the best experience as long as everything’s
compatible.&lt;/p&gt;

&lt;p&gt;If a Thunderbolt dock doesn’t work for you, or if you want to connect a display
over a regular USB connection, a DisplayLink dock might be the next best option.
I’d go for a USB-A 3.0 dock since most computers that don’t have a Thunderbolt
port also don’t have a USB-C port. Games and fullscreen videos won’t work well
with DisplayLink, but you won’t notice any issues with most other types of
day-to-day computing.&lt;/p&gt;

&lt;p&gt;Finally, if you don’t want to use DisplayLink (perhaps because you need higher
framerates for videos or gaming), consider using a USB hub (for your keyboard,
mouse, ethernet, speakers, etc) and connecting your monitor directly to the
laptop. Although it’s one extra cable to plug into the laptop compared to a
DisplayLink dock, a USB hub can still provide a good experience and help with cable
organization. And a USB hub is great if you’re on a budget – you should be able
to find a cheap one for about $20.&lt;/p&gt;

&lt;h2 id=&quot;what-am-i-doing&quot;&gt;What am I doing?&lt;/h2&gt;

&lt;p&gt;I’m currently using a DisplayLink dock as a USB hub. (The DisplayLink is
irrelevant in this setup – it might as well be a cheap hub.) I’m connecting an
HDMI cable from my monitor directly to my laptop to avoid the DisplayLink lag
with videos, but I’m connecting my keyboard, mouse, and audio through the USB
dock. I use 3 different laptops at my desk right now, and one of them doesn’t
have a Thunderbolt port, so this solution is a bit of a compromise but it’s
working very well for me. I did try using video through the DisplayLink dock for
several days but ultimately found that I &lt;em&gt;do&lt;/em&gt; want a fast display connection for
videos and games, and the HDMI port is the best way for me to get that across 3
different laptops with different ports. So I have 3 cables to plug in when I
change laptops. This isn’t completely ideal, but it’s still better than a
tangled mess of 6+ cables on my desk. Next time I upgrade the whole setup (and
stop using my oldest computers), I’ll probably opt for a Thunderbolt dock or
monitor.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/docks-and-ubuntu/outlet.jpg&quot; alt=&quot;A desktop-mounted power outlet&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve also made one other big improvement for using multiple computers at my desk –
I added a &lt;strong&gt;desktop-mounted outlet&lt;/strong&gt;, and I’m finding it even more useful than I
thought I would. I can use this outlet on top of my desk to plug in my laptop
charger, which means it’s very easy to change the charger I’m using when I swap
the laptop at my desk. This is also great for my work laptop – I need to take
both the laptop and charger to work with me when I go to the office, and I don’t
want to climb behind my desk each time to unplug the charger. There isn’t
anything special about this particular outlet – you can find something similar
on Amazon for about $25.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I hope you learned something useful from reading this or that I at least gave
you some ideas or inspiration for making your own setup nicer! I’ve found that
using a dock makes my life smoother and more convenient, so it’s great that many
docking solutions work well with Ubuntu, and I hope we’ll see even better Linux
docking support in the future!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/docks-and-ubuntu.jpg"/>
 </entry>
 
 <entry>
   <title>DisplayLink Docks and Ubuntu</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/08/07/displaylink-docks-and-ubuntu.html" type="text/html" title="DisplayLink Docks and Ubuntu"/>
   
   <published>2021-08-07T18:30:00-06:00</published>
   <updated>2021-08-07T18:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/08/07/displaylink-docks-and-ubuntu</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/08/07/displaylink-docks-and-ubuntu.html">&lt;p&gt;Can you use a DisplayLink dock with Ubuntu? And, more importantly, &lt;em&gt;should you&lt;/em&gt;?
I recently researched this for a dock I wanted to buy for myself, and here’s
what I found.&lt;/p&gt;

&lt;p&gt;If you’re thinking about getting a DisplayLink dock for your laptop, you might
not even be sure if the dock will work with Ubuntu. Well, there’s good news! I
used a &lt;a href=&quot;https://plugable.com/products/ud-6950h&quot;&gt;Plugable UD-6950H&lt;/a&gt; with Ubuntu
without problems, and it seems reasonable to expect similar docks to work as
well. In fact, there’s a good chance your DisplayLink dock works with Ubuntu
even if it says it doesn’t. That’s because, fundamentally, any DisplayLink dock
uses a DisplayLink chip, and &lt;a href=&quot;https://www.displaylink.com/&quot;&gt;DisplayLink&lt;/a&gt; (the
company that develops DisplayLink technology and chips for various dock
manufacturers) offers &lt;a href=&quot;https://www.displaylink.com/downloads&quot;&gt;Ubuntu drivers&lt;/a&gt;
themselves. If you have the DisplayLink software installed, connecting a
DisplayLink dock is just like connecting anything else over USB, so it’s likely
to work without problems, even on Ubuntu, on a wide variety of laptops.&lt;/p&gt;

&lt;p&gt;DisplayLink docks fit certain uses really well (and others not so much). Their
biggest advantage is that they can send video over a normal USB 3.0 (or USB 2.0
or USB-C) connection, so you can connect the dock to almost any laptop on the
planet! (Including laptops running Ubuntu!) You can connect your keyboard,
mouse, audio, ethernet, monitor, and other peripherals to the dock, and plug a
single USB cable into your laptop to get all those connections. DisplayLink
docks are also particularly well suited for connecting different laptops with
different display outputs to the same dock. But of course, you have to make
trade-offs.  Because DisplayLink compresses your video to be able to send it
over USB, it doesn’t do well with video playback, gaming, or other
high-framerate applications. (I tried watching a fullscreen 1080p video, and
while it played it was stuttering just enough visually to feel disorienting.)
I should mention that there’s no noticeable slowness aside from video/gaming –
if only a small part of the screen is changing, the compression works incredibly
well. I was able to do a full day of work (programming, web browsing, Slack,
etc) on a DisplayLink dock and wouldn’t have known the difference from a DVI
monitor connection.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/displaylink-docks-and-ubuntu/plugable.jpg&quot; alt=&quot;Plugable UD-6950H box&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The specific DisplayLink dock I recently bought is a &lt;a href=&quot;https://plugable.com/products/ud-6950h&quot;&gt;Plugable
UD-6950H&lt;/a&gt;. I chose this model
specifically because it’s a relatively modern dock (supports higher resolutions)
that’s compatible with USB-3.0, and I wanted to use it with a laptop that
doesn’t have USB-C. But purchasing a dock like this for Ubuntu is certainly a
little confusing. It isn’t advertised with Ubuntu compatibility. In fact, on the
product page, it literally says:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Not compatible with: Linux/Unix&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, as I mentioned above, it actually &lt;em&gt;is&lt;/em&gt; compatible with Linux (and Ubuntu
specifically) because it uses DisplayLink, and DisplayLink provides Ubuntu
software. I’m not sure why it isn’t advertised as compatible with Ubuntu, but my
hunch is that Plugable doesn’t want to guarantee support for Linux – if it
doesn’t work, it’s not their problem. That’s fine; I don’t really fault them for
it because I’m sure Ubuntu’s market share is small enough that it doesn’t really
make sense for them to officially support. But the dock &lt;em&gt;does&lt;/em&gt; work great with
Ubuntu. I’m actually typing this blog post on my UD-6950H on Ubuntu, and the
experience has been incredibly smooth! So, as far as I can tell, DisplayLink
docks should generally be compatible with Ubuntu, and they fill a nice niche for
someone who wants to regularly connect different laptops to their desk monitor,
keyboard, and mouse. (Though, of course, I haven’t personally tested every
DisplayLink dock myself, so the usual YMMV caveat applies here.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/displaylink-docks-and-ubuntu/desk.jpg&quot; alt=&quot;My desk, with a docked monitor&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;how-do-i-connect-a-displaylink-dock-with-ubuntu&quot;&gt;How do I connect a DisplayLink dock with Ubuntu?&lt;/h3&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;Updated:&lt;/b&gt; As of June 2023, Synaptics provides an apt repository that contains the
drivers. The instructions below have been updated to recommend that as the
easiest installation method.&lt;/p&gt;
&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Download the Synaptics APT Repository package from
&lt;a href=&quot;https://www.synaptics.com/products/displaylink-graphics/downloads/ubuntu&quot;&gt;Synaptics&lt;/a&gt;.
(The instructions below should roughly match the instructions you see there.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Install the repository package:
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt install synaptics-repository-keyring.deb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Update apt:
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Install the DisplayLink driver:
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt install displaylink-driver
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In summary, DisplayLink docks work great with Ubuntu. The driver is easy to
install and works well, and the overall experience is smooth. The biggest
downside you’ll notice is that videos, games, and other high framerate
applications don’t look great. This isn’t a problem with Ubuntu – it’s just the
way DisplayLink works. But if you want to primarily use your dock for web
browsing, writing, programming, spreadsheets, or any other office-like use, it
will probably work great, and you’re unlikely to notice any “slowness” at all!&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/displaylink-docks-and-ubuntu.jpg"/>
 </entry>
 
 <entry>
   <title>My New Bash Prompt (PS1)</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/06/28/my-new-bash-prompt.html" type="text/html" title="My New Bash Prompt (PS1)"/>
   
   <published>2021-06-28T09:30:00-06:00</published>
   <updated>2021-06-28T09:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/06/28/my-new-bash-prompt</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/06/28/my-new-bash-prompt.html">&lt;p&gt;I recently spent some time improving my bash prompt. I already had a lightly
customized prompt, but wanted to see if I could find something I liked that
provided even more information.&lt;/p&gt;

&lt;p&gt;I’ve known about powerline for years, but never actually sat down to experiment
with it on my own machine, so that’s where I started. I installed the Ubuntu
Mono &lt;a href=&quot;https://www.nerdfonts.com/&quot;&gt;Nerd Font&lt;/a&gt; and installed and configured
&lt;a href=&quot;https://github.com/powerline/powerline&quot;&gt;powerline-status&lt;/a&gt;. The prompt was nice
enough, but right off the bat there were a few things I didn’t like about it.
There was no easy way to add a newline to the prompt, it was noticeably slow,
and customizing the config seemed more complicated than it needed to be.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/my-new-bash-prompt/powerline.jpg&quot; alt=&quot;Powerline Bash prompt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Because I was a little disappointed with powerline, I looked for some
alternatives. &lt;a href=&quot;https://github.com/b-ryan/powerline-shell&quot;&gt;powerline-shell&lt;/a&gt;
seemed like a cool project – based on the original powerline, but with a focus on
the shell prompt. I liked powerline-shell better. The config was easier to
understand, and there was even easy newline support (Hooray 🎉)! But
powerline-shell was still slow enough that there was a noticeable lag to
generate the prompt. It was jarring enough that I kept searching for
alternatives.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/justjanne/powerline-go&quot;&gt;powerline-go&lt;/a&gt; looks like a good
alternative to powerline-shell that claims to be much faster. I can’t recommend
it from personal experience though, because I &lt;em&gt;never actually tried it&lt;/em&gt;. After
already installing two different powerline prompts I didn’t like that much, it was
getting a bit tedious to install all these different packages just to realize I
didn’t like them that much. And I’d also begun to realize, after trying a
couple different config variataions of both powerline-status and
powerline-shell, that they looked kinda cool but were generally painful to
set up and didn’t provide much info I couldn’t easily add to my own lightly
customized bash prompt, without installing anything.&lt;/p&gt;

&lt;p&gt;So, that’s what I did. I’d noticed that pretty much every powerline package
worked by breaking the prompt line into “segments” – individual pieces of
information that could be glued together to build the complete prompt. So I
refactored my own PS1 customizations to do something similar.&lt;/p&gt;

&lt;p&gt;Before the change, my PS1 looked &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/30875c33c7b569204e13e0022e0a56f7fb4f2def/dot_bashrc#L71&quot;&gt;like this&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;PS1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;\n\[${CYAN}\]${debian_chroot:+($debian_chroot)}\u\[${RESET}${BOLD}\] \[${RED}\]&amp;gt;&amp;gt;\[${GREEN}\]&amp;gt; \[${CYAN}\]\w\[${RESET}\]$(__git_ps1 &quot; (\[${MAGENTA}\]%s\[${RESET}\])&quot;)\n\[${BOLD}\]\$\[${RESET}\] &apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the change, I’d refactored my
&lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/99c51be200ad7b81a7a5c26ccf947259ad8a29d8/dot_bashrc#L63-L65&quot;&gt;PS1&lt;/a&gt;
into an entirely &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/99c51be200ad7b81a7a5c26ccf947259ad8a29d8/executable_dot_mkps1.sh#L1&quot;&gt;separate
file&lt;/a&gt;,
with each “segment” generated in its own function. Because of the functions, the
PS1 is much easier to read.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ps1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;__mkps1_box_top&lt;span class=&quot;si&quot;&gt;)$(&lt;/span&gt;__mkps1_debian_chroot&lt;span class=&quot;si&quot;&gt;)$(&lt;/span&gt;__mkps1_exitcode&lt;span class=&quot;si&quot;&gt;)$(&lt;/span&gt;__mkps1_time&lt;span class=&quot;si&quot;&gt;)$(&lt;/span&gt;__mkps1_username&lt;span class=&quot;si&quot;&gt;)$(&lt;/span&gt;__mkps1_arrows&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;__mkps1_workdir&lt;span class=&quot;si&quot;&gt;)$(&lt;/span&gt;__mkps1_git&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;__mkps1_box_bottom&lt;span class=&quot;si&quot;&gt;)$(&lt;/span&gt;__mkps1_user_prompt&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;so-where-am-i-at-now&quot;&gt;So, where am I at now?&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;✅ I’m still using &lt;a href=&quot;https://github.com/powerline/powerline&quot;&gt;powerline-status&lt;/a&gt; as
a statusline in Vim. I like the info it provides, and I haven’t noticed any
lag on the Vim statusline that’s significant enough to bother me.&lt;/li&gt;
  &lt;li&gt;❌ I’m not using any powerline fonts. Not in my Vim statusline, and not in my
shell prompt. Although they look kind of cool, I realized I value portability
and ease of setup over the look. (I don’t want to configure every application
that might render a terminal to use a powerline font.) I’ve
&lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/99c51be200ad7b81a7a5c26ccf947259ad8a29d8/private_dot_config/powerline/config.json#L3&quot;&gt;configured&lt;/a&gt;
the powerline Vim plugin to use ascii mode, and I didn’t include any powerline
characters in my own prompt.&lt;/li&gt;
  &lt;li&gt;➡ I am using a few Unicode characters in my own prompt though. They seem to be
supported by default pretty much everywhere these days, and it seemed like a
good compromise to get a cool look without the powerline font.&lt;/li&gt;
  &lt;li&gt;😍 I love the &lt;strong&gt;newlines&lt;/strong&gt; in my prompt, and I’d never switch back to a prompt
without newlines. I use a newline above the prompt to create separation from
the previous output, and I put the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt; on a newline because I like having lots
of room to type and I like having it consistently on the left side of the
screen. Overall, both these newlines make it easier to visually scan through
terminal output.&lt;/li&gt;
  &lt;li&gt;⏱ I added a timestamp to my prompt. I’ve found myself wishing I had this several
times in the past when I wanted to know how long a command’s been running.&lt;/li&gt;
  &lt;li&gt;❓ I also added an exit code indicator to my prompt – it only shows if the
previous command failed.&lt;/li&gt;
  &lt;li&gt;📦 Did you know &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__git_ps1()&lt;/code&gt; (the prompt function that ships with git) includes
several &lt;a href=&quot;https://github.com/git/git/blob/670b81a890388c60b7032a4f5b879f2ece8c4558/contrib/completion/git-prompt.sh#L38-L71&quot;&gt;environment
variables&lt;/a&gt;
you can use to show more info? I enabled several of these, and they’re great!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;sharing-my-work&quot;&gt;Sharing My Work&lt;/h2&gt;

&lt;p&gt;I &lt;a href=&quot;/blog/2021/05/12/my-dotfiles-story.html&quot;&gt;share my dotfiles&lt;/a&gt; on GitHub, so
you can look at my
&lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/99c51be200ad7b81a7a5c26ccf947259ad8a29d8/dot_bashrc#L63-L65&quot;&gt;.bashrc&lt;/a&gt;
or &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/99c51be200ad7b81a7a5c26ccf947259ad8a29d8/executable_dot_mkps1.sh#L3-L8&quot;&gt;.mkps1.sh
script&lt;/a&gt;
if you’re curious to see what I’m doing, or if you’d like to try it yourself.
You don’t need to install anything special to make it work - it’s all just shell
script. (I found it somewhat odd that many of the various bash prompt
generators require other languages like NodeJS, Python, etc.) If you like it,
&lt;a href=&quot;https://twitter.com/mike_kasberg&quot;&gt;let me know&lt;/a&gt; – I’m considering writing a
more detailed tutorial about how you can do something similar to what I’ve done
to customize your own Bash or Zsh prompt.&lt;/p&gt;

&lt;p&gt;And if you came here looking for a recommendation about how to set up a
powerline prompt in Bash, I’d probably start by trying
&lt;a href=&quot;https://github.com/justjanne/powerline-go#installation&quot;&gt;powerline-go&lt;/a&gt;. It
should be easy to install with precompiled binaries. (You don’t need to install
Go yourself - just put the binaries in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin&lt;/code&gt; or any similar location, run
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chmod +x powerline-go&lt;/code&gt;, and when editing your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bashrc&lt;/code&gt; as in the readme make
sure the path to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;powerline-go&lt;/code&gt; binary is where you saved it.)  Powerline-go
comes with reasonable defaults, it will look nice, and it should be faster than
some of the other options since it’s written in Go.&lt;/p&gt;

&lt;p&gt;Finally, if you’re using zsh instead of bash (as you probably are if you’re on a
modern version of macOS), you should check out &lt;a href=&quot;https://ohmyz.sh/&quot;&gt;Oh My Zsh&lt;/a&gt; as
a starting point if you want to customize your prompt. It comes with a good
selection of &lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh/wiki/Themes&quot;&gt;themes&lt;/a&gt; that are
easy to install by setting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZSH_THEME&lt;/code&gt; variable in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;, and you
can add additional customizations if you want to. If you choose a theme that
uses a powerline-like prompt, you’ll still need to install a &lt;a href=&quot;https://www.nerdfonts.com/&quot;&gt;Nerd
Font&lt;/a&gt; to render the special characters correctly.
Either way, I’m sure you can find something you like!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Programming"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/my-new-bash-prompt.jpg"/>
 </entry>
 
 <entry>
   <title>Scalaing Club Leaderboard Infrastructure for Millions of Users</title>
   
   <link href="https://medium.com/strava-engineering/scaling-club-leaderboard-infrastructure-for-millions-of-users-9ee857ce8cfe" type="text/html" title="Scalaing Club Leaderboard Infrastructure for Millions of Users"/>
   
   <published>2021-05-26T08:00:00-06:00</published>
   <updated>2021-05-26T08:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/05/26/scaling-club-leaderboard-infrastructure-for-millions-of-users</id>
   
   <content type="html" xml:base="https://medium.com/strava-engineering/scaling-club-leaderboard-infrastructure-for-millions-of-users-9ee857ce8cfe">&lt;p&gt;Sometimes, we work on shiny new features that (we hope) athletes will notice and love. Other
times, we need to work on back-end improvements that are unlikely to be noticed at all, but
are usually just as important as the other things we work on. Some recent work I did for our
club leaderboards fell into the latter category — unlikely to be noticed, but really
important for the scalability and reliability of strava.com.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="The Strava Engineering Blog"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/strava-blog/scaling-club-leaderboards.jpg"/>
 </entry>
 
 <entry>
   <title>My Dotfiles Story: A Journey to Chezmoi</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/05/12/my-dotfiles-story.html" type="text/html" title="My Dotfiles Story: A Journey to Chezmoi"/>
   
   <published>2021-05-12T20:30:00-06:00</published>
   <updated>2021-05-12T20:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/05/12/my-dotfiles-story</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/05/12/my-dotfiles-story.html">&lt;p&gt;When you’re a developer (or perhaps even a normal power-user), you seem to
acquire a bunch of custom config, scripts, and tools over time. Often, these are
little snippets. A one-liner command alias that you paste into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.bashrc&lt;/code&gt;. A
custom format for your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PS1&lt;/code&gt; prompt. A default setting for Vim.&lt;/p&gt;

&lt;p&gt;Then, at some point, you get a new computer, and you realize you want all the
config that was on your old computer. For me, this was how it started. In fact,
I started trying to solve this problem before I knew what Git was and before I
knew what dotfiles were. Back then, I kept a txt file with notes about what
Windows settings I had changed to makes things work the way I liked, so I could
change the same settings if I had to set up a new computer for myself!&lt;/p&gt;

&lt;p&gt;But you start to realize that maybe you don’t want to simply re-do your config
every time you get a new computer. And although with Linux dotfiles you can
simply copy the files over with a USB stick, you don’t want to have to think
about what to copy every time this happens. Also, you start to realize that maybe
you want to back some of this config up so you don’t lose it if your computer
crashes!&lt;/p&gt;

&lt;p&gt;My first solution to these problems was simple enough – I was a heavy user of
Dropbox at the time, and I created a folder in my Dropbox called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Config&lt;/code&gt;. I
stored a few files (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bashrc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitconfig&lt;/code&gt;) in there and tried to
remember to copy my changes to Dropbox if I made any edits.&lt;/p&gt;

&lt;p&gt;And that worked pretty well for a while, but eventually became a little tedious
to manage. You update the file on one computer, but forget to copy it down on
another one and the files get out of sync. Also, over time, you begin to realize
that you might want slightly different config on different computers. Maybe
there’s a bash alias you only want on your work computer, or maybe there’s a few
lines that apply to your macOS machine but not to your Linux computer. Around
this time in my own dotfiles journey, I was into bash scripting. So, I wrote a
tool in bash to diff the dotfiles in my Dropbox with the dotfiles installed to
my computer. I could then use normal diff tools (including the GUI
&lt;a href=&quot;https://meldmerge.org/&quot;&gt;Meld&lt;/a&gt;) to merge the lines I cared about, and leave
certain lines out if I wanted to! And the script would report which files were
up to date and which files had differences.&lt;/p&gt;

&lt;p&gt;To give you a rough idea what I’m talking about, I’d run the script like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ pwd
~/Dropbox/Config
$ ./diff-dotfiles.sh -l
The following files have differences:
  .bashrc
  .gitconfig

$ ./diff-dotfiles.sh .bashrc 
--- /home/mkasberg/.bashrc	2020-08-19 20:49:42.509801503 -0600
+++ .bashrc	2019-05-27 21:30:08.264957695 -0600
@@ -16,8 +16,8 @@
 # for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
-HISTSIZE=20000
-HISTFILESIZE=20000
+HISTSIZE=5000
+HISTFILESIZE=10000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This bash script/Dropbox approach actually worked pretty well for a couple
years, and I’m &lt;em&gt;really pleased with how powerful the script was for something
that was less than 100 lines long!&lt;/em&gt; The fact that I was able to get by with
something like this for more than a year is a testament to the power of the
command line tools available on any Linux computer. But it also speaks to the
fact that “managing your dotfiles” is fundamentally a very simple problem where
simple solutions can work well. If you want to check out, use, or build upon my
script, you’re welcome to – &lt;a href=&quot;https://gist.github.com/mkasberg/4848d69665742e54cd854f32f9c8d808&quot;&gt;here’s the
Gist&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;But, alas, I’m not using my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff-dotfiles.sh&lt;/code&gt; script anymore. As I collected
more and more bits of config, managing differences with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt; became tedious,
and I searched for something that was a little more powerful and purpose-built
than my bash script. And that’s how I found &lt;a href=&quot;https://www.chezmoi.io/&quot;&gt;&lt;strong&gt;Chezmoi&lt;/strong&gt;&lt;/a&gt;.
Chezmoi is like what you might get if you re-wrote my bash script in Go, came up
with better solutions than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt; for managing config on multiple machines, added in
secrets management and other useful dotfile tools, and tweaked and perfected it
over years. Although it’s slightly intimidating to move from a “copy your
dotfiles from Dropbox” approach to a custom-built tool like Chezmoi, the Chezmoi
docs are great and you start to see the benefits as soon as you start using it!&lt;/p&gt;

&lt;p&gt;When I began using Chezmoi, I also migrated my dotfiles off Dropbox and onto
Git.  Which was a &lt;strong&gt;great decision&lt;/strong&gt; because Git provides version control and
commit history, so I can remember why I made a minor change years later. And now
that I’m using Git, I’ve also &lt;a href=&quot;https://github.com/mkasberg/dotfiles&quot;&gt;published my dotfiles on
GitHub&lt;/a&gt;.  Storing my dotfiles on GitHub
makes it easy to sync between different computers, and also makes it easy to
share with others who might be interested in them.  (It’s really cool to just
drop a link &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/b496e5e5fd6e490708fc1b56c088952b3ae874bc/dot_vimrc#L16-L17&quot;&gt;like
this&lt;/a&gt;
when I want to share a quick tip with someone!)&lt;/p&gt;

&lt;p&gt;As a brief aside, here are some of my favorite tricks from my own dotfiles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;My &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/b496e5e5fd6e490708fc1b56c088952b3ae874bc/dot_bashrc#L71&quot;&gt;Bash
PS1&lt;/a&gt;
and &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/2d630af8c62b6312d137bd1baa7b7c92c0cba877/dot_zsh_custom/themes/mkasberg.zsh-theme&quot;&gt;Zsh
Theme&lt;/a&gt;,
which add an extra newline before the prompt, use custom colors, display my
current git branch, and put the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt; on a new line.&lt;/li&gt;
  &lt;li&gt;My &lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/master/dot_vimrc&quot;&gt;.vimrc&lt;/a&gt;,
which includes some light customization for things like color schemes,
formatting, and special display rules for tab characters.&lt;/li&gt;
  &lt;li&gt;A
&lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/master/run_once_01-install-ubuntu-applications.sh.tmpl&quot;&gt;script&lt;/a&gt;
that will install all my most frequently used non-default apps on Ubuntu.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mkasberg/dotfiles/blob/2d630af8c62b6312d137bd1baa7b7c92c0cba877/dot_gitconfig.tmpl#L4&quot;&gt;Template
support&lt;/a&gt;
to account for differences between machines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve been using Chezmoi for more than a year now, across at least 3 computers
simultaneously, and I really love it. Most of all, I love how &lt;em&gt;fast&lt;/em&gt; I can
configure a new machine when I use it. In just a couple minutes of work, I can
kick off a process on a brand-new computer that will set up my dotfiles and
install all my usual software so it feels like a computer I’ve been using for
years. I also appreciate features like secrets management, which allow me to
share my dotfiles while keeping my secrets safe. Overall, I love the way Chezmoi
fits so perfectly into the niche of managing dotfiles.&lt;/p&gt;

&lt;p&gt;If you want to try Chezmoi yourself, you can! It’s a free, open source tool and
it’s easy to get started. The best way to start is to find a file you’ve already
made a small customization to, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bashrc&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt;, and use that as a
starting point for your own dotfiles repository. Don’t worry about starting
small and simple, you’ll acquire a lot of config changes over the years! Chezmoi
provides a good &lt;a href=&quot;https://www.chezmoi.io/quick-start/&quot;&gt;quick start guide&lt;/a&gt; to
get you started. To give you the gist of it here, you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chezmoi init&lt;/code&gt; to
create a bare dotfiles repo and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chezmoi add ~/.bashrc&lt;/code&gt; (or any other file
you want) to add your first dotfile. After that, it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chezmoi cd&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git add .&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit&lt;/code&gt;, and you’re done!&lt;/p&gt;

&lt;p&gt;So, what does this all mean for you? I’m not necessarily suggesting that you
should dive head-first into Chezmoi overnight! (Though of course I recommend it
if you’re interested.) If you’ve never given your dotfiles any thought, I hope
this introduces you to the world of easy customizations you can do to make your
computer work better for you. If you already have a few custom lines in your
dotfiles, I hope this inspires you to add some more customization, back them up
somewhere, and share them. And if you’ve been looking for a tool to manager your
own dotfiles, I hope this inspires you to check out
&lt;a href=&quot;https://www.chezmoi.io/&quot;&gt;Chezmoi&lt;/a&gt;, but ultimately I hope you find the tool that
works best for you!&lt;/p&gt;

&lt;p&gt;Did you find this inspirational? Want to talk more about dotfiles? Let me know
&lt;a href=&quot;https://twitter.com/mike_kasberg&quot;&gt;@mike_kasberg&lt;/a&gt;.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/my-dotfiles-story.jpg"/>
 </entry>
 
 <entry>
   <title>How to Install Pi-hole on Orange Pi / Armbian Boards</title>
   
   <link href="https://www.mikekasberg.com/blog/2021/05/11/how-to-install-pi-hole-on-the-orange-pi-zero.html" type="text/html" title="How to Install Pi-hole on Orange Pi / Armbian Boards"/>
   
   <published>2021-05-11T20:00:00-06:00</published>
   <updated>2021-05-11T20:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2021/05/11/how-to-install-pi-hole-on-the-orange-pi-zero</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2021/05/11/how-to-install-pi-hole-on-the-orange-pi-zero.html">&lt;p&gt;I’ve already written a &lt;a href=&quot;/blog/2020/07/03/review-orange-pi-zero.html&quot;&gt;review of the Orange Pi Zero&lt;/a&gt;. As it turns out, this little board is
perfect for running your own &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi-hole&lt;/a&gt;! It’s probably even
better than a Raspberry Pi! The cheapest Raspberry Pi you can get with a wired
ethernet connection is the Raspberry Pi 1 Model B+, which is listed for &lt;a href=&quot;https://www.pishop.us/product/raspberry-pi-model-b-plus/?src=raspberrypi&quot;&gt;$28.95
at
PiShop.us&lt;/a&gt;
(as of May 2021). In contrast, you can get an &lt;a href=&quot;http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero.html&quot;&gt;Orange Pi Zero
512MB&lt;/a&gt; for &lt;a href=&quot;https://www.aliexpress.com/item/1005001688034180.html&quot;&gt;$16.99 + ~$4.00 shipping
&lt;/a&gt; from &lt;a href=&quot;https://www.aliexpress.us/&quot;&gt;AliExpress&lt;/a&gt;, or &lt;a href=&quot;https://www.aliexpress.com/item/4000070093365.html&quot;&gt;a couple
dollars more with a case&lt;/a&gt;.
(You can also find these items on Amazon, but shipping is more expensive there.)
In total, you can save about 30% compared to the Raspberry Pi! Of course, the
Raspberry Pi model Zero W might be cheaper, but isn’t a good choice for a
Pi-hole – it loses some network stability and speed because of its wireless
connection. The Orange Pi Zero, on the other hand, has a wired ethernet
connection and performs great!&lt;/p&gt;

&lt;p&gt;So, you’ve bought your Orange Pi Zero (or any other Armbian-compatible board),
now what? All the tutorials seem to be for Raspberry Pi, so how do you set up
Pi-hole on your new Orange Pi? There are 2 things you need to accomplish. First,
you need to install Linux on your Pi and get SSH access. Then, you need to
install and set up the Pi-hole software.&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;These instructions were originally written for the &lt;a href=&quot;http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero.html&quot;&gt;Orange
Pi Zero&lt;/a&gt;, but they should also work for the &lt;a href=&quot;http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero-Plus.html&quot;&gt;Orange
Pi Zero Plus&lt;/a&gt;, &lt;a href=&quot;http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero-2.html&quot;&gt;Zero2&lt;/a&gt;,
or other Orange Pi boards or anything that can install Armbian &amp;mdash; just make
sure you download the correct OS for your model.&lt;/p&gt; &lt;/div&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;b&gt;Changelog&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;2022-09-27:&lt;/b&gt; Updated to include instructions for setting a static IP and for setting the web interface admin password.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;h2 id=&quot;install-armbian-linux-on-the-orange-pi&quot;&gt;Install Armbian Linux on the Orange Pi&lt;/h2&gt;

&lt;p&gt;Although Orange Pi provides &lt;a href=&quot;http://www.orangepi.org/html/softWare/orangePiOS/index.html&quot;&gt;Orange Pi
OS&lt;/a&gt;, we’re going to
use &lt;a href=&quot;https://www.armbian.com/&quot;&gt;Armbian&lt;/a&gt;. Orange Pi OS sees little use and
is poorly maintained compared to Armbian, which has a bigger community because
of its support for a wide variety of ARM boards.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Download Armbian Jammy (under the variants section near the bottom) from the &lt;a href=&quot;https://www.armbian.com/orange-pi-zero/&quot;&gt;Armbian Orange Pi
Zero&lt;/a&gt;
(&lt;a href=&quot;https://www.armbian.com/orange-pi-zero-plus/&quot;&gt;Zero+&lt;/a&gt;)
(&lt;a href=&quot;https://www.armbian.com/orange-pi-zero-2/&quot;&gt;Zero2&lt;/a&gt;)
(&lt;a href=&quot;https://www.armbian.com/download/?device_support=Supported&quot;&gt;other&lt;/a&gt;) page.
Armbian Jammy is based on Ubuntu Jammy. (If a newer Ubuntu-based Armbian image
is available, use that! Debian images like Bookworm are also fine, though I
prefer Ubuntu.)&lt;/p&gt;

    &lt;div class=&quot;message&quot;&gt;
  &lt;p&gt;The &lt;a href=&quot;http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/service-and-support/Orange-Pi-Zero-3.html&quot;&gt;Orange
  Pi Zero 3&lt;/a&gt; does not yet have an official Armbian release. In the mean
  time, you can download one of the official images (try Ubuntu or Debian)
  from Orange Pi, or you can use an unofficial Armbian build from &lt;a href=&quot;https://github.com/leeboby/armbian-images&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Download &lt;a href=&quot;https://etcher.balena.io/&quot;&gt;Balena Etcher&lt;/a&gt; and use it to create
an SD card based on the Armbian image you downloaded. For more detailed
instructions, see the &lt;a href=&quot;https://docs.armbian.com/User-Guide_Getting-Started/#how-to-prepare-a-sd-card&quot;&gt;Armbian
docs&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Put the SD card in the Pi, plug the Pi into your network via ethernet, and
plug in the power cable to turn it on.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Assign or find the IP address of your Pi.&lt;/p&gt;

    &lt;p&gt;If possible, you should assign a static LAN IP address to your Pi with your
router.  This is preferable because it’s guaranteed not to change and break your
configuration. Try to do this in your router’s settings. It might be called
“DHCP Reservation” or similar. I set mine to 192.168.0.2, as you can see below.
The process to set a DHCP reservation can vary quite a bit depending on your
router, so see more info
&lt;a href=&quot;https://www.pcmag.com/how-to/how-to-set-up-a-static-ip-address&quot;&gt;here&lt;/a&gt; if you
need it. And don’t panic if your router doesn’t support this – there are other
ways to set a static IP, and in a worst-case scenario the dynamic IP
automatically assigned by your router is unlikely to change anyway.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/how-to-install-pi-hole-on-orange-pi/static-ip.jpg&quot; alt=&quot;DHCP reservation settings showing 192.168.0.2 static IP&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;If you can’t assign a static IP from your router, you’ll have to figure out
the IP your router assigned automatically. The easiest way is to look for the device in your
router’s devices list, often at &lt;a href=&quot;http://192.168.0.1&quot;&gt;http://192.168.0.1&lt;/a&gt;. (If that doesn’t work,
you might find instructions for connecting to your router on a sticker on the
router, or in the manual.) Alternatively, you can use the &lt;a href=&quot;https://www.fing.com/products/fing-app&quot;&gt;Fing
App&lt;/a&gt; or &lt;a href=&quot;https://angryip.org/&quot;&gt;Angry IP&lt;/a&gt;
to find the IP of the Pi device.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;SSH to the Pi and login. On macOS, you can simply run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh
root@&amp;lt;pi-ip-address-here&amp;gt;&lt;/code&gt; (and replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;pi-ip-address-here&amp;gt;&lt;/code&gt; with the actual
IP from step 4). On Windows, you’ll have to install an SSH client like
&lt;a href=&quot;https://www.putty.org/&quot;&gt;PuTTY&lt;/a&gt; and use that to connect (IP: &amp;lt;Pi IP&amp;gt;,
Username: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt;, Password: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1234&lt;/code&gt;, Port: default/22).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;As described in the
&lt;a href=&quot;https://docs.armbian.com/User-Guide_Getting-Started/#how-to-login&quot;&gt;docs&lt;/a&gt;, login
as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; using password &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1234&lt;/code&gt; and create a new user account for yourself.
You’ll use that new user account going forward – it’s best not to use root as
your primary account.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you did not assign a static IP address via a DHPC reservation on your
router in step 4, you should configure your Pi for a static IP address now. You
can do so by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo armbian-config&lt;/code&gt; and entering Network settings, as
shown below.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/how-to-install-pi-hole-on-orange-pi/armbian-config.png&quot; alt=&quot;armbian-config network settings&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When you’re finished, you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exit&lt;/code&gt; to log out and
disconnect.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Congrats, you’ve installed Linux on your Orange Pi and logged in! Now, let’s
install and set up Pi-hole!&lt;/p&gt;

&lt;h2 id=&quot;install-and-setup-pi-hole&quot;&gt;Install and Setup Pi-hole&lt;/h2&gt;

&lt;p&gt;Installing the Pi-hole software is pretty easy, and you can use the &lt;a href=&quot;https://docs.pi-hole.net/main/basic-install/&quot;&gt;official
instructions&lt;/a&gt; if you get confused
or want more detail.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Connect to the Pi using the account you created in step 6 above. To do so,
run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh &amp;lt;your-username&amp;gt;@&amp;lt;pi-ip-address-here&amp;gt;&lt;/code&gt; on macOS, or similarly using
PuTTY on Windows.&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl -sSL https://install.pi-hole.net | bash&lt;/code&gt; on the Pi.&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pihole -a -p&lt;/code&gt; to set the admin password for the web interface. You’ll use this to log in to the web interface from other computers.&lt;/li&gt;
  &lt;li&gt;Configure your network to use the Pi-hole as its DNS server. This is the
critical step that will make bad domains (advertisers, etc) be blocked on your
network.
    &lt;ul&gt;
      &lt;li&gt;If possible, you should try to &lt;a href=&quot;https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245&quot;&gt;configure your router to use the Pi-hole as
the DNS
server&lt;/a&gt;
(as in method 1).&lt;/li&gt;
      &lt;li&gt;If that doesn’t work, try &lt;a href=&quot;https://discourse.pi-hole.net/t/how-do-i-use-pi-holes-built-in-dhcp-server-and-why-would-i-want-to/3026&quot;&gt;using the Pi-hole as your network’s DHCP
server&lt;/a&gt;.&lt;/li&gt;
      &lt;li&gt;Finally, if neither of the above work, configure each device to use the
Pi-hole as the DNS server manually as in &lt;a href=&quot;https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245&quot;&gt;method 3
here&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Well, that’s it! You should now have network-wide ad blocking with your Pi-hole!&lt;/p&gt;

&lt;p&gt;And if you ever want to temporarily disable it, see how to do so &lt;a href=&quot;/blog/2020/06/26/a-bookmark-to-temporarily-disable-your-pihole.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Did you like this tutorial? Let me know
&lt;a href=&quot;https://twitter.com/mike_kasberg&quot;&gt;@mike_kasberg&lt;/a&gt;&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-to-install-pihole-on-the-orange-pi-zero.jpg"/>
 </entry>
 
 <entry>
   <title>Learning to Program by Making a Game</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/12/06/learning-to-program-by-making-a-game.html" type="text/html" title="Learning to Program by Making a Game"/>
   
   <published>2020-12-06T09:00:00-07:00</published>
   <updated>2020-12-06T09:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/12/06/learning-to-program-by-making-a-game</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/12/06/learning-to-program-by-making-a-game.html">&lt;p&gt;This year, &lt;a href=&quot;https://www.csedweek.org/&quot;&gt;Computer Science Education Week&lt;/a&gt; will be
December 7-13, 2020. In honor of CS Education Week, I wanted to write this blog
post about the first computer game I wrote, and what I learned along the way. I
was about thirteen, and this story picks up near where I left off in my previous
blog post about &lt;a href=&quot;/blog/2017/12/04/my-first-program.html&quot;&gt;my first computer program&lt;/a&gt;. When I wrote that blog post in 2017, I was
working mostly from memory. Since then, I actually &lt;em&gt;found an old floppy disk&lt;/em&gt;
with my game on it. And finding that old floppy disk got me thinking about about
how much I learned from writing computer games in my childhood, and how much
others might be able to learn from writing their own first computer games today.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/tag-game/floppy.jpg&quot; alt=&quot;3.5&amp;quot; floppy disk - Tag Game&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At a young age, I became fascinated with computers because by programming, you
can make something from nothing. You don’t need to pay money for a new Lego set,
and you don’t need a trip to the craft shop or the hardware store. You don’t
need any building material. With computers, you can make something that’s
interesting and valuable with just your brain. Most thirteen-year-olds probably
just want to &lt;em&gt;play&lt;/em&gt; computer games, but I wanted to &lt;em&gt;build my own&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;building-games-with-game-maker&quot;&gt;Building Games with Game Maker&lt;/h2&gt;

&lt;p&gt;My desire to build my own computer game led me to a piece of software called
&lt;a href=&quot;https://web.archive.org/web/20030623090955/http://gamemaker.nl:80/&quot;&gt;Game Maker&lt;/a&gt;.
This software made it relatively easy to program computer games from scratch. At
least, easy enough that a thirteen-year-old could figure it out. Although it
wasn’t &lt;em&gt;quite&lt;/em&gt; as easy as the drag-and-drop programming
&lt;a href=&quot;https://scratch.mit.edu&quot;&gt;Scratch&lt;/a&gt; provides today, the principles were largely
the same. In Game Maker, you create rooms and objects, and the objects can
display as &lt;a href=&quot;https://en.wikipedia.org/wiki/Sprite_(computer_graphics)&quot;&gt;sprites&lt;/a&gt;.
Game Maker provides a UI for programming the game by making the objects respond
to different events. You can make objects move by responding to keyboard events,
and you can make objects interact with each other by responding to collision
events. The combination of these inputs and interactions makes a complete game.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/tag-game/game-maker-interface.png&quot; alt=&quot;Game Maker screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I was a young teenager, Game Maker was the perfect program to satisfy my
curiosity about programming video games. It was relatively easy to learn, but it
was also very powerful. I wanted to program a multiplayer game so several people
could play at once on the same machine, like a Nintendo or a PlayStation. My
idea was simple – I was going to program a computer version of the playground
game “tag”. Fortunately for me, the logic for a tag game isn’t too complex. Each
player needs to be able to move, and you need to know when someone runs into
someone else. A perfect programming challenge for a young teenager!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/tag-game/animated-tag.webp&quot; alt=&quot;Tag game&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;programming-games-is-educational&quot;&gt;Programming Games Is… Educational?&lt;/h2&gt;

&lt;p&gt;Yes! Programming computer games can be very educational! As a lot of research
around &lt;a href=&quot;https://www.pblworks.org/what-is-pbl&quot;&gt;project based learning&lt;/a&gt; shows,
there’s no better way to learn something than to jump right in and do it. By
getting into a project a little outside your comfort zone, you quickly realize
what you don’t know and you’re motivated to learn so you can finish your
project. You’ll remember what you’ve learned better too because you understand
how to apply it.  Project based learning can be significantly more effective
than other forms of education.&lt;/p&gt;

&lt;h3 id=&quot;scratch&quot;&gt;Scratch&lt;/h3&gt;

&lt;p&gt;Today, there are a variety of platforms that people (both young and old) can use
to make their own games while building skills in software development.
&lt;a href=&quot;https://scratch.mit.edu/&quot;&gt;Scratch&lt;/a&gt; is probably the easiest to get started with
and also the most well-known. Unfortunately, I’ve personally known a few kids
who developed a bad impression of Scratch at a young age. Their first experience
with it might have been a silly project that they didn’t like, or perhaps they
only used it for animations in the past (which they weren’t interested in).
Scratch isn’t limited to silly animations or simple programming like making a
robot follow a path. Advanced Scratch projects can be very complex and look like
“real” video games – see &lt;a href=&quot;https://www.pblworks.org/what-is-pbl&quot;&gt;Flappy Bird&lt;/a&gt;,
&lt;a href=&quot;https://scratch.mit.edu/projects/424511068/&quot;&gt;The Crusty Quest&lt;/a&gt;, or
&lt;a href=&quot;https://scratch.mit.edu/projects/148769358/&quot;&gt;Chess&lt;/a&gt;. So while programming games
won’t appeal to &lt;em&gt;everyone&lt;/em&gt;, most kids get excited when they see what Scratch can
do. I’ve taught an &lt;a href=&quot;/blog/2017/12/09/hour-of-code-2017.html&quot;&gt;Hour of Code&lt;/a&gt;
for several years using Scratch (usually with some variation of a simple game)
and every single time the kids get excited when they see my game and realize
they’re going to be given the tools and knowledge to build their own.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/tag-game/scratch-asteroids.png&quot; alt=&quot;Scratch asteroids game&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Fundamentally, Scratch works with all the same principles I used when I made my
first game in Game Maker. You create a scene, you put objects in the scene, and
you program those objects to respond to input and events. I even re-wrote my tag
game (the one I made with Game Maker) &lt;a href=&quot;https://scratch.mit.edu/projects/453364463/&quot;&gt;in
Scratch&lt;/a&gt;, just to prove to myself
that it wouldn’t be hard to do. In addition to that, I’ve written an &lt;a href=&quot;https://scratch.mit.edu/projects/187823663/&quot;&gt;asteroids
game&lt;/a&gt;, a &lt;a href=&quot;https://scratch.mit.edu/projects/24016542/&quot;&gt;drawing
program&lt;/a&gt;, a &lt;a href=&quot;https://scratch.mit.edu/projects/24030594/&quot;&gt;platform
game&lt;/a&gt;, and a simple &lt;a href=&quot;https://scratch.mit.edu/projects/20975600/&quot;&gt;Frogger
clone&lt;/a&gt;. I designed most of these as
part of lessons I was teaching, but the important thing is that any of these
games could be written by a motivated 5th grader. When I wrote these projects,
my goal was to make a fun game with simple code that’s easy to understand.
Scratch lets you look at the code behind any game, and the code for most of mine
is usually no more than about 50 lines long (&lt;a href=&quot;https://scratch.mit.edu/projects/187823663/editor/&quot;&gt;take a
look&lt;/a&gt;)! This makes it easy
to modify other people’s projects (turn Asteroids into
&lt;a href=&quot;https://scratch.mit.edu/projects/188436807/&quot;&gt;Dodgeball&lt;/a&gt;) or learn from them to
make your own!&lt;/p&gt;

&lt;h3 id=&quot;other-tools&quot;&gt;Other Tools&lt;/h3&gt;

&lt;p&gt;Of course, in addition to Scratch, there are several other more advanced tools
available. At some point, Scratch becomes too limiting and too boring, and you
want to build better things with more advanced tools. &lt;a href=&quot;https://appinventor.mit.edu/&quot;&gt;MIT App
Inventor&lt;/a&gt; uses the same kind of programming blocks
that Scratch uses, but provides a wider variety of blocks and allows you to
create phone apps that will actually run on real phones. It’s a great tool to
move on to when you get bored with Scratch! Alternatively, you could give
&lt;a href=&quot;https://gdevelop-app.com/&quot;&gt;GDevelop&lt;/a&gt; a try. GDevelop is a much more advanced
tool for building real computer games, but it operates on many of the same of
the program I used back in 2003. No matter what tool you’re using, finding a
good tutorial (either written or on YouTube) isn’t a bad place to start.&lt;/p&gt;

&lt;p&gt;Building computer games can be as fun as playing them, and I hope I’ve inspired
you (or your children!) to try building something and to learn something new
along the way! If you like my Scratch games, come &lt;a href=&quot;https://scratch.mit.edu/users/mkasberg/&quot;&gt;follow me on
Scratch&lt;/a&gt;!&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/learning-to-program-by-making-a-game.jpg"/>
 </entry>
 
 <entry>
   <title>Building Local Legends</title>
   
   <link href="https://medium.com/strava-engineering/building-local-legends-290879265c83" type="text/html" title="Building Local Legends"/>
   
   <published>2020-10-28T08:00:00-06:00</published>
   <updated>2020-10-28T08:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/10/28/building-local-legends</id>
   
   <content type="html" xml:base="https://medium.com/strava-engineering/building-local-legends-290879265c83">&lt;p&gt;In late 2019, Strava’s product development team began forming ideas about a new way to compete
on Strava. Strava segments have always provided a way to recognize athletic achievement through
a form of racing. Now, we wanted to also recognize the grit that goes into training. Those
athletes who are out on the trail every day, rain or shine, striving for more. Local Legends is
the result of these ideas, recognizing the athlete who’s completed a segment the most times in
the last 90 days. I was lucky enough to play an important role in building this feature, and in
this blog post, I want to tell the story of how we developed Local Legends and explore some of
the technical challenges we faced along the way.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="The Strava Engineering Blog"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/strava-blog/building-local-legends.jpg"/>
 </entry>
 
 <entry>
   <title>Unsubscribe Me: How to Disconnect from Internet Chaos</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/10/23/unsubscribe-me.html" type="text/html" title="Unsubscribe Me: How to Disconnect from Internet Chaos"/>
   
   <published>2020-10-23T20:15:00-06:00</published>
   <updated>2020-10-23T20:15:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/10/23/unsubscribe-me</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/10/23/unsubscribe-me.html">&lt;p&gt;&lt;a href=&quot;https://www.thesocialdilemma.com/&quot;&gt;The Social Dilemma&lt;/a&gt; came out about a month
ago and got a lot of people thinking about the influence the internet and social
media have on their daily lives. I’m a programmer, and I’ve worked for an
internet advertising company, so none of this was new information to me. But for
a lot of the American public, watching The Social Dilemma was an eye-opening
experience into the ways that internet companies try to track and manipulate
people. Many of my friends used words like “unsettling” and “eerie” to describe
how they felt after seeing the film, but most of them also weren’t really sure
what to do about it. They weren’t sure how to make positive changes in their own
lives.&lt;/p&gt;

&lt;p&gt;So in this blog post, I want to explore some simple changes that I’ve made, and
that you might think about making too. I’ve been doing some of these things for
years and I’ve started some much more recently, but with these issues at the
front of my mind recently, and I think I have a reasonable approach to at least
heading in the right direction. The Social Dilemma website provides its own
&lt;a href=&quot;https://www.thesocialdilemma.com/take-action/&quot;&gt;Take Action&lt;/a&gt; page, and most of
the advice there isn’t bad as a starting point. Most of my own suggestions are
minimally invasive and relatively simple. I haven’t completely stopped using
social media, and I wouldn’t necessarily recommend that as a solution, unless
you want to of course. I think it’s important to be pragmatic about what you can
do in a way that still allows you to maximise the personal value you get from
online services.&lt;/p&gt;

&lt;h2 id=&quot;your-time-is-valuable-to-companies&quot;&gt;Your Time is Valuable to Companies&lt;/h2&gt;

&lt;p&gt;When people ask me about the movie, I always say that I think it’s a really
well-done visualization of the competition for your time. Companies want your
attention because your attention is valuable. This isn’t new. In the 80s and
90s, you saw ads on televisions, billboards, newspapers, and magazines.
Companies want you to notice them so they can sell you a product. And for the
most part, people have been pretty happy to make that trade. Who among us
doesn’t get excited for a 20% off coupon at your favorite store?&lt;/p&gt;

&lt;p&gt;In addition to straight-up ads, your time is valuable to companies in other
ways. Have you ever used a free app that didn’t have ads? Instagram &lt;a href=&quot;https://www.thesocialdilemma.com/take-action/&quot;&gt;didn’t have
ads&lt;/a&gt; before late 2013, for
example. So why did Instagram care about your time if they weren’t showing you
ads? Your time is valuable to companies even if they’re not directly advertising
to you. Instagram is valuable as a company, in part, because of the size of its
user base. Apps with a big user base are valuable because they can monetize that
user base in a variety of different ways – from showing ads to selling data or
getting you hooked on a service they’ll charge for.&lt;/p&gt;

&lt;h2 id=&quot;the-competition-for-your-time&quot;&gt;The Competition for Your Time&lt;/h2&gt;

&lt;p&gt;So apps want your time and they’re willing to compete for it. But they’re not
the only thing that’s competing for your time. Think about all your interactions
with media in a typical day. Maybe you listen to a podcast in the morning or on
the way to work. Does the podcast have ads? What about the app you use to play
it? Early in the workday, you notice a few notifications from your favorite
social apps. A couple likes, maybe a comment. Also, a notification from one of
your restaurant apps that they’re running a promotion today. Later, your phone
rings. It’s a spam call. You also notice a couple spam text messages that you
delete. In the afternoon, you check your email. Out of all the unread emails in
your inbox, you delete about half without reading them. When you get home, you
turn on your Fire TV where you see ads for a bunch of TV shows or movies. Then
you open the Netflix app where they’re promoting today’s top new shows. Everyone
is competing for your attention all the time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/unsubscribe-me/notifications.jpg&quot; alt=&quot;Unwanted app notifications&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ads aren’t new, but the intensity of the competition for your attention and time
is higher than it’s ever been before. It’s in companies’ interest to have as
much of your attention as they can, but it’s in &lt;em&gt;your&lt;/em&gt; interest to give them the
minimal amount of attention necessary to get the value you want from them.
Companies are ruthless and aggressive in their pursuit of your attention, and
they’ll win the battle if you don’t put in any effort. Fortunately, there are
some low effort solutions that can produce very beneficial results, and my
approach is focused on this.&lt;/p&gt;

&lt;h2 id=&quot;optimize-your-digital-life&quot;&gt;Optimize Your Digital Life&lt;/h2&gt;

&lt;h3 id=&quot;email&quot;&gt;Email&lt;/h3&gt;

&lt;p&gt;Companies love email. Sending emails for marketing purposes is a billion-dollar
industry. For anyone who wants to promote something to you, email is a way for
them to get your attention. Your email address itself is valuable to many
companies because it allows them to (possibly) show you ads when they otherwise
wouldn’t be able to show something to you. (This is why companies will do things
like giveaways in exchange for your email address.) They will hire marketing
professionals whose job is to write a clickbait subject line to get you to open
the email so they can show you their ad. And they will track when you open the
email so they know if it’s working. (Images are used to track views, which is
one reason Gmail blocks images from unknown senders by default.) Unfortunately,
spam is a hard problem to solve, and there isn’t a perfect solution. But there
are some steps you can consider that might help reduce the way email intrudes
into your life.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Unsubscribe from anything you don’t really need. If you’re not sure, it’s
probably best to unsubscribe. You don’t really need to be bothered by your
college alumni newsletter, and you don’t really need to be bothered by that
mailing list you signed up for 2 years ago. In fact, you probably don’t need
any sort of regular communication except perhaps from an organization you
actually interact with on a very regular basis.&lt;/li&gt;
  &lt;li&gt;Try to avoid giving out your email address. It’s really not worth it for
whatever one-time promotion you’re getting. Or perhaps use a second email just
for spam.&lt;/li&gt;
  &lt;li&gt;Turn off &lt;strong&gt;all&lt;/strong&gt; your email notifications. Email is asynchronous
communication. It should not ever send you a notification to interrupt
whatever you were doing. Not on your phone, not on your computer. Check your
email on your own schedule at your leisure.&lt;/li&gt;
  &lt;li&gt;Keep an eye out for better email services. &lt;a href=&quot;https://hey.com/&quot;&gt;Hey&lt;/a&gt; is trying
to make email better and they have some interesting solutions to the spam
problem. Maybe they will help, or maybe the industry as a whole will come up
with some new solutions based on their ideas. We can be hopeful!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;social-media&quot;&gt;Social Media&lt;/h3&gt;

&lt;p&gt;Like email, social media is another place companies fight for your attention.
You want to log on to Facebook to see your friends pictures, companies want to
show you their ads instead. Some of this is obvious – like an advertisement in
your news feed. Sometimes, it’s less obvious – like an athlete you follow
posting a photo with a big logo in it or a band you like posting about a show in
a far-away city. Whatever the case, it’s likely that you’re not getting the most
value from whatever social media platform you’re trying to use.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Unfollow things you don’t want to see. You don’t necessarily have to
“unfriend” someone to “unfollow” them (depending on the platform). But if
you’re following things you’re not interested in (whether it’s people, movies,
companies, or brands), you’re just giving away space in your news feed that
doesn’t even count as an ad.&lt;/li&gt;
  &lt;li&gt;Like email, disable &lt;strong&gt;all&lt;/strong&gt; notifications. For 99% of the people in the world,
the ideal way to use social media is on your own schedule, like email. You
don’t need to be notified on your phone or computer when a friend posts or even
replies to a comment. It will be there in your feed when you decide you want to
look at it.&lt;/li&gt;
  &lt;li&gt;Consider using &lt;a href=&quot;https://www.mozilla.org/en-US/firefox/facebookcontainer/&quot;&gt;Facebook
Container&lt;/a&gt; for
Firefox. It prevents Facebook from tracking your activity across many
non-Facebook websites.&lt;/li&gt;
  &lt;li&gt;Be aware of the ways you might be targeted by advertisers. When you share
information with Facebook (likes, interests, your profile page), they can use
that information to show you specific ads. This isn’t necessarily the worst
thing in the world, but it’s good to be aware that they’re using the info you
give them to do this.&lt;/li&gt;
  &lt;li&gt;Consider ditching social media completely. Do you really need to use Facebook
and Instagram, or can you just email your friends some pictures from your last
trip instead?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;other-apps&quot;&gt;Other Apps&lt;/h3&gt;

&lt;p&gt;Apps are the new email. Shortly after email was born, companies began figuring
out how to use it to advertise. Now, companies are figuring out how to advertise
on phone apps. Most of the time, “ads” on phone apps come in the form of
notifications. Have you ever installed a new app and been surprised about all
the notifications you start getting? “Welcome to so-and-so, let us show you
around.” “5 tips to get started quickly.” “Add this to your account.” “We
noticed you didn’t log in yesterday.” “Act now for a 10% discount.” You don’t
need any of this. Notifications on your phone make a noise to interrupt you.
They cut off the real-world conversation you might be having, or they break your
train of thought. As such, &lt;strong&gt;you should only get notifications for things you’d
actually want to interrupt you&lt;/strong&gt;. Namely, things that require immediate action.
A phone call. Perhaps a text message. Maybe meeting reminders from your calendar.
Of all the apps on your phone that can send you notifications, I bet about 90%
of them can wait ‘til later. I think it’s one of the biggest failures of UX
design on both Android and iOS that notifications are enabled and noisy by
default. Instead, they should probably be opt-in per app (off by default), and
silent by default (or even have a better mechanism for a “notification inbox”
that can be dealt with at your leisure). Regardless, there are some things you
can do that make a really big difference here.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Disable notifications for any app that doesn’t need to interrupt you. On both
Android and iOS, it’s possible to remove the app’s notification permission.
Any time an app sends you a notification that you don’t care about, do this.&lt;/li&gt;
  &lt;li&gt;If you can’t disable all notifications for an app, see if you can turn off all
but the type you care about. For example, maybe you want to be notified of
direct messages on Twitter, but you can still turn off all other notification
types for that app.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you can’t disable a notification (for whatever reason), would it make sense
to silence it? Silencing a notification means no vibration or noise, but you
can check it at your convenience in the notification bar. You should be able to
do this for any app from the system settings.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/unsubscribe-me/silent-notification.jpg&quot; alt=&quot;Silence a notification&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Can you just uninstall the app? Sometimes that’s an easier solution for
badly-behaved apps than trying to figure out the notification permissions.
Your phone is your space, don’t let companies/apps intrude into it who don’t
need to be there.&lt;/li&gt;
  &lt;li&gt;Do you need to limit your screen time? It’s pretty well documented that people
will spend hours using phone apps because of the dopamine hit the apps
provide. Like many things, it’s probably fine to use apps in moderation, but be
wary of becoming sucked in. Maybe talk to someone in real life or go outside
instead. And if you need help breaking the habit,
&lt;a href=&quot;https://support.apple.com/en-us/HT208982&quot;&gt;iOS&lt;/a&gt; and
&lt;a href=&quot;https://www.android.com/digital-wellbeing/&quot;&gt;Android&lt;/a&gt; come with tools to monitor
and limit the time you spend on your phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;internet-ads&quot;&gt;Internet Ads&lt;/h3&gt;

&lt;p&gt;As long as people have been using the internet, there have been ads on it. These
days, ads have become much more sophisticated. Large companies – including
Facebook and Google – gather data about nearly every website you visit. Even if
it’s the website of your local news station or a blog you like to read, it’s
likely that at least one big company knows about that visit, and they use this
information primarily to figure out what your interests are. This is why, at
times, some websites seem to have an uncanny ability to advertise whatever you
were just searching for or shopping for. It’s probably impossible to get rid of
100% of ads on the internet, but there are some simple things you can do to
protect your data and reduce the number of ads you see.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use &lt;a href=&quot;https://www.mozilla.org/en-US/firefox/new/&quot;&gt;Firefox&lt;/a&gt; instead of Chrome.
Chrome is owned by Google, a large advertising company. Firefox will protect
you from things like FLoC that Google is integrating with Chrome.&lt;/li&gt;
  &lt;li&gt;Use an ad blocker browser extension. I use &lt;a href=&quot;https://ublockorigin.com/&quot;&gt;uBlock
Origin&lt;/a&gt; and I think it’s the best because it’s open
source and not controlled by a company.&lt;/li&gt;
  &lt;li&gt;Google collects info about what you type into the search box, so consider
using a search engine other than Google and changing your browser’s default.
&lt;a href=&quot;https://duckduckgo.com/&quot;&gt;DuckDuckGo&lt;/a&gt; is a good alternative to try.&lt;/li&gt;
  &lt;li&gt;If you’re tech-savvy, install a &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi-hole&lt;/a&gt; on your home
network. This is the best generic ad-blocking solution I’ve found.
Installation and configuration is somewhat tricky (you need to understand some
basics about your home network), but there are guides available that make it
easy enough to do.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;protect-your-data&quot;&gt;Protect Your Data&lt;/h3&gt;

&lt;p&gt;If you’re worried about protecting your data online, you’re probably also
worried about geting hacked. One of the most common ways someone might hack you
is from a major data breach. Let’s say you have an account on LinkedIn. And
let’s suppose &lt;a href=&quot;https://www.troyhunt.com/observations-and-thoughts-on-the-linkedin-data-breach/&quot;&gt;LinkedIn gets
hacked&lt;/a&gt;.
Maybe you’re not worried about this - what’s a thief going to do, steal your
resume??? Well, one of the problems with large data breaches is that people can
use that data to figure out the password you used on LinkedIn. And then they can
try to use that password with your email on hundreds of other sites. Amazon,
your bank, your email, Facebook, the list goes on. So how do you protect your
data online?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/03/25/how-i-manage-passwords-with-keepass.html&quot;&gt;Use a password manager&lt;/a&gt;. A password manager helps
you use a different password on every site, so a hack on one site doesn’t
compromise your information on all the others.&lt;/li&gt;
  &lt;li&gt;Use a strong master password that’s never been used anywhere else. There are
two pretty good options for making up a password:
    &lt;ul&gt;
      &lt;li&gt;Think of a sentence with at least 14 words. Maybe a favorite quote. (But not
something you say all the time!) Use the first letter of each word. Even
better if you include capitalization and punctuation in the sentence.&lt;/li&gt;
      &lt;li&gt;Generate a random &lt;a href=&quot;https://www.useapassphrase.com/&quot;&gt;Passphrase&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whos-paying&quot;&gt;Who’s Paying?&lt;/h2&gt;

&lt;p&gt;If there’s one important thing to remember from this whole article, it’s that
&lt;strong&gt;nothing is free, even on the internet&lt;/strong&gt;. If you can’t figure out who’s paying
for something, &lt;em&gt;it’s you&lt;/em&gt;. You’re exchanging your time (or the ability of
companies to show you ads) for the use of a service. Many people have come to
expect free services on the internet (like email, Facebook, Twitter, YouTube,
etc.). But I think people are finally beginning to realize that they don’t want
to trade their time and attention to companies in the way that they have in the
past. So I think it’s likely that we’ll see a shift in the coming years, where
more online services do away with ads and data tracking and will charge a
nominal fee for their use, and many people will be happy to pay this fee to have
their privacy protected and be free from ads. If you’re paying a company for
their service, the company can focus on building things in a way that benefits
you, the user, rather than looking for ways to benefit their advertisers.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/unsubscribe-me.jpg"/>
 </entry>
 
 <entry>
   <title>How To Set Up a Ruby Dev Environment on Ubuntu Linux</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/09/27/set-up-a-ruby-dev-environment.html" type="text/html" title="How To Set Up a Ruby Dev Environment on Ubuntu Linux"/>
   
   <published>2020-09-27T09:15:00-06:00</published>
   <updated>2020-09-27T09:15:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/09/27/set-up-a-ruby-dev-environment</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/09/27/set-up-a-ruby-dev-environment.html">&lt;p&gt;Setting up a development environment correctly might not seem like a big deal at
first, but an incorrectly set-up environment can cause a lot of problems down
the road if you’re not careful. In my experience, the internet’s full of
solutions – both good and bad – for environment problems, but often does a poor
job of explaining &lt;em&gt;why&lt;/em&gt; a solution’s good or bad. And installing Ruby can be
tricky if you’ve never done it before. So here’s my advice.&lt;/p&gt;

&lt;p&gt;Let’s say you want to run a gem – for example, &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;.
To run the gem, you’ll have to install Ruby and then install the gem. Rather
than jumping straight to the best solution, I’m first going to look at &lt;strong&gt;what
not to do&lt;/strong&gt; so you can understand what problems this might cause. After seeing
what not do to, we’ll look at a couple better solutions, and we’ll see the
advantages and disadvantages of each. By the end, I’ll show you what I think is
the best way to install Ruby so you can avoid environment headaches.&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;In a hurry?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Want to go straight to the instructions for the proper way (in my opinion) to install Ruby without reading about what not to do?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;#the-best-solution-how-to-install-ruby-with-rbenv&quot;&gt;Skip ahead&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;what-not-to-do&quot;&gt;What Not To Do&lt;/h2&gt;

&lt;p&gt;When you’re a beginner with Ruby or Linux (we’ve all been there) and all you
want to do is run a gem, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; appears to solve all your problems. You might
come across advice like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Install Ruby&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install ruby
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install the gem&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo gem install jekyll
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Use the gem. Done.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ jekyll --version
jekyll 4.1.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While this might appear to solve everything, it can also create a host of new
problems. For example, maybe you’ll need to install some gems with
&lt;a href=&quot;https://bundler.io/&quot;&gt;Bundler&lt;/a&gt; a few days later. If you try using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; to
install gems with Bundler, you’ll probably see a warning like this one:&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo bundle install
Don&apos;t run Bundler as root. Bundler can ask for sudo if it is needed, and
installing your bundle as root will break this application for all non-root
users on this machine.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Bundler warns you that this isn’t the best way to do things - root will be the
owner of all the files bundler installs, and from that point on you’ll have to
continue using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; to get gem updates until you fix your file permissions.&lt;/p&gt;

&lt;p&gt;But beyond this, installing gems with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; can actually be dangerous -
particularly since gems can execute scripts when they install. For example, see
&lt;a href=&quot;https://github.com/wmorgan/killergem&quot;&gt;killergem&lt;/a&gt;. Even if you didn’t install
this malicious gem directly, if one of the gems you try to install even depends
on a malicious or compromised gem, it could be bad news. Let’s look at a better
solution…&lt;/p&gt;

&lt;h2 id=&quot;a-quick--simple-solution&quot;&gt;A Quick &amp;amp; Simple Solution&lt;/h2&gt;

&lt;p&gt;So if the above steps are problematic, what can we do that’s safer? Maybe you
just want to run a quick script or gem and you don’t care about managing
specific versions of Ruby. You want a simple solution that works and doesn’t
have permissions problems.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Install Ruby. For simplicity, we’ll use whatever version is in Ubuntu’s
repositories. Using sudo is OK here because we’re using apt-get.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt-get install ruby
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install the gem. Since we’re installing a gem (rather than a system package
from apt), we won’t use sudo. Instead, we’ll pass the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--user-install&lt;/code&gt; flag when
we install our gem.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ gem install --user-install jekyll
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--user-install&lt;/code&gt; portion is key – it installs the gem in a directory our
user has permissions to. You’ll probably see a warning like this:&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;WARNING:  You don’t have /home/mike/.gem/ruby/2.7.1/bin in your PATH, gem executables will not run.&lt;/p&gt;
    &lt;/blockquote&gt;

    &lt;p&gt;That’s OK – we’ll fix it in the next step.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;As the warning says, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gem/ruby/2.7.1/bin&lt;/code&gt; isn’t in our PATH. So let’s put
it there. Ubuntu comes with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.profile&lt;/code&gt; file that adds a couple things to our
path by default, and I like to build on that file. Modify it to include these
lines:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Add User Ruby Gems
if [ -d &quot;$HOME/.gem/ruby/2.7.1/bin&quot; ]; then
    PATH=&quot;$HOME/.gem/ruby/2.7.1/bin:$PATH&quot;
fi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;For the changes to take effect, we can either log out and log back in or
simply run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source ~/.profile&lt;/code&gt;. We can confirm it worked by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;which
jekyll&lt;/code&gt;, which should return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gem/ruby/2.7.1/bin/jekyll&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done. We’ve installed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll&lt;/code&gt; gem without using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt;. In this case, for
simplicity, we’re relying on our OS to provide an easy-to-user Ruby installer.
This is fine, but it does mean &lt;strong&gt;we’re stuck with the version of Ruby that our
OS provides&lt;/strong&gt;. Which is fine for simple needs (like running a few simple scripts
or gems), but might not be ideal if, for example, you’re developing a Ruby
application that requires a specific version and want to upgrade your version of
Ruby independently of your OS. If you’re regularly programming in Ruby, it will
be important for you to install the version of Ruby required by your
application, and possibly install multiple versions of Ruby for multiple
applications or during upgrades.&lt;/p&gt;

&lt;h2 id=&quot;the-best-solution-how-to-install-ruby-with-rbenv&quot;&gt;The Best Solution: How to Install Ruby with rbenv&lt;/h2&gt;

&lt;p&gt;The best solution for installing Ruby and managing your installation is a Ruby
version manager. Using a version manager allows you to have multiple versions of
Ruby installed at once and select the one you want to use. It also decouples
your Ruby installation from your OS package manager, and provides a solution for
doing everything without sudo. There are a few options for version managers.
&lt;a href=&quot;https://rvm.io/&quot;&gt;RVM&lt;/a&gt; is reputable and has a good following, but in my humble
opinion &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;rbenv&lt;/a&gt; is simpler and easier to use. To
install rbenv, you can find the most up-to-date official instructions
&lt;a href=&quot;https://github.com/rbenv/rbenv#basic-github-checkout&quot;&gt;here&lt;/a&gt;, but they can be a
little confusing, and they’ll boil down to something like this:&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;Remove old Ruby versions first!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;It is confusing to have Ruby installed by both apt-get and rbenv on the same system. If you&apos;ve already installed Ruby with apt-get, you should remove it before continuing. You can do so with &lt;code&gt;sudo apt-get purge ruby&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;We’ll need git and curl to run the rbenv installer. Run the following to
make sure they’re installed.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt-get install git curl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Run the rbenv installer.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Add rbenv to your path by adding it to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.bashrc&lt;/code&gt;. You’ll have to log
out and log back in or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source ~/.bashrc&lt;/code&gt; for the changes to take effect.
(And we’re tackling 2 steps at once by including the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv init&lt;/code&gt; line
here.)&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ echo &apos;
if [ -d &quot;$HOME/.rbenv/bin&quot; ]; then
    PATH=&quot;$HOME/.rbenv/bin:$PATH&quot;
    eval &quot;$(rbenv init - bash)&quot;
fi&apos; &amp;gt;&amp;gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Check to make sure it worked with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv-doctor&lt;/code&gt;. This will save you some
headaches by printing errors if anything’s not quite right.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-doctor | bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;You’ll see something like this, letting you know that rbenv is installed but
no versions of Ruby are installed yet.&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;Counting installed Ruby versions: none
  There aren’t any Ruby versions installed under `/home/mkasberg/.rbenv/versions’.
  You can install Ruby versions like so: rbenv install 2.7.1&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;We’re going to use rbenv to install a version of Ruby, but before we do that
we need to make sure we have the tools to build ruby installed. (If you
skip this step, the installer won’t work and will give you an error message
about what you need to install to compile Ruby. We can save time by just
installing what we need now.)&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt-get install build-essential libssl-dev libyaml-dev zlib1g-dev libffi-dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now, we’re ready to install Ruby. I like to run the help command first to
see what I’m about to do. You should generally install whatever the latest
stable version is. At the time of writing, it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.7.1&lt;/code&gt;.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ rbenv help install
$ rbenv install --list
$ rbenv install 2.7.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;At this point, you’re basically &lt;strong&gt;done&lt;/strong&gt;! Again, we can skim the help menu to
see what other commands are available. And it’s a good idea to set the
version we just installed as the global ruby version.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ rbenv help
$ rbenv global 2.7.1
$ rbenv version
$ ruby --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing that’s great about rbenv is it does not tie you in to a specific ruby
version. You can set up &lt;a href=&quot;https://github.com/rbenv/rbenv#rbenv-local&quot;&gt;local&lt;/a&gt;
(per-directory) and shell (per-terminal) versions if you want. Simply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv
install x.x.x&lt;/code&gt; and then use the correct commands (see the docs) to pick your
version. In addition to this, by default, rbenv will allow you to &lt;a href=&quot;https://github.com/rbenv/rbenv#installing-ruby-gems&quot;&gt;install
gems&lt;/a&gt; to a location that
doesn’t require sudo (with a normal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem install&lt;/code&gt; command), which should prevent
a lot of headaches.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/set-up-a-ruby-dev-environment.jpg"/>
 </entry>
 
 <entry>
   <title>How to Install Custom Firmware on Your Xiaomi M365 E-Scooter</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/07/05/how-to-install-custom-firmware-on-your-xiaomi-m365.html" type="text/html" title="How to Install Custom Firmware on Your Xiaomi M365 E-Scooter"/>
   
   <published>2020-07-05T15:30:00-06:00</published>
   <updated>2020-07-05T15:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/07/05/how-to-install-custom-firmware-on-your-xiaomi-m365</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/07/05/how-to-install-custom-firmware-on-your-xiaomi-m365.html">&lt;p&gt;The &lt;a href=&quot;https://www.mi.com/global/mi-electric-scooter&quot;&gt;Xiaomi m365&lt;/a&gt; (also known as
the Xiaomi Mi) is one of the most highly-rated electric scooters available. If
you’ve ever rented a Lyft or a Bird scooter, the design will look very familiar.
This electric scooter is fun to ride and great for commuting! But one of the
best things about the m365 scooter is that you can modify the firmware to suit
your own preferences! It’s easy to do and should only take 20 or 30 minutes.
Let’s get started!&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;Modify your firmware at your own risk!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;These instructions worked for me, but I can&apos;t guarantee they&apos;ll work for you.
There are inherent risks associated with this procedure and I&apos;m not responsible
for anything that goes wrong.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;part-1-ensure-your-scooter-is-compatible-with-modified-firmware&quot;&gt;Part 1: Ensure your scooter is compatible with modified firmware&lt;/h2&gt;

&lt;p&gt;This firmware mod will work on any Xiaomi m365 e-scooter with 2 fuses. Most m365
scooters manufactured after March 2017 do have 2 fuses. To verify that your
scooter has 2 fuses:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Find the serial number of your scooter. It’s located on a sticker on the
side of the base and will look like the one in the picture below. There are two
parts separated by a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;. The first part describes the country
and the color. The second part is a unique serial number.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/m365-custom-firmware/serial-number.jpg&quot; alt=&quot;m365 serial number sticker&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Use the first part of the number to find some info about your scooter in
the chart below:&lt;/p&gt;

    &lt;table&gt;
      &lt;thead&gt;
        &lt;tr&gt;
          &lt;th&gt;Serial No.&lt;/th&gt;
          &lt;th&gt;Country&lt;/th&gt;
          &lt;th&gt;Color&lt;/th&gt;
          &lt;th&gt;2 Fuses?&lt;/th&gt;
        &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;13678&lt;/td&gt;
          &lt;td&gt;China&lt;/td&gt;
          &lt;td&gt;White&lt;/td&gt;
          &lt;td&gt;Check Battery Date&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;13679&lt;/td&gt;
          &lt;td&gt;China&lt;/td&gt;
          &lt;td&gt;Black&lt;/td&gt;
          &lt;td&gt;Check Serial&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;16132&lt;/td&gt;
          &lt;td&gt;Europe&lt;/td&gt;
          &lt;td&gt;White&lt;/td&gt;
          &lt;td&gt;Check Battery Date&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;16133&lt;/td&gt;
          &lt;td&gt;Europe&lt;/td&gt;
          &lt;td&gt;Black&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;16349&lt;/td&gt;
          &lt;td&gt;US&lt;/td&gt;
          &lt;td&gt;Black&lt;/td&gt;
          &lt;td&gt;&lt;a href=&quot;https://www.amazon.com/ask/questions/Tx2RGKDYX0X9WRD/ref=ask_ql_ql_al_hza&quot;&gt;Yes&lt;/a&gt;&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you have the European black version or the US version, you should have 2
fuses. Skip ahead to part 2. If you have the Chinese black version, and the
second part of your serial number is greater than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;00035000&lt;/code&gt;, you should have 2
fuses – skip ahead to part 2. If you have a white model continue here to check
the battery date.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;To check the battery date of manufacture, you need to connect your scooter
to the Mi Home app
(&lt;a href=&quot;https://apps.apple.com/us/app/mi-home-xiaomi-smarthome/id957323480&quot;&gt;iOS&lt;/a&gt;/&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.xiaomi.smarthome&quot;&gt;Android&lt;/a&gt;).
After connecting your scooter to the app, look at &lt;strong&gt;Battery Info&lt;/strong&gt; and check the
&lt;strong&gt;Production Date&lt;/strong&gt;. If the battery production date is later than March 2017,
you should have 2 fuses, and can continue with the mod.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your scooter has 2 fuses, it should be safe to modify the firmware. If the
scooter is an older model with a single fuse, it is risky to continue because
the firmware mod is likely to destroy the fuse.&lt;/p&gt;

&lt;h2 id=&quot;part-2-build-your-custom-firmware&quot;&gt;Part 2: Build your custom firmware&lt;/h2&gt;

&lt;p&gt;GitHub user BotoX has developed a tool to patch custom parameters into the m365
scooter firmware. The tool is open source, developed in Python, and you can see
the source code &lt;a href=&quot;https://github.com/BotoX/xiaomi-m365-firmware-patcher&quot;&gt;on
GitHub&lt;/a&gt; if you’re
interested. Really though, the source code isn’t what we’re interested in. We’re
simply going to use the software on his website to patch a custom firmware.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open &lt;a href=&quot;https://m365.botox.bz&quot;&gt;https://m365.botox.bz&lt;/a&gt; on your phone.&lt;/li&gt;
  &lt;li&gt;Select the options for your own custom firmware. You can get started with
some defaults using the buttons at the top. The website explains most of the
settings well, but I’ll add some detail for a few important settings below.&lt;/li&gt;
  &lt;li&gt;When you’re done, click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Patch!&lt;/code&gt; and save the zip to your phone.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;my-settings&quot;&gt;My Settings&lt;/h3&gt;

&lt;p&gt;I’ll share the settings I prefer below. Use these as suggestions if you’re not
sure what to customize.&lt;/p&gt;

&lt;h4 id=&quot;base-version&quot;&gt;Base Version&lt;/h4&gt;

&lt;p&gt;There’s a strong consensus that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.3.8&lt;/code&gt; is the best version to use, so I used
that as my &lt;strong&gt;base version&lt;/strong&gt;.&lt;/p&gt;

&lt;h4 id=&quot;kers-min-speed&quot;&gt;KERS min speed&lt;/h4&gt;

&lt;p&gt;I didn’t change the &lt;strong&gt;KERS min speed&lt;/strong&gt; because I actually like the automatic
braking, and I’ve come to expect it.&lt;/p&gt;

&lt;h4 id=&quot;maximum-speed-in-normal-mode&quot;&gt;Maximum speed in normal mode&lt;/h4&gt;

&lt;p&gt;I bumped the &lt;strong&gt;maximum speed in normal mode&lt;/strong&gt; up to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;30&lt;/code&gt;. This is in km/h, so
that’s roughly 18.5 mph. I would have set it faster, but other components
prevent the scooter from going much faster, so I’ve found &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;30&lt;/code&gt; to be the highest
you can go here to keep a smooth ride. I didn’t patch the eco mode speed (which
I rarely use anyway).&lt;/p&gt;

&lt;h4 id=&quot;motor-start-speed&quot;&gt;Motor start speed&lt;/h4&gt;

&lt;p&gt;I set the &lt;strong&gt;motor start speed&lt;/strong&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5&lt;/code&gt;. This prevents those
situations where you need to kick more to get the motor to start.&lt;/p&gt;

&lt;h4 id=&quot;motor-power-constant&quot;&gt;Motor power constant&lt;/h4&gt;

&lt;p&gt;I also bumped the &lt;strong&gt;motor power constant&lt;/strong&gt; down to the DYoC setting of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;40165&lt;/code&gt;
(remember lower is more power). This seemed like a good compromise to get a
little extra power (i.e. acceleration) but not put too much extra wear on the
motor. (I don’t want it to break too quickly!)&lt;/p&gt;

&lt;p&gt;I left the rest of the settings alone – most of them are unnecessary to change
or only needed for advanced/unusual customization. But feel free to experiment
more if you want!  Here’s another blogger’s
&lt;a href=&quot;http://nelsonware.com/blog/2018/09/24/my-xiaomi-mijia-m365-dyoc-settings.html&quot;&gt;thoughts&lt;/a&gt;.
This tool lets you customize the firmware that’s right &lt;em&gt;for you&lt;/em&gt;, so you don’t
have to use my settings if you don’t want to.&lt;/p&gt;

&lt;h2 id=&quot;part-3-install-the-firmware-to-the-scooter&quot;&gt;Part 3: Install the firmware to the scooter&lt;/h2&gt;

&lt;p&gt;Finally, we’re ready to install our firmware. The best way is to use the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.m365downgrade&quot;&gt;m365
DownG&lt;/a&gt; Android
app. First, connect the app to the scooter. After you’re connected, choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Open
Bin&lt;/code&gt; to open the zipped firmware you downloaded from the BotoX website. Finally,
hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flash Bin&lt;/code&gt; to send the firmware to the scooter. If you don’t have an
Android device, see if you can find a friend who will let you use theirs.
Apparently there’s a new, similar app on iOS, but I’m not sure how well it
works.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/m365-custom-firmware/downg.jpg&quot; alt=&quot;m365 DownG app screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now you’re done! Take that scooter out for a test ride! And let me know what
you think &lt;a href=&quot;https://twitter.com/mike_kasberg&quot;&gt;@mike_kasberg&lt;/a&gt;. Of course, credit
is also due to &lt;a href=&quot;https://github.com/botox&quot;&gt;BotoX&lt;/a&gt;, who developed the firmware
tool, and &lt;a href=&quot;https://github.com/CamiAlfa&quot;&gt;CamiAlfa&lt;/a&gt;, who developed the flashing
app. Thanks for reading, have fun, and stay safe!&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Tutorials"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/m365-custom-firmware.jpg"/>
 </entry>
 
 <entry>
   <title>Review: Orange Pi Zero</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/07/03/review-orange-pi-zero.html" type="text/html" title="Review: Orange Pi Zero"/>
   
   <published>2020-07-03T15:00:00-06:00</published>
   <updated>2020-07-03T15:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/07/03/review-orange-pi-zero</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/07/03/review-orange-pi-zero.html">&lt;p&gt;&lt;a href=&quot;http://www.orangepi.org&quot;&gt;Orange Pi&lt;/a&gt; is a relatively unknown competitor to
&lt;a href=&quot;https://www.raspberrypi.org/&quot;&gt;Raspberry Pi&lt;/a&gt;. I recently learned about Orange Pi
while doing some research about a Raspberry Pi project I wanted to start, and I
actually ended up buying the &lt;a href=&quot;http://www.orangepi.org/orangepizero/&quot;&gt;Orange Pi
Zero&lt;/a&gt;. Orange Pi is a Chinese company
that is focused on producing small PC boards – similar to Raspberry Pi – as
cheaply as possible. And Orange Pi produces a wider variety of boards than
Raspberry Pi, so perhaps one of their boards will fit your project better than a
Raspberry Pi would.&lt;/p&gt;

&lt;p&gt;To get a sense for what Orange Pi is, let’s start with a side-by-side comparison
of the &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-zero-w/&quot;&gt;Raspberry Pi Zero
W&lt;/a&gt; and the &lt;a href=&quot;http://www.orangepi.org/orangepizero/&quot;&gt;Orange Pi
Zero&lt;/a&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Raspberry Pi Zero W&lt;/th&gt;
      &lt;th&gt;Orange Pi Zero&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Price&lt;/td&gt;
      &lt;td&gt;$9.99&lt;/td&gt;
      &lt;td&gt;$11.49&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Power In&lt;/td&gt;
      &lt;td&gt;Micro USB&lt;/td&gt;
      &lt;td&gt;Micro USB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Storage&lt;/td&gt;
      &lt;td&gt;Micro SD&lt;/td&gt;
      &lt;td&gt;Micro SD&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPU&lt;/td&gt;
      &lt;td&gt;1GHz BCM2835&lt;/td&gt;
      &lt;td&gt;1.2GHz Allwinner H2+&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RAM&lt;/td&gt;
      &lt;td&gt;512MB&lt;/td&gt;
      &lt;td&gt;512MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Display Out&lt;/td&gt;
      &lt;td&gt;Mini HDMI&lt;/td&gt;
      &lt;td&gt;None/External Board&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Wifi&lt;/td&gt;
      &lt;td&gt;802.11 b/g/n 2.4GHz&lt;/td&gt;
      &lt;td&gt;802.11 b/g/n, but don’t use it&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bluetooth&lt;/td&gt;
      &lt;td&gt;4.1&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ethernet&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;10/100M RJ-45&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Micro USB&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;USB 2.0&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;As you can see, the boards are &lt;em&gt;very&lt;/em&gt; similar. The Raspberry Pi has a few
advantages – micro HDMI support, Bluetooth support, micro USB support, and it’s
$1.49 cheaper. But the Orange Pi &lt;em&gt;also&lt;/em&gt; has a few advantages – an Ethernet
port, standard USB port, and a slightly faster processor. As such, there’s no
clear winner. It depends on the project you want to use it for.&lt;/p&gt;

&lt;p&gt;Now, I see a lot of people using the Pi Zero W for a wide variety of projects
where it’s essentially functioning as an always-on LAN server. This might be a
file server, web server, &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi-Hole&lt;/a&gt;, internet camera, or
a similar LAN application. For many of these types of projects, I think the
&lt;strong&gt;Orange Pi Zero is actually a better choice.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You don’t need a display, so the micro HDMI is somewhat useless. Also, who
owns a micro HDMI cable?&lt;/li&gt;
  &lt;li&gt;For an always-on server, wired networking is a lot more reliable than
wireless. So the RJ-45 port is a major plus.&lt;/li&gt;
  &lt;li&gt;The Pi Zero W doesn’t even support 5Ghz wireless! Again, relying on Ethernet
is probably better here.&lt;/li&gt;
  &lt;li&gt;I think the standard USB port is a small plus - you’re less likely to need an
adapter.&lt;/li&gt;
  &lt;li&gt;The slightly faster CPU on the Orange Pi is nice.&lt;/li&gt;
  &lt;li&gt;As long as you’re not doing a Bluetooth-specific project, there’s no loss
there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For only $1.49 more, you get a lot of advantages with the Orange Pi for
a project like an always-on LAN server. I’ve been running an Orange Pi Zero as
an always-on LAN server for a couple months now, and it’s performed great! I had
problems with the wifi dropping out on the Raspberry Pi, and the Orange Pi
didn’t have the same problem because it’s wired to the LAN.&lt;/p&gt;

&lt;p&gt;Of course, the Orange Pi isn’t better across the board. In particular, if you’re
doing a project where you need Bluetooth or micro HDMI, the Raspberry Pi is the
obvious choice.&lt;/p&gt;

&lt;p&gt;Documentation for the Raspberry Pi is great, and you’ll find that it’s a little
bit harder to figure out the Orange Pi. But if you’re comfortable with computer
basics it’s not bad at all. You’ll want to use
&lt;a href=&quot;https://www.armbian.com/&quot;&gt;Armbian&lt;/a&gt; – which is a lot like Raspbian but for a
broader range of devices. Once that’s set up, follow the “Linux” or “Debian”
installation instructions for whatever additional software you need.&lt;/p&gt;

&lt;p&gt;Should you use an Orange Pi your next project? At only $11.49 for the Orange Pi
Zero, I think it’s worth considering. If you do want to buy one, the best place
to do so is on
&lt;a href=&quot;https://www.aliexpress.com/item/1005001688034180.html&quot;&gt;AliExpress&lt;/a&gt; or
&lt;a href=&quot;https://www.amazon.com/dp/B07ZYJGPF8&quot;&gt;Amazon&lt;/a&gt;. You might find them in other
places as well, but the prices will probably be higher.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Reviews"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/review-orange-pi-zero.jpg"/>
 </entry>
 
 <entry>
   <title>A Bookmark to Temporarily Disable your Pi-hole</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/06/26/a-bookmark-to-temporarily-disable-your-pihole.html" type="text/html" title="A Bookmark to Temporarily Disable your Pi-hole"/>
   
   <published>2020-06-26T20:45:00-06:00</published>
   <updated>2020-06-26T20:45:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/06/26/a-bookmark-to-temporarily-disable-your-pihole</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/06/26/a-bookmark-to-temporarily-disable-your-pihole.html">&lt;p&gt;I use a &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi-hole&lt;/a&gt; on my home network and I love it! It
works by configuring your local network to use the Pi-hole as the DNS server,
and the Pi-hole refuses to resolve domains of sites you want to block. It
does a pretty good job of blocking everything from unwanted trackers to ads and
malware. Sometimes, though, it’s annoying because it blocks a link that I
actually want to visit. Maybe I’m trying to visit a sponsored Google link,
an Amazon product link, or a link from an email. In any case, I just don’t want
the link to be blocked, and the Pi-hole gets in the way. This can be
inconvenient because the usual process to temporarily allow blocked traffic is
to open the Pi-hole admin page in a new tab, login, and click the link to disable
it in the menu. Too much work! So I came up with a slightly easier way. I use a
bookmark in my bookmarks toolbar that will disable the Pi-hole for 30 seconds.&lt;/p&gt;

&lt;p&gt;Do you want a “Disable Pi-hole” bookmark for your own Pi-hole? You can generate
one using the form below! You’ll need the &lt;strong&gt;IP address&lt;/strong&gt; of your own Pi-hole (on
your local network). (Pro tip: You can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pi.hole&lt;/code&gt; in place of the IP address
for most Pi-hole setups. Try it and see if it works for you!) You’ll also need
the &lt;strong&gt;webpassword&lt;/strong&gt;, which you can get by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat
/etc/pihole/setupVars.conf&lt;/code&gt; on your Pi-hole (probably via ssh). I find 30s to be
a good default &lt;strong&gt;duration&lt;/strong&gt; for it to be disabled, but you can adjust that as
well if you want. Then just save the link as a bookmark (for example, by
right-clicking it) and you’re done!&lt;/p&gt;

&lt;form class=&quot;simple-form&quot;&gt;
  &lt;label for=&quot;ip&quot;&gt;Pi-hole IP:&lt;/label&gt;
  &lt;input type=&quot;text&quot; id=&quot;ip&quot; onchange=&quot;updateLink()&quot; value=&quot;192.168.0.1&quot; /&gt;
  &lt;br /&gt;
  &lt;label for=&quot;webpw&quot;&gt;webpassword:&lt;/label&gt;
  &lt;input type=&quot;text&quot; id=&quot;webpw&quot; onchange=&quot;updateLink()&quot; value=&quot;cec933c941073878ba603c01cd1b96&quot; /&gt;
  &lt;br /&gt;
  &lt;label for=&quot;duration&quot;&gt;Duration (s):&lt;/label&gt;
  &lt;input type=&quot;number&quot; id=&quot;duration&quot; onchange=&quot;updateLink()&quot; value=&quot;30&quot; /&gt;
  &lt;br /&gt;
 &lt;button type=&quot;button&quot; onclick=&quot;updateLink()&quot;&gt;Generate Link!&lt;/button&gt;
&lt;/form&gt;

&lt;p&gt;&lt;strong&gt;Bookmark this link:&lt;/strong&gt; &lt;a id=&quot;link&quot; href=&quot;http://192.168.0.1/admin/api.php?disable=30&amp;amp;auth=&quot;&gt;Disable Pi-hole (30s)&lt;/a&gt;&lt;/p&gt;

&lt;script&gt;
function updateLink() {
  var ip = document.getElementById(&quot;ip&quot;).value;
  var webpw = document.getElementById(&quot;webpw&quot;).value;
  var duration = document.getElementById(&quot;duration&quot;).value;
  var target = &quot;http://&quot; + ip + &quot;/admin/api.php?disable=&quot; + duration + &quot;&amp;auth=&quot; + webpw;

  var durationText = duration + &quot;s&quot;;
  if (duration &gt; 60) {
    durationText = Math.floor(duration / 60) + &quot;min&quot;;
  }
  var name = &quot;Disable Pi-hole (&quot; + durationText + &quot;)&quot;;

  var link = document.getElementById(&quot;link&quot;);
  link.href = target;
  link.innerHTML = name;
}
&lt;/script&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Tutorials"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/a-bookmark-to-disable-your-pihole.jpg"/>
 </entry>
 
 <entry>
   <title>Can You VNC to a Desktop on a Headless Raspberry Pi?</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/06/22/vnc-for-headless-raspberry-pi.html" type="text/html" title="Can You VNC to a Desktop on a Headless Raspberry Pi?"/>
   
   <published>2020-06-22T20:45:00-06:00</published>
   <updated>2020-06-22T20:45:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/06/22/vnc-for-headless-raspberry-pi</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/06/22/vnc-for-headless-raspberry-pi.html">&lt;p&gt;&lt;strong&gt;No keyboard, mouse, or monitor required!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my &lt;a href=&quot;/blog/2020/06/19/headless-setup-for-a-raspberry-pi-zero-w.html&quot;&gt;last blog post&lt;/a&gt;, I figured out how to
enable SSH access to a Pi Zero W without attaching a mouse, keyboard, or
monitor. This us useful because it means you don’t need a micro-HDMI or
micro-USB OTG cable to attach a monitor, keyboard, or mouse. But can we take it
a step further? I wanted to find out if I could get a GUI desktop environment
running without attaching a monitor to the Pi. And I did it!&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;After gaining SSH access to the Pi, it was fairly simple to set up VNC and get
to a desktop environment on the Pi without ever connecting a monitor. Here’s
what I did:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Set up SSH access on the Pi (as I describe &lt;a href=&quot;/blog/2020/06/19/headless-setup-for-a-raspberry-pi-zero-w.html&quot;&gt;here&lt;/a&gt;) and connect via SSH.&lt;/li&gt;
  &lt;li&gt;Setting up a VNC server on the Pi is easy because the OS &lt;a href=&quot;https://www.raspberrypi.org/documentation/remote-access/vnc/&quot;&gt;comes with
one&lt;/a&gt;
installed. On the Pi, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo raspi-config&lt;/code&gt;.
    &lt;ol&gt;
      &lt;li&gt;Select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Interfacing Options&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Enable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VNC&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;We’re done with config. Exit raspi-config.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;On the computer you’re going to connect from, you’ll need to install a VNC
Viewer. Download and install &lt;a href=&quot;https://www.realvnc.com/en/connect/download/viewer/&quot;&gt;RealVNC
Viewer&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Open VNC Viewer and connect to your Pi (using the same IP address you use to
ssh). Log in with the same credentials you use to SSH.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/vnc-for-headless-raspberry-pi/vnc-pi.png&quot; alt=&quot;VNC Viewer showing Pi Desktop&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;is-this-actually-usable&quot;&gt;Is This Actually Usable?&lt;/h2&gt;

&lt;p&gt;Kind of. I tested a VNC connection to a &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-zero-w/&quot;&gt;Raspberry Pi Zero
W&lt;/a&gt; desktop. The Pi
Zero has a 1GHz single-core CPU and only 512MB RAM – not much to work with! I
was able to log in to the default LXDE desktop environment, and the UI was
responsive enough to use.  Simple applications ran a little slow, but were
usable. It took about 4 seconds for the terminal app to open, and about 5
seconds for the text editor to open.  Once they were open, they were pretty
responsive. There wasn’t a noticeable delay when typing. Bigger apps, however,
weren’t really usable. Chromium took a whopping 35 seconds to open, and was very
slow to open a new tab. It felt like running Windows XP on an underpowered
computer. I think the slowness was primarily due to the limitations of the Pi
itself rather than the VNC setup.&lt;/p&gt;

&lt;p&gt;So overall, the VNC user interface would work well enough for doing some simple
setup, or installing whatever software you want to run on the Pi. But more
intensive things like web browsing are too slow to be really useful. And that
makes sense. If you want to use the Pi as a simple desktop computer for things
like web browsing, you’ll want to get a better version than the Pi Zero anyway.
I’d say the Pi Zero is &lt;strong&gt;not&lt;/strong&gt; usable for even simple desktop work.  But if
you’re using it for a simple project or home server and you need a GUI to set it
up, VNC could be really helpful! Honestly though, if you’re comfortable enough
at the terminal, I’d probably just stick to SSH.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This is part 3 of a 3-part series exploring the Raspberry-Pi Zero.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href=&quot;/blog/2020/06/19/headless-setup-for-a-raspberry-pi-zero-w.html&quot;&gt;Headless Setup for a Raspberry Pi Zero-W&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Part 2:&lt;/strong&gt; Can You VNC to a Desktop on a Headless Raspberry Pi?&lt;/li&gt;
&lt;/ul&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Tutorials"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/vnc-for-headless-raspberry-pi.jpg"/>
 </entry>
 
 <entry>
   <title>Headless Setup for a Raspberry Pi Zero-W</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/06/19/headless-setup-for-a-raspberry-pi-zero-w.html" type="text/html" title="Headless Setup for a Raspberry Pi Zero-W"/>
   
   <published>2020-06-19T21:00:00-06:00</published>
   <updated>2020-06-19T21:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/06/19/headless-setup-for-a-raspberry-pi-zero-w</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/06/19/headless-setup-for-a-raspberry-pi-zero-w.html">&lt;p&gt;&lt;strong&gt;No keyboard, mouse, or monitor required!&lt;/strong&gt; In this tutorial, I’ll show you how
to ssh to a Raspberry Pi Zero W without ever using a keyboard, mouse, or monitor
on the device itself. We’ll prepare the device to connect to your wireless
network (even with a password) when it’s turned on for the first time – this
process is known as “headless setup”.&lt;/p&gt;

&lt;p&gt;When you buy a Raspberry Pi, the cost of all the little accessories you might
need (like a way to connect a keyboard, mouse, and monitor to the micro-USB and
micro-HDMI ports) can add up quickly. But do you really need all that? &lt;strong&gt;You
don’t!&lt;/strong&gt; It isn’t too hard to configure a Raspberry Pi Zero W to connect to a
wireless network automatically when it boots, and then you can interact with it
via SSH without ever connecting a monitor, keyboard, or mouse!  (You’ll need
another computer, of course.) To accomplish this, we’ll modify a couple things
on the SD card after we’ve written the image but before putting it in the Pi.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Prepare your SD card with Raspberry Pi OS (AKA Raspbian), as described
&lt;a href=&quot;https://www.raspberrypi.com/documentation/computers/getting-started.html#installing-the-operating-system&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Open the “boot” partition of the SD card. This should show up as a removable
drive when you insert the card – you might need to remove and re-insert the SD
card after writing the image.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;On the boot partition, create a text file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wpa_supplicant.conf&lt;/code&gt; (no
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.txt&lt;/code&gt;!) with the following contents. This will configure the wifi when the
device boots.  (Change the ssid and psk to your network name and password.)&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
    ssid=&quot;MyWiFiNetwork&quot;
    psk=&quot;aVeryStrongPassword&quot;
    key_mgmt=WPA-PSK
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;div class=&quot;warning&quot;&gt;
  &lt;p&gt;&lt;b&gt;Warning!&lt;/b&gt;&lt;/p&gt;
  &lt;p&gt;The Pi Zero W doesn&apos;t support 5GHz networks. You&apos;ll need to use a 2.4GHz
  network with your Pi.&lt;/p&gt;
&lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Even if the Pi connects to wifi automatically, ssh access will be disabled by
default. To change this, make an empty text file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt; (no file
extension!) on the boot partition. As long as this file exists, ssh will
be enabled when the device boots for the first time.&lt;/li&gt;
  &lt;li&gt;Now we’re ready! Put the SD card in the Pi, plug in the Pi, and give it a
minute to boot up and connect to the wifi. This is where the magic happens,
where the Pi &lt;strong&gt;boots, connects to your network, and enables SSH access without
any interaction&lt;/strong&gt;. At this point, we’re basically
&lt;strong&gt;done&lt;/strong&gt; with setup, but we still need to connect to the Pi.&lt;/li&gt;
  &lt;li&gt;Find the IP address of the Pi on your local network. There are a variety of
ways to do this depending on your network configuration. Read the instructions
&lt;a href=&quot;https://www.raspberrypi.com/documentation/computers/remote-access.html#ip-address&quot;&gt;here&lt;/a&gt;
and pick a method that works for you. The Pi’s IP will probably be something
like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192.168.0.10&lt;/code&gt;, but it could be different.&lt;/li&gt;
  &lt;li&gt;SSH to the Pi.
    &lt;ul&gt;
      &lt;li&gt;If you’re on &lt;strong&gt;macOS&lt;/strong&gt; or &lt;strong&gt;Linux&lt;/strong&gt;, the ssh tool should already be installed. Open
the Terminal app (in the Utilities folder in Applications) and type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh
pi@192.168.x.x&lt;/code&gt; (using the IP you found in the previous step). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pi&lt;/code&gt; is the
username and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raspberry&lt;/code&gt; is the default password.&lt;/li&gt;
      &lt;li&gt;If you’re on &lt;strong&gt;Windows&lt;/strong&gt;, you’ll need to install an SSH tool. I recommend
&lt;a href=&quot;https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html&quot;&gt;PuTTY&lt;/a&gt;.
(You probably want the 64-bit MSI installer version from that website.)
Enter the IP you found in the previous step and connect using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pi&lt;/code&gt; as the
username and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raspberry&lt;/code&gt; as the password.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Done!&lt;/strong&gt; Now that you’re connected to the Pi via SSH, you can run any commands
you want to. You might start with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get upgrade&lt;/code&gt;
to make sure the system’s up to date. From there, you can play around as much as
you want in the Raspbian Linux environment (based on Debian/Ubuntu), or you can
install any additional software needed for your project.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/headless-pi-zero-w/ssh.gif&quot; alt=&quot;Terminal SSH animation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I hope this helped you get started with your Raspberry Pi, and maybe saved you a
few dollars on accessories you don’t need to buy!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This is part 1 of a 2-part series exploring the Raspberry-Pi Zero.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Part 1:&lt;/strong&gt; Headless Setup for a Raspberry Pi Zero-W&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href=&quot;/blog/2020/06/22/vnc-for-headless-raspberry-pi.html&quot;&gt;Can You VNC to a Desktop on a Headless Raspberry Pi?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Tutorials"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/headless-pi-zero-w.jpg"/>
 </entry>
 
 <entry>
   <title>How to Use Garmin&apos;s Workouts Feature on your GPS Running Watch</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/06/13/how-to-use-garmin-workouts.html" type="text/html" title="How to Use Garmin&apos;s Workouts Feature on your GPS Running Watch"/>
   
   <published>2020-06-13T11:45:00-06:00</published>
   <updated>2020-06-13T11:45:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/06/13/how-to-use-garmin-workouts</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/06/13/how-to-use-garmin-workouts.html">&lt;p&gt;I’ve been a runner for most of my life, running track and cross country in both
high school and college. Many years ago now, I got my first GPS running watch
and I came to love all the data it provides for my runs. Recently, I figured out
how to use the “Workouts” feature on my Garmin fēnix watch, and I absolutely
love it! I think more people should know about it, so I decided to write this
blog.&lt;/p&gt;

&lt;h2 id=&quot;what-is-it&quot;&gt;What is it?&lt;/h2&gt;

&lt;p&gt;The Workouts feature is a way to program a running workout on one of Garmin’s
GPS running watches. I’m using it on my fēnix 5s, but the feature also exists on
most other Garmin running watches including the new fēnix 6 and the Forerunner
series. It’s great because this feature helps you set up workouts that might
normally need a track (like 400m repeats) – but you don’t need a track. You can
go run a track workout on your favorite trail. And there are options besides
just distance – you can configure most workouts you could think up pretty
easily.&lt;/p&gt;

&lt;h2 id=&quot;how-do-you-use-it&quot;&gt;How do you use it?&lt;/h2&gt;

&lt;p&gt;Although it can be hard to find and figure out the Workouts feature, it’s pretty
simple once you know where to look and how it works.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/how-to-use-garmin-workouts/workouts-menu.jpg&quot; alt=&quot;Workouts menu in the app&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open the Garmin Connect app on your phone.&lt;/li&gt;
  &lt;li&gt;Go to “Workouts” under the training menu.&lt;/li&gt;
  &lt;li&gt;Hit the button to Create a Workout, and choose “Run” (or whichever other type
of workout you want to create).&lt;/li&gt;
  &lt;li&gt;Add workout steps and repeats. Click on any step to edit it. Long-press and
drag on the move icon to rearrange.&lt;/li&gt;
  &lt;li&gt;When you’re finished, save the workout, and then tap the button in the top
right to sync it to your watch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/how-to-use-garmin-workouts/workouts-designer.jpg&quot; alt=&quot;Workout designer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When you’re ready to do the workout, start your watch in run mode like normal.
Once you’re in run mode, but before you start the time, long-press the menu
button on your watch. Select “Training”, then select “My Workouts”. Find the
workout you want and hit “Do Workout”. Your watch will run the workout for you,
using the distance, time, or lap-button settings you chose when designing the
workout in the app. So awesome!&lt;/p&gt;

&lt;p&gt;A few other tips:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You can also design workouts on the &lt;a href=&quot;https://connect.garmin.com/&quot;&gt;Garmin
Connect&lt;/a&gt; website and they’ll
appear in your app. This is nice because the website UI is easier to use.&lt;/li&gt;
  &lt;li&gt;You can design workouts for other sport types too, including run, bike, swim,
strength, cardio, yoga, pilates, and custom.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you found this little tutorial useful, and I hope you like the feature as much as I do!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Reviews"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-to-use-garmin-workouts.jpg"/>
 </entry>
 
 <entry>
   <title>Why I Love Ubuntu as a Desktop OS</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html" type="text/html" title="Why I Love Ubuntu as a Desktop OS"/>
   
   <published>2020-05-24T12:00:00-06:00</published>
   <updated>2020-05-24T12:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html">&lt;p&gt;One of the top stories on &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt; today was
a blog post called &lt;a href=&quot;https://sigpipe.macromates.com/2020/macos-catalina-slow-by-design/&quot;&gt;macOS 10.15: Slow by
Design&lt;/a&gt;. I
loved reading it – I find it fascinating to see how a problem like that was
discovered through some reverse engineering. But it also got me thinking about
macOS vs Linux vs Windows and reminded me why I love using Linux. Many people I
know think the Linux Desktop is buggy and hard to use. And sometimes it is. But
it’s worth remembering that neither macOS nor Windows comes without its own set
of problems.  There are trade-offs between any operating system (and apparently,
the OS slowing down some executables by making network requests is now one of
those trade-offs 😂). At the end of the day, I just want my OS to get out of the
way and not be broken so I can be productive, and it seems to me that in the
last several years, Ubuntu is getting closer to Windows and macOS in terms of
stability and ease-of-use. On top of this, Ubuntu has always been lightyears
ahead of Windows and macOS in terms of data collection and privacy.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;Related posts about finding the perfect computer:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2019-05-03: &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;Computer Shopping: The Ultimate Developer Laptop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2021-08-12: &lt;a href=&quot;/blog/2021/08/12/dell-latitudes-are-great-laptops.html&quot;&gt;Dell Latitudes are Great Laptops (and they run Ubuntu well)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2022-05-07: &lt;a href=&quot;/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html&quot;&gt;The Best Computer You Can Buy For $100&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-09-09: &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;My $500 Developer Laptop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;h2 id=&quot;why-linux&quot;&gt;Why Linux?&lt;/h2&gt;

&lt;p&gt;I’m an Ubuntu Linux user. I’ve used Linux off and on since the first time I
installed it at the age of 12 – trying to fix up old hardware with no clue what
I was doing. But I really fell in love with Ubuntu when I started using it as my
primary OS at work in 2015. Shortly thereafter, I also made it my primary OS at
home and haven’t changed since.&lt;/p&gt;

&lt;p&gt;So why do I like Ubuntu so much as a desktop OS? To be honest, one of the
biggest reasons is because it’s become most familiar to me. I know what all the
shortcuts are, I know how all my tools work, and it has all the tools I need.
But it wasn’t always so natural for me, and I still chose to start using it many
years ago.&lt;/p&gt;

&lt;p&gt;The real reason I started using Ubuntu regularly and grew comfortable with it is
because it was better than Windows for web development. In 2015, I was doing PHP
web development and deploying my code to Linux servers. For that type of work,
the developer experience is way better on Linux than on Windows (and macOS
wasn’t an option at the time). In fact, I’ve come to believe that the developer
experience on Linux is better than Windows &lt;em&gt;or macOS&lt;/em&gt; for a lot of types of
development. The primary reason for this is that when you run Linux, the
software you run on your laptop is most similar to the software that runs on
your servers in production. You don’t need to worry about &lt;a href=&quot;https://stackoverflow.com/questions/4247068/sed-command-with-i-option-failing-on-mac-but-works-on-linux&quot;&gt;odd differences in
commands like
sed&lt;/a&gt;.
If you’re developing or deploying with Docker, you can run Docker natively on
your laptop (no need for a VM layer) - which eliminates problems and makes
things faster. Overall, there’s less friction between your laptop and your
production environment and things work the way you expect them to in both
places.&lt;/p&gt;

&lt;p&gt;But aside from the software and tools themselves, I think Ubuntu provides a
great “Desktop OS” experience - particularly for developers. Although it felt
weird at first, I’ve really come to love &lt;a href=&quot;https://en.wikipedia.org/wiki/GNOME_3&quot;&gt;Gnome
3&lt;/a&gt; (I use vanilla Gnome on Ubuntu – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get
install vanilla-gnome-desktop&lt;/code&gt;). After using Gnome 3 for a while, the Windows
Start menu feels old and clunky. Also, package management is great on Ubuntu. I
can install most tools I need with apt-get. This feels better-managed and more
stable than &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;. And the Linux desktop experience has
really improved in recent years. I can find most software I want or need – even
if it’s not available in the Ubuntu repositories. Spotify, IntelliJ, VS Code,
and Chrome are all available and easy to install. Last time I installed Windows,
I was left with a bunch of crap (games and ads) that I didn’t want all over my
Start menu. When you finish installing Ubuntu, you’re left with a simple
environment for getting things done.&lt;/p&gt;

&lt;h3 id=&quot;privacy&quot;&gt;Privacy&lt;/h3&gt;

&lt;p&gt;It continues to become increasingly clear that pretty much every app, website,
or piece of software you use wants to collect data about you. In a world where
everyone wants to collect your data and sell it, using open source tools it a
good way to protect your data. Your data is valuable, so any company that has
the means to collect your data will be tempted to collect it, store it, sell it,
and do what they want with it. Open source tools don’t have this problem – at
least not in any way like for-profit companies do. Open source software
organizations don’t usually have the means (or infrastructure) to perform data
collection on a massive scale, and don’t have motivation to collect data like
large companies do. Often, privacy is one of the values of open-source software
projects, so the maintainers would never allow any code that performs data
collection. (There are, of course, a few rare exceptions.) On top of that,
because the source code is available for anyone to view, it’s much harder to
hide unwanted data collection in the code. Because Linux is itself open source,
and because most of the programs you install on Linux are also open source,
unwanted data collection is a much smaller concern, so Linux offers great
privacy if that’s something that’s important to you.&lt;/p&gt;

&lt;h2 id=&quot;why-ubuntu&quot;&gt;Why Ubuntu?&lt;/h2&gt;

&lt;p&gt;Given the wide variety of Linux distributions, why do I run Ubuntu? Why not
Debian, Arch, Mint, or Fedora? Well, the first reason is because Ubuntu is a
desktop OS targeted at desktop users. And because of that, certain things tend
to work more nicely or look better than other distributions. But also, and
perhaps more importantly, I use Ubuntu because it’s probably the most popular
Linux distribution - particularly for desktop use. And while popularity itself
isn’t a huge factor, it means I can google and find the answer to almost any
question or problem. Which is really valuable for – again – keeping things
running smoothly and getting things done.&lt;/p&gt;

&lt;h2 id=&quot;isnt-linux-unstable-as-a-desktop-os&quot;&gt;Isn’t Linux Unstable as a Desktop OS?&lt;/h2&gt;

&lt;p&gt;Linux has an unfortunate reputation of being difficult to setup and use as a
desktop OS. macOS shouldn’t ever have any driver problems since Apple controls
both the hardware and software. And PC makers expect users to run Windows and
therefore build drivers primarily for Windows. Which leaves Linux in a somewhat
tough spot. I’ve seen my share Linux problems over the years, including a
particularly ugly
&lt;a href=&quot;https://bugs.launchpad.net/ubuntu/+source/wpa/+bug/1501588&quot;&gt;bug&lt;/a&gt; that broke my
wifi on corporate networks back in 2015. But in my experience, problems like
this seem to be happening less frequently. Ubuntu has good support for most
common hardware by default. And the other good news that there are some ways to
mitigate driver issues like these.&lt;/p&gt;

&lt;p&gt;A lot of problems with Linux on the desktop are really issues with drivers for a
specific piece of hardware. So, there are a couple things we can do to try to
avoid these types of issues. One solution is to use slightly older hardware.
With older hardware, it’s likely that any issues have been found and fixed
already. Another solution is to use hardware designed for Linux. Companies like
&lt;a href=&quot;https://system76.com/&quot;&gt;System76&lt;/a&gt; build hardware that’s designed to be
Linux-compatible. Even
&lt;a href=&quot;https://www.dell.com/en-us/work/shop/overview/cp/linuxsystems&quot;&gt;Dell&lt;/a&gt; and
&lt;a href=&quot;https://support.lenovo.com/us/en/solutions/pd031426&quot;&gt;Lenovo&lt;/a&gt; offer
Linux-compatible systems. With a laptop that’s designed or certified for Linux,
you’re much less likely to run into problems, which really helps make Linux a
worthwhile option as a desktop OS.&lt;/p&gt;

&lt;h2 id=&quot;why-not-macos&quot;&gt;Why not MacOS?&lt;/h2&gt;

&lt;p&gt;I’ve used macOS before - particularly at some previous jobs, and I’m
very comfortable with it. Most web developers I know prefer it (for many of the
same reasons I prefer Linux) – it provides a Unix-compatible terminal inside a
nice desktop environment. I think many people choose between macOS and Windows
for their laptop, and never really consider Linux. And while macOS is also a
good choice for development work, a lot of things feel slightly off to me
compared to Linux. Instead of using apt-get, I have to use Homebrew. Docker
works, but requires a VM layer. Many GNU tools are available, but they’re not
completely compatible with Linux versions. Catalina doesn’t even use Bash as the
default shell anymore. Apple has a pretty good record on privacy (better than
most!), but they’re still a for-profit company. Overall, these are mostly minor
complaints. But as I said at the start, everything’s a trade-off. For me, Ubuntu
comes out slightly ahead, and I’ve been a happy Ubuntu user for 5 years and
counting!&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/why-i-love-ubuntu.jpg"/>
 </entry>
 
 <entry>
   <title>My Home Office Setup</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/04/10/my-home-office-setup.html" type="text/html" title="My Home Office Setup"/>
   
   <published>2020-04-10T09:00:00-06:00</published>
   <updated>2020-04-10T09:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/04/10/my-home-office-setup</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/04/10/my-home-office-setup.html">&lt;p&gt;I’ve been meaning to write a blog post about my home office setup for a while
now – and with mandatory work from home restrictions for COVID-19 in most
places around the world, this seemed like a great time to write it!&lt;/p&gt;

&lt;p&gt;I’ve been steadily improving my home office for years, creating a comfortable
space that I like and that helps me be productive. And I’m really happy with it
right now! Because of COVID-19, I’ve been working from home a lot more than I
expected to, and it was a blessing to already have a good working space set up.&lt;/p&gt;

&lt;p&gt;I think the best way for me to “give you the tour” is to walk you through some
of the pieces I have and why I like them. So let’s jump right in!&lt;/p&gt;

&lt;h2 id=&quot;my-setup&quot;&gt;My Setup&lt;/h2&gt;

&lt;h4 id=&quot;autonomous-smartdesk-2-premium-&quot;&gt;&lt;a href=&quot;https://www.autonomous.ai/standing-desks/smartdesk-2-business&quot;&gt;Autonomous SmartDesk 2 Premium&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;My desk is the centerpiece to my workspace, and I absolutely love it! Although I
spend most of my day sitting, it’s nice to be able to stand for a while when I
want to. I opted for the “premium” model (instead of “home office”) not because
I wanted it to go &lt;em&gt;higher&lt;/em&gt;, but because I wanted it to go &lt;em&gt;lower&lt;/em&gt;. A “standard”
desk height of about 28 inches is actually way too high for most people to type
in an ergonomic position – hence, the need for a keyboard tray for ergonomics
– on most desks.  But because my standing desk can go all the way down to 26
inches, it goes low enough that I use my entire desk as a “keyboard tray” and
it’s very comfortable for me to type in the lowest position. My monitors are
still high enough that I’m not looking down at them because I have a good
monitor stand. Of course, it &lt;em&gt;is&lt;/em&gt; a standing desk, and I use the standing mode
for about 30 minutes or an hour each day. The programmable buttons are a big
convenience for changing height. If you’ve ever considered getting a standing
desk, I highly recommend it, and I think Autonomous is a great brand to get.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/my-home-office-setup/standing-desk.jpg&quot; alt=&quot;Autonomous SmartDesk 2 standing desk&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;steelcase-leap-chair-&quot;&gt;&lt;a href=&quot;https://www.steelcase.com/products/office-chairs/leap/&quot;&gt;Steelcase Leap Chair&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M5.025 20.775A.998.998 0 0 0 6 22a1 1 0 0 0 .555-.168L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082a1 1 0 0 0-.59-1.74l-5.701-.454-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.214 4.107-1.491 6.452zM12 5.429l2.042 4.521.588.047h.001l3.972.315-3.271 2.944-.001.002-.463.416.171.597v.003l1.253 4.385L12 15.798V5.429z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;To complement the desk, you need a good chair. Working for 8 hours a day on a
wooden kitchen chair won’t cut it! My Steelcase Leap is a slightly older model,
but it doesn’t look like much has changed in the 2020 design. What I love about
this chair is the large number of adjustments you can make. For a relatively
small person like me, getting a shorter seat depth and more narrow arm rests
makes a big difference! I give it 4.5 stars instead of 5 only because I don’t
like the way the arm rests slide in that much, and wish there was a better way
to fine-tune the width and depth of the arm rests.&lt;/p&gt;

&lt;h4 id=&quot;viozon-monitor-and-laptop-mount-&quot;&gt;&lt;a href=&quot;https://www.amazon.com/Monitor-Adjustable-Mounts%EF%BC%8CSingle-Computer-Screens/dp/B07LFWPRG5&quot;&gt;Viozon Monitor and Laptop Mount&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M5.025 20.775A.998.998 0 0 0 6 22a1 1 0 0 0 .555-.168L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082a1 1 0 0 0-.59-1.74l-5.701-.454-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.214 4.107-1.491 6.452zM12 5.429l2.042 4.521.588.047h.001l3.972.315-3.271 2.944-.001.002-.463.416.171.597v.003l1.253 4.385L12 15.798V5.429z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;I added this monitor stand pretty recently, and I love it and wish I had
invested in a good monitor stand sooner! Not only does the desk &lt;em&gt;look&lt;/em&gt; a lot
cleaner, I also got some valuable desk real-estate back because the monitor just
floats above the desk. The stand holds a monitor on one arm and a laptop on the
other, making it pretty easy to set both up at a good height. The Viozon mount I
have looks great, but it’s a little tricky to adjust and actually looks like
it’s sold out on Amazon. Anyway, with something like a monitor arm, I don’t
think the brand is terribly important – I just highly recommend a good monitor
stand.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/my-home-office-setup/monitor-stand.jpg&quot; alt=&quot;Viozon monitor stand&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;wasd-87-key-mechanical-keyboard-&quot;&gt;&lt;a href=&quot;https://www.wasdkeyboards.com/wasd-v3-87-key-custom-mechanical-keyboard.html&quot;&gt;WASD 87-Key Mechanical Keyboard&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;My WASD keyboard is another part of my work-at-home setup that I really love. In
fact, I like it so much that I’ve bought two – one for work and one for home.
WASD’s are a little expensive, but totally worth it in my opinion – they look
great and feel great to type on. I think my typing is a little faster and more
accurate with a good keyboard like this, and mechanical keyboards are also much
better ergonomically than a laptop keyboard.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/my-home-office-setup/desk.jpg&quot; alt=&quot;WASD Keyboard&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;logitech-b100-mouse-&quot;&gt;&lt;a href=&quot;https://www.logitech.com/en-us/product/b100-optical-usb-mouse-business&quot;&gt;Logitech B100 Mouse&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;I tried a few “fancy” mouses for a couple years only to have most of them break
after about 6 months of use. The scroll wheel is usually the first thing to go
for me – I guess I use it a lot. I like this simple Logitech mouse – it’s
comfortable, durable, and gets the job done.&lt;/p&gt;

&lt;h4 id=&quot;dell-precision-5510-&quot;&gt;&lt;a href=&quot;https://www.dell.com/aw/business/p/precision-m5510-workstation/pd&quot;&gt;Dell Precision 5510&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;I put a lot of thought into buying this Dell Precision laptop as my home
computer, and I’m really happy with it! I’ve already blogged about why I like
this laptop, and you can read more about that &lt;a href=&quot;/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;dell-tb-16-dock-&quot;&gt;Dell TB-16 Dock &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;When I bought my laptop, I specifically looked for a model that was compatible
with a USB-C dock. This dock has been great – plugging in a single USB-C cord
charges my laptop and connects it to my monitor, keyboard, mouse, and speakers.
A great investment that makes it quick and easy to take your laptop with you.
While working from home recently, I discovered that this dock is also mostly
compatible with my Lenovo ThinkPad work computer (it just needs separate power),
making it easy to transition from one to the other!&lt;/p&gt;

&lt;h2 id=&quot;wfh-extras&quot;&gt;WFH Extras&lt;/h2&gt;

&lt;h4 id=&quot;logitech-h390-headset-&quot;&gt;&lt;a href=&quot;https://www.logitech.com/en-us/product/stereo-headset-h390&quot;&gt;Logitech H390 Headset&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;When you’re working from home, a good headset is critical for communication with
your team. I’m currently using a cheap Logitech H390. It’s nothing fancy, but it
gets the job done. The microphone and headphones are both very clear. Even
though the headset’s lightweight (a good thing), it can get a little
uncomfortable after a while, and it isn’t wireless. So this might be the next
thing I decide to upgrade.&lt;/p&gt;

&lt;h2 id=&quot;my-software&quot;&gt;My Software&lt;/h2&gt;

&lt;h4 id=&quot;ubuntu-linux-&quot;&gt;&lt;a href=&quot;https://ubuntu.com/download/desktop&quot;&gt;Ubuntu Linux&lt;/a&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/h4&gt;
&lt;p&gt;I run Ubuntu Linux with a vanilla Gnome desktop on my personal computer. I’ve
grown really accustomed to it over several years, and I think it’s the best
desktop OS to use if you’re a web developer. But that’s probably going to be the
subject of another blog post.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed reading about home office, and I hope I gave you some ideas
to make your own home office setup more comfortable! All these opinions are my
own, shared because I like to talk about this stuff. I wasn’t compensated in any
way for any content in this blog post.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Reviews"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/my-home-office.jpg"/>
 </entry>
 
 <entry>
   <title>How to Dual-Boot Ubuntu (16.04 - 22.10) and Windows (10 or 11) with Encryption</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html" type="text/html" title="How to Dual-Boot Ubuntu (16.04 - 22.10) and Windows (10 or 11) with Encryption"/>
   
   <published>2020-04-08T20:00:00-06:00</published>
   <updated>2020-04-08T20:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/04/08/dual-boot-ubuntu-and-windows-with-encryption.html">&lt;p&gt;When you run the Ubuntu installer, there’s an option to dual-boot Ubuntu with an
existing Windows installation. There’s also an option to encrypt your Ubuntu
installation, but &lt;em&gt;only if you erase everything and install ubuntu&lt;/em&gt;. There’s no
automatic way to install Ubuntu alongside Windows 11 with encryption. And while
there are plenty of tutorials for dual-booting Ubuntu and Windows, many of them
are outdated – often referencing an MBR partition table – and almost none of
them seem to address encrypting your Ubuntu partition.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Dual-booting with encrypted storage should not be this hard in 2020.&lt;/p&gt;

  &lt;p&gt;–Me, while figuring out how to do this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In reality, once you figure it out, it’s not that hard. The tricky thing is that
this isn’t well-documented &lt;strong&gt;anywhere&lt;/strong&gt;! So I’m hoping to fix that with this
tutorial blog post. Honestly, if you know enough about Ubuntu to set up a
dual-boot with Windows, it’s only a little bit harder to do it with encryption.
I prepared this tutorial on a Dell Latitude e7450, fine-tuned it on my Dell
Precision 5510, and I’ve had others confirm it works on a range of devices from
both Dell and other manufacturers. So it should work with almost no modification
on most Dell systems, and with only minor modifications (particularly around
BIOS setup) on most other types of computers.&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;Compatibility&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;This guide was originally written for &lt;b&gt;Windows 10&lt;/b&gt;. It&apos;s compatible with &lt;b&gt;Windows 11&lt;/b&gt;, and the installation process is nearly identical in either version.&lt;/p&gt;
&lt;p&gt;This guide was originally written using Ubuntu 20.04 and is compatible with
&lt;b&gt;Ubuntu 16.04 - 22.10&lt;/b&gt; (and possibly earlier versions).&lt;/p&gt;
&lt;p&gt;On &lt;b&gt;Ubuntu 23.04 - 24.10&lt;/b&gt;, I
recommend using my newer guide for the new subiquity installer: &lt;a href=&quot;/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu 24.04 - 24.10 and Windows with Encryption&lt;/a&gt;. (While this guide does work with the legacy installer ISO for Ubuntu 23.04 and
23.10, I think you&apos;ll find the newer guide for the new installer less
tedious.)&lt;/p&gt;
&lt;p&gt;For Ubuntu &lt;b&gt;25.04+&lt;/b&gt;, see &lt;a href=&quot;/blog/2025/05/19/dual-boot-ubuntu-25-04-and-windows-with-encryption.html&quot;&gt;How to Dual-Boot Ubuntu (25.04+) and Windows with
Encryption&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;p&gt;To write this guide, I compiled information from several sources. Here are some
of the most useful references I found:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://gist.github.com/luispabon/db2c9e5f6cc73bb37812a19a40e137bc&quot;&gt;XPS 15 9560 Dual-Boot with Encryption Notes&lt;/a&gt;,
by &lt;a href=&quot;https://gist.github.com/luispabon&quot;&gt;luispabon&lt;/a&gt;. I followed these notes pretty
closely, but modified some partition sizes and names based on other guides.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gist.github.com/mdziekon/221bdb597cf32b46c50ffab96dbec08a&quot;&gt;XPS 15 9570 Dual-Boot with Encryption Notes&lt;/a&gt;,
by &lt;a href=&quot;https://gist.github.com/mdziekon&quot;&gt;mdziekon&lt;/a&gt;, upon which the above is based.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://help.ubuntu.com/community/Full_Disk_Encryption_Howto_2019&quot;&gt;Full Disk Encryption HowTo 2019&lt;/a&gt;,
from the Ubuntu Community Wiki. This is a great resource, but deals with
encryption without dual-booting.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://help.ubuntu.com/community/ManualFullSystemEncryption&quot;&gt;Manual Full System Encryption&lt;/a&gt;,
from the Ubuntu Community Wiki. This is longer, and isn’t focused on
dual-booting, but provides great details on the way certain things work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve also received responses to this post from some people who wrote their own notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://gist.github.com/bjohas/84c83982a24f34a6dc4bfbb38ca2a1e8&quot;&gt;Dual Boot Ubuntu and Windows with Encryption&lt;/a&gt; by &lt;a href=&quot;https://github.com/bjohas&quot;&gt;bjohas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is worth noting that this method doesn’t encrypt &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt;. While there are
valid reasons for encrypting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt;, the graphical installer does not encrypt it
when you do a graphical install with LUKS. As such, I’m matching that precedent,
and keeping the simplicity of an unencrypted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt; partition. Thus, the guide
I’ve compiled below is just about the &lt;strong&gt;simplest way to have a LUKS encryption
with dual-boot.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-encryption-is-important&quot;&gt;Why encryption is important&lt;/h2&gt;

&lt;p&gt;I began using encrypted storage on all my personal computers 5 or 6 years ago
after noticing that all the companies I’d worked for required it, and had good
reason to. Laptops get lost and stolen all the time. They’re high-value items
that are small and easy to carry. And when a thief gets your laptop, there’s
tons of valuable information on it that they can use or sell. Even if you use a
password to login, it’s easy for an attacker to gain access to your data if
your disk isn’t encrypted – for example, by using a live USB stick. And once
they have that data, they might get access to online accounts, bank statements,
emails, and tons of other data. For me, an encrypted hard disk isn’t optional
anymore – it’s a necessity.&lt;/p&gt;

&lt;h2 id=&quot;an-overview&quot;&gt;An Overview&lt;/h2&gt;

&lt;p&gt;So what are we going to do? This tutorial will help you set up a system to
&lt;strong&gt;dual-boot Ubuntu and Windows&lt;/strong&gt;. The system will use a GPT hard disk
with UEFI (your BIOS must support UEFI). The Ubuntu partition will be encrypted
with LUKS.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; The Windows partition can optionally be encrypted with BitLocker.
I’m going to keep the Ubuntu installation as close to a “default” installation
as possible – no fancy tricks like a separate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/home&lt;/code&gt; partition, but it should
be somewhat easy to add that yourself if you really want to.&lt;/p&gt;

&lt;p&gt;I’m going to start with a blank hard disk, installing both Windows 11 and Ubuntu
from scratch. If you already have Windows installed and you want to keep it, you
should be able to shrink your Windows partition instead of erasing your hard
disk at the end of phase 1, and skip phase 2 (installing Windows) entirely.
Beware that when you rejoin us in phase 3, your partition numbers might be
different from ours, so you should run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo sgdisk --print /dev/sdx&lt;/code&gt; to make
sure you understand your partition table.&lt;/p&gt;

&lt;p&gt;To give you a broad overview of where we’re headed, here’s what we’re going to
do:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Prepare installation media and computer&lt;/li&gt;
  &lt;li&gt;Install Windows 11&lt;/li&gt;
  &lt;li&gt;Create an encrypted partition for Ubuntu&lt;/li&gt;
  &lt;li&gt;Install Ubuntu&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course, as with any new OS installation, you should back up any important
data before proceeding. &lt;strong&gt;The instructions below will erase all the data on your
hard disk.&lt;/strong&gt; Proceed at your own risk; I’m not responsible for any damage or
data loss.&lt;/p&gt;

&lt;div class=&quot;warning&quot; id=&quot;partition-names&quot;&gt;
&lt;p&gt;&lt;b&gt;Partition Names&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;My computer uses &lt;code&gt;/dev/sda&lt;/code&gt; as the primary disk. Yours might be
different, and you should figure out what it is. For example, you might have
&lt;code&gt;/dev/sdb&lt;/code&gt; or &lt;code&gt;/dev/nvme0n1&lt;/code&gt;. One way to figure out what
yours is called is to run &lt;code&gt;lsblk&lt;/code&gt; and look at the disk size. You&apos;re
looking for the disk (like &lt;code&gt;/dev/sda&lt;/code&gt; or &lt;code&gt;/dev/nvme0n1&lt;/code&gt;),
not the partition (like &lt;s&gt;&lt;code&gt;/dev/sda1&lt;/code&gt;&lt;/s&gt; or &lt;s&gt;&lt;code&gt;/dev/nvme0n1p1&lt;/code&gt;&lt;/s&gt;).
If you&apos;d like, you can enter your device name below and this document will
update to replace &lt;code&gt;/dev/sda&lt;/code&gt; with your device name
(&lt;code&gt;/dev/sdx&lt;/code&gt;) so it&apos;s easier for you to read.&lt;/p&gt;

&lt;p&gt;Device Name: &lt;input id=&quot;dev-sdx&quot; value=&quot;/dev/sda&quot; class=&quot;px-2 border text-neutral-700&quot; /&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;phase-1-prepare-installation-media-and-computer&quot;&gt;Phase 1: Prepare installation media and computer&lt;/h2&gt;

&lt;p&gt;Since we’re installing both Windows 11 and Ubuntu from scratch, we’ll need a USB
stick for each. If you don’t already have a computer running Ubuntu or Windows,
making the installation media will be a little harder – but there are tutorials
for that and I’ll let you figure it out on your own.&lt;/p&gt;

&lt;div class=&quot;warning&quot; id=&quot;partition-names&quot;&gt;
&lt;p&gt;&lt;b&gt;Ubuntu 23.04+&lt;/b&gt; &lt;a href=&quot;https://ubuntu.com/blog/ubuntu-desktop-23-04-release-roundup&quot;&gt;uses a new
installer&lt;/a&gt; that does not yet support &lt;code&gt;/dev/mapper&lt;/code&gt; when choosing
the manual partitioning option. You need to use the &quot;Legacy Desktop Installer&quot;
ISO to use these instructions with Ubuntu 23.04 or 23.10, but I&apos;d recommend just
using the new installer with the &lt;a href=&quot;/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html&quot;&gt;new version of this guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Ubuntu 24.04+&lt;/b&gt; does not include the legacy installer. You must use the &lt;a href=&quot;/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html&quot;&gt;new version of this guide&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;Create a Windows Installer USB stick. The easiest way is to use the &lt;a href=&quot;https://www.microsoft.com/software-download/&quot;&gt;Windows
Media Creation Tool&lt;/a&gt; from a
computer that’s already running Windows. Any method that works for you is
fine.&lt;/li&gt;
  &lt;li&gt;Create an Ubuntu USB stick. The easiest way is to &lt;a href=&quot;https://ubuntu.com/download/desktop&quot;&gt;download the
ISO&lt;/a&gt; and use the Startup Disk Creator on a
computer that’s already running Ubuntu.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Great! We’ve got our USB sticks ready to go! One final thing before we get
started – we need to make sure our BIOS is set up correctly. In particular, we
want to make sure we’re using UEFI to boot our OS.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption/dell-bios.jpg&quot; alt=&quot;The Dell BIOS&quot; /&gt;&lt;/p&gt;

&lt;ol start=&quot;3&quot;&gt;
  &lt;li&gt;Ensure your computer is running the latest BIOS available. This is important
because an out-of-date BIOS can have bugs, and those bugs sometimes affect
things like UEFI, non-Windows operating systems, or other components we’ll be
touching.&lt;/li&gt;
  &lt;li&gt;Edit your BIOS settings. The following names are probably specific to Dell
BIOS, but other manufacturers will have similar settings.
    &lt;ol&gt;
      &lt;li&gt;Under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;General&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boot Sequence&lt;/code&gt;, make sure your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boot
List Option&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UEFI&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;General&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Advanced Boot Options&lt;/code&gt;, I disabled
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Legacy Option ROMs&lt;/code&gt;. It’s important that both OSes install in UEFI mode.
(You can probably enable this when installation is complete if you care.)&lt;/li&gt;
      &lt;li&gt;Under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Security&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TPM Security&lt;/code&gt; must be enabled if you
want to easily set up BitLocker in Windows.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that our BIOS is configured for UEFI, we’re going to set up our hard disk.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;For this tutorial, your BIOS must support UEFI!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Most modern computers support this, but if yours doesn&apos;t this tutorial won&apos;t
work for you. You might consider these alternatives:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Installing only Linux with encryption using the graphical installer.&lt;/li&gt;
  &lt;li&gt;OR Installing only Windows with encryption.&lt;/li&gt;
  &lt;li&gt;OR Dual-booting Linux and Windows without encryption using Ubuntu&apos;s graphical installer.&lt;/li&gt;
  &lt;li&gt;OR Finding another tutorial or figuring out how to do this with an MBR disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;ol start=&quot;5&quot;&gt;
  &lt;li&gt;&lt;strong&gt;Completely erase&lt;/strong&gt; your hard disk and set it up for UEFI by doing the
following.&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
    &lt;ol&gt;
      &lt;li&gt;Boot your Ubuntu USB stick and use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Try without installing&lt;/code&gt; (or just close
the installer window).&lt;/li&gt;
      &lt;li&gt;Open a terminal. Make it fullscreen while you’re at it.&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Run the following commands. This will initialize the drive as a GPT drive
and create a 1G EFI system partition formatted as FAT32. (See
&lt;a href=&quot;#partition-names&quot;&gt;here&lt;/a&gt; if you’re not working with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/sdx&lt;/code&gt;.)&lt;/p&gt;

        &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo su
# sgdisk --zap-all /dev/sdx
# sgdisk --new=1:0:+1G /dev/sdx
# sgdisk --change-name=1:EFI /dev/sdx
# sgdisk --typecode=1:ef00 /dev/sdx
# mkfs.fat -F 32 /dev/sdx1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;OK, phase 1’s complete. We have our installation media ready to go and the
computer’s BIOS and hard drive are set up correctly. Next, we’ll install Windows.&lt;/p&gt;

&lt;h2 id=&quot;phase-2-install-windows&quot;&gt;Phase 2: Install Windows&lt;/h2&gt;

&lt;p&gt;In this phase, we’re going to install Windows. Note that when we do this, we’re
going to leave some unallocated space to install Linux later. This is a good
approach because the Windows installer will mess with our partitions a little
bit, and its easier to let it do so before finalizing our Linux partitions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption/windows-installer.jpg&quot; alt=&quot;The Windows installer&quot; class=&quot;img-blog-content&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Boot from your Windows Installer USB stick.&lt;/li&gt;
  &lt;li&gt;Choose a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Custom (advanced)&lt;/code&gt; install to get to the Windows partitioning tool.&lt;/li&gt;
  &lt;li&gt;Create a new partition. The size of this partition should be the amount of
disk space you want to use for Windows. In this example, I did 80G since the SSD
on my computer is relatively small. If unsure, do about half of your hard
disk.&lt;/li&gt;
  &lt;li&gt;Windows will warn you that it is going to create an extra system partition.
This is good.&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Install Windows onto the partition you just made. There’s no need to format
any partitions – the Windows installer will take care of that for you.&lt;/li&gt;
  &lt;li&gt;When the Windows installation is finished, log in and enable BitLocker on
drive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:&lt;/code&gt;. This will automatically create yet another partition on your disk
(a Windows recovery partition) - which is why we’re doing it before
partitioning for Ubuntu.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, you can start using Windows. But I’d avoid doing too much setup
or personalization yet so you don’t have to do it again if something goes wrong
below. If you want to double check your partitions, this is what you’ll be left
with after installing Windows and enabling BitLocker:&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ubuntu@ubuntu:~$ sudo sgdisk --print /dev/sdx
Disk /dev/sdx: 500118192 sectors, 238.5 GiB

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1128447    1.0 GiB    EF00  EFI
   2         1128448         1161215    16.0 MiB   0C01  Microsoft reserved ...
   3         1161216       167825076    79.5 GiB   0700  Basic data partition
   4       167825408       168900607    525.0 MiB  2700
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;phase-3-partition-the-drive-for-ubuntu&quot;&gt;Phase 3: Partition the drive for Ubuntu&lt;/h2&gt;

&lt;p&gt;This is the trickiest phase since this is where we need to manually set up our
encrypted disks for Ubuntu. We’re going to make it work very similar to the way
the Ubuntu installer would set things up if you encrypted your whole disk.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/dual-boot-encryption/encryption_gparted.png&quot; alt=&quot;Disk partitions in GParted&quot; class=&quot;img-blog-content&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Throughout this section, I’m going to be referencing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sgdisk&lt;/code&gt; commands. You
could use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdisk&lt;/code&gt; if you’re comfortable with it and want an interactive
interface, or you could use GParted or Disks (in Gnome) if you prefer. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sgdisk&lt;/code&gt;
commands are just easier to reference in a tutorial.&lt;/p&gt;

&lt;p&gt;If you want, you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo sgdisk --print /dev/sdx&lt;/code&gt; to examine the partition
table before moving on. You’ll see multiple partitions created by the Windows
installer, and should see some empty space where we’re about to install Ubuntu.
(If your disk isn’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/sdx&lt;/code&gt;, see &lt;a href=&quot;#partition-names&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Again, boot the Ubuntu USB stick and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Try without installing&lt;/code&gt; (or just close
the installer window).&lt;/li&gt;
  &lt;li&gt;Open a terminal. It’s a good idea to make it fullscreen. We’ll use root here
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo su&lt;/code&gt; below) and use the same terminal for the rest of our commands.
(Anywhere you see the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; prompt, you should be at a root terminal.)&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Make a partition for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt; and a partition for our Linux system &amp;amp; data (to
be encrypted with LUKS).&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo su
# sgdisk --new=5:0:+2G /dev/sdx
# sgdisk --new=6:0:0 /dev/sdx
# sgdisk --change-name=5:/boot --change-name=6:rootfs /dev/sdx
# sgdisk --typecode=5:8300 --typecode=6:8300 /dev/sdx
# mkfs.ext4 -L boot /dev/sdx5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, we’re going to encrypt our Linux data partition.&lt;/p&gt;

&lt;ol start=&quot;4&quot;&gt;
  &lt;li&gt;
    &lt;p&gt;Setup LUKS on our Linux data partition.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# cryptsetup luksFormat --type=luks2 /dev/sdx6
WARNING!
========
This will overwrite data on /dev/sdx6 irrevocably.
   
Are you sure? (Type uppercase yes): YES
Enter passphrase for /dev/sdx6: 
Verify passphrase: 
   
# cryptsetup open /dev/sdx6 sdx6_crypt
Enter passphrase for /dev/sdx6: 
   
# ls /dev/mapper/
control sdx6_crypt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Setup LVM inside our encrypted partition for a data volume and swap space.
It’s good that our swap space is inside the encrypted partition because data
could be there when the system suspends. Also, for suspend/hibernate to
work, you should have at least as much swap space as memory. My laptop has 8G
memory, so I’ll create 8G swap space.&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# pvcreate /dev/mapper/sdx6_crypt
Physical volume &quot;/dev/mapper/sdx6_crypt&quot; successfully created.
# vgcreate ubuntu-vg /dev/mapper/sdx6_crypt
Volume group &quot;ubuntu-vg&quot; successfully created
# lvcreate -L 8G -n swap_1 ubuntu-vg
Logical volume &quot;swap_1&quot; created.
# lvcreate -l 100%FREE -n root ubuntu-vg
Logical volume &quot;root&quot; created.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, all our partitions are prepared. Don’t stop here! Continue to the next
phase without exiting the Ubuntu live environment.&lt;/p&gt;

&lt;h2 id=&quot;phase-4-install-ubuntu&quot;&gt;Phase 4: Install Ubuntu&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Run the Ubuntu installer from the Desktop, and choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Something else&lt;/code&gt; to
configure partitions yourself.
    &lt;ol&gt;
      &lt;li&gt;Use our ~2G partition as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ext4&lt;/code&gt; with mount point &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/mapper/ubuntu--vg-root&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ext4&lt;/code&gt; with mount point &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/mapper/ubuntu--vg-swap_1&lt;/code&gt; as swap&lt;/li&gt;
      &lt;li&gt;The bootloader device should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/sdx&lt;/code&gt; (though it appears that this
setting might not actually be used in UEFI mode)&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;When the installer is finished, hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continue Testing&lt;/code&gt;. We have to do a
couple more things before we restart.&lt;/li&gt;
  &lt;li&gt;Set up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;etc/crypttab&lt;/code&gt;. This is what will allow you to unlock your encrypted
drive by typing in your passphrase when booting.
    &lt;ol&gt;
      &lt;li&gt;Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blkid&lt;/code&gt; command to find the UUID of the partition you set up with LUKS. 
Note that you want the &lt;strong&gt;UUID, not the PARTUUID&lt;/strong&gt;. We’ll use this UUID in step 3 below.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo blkid /dev/sdx6&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Get into a chroot in the newly installed system. The commands below mount
our &lt;em&gt;decrypted&lt;/em&gt; partitions into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/target&lt;/code&gt; so we can install Ubuntu there.&lt;/p&gt;

        &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# mount /dev/mapper/ubuntu--vg-root /target
# mount /dev/sdx5 /target/boot
# for n in proc sys dev etc/resolv.conf; do mount --rbind /$n /target/$n; done 
# chroot /target
      
# mount -a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Inside your chroot (that is, in the same terminal), set up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/crypttab&lt;/code&gt;.
Use your favorite editor to edit this file. I’ll use vi. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo vi /etc/crypttab&lt;/code&gt;
Save the following file contents, &lt;strong&gt;replacing the UUID with your actual UUID you
found in step 3.1 above&lt;/strong&gt;.&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# &amp;lt;target name&amp;gt; &amp;lt;source device&amp;gt; &amp;lt;key file&amp;gt; &amp;lt;options&amp;gt;
# options used:
#     luks    - specifies that this is a LUKS encrypted device
#     tries=0 - allows to re-enter password unlimited number of times
#     discard - allows SSD TRIM command, WARNING: potential security risk (more: &quot;man crypttab&quot;)
#     loud    - display all warnings
sdx6_crypt UUID=abcdefgh-1234-5678-9012-abcdefghijklm none luks,discard
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Run the following to apply the changes you just made. (Still in the chroot.)&lt;/p&gt;

        &lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# update-initramfs -k all -c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done! Congratulations, you’ve created a dual-boot system with Ubuntu 23.10 and
Windows 11 with all your data encrypted! Now that both installations are
finished, you can reboot your computer.&lt;/p&gt;

&lt;p&gt;By default, your computer will boot into grub, which can boot Ubuntu. Although
Windows is listed in grub, booting Windows from grub with BitLocker enabled
won’t initially work because the system’s TPM will detect a change in the boot
sequence. The easiest way to avoid this problem is to boot Windows directly
from your computer’s BIOS boot menu – usually accessible by pressing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F12&lt;/code&gt; on
startup. If you prefer to boot into Windows from grub, I’ve been told you can
make this work by booting into Windows from grub, entering the encryption key
when prompted, &lt;a href=&quot;https://docs.microsoft.com/en-us/troubleshoot/windows-client/windows-security/suspend-bitlocker-protection-non-microsoft-updates&quot;&gt;suspending
BitLocker&lt;/a&gt;,
and rebooting into Windows via grub again. And if you want to access the Windows
BitLocker drive from Linux, this should also be possible with the recovery key.&lt;/p&gt;

&lt;p&gt;As a reference, here’s the final state of my hard drive (with 2 logical volumes
on the rootfs partition for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;swap&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-plaintext terminal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo sgdisk --print /dev/sdx
Disk /dev/sdx: 500118192 sectors, 238.5 GiB
 
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1128447   1.0 GiB     EF00  EFI
   2         1128448         1161215   16.0 MiB    0C01  Microsoft reserved ...
   3         1161216       167825076   79.5 GiB    0700  Basic data partition
   4       167825408       168900607   525.0 MiB   2700  
   5       168900608       170473471   2.0 GiB     8301  /boot
   6       170473472       500118158   157.2 GiB   8301  rootfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I hope you found this guide useful, and I hope full-disk encryption with Ubuntu
becomes more popular and better-supported as a result! If you’ve found this
helpful and it’s saved you some time, perhaps you’d like to &lt;a href=&quot;https://www.buymeacoffee.com/mkasberg&quot;&gt;buy me a
coffee&lt;/a&gt;? No pressure, just a little way
to say thanks if you feel like it 😊.&lt;/p&gt;

&lt;h2 id=&quot;changelog&quot;&gt;Changelog&lt;/h2&gt;

&lt;p&gt;This article has become quite popular in the four years since I originally wrote it!
I’ve made some (minor) changes over that time to make it easier to use, and I’m
going to document these changes here for clarity.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;2024-05-20:&lt;/strong&gt; Published a &lt;a href=&quot;/blog/2024/05/20/dual-boot-ubuntu-24-04-and-windows-with-encryption.html&quot;&gt;completely new revision of this guide&lt;/a&gt; for
the new installer in Ubuntu 24.04+. (Thanks &lt;a href=&quot;https://twitter.com/_C_King_123/status/1791537395424219196&quot;&gt;@_C_King_123&lt;/a&gt;.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2024-04-12:&lt;/strong&gt; Updated to note incompatibility with Ubuntu 24.04+ and recommend installing with Ubuntu 23.10.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2024-04-12:&lt;/strong&gt; Recommend 1G for the EFI partition and 2G for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt;, which is what Ubuntu 24.04 does by default.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2024-04-12:&lt;/strong&gt; Removed suggestion to disable Secure Boot. Disabling Secure Boot should not be required.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2024-01-09:&lt;/strong&gt; Updated to confirm it works with Windows 11. (Thanks Jérôme Froissart.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2023-10-06:&lt;/strong&gt; Updated to recommend luks2 (used by Ubuntu 23.04 GUI encryption) over luks1. (Thanks to Michal Stanke.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2023-05-15:&lt;/strong&gt; Added notes about Ubuntu 23.04 and the legacy installer.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2022-06-21:&lt;/strong&gt; Recommend 1800M instead of 1.8G for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt; – the sgdisk
command doesn’t work well with floats.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2022-05-03:&lt;/strong&gt; I’ve received reports of successful installs using the above
instructions with Ubuntu 22.04.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2022-05-03:&lt;/strong&gt; Recommend 1.8G instead of 768M for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt; partition.
While 768M is recommended in some other guides and won’t typically cause
problems, a clean Ubuntu 22.04 install uses a 1.8G &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot&lt;/code&gt; partition.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2022-05-03:&lt;/strong&gt; Added reference from &lt;a href=&quot;https://gist.github.com/bjohas/84c83982a24f34a6dc4bfbb38ca2a1e8&quot;&gt;bjohas&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;2021-10-24:&lt;/strong&gt; Added note about suspending Bitlocker to reset it after
modifying the bootloader.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;script&gt;
// https://stackoverflow.com/a/1836463
function wrapText(container, textRE, spanClass) {
  var nodeText;
  var nodeStack = [];

  // Remove empty text nodes and combine adjacent text nodes.
  container.normalize();

  // Iterate through the container&apos;s child elements, looking for text nodes.
  var curNode = container.firstChild;

  while (curNode != null) {
    if (curNode.nodeType == Node.TEXT_NODE) {
      // Get node text in a cross-browser compatible fashion.
      if (typeof curNode.textContent == &apos;string&apos;)
        nodeText = curNode.textContent;
      else
        nodeText = curNode.innerText;

      // Use a regular expression to check if this text node contains the target text.
      var match = textRE.exec(nodeText);
      if (match != null) {
        // Create a document fragment to hold the new nodes.
        var fragment = document.createDocumentFragment();

        // Create a new text node for any preceding text.
        if (match.index &gt; 0)
          fragment.appendChild(document.createTextNode(match.input.substr(0, match.index)));

        // Create the wrapper span and add the matched text to it.
        var spanNode = document.createElement(&apos;span&apos;);
        spanNode.classList.add(spanClass);
        spanNode.appendChild(document.createTextNode(match[0]));
        fragment.appendChild(spanNode);

        // Create a new text node for any following text.
        if (match.index + match[0].length &lt; match.input.length)
          fragment.appendChild(document.createTextNode(match.input.substr(match.index + match[0].length)));

        // Replace the existing text node with the fragment.
        curNode.parentNode.replaceChild(fragment, curNode);

        curNode = spanNode;
      }
    } else if (curNode.nodeType == Node.ELEMENT_NODE &amp;&amp; curNode.firstChild != null) {
      nodeStack.push(curNode);
      curNode = curNode.firstChild;
      // Skip the normal node advancement code.
      continue;
    }

    // If there&apos;s no more siblings at this level, pop back up the stack until we find one.
    while (curNode != null &amp;&amp; curNode.nextSibling == null)
      curNode = nodeStack.pop();

    // If curNode is null, that means we&apos;ve completed our scan of the DOM tree.
    // If not, we need to advance to the next sibling.
    if (curNode != null)
      curNode = curNode.nextSibling;
  }
}

wrapText(document.body, /\/dev\/sdx(?!\d)/, &quot;dev-sdx&quot;);
wrapText(document.body, /\/dev\/sdx1/, &quot;dev-sdx-1&quot;);
wrapText(document.body, /\/dev\/sdx5/, &quot;dev-sdx-5&quot;);
wrapText(document.body, /\/dev\/sdx6/, &quot;dev-sdx-6&quot;);
wrapText(document.body, /sdx6_crypt/, &quot;cryptname&quot;);

function applyPartitionNumber(el, deviceName, number, useP) {
  if (useP) {
    el.textContent = deviceName + &quot;p&quot; + number;
  } else {
    el.textContent = deviceName + number;
  }
}

function partitionName(deviceName, number, useP) {
  if (useP) {
    return deviceName + &quot;p&quot; + number;
  } else {
    return deviceName + number;
  }
}

function setDeviceName(deviceName) {
  useP = /nvme/.test(deviceName);
  for (el of document.getElementsByClassName(&quot;dev-sdx&quot;)) {
    el.textContent = deviceName;
  }
  for (el of document.getElementsByClassName(&quot;dev-sdx-1&quot;)) {
    el.textContent = partitionName(deviceName, &quot;1&quot;, useP);
  }
  for (el of document.getElementsByClassName(&quot;dev-sdx-5&quot;)) {
    el.textContent = partitionName(deviceName, &quot;5&quot;, useP);
  }
  for (el of document.getElementsByClassName(&quot;dev-sdx-6&quot;)) {
    el.textContent = partitionName(deviceName, &quot;6&quot;, useP);
  }
  for (el of document.getElementsByClassName(&quot;cryptname&quot;)) {
     shortName = partitionName(deviceName, &quot;6&quot;, useP).split(&quot;/&quot;).reverse()[0];
     el.textContent = shortName + &quot;_crypt&quot;;
  }
}

setDeviceName(document.getElementById(&quot;dev-sdx&quot;).value);

document.getElementById(&quot;dev-sdx&quot;).oninput = function(event) {
  setDeviceName(event.target.value);
};
&lt;/script&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Ubuntu supports other types of encryption. But home folder encryption can
  cause lots of problems and incompatibilities. And the &lt;a href=&quot;https://manpages.ubuntu.com/manpages/focal/man8/cryptsetup.8.html&quot;&gt;cryptsetup
  manpages&lt;/a&gt;
  recommend LUKS over plain dm-crypt. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Booting in UEFI is important for this process to work. There’s an &lt;a href=&quot;https://help.ubuntu.com/community/Full_Disk_Encryption_Howto_2019#Boot_Modes&quot;&gt;in-depth
  discussion of boot modes&lt;/a&gt;
  on the Ubuntu community wiki. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Here’s a good &lt;a href=&quot;https://wiki.archlinux.org/index.php/EFI_system_partition#GPT_partitioned_disks&quot;&gt;resource for UEFI info&lt;/a&gt;. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;According to the &lt;a href=&quot;https://manpages.ubuntu.com/manpages/focal/man8/sgdisk.8.html&quot;&gt;sgdisk man pages&lt;/a&gt;,
  “If Windows is to boot from a GPT disk, a partition of type Microsoft Reserved
  (sgdisk internal code 0x0C01) is recommended. This partition should be about 128
  MiB in size. It ordinarily follows the EFI System Partition and immediately
  precedes the Windows data partitions.” &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;There’s a good &lt;a href=&quot;https://help.ubuntu.com/community/ManualFullSystemEncryption/BasicsHybridSuspend&quot;&gt;discussion of suspend modes&lt;/a&gt;
  on the Ubuntu community wiki,
  highlighting the need for swap space. &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/dual-boot-encryption.jpg"/>
 </entry>
 
 <entry>
   <title>Why you should consider moving your tech blog off Medium</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/03/15/why-you-should-consider-moving-your-tech-blog-off-medium.html" type="text/html" title="Why you should consider moving your tech blog off Medium"/>
   
   <published>2020-03-15T20:45:00-06:00</published>
   <updated>2020-03-15T20:45:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/03/15/why-you-should-consider-moving-your-tech-blog-off-medium</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/03/15/why-you-should-consider-moving-your-tech-blog-off-medium.html">&lt;p&gt;This might turn into a bit of a rant, but humor me. The other day, I was working
on a hobby software project when I got hit with one of these:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/stop-blogging-on-medium/medium-membership-dialog.png&quot; alt=&quot;Medium Membership Dialog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Maybe it looks familiar? It’s a paywall for &lt;a href=&quot;https://medium.com/&quot;&gt;Medium&lt;/a&gt;,
telling me that I need to become a subscriber if I want to read more articles.
I’d been programming for several hours (I was in the zone), and because I was
learning some new technologies, I was doing a lot of Googling along the way. A
look at my search history reveals I ran about 50 separate searches! Often, my
searches would lead me to programming or technology blogs – sometimes hosted on
Medium. Although I clicked into a lot of blogs (both on Medium and elsewhere), I
didn’t read most of them very thoroughly – I was really just skimming for a
quick solution to whatever I had just searched for while programming.&lt;/p&gt;

&lt;p&gt;When I got hit with the paywall, I was momentarily annoyed… Then I simply
opened the next search result in Google (which wasn’t on Medium), found my
answer, and moved on. I started skipping over blogs hosted on Medium in my
search results, knowing I could find the info I was looking for in other places
too.&lt;/p&gt;

&lt;p&gt;I’m not really interested in paying for content on Medium when I can get the
same thing for free. Now look, Meduium hosts a lot of content, and the majority
of it probably isn’t programmer blogs. I’m sure there are people out there who
enjoy reading and paying for Medium like they enjoy reading and paying for a
Magazine. But I’m not one of them, even though I acutally read a fair amount of
blogs about programming. Most of them come from
&lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;HackerNews&lt;/a&gt;, and most of them aren’t on Medium.
One example that jumps to mind is &lt;a href=&quot;https://martinfowler.com/&quot;&gt;Martin Fowler’s
Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So – if you write a blog about programming or tech and you’re hosting it on
Medium, &lt;em&gt;why&lt;/em&gt;? Is your primary goal to be paid for your work by putting it
behind a paywall? If so, Medium’s probably right for you and that’s fine.
Indeed, it seems like their model targets this type of content producer. But if
that doesn’t seem like you, what &lt;em&gt;is&lt;/em&gt; your primary goal? To share your
knowledge? Document some technology you’re familiar with? Market yourself?
Discuss technology with others? Most programmer bloggers I can think of aren’t
&lt;em&gt;primarily&lt;/em&gt; interested in monetizing their content.&lt;/p&gt;

&lt;p&gt;So if your blog’s currently hosted on Medium and you’re not in it for the money,
I think you should consider moving somewhere else. There are better options out
there. Make it easier for people like me to find and read your blog when we’re
stuck while writing some code. Here are some alternatives you might consider:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2014/08/build-blog-jekyll-github-pages/&quot;&gt;GitHub Pages&lt;/a&gt; – Although
it’s at least a little bit harder to set up a blog here than on Medium, some
free themes or starters like these might help:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://jekyllthemes.io/theme/jekyll-now&quot;&gt;Jekyll Now&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jekyllthemes.io/theme/hyde&quot;&gt;Hyde&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jekyllthemes.io/theme/lanyon&quot;&gt;Lanyon&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;A blog generated with &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; or &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;
hosted on &lt;a href=&quot;https://aws.amazon.com/s3/&quot;&gt;S3&lt;/a&gt;, &lt;a href=&quot;https://surge.sh/&quot;&gt;Surge&lt;/a&gt;,
or any other static site host&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dev.to&quot;&gt;Dev.to&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.blogger.com&quot;&gt;Blogger&lt;/a&gt; (apparently, still a thing)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wordpress.com/&quot;&gt;WordPress&lt;/a&gt; (they still host free blogs here too)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know one of the appeals of Medium is the ease with which you can publish a
blog and not have to worry about it. Options like Dev.to and Blogger will
provide a similar easy-to-setup experience, but some of the other options like
GitHub Pages or Hugo require a little more initial investment. Still, with
something like a static site generator, the long-term maintenance and hosting
costs can be exceptionally low. This blog (the one you’re reading now) is
generated with Jekyll and hosted on
&lt;a href=&quot;https://www.nearlyfreespeech.net/&quot;&gt;NearlyFreeSpeech&lt;/a&gt; for a few dollars per
year. All my posts are simple markdown text documents. I think the costs of
maintaining this myself is small compared to the benefits of being completely in
control of my own content and making it available for free on the internet are
very low, so I don’t have any plans to change.&lt;/p&gt;

&lt;p&gt;To be clear, I don’t hate Medium and I don’t think they’re doing anything
inherently wrong. I’ve just seen a lot of programming blogs on Medium lately,
and I think there are better options out there if you want to write a
programming blog. &amp;lt;/rant&amp;gt;&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/stop-blogging-on-medium.jpg"/>
 </entry>
 
 <entry>
   <title>Comparison: GoTrax GXL vs Xiaomi M365</title>
   
   <link href="https://www.mikekasberg.com/blog/2020/03/07/gotrax-glxv2-vs-xiaomi-m365.html" type="text/html" title="Comparison: GoTrax GXL vs Xiaomi M365"/>
   
   <published>2020-03-07T13:45:00-07:00</published>
   <updated>2020-03-07T13:45:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2020/03/07/gotrax-glxv2-vs-xiaomi-m365</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2020/03/07/gotrax-glxv2-vs-xiaomi-m365.html">&lt;p&gt;For the last six months or so, I’ve been using an e-scooter on part of my
commute to work. I’ve spent a good amount of time commuting on both the &lt;a href=&quot;https://gotrax.com/collections/electric-scooters/products/gxl-commuter-scooter-hand-brake-edition&quot;&gt;GoTrax
GXL&lt;/a&gt;
and the &lt;a href=&quot;https://www.mi.com/global/product-list/vehicle&quot;&gt;Xiaomi M365&lt;/a&gt; so I
thought it would be fun to write a review and comparison of the two scooters.&lt;/p&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;&lt;b&gt;2023 Update&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;This article was written in 2020. Xiaomi has since replaced its &quot;M365 Scooter&quot; model with the &quot;Xiaomi Electric Scooter&quot;. The newer model is very similar to the M365 so most of the information here probably still holds true, and I think it&apos;s still worth reading. It&apos;s also worth noting that I&apos;m &lt;i&gt;still&lt;/i&gt; using the M365 I bought in 2020, and it&apos;s held up great over multiple years of use!&lt;/p&gt;
&lt;p&gt;If you want some additional information including newer scooter models, check out &lt;a href=&quot;https://scooterreports.com/gotrax-e-scooter/&quot;&gt;Scooter Reports&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;When I decided to start using an e-scooter as part of my commute, I wasn’t sure
how much I’d like it and how well it would work. So I wanted to try it without
making a huge investment in case I didn’t use the scooter as much as I thought I
would. Because of this, I wanted the cheapest e-scooter I could buy that seemed like
it would have reasonable performance - I wouldn’t want something that went too
much slower than the other options because it wouldn’t be a fair trial. My
search led me to purchase the &lt;a href=&quot;https://www.amazon.com/GOTRAX-GXL-Commuting-Electric-Scooter/dp/B07K9MB6X1/&quot;&gt;GoTrax GXL
(v2)&lt;/a&gt;,
and I was very happy with it for a while.  Riding this scooter on my commute
confirmed for me that commuting on an e-scooter was a good investment and
something I’d want to keep doing.&lt;/p&gt;

&lt;p&gt;After about 6 months, I started to experience some minor problems with the GXL.
Its motor would cut out randomly - just for a second or so - every couple of minutes. While this didn’t make the scooter completely
unusable, it did make the ride less enjoyable. But by this time, I knew that I
enjoyed commuting via scooter enough that I was ready to make a slightly bigger
investment.&lt;/p&gt;

&lt;p&gt;So I decided to to buy the Xiaomi M365 (also known as the Xiaomi Mi -
discontinued, and replaced by the Xiaomi Electric Scooter). The M365 is a
little more expensive, but it’s a popular scooter that’s loved by a lot of
people, and widely regarded as one of the best at it’s price point. The size of
the community around this scooter was a plus for me - it would be easier to
troubleshoot or find parts, and I was also interested in experimenting with the
custom firmware options. My first impression of the M365 was that it was
powerful and smooth. Interestingly, however, something about the way it turns
makes it feel a little less nimble than the GXL. Perhaps it’s the handlebar
angle or the placement of the battery, or maybe it just takes some getting used
to. In any case, I was pretty happy with it overall!&lt;/p&gt;

&lt;p&gt;After about a week on the stock firmware, I decided to try some of the custom
firmware that’s available (using the &lt;a href=&quot;https://m365.botox.bz/&quot;&gt;BotoX M365 Custom Firmware
Toolkit&lt;/a&gt;). I primarily wanted to increase the top speed
(thereby reducing my commute time). After some experimentation with several
options, I settled on a relatively modest increase of the top speed from 28 km/h
to 30 km/h, and I also bumped up the power to the DYoC setting (40165) and
reduced the motor start speed to 3 km/h. I wanted to gain a little more
performance without putting excessive wear on my motor or battery. I
also discovered (to my dissappointment) that setting the max speed higher than
about 30 km/h won’t actually work - there’s a separate mechanism somewhere that
will reduce the power to the motor and prevent it from going faster. If you want
to find out more about how to customize your own m365, read my &lt;a href=&quot;/blog/2020/07/05/how-to-install-custom-firmware-on-your-xiaomi-m365.html&quot;&gt;m365
firmware mod tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’ve been using the M365 for about two months now, and overall I’m quite happy with
it. It gets the job done for a good price, and it’s fun to ride. And although
I’ve only owned it for a couple months, there are many reports online of users who are
very happy with it after a year or more. If you’re thinking about buying either
of these scooters, I’d actually recommend either one of them depending on your
needs. The GXL is probably the cheapest worthwhile e-scooter you can buy right
now, and rides very nicely if you’re not putting a ton of miles on it. The M365
is also a great e-scooter at a great price, and is a worthwhile upgrade if you
want longer durability and some other improvements.&lt;/p&gt;

&lt;h2 id=&quot;gotrax-gxl&quot;&gt;GoTrax GXL&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/gotrax-vs-xiaomi/gxl.jpg&quot; alt=&quot;GoTrax GXL Scooter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I own a &lt;a href=&quot;https://gotrax.com/collections/electric-scooters/products/gxl-commuter-scooter-hand-brake-edition&quot;&gt;GoTrax GXL
(v2)&lt;/a&gt;.
It’s very similar to the &lt;a href=&quot;https://gotrax.com/collections/electric-scooters/products/gxl-electric-commuter-scooter&quot;&gt;GXL
(v1)&lt;/a&gt;
and the &lt;a href=&quot;https://gotrax.com/collections/electric-scooters/products/gotrax-xr-foldable-electric-scooter&quot;&gt;GoTrax
Xr&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;The GXL has a better folding mechanism than the M365. I never felt any wobble or had problems with the GXL folding mechanism while riding - even though I often used it without the locking pin.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;The GXL has a worse fold-locking mechanism (for carrying). The clip on the back fender has to be in exactly the perfect position or the scooter either won&apos;t stay folded or won&apos;t open easily.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;The GXL has better ground clearance and feels like it rides a little higher than the M365.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;The GXL seems to accelerate faster than the M365.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;The GXL has a better display. It shows your speed, and seems fairly accurate to me.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;No downhill speed limits - the GXL will let you pick up speed beyond its normal max when coasting downhill.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;The GXL always starts in eco mode. And you have to hold the button for two seconds to switch to fast mode. This is a minor annoyance every time you turn it on.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;The GXL has a shorter range than the M365.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;My GXL motor had problems after about 6 months of use (~300 mi).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;xiaomi-m365&quot;&gt;Xiaomi M365&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/gotrax-vs-xiaomi/m365.jpg&quot; alt=&quot;Xiaomi M365 Scooter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Xiaomi M365. Updated models are called the &lt;a href=&quot;https://www.mi.com/global/product-list/vehicle&quot;&gt;Xiaomi Electric Scooter&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;The M365 seems at least slightly faster than the GXL, even with stock firmware.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Much larger user base &amp;amp; community. This has lots of benefits - it&apos;s easier to find help, get parts, and even get custom firmware.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Custom firmware is available. Some users might not care, but this can be fun if you&apos;re into it. Although it&apos;s worth noting that you can&apos;t get &lt;em&gt;that much&lt;/em&gt; improvement over stock. This won&apos;t double the speed or anything like that.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;The M365 has a worse folding mechanism than the GXL. The handlebars squeak and wobble a little in the upright position.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;The M365 has a better fold-locking mechanism (for carrying). The bell doubles as a clip and it seems very sturdy.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;The M365 display doesn&apos;t show your speed unless you get the pro model.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Overall build quality of the M365 seems better than the GXL. Parts seem sturdier, and users have reported years of usage.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;The M365 doesn&apos;t coast as well as the GXL - the M365 max speed limit applies even when going downhill.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;The M365 feels less nimble than the GXL. But if you haven&apos;t ridden both scooters, you probably wouldn&apos;t notice.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;The M365 has a better range than the GXL.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;(Update) I&apos;m still using my (&lt;a href=&quot;/blog/2020/07/05/how-to-install-custom-firmware-on-your-xiaomi-m365.html&quot;&gt;lightly modded&lt;/a&gt;) M365 after more than 3 years!&lt;/li&gt;
&lt;/ul&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Reviews"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/gotrax-vs-xiaomi.jpg"/>
 </entry>
 
 <entry>
   <title>Computer Shopping: The Ultimate Developer Laptop</title>
   
   <link href="https://www.mikekasberg.com/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html" type="text/html" title="Computer Shopping: The Ultimate Developer Laptop"/>
   
   <published>2019-05-03T18:45:00-06:00</published>
   <updated>2019-05-03T18:45:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html">&lt;p&gt;Recently, I bought a new computer. My goal was to find &lt;strong&gt;the ultimate developer
laptop!&lt;/strong&gt; Well, maybe that’s a bit of an exaggeration… I actually set a fairly
reasonable budget for myself. So maybe &lt;strong&gt;the ultimate &lt;em&gt;affordable&lt;/em&gt; developer
laptop™&lt;/strong&gt; is more accurate. In any case, I put a lot of thought and research
into what my ideal machine would be like, so hopefully my research and
experience can help you find the computer of &lt;em&gt;your&lt;/em&gt; dreams too!&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;Related posts about finding the perfect computer:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2020-05-24: &lt;a href=&quot;/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html&quot;&gt;Why I Love Ubuntu As a Desktop OS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2021-08-12: &lt;a href=&quot;/blog/2021/08/12/dell-latitudes-are-great-laptops.html&quot;&gt;Dell Latitudes are Great Laptops (and they run Ubuntu well)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2022-05-07: &lt;a href=&quot;/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html&quot;&gt;The Best Computer You Can Buy For $100&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-09-09: &lt;a href=&quot;/blog/2023/09/09/my-500-developer-laptop.html&quot;&gt;My $500 Developer Laptop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;h2 id=&quot;the-basics&quot;&gt;The Basics&lt;/h2&gt;

&lt;p&gt;When I started looking for the computer I wanted to buy, I began with some
simple requirements. RAM, CPU, and hard disk. RAM tends to be a pretty good
indicator of overall system performance when you’re buying a laptop. (Nobody
builds a system with a modern i7 and 4GB of RAM, and nobody builds a system with
an old i3 and 32GB of RAM). I wanted something with 32GB of RAM if I could keep
it within budget, but I was willing to compromise to 16GB if necessary to get
other features I wanted within my price range.  CPUs are a little harder to
compare since clock speed measurements &lt;a href=&quot;https://www.howtogeek.com/177790/why-you-cant-use-cpu-clock-speed-to-compare-computer-performance/&quot;&gt;are somewhat
meaningless&lt;/a&gt;
these days, so I wasn’t looking for a specific clock speed. Hard disks, on the
other hand, are easy to compare since you’re really only comparing disk space.
So the basic performance specs I used to start my search were:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;32GB RAM (or 16GB for budget) for good performance when running a lot of applications at once -
particularly when developing &amp;amp; debugging complex services.&lt;/li&gt;
  &lt;li&gt;At least a 500GB SSD. In 2019, the performance of a traditional hard disk would
be unacceptable for me. I also wanted something big enough that space wouldn’t be an issue.&lt;/li&gt;
  &lt;li&gt;A relatively modern i7 processor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-details&quot;&gt;The Details&lt;/h2&gt;

&lt;p&gt;Those basic performance specs narrowed my search a lot, but there were still a
ton of laptops to pick from. I needed to narrow my search a little more.  I’ve
always had a love for “mobile workstations” like Lenovo’s ThinkPad and Dell’s
Latitude, and I really do think these types of computers probably make the best
developer laptops. They’re built for daily use - and the build quality feels a
little better than the average consumer-grade laptop. It’s also worth noting
that they’re generally made to be easy to repair or upgrade. When I upgraded to
an SSD in one of my old Dell Precision laptops, the hard drive was ridiculously
easy to install! Finally, (and for me this is an important factor,) there are
tons of used (and refurbished) models available that are only a couple years
old, and they’re usually much more affordable than “retail” price. Businesses
buy a lot of state-of-the-art workstations from Dell and Lenovo, and they end
up as used or refurbished models a few years later when the business upgrades
their fleet again.&lt;/p&gt;

&lt;p&gt;There were also some other features I wanted to consider. I like having the
screen real-estate of 1080p resolution, even on my laptop display (although for
some people this might be less important). And 1080p resolution on a 14-inch
screen is just a little too small for my eyes, so it was important to me that the
laptop was 15 inches. I didn’t want anything bigger either, since it would be
heavier and more difficult to carry around.&lt;/p&gt;

&lt;p&gt;Another factor I considered was docking. My old laptop didn’t have a dock, and
as a result it was a pain to bring it anywhere - I’d have to disconnect 6 or 7
cords for my keyboard, mouse, monitors, power, and internet. Some laptops (like
ThinkPads) have had proprietary docking ports on the bottom for a while, but
docking has gotten even better recently with technology like a USB 3 or
&lt;a href=&quot;https://en.wikipedia.org/wiki/Thunderbolt_(interface)&quot;&gt;Thunderbolt 3&lt;/a&gt; port.
Being able to use a single cable to connect monitors, keyboard, mouse, speakers,
internet, and other peripherals is a huge plus for me, and something I wanted to
look for in a new laptop if it was within my price range.&lt;/p&gt;

&lt;p&gt;Finally, I wanted to buy a laptop without a number pad. While number pads aren’t
too common on 14-inch laptops, a lot of 15-inch laptops have them. I don’t like
them because they offset the keyboard and touch pad to the left, which means
something’s always off-center if you’re using the laptop without a separate
keyboard and mouse.&lt;/p&gt;

&lt;h2 id=&quot;the-os&quot;&gt;The OS&lt;/h2&gt;

&lt;p&gt;For me, a laptop wouldn’t be a developer laptop if it didn’t run Linux. (More to
come on my OS setup in a later blog post.) Since I’m a web developer, most of
the servers I work with are running Linux, and it’s really helpful that my
laptop can run the same type of software. Also, with the popularity of Docker
and containers, it’s helpful to develop on a platform where those technologies can
run natively. Not to mention, most open source tools run well on Linux.&lt;/p&gt;

&lt;p&gt;Fortunately, Linux runs well on most of the “mobile workstation” laptops I’d
consider buying. In particular, Linux runs well on Dell Latitude, XPS, and
Precision laptops and Lenovo ThinkPads. Dell does a great job of ensuring some
of their laptops are Linux-compatible with &lt;a href=&quot;https://ubuntu.com/blog/project-sputnik-retrospective-10-years-of-developer-laptops-with-dell&quot;&gt;Project
Sputnik&lt;/a&gt;,
and Lenovo also certifies many of their laptops as &lt;a href=&quot;https://support.lenovo.com/us/en/solutions/pd031426&quot;&gt;Linux
compatible&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;requirements-summary&quot;&gt;Requirements Summary&lt;/h2&gt;

&lt;p&gt;With all these details in mind, here’s what I looked for when I was shopping for
a new computer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;32GB RAM&lt;/li&gt;
  &lt;li&gt;500GB+ SSD&lt;/li&gt;
  &lt;li&gt;Intel i7 processor&lt;/li&gt;
  &lt;li&gt;Business-grade laptop (Lenovo ThinkPad, Dell Latitude, or similar)&lt;/li&gt;
  &lt;li&gt;1080p screen&lt;/li&gt;
  &lt;li&gt;15 inch&lt;/li&gt;
  &lt;li&gt;USB docking (preferably with Thunderbolt 3)&lt;/li&gt;
  &lt;li&gt;No number pad&lt;/li&gt;
  &lt;li&gt;Linux compatible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using all of these requirements in my search, I decided to buy a &lt;a href=&quot;https://web.archive.org/web/20180310052038/http://www.dell.com/aw/business/p/precision-m5510-workstation/pd&quot;&gt;Dell Precision
5510&lt;/a&gt;.
Although I thought I wanted a Latitude E5550 or E5570, the off-center keyboard
(because of the number pad) pushed me to the Presicion 5510 instead. I bought a
Dell TB16 Thunderbolt dock to go with my new laptop, and the combo is great! The
dock works well with external monitors, ethernet, keyboard, and mouse under
Linux - plus it supplies power for the laptop. I’ve been running Ubuntu on the
Precision 5510 and TB16 dock for months now and haven’t run into any problems!&lt;/p&gt;

&lt;p&gt;It was fun for me to do some research and buy a new personal computer for
myself, and I hope some of my ideas and research help you next time you’re
looking for an upgrade!&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/ultimate-dev-machine.jpg"/>
 </entry>
 
 <entry>
   <title>Why Short Feedback Cycles Lead to Great Software</title>
   
   <link href="https://www.mikekasberg.com/blog/2019/02/24/feedback-cycles.html" type="text/html" title="Why Short Feedback Cycles Lead to Great Software"/>
   
   <published>2019-02-24T16:00:00-07:00</published>
   <updated>2019-02-24T16:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2019/02/24/feedback-cycles</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2019/02/24/feedback-cycles.html">&lt;p&gt;I think most software developers love short feedback cycles, whether they
realize it or not. And it makes sense! Really short feedback cycles are one of
the first things most developers experience when they write their first “Hello,
world!” program. A lot of developers get hooked when they see that any change
they make to the code is reflected immediately in the output. This feedback
cycle is nearly instantaneous, and many developers love that about programming.&lt;/p&gt;

&lt;p&gt;Unfortunately, most professional software developers don’t experience rapid
feedback like this in their day-to-day work. Actually, it seems like the length
of feedback cycles tends to increase as the size of an organization increases.
In a large enterprise with hundreds or thousands of developers, it’s possible
that you won’t see your change in production until months after you write it. In
contrast, if you’re working on a personal project or a very small team, it’s
likely that you can deploy a change to production in minutes.&lt;/p&gt;

&lt;p&gt;These feedback cycles apply to many components of software development besides
time between making a code change and seeing that change in production. For
example, the amount of time it takes to run the test suite is likely to follow
a similar pattern. Smaller teams or personal projects are likely to have small
unit test suites that can run in a matter of seconds while large enterprises are
likely to have large unit test suites along with complex integration test
suites, UI tests, or end-to-end tests. These suites are likely to take much
longer to run, and in some cases take hours or days to complete.&lt;/p&gt;

&lt;h2 id=&quot;what-does-the-data-say&quot;&gt;What does the data say?&lt;/h2&gt;

&lt;p&gt;I spent a small part of my career teaching high school math, and I actually have
a master’s degree in education from the alternative licensure program I
completed. One of the things I learned while studying education is that short,
frequent feedback cycles are incredibly important for both learning and
motivation. For example, if the goal is to correct misconceptions in a student’s
understanding, it’s much more effective to have them do a quick problem and then
trade papers to check a classmate’s work than it is to have them turn in a quiz
that might not be graded and returned for a week or more. Getting feedback while
the problem is still fresh in their minds is critical, and
&lt;a href=&quot;https://www.columbia.edu/~mvp19/ETF/Feedback.pdf&quot;&gt;several&lt;/a&gt;
&lt;a href=&quot;https://www.ascd.org/publications/educational-leadership/sept12/vol70/num01/Good-Feedback-Is-Targeted,-Specific,-Timely.aspx&quot;&gt;studies&lt;/a&gt;
have shown that short, frequent feedback cycles keep people engaged and promote
learning and achievement.&lt;/p&gt;

&lt;h2 id=&quot;rapid-feedback-in-practice&quot;&gt;Rapid Feedback in Practice&lt;/h2&gt;

&lt;p&gt;If the tendency is for organizations to slow down with longer and less frequent
feedback cycles as they grow, how do we fight that tendency? I have some ideas
about software engineering practices that can promote shorter and more frequent
feedback cycles to help engineers deliver higher-quality code more quickly.&lt;/p&gt;

&lt;h3 id=&quot;make-small-commits&quot;&gt;Make small commits.&lt;/h3&gt;

&lt;p&gt;Commits are the primary way that we make changes to and get feedback from the
system we’re developing. A smaller commit makes every step in the change cycle
faster, while a larger commit usually makes every step slower. Smaller commits
will lead to more isolated unit test changes, faster code reviews, more targeted
manual testing or UI verification, and easier monitoring after deployment. All
of these steps can have an impact on feedback cycle time, so commits play a big
role in slowing or speeding up the cycle.&lt;/p&gt;

&lt;h3 id=&quot;respond-to-code-review-requests-quickly&quot;&gt;Respond to code review requests quickly.&lt;/h3&gt;

&lt;p&gt;If you are slow to respond to pull requests, you make the feedback cycle longer
for the person who wrote the code. It’s most effective for the code author to
receive and act on feedback while the code he wrote is still fresh in his mind.
Ideally, I think it’s reasonable to expect most code reviews in less than a day
(in a professional work environment). If your code takes longer to review than
that, you should probably make smaller commits (see the section above).&lt;/p&gt;

&lt;h3 id=&quot;ensure-unit-tests-run-quickly&quot;&gt;Ensure unit tests run quickly.&lt;/h3&gt;

&lt;p&gt;When I’m working on implementing a new feature or fixing a bug, I prefer to use
test driven development (TDD). If my unit test runs quickly (in seconds), it’s
very easy to make a minor change to the code and see how it affected the tests.
But if my tests are slow (even tens of seconds is too long), my work cycle slows
down significantly. In reaction to my slow tests, I start running them less
frequently and making more changes between test cycles, which ultimately adds
complexity to my development workflow. The same principle extends to entire unit
test suites and the feedback cycles they create on pull requests.&lt;/p&gt;

&lt;p&gt;If your unit test suite isn’t this fast, what’s slowing it down? Oftentimes, the
slow components of a “unit” test are dependencies on external services like a
database, filesystem, or HTTP API. Generally speaking, it should be relatively
easy to mock these services and inject the mock into your unit test suite, which
would allow you to get the same assertions on your unit under test (the business
logic in the class you’re writing) without waiting for a real database, API, or
filesystem. If your unit tests are too slow, profile them, and see if it would
be possible to replace any of the slow components with a mock in the slowest tests.&lt;/p&gt;

&lt;h3 id=&quot;ensure-integration-and-end-to-end-tests-run-quickly&quot;&gt;Ensure integration and end-to-end tests run quickly.&lt;/h3&gt;

&lt;p&gt;Integration and end-to-end tests have a similar feedback cycle problem to unit
tests, but on a larger scale. With integration and end-to-end test suites, a
realistic goal is to have them run in minutes rather than hours or days. A slow
integration or end-to-end test suite ultimately leads to the same problems as a
slow unit test suite - slower suites are run less often, and as a result they
also run against bigger change sets.&lt;/p&gt;

&lt;p&gt;If your integration or end-to-end test suite is slow, a common cause is trying
to test too much in integration layer. It should be possible to validate the
majority of your business logic in the unit test layer. And you should prefer to
do so because of the faster feedback cycle there. In the integration layer,
then, we’re left only with things that couldn’t be validated in the unit test
layer. A relatively small test suite should be able to validate that the
services are integrated properly. If your integration suite is too slow because
it covers a lot of business logic, consider if some of those tests could be
moved to the unit layer to make the integration suite faster, and to get faster
feedback on the assertions in those tests than the integration suite can
provide.&lt;/p&gt;

&lt;h3 id=&quot;implement-a-fast-development-and-release-cycle&quot;&gt;Implement a fast development and release cycle.&lt;/h3&gt;

&lt;p&gt;Feedback cycles don’t end when the code is merged and released. There’s a
also a feedback cycle from the production code, which provides information to
the development team through logs, monitoring tools, A/B testing, etc. It’s
important to make sure developers have easy access to these tools so they &lt;em&gt;can&lt;/em&gt;
receive feedback from the code running in production. I’ve seen organizations
where it was exceptionally tedious to pull production logs, and business
processes like that make the production feedback loop nearly non-existent.&lt;/p&gt;

&lt;p&gt;If developers are already getting good feedback from production, the goal
(again) is to shorten the feedback loop. Developers who get feedback from
production quickly will be able to detect and fix problems while the code is
still fresh in their minds, and while the change set is relatively small.
They’ll also be able to iterate more quickly - particularly if they’re using
something like feature flags to perform dark launches. A slow feedback cycle
with production has the same symptoms as a slow feedback loop on unit or
integration tests - changes become bigger, and it takes a longer time for
developers to know how their solution is performing. The best way to shorten the
production feedback cycle is to release more frequently. I think a goal of one
release per day is a good target for most organizations, but it’s not unheard of
to go &lt;a href=&quot;https://www.youtube.com/watch?v=JR-ccCTmMKY&quot;&gt;much faster than that&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;shorten-your-cycles&quot;&gt;Shorten Your Cycles&lt;/h2&gt;

&lt;p&gt;Over the course of my career, I’ve seen time and again how slow feedback cycles
can be a burden to a development team, and I’ve seen how making improvements to
those feedback cycles can not only motivate the team, but also lead to more
stable releases and faster feature delivery. In Chuck Rossi’s
&lt;a href=&quot;https://atscaleconference.com/&quot;&gt;@Scale 2017&lt;/a&gt; talk,
&lt;a href=&quot;https://atscaleconference.com/videos/rapid-release-at-massive-scale/&quot;&gt;Rapid Release at Massive Scale (14:00)&lt;/a&gt;,
he notes that faster iteration is worthwhile because it leads to tighter
coupling between developers and users. It’s worthwhile to examen your own
processes because there’s always some room for improvement.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/feedback-cycles.jpg"/>
 </entry>
 
 <entry>
   <title>5 Tech Talks Every Web Developer Should Watch</title>
   
   <link href="https://www.mikekasberg.com/blog/2019/01/13/5-tech-talks-every-developer-should-watch.html" type="text/html" title="5 Tech Talks Every Web Developer Should Watch"/>
   
   <published>2019-01-13T14:00:00-07:00</published>
   <updated>2019-01-13T14:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2019/01/13/5-tech-talks-every-developer-should-watch</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2019/01/13/5-tech-talks-every-developer-should-watch.html">&lt;p&gt;I enjoy listening to tech talks from expert software engineers. When you
listen to a good tech talk, you’re given the opportunity to learn from someone
else’s experiences, and this is really valuable in such a fast-moving industry.
A lot of tech talks from big developer conferences are posted online for free,
and I’ve kept a list of some of my favorite tech talks from the last several
years. These are the five tech talks that have had the most influence on me
personally, and I think that anyone who’s a web developer should listen to them
if you haven’t already. Although most of them are several years old by now, I
think the ideas presented in them have already stood the test of time. So while
some technological details might change, the big ideas presented are still
relevant and will continue to be for a long time.&lt;/p&gt;

&lt;h2 id=&quot;5-why-google-stores-billions-of-lines-of-code-in-a-single-repository&quot;&gt;#5 Why Google Stores Billions of Lines of Code in a Single Repository&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=W71BTkUbdqE&quot;&gt;Why Google Stores Billions of Lines of Code in a Single Repository&lt;/a&gt;
(Rachel Potvin, 2015)&lt;/p&gt;

&lt;p&gt;As you might guess from the title, this video is all about the benefits of
storing your code in a large monolithic repository. I personally think monolithic
repositories are great, for all the reasons discussed in the talk.
Unfortunately, it often seems like starting a conversation about a monolithic
repository is almost as difficult as talking about tabs vs spaces. But
regardless of whether you think a monolithic repository is ideal for your
working environment, I think it’s important to understand the trade-offs you’re
making by going one way or the other. Besides that, the talk also contains
valuable ideas about the way Google approaches refactoring and code maintenance
on such a large scale.&lt;/p&gt;

&lt;h2 id=&quot;4-spotify-engineering-culture&quot;&gt;#4 Spotify Engineering Culture&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://vimeo.com/85490944&quot;&gt;Spotify Engineering Culture, part 1&lt;/a&gt; (Spotify,
2014)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://vimeo.com/94950270&quot;&gt;Spotify Engineering Culture, part 2&lt;/a&gt; (Spotify,
2014)&lt;/p&gt;

&lt;p&gt;The Spotify engineering culture videos were relatively popular in the software
engineering community when they first came out several years ago, and if you
haven’t seen them you definitely should. Although these are technically two
separate videos, I’m counting them as a single tech talk in my list. These
videos focus more on the business aspects of building software than the
technical details. They discuss things like agile practices and team
organization. The videos are really well done and present many well thought out
ideas, so it’s likely you’ll be able to learn something from them even if your
work environment is a little different.&lt;/p&gt;

&lt;h2 id=&quot;3-architecting-for-ci-success&quot;&gt;#3 Architecting for CI Success&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=_wnd-eyPoMo&quot;&gt;Architecting for CI Success&lt;/a&gt; (Jez
Humble, 2015)&lt;/p&gt;

&lt;p&gt;Jez talks about continuous delivery, dev ops, and how to overcome some of the
hurdles that people often face when trying to implement dev ops practices. His
talk is full of practical advice that focuses on building scalable,
maintainable, and reliable solutions. He touches on deployment pipelines and
testing strategies for producing low-risk, easy to manage releases. He also
discusses service architecture patterns that support some of these dev ops
practices. Overall, he’s a great speaker, and the talk is definitely worth
listening to.&lt;/p&gt;

&lt;h2 id=&quot;2-continuous-delivery-the-dirty-details&quot;&gt;#2 Continuous Delivery: The Dirty Details&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=JR-ccCTmMKY&quot;&gt;Continuous Delivery: The Dirty Details&lt;/a&gt;
(Mike Brittain, 2013)&lt;/p&gt;

&lt;p&gt;Mike Brittain was an engineer at Etsy when he gave this talk, which is an
overview of some of the engineering practices Etsy uses for continuous delivery.
He also briefly mentions some other challenges and solutions, such as scaling to
large traffic volumes and degrading service gracefully. One of the reasons I
like this talk so much is that it seems to pull back the curtain a little bit on
how continuous delivery impacts the developers at Etsy on a daily basis. When
watching the talk, it’s clear that Etsy doesn’t just talk about abstract ideas
but actually implements them in ways that some people would consider a little
bit radical. And you can see concrete benefits that have developed out of some
of the ideas they’ve implemented.&lt;/p&gt;

&lt;h2 id=&quot;1-pushing-millions-of-lines-of-code-five-days-a-week&quot;&gt;#1 Pushing Millions of Lines of Code Five Days a Week&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://vimeo.com/56362484&quot;&gt;Pushing Millions of Lines of Code Five Days a Week&lt;/a&gt;
(Chuck Rossi, 2011)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Nffzkkdq7GM&quot;&gt;Releng 2014 Keynote&lt;/a&gt;
(Chuck Rossi, 2014)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://atscaleconference.com/videos/rapid-release-at-massive-scale/&quot;&gt;Rapid Release at Massive Scale&lt;/a&gt;
(Chuck Rossi, 2017)&lt;/p&gt;

&lt;p&gt;If you only watch one tech talk from my list, watch one of Chuck Rossi’s talks.
The oldest one is my favorite, but they’re all great. Chuck Rossi was the
Release Engineering Director at Facebook, and his presentation about how
Facebook manages its releases remains my favorite tech talk to this day.  Chuck
talks about how source code is developed, tested, and released at a company that
maintains one of the largest web applications in existence. He’s a great
speaker, and presents ideas and solutions for challenges that are likely to
affect software organizations of any size. If you’re a web developer, it’s
critical that you understand how and when your code will roll out to production.
I think that one of the reasons I like this talk so much is that Chuck not only
explains how Facebook manages these processes, but also explains why they chose
certain solutions over others, revealing some aspects of their decision making
process.&lt;/p&gt;

&lt;p&gt;Chuck has actually given essentially the same talk multiple times, and I’d
recommend watching all of them. Although the basic ideas in the talk are the
same, by watching different iterations of the same talk you can see how
Facebook’s release process has evolved over time, and you can pick out some
themes, ideas, and challenges that remain consistent over time. Chuck
understands the need to minimize the time between merge and release to
production, and has developed solutions to help Facebook do this at a massive
scale.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;All of these videos have had a significant impact on the way I think about,
develop, and release software. I hope you take some time to watch some of them
if you haven’t seen them before, and I hope they spark some new ideas for you as
they did for me.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/tech-talks.jpg"/>
 </entry>
 
 <entry>
   <title>KeePass vs Bitwarden: A Comparison of Free Password Managers</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/11/20/keepass-vs-bitwarden.html" type="text/html" title="KeePass vs Bitwarden: A Comparison of Free Password Managers"/>
   
   <published>2018-11-20T21:00:00-07:00</published>
   <updated>2018-11-20T21:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/11/20/keepass-vs-bitwarden</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/11/20/keepass-vs-bitwarden.html">&lt;p&gt;It’s been nearly 2 years since I originally wrote about
&lt;a href=&quot;/blog/2017/03/25/how-i-manage-passwords-with-keepass.html&quot;&gt;How I Manage Passwords with KeePass&lt;/a&gt;.
That blog entry was inspired by Troy Hunt’s post,
“&lt;a href=&quot;https://www.troyhunt.com/only-secure-password-is-one-you-cant/&quot;&gt;The only secure password is one you can’t remember&lt;/a&gt;”.
Using KeePass was a wonderful experience, and I’m thoroughly convinced that
&lt;strong&gt;everyone should use a password manager&lt;/strong&gt;. The ease of use and level of
security a password manager provides is way better than anything else you could
do to remember your passwords. One of the most common ways a person can be
hacked is by reusing the same password on many websites. The problem is that if
&lt;em&gt;any&lt;/em&gt; website has a &lt;a href=&quot;https://haveibeenpwned.com/PwnedWebsites&quot;&gt;data breach&lt;/a&gt;,
&lt;em&gt;all&lt;/em&gt; the websites you used that password on are compromised. And a password
manager solves this problem by using a different password on every site.&lt;/p&gt;

&lt;p&gt;Recently, I switched from &lt;a href=&quot;https://keepass.info/&quot;&gt;KeePass&lt;/a&gt; to
&lt;a href=&quot;https://bitwarden.com/&quot;&gt;Bitwarden&lt;/a&gt;. Now that I’ve used Bitwarden for several
weeks, I want to compare the two most popular free password managers.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; It’s 2021 now, and I’ve been using Bitwarden for over two years.
I’m still &lt;strong&gt;incredibly happy with Bitwarden&lt;/strong&gt;, and I recommend it to anyone I
can!  It’s a great password manager, and I expect I’ll continue using it for a
long time to come.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;keepass&quot;&gt;KeePass&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/keepass-vs-bitwarden/keepass.png&quot; alt=&quot;KeePass logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;KeePass is a relatively old application. KeePass 1 was originally released for
Windows XP in 2003, and KeePass 2 came out of beta in 2009. It shows - KeePass
&lt;em&gt;feels&lt;/em&gt; like a Windows application from the early 2000’s. (Its icons and toolbar
are a bit dated.) Although the application itself is old, it’s still actively
developed, and remains one of the most popular password managers. There are a
large number of plugins available and because it’s open source, it’s pretty easy
to find a KeePass-compatible application for any device or operating system.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/keepass-vs-bitwarden/keepass_big.png&quot; alt=&quot;KeePass screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Using “KeePass” as your password manager really entails using a combination of
several different applications and services to work with a KeePass password
database file. I used &lt;a href=&quot;https://www.dropbox.com&quot;&gt;Dropbox&lt;/a&gt; to sync my password
database between all my devices. I used
&lt;a href=&quot;https://play.google.com/store/apps/details?id=keepass2android.keepass2android&quot;&gt;Keepass2Android&lt;/a&gt;
to read and store passwords from my phone. And I used
&lt;a href=&quot;https://keepassxc.org/&quot;&gt;KeePassXC&lt;/a&gt; on my Linux and macOS computers. (And if
you’re using KeePass, I highly recommend all of these apps.) Although there are
some browser plugins available for KeePass or KeePassXC, they always seemed
finicky to me and I never found one I liked, so I stuck with the desktop
application. If you decide to use KeePass, you’ll have to figure out what works
for you. Maybe you prefer to use Google Drive to sync it across all your devices
and need to find the best iOS app. But one way or another, you can make it work.&lt;/p&gt;

&lt;p&gt;I was originally drawn to KeePass because it’s open source, and that remains one
of its strengths. An open source password manager means that security experts
(and anyone else who’s interested) can check out the source code and
independently verify the strength of the encryption it uses. It also means that
the application will always be free, so you won’t get sucked in to an expensive
payment plan.&lt;/p&gt;

&lt;p&gt;All in all, KeePass has widespread support for different platforms and plugins
(although you’ll need to figure out how to sync the file to all your devices
yourself). It’s very mature and also very flexible. Although the number of
applications and plugins available can be appealing, less technical users might
find it confusing and difficult to set up.&lt;/p&gt;

&lt;h2 id=&quot;bitwarden&quot;&gt;Bitwarden&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/keepass-vs-bitwarden/bitwarden.png&quot; alt=&quot;Bitwarden logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Bitwarden is the new kid on the block, with version 1.0 released near the end
of 2016.  It was designed - from the beginning - to be used in a multi-device
environment and sync passwords over the web, providing an open source solution
that can compete with other internet-native password managers. Although KeePass
(and its derivatives) are open source, they aren’t designed to operate on the
internet so they require additional services (like Dropbox or Google Drive) to
make them sync across different devices.  Bitwarden is an all-inclusive
solution, providing a unified experience across every platform.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/keepass-vs-bitwarden/bitwarden-chrome.png&quot; alt=&quot;Bitwarden screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Bitwarden provides official desktop applications for Windows, Mac, and Linux;
official phone apps for iOS and Android; and official browser plugins for
Chrome, Firefox, and Safari (in addition to others). Because all the apps are
officially supported by Bitwarden, there’s no need to find a 3rd-party
application to support your device. And because Bitwarden has built-in support
to synchronize your passwords over the internet, there’s no need to set up
additional cloud storage systems. Ultimately, this leads to a smoother user
experience that’s also much easier to configure.&lt;/p&gt;

&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; a for-profit company behind Bitwarden, and they do offer different
&lt;a href=&quot;https://bitwarden.com/pricing/business/&quot;&gt;pricing plans&lt;/a&gt; for individuals and
businesses. But as long as you’re not using it in a corporate environment, you
shouldn’t have to worry about paying anything - it’s free for up to 2 users in
an organization. While the pricing plans may make Bitwarden feel proprietary, it
is indeed open source. And because it’s open source, it has all the same
open source advantages KeePass does. If you were to pay for Bitwarden (in a
corporate plan or a premium plan), you’d be paying for the company to manage
&lt;em&gt;running&lt;/em&gt; the software on the internet for you – the software itself is open
source and therefore free. And in a way, this gets you the best of both worlds.
You can use open source software and still have a company to lean on for support
when you need it.&lt;/p&gt;

&lt;p&gt;As I mentioned at the beginning of this article, I’m a Bitwarden user! To me,
Bitwarden feels like the new version of KeePass, solving all the problems
KeePass has about syncing passwords over the internet to multiple devices, and
providing a nice, clean interface to use.&lt;/p&gt;

&lt;h2 id=&quot;comparison&quot;&gt;Comparison&lt;/h2&gt;

&lt;p&gt;I’ve been using Bitwarden for a̶l̶m̶o̶s̶t̶ ̶a̶ ̶m̶o̶n̶t̶h̶ (update: &lt;strong&gt;years&lt;/strong&gt;) now, and I’m
really happy with it. I’m using the browser extension and the Android app, and
I love how seamless and effortless the experience is. Although my experience
with KeePass was good, there were minor annoyances here and there – like trying
to find the right plugin or fiddling with the browser extension. With Bitwarden,
all that friction is gone. I think the biggest reasons to use KeePass have
always been that it’s free (no cost) and open source, so the code can be
reviewed for security. I think Bitwarden also meets those criteria, and provides
a more modern password management solution with none of the drawbacks of
KeePass. The simplicity of configuring Bitwarden is the nail in the coffin for
me, and it was really easy to import my passwords.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;KeePass&lt;/th&gt;
      &lt;th&gt;Bitwarden&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Stood the test of time.&lt;/td&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Built for syncing multiple devices.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Includes password generator.&lt;/td&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Includes password generator.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Ported to every platform/OS imaginable.&lt;/td&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Support for all major platforms.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Lots of plugins.&lt;/td&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Seamless browser extension support.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11H7v-2h10v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Requires Dropbox to sync.&lt;/td&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Easier to set up.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11H7v-2h10v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Harder for beginners to understand.&lt;/td&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Professionally maintained.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11H7v-2h10v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Different application on each platform.&lt;/td&gt;
      &lt;td&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt; Seamless UI on all platforms.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;While I like KeePass, I think Bitwarden is &lt;em&gt;even better&lt;/em&gt;, and I’d encourage you to
give it a try if you’re thinking about using a different password manager or
trying one for the first time. And if you’re considering switching from a
different password manager, like 1Password or LastPass, I think you’ll be
pleasantly surprised with how nice it is to use Bitwarden. If you’re ready to
give it a shot, check it out at &lt;a href=&quot;https://bitwarden.com/&quot;&gt;bitwarden.com&lt;/a&gt;!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I’m not affiliated with KeePass nor Bitwarden, and I wasn’t compensated in any
way to promote one or the other. I just like writing about useful software.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/keepass-vs-bitwarden.jpg"/>
 </entry>
 
 <entry>
   <title>5 Reasons to Contribute to Open Source</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/10/07/5-reasons-to-contribute-to-open-source.html" type="text/html" title="5 Reasons to Contribute to Open Source"/>
   
   <published>2018-10-07T10:00:00-06:00</published>
   <updated>2018-10-07T10:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/10/07/5-reasons-to-contribute-to-open-source</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/10/07/5-reasons-to-contribute-to-open-source.html">&lt;p&gt;It’s October, and &lt;a href=&quot;https://hacktoberfest.digitalocean.com/&quot;&gt;Hacktoberfest&lt;/a&gt; is in
full swing! It’s a great time to contribute to open source. I love open source
software and the open source community, and I think contributing to open source
software has lots of benefits for professional software engineers. Here’s 5
reasons you should contribute to open source software.&lt;/p&gt;

&lt;h2 id=&quot;1-try-something-new&quot;&gt;#1 Try Something New&lt;/h2&gt;

&lt;p&gt;Whether you want to learn a new language or try a new framework, open source
projects provide a great opportunity to learn about new technologies. Want to
see the latest version of Angular or React in action? Want to play around with
some projects in Go or Scala? By working on open source projects, you can gain
experience with technologies you’re interested in but might not be exposed to at
your day job. In fact, it’s a great way to research technologies to see if they
might fit well with your business needs. And if you contribute a pull request,
you’ll likely get some feedback from people who are experts in the technology
you want to learn.&lt;/p&gt;

&lt;h2 id=&quot;2-collaborate-more-effectively&quot;&gt;#2 Collaborate More Effectively&lt;/h2&gt;

&lt;p&gt;When you work on an open source project, you probably won’t ever meet the people
you work with in person. All of your interaction will be online, via Github and
perhaps IRC or email. Because you can’t talk to the team face-to-face, written
communication is critically important. Most open source projects have
guidelines that promote best practices for communication, such as providing
clear, minimal examples of bugs and writing good commit messages. Open source
forces you to learn to communicate effectively in writing because there are no
other options for communication. Practices from open source projects can help
even in-person teams work more efficiently, and the communication skills you
pick up working on open source will make you a better developer.&lt;/p&gt;

&lt;h2 id=&quot;3-you-have-to-test-your-code&quot;&gt;#3 You Have To Test Your Code&lt;/h2&gt;

&lt;p&gt;Most open source projects are pretty well tested. &lt;em&gt;They have to be&lt;/em&gt; in order to
keep their products from breaking while accepting pull requests from dozens (or
hundreds) of different people. If you submit a pull request and your code isn’t
tested, you’ll probably be asked to test it (and commit the tests) before it can
be merged. In fact,
&lt;a href=&quot;https://github.com/sebastianbergmann/phpunit/pull/2938&quot;&gt;many projects&lt;/a&gt; use
tools like &lt;a href=&quot;https://codecov.io/&quot;&gt;Codecov&lt;/a&gt; to automatically request changes on
pull requests that aren’t sufficiently tested. Contributing to open source
projects like these forces you to thoroughly test your code.  Moreover, many of
the testing practices they use (CI testing, code coverage analysis, style
checkers, etc) are also useful tools for closed-source development.&lt;/p&gt;

&lt;h2 id=&quot;4-learn-from-other-developers&quot;&gt;#4 Learn From Other Developers&lt;/h2&gt;

&lt;p&gt;When you work on open source software, you get to interact with some of the best
developers around. You can learn a lot from the code they write as well as the
way they manage the project. How do they ensure their code is sufficiently
tested? How do they make the project accessible to newer developers without
letting the quality slip? Maybe you’ll be exposed to some new design patterns or
libraries that you haven’t seen before. Do they use
&lt;a href=&quot;https://junit.org/junit5/&quot;&gt;JUnit&lt;/a&gt; or
&lt;a href=&quot;https://testng.org/doc/index.html&quot;&gt;TestNG&lt;/a&gt;? Maybe they’re using
&lt;a href=&quot;https://hamcrest.org/&quot;&gt;Hamcrest&lt;/a&gt; for their assertions? How do they handle
dependency injection, and what patterns do they use to test their date logic?
There’s no better way to learn than to see these patterns in action.&lt;/p&gt;

&lt;h2 id=&quot;5-it-makes-you-a-better-developer&quot;&gt;#5 It Makes You A Better Developer&lt;/h2&gt;

&lt;p&gt;Contributing to open source is a great way to make yourself a better developer.
You’ll learn new skills, get new ideas, and have fun along the way. If you
haven’t worked on any open source projects lately, give it a try! There are
still 3 weeks left in &lt;a href=&quot;https://hacktoberfest.digitalocean.com/&quot;&gt;Hacktoberfest
2018&lt;/a&gt;, so it’s a great time to start!
(And a big thanks to &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;Digital Ocean&lt;/a&gt;,
&lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;, and &lt;a href=&quot;https://www.twilio.com/&quot;&gt;Twilio&lt;/a&gt; for
sponsoring the event!)&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/Hacktoberfest_2018_banner4_522x522.png"/>
 </entry>
 
 <entry>
   <title>Make Your Source Code Developer-Friendly</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/08/19/make-your-source-code-developer-friendly.html" type="text/html" title="Make Your Source Code Developer-Friendly"/>
   
   <published>2018-08-19T15:00:00-06:00</published>
   <updated>2018-08-19T15:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/08/19/make-your-source-code-developer-friendly</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/08/19/make-your-source-code-developer-friendly.html">&lt;p&gt;Is your source code developer-friendly? Developers talk about user-friendliness
of applications all the time, but don’t often think about whether their own
source code is user-friendly. Code tends to rot over time. The documentation
gets out of date, configuration files change, little “hacks” are put into
place… Eventually, it gets so bad it takes &lt;em&gt;days&lt;/em&gt; for a new developer to get
his environment set up. And along the way, he has to talk to two or three gurus
that know all the little tricks to make things work right. As code ages, it
becomes less developer-friendly. As developers, it’s our job to counteract that
tendency.&lt;/p&gt;

&lt;p&gt;It’s pretty easy to assess the developer-friendliness of a code base. A
developer who’s never seen your code before should be able to clone your Git
repository, run your tests, run your application, and produce a build without
any extra help. And the time it takes him to do all of that should be measured
in &lt;em&gt;minutes&lt;/em&gt;, not hours. Most enterprise code bases probably fail miserably by
these standards but I don’t think it’s &lt;em&gt;impossible&lt;/em&gt; to live up to them, even
with a large code base. And at least they provide a goal to shoot for.&lt;/p&gt;

&lt;p&gt;Software tends to change, and it tends to change quickly. Most code bases are
probably pretty friendly when they’re first created. Young code bases are
simple, so it’s easy to meet all the criteria listed above. And everyone has
every intention of keeping it that way. But as code bases grow, they &lt;a href=&quot;/blog/2018/07/23/code-reuse-and-simplicity.html&quot;&gt;become
very complex very fast&lt;/a&gt;. In what seems like an instant, the
code isn’t developer-friendly anymore. Developers need to be proactive about
fighting this trend.&lt;/p&gt;

&lt;h2 id=&quot;why-is-this-important&quot;&gt;Why is this important?&lt;/h2&gt;

&lt;p&gt;Developer-friendly code has business value. When your code isn’t
developer-friendly, it takes new developers longer to get their environment set
up. It also takes seasoned developers longer to keep their environment
maintained and up-to-date. Suppose a mid-level engineer drops and breaks his
laptop on the way to work and needs to set up his environment from scratch. Will
that take him a couple hours or a couple days? Alternatively, suppose a team
needs to add some features to a project that hasn’t been touched in a few years.
What kind of state was that project left in, and how long will it take the team
to create environments to build, test, and deploy the code? The long-term,
repetitive costs of code bases that aren’t developer-friendly add up quickly.&lt;/p&gt;

&lt;h2 id=&quot;how-do-you-fix-it&quot;&gt;How do you fix it?&lt;/h2&gt;

&lt;p&gt;So, what do you do if you have an older code base that needs a little love and
attention?&lt;/p&gt;

&lt;h3 id=&quot;write-a-readme&quot;&gt;Write a README&lt;/h3&gt;

&lt;p&gt;Github has influenced the software development industry in a lot of ways. One
subtle influence is that Github really encourages the use of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README.md&lt;/code&gt; file
at the root of your repository by rendering it on your repository’s Github page.
Even if you’re not using Github, including a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README.md&lt;/code&gt; file in your repository
is a great practice. When you have a readme file, developers don’t have to go
searching for the documentation - it’s right there next to the code. Because the
readme is checked into the repository, it’s version controlled, and can be
updated as the code changes. Even if it gets out of date, it’s easy for any
developer to fix it, since they already have permissions and know how to make
source file changes in the repository. When you’re writing a readme, keep your
developer-friendliness goals in mind. A good readme should explain how to run
the tests, run the application, and produce a build.&lt;/p&gt;

&lt;h3 id=&quot;simplify-your-toolchain&quot;&gt;Simplify Your Toolchain&lt;/h3&gt;

&lt;p&gt;If you create a readme file and it seems way too long, it probably is. The
instructions in your readme file should be &lt;em&gt;simple&lt;/em&gt;. And if they’re not, it’s a
good indication that your process is too complicated. Ideally, there would only
be a single command to test your application, a single command to run it, and a
single command to deploy it. If any of those steps currently require more than
one or two commands, consider creating a shell script (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.sh&lt;/code&gt;). This
makes it easy to run the tests without worrying about the complicated bits - and
a developer can still inspect the script if he wants to understand what’s
actually going on. Short, simple commands are easier for new developers to grasp
and faster for experienced developers to use.&lt;/p&gt;

&lt;h3 id=&quot;simplify-your-process&quot;&gt;Simplify Your Process&lt;/h3&gt;

&lt;p&gt;If you have a readme file and it’s easy for developers to get things running,
you’re already well on your way to improving the developer-friendliness of your
code base. But there’s probably still more you can do. Do developers know what
they have to do to get their code merged and approved? Many open source projects
document that process in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CONTRIBUTING.md&lt;/code&gt; file. What about continuous
integration? Do you have an automated process to ensure that code passes quality
checks before it gets merged into master? Automated test, build, and deployment
processes prevent human mistakes while also making your repositories easier to
interact with.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/developer-friendly.jpg"/>
 </entry>
 
 <entry>
   <title>LogRun: A Running Log in Google Sheets</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/07/31/logrun-a-running-log-in-google-sheets.html" type="text/html" title="LogRun: A Running Log in Google Sheets"/>
   
   <published>2018-07-31T10:00:00-06:00</published>
   <updated>2018-07-31T10:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/07/31/logrun-a-running-log-in-google-sheets</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/07/31/logrun-a-running-log-in-google-sheets.html">&lt;p&gt;I made a training log for runners in Google Sheets!&lt;/p&gt;

&lt;p&gt;Even in the age of Fitbit, GPS running watches, and Strava, a training log in a
spreadsheet is a useful tool. Because it’s a spreadsheet, it can be used to
analyze data in ways that websites and apps can’t. It’s easy to share with a
coach. And some people might find extra motivation from setting a goal and
recording their progression over time in a spreadsheet.&lt;/p&gt;

&lt;!--more--&gt;

&lt;div class=&quot;message&quot;&gt;
&lt;p&gt;
&lt;b&gt;Update:&lt;/b&gt; I joined the software engineering team at &lt;b&gt;Strava&lt;/b&gt;
in April, 2019! So although I didn&apos;t work for Strava when I wrote this blog
post, I do now! We&apos;re building amazing things for athletes, and I&apos;d encourage
you to check out &lt;a href=&quot;https://www.strava.com/subscribe&quot;&gt;Strava&lt;/a&gt; and
consider subscribing for our best features. It&apos;s amazing how much motivation can
come from being part of a community. And whether you join Strava or not, I hope
this running log spreadsheet is useful for you!
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;My running log spreadsheet does a lot of &lt;strong&gt;automatic calculations and graphs&lt;/strong&gt;
after you input your daily running time and mileage. The best way to see what it
can do is with an example. (Be sure to check out the tabs at the bottom of the
spreadsheet to see the graphs!)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Spreadsheet: &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1g0DCy-WwJM97s2KtAaIp1wWyX_10Y9zT_ii4S6DM1w0/edit?usp=sharing&quot;&gt;LogRun with Sample Data&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/1g0DCy-WwJM97s2KtAaIp1wWyX_10Y9zT_ii4S6DM1w0/edit?usp=sharing&quot;&gt;&lt;img src=&quot;/images/posts/logrun-full.jpg&quot; alt=&quot;Spreadsheet Graphs&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;features&quot;&gt;Features&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Calculates average pace based on distance and time.&lt;/li&gt;
  &lt;li&gt;Displays weekly mileage, average pace, and time spent running in tables and graphs.&lt;/li&gt;
  &lt;li&gt;Breaks down stats by type of run (easy, long, tempo, etc).&lt;/li&gt;
  &lt;li&gt;Mileage graphs are a great visualization tool for spotting trends and measuring progression.&lt;/li&gt;
  &lt;li&gt;Tracks mileage on shoes.&lt;/li&gt;
  &lt;li&gt;Tracks race performance and personal records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Long ago, &lt;a href=&quot;https://web.archive.org/web/20220331025852/http://davidhays.net/running/runlog/runlog.html&quot;&gt;David Hays&lt;/a&gt;
published his version of a training log in Excel. I was inspired by that, and
wanted to create something similar that presented the types of data &lt;em&gt;I find most
important&lt;/em&gt; in the &lt;em&gt;simplest way possible&lt;/em&gt;. And I wanted something that worked in
Google Sheets instead of Excel. The spreadsheet I’ve created here is what I came
up with.&lt;/p&gt;

&lt;h2 id=&quot;create-your-own-training-log&quot;&gt;Create Your Own Training Log&lt;/h2&gt;

&lt;p&gt;This training log spreadsheet is free for you to use and modify to track your
own workouts and share with your training partners, coaches, and friends.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;Do not &quot;Request Access&quot;&lt;/b&gt; to edit the blank copy or the example. You must make
your own copy first (with the instructions below) so the blank copy remains
available for everyone.&lt;/p&gt;
&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;Choose one of the blank versions below:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/1tr15fIaK7J32QNCq5Y3jl5T1Vex0PiGNlxDReeo5Hsg/edit?usp=sharing&quot;&gt;LogRun&lt;/a&gt;
– Standard version. 365 rows, fits up to a year of daily data. Use this version if you’re
not sure which to pick.&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/1k_pFnhHxqIJUI1Pjk-7I3y1Px8MNTpN7DmcbiMh00ic/edit?usp=sharing&quot;&gt;LogRun Metric&lt;/a&gt;
– Uses kilometers instead of miles.&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/18jNZ7H2g5ag9OPRX84EdojoZf1cOGop2p3plVK5SsXo/edit?usp=sharing&quot;&gt;LogRun 1000&lt;/a&gt;
– Fits 1000 rows of data. Because of this, it might also be slower. Use it
if 365 rows isn’t enough for you.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Make a copy...&lt;/code&gt; to save your own copy to your Google Drive.
Or, Click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Download as&lt;/code&gt; to download an Excel version.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This spreadsheet has been a fun and useful tool for me, and I hope you enjoy it
too! Please share it with your friends, and if you like it, let me know
&lt;a href=&quot;https://twitter.com/mike_kasberg&quot;&gt;@mike_kasberg&lt;/a&gt;! You can even &lt;a href=&quot;https://www.buymeacoffee.com/mkasberg&quot;&gt;buy me a
coffee&lt;/a&gt; if you want to, as a way to say
thanks.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/logrun.jpg"/>
 </entry>
 
 <entry>
   <title>Code Reuse and Simplicity</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/07/23/code-reuse-and-simplicity.html" type="text/html" title="Code Reuse and Simplicity"/>
   
   <published>2018-07-23T10:30:00-06:00</published>
   <updated>2018-07-23T10:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/07/23/code-reuse-and-simplicity</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/07/23/code-reuse-and-simplicity.html">&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Jn3kdTaa69U&quot;&gt;&lt;em&gt;The Myth of Code Reuse&lt;/em&gt;&lt;/a&gt;, a quick
video by Iain Lowe, became quite popular on Reddit in
&lt;a href=&quot;https://www.reddit.com/r/programming/&quot;&gt;/r/programming&lt;/a&gt; a few weeks ago. I think
the reason for it’s popularity is obvious. Lowe talks about the amount of time
developers invest in making code “reusable” with interfaces and abstractions,
only to watch that code be replaced a few years down the road. Many professional
developers have worked with legacy code bases that frequently use these types
of abstractions (designed for “future-proof” code), but found the code to be
more complicated than necessary and difficult to work with.&lt;/p&gt;

&lt;p&gt;Developers have been taught that code reuse is an important part of good code,
so they tend to try to put it into every system they design. Unfortunately, this
also tends to add to the complexity of the system. Often, the “reusable” code is
never actually reused. In the end, the abstraction adds complexity to the system
without delivering any additional value.&lt;/p&gt;

&lt;p&gt;Similarly, a blog post about the cost of using &lt;a href=&quot;https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction&quot;&gt;The Wrong
Abstraction&lt;/a&gt; was
recently on the front page of &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt;. Like
the video, this blog post illustrates the difficulty of working with
abstractions that are a poor match for the codebase. Whether by poor design or
by a lack of refactoring as the code base evolves, bad abstraction patterns can
turn into a big code maintenance cost.&lt;/p&gt;

&lt;p&gt;How can developers safe-guard against anti-patterns emerging in their code base
to create code that is &lt;em&gt;acutally&lt;/em&gt; maintainable? I think there are 2 important
rules to follow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Complexity is bad.&lt;/strong&gt; Keep the code as simple as possible.&lt;/li&gt;
  &lt;li&gt;Write unit tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;complexity-is-bad&quot;&gt;Complexity is Bad&lt;/h2&gt;

&lt;p&gt;It’s easy for software developers to get carried away with the design of their
code. They want to apply all the algorithms and design patterns they’ve learned.
They want to use the latest technologies and newest features.  They take pride
in their work, and they don’t just want a solution that’s good enough. They want
the best solution.&lt;/p&gt;

&lt;p&gt;This often leads to bloated, complex code that’s difficult to work with.
Interfaces and other abstractions pop up all over the code base, even when
they’re not needed at all. Sometimes, developers will go as far as developing
classes that support things like custom plugin or callback architectures because
they envision a future where it might be needed. This usually results in bad
code because it’s more complex than it needs to be. It’s easy for developers to
be conned into the trap of building cool stuff that wasn’t asked for. If a
plugin system is required (as part of the story, within scope) because there’s
specific planned work that will make use of it, then it obviously needs to be
written.  Otherwise, don’t write it. If code is going to be reused (or even
maintained for several years), it needs to be easy for other developers to read
and understand.  Every bit of added complexity makes code harder to maintain.
Complexity is bad.  In almost every case, the best code is the simplest code.&lt;/p&gt;

&lt;h2 id=&quot;write-unit-tests&quot;&gt;Write Unit Tests&lt;/h2&gt;

&lt;p&gt;The counter-argument to the case I’m making for simplicity is, of course, the
need to prepare for the future. Some might say that the argument against
complexity is short-sighted, and that it’s a good trade-off to introduce some
complexity into the system to make life easier down the road. But I don’t think
that’s usually the case. The future is unpredictable, and you’ll inevitably
build something that is different than what you end up needing.&lt;/p&gt;

&lt;p&gt;So, how do you protect your code base against an uncertain future? A
high-quality unit test suite is the best way to ensure a code base continues to
be maintainable. If developers can’t quickly introduce changes without the fear
of breaking existing functionality, the code base is no longer maintainable. The
best way to provide the security future developers need (whether it’s yourself
or others) is with a good unit test suite.  Good unit tests are a kind of living
documentation of what the code is expected to do, and they evolve with the code,
providing a safety net for making changes.  This safety net allows the
development team to make changes to the code base quickly, providing a hedge
against the uncertainty of the future.&lt;/p&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;Complexity is bad. Keep your code as simple as possible. Write high-quality unit
tests and keep them up-to-date to ensure the code base can evolve rapidly. Unit
tests are the best way to future-proof your code base.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/code-reuse-and-simplicity.jpg"/>
 </entry>
 
 <entry>
   <title>Tips for a Great Resume</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/06/19/tips-for-a-great-resume.html" type="text/html" title="Tips for a Great Resume"/>
   
   <published>2018-06-19T10:00:00-06:00</published>
   <updated>2018-06-19T10:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/06/19/tips-for-a-great-resume</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/06/19/tips-for-a-great-resume.html">&lt;p&gt;Your resume says a lot about you. Perhaps more than you think. I’ve looked at a
fair amount of resumes and when I read a resume, I’m not &lt;em&gt;only&lt;/em&gt; interested in
the experience that’s listed. I also look at how well the person communicates
their ideas in a clear and concise format. A well written and easy-to-read
resume can make a good first impression, and a poorly written resume can
definitely hurt you.&lt;/p&gt;

&lt;p&gt;Based on my experience - both reading resumes and writing my own - I’ve come up
with some simple tips to help you make sure your resume looks great and avoid
some pitfalls. And while my experience is centered around software engineering,
most of these tips will apply to other fields equally well.&lt;/p&gt;

&lt;h2 id=&quot;keep-it-to-a-page&quot;&gt;Keep it to a page&lt;/h2&gt;

&lt;p&gt;When I interview someone, it’s actually a bit of a red flag for me if their
resume is longer than a single page. It tells me that they don’t know how to
filter out unimportant information and summarize the most important ideas. Also,
it’s not uncommon for me to have to skim through several resumes at once. And
when I’m skimming, I don’t always have time to read all 5 pages, so sometimes I
only look at the first page anyway. While there are always exceptions, most
people will be better off with a single-page resume.&lt;/p&gt;

&lt;p&gt;If your resume’s longer than a single page, consider what’s really
relevant to the job you’re applying for. Are the oldest items on your resume
still relevant? Can some details be removed there? Also, consider removing
details from more recent work if the information isn’t applicable to the
position you’re applying for.&lt;/p&gt;

&lt;h2 id=&quot;make-it-look-a-little-unique-but-keep-it-formal&quot;&gt;Make it look a little unique (but keep it formal)&lt;/h2&gt;

&lt;p&gt;You want your resume to look like you put some time, thought, and effort into
it. Using Times New Roman with bold headings does none of these things.  You
probably won’t be eliminated for using the default MS Word font, but you won’t
stand out either. Essentially, you want to show your interviewer that you care
enough about your resume to make it look nice. Experiment with different fonts
and font sizes, and let your personality come out a little. A well-chosen
monospaced font can be a nice touch on a programmer’s resume. Both serif and
sans-serif fonts are OK, but make sure they look professional (not Comic Sans!).
Consider using a different font for your name or for headings throughout the
resume. If done well, this can help separate your headings from the rest of the
content, making your resume more readable.&lt;/p&gt;

&lt;h2 id=&quot;make-sure-its-easy-to-skim&quot;&gt;Make sure it’s easy to skim&lt;/h2&gt;

&lt;p&gt;Speaking of readability, it’s not uncommon for an interviewer to have to look
through 5, 10, or even 20 resumes in a short amount of time. &lt;strong&gt;Make it easy&lt;/strong&gt;
for your interviewer to find the information they’re looking for quickly. Make
sure different sections (like &lt;em&gt;Education&lt;/em&gt;, &lt;em&gt;Experience&lt;/em&gt;, and &lt;em&gt;Skills&lt;/em&gt;) can be
found at a glance. And it should be easy to quickly skim job titles, companies,
and dates. Consistent formatting is the biggest factor that can improve (or
hinder) readability. If your dates are right-aligned in the &lt;em&gt;Education&lt;/em&gt; section,
they should also be right-aligned in the &lt;em&gt;Experience&lt;/em&gt; section.&lt;/p&gt;

&lt;h2 id=&quot;dont-be-sloppy&quot;&gt;Don’t be sloppy&lt;/h2&gt;

&lt;p&gt;On the surface, it seems pretty easy (and obvious) to make sure your resume
doesn’t look sloppy. Don’t use square bullets in one section and round bullets
in another. Don’t use list numbers in one section and list letters in another.
Don’t highlight a couple too many characters when you hit the &lt;em&gt;italics&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;In addition to the obvious stuff, make sure that MS Word (or whichever program
you’re using) lines things up the right way. In resumes, it’s pretty common to
have left-aligned and right-aligned text on the same line. Don’t use spaces to
separate left-aligned text from right-aligned text on the same line. &lt;a href=&quot;https://superuser.com/questions/1062359/having-some-text-align-left-and-other-align-right-in-same-line/&quot;&gt;There’s a
proper way to do it&lt;/a&gt;,
and it looks bad when you don’t (because the right edge won’t line up exactly).&lt;/p&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In Conclusion&lt;/h2&gt;

&lt;p&gt;There are articles all over the internet with resume basics and sample layouts.
I hope these tips go a little bit deeper than that, and inspire you to create a
great-looking and highly functional resume for your next job search.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/6-questions-to-ask-when-you-interview.jpg"/>
 </entry>
 
 <entry>
   <title>6 Questions to Ask When You Interview for a Software Development Position</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/04/25/6-questions-to-ask-when-you-interview.html" type="text/html" title="6 Questions to Ask When You Interview for a Software Development Position"/>
   
   <published>2018-04-25T06:30:00-06:00</published>
   <updated>2018-04-25T06:30:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/04/25/6-questions-to-ask-when-you-interview</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/04/25/6-questions-to-ask-when-you-interview.html">&lt;p&gt;I recently completed a job search where I had the pleasure of interviewing at
several companies. Throughout the process, I developed and refined a list of
questions to ask the companies I interviewed with.&lt;/p&gt;

&lt;p&gt;I’ve also spent a lot of time on the other side of the interview process, and
I’m often surprised when the candidates I’m interviewing don’t have any good
questions prepared to ask me. They should want to know how we do things here!
Asking good questions during the interview process helps both parties find the
right fit, so I decided to share some of the best questions I’ve found.&lt;/p&gt;

&lt;!--more--&gt;
&lt;h3 id=&quot;what-types-of-computers-do-your-developers-use&quot;&gt;What types of computers do your developers use?&lt;/h3&gt;

&lt;p&gt;Obviously, there’s a pretty big difference between developing on macOS or
Windows, and many developers have a strong preference for one or the other.
Hopefully you already have an idea what the answer to this one might be before
you get too far into the interview. But it’s also good to go a little deeper -
what kind of hardware do developers get? Are they using SSDs? Does the company
buy licenses for IDEs and other tools? You might not want to work for a company
where you won’t have the tools you need to be great at your job.&lt;/p&gt;

&lt;h3 id=&quot;what-are-the-expectations-for-off-hours-work&quot;&gt;What are the expectations for off-hours work?&lt;/h3&gt;

&lt;p&gt;Will you be on call 24/7? Are there late-night or weekend deploys? How often?
Most companies have some kind of off-hours work for their developers, and it’s
good do know about it up front. Going into the interview, think about what your
own expectations are for off-hours work and how much you’re willing to
compromise.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-process-for-getting-code-reviewed-merged-and-released&quot;&gt;What is the process for getting code reviewed, merged, and released?&lt;/h3&gt;

&lt;p&gt;The answer to this question will have a &lt;em&gt;huge&lt;/em&gt; effect on some of your primary
responsibilities, yet the way these processes are handled varies greatly from
company to company.  Will you be creating pull requests on a private Github
repository? Or will your code be stored on a shared file server without version
control? Does the company use continuous deployment, or will your code sit for a
while and be deployed as part of a later release? How long are the release
cycles? The answer to this question can be complex, but it’s a good question to
ask because it can give you &lt;em&gt;a lot&lt;/em&gt; of insight into the company’s processes. In
fact, this is one of my favorite questions to ask during an interview. It lets a
company show off their strengths, but also exposes some of their weaknesses, and
it can be an entry point into a discussion about what value you might be able to
provide as a new employee.&lt;/p&gt;

&lt;h3 id=&quot;how-do-you-test-the-software-you-develop&quot;&gt;How do you test the software you develop?&lt;/h3&gt;

&lt;p&gt;Is there a QA team? Do developers do their own testing? What is the balance
between unit and integration tests? There are pretty big differences between the
way different companies test their software, and again, this can reveal a lot
about the company. What are your own beliefs about testing, and how much are you
willing to deviate from those? For me, it’s a big red flag if there’s little to
no unit testing. This question is a great follow-up to the previous one, and can
lead to a broader discussion of testing practices.&lt;/p&gt;

&lt;h3 id=&quot;how-is-developer-work-scheduled-and-managed&quot;&gt;How is developer work scheduled and managed?&lt;/h3&gt;

&lt;p&gt;Many companies use Scrum, Kanban, or some other agile methodology. Some
companies aren’t agile. There’s no “correct” answer here, but it’s still an
important interview question to ask because it has a big effect on your
day-to-day work. Going into the interview, you should at least have an idea
of what is involved in some of the most common development methodologies so you
can have an intelligent conversation about it.&lt;/p&gt;

&lt;h3 id=&quot;what-is-an-example-of-a-project-i-might-work-on&quot;&gt;What is an example of a project I might work on?&lt;/h3&gt;

&lt;p&gt;What will the balance of front-end vs back-end work be? Does the type of work
you’ll be doing align with your interests? Many companies list open roles as
generic “software engineer” positions. It’s important to gain a little more
insight into which technologies you’ll be working with on the job so that you
can make sure you do something you enjoy. Often, there will be differences
between your initial perception of the company and the type of projects you
might actually work on, and it’s important to talk about those.&lt;/p&gt;

&lt;h2 id=&quot;so-will-you-like-the-job&quot;&gt;So… Will you like the job?&lt;/h2&gt;

&lt;p&gt;I like these questions because they are all designed to help you get a better
idea of &lt;em&gt;what you’ll do on a daily basis&lt;/em&gt; at your new job. It’s surprising to me
how little people sometimes know about the position they’re interviewing for and
the responsibilities the position entails. Hopefully these questions can be the
seed of some useful and productive discussions throughout your interview
process, and lead to a job you really enjoy!&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/6-questions-to-ask-when-you-interview.jpg"/>
 </entry>
 
 <entry>
   <title>My Favorite Windows Software</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/04/24/my-favorite-windows-software.html" type="text/html" title="My Favorite Windows Software"/>
   
   <published>2018-04-24T10:00:00-06:00</published>
   <updated>2018-04-24T10:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/04/24/my-favorite-windows-software</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/04/24/my-favorite-windows-software.html">&lt;p&gt;I recently had to reinstall Windows on one of my personal computers. Although I
hardly ever use Windows anymore, I keep it around in case I need to run some
software that isn’t compatible with Linux (my primary OS).&lt;/p&gt;

&lt;p&gt;After reinstalling Windows, I needed to re-install all my favorite programs so
things are there when I need them. In the process, I kept a short list of all
the Windows software I like to have installed. Most of it is free or open
source, so have a look. Maybe you’ll find something useful.&lt;/p&gt;

&lt;!--more--&gt;
&lt;h2 id=&quot;general-software&quot;&gt;General Software&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.google.com/chrome&quot;&gt;Google Chrome&lt;/a&gt; is probably the first software
I install on any new computer. If you’re still using the default browser that
comes with Windows, try Chrome. You’ll probably like it.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.getfirefox.com/&quot;&gt;Mozilla Firefox&lt;/a&gt; is also very good if you want a 
browser that isn’t owned by Google.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.videolan.org/&quot;&gt;VLC Media Player&lt;/a&gt; will play pretty much any media
format you throw at it. I set pretty much all media file types to open in VLC
when I double-click on them.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://get.adobe.com/reader/&quot;&gt;Adobe Reader&lt;/a&gt;, because you’ll probably need it.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.7-zip.org/&quot;&gt;7-zip&lt;/a&gt; is a great program for working with archives.
But probably its best feature is that it adds “Extract Here” to your
right-click menu.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://keepassxc.org/&quot;&gt;KeePassXC&lt;/a&gt; is a good password manager that works on
pretty much any OS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;image-and-video-software&quot;&gt;Image and Video Software&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.getpaint.net/&quot;&gt;Paint.Net&lt;/a&gt; is a replacement for MS Paint that
makes basic image editing tasks really quick &amp;amp; easy.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.gimp.org/&quot;&gt;GIMP&lt;/a&gt; is like the free version of Photoshop. It can
handle much more than Paint.NET, but it’s also more complex.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://getgreenshot.org/&quot;&gt;Greenshot&lt;/a&gt; is a screenshot tool on steroids.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;development-software&quot;&gt;Development Software&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://atom.io&quot;&gt;Atom&lt;/a&gt; is the free &amp;amp; open-source text editor from Github.
It’s a great tool to have, since it supports (or has plugins for) most
languages, and is completely free.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://notepad-plus-plus.org/&quot;&gt;Notepad++&lt;/a&gt; was my default editor for years,
and I still like having it around. It’s great for quick text edits where I
don’t want or need a more complete environment like Atom.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/download/win&quot;&gt;Git&lt;/a&gt; is an obvious necessity on any development
machine.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.chiark.greenend.org.uk/~sgtatham/putty/download.html&quot;&gt;PuTTY&lt;/a&gt; will
let you SSH from your windows machine. Critical if you’re working with Linux
servers.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://filezilla-project.org/&quot;&gt;FileZilla&lt;/a&gt; is an easy to use FTP client.&lt;/li&gt;
&lt;/ul&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/my-favorite-windows-software.jpg"/>
 </entry>
 
 <entry>
   <title>Why Are My Tests Failing?</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/03/02/why-are-my-tests-failing.html" type="text/html" title="Why Are My Tests Failing?"/>
   
   <published>2018-03-02T17:00:00-07:00</published>
   <updated>2018-03-02T17:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/03/02/why-are-my-tests-failing</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/03/02/why-are-my-tests-failing.html">&lt;p&gt;Have you ever tried to diagnose a test failure and had no idea what’s broken?
Maybe you were looking at something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Failed asserting that false is true.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or, equally as bad:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java.lang.AssertionError
  at ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These are pretty bad failure messages. They have the bare minimum
amount of information you might get from a failing test. They tell you something’s broken,
and probably give you a line number or a stack trace, but that’s all. They don’t
give you very much &lt;em&gt;useful information&lt;/em&gt;. In the spirit of Google’s &lt;a href=&quot;https://testing.googleblog.com/search/label/TotT&quot;&gt;Testing on
the Toilet&lt;/a&gt;, this is my own
rant about one way you can make your tests better.&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;Failure messages like those above might have been produced by code like this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testMyCollection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assertTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The problem is that if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertTrue()&lt;/code&gt; fails, no additional information is
provided to the user.
Let’s see if we can clean that up a little. A simple approach is to just add a
failure message:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;assertTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The list is missing the number 2.&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That makes our test failure message a little more useful:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java.lang.AssertionError: The list is missing the number 2.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But that’s tedious. Nobody wants to write a message for every assertion. Also,
there will be a tendency for the message to become outdated as the code is
updated. Can we do better?&lt;/p&gt;

&lt;h2 id=&quot;matchers&quot;&gt;Matchers&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://hamcrest.org/&quot;&gt;Hamcrest&lt;/a&gt; is a framework that provides matchers for
various data types with nice failure messages. You call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertThat(actual,
matcher)&lt;/code&gt; and your object is compared to the Matcher. If the condition isn’t
fulfilled, you get a useful, easy-to-read failure message.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testMyCollection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hasItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java.lang.AssertionError: 
Expected: a collection containing &amp;lt;2&amp;gt;
     but: was &amp;lt;1&amp;gt;, was &amp;lt;3&amp;gt;, was &amp;lt;5&amp;gt;, was &amp;lt;7&amp;gt;, was &amp;lt;9&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Matchers are great because they know how to produce a useful message when a
condition isn’t fulfilled.  The best part about Hamcrest is that matchers are
reusable, and it comes with a lot of them. If it doesn’t have a Matcher you
need, it’s easy to write your own! And Hamcrest isn’t the only framework that
can do this. Other frameworks, like
&lt;a href=&quot;https://joel-costigliola.github.io/assertj/&quot;&gt;AssertJ&lt;/a&gt;, work in a very similar
way.&lt;/p&gt;

&lt;h2 id=&quot;more-complexity&quot;&gt;More Complexity&lt;/h2&gt;

&lt;p&gt;So far, we’ve looked at a few quick examples, but it’s not always so simple.
Suppose you have some integration tests for an API that are running code like
this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If that test fails, you’ll get a message that looks something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java.lang.AssertionError:
Expected :200
Actual   :422
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;OK, that’s a little useful… At least you know the API returned a 422 when it
was supposed to return a 200. If you were trying to debug this, would you want
any more information? Personally, I’d want to see the response body. It would
save me a lot of time, since without it I’ll have to debug through this test or
call the API manually to see what went wrong.&lt;/p&gt;

&lt;p&gt;But how to you make the response body show up when the test fails? Hamcrest (at
least as we’ve used it so far, without custom Matchers) won’t help you here,
since you’re already getting a reasonable message for the integer comparison.
What we need is to add more info to the message. That’s easy enough:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s better. Now, we can see the body of the HTTP response in our failing
test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java.lang.assertionError: ID is a required parameter, but no ID was provided.
Expected :200
Actual   :422
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This message gives us much more information about what is wrong with the system
we’re testing. It &lt;em&gt;saves us time&lt;/em&gt; when we’re trying to debug a problem, and that
can be incredibly valuable when it helps your team iterate quicker. If you find
yourself doing things like this often, it might make sense to look for a library
to help with it, or write your own matcher that can automatically parse the
response body (or status code, headers, etc.) out of an HTTPResponse.&lt;/p&gt;

&lt;p&gt;What we’ve talked about so far is only the tip of the iceberg. What kind of
failure message is most useful when comparing large blobs of JSON? CSVs?
Selenium pages? As you include more matchers in your codebase, patterns will
begin to emerge. You’ll learn what information is most relevant for particular
types of failures, and you’ll make that information quickly available. Over
time, these patterns can help you transform your test suite into a really
powerful test harness that matches perfectly with the unit you’re testing.&lt;/p&gt;

&lt;h2 id=&quot;so-what&quot;&gt;So What?&lt;/h2&gt;

&lt;p&gt;So what’s the point?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tl;dr:&lt;/strong&gt; Make sure your unit and integration tests print useful information
when they fail. It will save you time in the long run.&lt;/p&gt;

&lt;p&gt;That sounds like it’s really simple, but it’s easy to forget about it when you
just want to write a quick test and move on to the next piece of code. TDD can
help with this - if you watch a test fail before you watch it pass, you have a
great opportunity to improve the failure message if it needs more info.&lt;/p&gt;

&lt;p&gt;We can break all of this down into a few simple points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Always include some a useful failure message with your tests.&lt;/li&gt;
  &lt;li&gt;Always watch your tests fail (not just pass!) at least once, so you can see
the message that a failure produces and determine if it is useful.&lt;/li&gt;
  &lt;li&gt;It’s fine to use normal Assert methods, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Assert.assertEquals(expected, actual)&lt;/code&gt;,
if you like them and they produce useful messages for you.&lt;/li&gt;
  &lt;li&gt;Use Hamcrest matchers if there’s no good assertion method. If you can’t find
a Hamcrest matcher that you need, write your own (by subclassing BaseMatcher
or one of its children).&lt;/li&gt;
  &lt;li&gt;If it’s too complicated to write a matcher or you don’t plan on reusing it,
you can always fall back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertTrue(message, condition)&lt;/code&gt; with a useful
message.&lt;/li&gt;
&lt;/ul&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/why-are-my-tests-failing.jpg"/>
 </entry>
 
 <entry>
   <title>How Much Faster Does a New SSD Make a 2012 MacBook Pro?</title>
   
   <link href="https://www.mikekasberg.com/blog/2018/03/01/how-much-faster-does-a-new-ssd-make-a-2012-macbook-pro.html" type="text/html" title="How Much Faster Does a New SSD Make a 2012 MacBook Pro?"/>
   
   <published>2018-03-01T18:20:00-07:00</published>
   <updated>2018-03-01T18:20:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2018/03/01/how-much-faster-does-a-new-ssd-make-a-2012-macbook-pro</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2018/03/01/how-much-faster-does-a-new-ssd-make-a-2012-macbook-pro.html">&lt;p&gt;How much faster does a new SSD make a 2012 MacBook Pro? &lt;strong&gt;A lot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I recently upgraded a 2012 MacBook Pro with a new SSD, and I was &lt;em&gt;blown away&lt;/em&gt; by
the results. The computer was nearly unusable before the upgrade - it took
more than two minutes to boot and about 30 seconds to launch a program like
Chrome or Safari. (Although once it finally started, it wouldn’t be too bad to
do something simple like surf the web.) Apparently (as you’ll find in comments
all over the internet), lots of older MacBooks suffer from &lt;strong&gt;really slow
responsiveness&lt;/strong&gt; when running newer versions of macOS. Luckily, the problem can
be fixed pretty easily by replacing the OEM hard drive with an SSD – after the
fix, the MacBook Pro boots in about 25 seconds and launches programs almost
instantly. Overall, the SSD made the computer about &lt;strong&gt;four times faster&lt;/strong&gt; when
doing disk-bound operations like booting up or starting a program!&lt;/p&gt;

&lt;!--more--&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Operation&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;HDD Time&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;SSD Time&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Boot to login screen&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1m 30s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;25s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Login (to desktop icons)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;29s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;8s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Start Chrome&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;20s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2m 19s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;38s&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Will this fix your old MacBook?&lt;/strong&gt; Probably. If your old computer has similar
symptoms (a long boot time and slowness when launching applications), and if
your old computer uses a traditional hard disk, an SSD is likely to give you
similar results to mine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you know what kind of hard drive you have?&lt;/strong&gt; One of the easiest ways to
know what kind of hard drive came with your Mac is to use the &lt;a href=&quot;https://everymac.com/ultimate-mac-lookup/&quot;&gt;Ultimate Mac
Lookup&lt;/a&gt; tool provided by
EveryMac.com. Alternatively, look in your Mac’s System Report, as shown
&lt;a href=&quot;https://apple.stackexchange.com/questions/290329/how-to-know-whether-mac-has-ssd-or-hdd-installed&quot;&gt;here&lt;/a&gt;.
If your storage says &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HDD&lt;/code&gt;, you have a traditional hard disk. If your storage
says &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSD&lt;/code&gt;, you have an SSD.&lt;/p&gt;

&lt;h2 id=&quot;why-does-the-ssd-help-so-much&quot;&gt;Why does the SSD help so much?&lt;/h2&gt;

&lt;p&gt;Second generation MacBook Pros (2008-2012) come with a 5400RPM hard disk, which
is basically a bottom-shelf hard disk by modern standards. The low performance
of the disk limits the speed that data can be read from or written to the disk.
Basically, the MacBook runs slow because it can’t load it’s operating system and
programs from the hard drive fast enough. This becomes really apparent on newer
versions of macOS, where the time needed to boot the OS or load an application
is really slow. In fact, the hard drive is so slow that it’s the biggest
bottleneck in the system.&lt;/p&gt;

&lt;p&gt;Fortunately, this is easy to fix with a simple hard drive upgrade. Disk I/O on
an SSD is more than &lt;a href=&quot;https://en.wikipedia.org/wiki/IOPS#Examples&quot;&gt;100 times
faster&lt;/a&gt; than a 5400 RPM hard disk,
so replacing the disk with an SSD makes all the performance problems due to disk
I/O go away. The other components in the system weren’t the bottleneck, so the
system shows huge performance improvements simply by upgrading the hard disk.&lt;/p&gt;

&lt;h2 id=&quot;how-can-i-upgrade-my-own-macbook&quot;&gt;How can I upgrade my own MacBook?&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Buy an SSD. Any SSD that’s 2.5” form factor should be easy to install in
place of the old hard drive.&lt;/li&gt;
  &lt;li&gt;Run a Time Machine backup to an external drive.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.apple.com/en-us/HT201372&quot;&gt;Create a bootable macOS installer&lt;/a&gt;
on a USB stick. We’ll use this to format our new drive and install the OS.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://guides.crucial.com/Guide/MacBook+Pro+13-Inch+Unibody+Mid+2012+SSD+Installation/649&quot;&gt;Replace the hard drive&lt;/a&gt;
with the new SSD.&lt;/li&gt;
  &lt;li&gt;Boot the macOS installer USB stick, and open Disk Utility to format the new hard drive.
Format the disk as “Mac OS Extended (Journaled)”.&lt;/li&gt;
  &lt;li&gt;Now, use your USB stick again to install macOS. The installer will guide you
through restoring your backup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SSDs aren’t terribly expensive these days, so an upgrade like this one could
breathe life into your old laptop for as little as about $30 (up to about $120
depending on your storage needs). That’s a pretty good deal, and could be the
perfect solution if your slow computer’s driving you nuts but you’re not ready
to drop $1,200 on a new MacBook yet.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/2012-macbook-ssd.jpg"/>
 </entry>
 
 <entry>
   <title>Hour of Code 2017</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/12/09/hour-of-code-2017.html" type="text/html" title="Hour of Code 2017"/>
   
   <published>2017-12-09T13:30:00-07:00</published>
   <updated>2017-12-09T13:30:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/12/09/hour-of-code-2017</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/12/09/hour-of-code-2017.html">&lt;p&gt;The &lt;a href=&quot;https://hourofcode.com/us&quot;&gt;Hour of Code&lt;/a&gt; is an annual event to encourage
Computer Science education during &lt;a href=&quot;https://csedweek.org/&quot;&gt;Computer Science Education
Week&lt;/a&gt;. I participated this year by volunteering at an
elementary school, where I spent about an hour introducing the students to
computer programming through &lt;a href=&quot;https://scratch.mit.edu/&quot;&gt;Scratch&lt;/a&gt;. I think
Scratch is a great way to introduce people (both young and old) to programming
because it is simple enough that beginners can get started very quickly, but
also powerful enough to create good-looking games (like this &lt;a href=&quot;https://scratch.mit.edu/projects/17341536/&quot;&gt;Flappy Bird
Clone&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;asteroid-field-a-game&quot;&gt;Asteroid Field (A Game)&lt;/h2&gt;

&lt;p&gt;I designed my own lesson for the Hour of Code 2017. I wanted my lesson to be
centered around designing a simple game. The game needed to be fun to play, but
still exceptionally simple - simple enough that 5th grade students could program
a more-or-less functional copy in roughly an hour. I designed an &lt;a href=&quot;https://scratch.mit.edu/projects/187823663/&quot;&gt;Asteroid Field
Game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, although the game linked above is pretty simple (roughly 60 lines of
Scratch code), I needed to simplify it even more to ensure that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the program was not too complex for 5th-graders, and&lt;/li&gt;
  &lt;li&gt;we could program it in about an hour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To that end, I created an &lt;a href=&quot;https://scratch.mit.edu/projects/188595736/&quot;&gt;even more simple
version&lt;/a&gt; of the same game. This one
is only 20 lines of Scratch code, and some of those (such as the score) are
optional. I felt that programming a game like this was within the reach of 5th
grade students, and I planned the lesson in chunks so that we would finish
&lt;em&gt;something&lt;/em&gt; that resembles the Asteroid Field game, even if we didn’t finish
completely.&lt;/p&gt;

&lt;p&gt;Overall, the lesson was a huge success. The class loved it, and I think many of
them came away from the lesson with a better understanding of &lt;em&gt;what programming
is&lt;/em&gt; than they had going in. Perhaps more importantly, I think that for many kids
this opened a doorway to a new world. I hope that for at least a few students I
sparked an interest in computer science that they will pursue for many years to
come.&lt;/p&gt;

&lt;p&gt;If you’re interested in seeing the lesson, you can check out &lt;a href=&quot;https://docs.google.com/document/d/17zNhIeBrRy4l6vY3fIrG6gye4rsPbf0GaVWZOww-hos/edit?usp=sharing&quot;&gt;the lesson
plan&lt;/a&gt;
and &lt;a href=&quot;https://docs.google.com/presentation/d/1GiAH6giiEN-whpG_N6uPdZZW7DCss6u-5YX6OQr1D4k/edit?usp=sharing&quot;&gt;the
slides&lt;/a&gt;.
The Scratch game and source code, along with some of my other Scratch projects,
can be found on &lt;a href=&quot;https://scratch.mit.edu/users/mkasberg/&quot;&gt;my Scratch profile&lt;/a&gt;.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/hour-of-code-2017.jpg"/>
 </entry>
 
 <entry>
   <title>My First Program</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/12/04/my-first-program.html" type="text/html" title="My First Program"/>
   
   <published>2017-12-04T19:00:00-07:00</published>
   <updated>2017-12-04T19:00:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/12/04/my-first-program</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/12/04/my-first-program.html">&lt;p&gt;On the &lt;a href=&quot;https://stackoverflow.blog/podcasts/&quot;&gt;StackOverflow Podcast&lt;/a&gt;, they
always ask their guests on the show how they got introduced to programming.
Whenever I listen to that segment of the podcast, I always think back to my
first experiences with programming, and how I built on those to get where I am
today. I like the podcast segment a lot, and I think it’s fun to share these
stories. So, in honor of &lt;a href=&quot;https://csedweek.org/&quot;&gt;Computer Science Education
Week&lt;/a&gt;, I’m going to write a blog post about some of my
first programming experiences.&lt;/p&gt;

&lt;h2 id=&quot;learn-to-program-basic&quot;&gt;Learn to Program BASIC&lt;/h2&gt;

&lt;p&gt;I always wanted to learn to program. From a pretty young age, I grasped the idea
that there were people out there who could make computers do whatever they
wanted them to. To me, this idea was enchanting. But I also realized that I had
&lt;em&gt;no idea&lt;/em&gt; how to do this, and didn’t really know anyone who did. I wanted to
learn to program, but didn’t know how to get started.&lt;/p&gt;

&lt;p&gt;I think I noticed &lt;em&gt;Learn to Program BASIC&lt;/em&gt; in the back of a Scholastic book
order. I convinced my mom to buy it for me. (After all, it &lt;em&gt;was&lt;/em&gt; educational
software.) &lt;em&gt;Learn to Program BASIC&lt;/em&gt; was essentially a series of video lessons
for the BASIC programming language.
The software also came with its own BASIC compiler and execution environment, so
it was relatively easy to write and run simple code. The first real program I
ever wrote would have been some kind of “Hello World” program in BASIC,
following one of the first lessons. (A video like &lt;a href=&quot;https://www.youtube.com/watch?v=uBYz9syhNAA&quot;&gt;this
one&lt;/a&gt;. Wow, that’s so horrible. But
still, nostalgia…) Incredibly,
&lt;em&gt;Learn to Program BASIC&lt;/em&gt; still has a &lt;a href=&quot;https://www.amazon.com/Interplays-Learn-Program-Basic-Required/dp/B0048O15RG/ref=sr_1_1?ie=UTF8&amp;amp;qid=1512434786&amp;amp;sr=8-1&amp;amp;keywords=interplay+learn+to+program+basic&quot;&gt;product page on
Amazon&lt;/a&gt;
(although it appears to be sold out).&lt;/p&gt;

&lt;p&gt;I worked through many of the lessons, but ultimately became frustrated. I could
write a lot of simple text-based programs (add two numbers, ask a user’s name,
etc), but never learned enough to create any kind of program on my own that wasn’t
text-based. Perhaps I was still too young to completely grasp some of the more
advanced concepts, but I don’t think I ever programmed anything really
interesting in BASIC. I wanted to learn how to write computer games with real
graphics, and my understanding of BASIC and the programming environment I was
working in wasn’t enough to let me do anything exciting. &lt;em&gt;Learn to Program
BASIC&lt;/em&gt; got me off to a great start, but also left me thirsty for more knowledge.&lt;/p&gt;

&lt;h2 id=&quot;game-maker&quot;&gt;Game Maker&lt;/h2&gt;

&lt;p&gt;As I grew a little older, one of my primary motivations for wanting to
learn to program was to make computer games. Video games get expensive quickly,
and my childhood brain thought that if I simply wrote the game myself, I could play whatever I
wanted, essentially for free. My desire to write video games led me to an
application called &lt;a href=&quot;https://web.archive.org/web/20030623090955/http://gamemaker.nl:80/&quot;&gt;&lt;em&gt;Game
Maker&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Game Maker&lt;/em&gt; was great software (for my 13 year old self), and I used it to make
a couple of games. Back in 2003, it was free. I remember that one of the games I
wrote was a type of “laser tag” game, where multiple players could play on the
same keyboard. One used WASD while the other used the arrow keys. Eventually, I
think I added a 3rd and 4th player using other keyboard keys, to get even more
friends in at once. Game maker produced a Windows executable (.exe) that I could
put on a floppy disk and share with friends. I was really proud of the game I
made, and learned a ton about programming in the process. It worked well, and
the games I made were actually fun to play for a while.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Game Maker&lt;/em&gt; is still available, although it’s changed a lot. It’s now sold as
&lt;a href=&quot;https://www.yoyogames.com/gamemaker&quot;&gt;GameMaker Studio&lt;/a&gt; by YoYo Games.&lt;/p&gt;

&lt;h2 id=&quot;scratch&quot;&gt;Scratch&lt;/h2&gt;

&lt;p&gt;Looking back on what I did with &lt;em&gt;Game Maker&lt;/em&gt;, it’s really similar to what many
kids are introduced to today with &lt;a href=&quot;https://scratch.mit.edu/&quot;&gt;Scratch&lt;/a&gt;. &lt;em&gt;Game
Maker&lt;/em&gt; used it’s own programming language called GML (Game Maker Language).
Using GML, you could essentially program sprites to respond to events. The
language had simple commands (like move 10 left). Scratch is really the same
thing, but it runs in a web browser and you program with blocks. I like to
think that if I was a 10 year old today, I’d be learning to program by writing
games in Scratch. So it’s great to see it gaining popularity in elementary and
middle schools as an introduction to computer science.&lt;/p&gt;

&lt;h2 id=&quot;cs-education-week&quot;&gt;CS Education Week&lt;/h2&gt;

&lt;p&gt;Computer Science Education Week is December 4-10, 2017. What are you doing this
week to support computer science education? I’ll be doing an &lt;a href=&quot;https://hourofcode.com/us&quot;&gt;Hour of
Code&lt;/a&gt; later this week at an elementary school, where
I’ll be teaching one of &lt;a href=&quot;https://scratch.mit.edu/users/mkasberg/&quot;&gt;my own
lessons&lt;/a&gt; in Scratch. I’ve written
several different games in Scratch over the last couple of years, primarily to
use as teaching material. These days, when I write a game in Scratch, I try to
keep the code (and the game) as simple as possible. My goal is to show the kids
I’m teaching that you can make something fun with code that’s simple and easy to
understand. I hope to write more about some of the Scratch lessons I’ve
developed in the near future.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/my-first-program.jpg"/>
 </entry>
 
 <entry>
   <title>Hacktoberfest 2017</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/10/15/hacktoberfest-2017.html" type="text/html" title="Hacktoberfest 2017"/>
   
   <published>2017-10-15T21:28:00-06:00</published>
   <updated>2017-10-15T21:28:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/10/15/hacktoberfest-2017</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/10/15/hacktoberfest-2017.html">&lt;p&gt;&lt;a href=&quot;https://hacktoberfest.digitalocean.com/&quot;&gt;Hacktoberfest&lt;/a&gt; is a project sponsored
by Digital Ocean and Github that encourages contributions to open source
software. Anyone who contributes at least 4 pull requests to open source
projects on Github during the month of October will get a free Hacktoberfest
shirt for their participation.&lt;/p&gt;

&lt;p&gt;This year, I participated in Hacktoberfest for the first time as a project
maintainer.  After learning about the event, I decided to create several issues
in my &lt;a href=&quot;https://mkasberg.github.io/container-immersion/&quot;&gt;Container Immersion&lt;/a&gt;
project (which I &lt;a href=&quot;/blog/2017/02/11/so-you-want-to-learn-docker.html&quot;&gt;wrote
about&lt;/a&gt; in February) with the
Hacktoberfest label, inviting new developers to help out.  Initially, I was
surprised by the quick response of several people willing to help.  This is
probably the best benefit of Hacktoberfest - it creates an environment where
people are actively looking for projects to work on, and projects that normally
wouldn’t receive much attention are given a chance to gain exposure and receive
contributions from the community.&lt;/p&gt;

&lt;!--more--&gt;
&lt;h2 id=&quot;outcomes&quot;&gt;Outcomes&lt;/h2&gt;

&lt;p&gt;Hacktoberfest proved very beneficial for Container Immersion. I focused my
requests for help on styling improvements for the site, and the results are
great! Before Hacktoberfest, Container Immersion’s styling was a little rough around the edges:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/hacktoberfest-2017/Before_Hacktoberfest.jpg&quot; alt=&quot;Container Immersion Before Hacktoberfest
2017&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After Hacktoberfest, the site looks much more polished:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/hacktoberfest-2017/After_Hacktoberfest.jpg&quot; alt=&quot;Container Immersion After Hacktoberfest
2017&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We added a Github ribbon, improved the navigation buttons, improved the style of
code samples, and made the site more mobile-friendly. Each of those improvements
was contributed by someone participating in Hacktoberfest - evidence of
Hacktoberfest’s success at motivating the open source community.&lt;/p&gt;

&lt;h2 id=&quot;insights&quot;&gt;Insights&lt;/h2&gt;

&lt;p&gt;While participating in Hacktoberfest, I learned several lessons that are also
applicable to open source development in general. At the time when I first asked for
contributions by adding the Hacktoberfest label to some of my Github issues, the
documentation for Container Immersion was somewhat lacking. Although the project
had a README, a few of the initial contributors struggled a little bit to get
the site running locally (via Jekyll). It was also not clear to some
contributors what the site’s architecture was - that is, where the HTML and CSS
that Jekyll uses to build the site were located. While things like this might
seem obvious to really experienced developers, they were not obvious to newer
developers who wanted to get their feet wet, and it’s easy for more experienced
developers to overlook problems like this.&lt;/p&gt;

&lt;p&gt;Of course, the remedy is simple. I added additional information to the readme
about how to run the site locally for the first time. I also added a new section
about how the site is built using Jekyll, and where different components of the
site can be found in the project’s directories.&lt;/p&gt;

&lt;h2 id=&quot;open-source-community&quot;&gt;Open Source Community&lt;/h2&gt;

&lt;p&gt;Based on my experience with Hacktoberfest, I think that it’s really important
for open source projects that want to develop a strong community to make it
really easy to set up a development environment and get coding. It can be quite
difficult and intimidating for relatively new developers to contribute to
projects when they don’t receive much guidance from the maintainers. Open source
projects that support and mentor new developers will likely see more
contributions and will be able to build stronger communities. I’ve seen the
benefits of Hacktoberfest on Container Immersion, and I’m sure many other open
source projects are getting a lot of work contributed due to this event. Thank
you &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;Digital Ocean&lt;/a&gt;, for sponsoring open source
events like this one.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/hacktoberfest-2017.jpg"/>
 </entry>
 
 <entry>
   <title>How To: Deploy a Simple Java Web Service with AWS Lambda and API Gateway</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/08/31/how-to-deploy-aws-lambda.html" type="text/html" title="How To: Deploy a Simple Java Web Service with AWS Lambda and API Gateway"/>
   
   <published>2017-08-31T22:15:00-06:00</published>
   <updated>2017-08-31T22:15:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/08/31/how-to-deploy-aws-lambda</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/08/31/how-to-deploy-aws-lambda.html">&lt;p&gt;AWS is incredible technology, making it easy for anyone to deploy a web service
without needing to worry about managing servers. In this quick tutorial, we’re
going to see how to deploy a simple web service to AWS Lambda and make that
service available on the internet with AWS API Gateway. Let’s get started.&lt;/p&gt;

&lt;h2 id=&quot;start-with-a-seed&quot;&gt;Start with a Seed&lt;/h2&gt;

&lt;p&gt;AWS supports Node.js, Python, Java, and C#. For this tutorial, we’ll be using
Java. As an example, we’re going to write a web service that can add two numbers
and return the result. What it does isn’t important - we’re focusing on the
setup here. The easiest way to start is by cloning a seed project to put the
framework in place. We’ll use my &lt;a href=&quot;https://github.com/mkasberg/aws-lambda-java-template&quot;&gt;AWS Java
Seed&lt;/a&gt;, available on
Github. (&lt;em&gt;Warning: As of 2020, this seed is a couple years old and hasn’t been
updated. Consider finding a newer seed, or at least upgrade to newer versions of
the software after getting started.&lt;/em&gt;)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git clone git@github.com:mkasberg/aws-lambda-java-template.git 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point, if you want, you can remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin&lt;/code&gt; remote from your
repository clone, since we won’t be pushing our code back to it (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git remote
remove origin&lt;/code&gt;).&lt;/p&gt;

&lt;h2 id=&quot;write-some-code&quot;&gt;Write Some Code&lt;/h2&gt;

&lt;p&gt;Open the project in your favorite Java IDE (I’m using
&lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ&lt;/a&gt;). The project uses Maven for
dependency management (see pom.xml), and already includes the dependencies you
need to get started with AWS Lambda. Let’s run the compile target now (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn
compile&lt;/code&gt;). The code should compile without errors. Let’s also run the tests
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn test&lt;/code&gt;). The API seed comes with a single test, which should pass.&lt;/p&gt;

&lt;p&gt;Now let’s dig into the code a bit. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt; class is a great place to
start. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application#handleRequest()&lt;/code&gt; is the method that will be called when our
Lambda service receives a request (forwarded from API gateway). We can parse the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inputStream&lt;/code&gt; into a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSONObject&lt;/code&gt;, and the URL parameters will be under the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queryStringParameters&lt;/code&gt; key. Let’s write some code to add the numbers that are
sent to our service on URL parameters &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;. Here’s our new
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handleRequest()&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;InputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OutputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requestJson&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONTokener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;responseJson&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;responseBody&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Headers&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headerJson&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;headerJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requestJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getJSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;queryStringParameters&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;responseBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;result&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;responseJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;statusCode&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;200&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;JSONException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;responseBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;responseBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Send numbers to be added on URL parameters a &amp;amp; b.&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;responseJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;statusCode&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;400&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Assemble the response.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;responseJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;responseBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;responseJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headerJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;OutputStreamWriter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OutputStreamWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;responseJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unit tests are an important part of a codebase, and this tutorial wouldn’t be
complete without tests for the code we modified. If we run our unit tests now,
they’ll fail. It is easy for us to write unit tests to ensure that our code will
work when it comes time to deploy. We’ll unit test the public &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handleRequest()&lt;/code&gt;
method by mocking the input and output streams with byte array streams. We’ll
also test a request that’s missing some parameters. Again, the framework to get
this started comes with the seed project, so we only have to change a little
code.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testHandleRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;InputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ByteArrayInputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;{\&quot;queryStringParameters\&quot;:{\&quot;a\&quot;:2,\&quot;b\&quot;:3}}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ByteArrayOutputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaResponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ByteArrayOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lambdaRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byteArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toByteArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;{\&quot;headers\&quot;:{\&quot;Content-Type\&quot;:\&quot;application/json\&quot;},\&quot;body\&quot;:\&quot;{\\\&quot;result\\\&quot;:5}\&quot;,\&quot;statusCode\&quot;:\&quot;200\&quot;}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byteArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testBadRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;InputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ByteArrayInputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;{\&quot;queryStringParameters\&quot;:{\&quot;a\&quot;:2}}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ByteArrayOutputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaResponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ByteArrayOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lambdaRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byteArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambdaResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toByteArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;{\&quot;headers\&quot;:{\&quot;Content-Type\&quot;:\&quot;application/json\&quot;},\&quot;body\&quot;:\&quot;{\\\&quot;message\\\&quot;:\\\&quot;Send numbers to be added on URL parameters a &amp;amp; b.\\\&quot;,\\\&quot;status\\\&quot;:\\\&quot;error\\\&quot;}\&quot;,\&quot;statusCode\&quot;:\&quot;400\&quot;}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byteArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;package-and-deploy-the-lambda-application&quot;&gt;Package and Deploy the Lambda Application&lt;/h2&gt;

&lt;p&gt;Our tests are passing! Let’s package the application into a deployable jar. This
is easy - just use the Maven package step (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn package&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;After creating the jar, deploy it to AWS:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Sign in to the &lt;a href=&quot;https://console.aws.amazon.com/console/home&quot;&gt;AWS Console&lt;/a&gt; and
navigate to &lt;a href=&quot;https://console.aws.amazon.com/lambda/home?region=us-east-1&quot;&gt;AWS
Lambda&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Hit the “Create Function” button.&lt;/li&gt;
  &lt;li&gt;Choose “Author from Scratch”.&lt;/li&gt;
  &lt;li&gt;Add a trigger, and select “API Gateway” as the trigger mechanism.&lt;/li&gt;
  &lt;li&gt;Name your API Gateway. Leave the deployment stage at “prod”, and select an
“Open” security mechanism for now.&lt;/li&gt;
  &lt;li&gt;Give your new Lambda function a name (like “Addition Lambda”) and
description. Make sure to choose “Java 8” as the runtime.&lt;/li&gt;
  &lt;li&gt;Use the upload button to upload a jar, and upload
“aws-lambda-java-template-0.0.1.jar” (or similar if you renamed your project)
from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target&lt;/code&gt; folder.&lt;/li&gt;
  &lt;li&gt;Enter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at.goosefraba.aws.lambda.template.Application&lt;/code&gt; (or similar if you
renamed your project) as the handler.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create a new role for your lambda function from an existing template. “Basic
Edge Lambda permissions” should be sufficient.
  &lt;img src=&quot;/images/aws-lambda-howto/BasicInfo.png&quot; alt=&quot;AWS Lambda Configuration&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Hit the “Create Function” button to finish creating your function.&lt;/li&gt;
  &lt;li&gt;That’s it! You should see a public URL under your API Gateway. Let’s use this
URL to test our function.
  &lt;img src=&quot;/images/aws-lambda-howto/GatewayConfig.png&quot; alt=&quot;AWS Lambda API Config&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can see our function work by visiting the public URL and providing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; as URL parameters.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/aws-lambda-howto/API-Test.png&quot; alt=&quot;Running API&quot; /&gt;&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/how-to-deploy-aws-lambda.png"/>
 </entry>
 
 <entry>
   <title>Bufferbloat?</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/06/21/bufferbloat.html" type="text/html" title="Bufferbloat?"/>
   
   <published>2017-06-21T21:27:00-06:00</published>
   <updated>2017-06-21T21:27:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/06/21/bufferbloat</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/06/21/bufferbloat.html">&lt;p&gt;Have you ever noticed your internet connection get slower when uploading a large
file? I have, but I never put too much thought into it or realized I could do
anything about it… Until I stumbled upon
&lt;a href=&quot;https://medium.com/speedtest-by-ookla/engineer-maximizes-internet-speed-story-c3ec0e86f37a&quot;&gt;this&lt;/a&gt;
blog post from a systems engineer at Ookla (the company behind
&lt;a href=&quot;https://www.speedtest.net/&quot;&gt;speedtest.net&lt;/a&gt;). As it turns out, there’s a term for
this (bufferbloat), and this is an easily solvable problem.&lt;/p&gt;

&lt;h2 id=&quot;what-is-bufferbloat&quot;&gt;What is Bufferbloat&lt;/h2&gt;

&lt;p&gt;As Brennen explains in his blog,
&lt;a href=&quot;https://en.wikipedia.org/wiki/Bufferbloat&quot;&gt;bufferbloat&lt;/a&gt; is a problem that
occurs when a router’s buffers fill up due to a slow connection (like the slower
upload connection most ISPs provide). The rate of data going into the router is
faster than the rate of data going out, the buffers get full, and the whole
thing slows down. The solution is pretty simple - just send data in a little
slower than the fastest rate it can leave.&lt;/p&gt;

&lt;h2 id=&quot;do-i-have-bufferbloat&quot;&gt;Do I Have Bufferbloat?&lt;/h2&gt;

&lt;p&gt;After stumbling across Brennen’s blog, I wanted to check if my own internet
connection was affected by this problem. As it turns out, this is easy to test:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ping google.com&lt;/code&gt; and leave it running.&lt;/li&gt;
  &lt;li&gt;Run &lt;a href=&quot;https://www.speedtest.net/&quot;&gt;speedtest.net&lt;/a&gt; in a browser. Pay particular
attention to when the upload test starts.&lt;/li&gt;
  &lt;li&gt;Watch ping, and look for a spike in latency.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your response times go up during the upload, it’s bufferbloat! In my own
results, I saw a roughly 2,650% increase (\( \frac{330 - 12}{12} \)) in ping response time during upload.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/bufferbloat/slow-ping.png&quot; alt=&quot;Ping output&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;fixing-bufferbloat&quot;&gt;Fixing Bufferbloat&lt;/h2&gt;

&lt;p&gt;Brennen details how he fixed bufferbloat on his network, but I wanted to solve
the problem without shelling out for an expensive router if I could. Luckily, I
was already using a Linksys E900 wireless router with DD-WRT installed. DD-WRT
supports some basic QoS settings, so I went into the admin panel and set up QoS
to limit my WAN uploads to 4,720 kbps, or roughly 80% of what
&lt;a href=&quot;https://www.speedtest.net/&quot;&gt;speedtest.net&lt;/a&gt; says my max upload speed is.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/bufferbloat/dd-wrt-settings.png&quot; alt=&quot;DD-WRT settings&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After applying the QoS settings, I ran the ping test again and my results were
unaffected by the upload. Success! I checked the download speeds to, just to
confirm my settings changes did not make them significantly worse.&lt;/p&gt;

&lt;p&gt;So that’s it. A simple rate limit on my router fixed the problem. My upload rate
might be marginally slower due to my own artificial limit, &lt;em&gt;but my download
speed will not be impacted&lt;/em&gt;. That is, &lt;em&gt;I can continue browsing the internet with
normal, fast speeds even while uploading a large file&lt;/em&gt;. That’s a trade-off I’m
happy to make.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/bufferbloat.png"/>
 </entry>
 
 <entry>
   <title>Why CI?</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/06/19/why-ci.html" type="text/html" title="Why CI?"/>
   
   <published>2017-06-19T20:42:00-06:00</published>
   <updated>2017-06-19T20:42:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/06/19/why-ci</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/06/19/why-ci.html">&lt;p&gt;In recent years (and in some cases, for many years), Continuous Integration (CI)
has been taking off. Almost any open source project you look at is using some
kind of CI tool like &lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt;,
&lt;a href=&quot;https://travis-ci.org/&quot;&gt;TravisCI&lt;/a&gt;, or &lt;a href=&quot;https://codeship.com/&quot;&gt;CodeShip&lt;/a&gt;. In the
simplest cast, the CI server is just running unit tests. In more complex cases,
the CI server runs unit and integration tests, produces a build, and maybe even
deploys the software.&lt;/p&gt;

&lt;p&gt;Some people are quicker than others to add CI tooling to their own workflow. To
see the value in CI tools like these, you need to understand the problem they
are solving. Fundamentally, CI tools are solutions for &lt;em&gt;risk management&lt;/em&gt;. That
is, they help engineers reduce the risk associated with developing and deploying
code.&lt;/p&gt;

&lt;p&gt;There are several different approaches to risk management. Perhaps the most
basic is, “if it ain’t broken, don’t fix it.” In other words, if the code is
stable and you don’t change it, you don’t need to take on any additional risk.
And in that case, you probably don’t need CI either since you won’t be changing
the code very often.  This methodology works pretty well… Until it doesn’t.
Essentially, it will continue to work well as long as the software doesn’t need
to change (too much). Unfortunately, it breaks down when the software &lt;em&gt;does&lt;/em&gt;
need to change. And in the world of modern web applications, the software
&lt;em&gt;always&lt;/em&gt; needs to change. There’s always more traffic coming, APIs changing, or
a competitor who’s innovating. Thus, avoiding change in order to avoid risk is
not a viable strategy. In fact, after avoiding change for too long you start
&lt;em&gt;adding&lt;/em&gt; risk as the software “falls behind”, and changes become more difficult
and complex.&lt;/p&gt;

&lt;p&gt;Continuous Integration, essentially, is the solution to this problem. A CI
pipeline is designed to enable rapid changes in the safest way possible. It does
this by preventing code from being merged without passing unit tests. It does
this by producing a deployable build in a repeatable manner. It does this by
testing a build and ensuring that there are no regressions in functionality. And
it does this by deploying the build to production in a safe and repeatable
manner. A CI pipeline provides a pathway for developers to get their code into
production in the safest manner possible. In doing so, it provides developers a
way to quickly deploy changes to a code base with a minimal amount of risk.&lt;/p&gt;

&lt;p&gt;When developers are afraid to make changes to a codebase, the
code rots. Rotten code is more difficult, time-consuming, and risky to work
with. Good CI prevents code rot by reducing the risk associated with any code
change, which allows developers to make improvements and add features to a
codebase with minimal risk, and therefore minimal fear. This is incredibly
valuable, both to open source projects and to organizations.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/why-ci.jpg"/>
 </entry>
 
 <entry>
   <title>Fitbit Surge vs. Garmin Forerunner 35</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/06/18/fitbit-surge-vs-garmin-forerunner-35.html" type="text/html" title="Fitbit Surge vs. Garmin Forerunner 35"/>
   
   <published>2017-06-18T21:40:00-06:00</published>
   <updated>2017-06-18T21:40:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/06/18/fitbit-surge-vs-garmin-forerunner-35</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/06/18/fitbit-surge-vs-garmin-forerunner-35.html">&lt;p&gt;I was recently shopping around for a new GPS running watch, and I tried both the
&lt;strong&gt;Fitbit Surge&lt;/strong&gt; and the &lt;strong&gt;Garmin Forerunner 35&lt;/strong&gt;. These devices are both good GPS
watches with a similar feature set, so I thought it would be worthwhile to see
how they stack up against each other.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;2020 Update:&lt;/strong&gt; 3 years after writing this blog post, I think both these
watches are &lt;em&gt;a little&lt;/em&gt; dated. I still think the Forerunner 35 is a great watch
if you can find one somewhere (even used) - in fact, &lt;strong&gt;you can probably get this
watch very cheaply today&lt;/strong&gt;, and it’s really still a great watch! If you’re
looking for something a little more modern, try the Forerunner 45. It’s similar
to the Forerunner 35, but newer. And if you want something with a lot more
features, check out the Garmin fēnix series. I have a new fēnix 5s and I love
it!&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;fitbit-surge&quot;&gt;Fitbit Surge&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/fitbit-vs-garmin/fitbit.jpg&quot; alt=&quot;Fitbit Surge&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Fitbit Surge is a solid choice for a GPS watch. As I write this, its MSRP is
$249, but you can find it for about $150, new, on sites like eBay. At that price
point, it is really competitive.&lt;/p&gt;

&lt;p&gt;The Surge comes with all the features you’d expect on a GPS watch. It tracks
your distance for a variety of activities including running, cycling, and
hiking. It also tracks heart rate, and I found both GPS distance and heart rate
to be reasonably accurate.&lt;/p&gt;

&lt;p&gt;Now, Fitbit (the company) grew up making all-day activity tracking devices, and
that heritage shows in the Fitbit Surge. The device can track your activity all
day, and excels at syncing this information to the Fitbit app on your phone.
This is where the Fitbit really shines - the app is easy to use and navigate.
It’s polished.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Good&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Syncs to Fitbit app &amp;amp; website.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Fitbit app &amp;amp; website look nice and are easy to use.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;In run mode, you can see total distance, total time, and current pace. Scroll
   to see heart rate, average pace, etc.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Phone notifications for texts and calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Bad&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;Missing more advanced running features like intervals or lap recall.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;Does not support notifications other than text and calls. (No calendar
   notifications.)&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;Can&apos;t go back to look at recent notifications.&lt;/li&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;Can&apos;t go back to look at recent runs (on the watch).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;garmin-forerunner-35&quot;&gt;Garmin Forerunner 35&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/fitbit-vs-garmin/garmin.jpg&quot; alt=&quot;Garmin Forerunner 35&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Forerunner 35 is definitely a competitor for the Fitbit Surge. Officially priced (as I
write this) at $199, it can be found for about $170 (new). The Forerunner 35
lacks some of the features of the Surge, but also brings some additional
features that make it stand out.&lt;/p&gt;

&lt;p&gt;While Fitbit grew up making activity trackers, Garmin grew up making GPS
devices. Garmin has been making GPS devices for runners for a lot longer than
Fitbit has, and that shows in the Forerunner. For example, the Forerunner 35
lets you customize the screens you see during your activity with any of roughly
a dozen different stats. Things like distance, pace, average pace, time, etc.
The Forerunner does a better job managing things like interval workouts -
largely due to the fact that it has a better interface in run-mode that provides
more information than the Surge does. You can even let the watch run your
workout for you, as described
&lt;a href=&quot;/blog/2020/06/13/how-to-use-garmin-workouts.html&quot;&gt;here&lt;/a&gt;
(although on the Forerunner 35 you need to set it up on the watch instead of the
app). The Forerunner 35 is also a smaller device than the surge, and was
therefore more comfortable to wear.&lt;/p&gt;

&lt;p&gt;The Forerunner 35 is a runner’s GPS, and with my background as a college runner,
I appreciated all of the features it offered. Still, it has room for
improvement. The Garmin phone app did not feel quite as polished as the Fitbit
app. Garmin Connect (the phone app and web interface) provides more data
than Fitbit but (as a result?) feels clunkier and harder to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Good&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Syncs to Garmin Connect app &amp;amp; website.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Customizable screens in run mode - see any stat wherever you want.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Support for interval training.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Supports most phone notifications (text, call, calendar, etc).&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;Small, comfortable, lightweight.&lt;/li&gt;
  &lt;li class=&quot;list-image-plus&quot;&gt;You can go back to look at run history on the watch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Bad&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li class=&quot;list-image-minus&quot;&gt;Garmin Connect app &amp;amp; website are not as easy to use as Fitbit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Both the Surge and the Forerunner 35 are good devices. I’d give both of them 4/5
stars. If you want a device that has a really clean phone app, a more
user-friendly interface, or all-day activity tracking, the Surge is definitely
the way to go. On the other hand, if you’d prefer to have a more customizable
run-mode interface, or if you want more advanced running features like
customizable workouts, you’ll be happier with the Forerunner once you figure out
how to navigate the app. It’s also worth mentioning that both of these devices
support syncing to &lt;a href=&quot;https://www.strava.com&quot;&gt;Strava&lt;/a&gt;. This is an important
feature for me and is a huge plus for both of them.&lt;/p&gt;

&lt;p&gt;(Update/Full disclosure: I’m now employed by Strava, although I wasn’t at the
time I originally wrote this blog. Opinions in this blog are my own.)&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Reviews"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/fitbit-vs-garmin.jpg"/>
 </entry>
 
 <entry>
   <title>When&apos;s My Code Going Out?</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/04/30/whens-my-code-going-out.html" type="text/html" title="When&apos;s My Code Going Out?"/>
   
   <published>2017-04-30T05:22:00-06:00</published>
   <updated>2017-04-30T05:22:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/04/30/whens-my-code-going-out</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/04/30/whens-my-code-going-out.html">&lt;p&gt;At &lt;a href=&quot;https://www.spotx.tv&quot;&gt;SpotX&lt;/a&gt;, we manage our codebase with Git. Our commits
flow from our develop branch to production, with a code-freeze branch
in-between. Like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/whens-my-code-going-out/git_dev-freeze-prod.png&quot; alt=&quot;Git branching
diagram.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With any web application, it’s really important for developers to know when
their code is going out. So they want to know which branch it’s on. To help with
this, I created a few simple git aliases that show recent commits a developer
has made in any branch. Here are the commands to create them:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git config --global alias.my-dev &apos;rev-list origin/develop ^origin/freeze --author=mike@mikekasberg.com --pretty&apos;
$ git config --global alias.my-freeze &apos;rev-list origin/freeze ^origin/prod --author=mike@mikekasberg.com --pretty&apos;
$ git config --global alias.my-prod &apos;rev-list origin/prod --author=mike@mikekasberg.com --pretty --after=&quot;14 days ago&quot;&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using them is easy!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git fetch
$ git my-freeze
commit bebba3afb108719bbdc500cb3dce50ffc062f060
Author: Mike Kasberg &amp;lt;mike@mikekasberg.com&amp;gt;
Date:   Sun Apr 30 13:15:15 2017 -0600

    Write more code... Make another commit

commit 00a54a1d6cc80a7103389bc813c6d76c2014cd85
Author: Mike Kasberg &amp;lt;mike@mikekasberg.com&amp;gt;
Date:   Sat Apr 29 11:16:44 2017 -0600

    Write some code... Make a commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a really simple way to use a git alias to make branch management a
little easier. With slight modification, it will help with pretty much any
branching strategy - for example, you could get a list of commits written by you
on a feature branch that are not yet in master:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git config --global alias.my-feature-commits &apos;rev-list origin/feature ^origin/master --author=mike@mikekasberg.com --pretty&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With a little customization, you can make an alias like this to help out with
your own projects.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/whens-my-code-going-out.png"/>
 </entry>
 
 <entry>
   <title>How I Manage Passwords with KeePass</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/03/25/how-i-manage-passwords-with-keepass.html" type="text/html" title="How I Manage Passwords with KeePass"/>
   
   <published>2017-03-25T10:05:00-06:00</published>
   <updated>2017-03-25T10:05:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/03/25/how-i-manage-passwords-with-keepass</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/03/25/how-i-manage-passwords-with-keepass.html">&lt;p&gt;Several weeks ago, I wrote a &lt;a href=&quot;https://www.mikekasberg.com/blog/2017/03/10/dealing-with-passwords.html&quot;&gt;blog
post&lt;/a&gt;
about how horrible it is to have to deal with the various password restrictions
websites use. Of course, that post was influenced by Jeff Atwood’s post,
&lt;a href=&quot;https://blog.codinghorror.com/password-rules-are-bullshit/&quot;&gt;Password Rules are
Bullshit&lt;/a&gt;. While
writing the post, I did some research on what makes a good password. And after
writing it, I spent several weeks thinking about my own password management
strategies.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;July 2020 Update&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;I wrote this blog post back in 2017, and a little bit has changed since then!
I used to manage my passwords with KeePass, but more recently I&apos;ve been using &lt;a href=&quot;https://bitwarden.com&quot;&gt;Bitwarden&lt;/a&gt;. My reasoning for using a password
manager (described below) is largely unchanged &amp;ndash; I simply like Bitwarden
better than KeePassXC now. You can read my comparison between Bitwarden and
KeePassXC &lt;a href=&quot;/blog/2018/11/20/keepass-vs-bitwarden.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Really, the password problem boils down to this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The longer a password is, the more secure it is. Ideally 16 characters or
longer.&lt;/li&gt;
  &lt;li&gt;Don’t use the same password on different websites.&lt;/li&gt;
  &lt;li&gt;A password must conform to the rules of the website it’s used for
(length limits, character requirements, etc).&lt;/li&gt;
  &lt;li&gt;Oh, and humans can’t remember 20 different passwords that change every 6
months.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a world where I have accounts to dozens of different websites that I’m
actively using, a password manager really is the only good solution (other than
Sign In with Google) anyone’s come up with so far. Although I’ve spent years
avoiding any kind of password manager (putting all your passwords in one place
just seems like a bad idea (it is a single point of failure, after all)), I
think I’m finally giving in. &lt;em&gt;Sigh&lt;/em&gt;. (Really though, if you do it right, the
benefits outweigh the risks.) After researching the different password manager
options available, I settled on &lt;a href=&quot;https://keepass.info/&quot;&gt;KeePass&lt;/a&gt; because:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It’s open-source.&lt;/li&gt;
  &lt;li&gt;It doesn’t rely on browser extensions (which appear to be &lt;a href=&quot;https://twitter.com/taviso/status/843965519371812864&quot;&gt;relatively
vulnerable&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;It works on pretty much anything (I need something that supports both Linux
and Android).&lt;/li&gt;
  &lt;li&gt;It can sync via Dropbox.&lt;/li&gt;
  &lt;li&gt;It can generate pseudo-random passwords.&lt;/li&gt;
  &lt;li&gt;It stores passwords in an encrypted file, in an open format, that I own &amp;amp; control.&lt;/li&gt;
  &lt;li&gt;It’s free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I put some thought into getting everything set up nicely, and tried out a couple
different applications. KeePass is open source, and there are several different
options for post platforms listed on its &lt;a href=&quot;https://keepass.info/download.html&quot;&gt;download
page&lt;/a&gt;. Here’s what I’m using:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://keepassxc.org/&quot;&gt;KeePassXC&lt;/a&gt; has a much nicer interface on Linux than
the original KeePass (and reads/writes the same file format).&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=keepass2android.keepass2android&amp;amp;hl=en&quot;&gt;Keepass2Android&lt;/a&gt;
has a really rich feature set (including fingerprint unlock) and syncs to
Dropbox.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.dropbox.com/&quot;&gt;Dropbox&lt;/a&gt; to sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get started, I installed KeePassXC on my computer and created a new password
database file in my Dropbox folder. I’m adding passwords to it (and changing
them to new, pseudo-random passwords) as I visit the websites I use. After a
couple weeks of this, most of my passwords will be in KeePass!&lt;/p&gt;

&lt;p&gt;That one website that requires your password to be 12-14 characters with at
least one letter and one number but no symbols? Handled. Somebody got hacked?
OK, just generate a new password. Nothing new to remember. Password strength? 24
characters of pseudo-random letters and symbols, where websites allow it. New
password every 6 months? Not a big deal. I think this is going to work well.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Technology"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/password-keys.jpg"/>
 </entry>
 
 <entry>
   <title>Chasing Bugs: PHPUnit Hides Errors</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/03/18/phpunit-hides-errors.html" type="text/html" title="Chasing Bugs: PHPUnit Hides Errors"/>
   
   <published>2017-03-18T10:02:00-06:00</published>
   <updated>2017-03-18T10:02:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/03/18/phpunit-hides-errors</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/03/18/phpunit-hides-errors.html">&lt;p&gt;I love stories about hunting down interesting bugs. I find things like &lt;a href=&quot;https://arstechnica.com/information-technology/2016/03/rage-quit-coder-unpublished-17-lines-of-javascript-and-broke-the-internet/&quot;&gt;left-pad
breaking the
internet&lt;/a&gt;,
&lt;a href=&quot;https://bugs.chromium.org/p/project-zero/issues/detail?id=1139&quot;&gt;cloudbleed&lt;/a&gt;,
and the &lt;a href=&quot;https://www.eveonline.com/news/view/sleeping-beauty&quot;&gt;sleep bug in Eve
Online&lt;/a&gt;
extraordinarily interesting.&lt;/p&gt;

&lt;h2 id=&quot;the-bug&quot;&gt;The Bug&lt;/h2&gt;

&lt;p&gt;Recently, I went on my own bug hunt. I’d seen some strange behavior in our
PHPUnit test suite. The tests were passing when run normally, but certain tests
would consistently fail with process isolation enabled. The behavior was similar
to what is shown below, which is a from a simple test suite I made to reproduce
the problem:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkasberg$ phpunit .
PHPUnit 5.7.15 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 124 ms, Memory: 8.00MB

OK (3 tests, 3 assertions)

mkasberg$ phpunit --process-isolation .
PHPUnit 5.7.15 by Sebastian Bergmann and contributors.

..E                                                                 3 / 3 (100%)

Time: 421 ms, Memory: 8.00MB

There was 1 error:

1) ThingTest::testTheThing
array_keys() expects parameter 1 to be array, integer given

/home/mkasberg/Desktop/php-test/ThingTest.php:7

ERRORS!
Tests: 3, Assertions: 2, Errors: 1.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Uh-oh. That’s not good. I mean, the failure alone is bad enough, but the fact
that it only shows up with process isolation turned on is particularly
troubling. Process isolation starts a new PHP process for each unit test. If
tests are passing &lt;em&gt;without&lt;/em&gt; process isolation and failing &lt;em&gt;with&lt;/em&gt; process
isolation, it implies that the tests are changing global state. These type of
bugs are notoriously hard to track down.&lt;/p&gt;

&lt;h2 id=&quot;the-search-begins&quot;&gt;The Search Begins&lt;/h2&gt;

&lt;p&gt;I started by simply debugging one of the failing tests. And I found rather
quickly that the error (shown with process isolation enabled) was correct. Very
simply, a function was being called with incorrect parameters. Now, that’s easy
to fix, but I didn’t &lt;em&gt;want&lt;/em&gt; to fix it yet. Before I could fix it, I had to
understand why it appeared to pass when run as part of a larger test suite.
A test suite that hides failures is a &lt;em&gt;really scary thing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The scale of the problem I faced was overwhelming. The results shown above are
from a simple test suite designed to reproduce the problem. The suite where I
originally discovered the problem had more than 1000 different tests, more than
20 of which would fail with process isolation enabled. Figuring out which one of
the 1000+ tests was affecting the global state was a daunting task (because it
could literally be any of them).&lt;/p&gt;

&lt;h2 id=&quot;needle-in-a-haystack&quot;&gt;Needle in a Haystack&lt;/h2&gt;

&lt;p&gt;My first step was to reduce the size of the problem. Disable as many test cases
as possible while still producing the behavior I originally saw. Basically, I
turned off different parts of the test suite until I figured out exactly which
combination of tests was causing the problem. This is a bit strange - imagine, I have a
test suite with more than 1000 tests that should be producing roughly 20 errors, but isn’t.
And I’m turning off entire chunks of the test suite, hoping that I still don’t
see any errors even though I should. Eventually, I was able to reduce the problem to 3
tests.&lt;/p&gt;

&lt;p&gt;One of the tests was the test that is supposed to fail. The other two tests were
required to run before the failing test to reproduce the behavior that hides the
error. If I turned off either of the other two tests, the error would be shown.
These two tests were somehow affecting the global state of the PHP process. I
created a
&lt;a href=&quot;https://gist.github.com/mkasberg/ce7611c602e5f445b730493577b9c8e5&quot;&gt;gist&lt;/a&gt; on
Github that shows my own (simplified) reproduction of the problem.&lt;/p&gt;

&lt;h2 id=&quot;finding-the-bug&quot;&gt;Finding the Bug&lt;/h2&gt;

&lt;p&gt;At this point, although I knew &lt;em&gt;which&lt;/em&gt; tests were causing the problem, I still
didn’t know &lt;em&gt;why&lt;/em&gt;. In the codebase where I first discovered the problem, the
problematic function call was buried 15 layers down the call stack. To find it,
I took the same approach that I took to finding the 3 problematic tests. Comment
out large chunks of code ’til something changes. If I comment out a large chunk
of code and the error shows up because of it, I know that something in that
chunk is causing the problem. I didn’t care if I broke any of the other tests
along the way - I just needed to find out which line of code was preventing
PHPUnit from showing the correct error message.&lt;/p&gt;

&lt;p&gt;After a bit of searching, I found it. And after finding it, it made perfect
sense. Our code was calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_error_handler()&lt;/code&gt; to do some custom logging with
errors. In a production environment, our custom error handler would be
configured to send error messages to certain places, or act on them in other
ways. In PHPUnit, none of this config was loaded. So it would eat error messages
&lt;em&gt;without reporting them anywhere&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The Solution&lt;/h2&gt;

&lt;p&gt;If I was writing all of this from scratch, I’d probably solve this problem with
dependency injection. In production, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_error_handler()&lt;/code&gt; would actually be
called. And in unit tests, I’d use a mock that would never actually call the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_error_handler()&lt;/code&gt; function. But I wasn’t writing this from scratch. This
error handler was in a very old part of a very large codebase. I needed to be
&lt;em&gt;sure&lt;/em&gt; that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_error_handler()&lt;/code&gt; would be called 100% of the time in
production. And, for fear of running into the same problem again, I needed to be
&lt;em&gt;sure&lt;/em&gt; that it would never be called inside a unit test. The solution I came up
with is ugly. But it &lt;em&gt;always works&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;isset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GLOBALS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;argv&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;strpos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GLOBALS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;argv&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;phpunit&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;set_error_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The biggest lesson I learned from this experience is really just reinforcing
something I already knew. PHP has several functions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ini_set()&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_error_handler()&lt;/code&gt;, etc. that can affect the global state of a PHP process.
If you can’t avoid using these functions entirely, at least isolate them in your
code base so they’re not inter-mingled with other logic in your code.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Run your PHPUnit test suite with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--process-isolation&lt;/code&gt; occasionally. It’s
slower, but it might show you problems you didn’t otherwise know about.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/chasing-bugs-phpunit-hides-errors.png"/>
 </entry>
 
 <entry>
   <title>Dealing with Passwords</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/03/10/dealing-with-passwords.html" type="text/html" title="Dealing with Passwords"/>
   
   <published>2017-03-10T11:08:00-07:00</published>
   <updated>2017-03-10T11:08:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/03/10/dealing-with-passwords</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/03/10/dealing-with-passwords.html">&lt;p&gt;Jeff Atwood wrote a
&lt;a href=&quot;https://blog.codinghorror.com/password-rules-are-bullshit/&quot;&gt;post&lt;/a&gt; on Coding
Horror today calling out bullshit password rules. And he’s dead on. Password
rules, as most sites implement them today, are &lt;strong&gt;not&lt;/strong&gt; improving security. They
might actually be hurting it. Even
&lt;a href=&quot;https://nakedsecurity.sophos.com/2016/08/18/nists-new-password-rules-what-you-need-to-know/&quot;&gt;NIST&lt;/a&gt;
agrees.&lt;/p&gt;

&lt;p&gt;Personally, I agree with everything Jeff says. He makes several really
well-researched points. But there’s one particular topic he did &lt;em&gt;not&lt;/em&gt;
mention that I want to bring to attention. &lt;strong&gt;Password expiration is bad&lt;/strong&gt; (within
reason). I’ve heard of (and, unfortunately, used) services that expire passwords
as frequently as every 3 months. That’s just stupid. Seriously. Sometimes I
don’t even visit a website that frequently! Why does it need to expire that
soon? What, they store their passwords in MD5 and expect to be hacked 4 times a
year? There’s no reason to expire passwords that frequently. Moreover, it’s
actually harmful - what do most people do when their password expires so often
they can’t remember it? &lt;em&gt;They write it down on a sticky note next to their
monitor.&lt;/em&gt; Yeah, real secure.&lt;/p&gt;

&lt;p&gt;Really, the only time a user should &lt;em&gt;need&lt;/em&gt; to change their password is if a
website is hacked and a database is compromised. If your database hasn’t been
compromised and you make your users change their password on a regular time
interval, you’re providing a bad user experience with no real benefit for your
users.&lt;/p&gt;

&lt;p&gt;OK. Now that that’s off my chest, let’s talk about solutions. As a user of
websites with vastly different password rules, your options are somewhat limited
and they kind of suck.&lt;/p&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;p&gt;&lt;b&gt;July 2020 Update&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;I originally wrote this blog post back in 2017! I&apos;ve left most of it as it
was originally written because it&apos;s still accurate, but my personal views on
password managers have evolved a bit since I wrote this. I&apos;d now highly
recommend using &lt;a href=&quot;https://bitwarden.com/&quot;&gt;Bitwarden&lt;/a&gt; to manage your
passwords.  Read about why in my posts &lt;a href=&quot;/blog/2017/03/25/how-i-manage-passwords-with-keepass.html&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;/blog/2018/11/20/keepass-vs-bitwarden.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;what-makes-a-password-secure&quot;&gt;What Makes a Password Secure?&lt;/h2&gt;

&lt;h3 id=&quot;length&quot;&gt;Length&lt;/h3&gt;

&lt;p&gt;As it turns out, password length is probably the biggest factor in making your
password more secure. &lt;a href=&quot;https://blog.codinghorror.com/password-rules-are-bullshit/&quot;&gt;Jeff’s post&lt;/a&gt; has
all the data to back this up. Today, lots of computing power is easily available
with things like Amazon EC2 and GPU parallel processing. Passwords around 8
characters in length can be cracked in minutes. &lt;em&gt;The best weapon against
these attacks is password length&lt;/em&gt; because each additional character increases
the number of possible passwords &lt;em&gt;exponentially&lt;/em&gt;. A “secure” password would be
roughly 14 characters or longer.&lt;/p&gt;

&lt;h3 id=&quot;dont-use-stupid-passwords&quot;&gt;Don’t Use Stupid Passwords&lt;/h3&gt;

&lt;p&gt;Password dumps &lt;a href=&quot;https://wiki.skullsecurity.org/Passwords&quot;&gt;exist&lt;/a&gt; on the
internet. If the password you’re using exists in one of these dumps (even on
someone else’s account), it’s not secure. As you might imagine, that means you
can’t use simple words, “123456”, or anything like that.&lt;/p&gt;

&lt;h3 id=&quot;different-passwords-on-different-sites&quot;&gt;Different Passwords on Different Sites&lt;/h3&gt;

&lt;p&gt;You’ve probably heard it before. Don’t use the same password for your email
account that you use for your bank account. Websites get broken into
&lt;a href=&quot;https://en.wikipedia.org/wiki/Cloudbleed&quot;&gt;all&lt;/a&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/2012_LinkedIn_hack&quot;&gt;the&lt;/a&gt;
&lt;a href=&quot;https://www.theguardian.com/technology/2016/aug/31/dropbox-hack-passwords-68m-data-breach&quot;&gt;time&lt;/a&gt;.
If you use the same password on every website, a hack on any of the websites you
visit becomes a hack on all the websites you visit. If you use a different
password for each website, you limit your exposure in case any of the websites
you use gets compromised.&lt;/p&gt;

&lt;h3 id=&quot;multi-factor-authentication&quot;&gt;Multi-Factor Authentication&lt;/h3&gt;

&lt;p&gt;Where it’s offered, &lt;a href=&quot;https://en.wikipedia.org/wiki/Multi-factor_authentication&quot;&gt;multi-factor
authentication&lt;/a&gt; can
provide better security than a password alone. Multi-factor authentication fixes
many of the problems with passwords. Accounts protected with multi-factor
authentication are likely to be difficult to compromise even if the password
protecting them is weak.&lt;/p&gt;

&lt;h2 id=&quot;what-are-the-options&quot;&gt;What are the Options&lt;/h2&gt;

&lt;p&gt;As a user, it’s hard to use good passwords. Passwords (or even password
algorithms) that work on one site often won’t work on another. Websites make you
change your password at random intervals. It can all be very not-user-friendly.
Here are some strategies that can help.&lt;/p&gt;

&lt;h3 id=&quot;use-single-sign-on&quot;&gt;Use Single Sign-On&lt;/h3&gt;

&lt;p&gt;Many websites are allowing their users to sign in using a Google or Facebook
account. Where it’s available, I think this is a great option. I’m usually
already logged in to my Google account anyway. And I trust Google to do a better
job verifying my identity than most other websites. If other websites can use
Google to verify my identity, that’s one less password I need to manage.&lt;/p&gt;

&lt;h3 id=&quot;use-a-password-manager&quot;&gt;Use a Password Manager&lt;/h3&gt;

&lt;p&gt;A password manager like &lt;a href=&quot;https://bitwarden.com&quot;&gt;Bitwarden&lt;/a&gt;,
&lt;a href=&quot;https://1password.com/&quot;&gt;1Password&lt;/a&gt;, &lt;a href=&quot;https://www.lastpass.com/&quot;&gt;LastPass&lt;/a&gt;,
&lt;a href=&quot;https://www.passwordstore.org/&quot;&gt;Pass&lt;/a&gt;, or even
&lt;a href=&quot;https://passwords.google.com&quot;&gt;Google&lt;/a&gt; can remember your passwords for you. Good
ones will generate new randomized passwords for you when you sign up to new
websites. Many people find this option attractive. Personally, I’m hesitant to
use a password manager. Mainly because I don’t want to have to rely on it to be
able to log in to all my other websites. And because I don’t like the idea of
having all my passwords in one place if that service were to be hacked. But
perhaps this &lt;em&gt;is&lt;/em&gt; the way of the future - maybe I need to get over it.&lt;/p&gt;

&lt;p&gt;(&lt;strong&gt;2020 Update:&lt;/strong&gt; I did get over it. Despite all the advantages of password
algorithms, which I describe below, they have many disadvantages too.
They break on sites that require you to change your password frequently
or that disallow special characters. I’ve been using
&lt;a href=&quot;https://bitwarden.com&quot;&gt;Bitwarden&lt;/a&gt; for well over a year now. It’s unobtrusive,
open-source, and the code’s been audited. Read more about my new thinking
&lt;a href=&quot;/blog/2017/03/25/how-i-manage-passwords-with-keepass.html&quot;&gt;here&lt;/a&gt; and
&lt;a href=&quot;/blog/2018/11/20/keepass-vs-bitwarden.html&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;h3 id=&quot;use-a-password-algorithm&quot;&gt;Use a Password Algorithm&lt;/h3&gt;

&lt;p&gt;I’ve been using this trick for a while on some websites. It solves the problem
of using a different password on different websites without having to remember a
large number of different passwords (and without using a password manager). It
works like this: Start with a “base” password. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILikeDoughnuts&lt;/code&gt;.
Then, add something about the name of the website you’re logging in to. If
you’re logging into your bank, you might use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BILikeDoughnutsank&lt;/code&gt;, combining the
works &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bank&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILikeDoughnuts&lt;/code&gt; to form your password. Now you’ve got a
different password for each site, but you only have to remember one algorithm.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;Unfortunately, passwords are in some ways a necessary evil. Something that isn’t
ideal but something we have to deal with. Understanding the different approaches
to dealing with them and the trade-offs between the approaches is really
important.&lt;/p&gt;

&lt;p&gt;What password strategies do you use? How can software developers make the login
experience more user-friendly?&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/dealing-with-passwords.png"/>
 </entry>
 
 <entry>
   <title>So You Want to Learn Docker</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/02/11/so-you-want-to-learn-docker.html" type="text/html" title="So You Want to Learn Docker"/>
   
   <published>2017-02-11T10:50:00-07:00</published>
   <updated>2017-02-11T10:50:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/02/11/so-you-want-to-learn-docker</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/02/11/so-you-want-to-learn-docker.html">&lt;h2 id=&quot;everyones-learning-docker&quot;&gt;Everyone’s Learning Docker&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; is the cool new technology. Last year, it had
264% YoY growth in Stack Overflow questions.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; And with good reason. Docker
solves a lot of software development and systems engineering challenges. It
provides a great solution for development and testing environments, enabling
developers to test their code in an environment very similar to production
(assuming production runs on containers). Even if production doesn’t run on
containers, many developers still prefer Docker over virtual machines for
development and testing because of the advantages it provides for speed and
consistent state.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/so-you-want-to-learn-docker/docker_small_h-trans.png&quot; alt=&quot;Docker Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With the rapid growth Docker is experiencing, it seems like everyone is trying
to learn how to use the technology. In fact, I’ve invested many over the last
year in teaching &lt;em&gt;myself&lt;/em&gt; to use Docker. When I was just starting out, I spent a
lot of time searching for the quickest, easiest way to learn.&lt;/p&gt;

&lt;h2 id=&quot;how-we-learn&quot;&gt;How we Learn&lt;/h2&gt;

&lt;p&gt;When I learned Git many years earlier, I used a really great website called &lt;a href=&quot;https://gitimmersion.com/&quot;&gt;Git
Immersion&lt;/a&gt;. Essentially, this walks you through pretty
much everything you might want to do with a Git repository. It assumes nothing,
and starts with the most basic principals. But by the time you reach lab 15 or
20, you start to feel like you really know what you’re doing. You start to feel
&lt;em&gt;comfortable&lt;/em&gt; using Git because you’ve been actively using it as part of every
lesson.&lt;/p&gt;

&lt;p&gt;And there’s actually reason behind this learning methodology. Educators know
that people learn more by doing things than they do by reading about them. If
you read a book about a new technology, even if it feels like you learn a lot
from the book, you won’t feel at-home when you start to use it for the first
time. You won’t feel &lt;em&gt;comfortable&lt;/em&gt; with it. Git Immersion is based on the
premise, “to know a thing is to do it.” When you work through Git Immersion, you
don’t just read about Git. You &lt;em&gt;use&lt;/em&gt; it. You &lt;em&gt;build something&lt;/em&gt; with it. And this
is critical for the effectiveness with which it helps you learn.&lt;/p&gt;

&lt;h2 id=&quot;container-immersion&quot;&gt;Container Immersion&lt;/h2&gt;

&lt;p&gt;When I began teaching myself Docker, I was looking for something that would
teach me Docker in the same way Git Immersion taught me Git. A walk-through of
sorts, that would teach me how to build things with Docker from the ground up.
While I found a few good books and reference materials, I never found exactly
what I was looking for. (But I do recommend &lt;a href=&quot;https://dockerbook.com/&quot;&gt;The Docker
Book&lt;/a&gt; if you’re serious about learning Docker.)&lt;/p&gt;

&lt;p&gt;Now that I know Docker pretty well, I’ve decided to create the type of lessons
that I wish I had when I was learning. &lt;a href=&quot;https://mkasberg.github.io/container-immersion/&quot;&gt;Container
Immersion&lt;/a&gt; is a “guided tour”
for Docker, written in the spirit of Git Immersion. It is hosted on Github via
Github Pages, so it is open source and anybody can use or contribute to it. I
don’t know if it will ever turn into a very popular project, but I enjoyed
writing it and I hope someone finds it useful.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Image: &lt;a href=&quot;https://www.flickr.com/photos/lord_yo/4554730854/&quot;&gt;https://www.flickr.com/photos/lord_yo/4554730854/&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://blog.newrelic.com/2016/06/16/docker-container-infographic/&quot;&gt;https://blog.newrelic.com/2016/06/16/docker-container-infographic/&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/containers.jpg"/>
 </entry>
 
 <entry>
   <title>Testing is Hard</title>
   
   <link href="https://www.mikekasberg.com/blog/2017/01/28/testing-is-hard.html" type="text/html" title="Testing is Hard"/>
   
   <published>2017-01-28T11:30:00-07:00</published>
   <updated>2017-01-28T11:30:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2017/01/28/testing-is-hard</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2017/01/28/testing-is-hard.html">&lt;p&gt;One of my friends and colleagues writes for the &lt;a href=&quot;https://www.pmfordevs.com/&quot;&gt;Project Management for
Developers&lt;/a&gt; blog. He recently wrote &lt;a href=&quot;https://www.pmfordevs.com/blog/a-case-for-writing-tests&quot;&gt;an
article&lt;/a&gt; about the
importance of testing. I want to respond to his blog post and provide some
additional insight into testing from a developer perspective.&lt;/p&gt;

&lt;p&gt;I’m going to talk mainly about unit tests here because I believe unit tests are
the foundation and most important part of any test suite. But most of the ideas
apply equally well to other types of tests.&lt;/p&gt;

&lt;h2 id=&quot;time--coverage&quot;&gt;Time &amp;amp; Coverage&lt;/h2&gt;

&lt;p&gt;Somehow, it’s always easy to find an excuse &lt;em&gt;not&lt;/em&gt; to write tests. You’re
spending too much time on tests. That component isn’t testable. You tested it
manually right? We need to get this out. It’s just too hard to test. The code’s
too old. It’s not &lt;em&gt;worth it&lt;/em&gt;. If you’re a developer, you’ve heard pretty much
all these excuses at one point or another.&lt;/p&gt;

&lt;p&gt;So here’s my advice: &lt;strong&gt;Just write the tests!&lt;/strong&gt; Wherever you think you don’t need
tests, that’s where the bug will be. Every time. It’s &lt;em&gt;almost always&lt;/em&gt; worth it to
have tests. And as you gain more experience, you’ll find testing to be quicker
and easier than you thought it was. Tests catch your bugs when you’re writing
code, and they prevent you from breaking the code when you add a feature a year
or two later.&lt;/p&gt;

&lt;p&gt;Now that I’ve convinced you how important tests are, I’m going to backpedal a
little bit. You’re not likely to ever reach 100% coverage. In fact, 90% or even
80% will be really hard to hit. So be smart about what you test. Test the
components of your code that have the most logic and are most likely to break.
And write a test whenever you fix a bug.&lt;/p&gt;

&lt;h2 id=&quot;testing-patterns&quot;&gt;Testing Patterns&lt;/h2&gt;

&lt;p&gt;I mentioned that as you gain more experience writing tests, it becomes quicker
and easier. It’s true, and one of the main reasons for this is that you start to
recognize patterns in your code. When you’re working with old code that doesn’t
have unit tests, you’ll initially find it hard to test. You need to find what
Michael Feathers calls
&lt;a href=&quot;https://www.informit.com/articles/article.aspx?p=359417&amp;amp;seqNum=3&quot;&gt;seams&lt;/a&gt;. Places
in your code where you can inject a mock or a stub and run some tests.&lt;/p&gt;

&lt;p&gt;As you start to look for these seams in older code, you also start adding them
to new code as you’re designing it. You don’t want to have to refactor your new
code just to test it later on, so you make it easy to test from the beginning.
You’ll start thinking about things like &lt;a href=&quot;https://en.wikipedia.org/wiki/Dependency_injection#Without_dependency_injection&quot;&gt;dependency
injection&lt;/a&gt;
when you’re writing your constructors. Test driven development forces you to
write code that’s testable. But even if you’re not doing strict test driven
development, your code will be easier to test if you think about how you’ll test
it when you write it. This means less time spent testing, better test coverage,
and more reliable code.&lt;/p&gt;

&lt;h2 id=&quot;think-about-your-client&quot;&gt;Think About your Client&lt;/h2&gt;

&lt;p&gt;No matter what type of application you’re working with, well-written tests
provide a huge amount of value that is not immediately apparent. When you write
tests, you’re making an investment in the quality (especially long-term) of your
application. Your clients are always going to want their code sooner, and tests
may seem like a bad trade-off when it delays this week’s release a couple of
days. This is why it’s important to explain to your clients the value that tests
provide. In the long run, you’re better off having tests, and it may even
increase your team’s development speed down the road.&lt;/p&gt;

&lt;h2 id=&quot;in-summary&quot;&gt;In Summary&lt;/h2&gt;

&lt;p&gt;As a software developer, your job is to deliver working code to your client.
Tests are one of the tools that enable you do do this. If you’re delivering code
that doesn’t have good test coverage, you’re much more likely to be delivering
buggy code. Experienced developers know this inherently because they find bugs
pretty much every time they write tests. As a professional software developer,
it’s part of your job to understand this, to help your client make informed
decisions, and to deliver well-tested and bug-free software.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/testing-is-hard.jpg"/>
 </entry>
 
 <entry>
   <title>Automated Jekyll Deployments</title>
   
   <link href="https://www.mikekasberg.com/blog/2016/11/08/automated-jekyll-deployments.html" type="text/html" title="Automated Jekyll Deployments"/>
   
   <published>2016-11-08T20:40:00-07:00</published>
   <updated>2016-11-08T20:40:00-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2016/11/08/automated-jekyll-deployments</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2016/11/08/automated-jekyll-deployments.html">&lt;p&gt;My last post was about using Jekyll as a podcasting platform. Now, I want to
talk about how I set up automatic deployments with Jekyll. I’m deploying to a
Dreamhost server, but the principles I applied should work for most servers that
provide ssh access.&lt;/p&gt;

&lt;h2 id=&quot;why&quot;&gt;Why?&lt;/h2&gt;

&lt;p&gt;Automatic deployment is important to me. Anything that is not automated is
error-prone, and I don’t want to run that risk when deploying my website. With
the podcast site I recently developed, there was an additional benefit. I wanted
to be able to schedule the podcast to publish an episode at a specific time
(preferably overnight), without having to interact with it during the
delpoyment. This was important because we publish our podcast at a regular
interval. With automated deployment, I don’t need to worry about modifying my
schedule to make sure I’m free at the time we need to publish the episode.&lt;/p&gt;

&lt;h2 id=&quot;how&quot;&gt;How?&lt;/h2&gt;

&lt;p&gt;The idea for this setup is pretty simple. We were already using Git for version
control. To setup automated deployments, I began by simply putting a bare Git
repo on our Dreamhost server. Developers can push to this repo via ssh just like
any other repo.&lt;/p&gt;

&lt;p&gt;With the Git repository on the web server itself, it is easy to write a script
that builds and deploys the site from that repository. The script itself isn’t
too complex.  It just checks out a copy of our source, uses Jekyll to build it,
and then deploys it to our public html folder. The hardest piece of all of this
was getting Jekyll to run on our Dreamhost server - which we accomplished with
Ruby RVM and a user install of Jekyll.&lt;/p&gt;

&lt;p&gt;Here’s a slightly simplified version of our deployment script:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;GIT_REPO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/myjekyllsite.git
&lt;span class=&quot;nv&quot;&gt;TMP_GIT_CLONE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/tmp/myjekyllsite
&lt;span class=&quot;nv&quot;&gt;PUBLIC_WWW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/mypodcast.com

&lt;span class=&quot;c&quot;&gt;# Check out the Git repository so we have a working copy in a temp dir.&lt;/span&gt;
git clone &lt;span class=&quot;nv&quot;&gt;$GIT_REPO&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TMP_GIT_CLONE&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Build it.&lt;/span&gt;
jekyll build &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TMP_GIT_CLONE&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TMP_GIT_CLONE&lt;/span&gt;/_site

&lt;span class=&quot;c&quot;&gt;# Deploy it.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TMP_GIT_CLONE&lt;/span&gt;/_site/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PUBLIC_WWW&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Clean up.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TMP_GIT_CLONE&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Done.&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;so&quot;&gt;So?&lt;/h2&gt;

&lt;p&gt;Using a script like this to deploy our Jekyll site has been great! Since the
process is entirely scripted, there’s little room for error. Also, deploying the
site on a regular schedule is as simple as running this script from a cron job.
By default, Jekyll will not publish anything with a future date, so we can queue
up as many posts as we want which will be automatically published in the future.
This method of deployment has simplified our process and removed some of the
risk normally associated with updates.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/automated-jekyll-deployments.jpg"/>
 </entry>
 
 <entry>
   <title>Podcasting with Jekyll</title>
   
   <link href="https://www.mikekasberg.com/blog/2016/09/21/podcasting-with-jekyll.html" type="text/html" title="Podcasting with Jekyll"/>
   
   <published>2016-09-21T13:00:00-06:00</published>
   <updated>2016-09-21T13:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2016/09/21/podcasting-with-jekyll</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2016/09/21/podcasting-with-jekyll.html">&lt;p&gt;I recently did some volunteer work to upgrade the website for a podcast. I chose
to re-write the site from scratch, but needed to migrate all of the existing
content. After some research, I determined that &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;
fit our needs best because:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;One of my own requirements was that the site, and all the posts (episodes)
would be under version control.&lt;/li&gt;
  &lt;li&gt;I wanted to automate certain parts of the process. Jekyll, being a simple
command line application, made this relatively easy.&lt;/li&gt;
  &lt;li&gt;I was the sole person contributing to the site, and was comfortable with
deploying and updating a website, so a full-blown CMS would be overkill.&lt;/li&gt;
  &lt;li&gt;Jekyll, once built, is plain HTML. No databases to manage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-old-site&quot;&gt;The Old Site&lt;/h2&gt;

&lt;p&gt;When I took over the web presence of the podcast, it had roughly 4 years of
content that needed to be preserved. The old site was driven by WordPress.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The site itself was a simple WordPress site, based on a template.&lt;/li&gt;
  &lt;li&gt;All audio files for the podcast were stored in their own folder, managed
outside of WordPress. Fortunately for me, this made several aspects of the
migration easier.&lt;/li&gt;
  &lt;li&gt;The iTunes RSS feed for the podcast was an XML file that was manually
updated. I guess this is an artifact from the “old old” site, before they
were on WordPress.&lt;/li&gt;
  &lt;li&gt;Each episode had a WordPress post that contained the title, a short
description, an image, and a link for the audio.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;podcasting-with-jekyll&quot;&gt;Podcasting with Jekyll&lt;/h2&gt;

&lt;p&gt;I wanted the new site to be simple, easy (for me) to update, version
controlled, and as automated as possible. First, I had to work out the
mechanics of how to actually do podcasting with Jekyll. After publishing
several new episodes, I’ve found the setup to be quite nice:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The site itself is a pretty standard Jekyll blog.  Blog “posts” are actually
podcast episodes. Generally speaking, they work just like Jekyll blog posts
in any other site. To add a new episode, I just create a markdown file in the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts&lt;/code&gt; folder with some YAML frontmatter.&lt;/li&gt;
  &lt;li&gt;The iTunes RSS feed (which is what makes this a podcast) is generated just
like any other HTML page in Jekyll using the Liquid templating engine.
(Based, roughly, on
&lt;a href=&quot;https://github.com/taxigy/itunes-jekyll-template/blob/master/itunes-feed-layout.xml&quot;&gt;this&lt;/a&gt;.)&lt;/li&gt;
  &lt;li&gt;The frontmatter for episodes has a few special fields. These are used to
populate metadata for the RSS feed, and to link to the correct audio file.&lt;/li&gt;
  &lt;li&gt;The Jekyll project is stored in Git.  To keep the repository small, audio&lt;/li&gt;
  &lt;li&gt;files are stored outside the repository
and backed up elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-migration&quot;&gt;The Migration&lt;/h2&gt;

&lt;p&gt;After developing the Jekyll site, I had to migrate all of the content off of the
old WordPress site and into the new Jekyll site. Fortunately, since the audio
files were already in a separate folder, they did not require any migration.&lt;/p&gt;

&lt;p&gt;Since I was now generating the RSS feed with Jekyll, I needed to import all the
necessary metadata into a markdown file for each existing episode. To
accomplish this, I wrote a
&lt;a href=&quot;https://github.com/mkasberg/jekyll-podcast-import-script&quot;&gt;script&lt;/a&gt; that will
generate Jekyll posts (markdown files) from an existing RSS feed. This was the
most difficult (and riskiest) part of the migration because I needed these
Jekyll posts to produce an RSS feed nearly identical to the existing one (which
was not managed by Jekyll).&lt;/p&gt;

&lt;p&gt;After importing all the old episodes, all that was left was to deploy the site.
Again, because Jekyll generates static HTML, this was easy. I deployed all
pages except index.html while the wordpress site was still live. Finally,
deleting wordpress’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.php&lt;/code&gt; and replacing it with the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt;
switched the new site on with no downtime. Because the location of the iTunes
RSS feed didn’t move, iTunes and other podcast scrapers did not need to be
changed and immediately started picking up any new episodes deployed with
Jekyll.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Exporting the old website from WordPress and importing it to Jekyll was easier
than I could have hoped it would be, thanks to a few simple scripts. So far,
Jekyll has been easier and more enjoyable to work with than WordPress. It’s also
proving to be a great platform for serving a podcast. I’m working on a solution
for automated deployment, but that’s a topic for a different post.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Software Development"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/podcasting-with-jekyll.png"/>
 </entry>
 
 <entry>
   <title>Chromium OS on a 2007 MacBook</title>
   
   <link href="https://www.mikekasberg.com/blog/2016/05/28/chromium-os.html" type="text/html" title="Chromium OS on a 2007 MacBook"/>
   
   <published>2016-05-28T14:00:00-06:00</published>
   <updated>2016-05-28T14:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2016/05/28/chromium-os</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2016/05/28/chromium-os.html">&lt;p&gt;&lt;a href=&quot;https://www.google.com/chromebook/&quot;&gt;Chromebooks&lt;/a&gt; are becoming more popular these
days, and their simplicity is something that really appeals to me. I wanted a
Chromebook, but not so much that I wanted to spend a lot of money to buy one. At
the same time, I had an old 2007 MacBook lying around that was becoming nearly
unusable because it was so &lt;strong&gt;slow&lt;/strong&gt;. The version of macOS it was running was
outdated, and I didn’t want to spend money on an upgrade for such a slow
computer. So… I turned it into a Chromebook!&lt;/p&gt;

&lt;p&gt;I used the &lt;a href=&quot;https://www.neverware.com/&quot;&gt;CloudReady&lt;/a&gt; Chromium OS distribution
provided by Neverware, and the installation process was really easy! The hardest
part was getting the MacBook to boot from a big enough USB stick. For some
reason, my MacBook wouldn’t boot from my Toshiba 16GB flash drive. Using a
&lt;a href=&quot;https://www.amazon.com/gp/product/B00DYQYITG/ref=oh_aui_detailpage_o01_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;Kingston DataTraveler&lt;/a&gt;
drive (recommended on the CloudReady forums) fixed the problem - something about
the way this drive is recognized by the MacBook made it work. After booting from
the USB stick, just a few quick installation steps and you’re done!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/CloudReady/cloudready-installation.jpg&quot; alt=&quot;Installing CloudReady&quot; /&gt;&lt;/p&gt;

&lt;p&gt;CloudReady (and other Chromium OS distributions) are a great way to repurpose
old hardware. A lot of the time, you just want a device that boots up quickly
and lets you browse the web or send some email, and Chromium OS/CloudReady is
perfect for that.&lt;/p&gt;

&lt;h2 id=&quot;want-to-give-this-a-try-with-your-own-old-computer&quot;&gt;Want to give this a try with your own old computer?&lt;/h2&gt;

&lt;p&gt;There are a few different variants of &lt;a href=&quot;https://www.chromium.org/chromium-os&quot;&gt;Chromium
OS&lt;/a&gt; you could use, but
&lt;a href=&quot;https://www.neverware.com/freedownload&quot;&gt;CloudReady&lt;/a&gt; is probably the easiest to
install.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Download CloudReady from &lt;a href=&quot;https://www.neverware.com/freedownload&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Follow the instructions to create a bootable USB stick with CloudReady on it.&lt;/li&gt;
  &lt;li&gt;Insert the USB stick into your old computer, boot from the USB stick, and
follow the instructions to complete the installation.&lt;/li&gt;
&lt;/ol&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/chromium-os.png"/>
 </entry>
 
 <entry>
   <title>How to Learn Vim: Vim Refcard</title>
   
   <link href="https://www.mikekasberg.com/blog/2015/05/02/vim-refcard.html" type="text/html" title="How to Learn Vim: Vim Refcard"/>
   
   <published>2015-05-02T13:00:00-06:00</published>
   <updated>2015-05-02T13:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2015/05/02/vim-refcard</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2015/05/02/vim-refcard.html">&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/mkasberg/vim-refcard&quot;&gt;Vim Refcard on Github&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m a huge fan of reference cards like the one &lt;a href=&quot;https://www.gnu.org/software/emacs/refcards&quot;&gt;Emacs
provides&lt;/a&gt;. In fact, I think that a
good reference card is probably the best way to learn a new software program.
Whether it’s Vim or anything else.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;&lt;strong&gt;Do you want to learn Vim?&lt;/strong&gt; Print out this PDF, put it next to your monitor,
and start using Vim whenever you need a text editor. Seriously. Just do it. It
will be slow at first.  In fact, it might be a little painful. But after an hour
or two, you’ll be cruising along like a seasoned vet. You’ll be amazed how well
you can remember the shortcuts - your brain can remember them because you were
&lt;em&gt;using&lt;/em&gt; them.&lt;/p&gt;

&lt;p&gt;The Emacs refcard is really well done, but I couldn’t find a similar one for
Vim. This was disappointing because I know how useful a good reference card is.
So I busted out my LaTeX editor and made one. It’s open source, and available on
Github. Feel free to open a pull request if you think it’s missing anything
important.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/vim-refcard.png"/>
 </entry>
 
 <entry>
   <title>Make a Useful Budget in Mint</title>
   
   <link href="https://www.mikekasberg.com/blog/2014/07/06/make-a-useful-budget-in-mint.html" type="text/html" title="Make a Useful Budget in Mint"/>
   
   <published>2014-07-06T13:00:00-06:00</published>
   <updated>2014-07-06T13:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2014/07/06/make-a-useful-budget-in-mint</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2014/07/06/make-a-useful-budget-in-mint.html">&lt;p&gt;&lt;a href=&quot;https://www.mint.com/&quot;&gt;Mint.com&lt;/a&gt; has always done a great job of showing me
where I spent my money last week or last month, but until recently I was never
able to figure out how to use it to tell me if I could afford to buy that new
pair of shoes. To be really effective, a budget has to easily show me if I can
afford to buy something I want.&lt;/p&gt;

&lt;p&gt;I used to get graphs on Mint.com that looked like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/mint-budget/mint-before-budget.png&quot; alt=&quot;Mint graph before &amp;quot;Misc expenses&amp;quot;
trick&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That graph doesn’t really provide much useful information. After coming up with
a new budgeting strategy that is integrated with Mint, it is easy to see more
detailed information in your graphs:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/mint-budget/mint-after-budget.png&quot; alt=&quot;Mint graph after &amp;quot;Misc expenses&amp;quot;
trick&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My new budgeting strategy is based on &lt;a href=&quot;https://www.iwillteachyoutoberich.com/&quot;&gt;Ramit
Sethi&lt;/a&gt;’s idea of “guilt-free spending
money”. Essentially, I create a monthly budget of money that I can spend on
whatever I want, without worrying about it.&lt;/p&gt;

&lt;p&gt;Unfortunately, Mint.com doesn’t have built-in functionality that lets you keep
track of how much “guilt-free spending money” you have. But there is a simple
workaround – use Mint’s Misc Expenses category to track everything you buy with
your guilt-free spending money, creating sub-categories as needed. For example,
even though Mint already has a category for Bars &amp;amp; Alcohol, create a #Bars
sub-category under Misc Expenses and use the new category any time you spend
your guilt-free money at a bar. (I use a hashtag in front of my custom
categories so I can tell them apart from the built-in categories.)&lt;/p&gt;

&lt;p&gt;What does this do for you? Because all of your guilt-free spending money
purchases are under the same category (Misc Expenses), you can create a budget
for your guilt-free spending money by simply creating a budget for that
category. You can set it to roll over at the end of the month, and you’ll always
know how much money you can spend without worrying about it. You just have to
make sure things are categorized correctly in Mint. (Maybe re-categorize your
transactions once a week.)&lt;/p&gt;

&lt;p&gt;As an added bonus, you can view graphs to see how you’re spending your
guilt-free spending money by filtering for the “Misc Expenses” category on the
graphs tab. This graph is much more useful than, say, a graph showing that most
of my money went to a check and educational expenses, and the rest went to
“Other”.&lt;/p&gt;

&lt;p&gt;Many people dislike Mint because it is better at showing you how you spent your
money &lt;em&gt;last&lt;/em&gt; month than it is at showing you how you can spend your money &lt;em&gt;next&lt;/em&gt;
month. Tracking your guilt-free spending money in a separate category in Mint
fixes that problem, and makes Mint a much more useful tool.&lt;/p&gt;

</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Thoughts &amp; Ideas"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/mint-budget.jpg"/>
 </entry>
 
 <entry>
   <title>Boom Box Aux In Mod</title>
   
   <link href="https://www.mikekasberg.com/blog/2014/05/10/boom-box-aux-in-mod.html" type="text/html" title="Boom Box Aux In Mod"/>
   
   <published>2014-05-10T13:00:00-06:00</published>
   <updated>2014-05-10T13:00:00-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2014/05/10/boom-box-aux-in-mod</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2014/05/10/boom-box-aux-in-mod.html">&lt;p&gt;I wanted to modify an old boom box so it could play my iPod with an auxiliary cable. It turned out really well, and it wasn’t too difficult to complete. Check it out!&lt;/p&gt;

&lt;div class=&quot;text-center&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/mae6Xn5KrfQ&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Want to do something similar yourself? Full instructions for the mod are available on &lt;a href=&quot;https://www.instructables.com/Boom-Box-Aux-In-Mod/&quot;&gt;Instructables&lt;/a&gt;.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/boom-box-aux-in-mod.png"/>
 </entry>
 
 <entry>
   <title>Loft Bed</title>
   
   <link href="https://www.mikekasberg.com/blog/2009/08/08/loft-bed.html" type="text/html" title="Loft Bed"/>
   
   <published>2009-08-08T16:08:10-06:00</published>
   <updated>2009-08-08T16:08:10-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2009/08/08/loft-bed</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2009/08/08/loft-bed.html">&lt;p&gt;When I searched for loft bed plans online, I didn’t find any freely available plans that I liked. I decided to make my own plans for a wooden loft bed, and I’m making these plans available for free.  These plans may be used &lt;strong&gt;and redistributed&lt;/strong&gt; freely under a &lt;a rel=&quot;license&quot; href=&quot;https://creativecommons.org/licenses/by-nc/3.0/us/&quot;&gt;Creative Commons Attribution-Noncommercial 3.0 United States License&lt;/a&gt; &lt;a rel=&quot;license&quot; href=&quot;https://creativecommons.org/licenses/by-nc/3.0/us/&quot;&gt;&lt;img alt=&quot;Creative Commons License&quot; style=&quot;margin:0; display:inline-block;&quot; src=&quot;https://licensebuttons.net/l/by-nc/3.0/us/88x31.png&quot; /&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href=&quot;/files/LoftBedPlans.pdf&quot; target=&quot;_blank&quot;&gt;plans&lt;/a&gt;&lt;/strong&gt; are available as a single page pdf. They’re not the most detailed plans available, and they don’t provide step-by-step instructions, but they should be sufficient if you’re comfortable with some basic tools. The bed’s designed so that the legs are attached with 8 bolts, and can be easily removed so that the legs store and travel flat. These plans are for a twin mattress, but can easily be adapted to other sizes. If you wanted, you could easily build a desk into the bed over the second ladder rungs.&lt;/p&gt;

&lt;p&gt;Disclaimer: I am not responsible for anything that might result from your use of these plans. By using these plans, you assume full responsibility for any consequences. These plans worked well for me, but I do not guarantee that they’ll work for you.&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/loft-bed.png"/>
 </entry>
 
 <entry>
   <title>SkillsUSA 2008 Nationals</title>
   
   <link href="https://www.mikekasberg.com/blog/2008/06/30/skillsusa-2008-nationals.html" type="text/html" title="SkillsUSA 2008 Nationals"/>
   
   <published>2008-06-30T16:08:10-06:00</published>
   <updated>2008-06-30T16:08:10-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2008/06/30/skillsusa-2008-nationals</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2008/06/30/skillsusa-2008-nationals.html">&lt;p&gt;Nathan Witt and I &lt;a href=&quot;https://www.skillsusa-register.org/rpts/eventmedalists.aspx&quot;&gt;won the bronze medal&lt;/a&gt; at SkillsUSA Nationals for 3D Animation in 2008. The challenge was to create an animation that modeled a given picture of a warehouse and showed something that couldn’t be seen in the original photo. See some of our renderings below.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/SkillsUSA/Team29Still10.jpg&quot; alt=&quot;SkillsUSA Still Render&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/SkillsUSA/Team29Still5.jpg&quot; alt=&quot;SkillsUSA Still Render&quot; /&gt;&lt;/p&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/skillsusa-2008-nationals.png"/>
 </entry>
 
 <entry>
   <title>Suzuki GSXR</title>
   
   <link href="https://www.mikekasberg.com/blog/2008/05/30/suzuki-gsxr.html" type="text/html" title="Suzuki GSXR"/>
   
   <published>2008-05-30T17:08:10-06:00</published>
   <updated>2008-05-30T17:08:10-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2008/05/30/suzuki-gsxr</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2008/05/30/suzuki-gsxr.html">&lt;p&gt;I created this Suzuki GSXR model using Autodesk Maya as part of the &lt;a href=&quot;https://sites.google.com/view/digital-evolutions/main&quot;&gt;Digital Evolutions&lt;/a&gt; 3D modeling &amp;amp; animation program at Smoky Hill High School.&lt;/p&gt;

&lt;div class=&quot;text-center&quot;&gt;
&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/DJOW3z_JA7U&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/suzuki-gsxr.png"/>
 </entry>
 
 <entry>
   <title>Senior Demo Reel</title>
   
   <link href="https://www.mikekasberg.com/blog/2008/05/29/senior-demo-reel.html" type="text/html" title="Senior Demo Reel"/>
   
   <published>2008-05-29T17:08:10-06:00</published>
   <updated>2008-05-29T17:08:10-06:00</updated>
   <id>https://www.mikekasberg.com/blog/2008/05/29/senior-demo-reel</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2008/05/29/senior-demo-reel.html">&lt;p&gt;This is my senior demo reel for the &lt;a href=&quot;https://sites.google.com/view/digital-evolutions/main&quot;&gt;Digital Evolutions&lt;/a&gt; 3D modeling &amp;amp; animation program at Smoky Hill High School.&lt;/p&gt;

&lt;div class=&quot;text-center&quot;&gt;
&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/gxS-6psCDCw&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/senior-demo-reel.png"/>
 </entry>
 
 <entry>
   <title>Nissan Skyline</title>
   
   <link href="https://www.mikekasberg.com/blog/2008/01/22/nissan-skyline.html" type="text/html" title="Nissan Skyline"/>
   
   <published>2008-01-22T15:08:10-07:00</published>
   <updated>2008-01-22T15:08:10-07:00</updated>
   <id>https://www.mikekasberg.com/blog/2008/01/22/nissan-skyline</id>
   
   <content type="html" xml:base="https://www.mikekasberg.com/blog/2008/01/22/nissan-skyline.html">&lt;p&gt;I created this 3D Nissan Skyline model during my junior year in the &lt;a href=&quot;https://sites.google.com/view/digital-evolutions/main&quot;&gt;Digital Evolutions&lt;/a&gt; program at Smoky Hill High School.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/Render_Good_GTR.png&quot; alt=&quot;Nissan Skyline GTR&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/Render_Good_GTR_Front.png&quot; alt=&quot;Nissan Skyline GTR Front&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;text-center&quot;&gt;
&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/PEtbX1AihWo&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
</content>
   
   <author><name>Mike Kasberg</name></author>
   
   <category term="Projects"/>
   
   <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mikekasberg.com/images/posts/nissan-skyline.png"/>
 </entry>
 

</feed>
