<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Evan Carlin</title><link href="https://evan.carlin.com/" rel="alternate"/><link href="https://evan.carlin.com/feeds/all.atom.xml" rel="self"/><id>https://evan.carlin.com/</id><updated>2026-09-03T00:00:00-06:00</updated><entry><title>So long SourceHut and Netlify</title><link href="https://evan.carlin.com/blog/so-long-sourcehut-and-netlify/" rel="alternate"/><published>2026-09-03T00:00:00-06:00</published><updated>2026-09-03T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-09-03:/blog/so-long-sourcehut-and-netlify/</id><summary type="html">&lt;p&gt;Switching from SourceHut and Netlify to self-hosting.&lt;/p&gt;</summary><content type="html">&lt;h1 id="intro"&gt;&lt;a class="toclink" href="#intro"&gt;Intro&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;For the past six years I've been a happy user of both
&lt;a href="https://sourcehut.org/"&gt;SourceHut&lt;/a&gt; (my code forge) and
&lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; (my blog host). A few days ago
SourceHut &lt;a href="https://sourcehut.org/blog/2026-08-27-tos-changes-and-llms/"&gt;changed their terms of
service&lt;/a&gt;
to no longer accept the "use of LLMs or other generative AI tools to
produce or assist with the production of source code, assets, tickets,
emails, and so on".&lt;/p&gt;
&lt;p&gt;First off, I want to commend SourceHut for taking a stance that aligns
with their values. I initially switched to SourceHut because it aligned
with some of my values. I like the simplicity of their site, and paying
for a service instead of trading my data for something "free". It takes
commitment to put one's foot down and turn away potential customers.
I'm happy they took their stance and I wish them all the best. While (I
hope) I'm not drunk on the LLM craze, I do use LLMs to author some of
my code and to help me solve problems. So, SourceHut's change set me
looking for an alternative.&lt;/p&gt;
&lt;p&gt;I received the email about SourceHut's TOS change while I was in the
airport flying home from a work trip. I was in Las Vegas, and maybe it
was all of the annoying screens and bright lights of the slot machines
that pushed me to want to self-host on a dusty old server. Whatever it
was, on my way home from the airport I swung by Colorado State
University's &lt;a href="https://surplus.colostate.edu/"&gt;Surplus Property store&lt;/a&gt;
to see what they had in stock. It is like stepping back in time in
there. Lots of old doohickeys with one foot in the grave. If I ever
need a VGA cable again, I know where to go. I opted for an HP Z240 SFF.
Onboard, this beast has an Intel i7-6700, 8GB of DDR4 (at current
prices that was probably the entire cost of the machine), and a lovely
250GB spinning hard drive. $70 and it was mine. I got a nice nostalgic
feeling booting it up, like firing up my childhood home computer. A
great tactile power button, a nice whir to the fans, and the screech of
the HDD. I've been using Mac laptops for too long...&lt;/p&gt;
&lt;p&gt;To make matters better (worse?), I also decided it was time to switch
to a proper config management tool. Something to provision machines so
that when this box eventually died I'd be able to easily switch over to
something new. Ansible, Salt, and the like have never really grabbed
me, but I've read over the years about &lt;a href="https://nixos.org/"&gt;NixOS&lt;/a&gt; and
it seemed like an interesting approach. So, with a weekend ahead of me,
some fresh metal, and the helpful/infuriating/clever/moronic hand of
Claude, I set off.&lt;/p&gt;
&lt;h1 id="porting-router-to-nixos"&gt;&lt;a class="toclink" href="#porting-router-to-nixos"&gt;Porting router to NixOS&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;My first port of call was converting my BABS (big ass bash scripts)
that configured my router (a Beelink EQ14) to NixOS. The scripts were
about as close to a 1:1 conversion as I could hope for. For example:&lt;/p&gt;
&lt;p&gt;This Bash&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;install -Z -m 600 /dev/stdin /etc/dnsmasq.d/router.conf &amp;lt;&amp;lt;'EOF'
&amp;lt;snip&amp;gt;
# Don’t read /etc/resolv.conf because we configure our
# own name servers.
no-resolv
server=1.1.1.1
server=9.9.9.9
&amp;lt;snip&amp;gt;
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;became this NixOS equivalent&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  services.dnsmasq = {
&amp;lt;snip&amp;gt;

      # Don't read /etc/resolv.conf because we configure our
      # own name servers.
      no-resolv = true;
      server = [
        &amp;quot;1.1.1.1&amp;quot;
        &amp;quot;9.9.9.9&amp;quot;
      ];
&amp;lt;snip&amp;gt;
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This went fairly smoothly. Regardless of whether it was Bash or NixOS,
I still grind my teeth doing this work. Networking code in particular
can "work" but be totally wrong and/or insecure. Likewise, when it doesn't
work there isn't a stack trace pointing me to the issue. Just the
inability to reach the server. I want to make my configuration repo
public, but I'm afraid there will be some glaring bugs in it. Putting
aside my epistemic anxiety, I came away really liking NixOS. The
internet tells me folks balk at the syntax, which I found fairly
pleasant to read and which LLMs were good at generating (after deleting
an absolute mountain of comments to get at the actual code
underneath...).&lt;/p&gt;
&lt;h1 id="porting-blog-and-code-forge"&gt;&lt;a class="toclink" href="#porting-blog-and-code-forge"&gt;Porting blog and code forge&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;On to my new server. I first needed to figure out which code forge to
use. Prior to SourceHut, I used both GitHub and GitLab. I would
describe both of them as "meh", or "what I use at work". Recently
&lt;a href="https://isgithubcooked.com/"&gt;GitHub has been circling the drain&lt;/a&gt;
looking for BitBucket down in the p-trap. I chose
&lt;a href="https://forgejo.org/"&gt;Forgejo&lt;/a&gt; as my new code forge. I thought for a
moment about self-hosting SourceHut, but in their TOS change blog post
they themselves said Forgejo is "easier to deploy". Maybe a dig
at LLM sloppers?;) Only so many oceans I can boil in a
weekend...&lt;/p&gt;
&lt;p&gt;My blog was very easy to port over. I have had no problems with
Netlify, but I also don't really get much from them. Might as well
ditch them along the way. My blog is just a static site generated by
&lt;a href="https://getpelican.com/"&gt;Pelican&lt;/a&gt;. So, it was a matter of pointing
nginx at the files it outputs. Again, NixOS made this simple. Friendly
settings like "enableACME" are easier than managing the systemd
services myself, and their language reads a lot like the nginx conf
files I'm used to. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;virtualHosts = lib.mkMerge [
    {
    &amp;quot;evan.carlin.com&amp;quot; = {
        enableACME = true;
        forceSSL = true;
        root = &amp;quot;/var/www/blog&amp;quot;;
    };
    }
    &amp;lt;snip&amp;gt;
    ];
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="so-long"&gt;&lt;a class="toclink" href="#so-long"&gt;So long&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A Saturday evening, all day Sunday, and I was back up and running. I
have another hand-rolled box (Beelink SER8) still to switch to NixOS.
This one runs Home Assistant and UniFi OS Server in VMs, so I think it
will take a bit more noodling to get them moved over. We'll see how
this journey goes. I also switched my yearly payment to SourceHut into
a donation split between Forgejo and NixOS. I told my wife recently
that I wanted to simplify my life. Too many things vying for my time. I
don't think this is what she or I had in mind.&lt;/p&gt;
&lt;p&gt;Happy Hacking.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>McMansion Programs</title><link href="https://evan.carlin.com/blog/mcmansion-programs/" rel="alternate"/><published>2026-05-25T00:00:00-06:00</published><updated>2026-05-25T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-05-25:/blog/mcmansion-programs/</id><summary type="html">&lt;p&gt;The kind of code LLMs write (for now)&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had some cleaning up to do this morning so I tuned in to the Oxide
and Friends podcast. I chose the episode &lt;a href="https://oxide-and-friends.transistor.fm/episodes/are-llms-insufficently-lazy"&gt;"Are LLMs Insufficiently
Lazy"&lt;/a&gt;.
I haven't finished the episode (or my cleaning) because it led to a
post by Bryan Cantrill called &lt;a href="https://bcantrill.dtrace.org/2026/04/12/the-peril-of-laziness-lost/"&gt;"The peril of laziness
lost"&lt;/a&gt;
(quick and worth a read).&lt;/p&gt;
&lt;p&gt;One sentence that really struck a chord with me in that post is: "Left
unchecked, LLMs will make systems larger, not better". Oh how true
that is!&lt;/p&gt;
&lt;p&gt;I've been recently calling it "industrial programming". While writing
this post I thought of calling it the &lt;a href="https://en.wikipedia.org/wiki/Supersize"&gt;"supersize
era"&lt;/a&gt;. I then thought of
"&lt;a href="https://en.wikipedia.org/wiki/McMansion"&gt;McMansion&lt;/a&gt; Programs". If I
were an LLM I'd use all of these abstractions interchangeably
throughout this post and probably dash in a few more along the way.&lt;/p&gt;
&lt;p&gt;We seem to be fully immersed in a moment in software where we need
more "stuff". Burn more tokens. Write more lines of code. Ship more
features. IPO for a trillion dollars. Who cares if it's better.&lt;/p&gt;
&lt;p&gt;What we seem to be lacking in all of this is taste. A quip I learned
from my older sister which I've repeated many times is "money can't
buy taste". McMansions are the USA's greatest call to this saying.
Humongous homes. Sloppily built. Filled with junk. Expensive to
maintain. They give off the appearance of great wealth. But, take
another look or use any of the janky hardware in the home and the
shine will be gone.&lt;/p&gt;
&lt;p&gt;The programs LLMs build tend to fall in this style. Just because we
used a lot of tokens building something doesn't make it any good. With
LLMs we can build first and think later (or not at all). We can create
more features and new additions faster than we can understand our own
code/product and certainly faster than our customers can make sense of
it. Instead of refining the features we do have and carefully adding
new ones we are able to implement every idea that comes into our head.&lt;/p&gt;
&lt;p&gt;With the aid of LLMs we have to be more careful. Review our code more
deeply before opening a PR. Take another pass at honing it. The first
pass we make as humans isn't good enough. Likewise, the first pass the
LLM makes isn't good enough. In 1983 Stephen Johnson and Brian
Kernighan said &lt;a href="https://dn790008.ca.archive.org/0/items/byte-magazine-1983-08/1983_08_BYTE_08-08_The_C_Language.pdf"&gt;"first make it work, then make it right, and, finally,
make it
fast"&lt;/a&gt;.
In the same year Butler Lampson said &lt;a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/acrobat-17.pdf"&gt;"Plan to throw one
away"&lt;/a&gt;.
With an LLM, making a version and throwing it away is now easier. But,
it is just as important as ever to throw (at least) one away.&lt;/p&gt;
&lt;p&gt;Extending the McMansion metaphor a little further: A curious overlap
between McMansion Programs and McMansion Homes is that they were both
aided by a new technology. In the case of McMansions Homes it was the
&lt;a href="https://en.wikipedia.org/wiki/Truss_connector_plate"&gt;truss connector
plate&lt;/a&gt;. If you're
curious about that sort of thing &lt;a href="https://youtu.be/3oIeLGkSCMA?si=IxET34zCUbufHze5"&gt;this
video&lt;/a&gt; goes into
more details on it and the industrialization of home building. I hope
our programs aren't doomed to the same soulless fate as many American
suburbs.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Shop Notes v3: Thank you doonut</title><link href="https://evan.carlin.com/blog/shop-notes-v3-thank-you-doonut/" rel="alternate"/><published>2026-05-25T00:00:00-06:00</published><updated>2026-05-25T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-05-25:/blog/shop-notes-v3-thank-you-doonut/</id><summary type="html">&lt;p&gt;Removing inner bearing races seized on a shaft&lt;/p&gt;</summary><content type="html">&lt;p&gt;After managing to &lt;a href="https://evan.carlin.com/blog/shop-notes-v2-the-cat-strikes-back/"&gt;remove the brake rotor from the track
driveshaft&lt;/a&gt; I thought my journey with this
part of the snowmobile repair was over. I could now remove the
driveshaft from the brake caliper which would let me remove the
caliper. I could then push out the seized brake piston and be on my
way. How naive.&lt;/p&gt;
&lt;p&gt;What I should've realized was that if the brake rotor was so seized on
the shaft that it broke my puller, then everything else would be
equally seized on the shaft.&lt;/p&gt;
&lt;p&gt;I won't go into all of the details, but I ended up cutting the brake
caliper off of the shaft. I highly doubt this was the best path to
take, but in my frustration it is where I ended up. What I was left
with was one destroyed brake caliper (easy enough to replace) and a
track driveshaft with 2 inner bearing races and 1 spacer completely
seized on the shaft. At this point, I didn't want to also have to buy a
new (used) track driveshaft so I slowed down a bit.&lt;/p&gt;
&lt;p&gt;I could see no good way of removing the races from the shaft. They
were completely seized on. Two nights of a PB B'laster soak made no
dent. And there wasn't enough purchase anywhere to get a puller on.
Off to do some research.&lt;/p&gt;
&lt;p&gt;I came across &lt;a href="https://www.snowmobileworld.com/threads/seized-bearing.34945/?post_id=323044&amp;amp;nested_view=1#post-323044"&gt;this wonderful
post&lt;/a&gt;
which instructed me to cut a groove in the race. Cut it as deep as I
could without going into the shaft. Then use a cold chisel and heavy
hammer to split the bearing.&lt;/p&gt;
&lt;p&gt;The post said "a real good whack". Mine took considerably more than
one whack. But, eventually, the bearing split and I could then walk it
off!&lt;/p&gt;
&lt;p&gt;&lt;img alt="bearing race split" src="https://evan.carlin.com/static/images/bearing-race-split.jpg"&gt;&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;&lt;a class="toclink" href="#closing-thoughts"&gt;Closing thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many thanks to forums and the people who post in them. For years I've
felt a growing sense of shame that I have consumed so much helpful
information from folks on the internet and paid back almost nothing.
Besides &lt;a href="https://evan.carlin.com/blog/e-street-garage/"&gt;one YouTube video&lt;/a&gt; and a
couple Stack Overflow answers when I was bored at my first job, I
don't think I've ever posted on someone else's site. I'm not sure how or
when that will change.&lt;/p&gt;
&lt;p&gt;I hope &lt;a href="https://www.engadget.com/2179165/meta-forum-groups-app/"&gt;Meta's new Forum
app&lt;/a&gt; is a
flop and people continue to post freely on forums that are indexable,
viewable without an account, and not run by such a turd of a company.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Shop Notes v2: The cat strikes back</title><link href="https://evan.carlin.com/blog/shop-notes-v2-the-cat-strikes-back/" rel="alternate"/><published>2026-05-10T00:00:00-06:00</published><updated>2026-05-10T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-05-10:/blog/shop-notes-v2-the-cat-strikes-back/</id><summary type="html">&lt;p&gt;Arctic cat disc rotor removal&lt;/p&gt;</summary><content type="html">&lt;h3 id="tool-foam"&gt;&lt;a class="toclink" href="#tool-foam"&gt;Tool Foam&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Working on my snowmobile again has me using my wrenches. My wrench
drawer has been on my list for organizing. I bought a CNC at the start
of this year and it has been very fun to organize the tools in a
drawer in Fusion 360 and then have the CNC cut out their profile in
some tool foam. On Saturday I made an insert for my wrenches.&lt;/p&gt;
&lt;p&gt;&lt;img alt="wrench tool foam" src="https://evan.carlin.com/static/images/wrench_tool_foam.jpg"&gt;&lt;/p&gt;
&lt;h2 id="snowmobile"&gt;&lt;a class="toclink" href="#snowmobile"&gt;Snowmobile&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that the sled is running there are just a couple of loose ends to
finish. Chief among them is fixing the brakes. They drag and
definitely need a bleed.&lt;/p&gt;
&lt;p&gt;To fix the drag my thinking was I'd need to
pull the brake pistons and give them a once-over with some fine Scotch
Brite. To do that I'd need to pull the caliper and to do that I'd need
to pull the disc rotor.&lt;/p&gt;
&lt;p&gt;&lt;img alt="rusty brake rotor" src="https://evan.carlin.com/static/images/rusty_rotor.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The job started with a minor hiccup. I stripped one of the socket head
bolts in the caliper. Thankfully by switching to a larger size hex key
I was able to get the bolt out. Not often that trick actually works.&lt;/p&gt;
&lt;p&gt;The disc rotor bolt was also somewhat of a challenge. It is an odd
shape and requires a special socket. I have the socket but I had a
hard time getting it to seat far enough on the bolt that I could put
the torque I needed to on the wrench.&lt;/p&gt;
&lt;p&gt;I made my way through, and once the outer half of the caliper and
brake rotor bolt were off I tried yanking on the disc rotor. No
movement. The rotor sits on splines on the track driveshaft. I figured
it was rusted on like many of the other parts on the sled.
It was getting late so I sprayed some PB Blaster on and gave it a night to soak.&lt;/p&gt;
&lt;p&gt;In the morning I gave the rotor another yank. Nothing. I sprayed on
some more PB Blaster and went on a hike. When I got back from my hike
I was very hopeful the rotor would finally break free. This time I
used a small pry bar. Still no movement. Bummer.&lt;/p&gt;
&lt;p&gt;A search through the forums turned up a couple of posts with people
facing the same problems. Many of the replies mentioned using pullers.
Hmm, I think I've seen those at Horror Freight. Should I go buy them?
I struggle in moments like this with what tool to buy. In general,
tools that I know I will repeatedly use I don't mind spending some
money on to get a well-made Western tool. But, for tools I rarely use
I'm okay buying a cheap Chinese version. The problem is when a project
is going sideways a good tool can straighten everything out. A bad
tool can make it 100x worse ("the most expensive tool is a cheap
tool"). I don't know that I'll use pullers enough to justify a nice
tool. The folks at Garage Journal recommend the SnapOn (figures...) as well
as the OTC or Proto. I've had very good luck with OTC tools and pretty
good with Proto. But, both would take a few days to get here and be
several times more expensive. I went back and forth but in the end
decided to stay cheap today and I bought the 4-piece puller set from Harbor
Freight.&lt;/p&gt;
&lt;p&gt;&lt;img alt="jaw pullers on the rotor" src="https://evan.carlin.com/static/images/jaw_pullers_on_roto_with_breaker_bar.jpg"&gt;&lt;/p&gt;
&lt;p&gt;It took some finagling to figure out how to get the jaws lined up and
locked in. But once they were on I started to crank. The first couple
times the jaws snapped off with a bang. In both cases the rotor itself
had bent and the jaws then slipped off. I think initially I was using
too small of a puller set. There was a balance between what jaws I
could fit in the limited space behind the rotor and what jaws were big
enough to get the job done.&lt;/p&gt;
&lt;p&gt;I switched to the 6" jaws and a 3 foot breaker bar. I really started
to put my weight on it. The wet noodles they use to make Pittsburgh
products gave way.&lt;/p&gt;
&lt;p&gt;&lt;img alt="broken jaw puller bolt" src="https://evan.carlin.com/static/images/broken_jaw_puller.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Dang! Should've bought the nice pullers... Now I had a rotor that
hadn't moved a millimeter and a puller stuck on with no easy way to
get it loosened. I devolved into a primate and started to wail on
the puller with a dead blow hammer. I believe this is the only time in
my entire life that this did anything even remotely positive! After many
blows I went to try and loosen the screw on the jaws and I
could twist it with my fingers. The rotor must be coming off! I
twisted off the puller and now there was enough space for the 8" jaws
to fit behind the rotor.&lt;/p&gt;
&lt;p&gt;&lt;img alt="rotor coming off" src="https://evan.carlin.com/static/images/brake_rotor_coming_off.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The 8" jaws made quick work of the rotor now that it had mostly come
off.&lt;/p&gt;
&lt;p&gt;&lt;img alt="rotor fully off " src="https://evan.carlin.com/static/images/brake_rotor_off.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Lots of rust on that thing. I don't think I've ever had to work that
hard to get a rusty piece off.&lt;/p&gt;
&lt;p&gt;Some carnage from the puller.&lt;/p&gt;
&lt;p&gt;&lt;img alt="plate steel dents" src="https://evan.carlin.com/static/images/plate_steel_dented_from_jaw_puller.jpg"&gt;&lt;/p&gt;
&lt;p&gt;That is 5 pieces of 1/8" plate steel that the lead screw of the puller
was pressed against. It looks like they stopped a bullet.&lt;/p&gt;
&lt;p&gt;&lt;img alt="bent jaw puller bolt" src="https://evan.carlin.com/static/images/bent_jaw_puller_bolt.jpg"&gt;&lt;/p&gt;
&lt;p&gt;A bent bolt from one of the puller arms. I'm very glad that didn't
break and send pieces flying.&lt;/p&gt;
&lt;p&gt;All's well that ends well. I'll be testing out Harbor Freight's
return policy tomorrow morning.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Shop Notes v1: The cat purrrs</title><link href="https://evan.carlin.com/blog/shop-notes-v1-the-cat-purrrs/" rel="alternate"/><published>2026-05-04T00:00:00-06:00</published><updated>2026-05-04T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-05-04:/blog/shop-notes-v1-the-cat-purrrs/</id><summary type="html">&lt;p&gt;Getting my snowmobile running again.&lt;/p&gt;</summary><content type="html">&lt;h2 id="the-players"&gt;&lt;a class="toclink" href="#the-players"&gt;The Players&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I've been kicking around the idea of keeping a journal of things I do
in the garage / around the house. My sister, Emily, keeps a journal of
&lt;a href="https://ethink.substack.com/"&gt;her woodworking journeys&lt;/a&gt; and while I was talking to her on the phone
yesterday she encouraged me to start writing my own posts. With her
encouragement and a successful day in the shop I figured now is as
good of a time as any for my first post. Also, if you'd rather read
good writing instead of my boring prose head over to her site.&lt;/p&gt;
&lt;h2 id="the-setup"&gt;&lt;a class="toclink" href="#the-setup"&gt;The Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A fine feature of rural areas that get snow: rundown houses with even
more rundown piles of decaying snowmobiles in the yard. In November of
2023 I had the great fortune of helping one of those people unload
some of their junk. For $600 I picked up a barely running 2006 Arctic
Cat M7 snowmobile.&lt;/p&gt;
&lt;p&gt;&lt;img alt="snowmobile drive home" src="https://evan.carlin.com/static/images/snowmobile_drive_home.jpg"&gt;&lt;/p&gt;
&lt;h2 id="the-hook"&gt;&lt;a class="toclink" href="#the-hook"&gt;The Hook&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The new sled was in poor but serviceable condition. These sleds are
known to be durable and there is decent availability of second-hand
parts. So, I brought her home and tore in.&lt;/p&gt;
&lt;p&gt;The rough plan was to clean up all of the 2-stroke oil splattered
across the engine, install a new top end, cross my fingers, and be
surfing pow by spring.&lt;/p&gt;
&lt;p&gt;&lt;img alt="packed garage" src="https://evan.carlin.com/static/images/packed_garage.jpg"&gt;&lt;/p&gt;
&lt;h2 id="the-tale"&gt;&lt;a class="toclink" href="#the-tale"&gt;The Tale&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The project started with a bang. I got the engine out quickly. Cleaned
it up and started disassembling. I swapped cylinder jugs for new ones.
Put in new pistons. Replaced some old hoses. Yada yada yada. Breaking
things apart is easy.&lt;/p&gt;
&lt;p&gt;&lt;img alt="engine on my bench" src="https://evan.carlin.com/static/images/engine_out_of_sled.jpg"&gt;&lt;/p&gt;
&lt;h2 id="the-wire"&gt;&lt;a class="toclink" href="#the-wire"&gt;The Wire&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Assembly is where I hit the wall. The clutches both needed to be
rebuilt. The shocks were shot. And the engine had many suprises.
When you rebuild a 2-stroke engine, you get everything
back together, plug up all of the holes in the engine, pump some air
in, and then see if/where air leaks out. The goal is for little to no
air to leak out. This engine was leaking like a sieve. I could hardly
pinpoint a single place to get started. Out came the high-temp
silicone sealant. I unbolted much of the engine and slathered it all
over. The leaks slowed but the engine was still losing too much air.
More silicone. Fewer leaks. Rinse and repeat. Eventually I narrowed
the final (and worst) leak down to a crack in the "y-pipe". That is the part
of the exhaust that comes straight off of the cylinders. Doh. That's
where all of the oil soaking the engine was coming from!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Installing new cylinders" src="https://evan.carlin.com/static/images/snowmobile_new_jugs.jpg"&gt;&lt;/p&gt;
&lt;h2 id="the-shut-out"&gt;&lt;a class="toclink" href="#the-shut-out"&gt;The Shut Out&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This started an Olympic-caliber project detour. The section of exhaust
that was cracked is no longer manufactured. The ones available on eBay
looked equally as rusty as mine but I bought one anyway. It was
cracked in the same spot. I've wanted to learn how to weld and this
seemed like a great time to do it. I could learn a new skill and fix
my project.&lt;/p&gt;
&lt;p&gt;Denver has a very cool &lt;a href="https://denvertoollibrary.org/"&gt;tool library&lt;/a&gt;. You can rent tools, take classes,
and meet like-minded folks. Weeks went by. The welding class came. I
took it and could barely get two pieces of metal stuck together. It
turns out welding is actually a skill that takes practice and not
something I could pick up in an evening (who would've guessed!?). I
decided I'd take the level 2 course to get more experience. More
dismal welds. I then bought a welder and
started practicing at home. &lt;del&gt;When I finally felt like I was ready&lt;/del&gt;.
When I finally didn't feel like waiting anymore, I took my
preschool-level welding skills to my exhaust. What I would come to
learn is this exhaust is one of the more challenging welding tasks I
could've given myself. The metal is exceptionally dirty. It is cast
steel. The area I needed to weld was hard to reach. The weld needed to
be airtight. And I bought a TIG welder. TIG welders really dislike
dirty metal. For the non-welders, that is all a recipe for welding
to not go well. Add in my impatience and I just blew hole after hole
in the metal. What started as a minor crack turned into a yawning
cavern. I eventually got some metal glommed on. But, it was definitely
not air tight.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Preheat and welding ypipe" src="https://evan.carlin.com/static/images/ypipe_preheat_and_welding.jpg"&gt;&lt;/p&gt;
&lt;p&gt;At this point I think winter had passed. I was sad about my welding
journey and mostly left the snowmobile alone. Aside: A colleague
recently asked me how I work on so many projects. My answer - by
starting many and finishing few.&lt;/p&gt;
&lt;p&gt;More time passed. I proposed to my girlfriend (now wife:)) and we
moved to Fort Collins. The night before the movers came, I manically
bolted things back together. Not before I had to unpack all of my
tools I had neatly packed that day. If I was going to move this
snowmobile it had to be put back together enough to be strapped to a
trailer and driven 60 miles.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Vine st garage packing" src="https://evan.carlin.com/static/images/vine_st_garage_packing.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Upon arriving in Fort Collins, the snowmobile sat for another winter
collecting dust. Too many new house projects to distract myself with.&lt;/p&gt;
&lt;h2 id="the-sting"&gt;&lt;a class="toclink" href="#the-sting"&gt;The Sting&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The weekend before last I decided enough time had passed. No more
distractions. It was time to get the snowmobile running or sell it. I
bit the bullet and found a company selling a new upgraded exhaust. I
ordered it and a few other odds and ends and was off to the races. I
really wasn't that far from getting this snowmobile running. All told
it took me two Sundays to get everything back together.&lt;/p&gt;
&lt;p&gt;Here it is on first start. Volume up.&lt;/p&gt;
&lt;video controls width="640"&gt;
  &lt;source src="https://evan.carlin.com/static/videos/snowmobile_start.mp4" type="video/mp4"&gt;
&lt;/video&gt;

&lt;p&gt;Thanks to Em for the encouragement to write this. It brings me a great
deal of happiness on a weekend when I'm out in the shop to think of
her and know she's probably in her own garage lost in a project as
well. And thanks to my neighbors for putting up with the noise and
smoke. Don't show that video to the EPA.&lt;/p&gt;
&lt;p&gt;Lessons learned&lt;/br&gt;
- &lt;a href="https://christopherschwarz.substack.com/p/quality-is-job-no-4"&gt;Job 1 is "job
  done"&lt;/a&gt;.
  My wife likes to describe most of my shop time as "low priority
  tasking." I did an ungodly amount of that in this project. I
  should've written a todo list every day and stuck to it.&lt;/br&gt;
- Don't mix projects. To save myself a couple hundred dollars on an
  exhaust pipe I ended up spending several hundred dollars on classes
  and welding equipment. All to end up right back where I started. I
  wanted to learn how to weld. But, that should've been its own
  activity. I think it set my welding journey back by how frustrated I
  got.&lt;/br&gt;
- A well-written shop manual is a work of art. The Arctic Cat shop
  manual I used had spectacular descriptions and a picture at every
  confusing step. I pray with all of my might that AI slop doesn't
  crush this industry. When embarking on a project, try to see if
  there is a good manual beforehand. It will make the whole thing a
  lot more fun.&lt;/br&gt;
- Most important: Take a thousand pictures. As the years passed, I
  invariably forgot how things went together. Shop manuals are great
  but they can't go into every little detail. Being able to look back
  from many angles is extremely helpful. I even tried &lt;a href="https://evan.carlin.com/blog/e-street-garage/"&gt;my hand at filming&lt;/a&gt;.&lt;/br&gt;&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Notes: Autodesk Fusion 360</title><link href="https://evan.carlin.com/blog/notes-autodesk-fusion-360/" rel="alternate"/><published>2026-03-07T00:00:00-07:00</published><updated>2026-03-07T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-03-07:/blog/notes-autodesk-fusion-360/</id><summary type="html">&lt;p&gt;Bits of Fusion 360 to remember.&lt;/p&gt;</summary><content type="html">&lt;h3 id="cutting-a-toolpath-in-half"&gt;&lt;a class="toclink" href="#cutting-a-toolpath-in-half"&gt;Cutting a toolpath in half&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Nice to be able to cut toolpaths in half when working on odd 3D
shapes. That way I can clamp half of the piece down really well on one
half while milling the other half and then switch.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a construction plane offset from the surface you want to mill&lt;/li&gt;
&lt;li&gt;Draw a sketch on that plane around the area you want to mill&lt;/li&gt;
&lt;li&gt;In manufacture create your toolpath (adaptive, parallel, etc)&lt;/li&gt;
&lt;li&gt;In the geometry tab set the machining boundary to your sketch.
   Under Model select the faces to mill (&lt;em&gt;KEY&lt;/em&gt;: uncheck "Include Setup
   Model". Under Avoid/Machine Surfaces you can select surfaces to
   avoid if you find your toolpath running into anything weird.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ref:
https://forums.autodesk.com/t5/fusion-manufacture-forum/parallel-tool-path-is-plunging-on-the-end-of-a-face-selection/td-p/10035789#M71684&lt;/p&gt;
&lt;h3 id="inserting-a-canvas-and-tracing"&gt;&lt;a class="toclink" href="#inserting-a-canvas-and-tracing"&gt;Inserting a canvas and tracing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is useful for things like tracing a wrench to build a &lt;a href="https://web.archive.org/web/20260322201619/https://www.fastcap.com/product/kaizen-foam"&gt;tool foam
drawer&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Insert &amp;gt; Canvas&lt;/li&gt;
&lt;li&gt;Document explorer &amp;gt; Canvases &amp;gt; canvas you inserted &amp;gt; calibrate&lt;/li&gt;
&lt;li&gt;In the edit menu of the canvas you can flip it
   horizontally/vertically&lt;/li&gt;
&lt;li&gt;Trace a fit point spline around it. For tool foam I've found being
   a tiny bit wide of the tool is best (too tight then it is hard to
   get in and out)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="moving-a-canvas-and-spline-sketch"&gt;&lt;a class="toclink" href="#moving-a-canvas-and-spline-sketch"&gt;Moving a canvas and spline sketch&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once I've inserted a canvas and drawn a spline it can be nice to move
them together to adjust their positioning.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Document explorer &amp;gt; canvases &amp;gt; canvas you inserted &amp;gt; edit &amp;gt; adjust
   x/y&lt;/li&gt;
&lt;li&gt;edit sketch &amp;gt; draw box around all points &amp;gt; press m key (for move) &amp;gt;
   move x/y same amount as canvas&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="variables"&gt;&lt;a class="toclink" href="#variables"&gt;Variables&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Create:
s (hotkey to open shortcuts menu) -&amp;gt; Change Parameters -&amp;gt; Click + button in dialog box to create new use&lt;/p&gt;
&lt;p&gt;Use:
When you need an amount just type the name of the var. Can be used in expressions too (2+outside_diameter)&lt;/p&gt;
&lt;h3 id="dogbones"&gt;&lt;a class="toclink" href="#dogbones"&gt;Dogbones&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Install this lib to make it easy to place dogbones for mortise/tenon in wood.
https://github.com/DVE2000/Dogbone&lt;/p&gt;
&lt;p&gt;s -&amp;gt; dogbone. You can manually select/deselect dogbones.&lt;/p&gt;
&lt;h3 id="mortise-and-tenons"&gt;&lt;a class="toclink" href="#mortise-and-tenons"&gt;Mortise and Tenons&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://forge.carlin.com/e-carlin/F360MortiseAndTenon"&gt;https://forge.carlin.com/e-carlin/F360MortiseAndTenon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;s -&amp;gt; Mortise &amp;amp; Tenon&lt;/p&gt;
&lt;h3 id="french-cleats"&gt;&lt;a class="toclink" href="#french-cleats"&gt;French Cleats&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://forge.carlin.com/e-carlin/F360FrenchCleat"&gt;https://forge.carlin.com/e-carlin/F360FrenchCleat&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;s -&amp;gt; French Cleat Hook&lt;/p&gt;
&lt;h3 id="midplane-in-a-body"&gt;&lt;a class="toclink" href="#midplane-in-a-body"&gt;Midplane in a body&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Select two opposite faces on the body (like the two ends of the board).&lt;/p&gt;
&lt;h2 id="aligning-design-for-manufacture"&gt;&lt;a class="toclink" href="#aligning-design-for-manufacture"&gt;Aligning design for manufacture&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Arrange requires a commercial license. This is how to do it manually on the free personal license.&lt;/p&gt;
&lt;h3 id="1-convert-bodies-to-components"&gt;&lt;a class="toclink" href="#1-convert-bodies-to-components"&gt;1. Convert bodies to components&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Browser → expand &lt;strong&gt;Bodies&lt;/strong&gt; → Shift-select all bodies&lt;/li&gt;
&lt;li&gt;Right-click → &lt;strong&gt;Create Components from Bodies&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-create-a-manufacturing-model"&gt;&lt;a class="toclink" href="#2-create-a-manufacturing-model"&gt;2. Create a Manufacturing Model&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Switch to &lt;strong&gt;MANUFACTURE&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SETUP → Create Manufacturing Model&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Right-click &lt;strong&gt;Manufacturing Model 1&lt;/strong&gt; → &lt;strong&gt;Edit Manufacturing Model&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-align-each-part-flat-on-xy"&gt;&lt;a class="toclink" href="#3-align-each-part-flat-on-xy"&gt;3. Align each part flat on XY&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MODIFY → Align&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select the part's large flat face → select &lt;strong&gt;Origin → XY Plane&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flip&lt;/strong&gt; if it lands below the plane → &lt;strong&gt;OK&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Repeat for each part&lt;/li&gt;
&lt;li&gt;Check from &lt;strong&gt;FRONT&lt;/strong&gt; view: all parts sit on the ground line&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-space-the-parts-apart"&gt;&lt;a class="toclink" href="#4-space-the-parts-apart"&gt;4. Space the parts apart&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TOP&lt;/strong&gt; view → &lt;strong&gt;MODIFY → Move/Copy&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Gap between parts: ≥ 2× bit diameter&lt;/li&gt;
&lt;li&gt;Accept &lt;strong&gt;Capture Position&lt;/strong&gt; if prompted&lt;/li&gt;
&lt;li&gt;Verify gaps with &lt;strong&gt;INSPECT → Measure&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finish Edit&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="5-create-the-setup"&gt;&lt;a class="toclink" href="#5-create-the-setup"&gt;5. Create the Setup&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SETUP → New Setup&lt;/strong&gt; → Milling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt;: Manufacturing Model 1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WCS&lt;/strong&gt;: Z up, origin at stock box point (top corner)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stock&lt;/strong&gt;: Fixed size box = sheet size × thickness&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/></entry><entry><title>Easy Links to Source Code with Emacs</title><link href="https://evan.carlin.com/blog/easy-links-to-source-code-with-emacs/" rel="alternate"/><published>2026-01-22T00:00:00-07:00</published><updated>2026-01-22T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-01-22:/blog/easy-links-to-source-code-with-emacs/</id><summary type="html">&lt;p&gt;An elisp function to generate links to source code&lt;/p&gt;</summary><content type="html">&lt;p&gt;This little bit of elisp is probably my most used command. I use it a
dozen times a day for sending links to colleagues. When run with my
cursor over a line of code it will generate a GitHub url linking
straight to the line of code.&lt;/p&gt;
&lt;p&gt;Someday I'll make it smart enough to read the git remote and pull the
url from there so it can work with non-GitHub repos.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(global-set-key (kbd &amp;quot;C-c cg&amp;quot;)
                'ec-generate-github-file-url)
(defun ec-generate-github-file-url ()
  &amp;quot;Generate a GitHub URL for the current buffer file and line number.&amp;quot;
  (interactive)
  (if buffer-file-name
      (progn
        (kill-new
         (format &amp;quot;https://github.com/%s/blob/%s/%s#L%d&amp;quot;
                 (replace-regexp-in-string
                  &amp;quot;https://github.com/\\(.*?\\)\\(.git\\)?$&amp;quot; &amp;quot;\\1&amp;quot;
                  (string-trim (shell-command-to-string
                                &amp;quot;git config --get remote.origin.url&amp;quot;)))
                 (string-trim (shell-command-to-string &amp;quot;git rev-parse HEAD&amp;quot;))
                 (file-relative-name buffer-file-name
                                     (vc-git-root buffer-file-name))
                 (line-number-at-pos)))
        (message &amp;quot;Copied GitHub URL&amp;quot;))
    (message &amp;quot;Not visiting a file.&amp;quot;)))
&lt;/code&gt;&lt;/pre&gt;</content><category term="blog"/></entry><entry><title>LLM's Can Delete Code Too</title><link href="https://evan.carlin.com/blog/llms-can-delete-code-too/" rel="alternate"/><published>2026-01-22T00:00:00-07:00</published><updated>2026-01-22T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2026-01-22:/blog/llms-can-delete-code-too/</id><summary type="html">&lt;p&gt;An LLM helped delete 1400 lines.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the past month I’ve opened 2 PRs that deleted 2 confusing features
my team was fighting against. In total it was about 1,400 lines of
code. It turns out both of these features were completely dead code
and the battle was in vain. An LLM was no use in finding out that the code
was dead. It led all of us down confusing paths, spinning tales about
what work we needed to do to support these features. But once I
realized the code was dead, I could prompt an agent to help me clean
up the code.&lt;/p&gt;
&lt;p&gt;Pre-LLM, one of the more tedious parts of deleting code is finding all
of the code that can be deleted after each round of deletion is
through. I delete some code, now I need to go find all of the code
that was only used by the deleted code, delete it, recurse. I found
prompting an agent to help with this dramatically sped up the process.&lt;/p&gt;
&lt;p&gt;I’m not positive why I trust an LLM to find all related dead code when
it couldn’t even tell me the code was dead in the first place. But I
have a sense (and some double-checking told me) that it seemed to be
doing a good enough job.&lt;/p&gt;
&lt;p&gt;Best case is that dead code isn’t left lingering around codebases. And
LLMs struggle to find it (as far as I can tell). But once a human
identifies it, an LLM is pretty decent at cleaning up the mess. About the
least it can do with all of the verbose brain-dead code it seems to
create.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Stripe is Good</title><link href="https://evan.carlin.com/blog/stripe-is-good/" rel="alternate"/><published>2025-02-28T00:00:00-07:00</published><updated>2025-02-28T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2025-02-28:/blog/stripe-is-good/</id><summary type="html">&lt;p&gt;Stripe makes my life easier.&lt;/p&gt;</summary><content type="html">&lt;p&gt;It is always easier to tear down than to create. I don’t consider
myself a negative person, but I’ve done some complaining on this blog.
I’d like to change course on that.&lt;/p&gt;
&lt;p&gt;Today, I was implementing Stripe for the second time (once before on
&lt;a href="https://mossloyalty.com/"&gt;Moss&lt;/a&gt; and now on
&lt;a href="https://www.sirepo.com/en/"&gt;Sirepo&lt;/a&gt;). I have to say that each time
has been a pleasure.&lt;/p&gt;
&lt;h3 id="good-docs"&gt;&lt;a class="toclink" href="#good-docs"&gt;Good Docs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like, seriously good docs. Like someone deeply thought about them. And
then someone else did a really thorough review. And then someone else
diligently kept them up to date. And then someone listened to
developers and added more docs for specific things that were causing
people confusion.&lt;/p&gt;
&lt;p&gt;Documentation like this is not easy to make. It is a whole job
function and can’t just be tacked on after the fact. I’m really
grateful for their work.&lt;/p&gt;
&lt;p&gt;A prime example is the first docs you’ll probably come across: &lt;a href="https://docs.stripe.com/checkout/embedded/quickstart"&gt;the
quickstart&lt;/a&gt;.
They have major frameworks/languages ready to go. They also have raw
HTML as an escape hatch (we use AngularJS, so I started with the basic
HTML docs and adapted). These docs are thoughtfully written. They
cover enough of the initial use case to be useful. It is a fine line
in quickstarts to balance useless toy “hello world” examples vs.
information overload, and they nailed it.&lt;/p&gt;
&lt;h3 id="cli-to-make-testing-easy"&gt;&lt;a class="toclink" href="#cli-to-make-testing-easy"&gt;CLI to Make Testing Easy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At Moss, we haven’t yet implemented handling webhook events. On
Sirepo, we are going to want to handle them out of the gate. When I
was building the plan for implementing Stripe in Sirepo, I set aside
extra time for developing/testing the webhook. With other tools,
testing webhooks can be a pain. They don’t have a good way to trigger
events in development to easily test. Not the case with Stripe.&lt;/p&gt;
&lt;p&gt;First, one needs to install the CLI. They have that &lt;a href="https://docs.stripe.com/stripe-cli#install"&gt;covered in
spades&lt;/a&gt;. They handle all
major OSs/distros (and then some). And it is just a painless process.
Getting the RPM for Fedora and installing it took me 30 seconds.&lt;/p&gt;
&lt;p&gt;The CLI has a &lt;a href="https://docs.stripe.com/webhooks#local-listener"&gt;local
listener&lt;/a&gt;, so you can
easily forward real webhooks to your development server running on
localhost. No ngrok or socat needed here. Just a simple tool that does
exactly what so many systems like it need.&lt;/p&gt;
&lt;p&gt;Finally, you can &lt;a href="https://docs.stripe.com/webhooks#trigger-test-events"&gt;trigger any
event&lt;/a&gt;. They
make this dead simple (also a consequence of their good API design).&lt;/p&gt;
&lt;p&gt;I had set aside half a day for hacking up some testing of the
different webhook events we are going to need to handle. In the end, I
spent only about an hour implementing and testing. That is a huge gain
and is thanks to the work Stripe has done to make developers’ lives
easier.&lt;/p&gt;
&lt;h3 id="bugs"&gt;&lt;a class="toclink" href="#bugs"&gt;Bugs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I was cutting a release one night for Moss and ran into a bug caused
by the &lt;a href="https://github.com/stripe/stripe-python/issues/1437"&gt;Stripe Python
SDK&lt;/a&gt;. Stripe was
quick to fix it. My only slight comment is that for
a regression like that, one should add a test. But we can’t all be
perfect all the time. :)&lt;/p&gt;
&lt;p&gt;Thank you, Stripe! You’ve saved me time and made your product easy to
integrate. Please take my money!&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Cloud Sync/Storage</title><link href="https://evan.carlin.com/blog/cloud-syncstorage/" rel="alternate"/><published>2025-01-22T00:00:00-07:00</published><updated>2025-01-22T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2025-01-22:/blog/cloud-syncstorage/</id><summary type="html">&lt;p&gt;Features I'd like from a cloud sync/storage provider.&lt;/p&gt;</summary><content type="html">&lt;p&gt;For the past three years, my family and I have changed cloud
sync/storage providers each year. We started with Google Drive/Photos,
then moved to Dropbox, and most recently to OneDrive. Don’t ask me why
we’ve changed so many times. Maybe we’re cheap? Maybe we’re dumb?
Probably both. Each provider has its pros and cons, but OneDrive has
been the worst so far. I’m thinking I’ll switch back to Dropbox, which
seemed to be the least evil.&lt;/p&gt;
&lt;p&gt;As part of this, I thought about the features I need from a cloud
storage provider. I’m writing them down so that next year, when I get
annoyed (hopefully not!), I can remember what I actually need and see
what’s out there.&lt;/p&gt;
&lt;h3 id="sync-and-backup-duh"&gt;&lt;a class="toclink" href="#sync-and-backup-duh"&gt;Sync and backup, duh&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is the basics. I need to be able to sync with different phones
and computers and count on the service to not lose my data. I don’t
want all data in all places at all times, but I want to be able to
pull files onto a device as I need them.&lt;/p&gt;
&lt;h3 id="mobile-app-with-photo-backup"&gt;&lt;a class="toclink" href="#mobile-app-with-photo-backup"&gt;Mobile app with photo backup&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I need an iOS app that backs up all photos and videos I take. I never
want to think about this feature. It better just work day in and day
out without me fiddling. I also want some sort of logic to decide how
these photos are named. Ideally, they should be named using the date
and time they were taken.&lt;/p&gt;
&lt;h3 id="view-pictures"&gt;&lt;a class="toclink" href="#view-pictures"&gt;View pictures&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I want to be able to view all my pictures in the web and iOS UIs. I
want this to be fast. I don’t know what it is, but some services are
just terribly slow when loading thumbnails.&lt;/p&gt;
&lt;h3 id="crud-with-text-files"&gt;&lt;a class="toclink" href="#crud-with-text-files"&gt;CRUD with text files&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I leave small text files scattered throughout folders I back up. For
example, I have a folder where I store information about a snowmobile
I’m bringing back to life. I have a shop_log.txt where I record
reminders about the work I did, a todo.txt for work I need to do, and
a to_buy.txt for parts I need to order. The UI for working with these
files can be extremely simple, but I need to be able to access and
edit them on both the web and iOS.&lt;/p&gt;
&lt;h3 id="google-docssheets-integration"&gt;&lt;a class="toclink" href="#google-docssheets-integration"&gt;Google Docs/Sheets integration&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For better or worse, I use Google Docs and Sheets when I have to
create those types of documents. I’d like some sort of integration
between the cloud storage provider and Google. Ideally, I want to be
able to search for files on the storage provider, click on them, and
have them open in Docs or Sheets. From there, I can do my work, and it
should be automatically saved back to the storage provider.&lt;/p&gt;
&lt;h3 id="ability-to-ignore-filesdirectories"&gt;&lt;a class="toclink" href="#ability-to-ignore-filesdirectories"&gt;Ability to ignore files/directories&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I need to be able to ignore certain file types and directories. For
example, I have multiple node_modules directories under ~/OneDrive.
OneDrive diligently backs them up—even though I have no desire for
that. To make matters worse, when looking at pictures in OneDrive, it
shows me thousands of images from node_modules.&lt;/p&gt;
&lt;h3 id="nice-to-have-photos-ai"&gt;&lt;a class="toclink" href="#nice-to-have-photos-ai"&gt;Nice to have: Photos AI&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I should really organize my photos, but I never make the time. So, I
like having the ability to use AI searches to have some shot at
finding photos.&lt;/p&gt;
&lt;p&gt;I think that’s all. I’ll update as I come across other things.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Emacs</title><link href="https://evan.carlin.com/blog/emacs/" rel="alternate"/><published>2025-01-07T00:00:00-07:00</published><updated>2025-01-07T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2025-01-07:/blog/emacs/</id><summary type="html">&lt;p&gt;Bits of Emacs I want to remember.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Below is a running list of things I've learned about Emacs that I want
to remember but I'm unlikely to recall off the top of my head.&lt;/p&gt;
&lt;h2 id="kill-buffers"&gt;&lt;a class="toclink" href="#kill-buffers"&gt;Kill buffers&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;C-x C-b # open helm-buffers-list
D # mark buffers for deletion
x # execute deletion
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="m-x-proced"&gt;&lt;a class="toclink" href="#m-x-proced"&gt;M-x proced&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like top but in Emacs&lt;/p&gt;</content><category term="blog"/></entry><entry><title>HomeNotes: Electricity</title><link href="https://evan.carlin.com/blog/homenotes-electricity/" rel="alternate"/><published>2025-01-04T00:00:00-07:00</published><updated>2025-01-04T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2025-01-04:/blog/homenotes-electricity/</id><summary type="html">&lt;p&gt;Reminders for electrical home work&lt;/p&gt;</summary><content type="html">&lt;h2 id="outlets"&gt;&lt;a class="toclink" href="#outlets"&gt;Outlets&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Black (hot) to gold, white to silver&lt;/li&gt;
&lt;li&gt;Line is incoming power. Load is outgoing power. For example, imagine
  a kitchen counter wall with many outlets. The line power comes from
  the electrical panel (or one of the other outlets) and then the load
  power goes to power the next outlet in the line. When the wires
  reach the new outlet then they go into the line power for it.
  Recurse.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="code"&gt;&lt;a class="toclink" href="#code"&gt;Code&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GFCI protection is needed on each circuit in a kitchen. So, if you
  have multiple outlets you can wire the first one to be GFCI and that
  protects the whole series of outlets on that circuit.&lt;/li&gt;
&lt;li&gt;Outlet orientation is notdefined. But, generally hot side (smaller
  opening) is on the right hand side when outlet is vertical.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/></entry><entry><title>GitHub URL UX</title><link href="https://evan.carlin.com/blog/github-url-ux/" rel="alternate"/><published>2024-11-17T00:00:00-07:00</published><updated>2024-11-17T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-11-17:/blog/github-url-ux/</id><summary type="html">&lt;p&gt;GitHub provides a great UX with their URLs.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was adding a GitHub URL to a presentation to reference
the source code of what I was talking about. The URL was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/slaclab/slactwin/blob/3c84ca7d4b3b31c21d07a95cf77766507d4e1c97/slactwin/pkcli/db.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's pretty long for text on a slide and took up more real estate
than I liked. A hyperlink would work, but I wanted people to see that
the link was from GitHub and hopefully that would prompt them to click
on it and look at the source code. Git (and by extension GitHub) works
with shortened commit hashes. Usually, just the first few characters
of a hash are enough to uniquely identify a commit. Looking at the
repo, it seems like GitHub is using the first 7 characters. So, I
tried shortening the URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/slaclab/slactwin/blob/3c84ca7/slactwin/pkcli/db.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voila! That works! That will fit nicely on my slide. I find GitHub, in
general, is kind to its users regarding URLs. For example, you can go
to &lt;code&gt;/issues/pr_number&lt;/code&gt;, and it will redirect to &lt;code&gt;/pulls/pr_number&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/radiasoft/sirepo/issues/7292
# redirects because 7292 is actually a PR
https://github.com/radiasoft/sirepo/pull/7292
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Often in issues, emails, Slack, etc., people will put something like
&lt;code&gt;#1234&lt;/code&gt;. Just by looking at the number, it is impossible to know
whether it is an issue or a PR. So, GitHub lets you just say it is one
or the other, and it will do the work of redirecting if you guessed
wrong. One caveat is that (to my eyes) there is confusion in their
URLs regarding words being plural:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pull is singular
https://github.com/radiasoft/sirepo/pull/7292
# issues is plural
https://github.com/radiasoft/sirepo/issues/7292
# pull -&amp;gt; pulls searches author:&amp;lt;number&amp;gt;
https://github.com/radiasoft/sirepo/pulls/7292
# issues -&amp;gt; issue is a 404
https://github.com/radiasoft/sirepo/issue/7292
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's a bit odd. I wonder why? Also, in case you didn't know, when
you want to provide a link to a line of code, you can select the line
number next to the link, and GitHub will link directly to that line
(with it highlighted). Before copying the link, you can press &lt;code&gt;y&lt;/code&gt;,
which will change &lt;code&gt;/blob/master&lt;/code&gt; in the URL to &lt;code&gt;/blob/&amp;lt;commit_sha&amp;gt;&lt;/code&gt;.
Including the commit SHA in links ensures future readers go to exactly
the line of code you were looking at when you created the link.
Without this, who knows what line 62 in foo.py on master will be
pointing to 3 years from now.&lt;/p&gt;
&lt;h2 id="ps"&gt;&lt;a class="toclink" href="#ps"&gt;P.S.&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While writing this, I learned git &lt;a href="https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev"&gt;has
configuration&lt;/a&gt;
for the number it uses when abbreviating hashes. Nice.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>TypeScript (+tsx) mode for Emacs.</title><link href="https://evan.carlin.com/blog/typescript-tsx-mode-for-emacs/" rel="alternate"/><published>2024-10-07T00:00:00-06:00</published><updated>2024-10-07T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-10-07:/blog/typescript-tsx-mode-for-emacs/</id><summary type="html">&lt;p&gt;How to enable a workable TypeScript (and TSX) mode for Emacs.&lt;/p&gt;</summary><content type="html">&lt;p&gt;It has been a while since I did much development with React. Last time
I had a good setup I was using VSCode. But, I wanted to do some work
with it so I took some time to get a good Emacs setup.&lt;/p&gt;
&lt;p&gt;The React docs now mention to use a tool like Next.js on top of React
(some great fodder for another article...). So, I setup Next.js and
the default config is TypeScript. Of course, Emacs doesn't have great
support for TypeScript out of the box. I briefly felt the pang of
wanting to switch back to VSCode. But, I dug in and decide to make it
work in Emacs. Below are my notes on how you too can get it setup.&lt;/p&gt;
&lt;p&gt;I've done the following procedure successfully on an M2 Macbook. An
Intel Mac probably won't require anything different. For Linux you'll
have to use your OS's package manager (I use homebrew below) but the
rest should work the same. No clue about other systems.&lt;/p&gt;
&lt;h2 id="compile-emacs-with-tree-sitter"&gt;&lt;a class="toclink" href="#compile-emacs-with-tree-sitter"&gt;Compile Emacs with Tree Sitter&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mastering Emacs has &lt;a href="https://www.masteringemacs.org/article/how-to-get-started-tree-sitter"&gt;a wonderful
article&lt;/a&gt;
on how to get Emacs with Tree Sitter support as well as some
background on Tree Sitter. I copied quite a bit of my install directly
from that article.&lt;/p&gt;
&lt;p&gt;Emacs has Tree Sitter support but it isn't enabled by default (as of
writing). So, you'll likely need to compile Emacs from source with
Tree Sitter enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
set -eou pipefail

brew install wget
cd ~
declare v='29.4' # In 29 emacs has tree sitter support but need to be compiled with it
if [[ ! -f &amp;quot;emacs-$v.tar.gz&amp;quot; || ! -d &amp;quot;emacs-$v&amp;quot; ]]; then
    wget &amp;quot;https://ftp.gnu.org/pub/gnu/emacs/emacs-$v.tar.gz&amp;quot;
    tar -zxf &amp;quot;emacs-$v.tar.gz&amp;quot;
fi
cd &amp;quot;emacs-$v&amp;quot;
# Install adapted from https://www.adventuresinwhy.com/post/compiling-emacs-with-tree-sitter/
brew install \
    autoconf \
    gcc \
    giflib \
    gnutls \
    imagemagick \
    jansson \
    jpeg \
    libgccjit \
    libpng \
    librsvg \
    libtiff \
    texinfo \
    tree-sitter
CC=gcc-12 ./autogen.sh
# --with-tree-sitter is all you really need. I like the rest so I can view images.
# --with-native-comilation=aot used to really speed up Emacs but the effects seem
# to diminish with every release. Still can't hurt.
CPPFLAGS=&amp;quot;-I/opt/homebrew/opt/jpeg/include&amp;quot; LDFLAGS=&amp;quot;-L/opt/homebrew/opt/jpeg/lib&amp;quot; ./configure \
    --with-gif \
    --with-imagemagick \
    --with-jpeg \
    --with-native-compilation=aot \
    --with-png \
    --with-rsvg \
    --with-tiff \
    --with-tree-sitter \
    --with-x-toolkit=gtk3 \
    --with-xwidgets
make install
mv nextstep/Emacs.app /Applications
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay. You should now be able to run the Emacs app.&lt;/p&gt;
&lt;h2 id="adding-tsx-modeel"&gt;&lt;a class="toclink" href="#adding-tsx-modeel"&gt;Adding tsx-mode.el&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm using &lt;a href="https://github.com/orzechowskid/tsx-mode.el"&gt;tsx-mode.el&lt;/a&gt;
to add a major mode for TypeScript in Emacs. To install it you'll need
to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd emacs.d # Whever you keep your .el files
# Add to your init.el.
cat &amp;gt;&amp;gt; init.el &amp;lt;&amp;lt;'EOF'
;; Enables downloading of grammars for different languages
(setq treesit-language-source-alist
   '((bash &amp;quot;https://github.com/tree-sitter/tree-sitter-bash&amp;quot;)
     (cmake &amp;quot;https://github.com/uyha/tree-sitter-cmake&amp;quot;)
     (css &amp;quot;https://github.com/tree-sitter/tree-sitter-css&amp;quot;)
     (elisp &amp;quot;https://github.com/Wilfred/tree-sitter-elisp&amp;quot;)
     (go &amp;quot;https://github.com/tree-sitter/tree-sitter-go&amp;quot;)
     (html &amp;quot;https://github.com/tree-sitter/tree-sitter-html&amp;quot;)
     (javascript &amp;quot;https://github.com/tree-sitter/tree-sitter-javascript&amp;quot; &amp;quot;master&amp;quot; &amp;quot;src&amp;quot;)
     (json &amp;quot;https://github.com/tree-sitter/tree-sitter-json&amp;quot;)
     (make &amp;quot;https://github.com/alemuller/tree-sitter-make&amp;quot;)
     (markdown &amp;quot;https://github.com/ikatyang/tree-sitter-markdown&amp;quot;)
     (python &amp;quot;https://github.com/tree-sitter/tree-sitter-python&amp;quot;)
     (toml &amp;quot;https://github.com/tree-sitter/tree-sitter-toml&amp;quot;)
     (tsx &amp;quot;https://github.com/tree-sitter/tree-sitter-typescript&amp;quot; &amp;quot;master&amp;quot; &amp;quot;tsx/src&amp;quot;)
     (typescript &amp;quot;https://github.com/tree-sitter/tree-sitter-typescript&amp;quot; &amp;quot;master&amp;quot; &amp;quot;typescript/src&amp;quot;)
     (yaml &amp;quot;https://github.com/ikatyang/tree-sitter-yaml&amp;quot;)))
(require 'tsx-mode)
(add-to-list 'auto-mode-alist '(&amp;quot;\\.[jt]s[x]?\\'&amp;quot; . tsx-mode))
EOF
wget https://raw.githubusercontent.com/orzechowskid/tsx-mode.el/refs/heads/main/tsx-mode.el
wget https://raw.githubusercontent.com/orzechowskid/tree-sitter-css-in-js/refs/heads/main/css-in-js-mode.el
M-x package-install coverlay origami corfu # Deps of tsx-mode
npm install -g typescript-language-server # Language server eglot will use
M-x treesit-install-language-grammar typescript # Install the TypeScript grammar
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="profit"&gt;&lt;a class="toclink" href="#profit"&gt;Profit&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Not too bad when the instructions are set out like this. But, it took
me an evening to figure out how to string all of the pieces together. You
should now be able to open a &lt;code&gt;.tsx&lt;/code&gt; file and have tsx-mode provide a
nice major mode for working with the file.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Kubernetes: A first pass</title><link href="https://evan.carlin.com/blog/kubernetes-a-first-pass/" rel="alternate"/><published>2024-08-23T00:00:00-06:00</published><updated>2024-08-23T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-08-23:/blog/kubernetes-a-first-pass/</id><summary type="html">&lt;p&gt;Random musings on Kubernetes.&lt;/p&gt;</summary><content type="html">&lt;h2 id="prologue"&gt;&lt;a class="toclink" href="#prologue"&gt;Prologue&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What follows is really more of a journal entry than a blog post. It is
a start at a collection of my thoughts on using Kubernetes (K8s). I'm
planting this stake in the ground so I can see how my thoughts change
over time.&lt;/p&gt;
&lt;h2 id="background-on-my-world-with-regards-to-k8s"&gt;&lt;a class="toclink" href="#background-on-my-world-with-regards-to-k8s"&gt;Background on my world with regards to K8s&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;K8s has been the talk of the town among the physics/software community
I've been working in. Many large institutions are switching to it to
manage as many of their applications as they can. &lt;a href="https://kubernetes.io/case-studies/cern/"&gt;CERN
being&lt;/a&gt; the largest and most
well known of them. So, I've been looking forward to my chance to use
it.&lt;/p&gt;
&lt;p&gt;At RadiaSoft we deploy everything inside of docker containers. But,
we've chosen to not use K8s to manage containers. In as few words as
possible our reasoning is: K8s doesn't provide enough abstractions at
a high enough level to actually solve our problem. We'd have to write
a piece of software on top of it to speak a langauge above it for our
apps. And we'd still need a configuration management tool like Ansible
to actually configure our metal. So, we'd add a dependency (K8s) and
not really be any closer to having our problems solved. Instead we
wrote our own tool &lt;a href="https://github.com/radiasoft/rsconf"&gt;RSConf&lt;/a&gt; which
speaks a level of abstraction that matches our domain. It is akin to
ansible - we use it to configure our machines. It sets up docker and
systemd (among many other things). Then those two (along with our
custom job supervisor) do mostly what K8s would do for us.&lt;/p&gt;
&lt;p&gt;Inside of Sirepo, our web-app for runing physics simulations, is where
the
&lt;a href="https://github.com/radiasoft/sirepo/blob/fef0fd3a456b1f046fa37169f80f39b775a59abe/sirepo/pkcli/job_supervisor.py#L36"&gt;job_supervisor&lt;/a&gt;
lives. The job supervisor manages our "agents" which is where the
actual simulation/computation occurs. In production we use agents
running in their own docker container. For development (and quick prod
setups) we run local agents which just run as a unix process. In prod
we also run "sbatch agents" which are unix processes run on login
nodes at super computers. Those agents then manage job submissions
through the SLURM workload manager. So, we run our "top-level"
services using docker/systemd and then the job supervisor has
flexibility about the types of agents it creates and it does the
management of them (ex restarting if they die).&lt;/p&gt;
&lt;p&gt;So, that's where I stood going into using K8s. We have an app and it
could be deployed in K8s instead of with systemd. And maybe parts of
the job supervisor could even be replaced with K8s. Or at the very
least, it could "speak K8s" so agents would be started as K8s pods. I
was curious how everything worked and excited to get the chance to
see.&lt;/p&gt;
&lt;h2 id="my-first-real-use-of-k8s"&gt;&lt;a class="toclink" href="#my-first-real-use-of-k8s"&gt;My first real use of K8s&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We've been working with a national lab who is making the move to K8s.
They've setup a cluster and have a couple demo applications deployed.
We're working for them on a contract to build a better dashboard for
visualizing simulation results. Eventually we will run a "digital
twin" of their system. This would be a simulated system that would run
alongside the real life system and could be used by operators to help
optimize the functioning of the real system. So, I have been tasked
with deploying &lt;a href="https://github.com/radiasoft/sirepo"&gt;Sirepo&lt;/a&gt; inside of
their K8s cluster.&lt;/p&gt;
&lt;p&gt;Sirepo consists of multiple front-end servers, one job supervisor, and
n number of agents that run the simulations for users. The database is
split between sqlite and postgres. A few services but nothing that
crazy.&lt;/p&gt;
&lt;p&gt;Time to get coding...&lt;/p&gt;
&lt;h2 id="the-make-make-problem"&gt;&lt;a class="toclink" href="#the-make-make-problem"&gt;The make make problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first thing I noticed about K8s is a common problem. I know it as
the "make make problem". A phrase I learned from
&lt;a href="https://www.robnagler.com/"&gt;Rob&lt;/a&gt;. Tools like Autoconf and CMake
exemplify the make make problem. They are abstractions on top of an
underlying abstraction (Makefiles) to build a piece of software. When
Makefiles (and deployment targets) become too
numerous/large/cumbersome then people reach for tools like CMake which
creates a higher level abstraction on top of the Makefiles. This
recursion of making programs that make other programs to eventually
get the actual program working is in some ways what software is all
about, abstraction. But, in other ways it is a sign that the low level
thing (ex Makefiles) don't offer enough abstraction to get the job
done so something else needs to be used to make them useful.&lt;/p&gt;
&lt;p&gt;To me K8s looks like a bunch of Makefiles. I'm being glib when I say
that. K8s is operating at quite a high level of abstraction. But, the
individual yaml files are the Makefiles. For Sirepo this resulted in:
A namespace, two config maps, two secrets, a persistent volume, a
persistent volume claim, one storage class, 2 deployments, 2 services,
and one ingress. As I wrote out each file and started to see
how everying in K8s tied together I could already feel myself wanting
some tooling that generated the yaml files. Things likes having
variables (ex port numbers) be shared between the files would be nice.
Also, I just described only our alpha system. A copy of each one of
those files (with minor differences) would be needed for beta and
prod. I needed something to generate all of this. A "make make" is
born.&lt;/p&gt;
&lt;p&gt;In itself make make isn't a terrible problem. It is common and can be
solved with programming. It happens whenever a generic tool like K8s
is used. The creators can't possibly cover all use cases so they give
a hammer and nails with some instructions and tell you to build the
house. The problem with K8s is they don't give you a hammer and nails.
They give you and entire consturction company but you're only allowed
to communicate with the workers via smoke signals.&lt;/p&gt;
&lt;h2 id="aside-devops-tooling"&gt;&lt;a class="toclink" href="#aside-devops-tooling"&gt;Aside: DevOps tooling&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There seems to be an illusion around a lot of DevOps tooling that it
will be useful without much work. I don't have specific citations I
can point to but this is more a feeling I get reading articles and
talking to peers. For example, no one would install Python and Flask
and expect to have a working web app. Many hours of work are going to
have to be put in using those tools to build an app. But, there is
some idea one can install a tool like Ansible, write up a couple yaml
files, and voila - all infrastructure is now managed and perfectly
happy. The DevOps tools themselves lend themselves to this illusion.
The fact that so many of them have a yaml interface adds to the idea
that you won't have to "program" them.&lt;/p&gt;
&lt;h2 id="yaml-is-not-a-programming-language"&gt;&lt;a class="toclink" href="#yaml-is-not-a-programming-language"&gt;YAML is not a programming language&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Back to my point about smoke signals above: YAML is smoke signals. It
is a severly lacking way to communicate anything with even remote
complexity. I don't have beef with YAML itself - json, HCL, toml, it
doesn't matter to me. All of them suffer from the same problem: they
are configuration languages and not programming languages. The fact
is, that setting up a K8s cluster and deploying an application
involves some configuration but it also involves some programming. I
think we are kidding our selves, and by extension making our lives
harder, by thinking that it is only a configuration problem. All of
the programming details can't be hidden by the tooling makers. A toolmaker
must have a great deal of confidence that their tool solves every last
problem if they are only going to give YAML as the language to interface
with the tool.&lt;/p&gt;
&lt;p&gt;In "Code Complete" Steve McConell advises one to program into your
langauge not in your langague. By that I think he means to create the
missing pieces of the programming langague that you need to bring the
abstraction level up to solve your problem. The simplest example I
remember from the book is to create an assert function if your
language doesn't have one and you need it.&lt;/p&gt;
&lt;p&gt;For K8s I want (at the very least) a "make maker" so I can program
into K8s and avoid a lot of repetition. But, YAML is all I have to build this with. With YAML I am forever bound to the abstraction level offered by
the tool. In the case of K8s that means I am doomed to problems like
copying port numbers around because YAML (and K8s underneath it)
doesn't really support variables. To take a real example: Our client
is securing their system by whitelisting IPs. They are just copying
the list of IPs between each ingress resource they deploy. They don't
have something on top of all ingress resources to manage the list. It
goes without saying that is quite fragile. If K8s had a programming
interface instead of a configuration interface this problem would be
more easily resolved.&lt;/p&gt;
&lt;p&gt;YAML has constructs &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&amp;amp;tabs=yaml%2Cbatch#understand-variable-syntax"&gt;like
variables&lt;/a&gt;
but I strongly believe it is a misfeature and a code smell to start
using them. Once you need variables then you need conditionals. And
then loops. And then you just want a full programming language not a
configuration language.&lt;/p&gt;
&lt;p&gt;I don't think I'm the only one with this problem. Tools like
&lt;a href="https://aws.amazon.com/blogs/containers/introducing-cdk-for-kubernetes/"&gt;cdk8s&lt;/a&gt;
solves this exact problem. They offer a programming interface instead
of a configuration interface. There are also tools like Helm and
Kustomize that solve it in other ways but are still hamstrung by a
YAML interface. But, when I need to use cdk8s to start creating the
abstractions I need to actually make K8s useful I'm meeting my
brethren in the land of &lt;a href="https://medium.com/@ericclemmons/javascript-fatigue-48d4011b6fc4"&gt;JavaScript
fatigue&lt;/a&gt;.
I need tools for my tools and that &lt;a href="https://www.youtube.com/watch?v=eu9pmHIMdOM"&gt;2:30
feeling&lt;/a&gt; is setting in.
K8s better be offering me something really powerful to warrant this
russian doll stacking of tools. For Sirepo I don't think it offers
enough to warrant using it.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The make make / yaml problem doesn't make K8s bad per se. But, it just
means that once you install K8s and write your first few yaml files
the story has just begun. You will probably need (or at least I would
certainly want) another layer of abstraction on top of it. Another
layer with programatic control is key. That way I could program "into"
K8s to build abstractions for my domain. At that point I think it
would be a good place to pump the brakes and see if you need K8s at
all. Does it really solve your problems? Could you get by with less?
At 2am when the pager goes off the fewer tools I have between me and
my code the better. But, maybe you do need it. Google certainly did.
That's why they wrote it. But, few people are working on Google scale
problems.&lt;/p&gt;
&lt;h1 id="epilogue"&gt;&lt;a class="toclink" href="#epilogue"&gt;Epilogue&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;It is always easier to &lt;a href="https://www.youtube.com/watch?v=B-TflyvzrwY"&gt;tear down than
create&lt;/a&gt;. I've just taken
my swings at K8s. I went from never using K8s to having our app
running in about a day. Any tool that can do that I think is getting
some of the abstractions right. Kubectl is cohesive and well designed.
The introspection one can have into the environment is really nice.
And there is a lot of polish on K8s. Polish that is nearly impossible
to achieve outside of a large tool worked on by many people. There is
a huge library of informative talks, blog posts, tutorials, and docs.
So, all of those resources give a lot of support to move quickly and
get help when you're stuck.&lt;/p&gt;
&lt;p&gt;In an effort to do some of our own building we're considering adding
support for Kubernetes to
&lt;a href="https://github.com/radiasoft/rsconf"&gt;RSConf&lt;/a&gt;. So with just the right
bits of YAML and Python we can program into Kubernetes.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>LinkedIn Verification</title><link href="https://evan.carlin.com/blog/linkedin-verification/" rel="alternate"/><published>2024-07-15T00:00:00-06:00</published><updated>2024-07-15T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-07-15:/blog/linkedin-verification/</id><summary type="html">&lt;p&gt;Trying to verify myself as a LinkedIn user.&lt;/p&gt;</summary><content type="html">&lt;p&gt;LinkedIn verification is an annoying anti-pattern. First, I needed to
download their app. Why? Why can't I do this on my computer? I'm on an
iPhone so usually the security controls are okay but I really hate
downloading apps. They have in the past done things like upload
people's entire contacts after being installed.&lt;/p&gt;
&lt;p&gt;But okay I bit the bullet. Now I need to verify myself with Clear?
UGH! Again, why? Now I have to read Clear's privacy policy to see what
they are doing with my data? I see them at the airport and I think
they have something to do with collecting biometric information to
verify my identity. Are they selling it to other people? Have they
ever been hacked? In the future will they ever be hacked? I'm a little
gobsmacked I have to do this to use a social network.&lt;/p&gt;
&lt;p&gt;Is verification like this necessary? I don't work at LinkedIn so I
don't know what went into this decision. But, I'd really appreciate at
least some acknowlegement of why they need verification at all and why
this type of verification was selected. Instead, they just tell me that
verified users get more traction on their account. Great, thanks for
pressing me to do this.&lt;/p&gt;
&lt;p&gt;I was talking with someone else about this and they said they tried to
verify but it didn't work. So, we have an onerous system and it
doesn't work. Not great.&lt;/p&gt;
&lt;p&gt;It is a bummer when companies use their weight to press on users.
LinkedIn knows people need to work. So, they know people are desperate
and willing to jump through hoops to have the best shot at getting a
job. So, they implement this wacko verification system and leave
people in a lurch. Get a job or give up my data? Not a trade I want to
make. The steps aren't worth it for me for now. But, maybe in the future
I'll be more desperate...&lt;/p&gt;</content><category term="blog"/></entry><entry><title>SSH key basics</title><link href="https://evan.carlin.com/blog/ssh-key-basics/" rel="alternate"/><published>2024-07-15T00:00:00-06:00</published><updated>2024-07-15T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-07-15:/blog/ssh-key-basics/</id><summary type="html">&lt;p&gt;The basics of the different keys in SSH.&lt;/p&gt;</summary><content type="html">&lt;p&gt;At work we're migrating from CentOS7 to Alma Linux 9. As part of that
we are separating out some pieces of our infrastructure and generally
cleaning up cruft. To separate out parts of our infrastructure we are
going to setup a new build server at a cloud provider. So, we'll need
to migrate some of our SSH infrastructure to get everything working.&lt;/p&gt;
&lt;p&gt;I was working on a plan for the migration with
&lt;a href="https://www.robnagler.com/"&gt;Rob&lt;/a&gt; and we were going over the SSH bits.
Every time I need to setup SSH, which I've done quite a few times, I
forget the different keys and what each one does. What is known hosts?
What goes in authorized keys? Where is the public and private key? I'm
embarassed to even be writing that out. It isn't very complicated. But
whatever the cache invalidation strategy is in my head it keeps
removing SSH knowledge before the next time I need it. So, this is a
very basic article of the bare minimum of knowledge needed to work
with SSH keys. Hopefully I can at least remember I wrote this article
and refer back to it next time I need this information.&lt;/p&gt;
&lt;h2 id="publicprivate-keypair"&gt;&lt;a class="toclink" href="#publicprivate-keypair"&gt;Public/Private Keypair&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When people say "SSH keys" this is usually what they are reffereing
to. The keys clients use to authenticate.&lt;/p&gt;
&lt;h3 id="private-key-ex-sshid_algo"&gt;&lt;a class="toclink" href="#private-key-ex-sshid_algo"&gt;Private Key (ex. &lt;code&gt;~/.ssh/id_&amp;lt;algo&amp;gt;&lt;/code&gt;)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The private key lives on a client's machine and is usually protected by a
password. This key is a secret. It is able to decrypt messages
encrypted by the public key.&lt;/p&gt;
&lt;h3 id="public-key-ex-sshid_algopub"&gt;&lt;a class="toclink" href="#public-key-ex-sshid_algopub"&gt;Public Key (ex .&lt;code&gt;~/.ssh/id_&amp;lt;algo&amp;gt;.pub&lt;/code&gt;)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This key lives on the server one wants to authenticate with. It is not a secret.&lt;/p&gt;
&lt;p&gt;Other words to remember:
- &lt;code&gt;public key authentication&lt;/code&gt;: Another way to describe this auth
  mechanism.
- &lt;code&gt;Assymetric encryption&lt;/code&gt;: One half public and one half private.&lt;/p&gt;
&lt;h2 id="authorized-keys-sshauthorized_keys"&gt;&lt;a class="toclink" href="#authorized-keys-sshauthorized_keys"&gt;Authorized keys (&lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This file contains the public keys of all clients that
can authenticate with that user/server (they live under a users $HOME).&lt;/p&gt;
&lt;h2 id="host-keys-ex-etcsshssh_host_algo-and-etcsshssh_host_algopub"&gt;&lt;a class="toclink" href="#host-keys-ex-etcsshssh_host_algo-and-etcsshssh_host_algopub"&gt;Host Keys (ex. &lt;code&gt;/etc/ssh/ssh_host_&amp;lt;algo&amp;gt;&lt;/code&gt; and &lt;code&gt;/etc/ssh/ssh_host_&amp;lt;algo&amp;gt;.pub&lt;/code&gt;)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The host key identifies a server. It should be unique to every server.
When setting up an SSH connection the server exchanges this key with
the client. The client validates this key against the fingerprint it
has for it in &lt;code&gt;~/.ssh/known_hosts&lt;/code&gt;. If it is different the user is
notified. If it is the first time the client is connecting then the
user must have some alternate means of validating that the key is correct.&lt;/p&gt;
&lt;h2 id="known-hosts-ex-sshknown_hosts"&gt;&lt;a class="toclink" href="#known-hosts-ex-sshknown_hosts"&gt;Known hosts (ex. &lt;code&gt;~/.ssh/known_hosts&lt;/code&gt;)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Contains a fingerprint of the public keys of all hosts the client has
authenticated with.&lt;/p&gt;
&lt;p&gt;There is also an ip/hostname that is stored/checked
(&lt;a href="https://serverfault.com/questions/1040512/how-does-the-ssh-option-checkhostip-yes-really-help-me"&gt;maybe&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="baisc-steps-of-setting-up-an-ssh-connection"&gt;&lt;a class="toclink" href="#baisc-steps-of-setting-up-an-ssh-connection"&gt;Baisc steps of setting up an SSH connection&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Sever and client negotiate encryption for the session and setup
   symmetric encryption keys they will use to secure the conneciton.&lt;/li&gt;
&lt;li&gt;As part of step 1 the server provides it's public host key which
   the client checks against known_hosts.&lt;/li&gt;
&lt;li&gt;The server and client then do a dance using the ssh keys,
   authorized_keys, and session key to authenticate the client.&lt;/li&gt;
&lt;li&gt;Use the encrypted tunnel!&lt;/li&gt;
&lt;/ol&gt;</content><category term="blog"/></entry><entry><title>Application CLIs</title><link href="https://evan.carlin.com/blog/application-clis/" rel="alternate"/><published>2024-06-30T00:00:00-06:00</published><updated>2024-06-30T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-06-30:/blog/application-clis/</id><summary type="html">&lt;p&gt;Making CLIs part of every application.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://web.archive.org/web/20240628130152/https://notes.billmill.org/blog/2024/06/Serving_a_billion_web_requests_with_boring_code.html"&gt;This
article&lt;/a&gt;
made its way to the front page of Hacker News last week. One of the
points the author makes is on &lt;a href="https://web.archive.org/web/20240628130152/https://notes.billmill.org/blog/2024/06/Serving_a_billion_web_requests_with_boring_code.html#miscellaneous-tooling"&gt;miscellaneous
tooling&lt;/a&gt;.
Specifically, the benefit of having a place to put utility shell
scripts from the get-go of the project.&lt;/p&gt;
&lt;h2 id="benefits-of-utility-shell-scripts"&gt;&lt;a class="toclink" href="#benefits-of-utility-shell-scripts"&gt;Benefits of utility shell scripts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I agree with the author. Having a structure for writing utilities
makes it easier to write and share them among the team. These can be
for things like fundamental features (ex. starting the application) to
rarely run scripts used just for development. Creating a structure for
the whole team to share them makes everyone's life easier.&lt;/p&gt;
&lt;h2 id="codifying-the-utility-shell-script-concept-pkclis"&gt;&lt;a class="toclink" href="#codifying-the-utility-shell-script-concept-pkclis"&gt;Codifying the utility shell script concept (pkclis)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I think there is something even better than just having a collection
of shell scripts. Something I'm calling "application CLIs" for lack of
a better term.&lt;/p&gt;
&lt;p&gt;At work, we have a framework we use to initialize all new Python
projects. It is called
&lt;a href="https://github.com/radiasoft/pykern/blob/70b894d57a957664c384059a71c9615d70f810d1/pykern/pkcli/projex.py"&gt;projex&lt;/a&gt;.
This is a scaffolding tool like &lt;a href="https://yeoman.io/generators/"&gt;yeoman
generator&lt;/a&gt;. It sets out the initial
bones of the application. Adds a README, LICENSE, GitHub workflows,
test and source directories, etc.&lt;/p&gt;
&lt;p&gt;One of the important features it creates, that I think is an evolution
beyond just shell scripts, is a concept called "pkcli". Pkclis are
CLIs that one can write in Python (the language of our app) and
through a small bit of infrastructure become CLIs that anyone who has
installed the application can use. These are the "application CLIs"
for our projects. Projex itself is actually a pkcli.&lt;/p&gt;
&lt;h2 id="benefit-in-the-native-language"&gt;&lt;a class="toclink" href="#benefit-in-the-native-language"&gt;Benefit: In the native language&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Shell scripts certainly have their place. Even though we have
application CLIs, we still write shell scripts. But, something you
can't do with shell scripts is write them in the native language of
your application (assuming your app isn't written in a shell
programming language).&lt;/p&gt;
&lt;p&gt;By writing CLIs in the language of the project we can use all of the
features of the project. For example, we have an application CLI that
&lt;a href="https://github.com/radiasoft/sirepo/blob/0e88ead1f589f86ffa4dfcab841074ea2a87ca94/sirepo/pkcli/admin.py#L69"&gt;deletes users from our
system&lt;/a&gt;.
Someone could certainly write this utility as a shell script. But,
it's trivial to write this in the language of the application by just
using the APIs already in our application for managing users. For
example, we don't have to invent a new way of finding the database in
a shell script. Our application needs the database so we already have
an API for finding it. In addition, if we want to add a user interface
for deleting users we can share the code between the two modes of
interaction. This creates a more explicit coupling between the
application and the CLI. This, in turn, makes testing and future
changes easier to support and more likely to be correct.&lt;/p&gt;
&lt;h2 id="benefit-fewer-languages"&gt;&lt;a class="toclink" href="#benefit-fewer-languages"&gt;Benefit: Fewer languages&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Writing CLIs in the language of your application comes along with the
normal benefits of using fewer languages. Bill mentioned in his
article that he learned how to write shell scripts from a colleague
who set up the shell scripts in his project. Everyone on our team
knows some shell programming language (Bash for us). But, they know
more Python. By writing CLIs in the language of our application we
reduce the amount of things people need to know. Likewise, we get to
use all of our existing infrastructure for free. We can do things like
test our application CLIs alongside our regular application tests. We
can use the same formatter and linter our application uses. We can use
the same documentation generator to build docs from comments in the
code. We can also use the features of our IDEs to navigate and
refactor code in just one language. By writing CLIs in the language of
the application we reduce the barrier to entry for writing CLIs and
get to share in all of the infrastructure/tooling we have established
in our application.&lt;/p&gt;
&lt;h2 id="benefit-availability"&gt;&lt;a class="toclink" href="#benefit-availability"&gt;Benefit: Availability&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Shell scripts can be hard to distribute. How do you update your $PATH
so all scripts in all of your projects are available globally on all
systems where you might want to run them? At what level of portability
do you write them to make sure different systems can execute them?
These are solvable problems but application CLIs solve them without
much thought.&lt;/p&gt;
&lt;p&gt;Using Python's pyproject.toml we can &lt;a href="https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#creating-executable-scripts"&gt;easily define
CLIs&lt;/a&gt;
that then become globally available. Anywhere we're running our
application, which in my experience is everywhere we want to run the
application CLI, already has the runtime installed. For us that means
Python and all of the needed dependencies. In terms of portability,
Python handles this for us. Anywhere that can run Python can run our
CLIs.&lt;/p&gt;
&lt;h2 id="benefit-dependencies"&gt;&lt;a class="toclink" href="#benefit-dependencies"&gt;Benefit: Dependencies&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The shell programming language I'm most familiar with is Bash. So,
this may only be relevant to it. But, it doesn't have great support
for managing dependencies. This is related to availability above. For
example, if one wants to add a testing framework to their Bash scripts
they may use
&lt;a href="https://bats-core.readthedocs.io/en/stable/index.html"&gt;bats&lt;/a&gt;. It
provides a &lt;a href="https://bats-core.readthedocs.io/en/stable/installation.html"&gt;myriad of
ways&lt;/a&gt; to
install. None of them are particularly friendly. We could have
documentation that tells users what they need to install. Or roll our
own Bash dependency management system that we'd have to make portable
to different systems. Neither docs nor our own system are great
solutions. Python's dependency situation is a mess. But, at the very
least it provides files that the built-in tools in Python know how to
use to manage dependencies. So, someone new to our application just
has to get the application installed and then they can use our app and
all of our CLIs. By using the language of our application we get good
enough dependency management built in.&lt;/p&gt;
&lt;h2 id="benefit-better-namespacing"&gt;&lt;a class="toclink" href="#benefit-better-namespacing"&gt;Benefit: Better namespacing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another benefit is namespacing. One must be careful in shell scripts
to create namespaces without conflicts. In our application CLIs, this
comes easier. The first part of all of our application CLIs is the
name of the project. Then the second part is the name of the module.
And finally, the third part is the name of the function in the module.
So &lt;code&gt;sirepo admin delete_user&lt;/code&gt; is in the project sirepo, in the module
admin.py, and runs the delete_user function. This can be done in shell
scripts. Just prepend the application/module name to the name of every
function. But, it can devolve into long/messy names and make
integrating different projects a challenge.&lt;/p&gt;
&lt;h2 id="benefit-better-arg-handling"&gt;&lt;a class="toclink" href="#benefit-better-arg-handling"&gt;Benefit: Better arg handling&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm not a Bash wizard, so take this with a grain of salt, but I find
parsing args in Bash to be a pain. The top hit for "how to parse args
in Bash" is a &lt;a href="https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash"&gt;classic Bash
answer&lt;/a&gt;.
Conflicting info. Confusion over portability. Many caveats. New ways
that supposedly fix all of the problems. Again, all solvable problems.
But, one has to create infrastructure and knowledge among the team to
make it happen. With our pkclis, the details are handled for free
(mostly) by a well-supported third-party library. From the perspective
of someone writing an application CLI, they just have to understand
args and kwargs in a fairly friendly way. Anything that is a &lt;code&gt;*arg&lt;/code&gt;
becomes a positional argument. Anything that is a &lt;code&gt;**kwarg&lt;/code&gt; becomes an
optional flag that you can supply a value for. We haven't run into
much friction with people learning that.&lt;/p&gt;
&lt;p&gt;As always, there is no free lunch. We've &lt;a href="https://github.com/radiasoft/pykern/issues/413"&gt;run into
problems&lt;/a&gt; with our arg
parsing. But, the fix was in one place and fairly easy to figure out.&lt;/p&gt;
&lt;h2 id="shell-scripts-are-still-used"&gt;&lt;a class="toclink" href="#shell-scripts-are-still-used"&gt;Shell scripts are still used&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even with application CLIs, there is still a place for shell scripts.
For example, we use a shell script that &lt;a href="https://github.com/radiasoft/sirepo/blob/d9834747a75fe049cfb82347d36e181d137ee91e/etc/run.sh#L4"&gt;configures and starts our
application&lt;/a&gt;.
Setting up an environment and managing certain parts of a Linux system
are just easier in a shell script. But, our startup shell script calls
our application CLIs to actually start the application. The
application CLIs provide a nice interface to bridge the gap between
the shell scripts and the application. In other cases, we &lt;a href="https://github.com/radiasoft/sirepo/blob/d9834747a75fe049cfb82347d36e181d137ee91e/etc/setup-ldap.sh"&gt;only use
shell
scripts&lt;/a&gt;.
Sometimes it is just the best tool for the job.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Application CLIs are not unique to the company I work for. WordPress
has &lt;code&gt;wp-cli&lt;/code&gt;, Django has &lt;code&gt;django-admin&lt;/code&gt;, and Ruby has &lt;a href="https://edgeguides.rubyonrails.org/command_line.html#command-line-basics"&gt;a comprehensive
suite of
CLIs&lt;/a&gt;.
&lt;a href="https://www.robnagler.com/"&gt;Rob&lt;/a&gt;, the CTO of my company, brought the
idea of application CLIs from a previous &lt;a href="https://github.com/biviosoftware/perl-Bivio/blob/b16b7b772dfcdcab56eb439d9bf6ec16c6a4549d/ShellUtil.pm"&gt;framework he
built&lt;/a&gt;.
But, I think the gospel of application CLIs could still be spread
further.&lt;/p&gt;
&lt;p&gt;There's nothing inherently wrong with shell scripts. In the right
hands, they can be just as powerful, offering their own unique
benefits. But I've found success with application CLIs and will bring
them into the codebases I work on in the future.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>A spin on Windows</title><link href="https://evan.carlin.com/blog/a-spin-on-windows/" rel="alternate"/><published>2024-04-20T00:00:00-06:00</published><updated>2024-04-20T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-04-20:/blog/a-spin-on-windows/</id><summary type="html">&lt;p&gt;Wading through the ad software that is Windows.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I haven't spent much time using Windows. My personal laptop is a Dell
XPS running Ubuntu and at work my team uses Macs (ssh'ing into Fedora
VMs for development). The longest stretch of time I spent using
Windows was eight months or so at my first job. I was given a laptop
running Windows 10 and it took me 8 months to convince them to give me
a Mac. Even then I spent most of my time in the Windows Subsystem for
Linux. With just this little taste of Windows, my feeling was I'd like
to avoid it if possible. For web browsing and other general computing
it is fine. But, for any development it is a world I'd rather avoid.
Not so much for the OS which I thought was usable. But, because I didn't
want to get totally immersed in the Windows world.&lt;/p&gt;
&lt;p&gt;I follow along with &lt;a href="https://world.hey.com/dhh"&gt;DHH's blog&lt;/a&gt;*. He has
been &lt;a href="https://world.hey.com/dhh/we-need-a-right-to-compute-0add65df"&gt;fighting with
Apple&lt;/a&gt;
trying to get his company's calendar app released. As part of his
battle he's made the commendable decision to try to "put his money
where his mouth is" and to get off of Apple products. As part of that
process he has tried out Windows and has had &lt;a href="https://world.hey.com/dhh/vscode-wsl-makes-windows-awesome-for-web-development-9bc4d528"&gt;good things to say about
it&lt;/a&gt;.
That made me a tiny bit curious to try out Windows again now that I
have some more years under my belt. Maybe I'd like it too?&lt;/p&gt;
&lt;p&gt;This past month I had a chance to do that. We are setting up JumpCloud
for MDM at our company so I've been trying out provisioning Windows
devices (some folks at the company use Windows). I haven't done any
development on the machines but just some basic clicking around. After
this little bit of time I can say unequivocally that I won't be
switching any time soon. Windows is one giant ad and my experience as
a user suffered from it.&lt;/p&gt;
&lt;p&gt;Here is my experience: When I opened my Windows machine (running
Windows 11 Pro) I was greeted with the normal setup your computer
flow. Connect to the internet. Enter my name. Then came the first bit
of ad junk. I had to enter Microsoft account credentials to get to the
next step. There are ways around this but they aren't just a click
away so I conceded and set up an account. Then I was presented with a
"Free offer for Microsoft 365". No, thanks. Then it was "Free offer
for Xbox game pass". I'll pass. Finally, into the desktop. In Windows
10 I remember the Windows start button was on the bottom left so I
instinctively clicked there. I was greeted with some half broken mess
of widgets showing me news articles and stock prices. How delightful.
Looks like the Windows start button is now center aligned (copying Mac
dock?). I clicked it and was able to open Edge. But, I had to click
through three windows before I could actually use the browser. Each
one of them asked me some form of what data I wanted to share with god
knows who. The final one gave me a chuckle. It read "do you want to
make your Windows experience better?". Yes! That would be great. But,
I'm sure that by "better" they mean better for them and worse for me.
So, I clicked no. It would be hard for my experience so far to get
worse so I figured "no" couldn't be that disastrous. Finally I was
just about to sign into the JumpCloud console when a dialog box popped
up asking me if I wanted to secure my computer with McAfee. I hadn't
seen that since the library computers in middle school. Once again, no
thanks. Now, I was able to use the browser. But, the McAfee popup kept
appearing as well as a few guest spots from Xbox Game Pass.&lt;/p&gt;
&lt;p&gt;After navigating the sea of ads/popups I was left dismayed. I imagine
there is a way to turn them all off but I wish off was the default. I
know Apple is no shining star but at least I can click "setup later"
and "later" is on my watch, not theirs. I have to remove things like
AppleTV from the dock when I get a new machine but they don't find
their way back. And there are no popups from companies like McAfee
following me around. I encourage DHH to continue his search but I
won't be leaving my Mac for Windows anytime soon.&lt;/p&gt;
&lt;p&gt;*I could write another post on his blog and the ideas he shares. I
have no plans to but just know that just because I read it doesn't
mean I agree with him.&lt;/p&gt;
&lt;p&gt;P.S. While updating my site with this post I was greeted with this
&lt;code&gt;›Error: request to https://api.netlify.com/&amp;lt;snip&amp;gt; failed, reason: Hostname/IP does not match certificate's altnames: Host: api.netlify.com. is not in the cert's
 ›altnames: DNS:*.safezone.mcafee.com, DNS:safezone.mcafee.com&lt;/code&gt;
 My router from CenturyLink has Mcafee garbage too. I've turned it off no less than a dozen times...&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Carbide Scraper</title><link href="https://evan.carlin.com/blog/carbide-scraper/" rel="alternate"/><published>2024-02-16T00:00:00-07:00</published><updated>2024-02-16T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2024-02-16:/blog/carbide-scraper/</id><summary type="html">&lt;p&gt;A magical tool for removing old gaskets.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been rebuilding the the motor on my snowmobile. The only part of
the process I dread is dealing with old gaskets. After 18 years of use
the gaskets on this motor were totally baked on. Common advice is to
carefully use a razor blade to remove the old gasket material. But,
under the gaskets is soft aluminum. If you knick the aluminum while
you're getting the gasket off you will create a surface that the new
gasket will have a hard time sealing. I have never removed a gasket
without knicking the surface. Some people reccomend a tool like a
scouring pad (by hand or on a rotary tool). But, this will fling
gasket material and little metal shards from the scouring pad all over
the motor. Possibly into the crank of the motor where the debris will
wreak havoc on all of the spinning parts.&lt;/p&gt;
&lt;p&gt;After desperately searching I came across a tool that has
revolutionized the process for me. A carbide scraper. The always
reliable mechanic porn site &lt;a href="https://web.archive.org/web/20240216212017/https://www.garagejournal.com/the-best-gasket-scraper-ever/"&gt;Garage
Journal&lt;/a&gt;
is what tipped me off to this tool. I want to buy an alphorn and yell
about carbide scrapers from the mountaintops. They are amazing! It is
shocking to me that any other tool is ever reccomended.&lt;/p&gt;
&lt;p&gt;The scraper has a perfectly flat blade that doesn't nick the surface.
You hardly need to apply any pressure and the gasket is easily scraped
away.&lt;/p&gt;
&lt;p&gt;I bought the cheapo version of the tool from O'reily. Next time I'll
buy the USA made Super Scraper. But, if you're in a time crunch like I
was the Titan scraper works wonders.&lt;/p&gt;
&lt;p&gt;The tool worked so well I spent the afternoon going over every gasket
surface I could find. If you ever find yourself removing gaskets (or
I'm guessing many other stuck on substances) don't even think about
reaching for a razor blade. Do yourself a favor and get a carbide
scraper.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Bangle.js Hackable Smartwatch</title><link href="https://evan.carlin.com/blog/banglejs-hackable-smartwatch/" rel="alternate"/><published>2023-12-28T00:00:00-07:00</published><updated>2023-12-28T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2023-12-28:/blog/banglejs-hackable-smartwatch/</id><summary type="html">&lt;p&gt;Writing my first Bangle.js app&lt;/p&gt;</summary><content type="html">&lt;p&gt;Two years ago I did the &lt;a href="https://thegrandtraverse.org/ski/"&gt;Grand
Traverse&lt;/a&gt; ski race. It is a 40 mile
race between Crested Butte and Aspen through the Elk Mountains. I
wouldn't exactly say I was racing. More just hoping to survive to the
finish line. As part of the preperation for the race I followed an
&lt;a href="https://www.8020endurance.com/"&gt;80/20 Endurance&lt;/a&gt; 50 mile
ultramarathon training plan.&lt;/p&gt;
&lt;p&gt;This plan involves workouts that look something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5:00 in Zone 1
5:00 in Zone 2
5 x (0:30 in Zone 3/0:20 in Zone 4/0:10 in Zone 5)
5:00 in Zone 1
5 x (0:30 in Zone 3/0:20 in Zone 4/0:10 in Zone 5)
5:00 in Zone 1
5 x (0:30 in Zone 3/0:20 in Zone 4/0:10 in Zone 5)
10:00 in Zone 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is one of the more involved workouts. The specifics aren't
important but what is important is there are 50 steps in there and
some of the steps are as short as 10 seconds. So to follow along one
needs some sort of system. I ended up doing the workouts just by
bouncing between the Training Peaks app where the workout is stored,
Strava where I could see my heart rate, and the iOS timer app. This
would result in me staring at my phone the whole run which isn't very
fun. So, near the end of my plan I went in search of a better system.&lt;/p&gt;
&lt;p&gt;I thought the simplest solution would be to buy a Garmin smartwatch
(or similar). They know about workouts like this and you can download
the workout straight to the watch. But, I didn't really want to spend
a few hundred dollars on a watch that would sit in a drawer after my
five month traning plan was done. Also, from the people I've talked to
the Garmin software is nothing amazing.&lt;/p&gt;
&lt;p&gt;During my search for something better I stumbled upon a pretty cool
solution. The &lt;a href="https://banglejs.com/"&gt;Bangle.js&lt;/a&gt;. The Bangle.js is
"the world's first open source hackable smartwatch." You write code in
JavaScript for it and it only costs about $90. Perfect!&lt;/p&gt;
&lt;p&gt;I finished my traning plan (and the race) before I had time to
actually program the watch. It then sat in a drawer for two years
before I got the idea to do the Grand Traverse again. So, recently I
pulled out the old training plan and dusted off my watch.&lt;/p&gt;
&lt;p&gt;Below is some information on the app I built to manage my training
plan. I think the content is relevant to many people who want to
program on the Bangle.js even if the app you want is for something
else.&lt;/p&gt;
&lt;p&gt;First some eye candy.&lt;/p&gt;
&lt;p&gt;&lt;img alt="bangle.js workout
step" src="{filename}/static/images/bangle-workout-step.jpg"&gt;&lt;/p&gt;
&lt;p&gt;That is the main face of the watch while running my app. It has time
left in the current stage, current heart rate, target heart rate range,
and the name of the target heart rate zone. It isn't that amazing but
getting there involved some twists and turns.&lt;/p&gt;
&lt;p&gt;If you prefer reading code over prose &lt;a href="https://forge.carlin.com/e-carlin/banglejs/src/commit/e613d95b0dbaac87c55b2872aec33fe7502a4245/apps/trpks/app.js"&gt;here is a link to my
app&lt;/a&gt;.
There is much to improve but it works for now.&lt;/p&gt;
&lt;h2 id="basic-development"&gt;&lt;a class="toclink" href="#basic-development"&gt;Basic development&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The simplest way to program the Bangle.js is to do so in the &lt;a href="https://www.espruino.com/ide/"&gt;web
IDE&lt;/a&gt;. That is the way they tell you to
program in the docs. But, I like programming in Emacs so a web IDE
isn't going to fly.&lt;/p&gt;
&lt;p&gt;Instead I connected to the watch using the &lt;a href="https://www.npmjs.com/package/espruino"&gt;espruino
cli&lt;/a&gt;. Connection is fairly
simple, you can do &lt;code&gt;espruino -d Bangle --watch app.js&lt;/code&gt;. &lt;code&gt;-d Bangle&lt;/code&gt;
connects to any nearby bluetooth device with "Bangle" in the name.
&lt;code&gt;--watch app.js&lt;/code&gt; uploads my app and watches for changes. Using this
cli I was able to program in Emacs and on every save my code was
uploaded to the watch.&lt;/p&gt;
&lt;h2 id="bluetooth-connection"&gt;&lt;a class="toclink" href="#bluetooth-connection"&gt;Bluetooth connection&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dealing with bluetooth is always a pain. From just being a consumer
trying to get my headphones connected to my laptop to actually
programming it on the watch I loathe interacting with bluetooth. The
APIs for dealing with bluetooth on the Bangle.js are decent but
bluetooth is just a finicky protocol no matter what.&lt;/p&gt;
&lt;p&gt;I need bluetooth connectivity in my app to connect to an external
heart rate monitor and to receive push notifications. The Bangle.js
comes with a heart rate monitor under the watch face but it is pretty
innacurate with any movement. So, I wanted to connect to a heart rate
monitor I wear on my chest.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://forge.carlin.com/e-carlin/banglejs/src/commit/e613d95b0dbaac87c55b2872aec33fe7502a4245/apps/trpks/app.js#L54"&gt;This is the main
bit&lt;/a&gt;
of bluetooth code in my app. Like most bluetooth code once you get it
working it isn't that many lines but getting there takes some time.
Also, it is all wrapped in an infinite retry because bleutooth
connections never work on the first try and tend to drop after a
while.&lt;/p&gt;
&lt;p&gt;One issue with programming with bluetooth on the Bangle.js is that you
can only connect to one peripheral and one "central" device at a time.
So, I can connect to the debugger and to my heart rate monitor but
nothing else. You'll see how this becomes a problem in the next
section.&lt;/p&gt;
&lt;h2 id="getting-the-workout-from-my-iphone-to-the-watch"&gt;&lt;a class="toclink" href="#getting-the-workout-from-my-iphone-to-the-watch"&gt;Getting the workout from my iPhone to the watch&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I needed some way of loading my workout onto the watch. While
developing I just had it hardcoded in the app. But, in the
"production" version I settled on a nice Rube Goldberg contraption.&lt;/p&gt;
&lt;p&gt;I first download the day's workout from the iOS Training Peaks app on
my phone. This comes in a json file. I then use an iOS &lt;a href="https://www.icloud.com/shortcuts/961ee252543c48149f21288898edd431"&gt;shortcuts
flow&lt;/a&gt;
that I wrote which let's me select the file, parse it, and send a push
notification with the parsed data. My watch is paired with my phone so
it receives the push notification and gets the workout from the body
of the notification.&lt;/p&gt;
&lt;p&gt;All in all, this seems to work fine. Getting the connection between my
phone and the watch working and creating a small enough noticication
so it wouldn't get clipped by iOS proved a bit challenging. I think
the iOS push notification size limit is 4KB but I'm not really sure. I
was bumping against whatever the limit was until I made the message as
small as possible. This involved doing point and click programming in
the iOS shortcuts app which was a chore but I was happy to have some
way of writing a little program on my phone without having to resort
to writing a full app.&lt;/p&gt;
&lt;p&gt;The hardest part of this entire process is there is no good way to
debug. As I said above you can only be connected to one peripheral and
one "central" device at a time. My heart rate monitor counts as a
peripheral. So, for the "central" connection I had to decide between
being connected to the debugger or to my phone to receive a push
notification. This resulted in numerous cycles of connecting to the
deugger, uploading my code, disconnecting, fighting to connect my
phone and the watch, sending the notification, having the app not
work, and having no clue why.&lt;/p&gt;
&lt;p&gt;I found a few solutions that may help future Bangle.js users.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write to a file instead of writing to stdout. You can &lt;a href="https://forge.carlin.com/e-carlin/banglejs/src/commit/e613d95b0dbaac87c55b2872aec33fe7502a4245/apps/trpks/app.js#L30"&gt;write to a
   file&lt;/a&gt;
   quite easily so I would do that as a means to figure out what was
   going on.&lt;/li&gt;
&lt;li&gt;Similar to the first hint you can write all &lt;a href="https://forge.carlin.com/e-carlin/banglejs/src/commit/e613d95b0dbaac87c55b2872aec33fe7502a4245/apps/trpks/app.js#L343"&gt;uncaught exceptions to
   a
   file&lt;/a&gt;.
   I was quite relieved when I found that burried down in the docs.&lt;/li&gt;
&lt;li&gt;Use the &lt;a href="https://apps.apple.com/us/app/lightblue/id557428110"&gt;Light
   Blue&lt;/a&gt; app for
   connecting to the watch. Once the watch is paired the iPhone will
   connect to it again in the future. But, for whatever reason BLE
   devices aren't listed when trying to pair for the first time.&lt;/li&gt;
&lt;li&gt;There is more advice in the
   &lt;a href="https://forge.carlin.com/e-carlin/banglejs/src/commit/e613d95b0dbaac87c55b2872aec33fe7502a4245/README.md"&gt;README&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After more time than I care to admit of carefully reading code and fixing bugs I still
wasn't able to get my code working. But, the above tools
finally gave me enough of a view into the errors that I could finish
the app.&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;&lt;a class="toclink" href="#final-thoughts"&gt;Final thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm really happy with the Bangle.js. It is a super impressive bit of
tech and I think it is almost entirely built by one guy, &lt;a href="https://github.com/gfwilliams"&gt;Gordon
Williams&lt;/a&gt;. He is a very active coder
and is very active on the forums. He's built quite an ecosystem.&lt;/p&gt;
&lt;p&gt;If you're curious about hacking on a smart watch I highly reccomend
the Bangle.js. I have the first version of the device but there is a
newer version out with better specs. I hope to upgrade one of these
days.&lt;/p&gt;
&lt;p&gt;If you happen to already have a Bangle.js you can get my app from
&lt;a href="https://e-carlin.github.io/BangleApps/?id=trpks"&gt;here&lt;/a&gt;. It has my
heart rate zones hard-coded but I hope to fix that one of these days.
You'll also need the &lt;a href="https://banglejs.com/apps/?id=ios"&gt;ios integration
app&lt;/a&gt;, my &lt;a href="https://www.icloud.com/shortcuts/961ee252543c48149f21288898edd431"&gt;iOS
shortcut&lt;/a&gt;
and a workout loaded in &lt;a href="https://apps.apple.com/us/app/trainingpeaks/id408047715"&gt;Training
Peaks&lt;/a&gt;. I
haven't put any thought into whether or not this app will work for
anyone besides me so buyer beware!&lt;/p&gt;</content><category term="blog"/></entry><entry><title>E Street Garage</title><link href="https://evan.carlin.com/blog/e-street-garage/" rel="alternate"/><published>2023-12-04T00:00:00-07:00</published><updated>2023-12-04T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2023-12-04:/blog/e-street-garage/</id><summary type="html">&lt;p&gt;Posting my first video to YouTube&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm on the cusp between being a Millenial and Gen-Z. I remember 9/11
which I think is a reasonable distinction for being a Millenial but it
is one of my earlier memories. Today I let my Gen-Z shine by becoming
a content creator. I posted my first video to YouTube*. But the truth
is, if I had to put my videos on a generational scale from Charlie
Chaplin to Troye Sivan I would put them squarely in "hey boomer, turn
your phone sideways when you're filming."&lt;/p&gt;
&lt;p&gt;My desire to create a YouTube channel is twofold. First, I've received
thousands of hours of eduction from strangers through YouTube. I feel
it is time for me to give back in whatever small weird way I can.
Second, I'm already taking videos of most of the things I work on. I
have an exceptional talent for being able to take things apart and not
being able to put them back together. Recording videos helps me figure
out what things looked like as they came apart. So, between feeling a
debt to the person with 3 views on their video detailing how to
replace the cabin air filter on a 2004 Honda Odyssey and already
having videos of me doing equally obscure work I figured it was time
to make a channel of my own.&lt;/p&gt;
&lt;p&gt;These videos aren't intended to be well shot, lit, edited, or
entertaining to watch. They are supposed to be easy for me to create
and help someone who is in a jam trying to finish a project.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/Y-8LTVRqaWE"&gt;Here is my first video&lt;/a&gt;. It shows how
to replace the secondary clutch rollers on a 2006 Arctic Cat M7
snowmobile.&lt;/p&gt;
&lt;p&gt;*I'll be honest I don't even know if Gen-Z uses YouTube. For all I
know I'm just solidifying my boomer creds.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Moving in vi</title><link href="https://evan.carlin.com/blog/moving-in-vi/" rel="alternate"/><published>2023-06-28T00:00:00-06:00</published><updated>2023-06-28T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2023-06-28:/blog/moving-in-vi/</id><summary type="html">&lt;p&gt;Avoiding arrow keys when moving navigating in vi&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently paired with a colleague who is learning vi. While watching
him navigate code, I noticed he almost exclusively used the arrow
keys. When I started using vi, I read numerous times that one should
avoid using the arrow keys. So, I relayed this advice to him. He,
quite rightly, asked "why?". Of course, I should've considered why I
was giving this advice before blurting it out.  I dregdged up some
memory of the articles I had read and explained that it is faster to
keep one's fingers on the home row than to move to the arrow
keys. However, after making this point, I realized that wasn't the
complete explanation.&lt;/p&gt;
&lt;p&gt;While it might be faster to keep your fingers on the home row, there
is a deeper reason to avoid the arrow keys. Utilizing the arrow keys
(and hjkl, for that matter) is the slowest possible way to navigate in
vi. Moreover, observing him work with the arrow keys made me realize
that this approach often kept him in insert mode rather than inserting
text and instinctively switching back to normal mode. Being in an
"insert mode mindset" meant that he wasn't thinking about other,
faster ways to navigate code. Techniques like searching, moving by
word, finding a character, etc., are all only possible in normal mode.&lt;/p&gt;
&lt;p&gt;While writing this article, I did a little research to see what others
had to say about why the arrow keys should be avoided. Most articles I
read stated that it's slow to move from the home row to the arrow keys
and that moving one char/line at a time is tedious. However, some
people only mentioned the slowness of moving away from the home row
(&lt;a href="https://news.ycombinator.com/item?id=1556903"&gt;for example&lt;/a&gt;). But,
the issue of moving away from the home row is not that
significant. Although it's slower, it's not nearly as time-consuming
as navigating one char/line at a time.&lt;/p&gt;
&lt;p&gt;I also wondered why recalling information about moving more than one
character or line at a time took me a while. My guess is that when I
read the vi articles instructing me to avoid the arrow keys, I had yet
to master the more powerful navigation techniques. So, while I
understood the speed advantage of not leaving the home row, I lacked
the muscle memory to make other forms of navigation faster. As a
result, this piece of information didn't stick in my mind.&lt;/p&gt;
&lt;p&gt;I must admit that, to this day, I am no navigation master myself. I
don't use the arrow keys, but I commit a related sin. I &lt;a href="https://forge.carlin.com/e-carlin/home-env/src/commit/9dc373c2775487682bb3ab7a6ad92ce5ae912936/bashrc.d/zz-10-base.sh#L16"&gt;increase the
key repeat
speed&lt;/a&gt;
on my machine so that navigating by hjkl doesn't feel so
sluggish. Although I use some combinations other than hjkl to move,
I'm confident the key repeat crutch is holding me back. Perhaps it's
time to disable it...&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Using ChatGPT</title><link href="https://evan.carlin.com/blog/using-chatgpt/" rel="alternate"/><published>2023-03-31T00:00:00-06:00</published><updated>2023-03-31T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2023-03-31:/blog/using-chatgpt/</id><summary type="html">&lt;p&gt;A Little Fiddling with ChatGPT&lt;/p&gt;</summary><content type="html">&lt;p&gt;When &lt;a href="https://web.archive.org/web/20230401040200/https://github.com/features/copilot"&gt;GitHub
Copilot&lt;/a&gt;
came out, I joined the technology preview hoping it would help me be more
productive at work. The result was extremely underwhelming. The code it produced
was buggy and didn't meet any of the coding standards we use. It seemed more
risk than reward, with the subtle bugs it would introduce. So, after a few hours
of using it, I gave up.&lt;/p&gt;
&lt;p&gt;Then, ChatGPT came out. It seemed like a similar hype cycle. But, I am genuinely
interested in finding ways to be more productive. I'm also curious if one of
these tools is going to take my job or become my boss. So, I messed around with
it some. I thought it was better than Copilot but still not amazing. It seemed
way too eager to give me wrong answers, apologize for them, and then give me the
same wrong answers. So, like Copilot, I gave up after a few hours.&lt;/p&gt;
&lt;p&gt;This morning, I read &lt;a href="https://web.archive.org/web/20230331005235/https://simonwillison.net/2023/Mar/27/ai-enhanced-development/"&gt;this
article&lt;/a&gt;
about one person's path to increased productivity with ChatGPT. The specific use
case outlined for ChatGPT didn't strike me as particularly interesting. If I'm
being totally honest, it seemed like most of the hello world examples I
see. Cool that it helped this one person write some little script. But, not
really going to move the needle for me getting my job done. For no apparent
reason, though, this article lit a fairly large spark in me to try again to see
if I could make use of ChatGPT.&lt;/p&gt;
&lt;p&gt;Today at work, I spent a lot of time on &lt;a href="https://github.com/radiasoft/download/pull/444"&gt;this
PR&lt;/a&gt;. We are trying to get NFS
mounts working in a Fedora 36 VM running on a MacOS host. The meat of the work
has been taking place in &lt;a href="https://github.com/radiasoft/download/blob/a2e71aaa4e4f5238b448d6e85d1cebff269975c6/installers/vagrant-dev/radiasoft-download.sh"&gt;this
file&lt;/a&gt;. It
is a shell script to generate a
&lt;a href="https://web.archive.org/web/20230123051215/https://developer.hashicorp.com/vagrant/docs/vagrantfile"&gt;Vagrantfile&lt;/a&gt;
(a file which defines the configuration for a VM), which will create the VM and
then do some provisioning of the machine. There are lots of little details to
hold in one's head and many different cases all competing for my attention.&lt;/p&gt;
&lt;p&gt;To add to the confusion, it uses bash parameter expansion heavily. For the life
of me, I can't remember what &lt;code&gt;${param:-1}&lt;/code&gt; vs &lt;code&gt;${param-1}&lt;/code&gt; vs &lt;code&gt;${param:+1}&lt;/code&gt;
etc., all mean. Every time I interact with parameter expansion, I need to visit
&lt;a href="https://web.archive.org/web/20230321173611/https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html"&gt;the
docs&lt;/a&gt;
to remind myself what each one of the expansions does, and I usually then end up
writing a little note to summarize everything.&lt;/p&gt;
&lt;p&gt;Today, I decided to plug my questions into ChatGPT. I said:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-txt"&gt;Can you explain bash parameter expansion? Specifically,
${parameter:-word} ${parameter:-+word}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll notice right off the bat, I have a typo. The last example should be
&lt;code&gt;${parameter:+word}&lt;/code&gt;. ChatGPT didn't mind and gave me a very useful answer.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-txt"&gt;Bash parameter expansion is a powerful feature in shell scripting that allows
you to manipulate and transform variables or parameters in various ways. It
helps in enhancing the flexibility of scripts by providing ways to use default
values, substitutions, string manipulation, and more.

In your question, you've mentioned two types of parameter expansions:

    ${parameter:-word}
    ${parameter:+word}

Let's dive into each of these:

    ${parameter:-word} - This is called the default value parameter expansion.
    It expands to the value of 'parameter' if it is set and not null; otherwise,
    it expands to the value of 'word'. This is useful when you want to provide
    a default value for a variable that may not be set or may be empty.

Example:

name=&amp;quot;John&amp;quot;
greeting=&amp;quot;Hello, ${name:-User}!&amp;quot;
echo $greeting

This will output &amp;quot;Hello, John!&amp;quot; since the 'name' variable is set to &amp;quot;John&amp;quot;.
If 'name' were unset or empty, it would output &amp;quot;Hello, User!&amp;quot;.

    ${parameter:+word} - This is called the alternate value parameter
    expansion. It expands to the value of 'word' if 'parameter' is set and not
    null; otherwise, it expands to nothing. This is useful when you want to
    conditionally insert a value or perform an operation based on the existence of a variable.

Example:

name=&amp;quot;John&amp;quot;
greeting=&amp;quot;Hello${name:+, $name}!&amp;quot;
echo $greeting

This will output &amp;quot;Hello, John!&amp;quot; since the 'name' variable is set to &amp;quot;John&amp;quot;. If
'name' were unset or empty, it would output &amp;quot;Hello!&amp;quot;.

These parameter expansions are just a couple of examples of the many ways you
can manipulate variables in Bash scripts. They help in creating more robust and
versatile scripts by accounting for different situations and handling variables
more effectively.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From 1000 feet above, the docs and ChatGPT look to give the same answer. But
some of the subtle wording in the ChatGPT response finally helped me hold in my
head what each expansion does. What really did it for me was "this is called the
default value parameter expansion" and "this is called the alternate parameter
expansion". For whatever reason, "default" vs "alternate" caused things to
click.&lt;/p&gt;
&lt;p&gt;I was pretty impressed with ChatGPT at this point! I spent the rest of the day
forgetting how expansion worked or running into a slightly different expansion
and getting ChatGPT to help me. Like a perfect little helper, it held context
all day long as I asked it questions.&lt;/p&gt;
&lt;p&gt;The funniest part was, like a coworker helping, it even reminded me that I was
dumb and it had already answered the question before:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-txt"&gt;In the given expression ${vagrant_dev_no_nfs_src:+1}, the parameter expansion
used is the alternate value parameter expansion, which we have covered earlier
as ${parameter:+word}.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, while not earth-shattering and not doing my whole job for me, I actually
found some real value in using ChatGPT to help me along. It won't take my job,
but I definitely plan on having a tab with it running going forward.&lt;/p&gt;
&lt;p&gt;P.S. If you're from the future and ChatGPT doesn't exist, send me a note so I
can reminisce.&lt;/p&gt;
&lt;p&gt;P.P.S. In a recent article, I was told by a friend that I could use more
commas. I don't have the faintest idea where to put a comma when I write. But, I
fed it through ChatGPT and it produced an article with at least 2x the number of
commas, so from now on, all of my articles will have &lt;a href="https://www.youtube.com/watch?v=cVsQLlk-T0s"&gt;more comma
baby&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>ImageMagick</title><link href="https://evan.carlin.com/blog/imagemagick/" rel="alternate"/><published>2023-03-27T00:00:00-06:00</published><updated>2023-03-27T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2023-03-27:/blog/imagemagick/</id><summary type="html">&lt;p&gt;Cheat sheet for ImageMagick commands.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've written about ImageMagick
&lt;a href="https://evan.carlin.com/blog/converting-heic-image-to-jpg/"&gt;before&lt;/a&gt;. It is a very useful tool
with a great interface. It seems that whatever I want to do to an
image is just one command away.&lt;/p&gt;
&lt;p&gt;I used ImageMagick again yesterday when I wrote my first &lt;a href="https://evan.carlin.com/blog/dirt-bike-fuel-injector-cleaner/"&gt;post with
images&lt;/a&gt;. I needed to resize
the images and combine two together. In an effort to not have to go
searching every time I want to do some image manipulation I'm going to
keep this living cheatsheet of commands I've found useful.&lt;/p&gt;
&lt;h3 id="convert-heic-to-jpg"&gt;&lt;a class="toclink" href="#convert-heic-to-jpg"&gt;Convert HEIC to JPG&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;See my &lt;a href="https://evan.carlin.com/blog/converting-heic-image-to-jpg/"&gt;previous post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="resize-an-image"&gt;&lt;a class="toclink" href="#resize-an-image"&gt;Resize an image&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;convert original.jpg -resize 1000x1000! resized.jpg&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="combine-two-images-top-to-bottom"&gt;&lt;a class="toclink" href="#combine-two-images-top-to-bottom"&gt;Combine two images top-to-bottom&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;convert -append top.jpg bottom.jpg combo.jpg&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="combine-two-images-side-by-side"&gt;&lt;a class="toclink" href="#combine-two-images-side-by-side"&gt;Combine two images side-by-side&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;convert +append left.jpg right.jpg combo.jpg&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="make-a-slideshow"&gt;&lt;a class="toclink" href="#make-a-slideshow"&gt;Make a slideshow&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;mogrify -resize 800x600 -background black -gravity center -extent 800x600 *jpg # Make images the same size&lt;/code&gt;
&lt;code&gt;mogrify -auto-orient *jpg # Make the images all the same orientation&lt;/code&gt;
&lt;code&gt;convert -delay 800 -loop 0 *jpg slideshow.gif # Make a gif slideshow&lt;/code&gt;
&lt;code&gt;ffmpeg -i slideshow.gif slideshow.mp4 # Convert gif to mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="strip-identifying-info"&gt;&lt;a class="toclink" href="#strip-identifying-info"&gt;Strip identifying info&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;magick mogrify -strip *.jpg&lt;/code&gt;&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Dirt Bike Fuel Injector Cleaner</title><link href="https://evan.carlin.com/blog/dirt-bike-fuel-injector-cleaner/" rel="alternate"/><published>2023-03-26T00:00:00-06:00</published><updated>2023-03-26T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2023-03-26:/blog/dirt-bike-fuel-injector-cleaner/</id><summary type="html">&lt;p&gt;Cheap way to clean a fuel injector&lt;/p&gt;</summary><content type="html">&lt;p&gt;This winter, I set up my 2019 KTM 500 EXC-F as a snowbike. Riding and
working on it has been considerably more fun than I could have
imagined.&lt;/p&gt;
&lt;p&gt;&lt;img alt="snowbike" src="https://evan.carlin.com/static/images/snowbike.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The past few times out, it has been bogging around 90% throttle and
above. I'm new to working on dirt bikes, so I don't really know the
cause. But, I've read that bogging near wide-open throttle can be an
indication of fuel starvation. I have already replaced the fuel filter
in the gas tank on this bike, which is a common weak point. So, today
I decided to clean the fuel filter outside of the tank and clean the
fuel injector.&lt;/p&gt;
&lt;p&gt;Reading online, it seems that the two cleaning strategies are either
to send your fuel injector to a professional to have it cleaned or to
buy a tool to do it yourself. Part of the joy of owning this bike has
been working on it, so I went the DIY route.&lt;/p&gt;
&lt;p&gt;Normally, I don't mind spending money for the right tool for the
job. "Buy once, cry once" is cliché but works. Although, I have been
known to visit Harbor Freight to limit the number of tears I
shed. From my brief research, the go-to (and maybe only?) tool for
cleaning dirt bike injectors is the Motion-Pro Fuel Injector Cleaner
Kit. It retails for about $160, and I would have to wait for it to be
shipped to my house. Blugh, expensive for what amounts to a 9-volt
battery, switch, and a holder to direct the cleaner into the fuel
injector. I was hoping to solve this problem today. So, I went the
DIY-DIY route and built one myself.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fuel injector
cleaner" src="https://evan.carlin.com/static/images/fuel-injector-cleaner.jpg"&gt;&lt;/p&gt;
&lt;p&gt;A perfectly hideous homebrew tool. The 22-18 AWG butt splice
connectors slipped right over the prongs in the injector. They don't
fit snugly, but they hang on well enough to get the job done. I used a
9-volt battery because that is what the Motion-Pro tool uses. They say
not to hold the injector open for more than 3 seconds, presumably to
not burn it out. The injector is used to being opened and closed
rapidly, not being held open. The fuel system operates around 50 psi,
so I set the regulator on my compressor to that. When power is applied
to the injector, it opens and you can blow cleaner/air through it. I
backflushed first and then blew out the direction fuel travels.&lt;/p&gt;
&lt;p&gt;The tool worked like a charm, and I was able to flush some gunk out of
the injector. In the process, I realized my fuel hoses were crumbling
and definitely introducing little bits of rubber hose to the fuel. Of
course, all downstream of the two fuel filters in the system.&lt;/p&gt;
&lt;p&gt;I'll probably add a connector for the 9-volt and a switch to make
things easier. But, I will miss the leopard duct tape.&lt;/p&gt;
&lt;p&gt;There you go. If you're cheap, impatient, or both, like me, this will
get the job done for a fraction of the cost of the professional tool.&lt;/p&gt;
&lt;p&gt;For the programmers who read this article: That tool is a
slapped-together shell script. I wouldn't hand it to someone else and
tell them to use it, but it worked for me, and if you're careful,
maybe it will for you too.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Docker top</title><link href="https://evan.carlin.com/blog/docker-top/" rel="alternate"/><published>2023-01-27T00:00:00-07:00</published><updated>2023-01-27T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2023-01-27:/blog/docker-top/</id><summary type="html">&lt;p&gt;Finding which Docker container is running a process&lt;/p&gt;</summary><content type="html">&lt;p&gt;We run an internal GPU server at work. The scientists share the
resource and access them through Jupyter servers. Sometimes we run
into issues where GPUs report being in use even though users aren't
actively using them. This is caused by Jupyter kernels left idling
that once upon a time were using the GPU.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;nvidia-smi&lt;/code&gt; will tell you the PID of each process and what
GPU it is using. You can then feed that into the script below which
will tell you which container is running that PID.&lt;/p&gt;
&lt;p&gt;Someday I'll figure out if there is a fix to idling kernels holding
resources. But for now this will do.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/bash
set -eou pipefail

pid=$1
for c in $(docker ps --format '{{.Names}}'); do
    if docker container top &amp;quot;$c&amp;quot; '-o pid' | grep &amp;quot;$pid&amp;quot; &amp;gt; /dev/null 2&amp;gt;&amp;amp;1; then
        echo &amp;quot;$c&amp;quot;
        break
    fi
done
&lt;/code&gt;&lt;/pre&gt;</content><category term="blog"/></entry><entry><title>Converting HEIC image to JPG</title><link href="https://evan.carlin.com/blog/converting-heic-image-to-jpg/" rel="alternate"/><published>2022-11-08T00:00:00-07:00</published><updated>2022-11-08T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2022-11-08:/blog/converting-heic-image-to-jpg/</id><summary type="html">&lt;p&gt;Using ImageMagick to convert HEIC to JPG&lt;/p&gt;</summary><content type="html">&lt;p&gt;This isn't new information. I learned how to do it from
&lt;a href="https://www.computerhope.com/issues/ch002248.htm"&gt;here&lt;/a&gt;. But, I'm
recording this because I have trouble finding that link. And maybe
someone else will come across this and it will be useful.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;#! /bin/bash
set -eou pipefail
shopt -s nocaseglob

b=heic-bkp
mkdir &amp;quot;$b&amp;quot;
for f in *.heic; do
  convert &amp;quot;$f&amp;quot; &amp;quot;$(basename $f .HEIC)&amp;quot;.jpg
  mv &amp;quot;$f&amp;quot; &amp;quot;$b&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;</content><category term="blog"/></entry><entry><title>Linux threads and PIDs</title><link href="https://evan.carlin.com/blog/linux-threads-and-pids/" rel="alternate"/><published>2022-09-17T00:00:00-06:00</published><updated>2022-09-17T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2022-09-17:/blog/linux-threads-and-pids/</id><summary type="html">&lt;p&gt;The relationship between threads and PIDs&lt;/p&gt;</summary><content type="html">&lt;p&gt;At our weekly software team meeting we were discussing our progress
towards removing Flask in favor of Tornado. As part of the dicussion
we were talking about how we need to be careful in Tornado because no
"work" can happen in the main server process. In Flask work could
happen because multiple threads were running. So if one thread was
blocked doing some work other threads could still answer
requests. Tornado is single threaded with cooperative multitasking so
that won't work. Tasks need to cooperatively monitor other processes
doing the actual work. In passing the CTO mentioned that each thread
has a unique PID (or at least, that's what I heard him say). That
surprised me. I assumed that threads shared a PID.&lt;/p&gt;
&lt;p&gt;I did a bit of research and it looks like things have changed over
time. Specifically pre 2003 (&amp;lt;2.6 kernel) the thread implementation
was called
&lt;a href="https://en.wikipedia.org/wiki/LinuxThreads"&gt;LinuxThreads&lt;/a&gt;. Each
LinuxThread had a separate PID. In &amp;gt;=2.6 the thread implementation
changed to &lt;a href="https://en.wikipedia.org/wiki/Native_POSIX_Thread_Library"&gt;Native Posix Thread Library
(NPTL)&lt;/a&gt;. This
is an implementation of threads that aligns more closely with the
POSIX threads standard. In pthreads they &lt;a href="https://man7.org/linux/man-pages/man7/pthreads.7.html"&gt;share the same
PID&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To add to the confusion the definition of a PID is hard to nail
down. It seems POSIX and Linux have &lt;a href="https://stackoverflow.com/a/9154725"&gt;different
meanings&lt;/a&gt; for the term PID. In
addition, the kernel meaning of a PID and user meaning of a PID aren't
the same. See the notes section of the &lt;a href="https://man7.org/linux/man-pages/man2/getpid.2.html"&gt;getpid man
page&lt;/a&gt; for more
discussion. In programs like top it may display one of many different
identifiers under the PID column (see section &lt;a href="https://man7.org/linux/man-pages/man1/top.1.html"&gt;3.a.19
PID&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To add further to the confusion it seems threads are mostly
implemented in user space so libc implementations besides glibc (ex
&lt;a href="https://www.musl-libc.org/intro.html"&gt;musl&lt;/a&gt;) may do things
differently.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Going one level deeper: The proc filesystem (procfs)</title><link href="https://evan.carlin.com/blog/going-one-level-deeper-the-proc-filesystem-procfs/" rel="alternate"/><published>2022-09-16T00:00:00-06:00</published><updated>2022-09-16T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2022-09-16:/blog/going-one-level-deeper-the-proc-filesystem-procfs/</id><summary type="html">&lt;p&gt;Learning a bit about procfs&lt;/p&gt;</summary><content type="html">&lt;p&gt;At work our CTO has started doing "Software Life Stories". The idea is
he will bring in someone who has had a career as a programmer to talk to
us over lunch. For our first installment he brought in his friend &lt;a href="https://web.archive.org/web/20100221023556/http://www.profcon.com/profcon/tac.htm"&gt;Tom
Cargill&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tom has been a programmer for longer than I've been alive and had a
wealth of information to share. One question I asked him was if he
could point to specific things he did that made him a better
programmer One of the answers he gave was that he understood what he
was working on at one level deeper than the current problem he was
solving. For example, he said while working on debuggers he understood
the machine code beneath the language he was writing a debugger for.&lt;/p&gt;
&lt;p&gt;As part of Tom's career he worked at the Bell Labs.  While there he
helped invent the proc filesystem. He mentioned this in passing saying
that it helped him build a debgger. I had heard of proc before but
didn't know anything about it. A few days later I was checking in on
the health of our servers and applications. The script we use to do
that had identified a problem. To understand what might be going on I
first needed to understand what the script was doing. While reading I
saw &lt;code&gt;"/proc/$pid/cwd"&lt;/code&gt;. Shoot there's proc again. Time to take Tom's
advice and go one level deeper to understand what the heck it is. Or
at the very least scratch the surface of the next level.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20220426215242/https://opensource.com/article/20/4/proc-filesystem"&gt;This
article&lt;/a&gt;
seems like a good overview of procfs. Take that from someone who
doesn't know anything about procfs so everything in there could be a
lie. The high-level view is it dumps kernel datastrcutures to files so
applications can learn more about what is going on in the system. So,
the &lt;code&gt;"/proc/$pid/$cwd"&lt;/code&gt; above is getting the current working directory
of &lt;code&gt;$pid&lt;/code&gt;. Interesting! Systems that know about themselves and you can
ask questions of are one's I like working on.&lt;/p&gt;
&lt;p&gt;I messed around some with the examples in the article. This one jumped
out to me:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;~$ cat
# In another terminal window
~$ pgrep -x cat
187843
~$ echo foo &amp;gt; /proc/187843/fd/0
# foo will appear in the other terminal window
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool. So, we can write to fd/0 (STDIN) of the process. Since that
process is cat it writes out to STDOUT whatever is input on STDIN. I
wonder if we can read from STDOUT?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;~$ tail -f /proc/187843/fd/1
# In another terminal window
~$ echo foo &amp;gt; /proc/187843/fd/0
# crickets in the window running tail...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hmm, nothing is ever quite as easy as I hope it will be. I don't
understand why I can't just listen to STDOUT but it has something to
do with the fact that &lt;a href="https://unix.stackexchange.com/a/275826"&gt;cat is connected to a tty&lt;/a&gt;. In an
effort to not go too deep I'll save that rabbit hole for another day.&lt;/p&gt;
&lt;p&gt;As I said earlier Tom mentioned that that he helped to invent this
filesystem. I looked up &lt;a href="https://lucasvr.gobolinux.org/etc/Killian84-Procfs-USENIX.pdf"&gt;the paper&lt;/a&gt; about the invention of /proc. It
is shockingly readable and short for a technical paper so I definitely
think it is worth a read.&lt;/p&gt;
&lt;p&gt;If you happen to be trying to run the examples on your own machine you
may have found that &lt;code&gt;/proc&lt;/code&gt; doesn't exist. On some flavors of Unix
there is no &lt;code&gt;/proc&lt;/code&gt;. This got me curious about the reasons why. It
seems that &lt;a href="https://stackoverflow.com/a/5880485"&gt;it can be
unsafe&lt;/a&gt;. FreeBSD had it at one
point &lt;a href="https://lists.freebsd.org/pipermail/freebsd-fs/2011-February/010747.html"&gt;but removed
it&lt;/a&gt;. There
is much hand waving aobut the reasons why it was removed. My takeway
from that is that to build anything mission critical on top of proc
I'll need to understand it a bit more and some of the race conditions
that can crop up when accessing the files in there*.&lt;/p&gt;
&lt;p&gt;That's all for today. I'll call it 5% of a full level deeper.&lt;/p&gt;
&lt;p&gt;* While looking into procfs I came across a really neat &lt;a href="https://youtu.be/1hScemFvnzw"&gt;YouTube
video&lt;/a&gt; of how file descriptors can be
used to prevent race conditions when accessing files. I rarely watch
YouTube videos about coding but that video is worth a watch and makes
me want to see more of his videos.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>All the small things</title><link href="https://evan.carlin.com/blog/all-the-small-things/" rel="alternate"/><published>2022-02-05T00:00:00-07:00</published><updated>2022-02-05T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2022-02-05:/blog/all-the-small-things/</id><summary type="html">&lt;p&gt;Refactoring by creating seams and using small objects&lt;/p&gt;</summary><content type="html">&lt;p&gt;As I cruised the internet last night I came across &lt;a href="https://news.ycombinator.com/item?id=30191636"&gt;this Hacker News
thread&lt;/a&gt; about rewriting an entire application. At work we've been
having talks about rewriting our frontend code. It is written in
AngularJS which is awful and reached end of life on December 31, 2021&lt;/p&gt;
&lt;p&gt;In the thread a commenter mentions the talk "&lt;a href="https://www.youtube.com/watch?v=8bZh5LMaSmE"&gt;All The Small Things&lt;/a&gt;" by
Sandi Metz. I figured I'd do what I normally do: start the talk, get
bored 2 minutes in, and then move on to other content. I ended up
watching the talk the whole way through and then waking up in the
morning and writing this post I enjoyed it so much. Sandi is an
excellent speaker and very clearly describes the concepts she's
talking about. I highly recommend watching the talk.&lt;/p&gt;
&lt;h1 id="seams"&gt;&lt;a class="toclink" href="#seams"&gt;Seams&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The talk focuses quite a bit on object oriented design. At work we
don't use many objects. But, I think the concepts the talk focuses on
through objects are concepts we use. These concepts have proven to be life savers
when complexity needs to be added to our system (ex dynamic dispatch).&lt;/p&gt;
&lt;p&gt;One concept Sandi brings up is “seams”. My interpretation of this is
they are places where you can get a handle on what needs to happen,
dispatch, and then go to one small area of the code to handle the
complexity for just the thing you care about. The opposite is what the
code in the talk starts as, complicated branching where all of the
complexity for everything is mixed together.&lt;/p&gt;
&lt;h1 id="hourglass"&gt;&lt;a class="toclink" href="#hourglass"&gt;Hourglass&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I've come across this concept before and thought of it like an
hourglass. For example, &lt;a href="https://github.com/radiasoft/sirepo"&gt;at work&lt;/a&gt;
all functions that begin with &lt;code&gt;def api_&lt;/code&gt; are our API endpoints. In our
Flask server
(&lt;a href="https://github.com/radiasoft/sirepo/blob/cb5359c0f3e20c8d93385f638c893ce47a57bfba/sirepo/server.py"&gt;server.py&lt;/a&gt;
and
&lt;a href="https://github.com/radiasoft/sirepo/blob/cb5359c0f3e20c8d93385f638c893ce47a57bfba/sirepo/job_api.py"&gt;job_api.py&lt;/a&gt;)
I think of them as the top of the hourglass. They are varied and do
many different things. But if you squint all of these API endpoints
share some common functionality. They need security (who can access
it?), they have common input (how do we parse the request to convert
json to a Python dict?), and they need common resources (is the
database initialized?).&lt;/p&gt;
&lt;p&gt;At the small constricted neck of the hourglass we answer these
questions. Importantly, we answer them abstractly for all API
endpoints and we do it in only one place
(&lt;a href="https://github.com/radiasoft/sirepo/blob/cb5359c0f3e20c8d93385f638c893ce47a57bfba/sirepo/uri_router.py"&gt;uri_router.py&lt;/a&gt;). I
think this is the seam.&lt;/p&gt;
&lt;p&gt;The API requests are then dispatched (there is even a
&lt;a href="https://github.com/radiasoft/sirepo/blob/cb5359c0f3e20c8d93385f638c893ce47a57bfba/sirepo/uri_router.py#L193"&gt;&lt;code&gt;_dispatch&lt;/code&gt;&lt;/a&gt;
method in uri_router) to their respective function. At that function a
complex but small and isolated bit of code unique to that API happens.&lt;/p&gt;
&lt;h1 id="using-seamshourglass-to-fix-problems"&gt;&lt;a class="toclink" href="#using-seamshourglass-to-fix-problems"&gt;Using seams/hourglass to fix problems&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I've put these concepts to use before. We once had &lt;a href="https://github.com/radiasoft/sirepo/issues/3350"&gt;an
issue&lt;/a&gt; where we had a
bug in threaded code because we were clobbering flask.g (a thread safe
datastructure). &lt;a href="https://github.com/radiasoft/sirepo/pull/3352/files"&gt;Here is&lt;/a&gt; the
PR that fixed the bug. It is pretty hairy but we fixed many concepts
along the way. The important part of that PR with regards to
seams/hourglass is that &lt;a href="https://github.com/radiasoft/sirepo/pull/3352/files#diff-e650af3da09ca9afb6af3c37c36877609af02767bcbf301d0f896bec8c045b67R195"&gt;we were able to add&lt;/a&gt; &lt;code&gt;with
sirepo.auth.process_request()&lt;/code&gt; to the &lt;code&gt;_dispatch&lt;/code&gt; of uri_router to fix
all APIs. &lt;code&gt;process_request&lt;/code&gt; is tricky code. It handles threaded
(flask) and non-threaed (tornado) environments and sets up our auth
(cookie) and database (sqlite) for every request. By having all API
requests go through &lt;code&gt;_dispatch&lt;/code&gt; we had a handle (a seam or the neck of
the hourglass) where we could do this.&lt;/p&gt;
&lt;p&gt;Had we not had this seam I think the fix would have been harder to
identify. Answering a question like "how do I do this for all APIs?"
would have been overwhelming. Likewise, it may have lead to a
solution like adding a new decorator to every &lt;code&gt;def api_*&lt;/code&gt; which
spreads more complexity throughout the codebase and introduces more
areas for error (ex. forgetting to add the decorator to a new API).&lt;/p&gt;
&lt;h1 id="how-to-write-code-with-seams"&gt;&lt;a class="toclink" href="#how-to-write-code-with-seams"&gt;How to write code with seams&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Sadly, I'm going to leave you empty in one critical way as the talk
left me. How does one identify code without seams, find what the seams
are, and add them? I think I'm ok at adding seams. Maybe I can
identify code without them (lot's of complicated logic mixed
together). But, I'm not confident I know how to find what the seems
are. For example, I could use uri_router to add complexity (using the
seams) but I didn't write that code initially.&lt;/p&gt;
&lt;p&gt;My hope is that by having this new mental model, "seams", maybe I'll
be able to add them in the future. If you know of a good way to
identify what the seams should be in an application please reach out.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Successful use of git reflog</title><link href="https://evan.carlin.com/blog/successful-use-of-git-reflog/" rel="alternate"/><published>2022-01-20T00:00:00-07:00</published><updated>2022-01-20T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2022-01-20:/blog/successful-use-of-git-reflog/</id><summary type="html">&lt;p&gt;Using &lt;code&gt;git reflog&lt;/code&gt; to recover from a botched rebase&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;code&gt;git reflog&lt;/code&gt; is one of those commands that I've tried to use many
times with no success. Usually something goes horribly wrong with one
of git's more advanced commands (ex rebase, cherry-pick, reset), I
then search for something to recover my mistake, someone online says
it is so easy with a simple reflog, I try it, make my problem worse,
and eventually give up. But, a few days ago I had success!&lt;/p&gt;
&lt;h2 id="play-by-play"&gt;&lt;a class="toclink" href="#play-by-play"&gt;Play-by-play&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I opened &lt;a href="https://github.com/radiasoft/sirepo/pull/4044"&gt;a pull
request&lt;/a&gt; and was
resolving comments left on it. I fixed a group of related comments,
made a commit for the changes, and started to run the tests. While the
tests were running I went on to fix other comments. I stacked on two
more commits. I then went to the tests and saw that they failed. My
changes in the first commit since opening the pull request weren't
quite right.&lt;/p&gt;
&lt;p&gt;Here is what my commit history looked like&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;$ git log --abbrev-commit --pretty=oneline | head -n 5
64f97aac7 move checking for mpi to sim_data
5e95a50e3 simplify
a565074a0 mpi abort is needed; share between common-header.py.jinja and sirepo.mpi
688fd1c92 Fix #4038: Always call python parameters.py from slurm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;688fd1c92&lt;/code&gt; was the original commit where I opened the pull
request. &lt;code&gt;a565074a0&lt;/code&gt; was the first commit resolving pull request
comments. &lt;code&gt;5e95a50e3&lt;/code&gt; and &lt;code&gt;64f97aac7&lt;/code&gt; were more fixing of pull request
comments.&lt;/p&gt;
&lt;p&gt;So, I wanted to make some changes but they are relevant to &lt;code&gt;688fd1c92&lt;/code&gt;
since that was the commit I was running the tests for. My normal
workflow for this sort of thing is to:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;# make some changes
$ git stash
$ git rebase -i master
# select edit for the commit I want to edit (in this case 688fd1c92)
$ git stash pop
$ git commit --all --amend --no-edit
$ git rebase continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I did just that and here is what I saw:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;$ git status
On branch 4038-nersc-prepare-slurm
Your branch and 'origin/4038-nersc-prepare-slurm' have diverged,
and have 4 and 4 different commits each, respectively.
  (use &amp;quot;git pull&amp;quot; to merge the remote branch into yours)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Shoot! I did &lt;code&gt;git rebase -i master&lt;/code&gt;. Rebasing on master means that my
original commit for the pull request, &lt;code&gt;688fd1c92&lt;/code&gt;, was modified from
the rebase because I set the base to be master. I had broken the golden
rule of rebase - &lt;a href="https://www.gitkraken.com/blog/golden-rule-of-rebasing-in-git#:~:text=Rule%20of%20Rebasing-,The%20Golden%20Rule%20of%20Rebasing%20reads%3A%20%E2%80%9CNever%20rebase%20while%20you,no%20possibility%20of%20deleting%20data."&gt;never rebase on a public
branch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The correct syntax would've been &lt;code&gt;git rebase -i a565074a0^&lt;/code&gt;. Which
means rebase on the commit one before the commit I want to edit.&lt;/p&gt;
&lt;p&gt;Ugh, now to recover from my mistake. Thankfully I hadn't pushed
anything so recovery shouldn't be too bad.&lt;/p&gt;
&lt;p&gt;Off to Google how to recover. I searched for "how to undo a git
rebase". &lt;a href="https://stackoverflow.com/a/135614/5518313"&gt;The first
answer&lt;/a&gt;, as expected, says
to use &lt;code&gt;git reflog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is what I saw:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;$ git reflog | head
c152af107 HEAD@{0}: rebase (finish): returning to refs/heads/4038-nersc-prepare-slurm
64f97aac7 HEAD@{1}: rebase (pick): move checking for mpi to sim_data
c7e8b67af HEAD@{2}: rebase (pick): simplify
df5260920 HEAD@{3}: commit (amend): mpi abort is needed; share between common-header.py.jinja and sirepo.mpi
a56507a40 HEAD@{4}: rebase: fast-forward
688fd1c92 HEAD@{5}: rebase (start): checkout master
9fc121349 HEAD@{6}: reset: moving to HEAD
9fc121349 HEAD@{7}: commit: move checking for mpi to sim_data
5e95a50e3 HEAD@{8}: commit: simplify
a56507a40 HEAD@{9}: commit: mpi abort is needed; share between common-header.py.jinja and sirepo.mpi
688fd1c92 HEAD@{10}: commit: Fix #4038: Always call python parameters.py from slurm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, I must be getting better as a programmer because this looks
10x less initmidating than the last time I remember looking at it. I
see that for &lt;code&gt;HEAD@{5}&lt;/code&gt; it says &lt;code&gt;rebase (start)&lt;/code&gt;. I probably want to
move to the commit just before that. So I tried that:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;$ git checkout HEAD@{6}
$ git diff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I did the &lt;code&gt;git diff&lt;/code&gt; to make sure that things look ok. They do so I
think this is where I want to be. Let's do it for real:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;$ git switch -
$ git reset --hard HEAD@{6}
$ git status
On branch 4038-nersc-prepare-slurm
Your branch is behind 'origin/4038-nersc-prepare-slurm' by 2 commits, and can be fast-forwarded.
  (use &amp;quot;git pull&amp;quot; to update your local branch)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;git diff&lt;/code&gt; confirmed that I was back to where I was before the botched
rebase! The only bummer is that I lost the work that I wanted to
apply to &lt;code&gt;688fd1c92&lt;/code&gt;. I'm sure there is a way I could've saved it but no big deal. It was easy to write again.&lt;/p&gt;
&lt;h1 id="editing-a-commit-the-right-way"&gt;&lt;a class="toclink" href="#editing-a-commit-the-right-way"&gt;Editing a commit the right way&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Now that &lt;code&gt;git reflog&lt;/code&gt; saved my skin I wanted to edit the commit with
my changes. Here is what I did:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;# make the changes
$ git stash
$ git rebase -i 688fd1c92^ # ^ means one commit before 688fd1c92
# select edit for 688fd1c92
$ git stash pop
$ git commit --all --amend --no-edit
$ git rebase continue
$ git status
On branch 4038-nersc-prepare-slurm
Your branch is ahead of 'origin/4038-nersc-prepare-slurm' by 3 commits.
  (use &amp;quot;git push&amp;quot; to publish your local commits)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice, 3 commits and not in conflict with the upstream. Thanks &lt;code&gt;git
reflog&lt;/code&gt;!&lt;/p&gt;</content><category term="blog"/></entry><entry><title>What Does This Code do?</title><link href="https://evan.carlin.com/blog/what-does-this-code-do/" rel="alternate"/><published>2021-12-24T00:00:00-07:00</published><updated>2021-12-24T00:00:00-07:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2021-12-24:/blog/what-does-this-code-do/</id><summary type="html">&lt;p&gt;Understanding code in a pull request I reviewed.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A colleague opened a &lt;a href="https://github.com/radiasoft/sirepo/pull/3785"&gt;pull
requests&lt;/a&gt; with this code in it a
some months ago ago.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;self.nextId = function() {
    // a uuid generator found on the interwebs
    return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =&amp;gt;
        (c ^ window.crypto.getRandomValues(new Uint8Array(1))[0] &amp;amp; 15 &amp;gt;&amp;gt; c / 4).toString(16)
    );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I reviewed the code and &lt;a href="https://github.com/radiasoft/sirepo/pull/3785#discussion_r713234541"&gt;left this comment&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-txt"&gt;The word next is misleading because to me that implies some ordering and the
uuids generated are random. Also, is there a reason we need true uuids instead
of doing something like sirepo-lattice.js? If there is a reason I think adding a
comment about it in the code would be helpful.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the comment was fine, I hope, I did not actually understand this
code which is a problem since I reviewed it. So, here is my attempt to
go back and figure out what the code actually does.&lt;/p&gt;
&lt;p&gt;This is just a stream of consciousness of me solving the problem. It
bounces around. For better or worse that is how I think.&lt;/p&gt;
&lt;h1 id="breaking-down-the-code"&gt;&lt;a class="toclink" href="#breaking-down-the-code"&gt;Breaking down the code&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;There is quite a bit of this code I don't understand so let's dive
right in.&lt;/p&gt;
&lt;p&gt;I think &lt;code&gt;1e7&lt;/code&gt; is scientific notation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; 1e7
10000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The REPL confirms that it is.&lt;/p&gt;
&lt;p&gt;Why is &lt;code&gt;1e7&lt;/code&gt; an array and what does it mean to add the other values to
it?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; [1] + 2
'12'
&amp;gt; x = [1] + 2
'12'
&amp;gt; typeof x
'string'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://www.destroyallsoftware.com/talks/wat"&gt;WAT&lt;/a&gt;?? (If you do
nothing else with this article please stop and watch this talk.)&lt;/p&gt;
&lt;p&gt;JS is always full of surprises. Some days I kick myself wishing I
could remember all of the gotchas. Other days I'm just happy if I
remember to zip up my pants after going to the bathroom. On those days
I can not be bothered by remembering details like this. What I do try
and remember is that JS has strangeness like this. When doing any
operation to two things of different types it is a good idea to slow
down and make sure I get it right.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; [1e7]+-1e3+-4e3+-8e3+-1e11
'10000000-1000-4000-8000-100000000000'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, we have a string of some numbers separated by dashes. Looks
something like a UUID.&lt;/p&gt;
&lt;p&gt;At this point I get distracted and think "what happens when I just
search 'js uuid generate'."&lt;/p&gt;
&lt;p&gt;The first result is, as expected, a &lt;a href="https://stackoverflow.com/a/2117523"&gt;link to
StackOverflow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The top answer mentions the &lt;a href="https://www.npmjs.com/package/uuid"&gt;UUID
module&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the simple case (all we need) that module just calls
&lt;code&gt;crypto.randomUUID()&lt;/code&gt;. Why can't we just call that? Off to
&lt;a href="https://caniuse.com/?search=randomUUID"&gt;caniuse.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ugh, we don't have the strictest requirements for supporting old
browsers but if the latest Safari (15.1) does not support it then it
won't work for us.&lt;/p&gt;
&lt;p&gt;This makes me wonder how JS implementations implement it. A quick
search yields &lt;a href="https://wicg.github.io/uuid/#the-randomuuid-method"&gt;the W3C
spec&lt;/a&gt;. The
description of the spec does not look much like the code we are
using. I then go to the
&lt;a href="https://nodejs.org/dist/latest-v15.x/docs/api/crypto.html#crypto_crypto_randomuuid_options"&gt;Node.js&lt;/a&gt;
docs. Why don't the docs link to the source code? That is something
&lt;a href="https://ruby-doc.org"&gt;ruby-doc.org&lt;/a&gt; really got right. Anyways, &lt;a href="https://github.com/nodejs/node/blob/ad91abcbad7174f61794b52e2ace5503d0b1b576/lib/internal/crypto/random.js#L395"&gt;the
code&lt;/a&gt;
is not that hard to find. Again, this looks like the spec and not much
like our code.&lt;/p&gt;
&lt;p&gt;From reading the spec and looking at our code I think our code is
probably taking some short cuts. Reading through the SO comments this
seems to be the case. Again, I don't feel it is that big of a deal
because the likelihood of a collision seems pretty small and fairly
harmless (the UUID's are for one simulation for one user).&lt;/p&gt;
&lt;p&gt;Back to the code...&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;'10000000-1000-4000-8000-100000000000'
&amp;gt; ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =&amp;gt; console.log(c));
1
0
0
&amp;lt;snip&amp;gt;
&amp;gt; ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =&amp;gt; 'e');
'eeeeeeee-eeee-4eee-eeee-eeeeeeeeeeee'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is replacing '0', '1', and '8' with the result of the callback.&lt;/p&gt;
&lt;p&gt;Now I need to understand the structure of a UUID.&lt;/p&gt;
&lt;p&gt;The '4' that is not replaced is the version of the UUID spec. Version
&lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)"&gt;4 means
random&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Why is the '8' getting replaced though? That is &lt;a href="https://www.famkruithof.net/guid-uuid-make.html"&gt;the
"variant"&lt;/a&gt; and has
some meaning I don't yet fully understand. Maybe the &lt;code&gt;c ^ ...&lt;/code&gt; does
some mask operation that includes it?&lt;/p&gt;
&lt;p&gt;There are still some missing pieces but I'm starting to see that we
are mostly just replacing values in the original string with randomly
generated values.&lt;/p&gt;
&lt;p&gt;Now, let's understand some about the random generation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; crypto.getRandomValues(new Uint8Array(1))[0]
Uncaught TypeError: crypto.getRandomValues is not a function
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gr888. Maybe my version of node is out of date? Yes, but also there is
&lt;a href="https://github.com/nodejs/node/blob/ad91abcbad7174f61794b52e2ace5503d0b1b576/lib/internal/crypto/random.js#L304"&gt;this handy
comment&lt;/a&gt;
in the code saying I can just use &lt;code&gt;randomFillSync&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; crypto.randomFillSync(new Uint8Array(1))[0]
124
&amp;gt; crypto.randomFillSync(new Uint8Array(1))[0]
91
&amp;gt; crypto.randomFillSync(new Uint8Array(2))
Uint8Array(2) [ 39, 53 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, we are getting one 8 bit unsigned integer. But, now I'm
starting to get scared. Putting together this whole line is going to
be tricky.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;(c ^ window.crypto.getRandomValues(new Uint8Array(1))[0] &amp;amp; 15 &amp;gt;&amp;gt; c / 4).toString(16)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is more bitwise operations going on than I am comfortable
with. I need to remember operator precedence rules (does the &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;
happen before or after the &lt;code&gt;&amp;amp;&lt;/code&gt; or does it even matter)? And, I need to
understand why we do each operation as well as what the magic numbers
15, 4, and 16 all mean.&lt;/p&gt;
&lt;p&gt;Back to the REPL.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; c = 1
1
&amp;gt; c ^ crypto.randomFillSync()[0]
9
&amp;gt; a = new Uint8Array(1)
Uint8Array(1) [ 0 ]
&amp;gt; a[0] = 34
34
&amp;gt; x = a[0]
34
&amp;gt; c ^ x
35
&amp;gt; 0 ^ x
34
&amp;gt; x = 254
254
&amp;gt; 1 ^ x
255
&amp;gt; 0 ^ x
254
&amp;gt; 1 ^ 1
0
&amp;gt; a = new Uint8Array(1)
Uint8Array(1) [ 0 ]
&amp;gt; a[0] = 260
260
&amp;gt; a[0]
4
&amp;lt;snip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I did a lot more messing around. I realized I was mistaking &lt;code&gt;^&lt;/code&gt; with
&lt;code&gt;|&lt;/code&gt;. Clearly I don't do much bit twiddling. &lt;code&gt;^&lt;/code&gt; is
&lt;a href="https://en.wikipedia.org/wiki/Exclusive_or"&gt;XOR&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I am still not quite sure what this step is doing but I think it does
not matter for the 1's or 0's. It has to do with the 8.&lt;/p&gt;
&lt;p&gt;Looking at other &lt;a href="https://stackoverflow.com/a/8809472"&gt;SO answers&lt;/a&gt;
this seems to be the case. That answer does some masking in the 8 case
but not in the other cases.&lt;/p&gt;
&lt;p&gt;Things are still unclear and I realize I could be thrown off by
operator precedence. It turns out &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table"&gt;I am getting it
wrong&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So the division happens first&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; 1/4
0.25
&amp;gt; 0/4
0
&amp;gt; 8/4
2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the bit shift&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; 15 &amp;gt;&amp;gt; 1 / 4
15
&amp;gt; 15 &amp;gt;&amp;gt; 0 / 4
15
&amp;gt; 15 &amp;gt;&amp;gt; 8 / 4
3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the bitwise AND&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;gt; 0b1111
15
&amp;gt; 0b0011
3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;15 and 3 are the two possible values for the result of the &lt;code&gt;&amp;amp;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0b1111&lt;/code&gt; means that any bit that is set in the number being &lt;code&gt;&amp;amp;&lt;/code&gt; with
will be present in the result. So, once again the 0 vs 1 is confirmed
to not mean anything because the original number is just being passed
through.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0b0011&lt;/code&gt; means that the leading two bits will be 0 in the output of
the &lt;code&gt;&amp;amp;&lt;/code&gt;. Why is this necessary? Let's look at the &lt;code&gt;^&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;8 is &lt;code&gt;0b1000&lt;/code&gt;. Then we are doing a &lt;code&gt;^&lt;/code&gt; with some number that is in the
form &lt;code&gt;0b00xx&lt;/code&gt; (the x's mean could be 1 or 0 since we did an &lt;code&gt;&amp;amp;&lt;/code&gt; with a
1 in that position). So this results in some number of the form
&lt;code&gt;Ob10xx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Remember that the 8 is the "variant" bit in the UUID? Looking at &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants"&gt;the
Wikipedia
page&lt;/a&gt;
for UUID's I see that a variant of the form &lt;code&gt;0b10xx&lt;/code&gt; is a variant that
conforms to DCE 1.1, ISO/IEC 11578:1996. That is the variant for the
spec we are after!&lt;/p&gt;
&lt;p&gt;The final bit of code is the &lt;code&gt;toString(16)&lt;/code&gt;. I know that UUID's are
made up of hex characters and that hex is base 16. Looking at the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString#parameters"&gt;JS
docs&lt;/a&gt;
I see that specifying a number in toString sets the radix (aka the
base). So, setting it to 16 makes it a base 16 (hex) string.&lt;/p&gt;
&lt;p&gt;Holy moly! There were moments in there I wasn't sure I was going to be
able to figure all of this out.  Putting it all together, the code
creates a string of the right shape as a UUID with a few special
characters (4 and 8). Everything besides the special characters are
replaced with a random character in the hex range. The 4 is not
replaced at all. The 8 is replaced with a value that conforms to
&lt;code&gt;0b10xx&lt;/code&gt;. That's all that's needed to create the UUID we are after.&lt;/p&gt;
&lt;h1 id="thoughts-on-the-code"&gt;&lt;a class="toclink" href="#thoughts-on-the-code"&gt;Thoughts on the code&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Now looking at this code it makes sense (yay:)) but honestly it looks
to me like &lt;a href="https://en.wikipedia.org/wiki/Code_golf"&gt;code golf&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using scientific notation (ex &lt;code&gt;1e7&lt;/code&gt;) is a cool way to make something
with 8 characters but throws the reader off in a couple of important
ways. First, the number is going to be converted to a string so it is
confusing to use a number. Second, the number is made up of a 1
followed by n 0's. When I read code I'm looking for patterns. This
pattern is actually totally not important. The 1's vs 0's make no
difference in the final result (only the field with the leading 4 and
8 mattered).&lt;/p&gt;
&lt;p&gt;Using the syntax &lt;code&gt;[1e7]+-1e3&lt;/code&gt; is using bizarre (IMHO) type rules in
JS. You get a string, and the &lt;code&gt;-&lt;/code&gt; isn't for creating a negative number
but for the hyphen separating the parts of the UUID.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;c ^ window.crypto.getRandomValues(new Uint8Array(1))[0] &amp;amp; 15 &amp;gt;&amp;gt; c /
4&lt;/code&gt; is a fairly hard to understand line too. It should be read from
right to left (the opposite of the way code is normally read) because
of operator precedence. Operator precedence is something developers
need to know but also isn't something they will know off of the top of
their head. Especially for operators that don't appear anywhere else
in our code (&lt;code&gt;^&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;). I think too many ideas are happening in
one line. The terse vs readable balance has swung too far in the terse
direction. In addition, there are the magic numbers of 15 and 4. They
make sense once you break the code down but having a function/named
const I think would be better.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Ultimately, I think the code is too terse but maybe that is a matter
of taste and it is an exercise in futility to make it clearer. It is
pretty unclear to the untrained eye what it does. But, I never know if
the eye should become more trained (read: I should become a better
programmer) or if the code should be re-written to be clearer.&lt;/p&gt;
&lt;p&gt;The code is in a small part of our codebase, isn't mission critical,
can be recovered from if it is buggy, and is constrained neatly within
a function call. So, it is probably ok as it is but I still can't help
but try and re-write it.&lt;/p&gt;
&lt;p&gt;Here is my attempt&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;// Generate a version 4 (random) variant DCE 1.1, ISO/IEC 11578:1996 UUID
// Adapted from https://stackoverflow.com/a/2117523
// TODO(e-carlin): When more browsers support crypto.randomUUID() use it instead
self.randomUuid = function() {
    function randomHexValues(count, prefix) {
        return (prefix || '') + [...Array(count)].map(
            _ =&amp;gt; toHexString(randomUint4())
        ).join('');
    }

    function randomUint4() {
        return window.crypto.getRandomValues(new Uint8Array(1))[0] &amp;gt;&amp;gt; 4;
    }

    function toHexString(uint4) {
        return uint4.toString(16);
    }

    function variantHexValue() {
        // Variant must begin with 0b10xx
        // See https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants
        let v = randomUint4();
        v = v | 0b1000;
        v = v &amp;amp; 0b1011;
        return toHexString(v) + randomHexValues(3);
    }

    const VERSION = '4';

    return [
        randomHexValues(8),
        randomHexValues(4),
        randomHexValues(3, VERSION),
        variantHexValue(),
        randomHexValues(12),
    ].join('-')
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is much less concise. But, since our code rarely handles things
like this I think being less concise here is better. By being less
concise there are many breadcrumbs sprinkled throughout that I think
help explain what is going on. In addition, I think it is possible it
will make developers smarter by reading it. If you don't know about
UUID's (I didn't before writing this) then you at least learn they
have this specific layout and there are versions and variants embedded
in their structure. Ultimately, when browsers support &lt;code&gt;randomUUID&lt;/code&gt;
we'll be using that and leave UUID generation up to the pro's.&lt;/p&gt;
&lt;p&gt;Now, off to forget everything I just learned about UUID's, operator
precedence, and bitwise operators.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Git Branch Naming</title><link href="https://evan.carlin.com/blog/git-branch-naming/" rel="alternate"/><published>2021-08-27T00:00:00-06:00</published><updated>2021-08-27T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2021-08-27:/blog/git-branch-naming/</id><summary type="html">&lt;p&gt;The correct name is "yes."&lt;/p&gt;</summary><content type="html">&lt;p&gt;The CTO of my company recently reviewed a set of dependent PR's I submitted
(&lt;a href="https://github.com/radiasoft/sirepo/pull/3786"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/radiasoft/download/pull/179"&gt;here&lt;/a&gt;, and
&lt;a href="https://github.com/radiasoft/container-beamsim-jupyter/pull/49"&gt;here&lt;/a&gt;). They
had a particularly long and annoying branch name
&lt;code&gt;radiasoft/devops/issuue/232-raydata-initial-app&lt;/code&gt;**. The branch name was
annoying enough that he sent an email to our software team saying: he would
prefer if we use kebab-case in our names and we shouldn't include the
organization name (ex radaisoft) in the branch name. His reasoning for kebab
case is personal preference and the organization name is unnecessary since we
rarely submit PR's outside of our organization. My feeling when someone sends
and email like this is that the answer should almost always be "yes."&lt;/p&gt;
&lt;p&gt;Issues like branch naming are the thorny issues in software where finding the
right answer is nearly impossible. But, the wrong answer is to spend time
thinking about it and for folks to not come to an agreement. Having a regular
naming pattern that everyone agrees on allows tooling to be built around it and
frees minds to think about other things that matter more. Amazon has a value of
&lt;a href="https://www.amazon.jobs/en/principles"&gt;"Have backbone; Disagree and Commit"&lt;/a&gt;
that I think that applies well in this case. Maybe by saying "yes" so quickly I
didn't have a backbone. But, all of us committing to using the same strategy is
what I think is important.&lt;/p&gt;
&lt;p&gt;But, if I was the kind of person that liked to die on hills of no value then I
would say I disagree.&lt;/p&gt;
&lt;p&gt;The long name with the organization is annoying but it is only annoying once,
the first time I type it in. We use GitHub and their UI has nice "copy to
clipboard" buttons in most places where a branch name is displayed. In my shell
I have the function
&lt;a href="https://forge.carlin.com/e-carlin/home-env/src/commit/595622509b7a8d3253cbafdf7f2c788adae5df7d/bashrc.d/bash-aliases.sh#L58"&gt;&lt;code&gt;gb&lt;/code&gt;&lt;/a&gt;
which displays all of my local branches and lets me enter a number to select
one. So, after the initial &lt;code&gt;git checkout -b&lt;/code&gt; I never fully type out the name
again.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;organization/issue/number-description&lt;/code&gt; maps closely to the syntax for
tagging an issue in GitHub (&lt;code&gt;organization/issue#number&lt;/code&gt;). That means less syntax
for me to remember and fewer characters to change when I copy the branch name
into a commit message to tag an issue.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;organization/issue/number-description&lt;/code&gt; also maps closely to URL's.  It
would have been nice if GitHub had chosen &lt;code&gt;issues/number&lt;/code&gt; for tagging because
then branch names would map perfectly to their URL's. But, still I only have to
pluralize issue and remove the description (easy with &lt;code&gt;C-k&lt;/code&gt; in the browser) and
I can quickly navigate to the issue I'm working on.&lt;/p&gt;
&lt;p&gt;Maybe one day when I'm in charge I'll use my strategy but for now it's kebab
case with no organization name for me.&lt;/p&gt;
&lt;p&gt;** In case you didn't catch it I misspelled &lt;code&gt;issuue&lt;/code&gt;. I've started using
&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Spelling.html"&gt;flyspell&lt;/a&gt;
in Emacs but it doesn't play nice with code. If someone has a spellchecker they
use in their editor that actually works please send it to me.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Create More, Consume Less</title><link href="https://evan.carlin.com/blog/create-more-consume-less/" rel="alternate"/><published>2021-08-25T00:00:00-06:00</published><updated>2021-08-25T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2021-08-25:/blog/create-more-consume-less/</id><summary type="html">&lt;p&gt;Getting back to creating.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few days ago I read an article titled &lt;a href="https://blog.tjcx.me/p/consume-less-create-more"&gt;Consume Less, Create
More&lt;/a&gt;. It reminded me of the
article I read in college that was one of the inspirations for &lt;a href="https://evan.carlin.com/pages/about-this-site"&gt;this
site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I haven’t posted an article since May 16, 2020.&lt;/p&gt;
&lt;p&gt;In that time I’ve written one article that covers my thoughts on Bruce
Springsteen’s entire discography. I don’t know if I’ll ever share it but it was
fun to write.&lt;/p&gt;
&lt;p&gt;I’ve written down many ideas for articles I might write but I get scared
wondering if they are good enough which prevents me from writing even a single
word. For example, while writing this I’ve stared at the screen for ten minutes
trying to decide if the title should be “Create More Consume Less” or “Consume
Less Create More.” Spending time worry about details like that is preventing me
from gaining any of the insights I'm hoping to find from writing.&lt;/p&gt;
&lt;p&gt;So, I’m going to start writing more. These articles are going to be bad. I’m
going to proofread very little and I’m not going to have someone else look at
them before I publish them. I’m trading quality for quantity and I have a
feeling that quality will naturally arise overtime. My goal is to publish one
article per week.&lt;/p&gt;
&lt;p&gt;I’m going to write about things I’m not qualified to talk about so please take
everything you read with a grain of salt and reach out when you see
inaccuracies.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Shortened URLs in email are evil</title><link href="https://evan.carlin.com/blog/shortened-urls-in-email-are-evil/" rel="alternate"/><published>2020-05-16T00:00:00-06:00</published><updated>2020-05-16T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2020-05-16:/blog/shortened-urls-in-email-are-evil/</id><summary type="html">&lt;p&gt;Shortened URLs in email are evil and should be avoided.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Shortened URLs in emails are bad for security, encourage tracking, and
are completely unnecessary.&lt;/p&gt;
&lt;p&gt;Huzzah, take that URL shorteners!&lt;/p&gt;
&lt;p&gt;A few days ago, a recruiter reached out to me. All of the links in the
email were shortened URLs. The URLs redirected to the domain
zen.sr. My initial reaction was "Ooh, definitely won't be clicking on
those links!"&lt;/p&gt;
&lt;p&gt;I've never seen an sr domain, I hadn't heard of the company, and I'm
pretty suspicious of clicking on any links in emails. Gmail does a
pretty good job of filtering out the phishing emails that come my way;
and the few that do manage to slip through tend to be blatant spam. I
read over the recruiter’s email again. It didn't seem very spammy. It
was well-written and the content looked pretty standard for an email
from a recruiter. I suppose that all blanket recruiter emails that are
general enough that they could be sent to any engineer for any
position are spam by definition but I'll save that argument for a
later time. Now I was curious: Was this a real recruiting email or was
it some well-disguised spam?&lt;/p&gt;
&lt;p&gt;First, I looked at the sender email. I figured that if they worked for
the company they were hiring for their email would have the same
domain. One point for this being a real email; the email was
joe@acme.com. I opened up a private Firefox window and did a search
for acme.com. There was a hit and everything seemed pretty normal. I
went to the website and clicked around. If this was a phishing scam,
it was pretty elaborate. Looked like a real company to me. At this
point, I decided that the company seemed real. But I was bugged by the
shortened URLs!&lt;/p&gt;
&lt;p&gt;So, I decided to do some investigation into the shortening service
behind zen.sr. The first search result was exactly what I was looking
for: A help center article from gem.com. According to Gem's website,
they are "The Platform For Modern Recruiting." Okay, the recruiting
email almost certainly wasn’t spam. The &lt;a href="https://web.archive.org/web/20200517002230/https://help.gem.com/en/articles/2646453-click-tracking"&gt;help center
article&lt;/a&gt;
is pretty funny. It says that they shorten URLs to zen.sr so they can
track clicks. Yep, I had a feeling. The funny part is the last
sentence of the article. It says, "As a best practice, we recommend
turning it on, with the exception of a few roles that tend to care
more about privacy (e.g. security engineers)." Ha! Security
engineers?? So you should turn off tracking for people who know they
are being tracked but tough luck for everyone else? Ugh.&lt;/p&gt;
&lt;p&gt;This sent me off on a little hunt to learn more about shortened URLs
and their history. &lt;a href="https://blog.rebrandly.com/the-history-of-url-shorteners/"&gt;The first
site&lt;/a&gt; I
landed on had all of the info I wanted. The prose bugged me a bit (I
wasn't in the best mood at this point) but it was a treasure trove of
information on URL shorteners. The tl;dr is that they have been around
since 2001. They were originally invented so a guy could post links on
a unicycle forum (I love the web). Since then shorteners have had a
checkered history. At first, people were upset that they masked the
true destination of links. A few years later, they started to show up
in any plain text context where long strings were an issue
(ex. Twitter) -- this is arguably the only place where URL shorteners
are somewhat useful. In 2008, as so much of the web started to shift
into a tracking and advertising machine, so too did URL shorteners
with &lt;a href="http://bit.ly"&gt;bit.ly's&lt;/a&gt; launch of the first link analytics
tracking service.&lt;/p&gt;
&lt;p&gt;After the history lesson, I looped back to the recruiter's email. Why
did they need to use the shortening service? The email was HTML and
the link was in an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag so the shortening aspect of it was
unnecessary. That leaves one reason for the shortened URL:
Tracking. This type of tracking is invasive. What are the metrics that
are gained by tracking? They already know how many people respond to
their emails. Isn't that enough? You can run a/b experiments and the
like just based on that. Also, are the metrics really that sound?
&lt;a href="https://github.com/radiasoft/sirepo/issues/1937"&gt;Certain email&lt;/a&gt;
clients will click on links to assess their security. Likewise, when I
held down on the link on my iPhone to copy it, it opened it in a
little modal. I wouldn't count either of those as real clicks. Does
Gem not count them too? I'm sure my imagination is letting me down
here but this tracking just seems unnecessary.&lt;/p&gt;
&lt;p&gt;At the very least, obscure links in emails are unsafe.
&lt;a href="https://www.safetynet-inc.com/resources/blog/phishing-attacks-do-not-click-links/"&gt;Every&lt;/a&gt;
&lt;a href="https://www.komando.com/safety-security-reviews/questions-to-avoid-phishing/349664/"&gt;publication&lt;/a&gt;
&lt;a href="https://www.bruceb.com/2017/11/security-warning-do-not-click-on-links-in-email-messages/"&gt;under&lt;/a&gt;
&lt;a href="https://www.forbes.com/sites/bradmoon/2016/01/14/how-to-avoid-becoming-a-victim-of-phishing/?sh=7dbde41943f6"&gt;the&lt;/a&gt;
&lt;a href="https://www.foxnews.com/tech/dont-click-an-email-link-or-web-link-before-asking-these-questions"&gt;sun&lt;/a&gt;
has written about not trusting links in emails. Informed users have
been trained to not click on links in email. They may have even tried
to fight invasive email services like
&lt;a href="https://www.howtogeek.com/427454/how-to-stop-superhuman-and-other-apps-from-tracking-your-email-opens/"&gt;Superhuman&lt;/a&gt;.
But with invasive email practices (as with so much else in tech) there
is an asymmetry between the people building the tech and those who use
it. Steve Jobs &lt;a href="https://www.popsci.com/industry-insiders-dont-use-their-products-like-we-do/"&gt;discouraged his
kids&lt;/a&gt;
from using iPads. The same ones he was trying to sell to schools. And
most people don’t have the time to dodge tracking at every turn. If we
normalize obscure links in emails then we’ll never get the cat back in
the bag (though perhaps the cat is already out of the bag with the
prevalence of shortened URLs on Twitter).&lt;/p&gt;
&lt;p&gt;Link shorteners in email are evil. At the very least, services like
Gem should disable them by default. Their docs encourage users to
disable them for “security engineers.” If they are willing to disable
them for the population in the know then they should extend that
decency to everyone. This email was from a recruiter who wrote me out
of the blue. I’m not looking for a job and I don’t advertise that I am
anywhere. If someone is going to write to me then they shouldn’t track
me while doing it. This is like a door to door salesman snapping your
photo as soon as you open the door. There is an information asymmetry
that means the sender gets to know more about me before I even know
what their business is.&lt;/p&gt;
&lt;p&gt;P.S. If you're thinking to yourself that it is annoying to read a post
that bemoans tracking written by someone who has worked for the chief
tracker (rhymes with “Schmoogle”) and uses Gmail for their email; all
I can say is that isn't lost on me and you are certainly
right. Happily, &lt;a href="https://hey.com/problems-with-email/"&gt;there are
people&lt;/a&gt; hard at work trying to
reduce harmful email practices and I encourage you to check them out.&lt;/p&gt;
&lt;p&gt;P.P.S. This website uses analytics. You can read more about them and
get a link to the public dashboard by visiting my &lt;a href="pages/about-this-site"&gt;about this
site&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>The Interactive Subshell: Emacs’ Killer Feature</title><link href="https://evan.carlin.com/blog/emacs-interactive-subshell/" rel="alternate"/><published>2020-04-07T00:00:00-06:00</published><updated>2020-04-07T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2020-04-07:/blog/emacs-interactive-subshell/</id><summary type="html">&lt;p&gt;The interactive subshell let's you interact with your shell input and output as it should be, like any other text.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using Emacs exclusively for a couple months now.* Like any
good tool, there are things I like and things I don't like. I'll save
diving into all of those upsides and downsides for another post. In
this post, I’ll talk about Emacs' killer feature: the interactive
subshell. Perhaps the "real" killer feature is something more meta
like Emacs' programmability but that wouldn't have made for such a
juicy title would it?&lt;/p&gt;
&lt;p&gt;*If you’ve never heard of &lt;a href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt;
and you are curious, prepare to spend the next hours, to weeks, to
your lifetime learning and using one of the most extensible text
editors. Once you’ve played around with it come back and read this.&lt;/p&gt;
&lt;h2 id="disclaimers"&gt;&lt;a class="toclink" href="#disclaimers"&gt;Disclaimers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before I begin, you should know that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I have a bias towards interacting with my computer through the
   keyboard as much as possible. The full power of the interactive
   subshell can only be harnessed with the keyboard. So, this article
   will probably be most valuable to people who also prefer to use
   their keyboard, but even if you prefer to use your mouse you may
   still find something worthwhile here. And who knows, maybe you'll
   be so inspired by the interactive subshell that it will be your
   hook into using your keyboard more.&lt;/li&gt;
&lt;li&gt;I don't have very much experience programming on an OS besides
   macOS or Linux. I used the Windows Subsystem for Linux briefly but
   that's about as far out as I've ventured. I think this article has
   a bit of a Linux slant but I believe the message holds true
   regardless of what OS you use as long as you use a terminal.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that I've checked off the mouse vs keyboard and OS boxes on my
flamewar bingo card, let's get into the good stuff.&lt;/p&gt;
&lt;h2 id="what-is-an-interactive-subshell-and-why-should-you-care"&gt;&lt;a class="toclink" href="#what-is-an-interactive-subshell-and-why-should-you-care"&gt;What is an interactive subshell and why should you care?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The "interactive" in "interactive subshell" means that all of the text
in the terminal can be interacted with just like the text in any other
file. Everything you can do with text (delete, move, select, etc) you
can do with the input and output of the shell.&lt;/p&gt;
&lt;p&gt;Interactive means you just &lt;code&gt;grep&lt;/code&gt;'ed for something and because your
regex fu is so bad you don't know how to craft one that doesn't
include a bunch of useless results — so you just delete those lines
from the output.&lt;/p&gt;
&lt;p&gt;Interactive means you just &lt;code&gt;cat&lt;/code&gt;'ed a log file and you're sifting
through the output and see two lines that you want to compare side by
side. You just delete all of the lines in between.&lt;/p&gt;
&lt;p&gt;Interactive means your test suite is running and you want to search
for a keyword in the output but you forgot to clear the output of the
last run so you just select the old output and delete it. No need to
worry about possibly finding the keyword in the old run.&lt;/p&gt;
&lt;p&gt;Interactive means you have an
&lt;code&gt;ENVIRONMENT_VARIABLE_THAT_IS_UNBELIEVABLY_LONG&lt;/code&gt; and you want to
&lt;code&gt;unset&lt;/code&gt; it. If you wanted to &lt;code&gt;echo $&lt;/code&gt; the variable then your shell
would let you tab complete it but it can't tab complete with the word
unset in front of it. In an interactive subshell, you can use the
autocomplete of Emacs to tab-complete the variable name.&lt;/p&gt;
&lt;p&gt;Interactive means you are trying to find a file and you can't remember
the name so you run a few &lt;code&gt;find . -name "filename"&lt;/code&gt;. Each time you run
the command you want to &lt;code&gt;di"&lt;/code&gt; (delete all text in-between "", would
delete filename). But oh wait &lt;code&gt;set -o vi&lt;/code&gt; in Bash doesn't support that
feature even though your editor does.&lt;/p&gt;
&lt;p&gt;I could go on but I'm ranting and I didn't anticipate ranting in just
my second blog post. Here’s what matters: Interactive means you are in
control. You can do whatever you want with the text in the shell as if
you had written each line by hand yourself.&lt;/p&gt;
&lt;p&gt;Why should you care? Well, I'll describe why I care and maybe you'll
agree. Like many programmers, I interact with my shell (Bash)
regularly. There are
&lt;a href="http://catb.org/esr/writings/unix-koans/gui-programmer.html"&gt;countless&lt;/a&gt;
&lt;a href="https://en.wikipedia.org/wiki/In_the_Beginning..._Was_the_Command_Line"&gt;tales&lt;/a&gt;
on the web of why shells matter and how they are a powerful way of
interacting with the os. I won't get into the shell/GUI debate (add it
to your flamewar bingo;)) but I use my shell and I like my experience
interacting with it to be comfortable. I want to interact with the
shell input and output however I please. I’ve described a few
scenarios where I want to manipulate the shell input and output and I
don't think my terminal should get in the way of me doing that.&lt;/p&gt;
&lt;p&gt;In addition to manipulating the text as I please, using all of the
features I use elsewhere in my editor within my terminal reduces
cognitive load. If I want to use vi keybindings in my editor why
should I get some watered-down version of them in my terminal? If I
want to autocomplete, I should be offered suggestions of strings found
elsewhere in my program. In both places I'm just editing text so I'd
like my workflow to be the same. My terminal should trust me to edit
the content however I want.&lt;/p&gt;
&lt;h2 id="ides-and-terminals"&gt;&lt;a class="toclink" href="#ides-and-terminals"&gt;IDE's and Terminals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;VSCode, the JetBrains suite of IDE's, Eclipse and many other editors
of the day have powerful text editing features. You can efficiently
manipulate text; moving it here and there and customizing your editing
experience late into the night. All of these editors support plugins
to allow for vi or Emacs keybindings which further increases your
ability to manipulate text without leaving your keyboard. What all of
these editors lack (at least in my experience, please prove me wrong)
is a terminal where you can work on the text as you do in the rest of
the editor. They all have a terminal but the terminals offer no
special text editing features. For example, in exactly zero of these
terminals can you delete a line of output.&lt;/p&gt;
&lt;p&gt;NeoVim and Vim 8 have a half-baked terminal mode where you can
navigate text (hjkl, search, highlight, etc) but you can't manipulate
(delete, autocomplete, etc) text using the same commands you use in
the editor. Vim also offers a mode where you can take the command, go
into a temporary buffer, edit the command with normal text editing
capabilities, and then drop back in the terminal pasting the command
in. This is clunky; you lose the context of everything else going on
in your terminal, and it only works for the shell input, not the
output.&lt;/p&gt;
&lt;p&gt;In addition to the editors, the terminal emulator apps (iTerm, GNOME
terminal, etc) suffer from the same lack of editability. All of them
offer some amount of text manipulation for editing just the command
but not anything else. Usually, they default to having Emacs
keybindings and if you're so inclined you can &lt;code&gt;set -o vi&lt;/code&gt; and hjkl to
your heart's content. While the ability to edit the command is nice
(mandatory!) the inability to edit anything else is suffocating. I'm a
vi keybinding user (talking about how good Emacs is, gasp) so I can't
speak to the Emacs keybindings but if they are like the vi ones then
they are half-implemented at best.&lt;/p&gt;
&lt;p&gt;For example, in iTerm with vi keybindings enabled &lt;code&gt;gUw&lt;/code&gt; (make
uppercase from cursor to the end of the word) doesn't work. The
specific command isn’t so important, but what is important is that
it’s muscle memory. It slows me down when I have to stop and say "ohh
I'm in the terminal now and the terminal says I can't do that" (said
in a whiny kid voice). Other things like copy and paste keybindings
also don't work; I can't move text elsewhere in the terminal (or in
another split pane). This can be done with NeoVim and it can be done
using Tmux but it doesn’t feel natural, in my experience. In NeoVim
I'm always forgetting whether I'm in insert mode, normal mode from
&lt;code&gt;set -o vi&lt;/code&gt;, or normal mode in NeoVim. Tmux uses different keys
altogether; so more muscle memory where my wires get crossed. Ok,
that's enough bloviating on why the terminal you use is awful and why
my setup is just so much better:). Check off Emacs vs vi on your bingo
cards (sheets? I've never actually played bingo...) and let's move on
to why Emacs solves all of the issues elaborated so far.&lt;/p&gt;
&lt;h2 id="emacs-shells"&gt;&lt;a class="toclink" href="#emacs-shells"&gt;Emacs Shells&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you’ve made it this far hopefully I’ve persuaded you a bit that
your terminal isn't interactive but it should be. Thankfully Emacs has
us covered. As with seemingly all things Emacs, there are multiple
shells and modes of interaction. Any shell could conceivably be made
interactive. The shell itself isn't what is important but the way its
input and output are presented is. I describe a mixture of shells and
terminals below. Try to ignore that I sort of conflate the two. The
point is these are the default shell/terminal combos that come with
Emacs and let you do the shell/terminal things you might expect.&lt;/p&gt;
&lt;p&gt;There is
&lt;a href="https://www.gnu.org/software/emacs/manual/html_mono/eshell.html"&gt;Eshell&lt;/a&gt;,
which is a shell implemented entirely in Elisp. The Eshell is
presented in an interactive manner which is excellent but the rest of
the shell is not my cup of tea. I don't know Elisp so I think I'm not
the target audience. The skills learned using it are not very portable
to other shells.&lt;/p&gt;
&lt;p&gt;There is the &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Terminal-emulator.html"&gt;Emacs terminal
emulator&lt;/a&gt;
which, as the name implies, is a terminal emulator running in
Emacs. You can run whatever shell program you want and you can edit
the text as you wish. This is all great but the term mode has one
downfall. It &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Term-Mode.html"&gt;is
modal&lt;/a&gt;
(A vi user bashing on something being modal? I can't get my story
straight). There is line mode and there is char mode. Line mode is the
fully interactive mode where you can manipulate the text
displayed. Char mode is like any other non-interactive terminal
emulator. I have no need for char mode so I find switching between the
two annoying and pointless.&lt;/p&gt;
&lt;p&gt;Finally, there is the Emacs &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Shell-Mode.html#Shell-Mode"&gt;shell
mode&lt;/a&gt;. Shell
mode is like the Winston Churchill quote about democracy, it is the
worst shell experience except for all of the other ones. It is slow,
C-c frequently doesn't work (I think that is an artifact of using
&lt;a href="https://github.com/emacs-evil/evil"&gt;Evil Mode&lt;/a&gt;), and just generally
feels kind of clunky (I know, very scientific). Also, it doesn't
support ANSI escape sequences. If you're like the guy I work for,
you'll go all the way to building &lt;a href="http://www.respectmyterm.com/"&gt;a
website&lt;/a&gt; to remind people that in shell
mode you
&lt;a href="https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals"&gt;$TERM=dumb&lt;/a&gt;
and they should play nice. Programs like top won’t work at all. These
defects are just minor inconveniences and the upside, interactivity,
far outweighs them. If you want an interactive shell then this mode is
what you want.&lt;/p&gt;
&lt;h2 id="interactivity-or-bust"&gt;&lt;a class="toclink" href="#interactivity-or-bust"&gt;Interactivity or bust&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An interactive subshell might be something you have to play with to
grasp. If you're unconvinced by my yelling then fire up Emacs and type
&lt;code&gt;M-x shell&lt;/code&gt;. That's Emacs speak for "hold down the alt/option key
(&lt;u&gt;M&lt;/u&gt;eta) and x." Then release, type in shell, and hit enter. That
will drop you into the Emacs shell. Try using it for a day and see
what you think. At first, it will feel clunky. This feeling doesn't
really wear off but at some point you'll be so addicted to editing the
text in your shell that you won't even care.&lt;/p&gt;
&lt;p&gt;NY style pizza is better than Chicago deep-dish. That's five across
and I win flamewar bingo. Have a great day.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>It's just code</title><link href="https://evan.carlin.com/blog/its-just-code/" rel="alternate"/><published>2020-03-27T00:00:00-06:00</published><updated>2020-03-27T00:00:00-06:00</updated><author><name>Evan Carlin</name></author><id>tag:evan.carlin.com,2020-03-27:/blog/its-just-code/</id><summary type="html">&lt;p&gt;An introduction to blogging and reading code.&lt;/p&gt;</summary><content type="html">&lt;h2 id="finding-my-way-to-the-first-post"&gt;&lt;a class="toclink" href="#finding-my-way-to-the-first-post"&gt;Finding my way to the first post&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ahh, my first post. A daunting task for someone who has spent their
entire life avoiding writing at any cost. I suppose this is a make or
break moment where I decide if I can learn to enjoy writing. Lately,
when my mind has the time to drift I have been mulling over what I
should choose as the topic.&lt;/p&gt;
&lt;p&gt;My first thought was something simple, personal, and harmless. I
figured writing about my personal code setup (editor, shell, etc)
seemed like a fine choice. Not very exciting but would get me in the
groove. These types of posts are like candy; I can't say no to them
but they're not what I need. But recently I reached what feels like a
new milestone on my programming journey and I figured describing what
happened would be much more interesting. So here it is.&lt;/p&gt;
&lt;h2 id="becoming-an-apprentice"&gt;&lt;a class="toclink" href="#becoming-an-apprentice"&gt;Becoming an apprentice&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In September 2019 I started a new job. I felt like I wasn't improving
as a programmer in my previous job and I dreaded going to work every
day. I love programming and working so that was a bad
sign. Thankfully, I had a friend working at an interesting company and
he was open to working with me. I wanted the job because
the &lt;a href="https://radiasoft.net/"&gt;company&lt;/a&gt; is small. They're solving hard
problems - physics simulations. Each of the three other software
engineers has been programming for longer than I've been alive. And
most important, they seemed willing to take time to pass on their
collective knowledge to me. I had a hunch that there would be plenty
to learn. Turns out that hunch was dead-on.&lt;/p&gt;
&lt;p&gt;I work closely with the CTO, &lt;a href="https://www.robnagler.com/"&gt;Rob
Nagler&lt;/a&gt;. He has imparted quite a lot of
wisdom so far. I'm uncertain I’m qualified to make this judgment
(you'll see in this post how little I know about programming) but I
have a strong suspicion Rob is a world-class programmer. I have had
the pleasure of sitting 5 feet from him every day for the past six
months. We talk about programming and I watch him solve problems day
after day. Watching a master at work has helped me improve my skills
at a faster pace than ever before. Reading works by smart people or
being on a team with them is one thing. Being able to sit with a
craftsman, watch them do their work, and get snippets of their
thinking is so much more powerful.&lt;/p&gt;
&lt;h2 id="its-just-code"&gt;&lt;a class="toclink" href="#its-just-code"&gt;It’s just code&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One difference between Rob and myself is our fluency in reading
code. We &lt;a href="https://github.com/radiasoft/sirepo"&gt;work on&lt;/a&gt; open source
code and use many open source tools so there is a great deal of code
to read. Before working at Radiasoft I can say (with some degree of
shame from my present vantage point) that I had never &lt;em&gt;really&lt;/em&gt; read
code. If you're a professional programmer a statement like that
probably makes your skin crawl. Looking in the rearview, it is
somewhat amazing to me that I ever solved a problem with how little
code I read.&lt;/p&gt;
&lt;p&gt;I've worked on projects with other people and read the code they
check-in. I've started new projects with existing codebases and read
the absolute bare minimum of the code to do what I need to do. I've
read snippets of code on Stack Overflow (duh) and blogs. I've even
poked around open source projects I was thinking of using. But I've
never really &lt;em&gt;truly&lt;/em&gt; read code. I've never skipped the documentation
and gone straight to the source code. I've never read another
project's source code and used it to inform my approach to a similar
problem. I've never encountered a bug in an open source project and
fixed it myself (except
for &lt;a href="https://gitlab.com/gitlab-org/gitlab-development-kit/-/merge_requests/706/diffs"&gt;this&lt;/a&gt;
small change). Rob does this kind of reading every day.&lt;/p&gt;
&lt;p&gt;When I run into a problem I spend time fumbling through documentation,
bug reports, mailing lists, and Stack Overflow trying to find
answers. Most of the time this solution works well enough, but it can
be slow, lead to awkward solutions, and sometimes just doesn't
work. What I now realize is that programmers better than myself have
another tool to use. Reading the source code.&lt;/p&gt;
&lt;p&gt;In the past 6 months, there have been a few times where I have been
truly stumped, tapped Rob on the shoulder to ask for help, and seen
him go straight to the source code to find the answer. He's done this
with the &lt;a href="https://github.com/tornadoweb/tornado"&gt;Tornado&lt;/a&gt; web server,
the &lt;a href="https://github.com/ronf/asyncssh"&gt;asyncssh&lt;/a&gt; Python library, the
&lt;a href="https://github.com/python/cpython"&gt;cPython source code&lt;/a&gt;, and &lt;a href="https://github.com/radiasoft/sirepo/"&gt;our own
codebase&lt;/a&gt;. All of these are
large codebases with plenty of places to get lost. The first few times
I saw Rob do this I was blown away. In seconds, he figured out where
to look and in minutes he'd found the relevant bits of code and the
answer. He knew that if he read the code, he’d find the answer. When I
ask him how he does it he says, “it’s just code.”&lt;/p&gt;
&lt;p&gt;I struggle to have this level of confidence. When I read code I tend
to give up quickly, telling myself that the other person is clearly
some sort of genius and my mind is too feeble to understand their
masterful work. I usually start at a call site or object that I think
should be relevant. I then go down long rabbit holes that are
orthogonal to my problem and overly specific. For example, I’ll find
myself reading through the ASGI spec and how the server implements it
even though I just want to know why the incoming request connection
was closed abruptly. I try to really understand every last bit of code
because it is the only way I can make sense of anything (not
everything, &lt;strong&gt;anything&lt;/strong&gt;). This is usually the moment I get frustrated
that I can’t find what I need and start to think it simply can’t be
found. Rob, on the other hand, churns through other codebases. He
moves along knowing that it is &lt;em&gt;just code&lt;/em&gt; and with some time and
determination he can understand it.&lt;/p&gt;
&lt;p&gt;Now that phrase — &lt;em&gt;it's just code&lt;/em&gt; — is a mantra for me when I read
new code. Every time I read code it is slightly easier than the last
time: It’s a muscle that you can build like any other.&lt;/p&gt;
&lt;h2 id="faith-in-the-abstractions"&gt;&lt;a class="toclink" href="#faith-in-the-abstractions"&gt;Faith in the abstractions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to reading the code (and remembering that &lt;em&gt;it’s just
code&lt;/em&gt;), Rob has taught me another trick: "Have faith in the
abstractions."&lt;/p&gt;
&lt;p&gt;Recently I &lt;a href="https://github.com/radiasoft/sirepo/pull/2315"&gt;added an administrative
page&lt;/a&gt; to our
website. It allows a user to see their running and pending jobs. I had
just finished re-writing our entire job execution system (with a great
deal of help from Rob) so I knew the back-end of the code well. I was
less in tune with the front-end.&lt;/p&gt;
&lt;p&gt;I needed to provide users with a link to each currently running job in
the list. I wasn't sure how to do this so I set out in standard
fashion: I hunted around the website for somewhere that provides a
link to a job and tried to find the relevant source code, which I
could then reuse for my purposes.&lt;/p&gt;
&lt;p&gt;There was only one other place with a link and it was on the page of
the simulation itself so we just used &lt;code&gt;$window.location.href&lt;/code&gt;. Not going
to work for what I need because I need to link to jobs that are not on
the current page. So armed with "it's just code," into the code I
went.&lt;/p&gt;
&lt;p&gt;I knew that we have a concept of local and global routes so I figured
if I looked near those words I'd find something. Sure enough, we have
a function called
&lt;a href="https://github.com/radiasoft/sirepo/blob/403672672054e765747551794d5359f7b1a9f500/sirepo/package_data/static/js/sirepo.js#L1610"&gt;formatUrlLocal&lt;/a&gt;. The
code calls some other code that isn't dead simple. I'd need to spend
some time with it to really understand what it was doing and how I
might use it. I had the sense that the code was going to need to
change (which was wrong) so I felt like I really had to grok it.&lt;/p&gt;
&lt;p&gt;What I &lt;em&gt;should&lt;/em&gt; have done at this point is looked for other uses of the
code and just used it the same way myself. Instead, I was frustrated
that I couldn't find what seemed like a piece of code that had to
exist. I asked Rob for guidance. Specifically, I asked, "Are there any
places on the site where we have links to simulations. I'm not sure
how to construct the link but it seems like something we would already
have code for."&lt;/p&gt;
&lt;p&gt;Thankfully he's patient and happy to help. As one might expect, he
navigated directly to the formatUrlLocal code I was looking at and
said, "Here, use this." It took him the amount of time it took his
fingers to search to find code it had taken me 15 minutes to get to
(and that I didn’t quite know how to use even once I found it).&lt;/p&gt;
&lt;p&gt;I asked Rob how he got to the code so quickly and how he knew it was
going to help. His answer, "I have faith in abstractions." If you're
adept at reading code you may be thinking, “Duh, you can't understand
everything so look for the abstractions that may be relevant, try
using them, and when necessary dive in and understand them.” But if
you're like I was you may be thinking, “Wow I have no clue how to use
that advice.”&lt;/p&gt;
&lt;p&gt;This is the kind of advice that makes no sense until you use it and
then becomes totally obvious. Maybe it’s akin to telling someone to
counter-steer when their car is sliding. It just isn't obvious how it
works until you can do it. Sure enough, the code worked. It said it
formatted URLs and it did just that. There was an abstraction and it
worked. I didn't need to understand it top to bottom but I needed to
have faith. I needed to try using it.&lt;/p&gt;
&lt;p&gt;Now when I encounter problems I have new tools. First, I tell myself:
&lt;em&gt;it's just code&lt;/em&gt; and dive right into the source code. As I look at the
code, I remind myself to &lt;em&gt;have faith in the abstractions&lt;/em&gt;. The latter
strategy is much harder. There are words and styles that are
suggestive of abstractions that will lead you to the answer but it
takes time and experience to discern the worthwhile ones from the
irrelevant ones. And over time, you hopefully learn when the
abstractions aren’t quite right. That’s when it’s time to go deeper. I
can't say I really have any sense for this yet. But what I do have is
the awareness that the answer is out there, in code, waiting for me to
read it.&lt;/p&gt;
&lt;p&gt;These are just the first steps of my journey towards &lt;em&gt;really&lt;/em&gt; reading
code. I hope that, as my journey progresses, I will be able to write
many more posts with actionable insight on how to become more
fluent. For now, the only advice I have is to reiterate Rob's
insights: &lt;em&gt;It’s just code&lt;/em&gt; and &lt;em&gt;have faith in the abstractions&lt;/em&gt;. On top
of this, I have a small piece of advice of my own: If you're not
reading code, start today. It gets easier every time.&lt;/p&gt;</content><category term="blog"/></entry></feed>