<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>ryan.himmelwright.net</title>
  <subtitle>Recent content in Posts on λ ryan. himmelwright. net</subtitle>
  <link href="https://ryan.himmelwright.net/post/index.xml" rel="self" />
  <link href="https://ryan.himmelwright.net/" />
  <updated>2026-05-08T01:55:01Z</updated>
  <id>https://ryan.himmelwright.net/</id>
  <author>
    <name>Ryan Himmelwright</name>
  </author>
  <entry>
    <title>2026 Yearly Theme</title>
    <link href="https://ryan.himmelwright.net/post/2026-yearly-theme/" />
    <updated>2026-05-08T01:55:01Z</updated>
    <id>https://ryan.himmelwright.net/post/2026-yearly-theme/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2026-yearly-theme/jaLFHdMOID-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2026-yearly-theme/jaLFHdMOID-1200.jpeg&quot; alt=&quot;Two white flowers on a blooming tree.&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Better late than never, right?&lt;/p&gt;
&lt;h2&gt;Deciding the Theme&lt;/h2&gt;
&lt;p&gt;This year I was a bit different in deciding my &lt;a href=&quot;https://www.themesystem.com&quot;&gt;yearly theme&lt;/a&gt;, mostly in how and when I named it. When the year started, I had no major drive to figure out my theme &lt;em&gt;immediately&lt;/em&gt;. I opted to mull over it instead. During those first few months, I quickly recognized a pattern in my thinking for my theme, the phrase “everything is up for reconsideration”. However, I couldn’t determine a nice Cortex-style name for it.&lt;/p&gt;
&lt;p&gt;Fast-forward to April, and I decided that I really needed to clean up and formalize this vague idea now that it was the second quarter of the year. After some brainstorming and dabbling, I stumbled on &lt;strong&gt;The Year of Sovereign Inquiry&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Year of Sovereign Inquiry&lt;/h2&gt;
&lt;p&gt;Weird name, right? But it works. To expand a bit, the breakdown is something like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sovereign → self-governing, independent, in ultimate control of your life and choices&lt;/p&gt;
&lt;p&gt;Inquiry → active questioning, investigation, testing assumptions&lt;/p&gt;
&lt;p&gt;Why it works: It frames the year as a process of questioning everything under my own authority. This includes habits, philosophies, systems, and tech.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The name goes back to that &lt;em&gt;feeling&lt;/em&gt; that everything in &lt;em&gt;my&lt;/em&gt; life, no matter how long I’ve held onto it, is really up for evaluation this year. I’m slowly going through various areas of my life and looking at everything and wondering “does it &lt;em&gt;have&lt;/em&gt; to be this way?”. I then test out the alternatives.&lt;/p&gt;
&lt;p&gt;I had been working with that mindset throughout 2026 already, and I finally had a name for it.&lt;/p&gt;
&lt;h2&gt;So Far&lt;/h2&gt;
&lt;p&gt;As a yearly theme, it’s been working quite well. By default, I am already someone that questions everything. Well, &lt;em&gt;almost&lt;/em&gt; everything. This theme has encouraged me to push that extra bit whenever I come up against something I would &lt;em&gt;normally&lt;/em&gt; consider untouchable. Instead of blindly moving on, I think &lt;em&gt;“Go ahead. Anything is up for reconsideration, it’s the ‘Year of Sovereign Inquiry’…”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Since the start of the year, there have been a number of ‘inquiries’ I’ve dug into. Many of these might appear as habit changes I would likely investigate regardless, but now, I am doing so with much less hesitation. I am also willing to test out alternatives no matter how long-standing or important the current system is. Here are a few examples:&lt;/p&gt;
&lt;h3&gt;Reliance on Apple&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I’ve been using NixOS more heavily on the GPD Pocket as my secondary daily driver.&lt;/li&gt;
&lt;li&gt;This has pushed me to slowly move away from services and apps that are locked within Apple.&lt;/li&gt;
&lt;li&gt;I got an Android device (original Google Pixel) to test out GrapheneOS.&lt;/li&gt;
&lt;li&gt;I’m researching how to use a stack like Flutter+ClojureDart for app development instead of straight Swift+SwiftUI. It would be cross-platform and allow for the &lt;em&gt;majority&lt;/em&gt; of the development (still need to build/debug iOS/macOS apps on Mac) to also be done on Linux if I choose.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Shifting Local-first and Home-labbing again&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I’ve been moving some services and systems to self-hosted options when it makes sense. Sometimes it’s to enable the service outside of Apple, sometimes it’s to get my data out of the cloud. Other times, the self-hosted option is simply better. Here are a few I set up:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Immich&lt;/code&gt; - Photo backup outside of Apple Photos formatting. Available to GrapheneOS and Linux devices now.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Pinchflat&lt;/code&gt; - continuing to test out systems like this to view YouTube content like my text RSS feeds.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Nextcloud&lt;/code&gt; - my sync service, notes on mobile, and honestly a playground for other stuff (calDAV, etc).&lt;/li&gt;
&lt;li&gt;Switched to &lt;code&gt;Unifi&lt;/code&gt; gear - Better network control. I can disable WAN on my client devices (for better focus), and still use my services on the homelab (which is still connected to WAN to do its thing).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Notes/Journal&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Switched from org mode to simply using markdown files, except when &lt;code&gt;org&lt;/code&gt; makes more sense (when tables, &lt;code&gt;TODO&lt;/code&gt; lists, and other features are important).&lt;/li&gt;
&lt;li&gt;Configuring various clients to properly use links in my notes was a pain. So I stopped using links. (&lt;em&gt;Do I need links? Do I actually use them often, if at all?&lt;/em&gt; --&amp;gt; &lt;strong&gt;No&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Time Tracking&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I switched to using time-warrior instead of dedicated apps. I have it configured on my server and make entries via &lt;code&gt;ssh&lt;/code&gt; using a &lt;a href=&quot;https://ryan.himmelwright.net/post/initial-timewarrior-ios-shortcut/&quot;&gt;shell alias or Apple Shortcut&lt;/a&gt;. It works surprisingly well. It started as a short experiment, but I have been using it now for &lt;em&gt;months&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Task Management&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I tested out &lt;a href=&quot;https://super-productivity.com&quot;&gt;Super Productivity&lt;/a&gt; for dev and hobby tasks. I wanted something more open since I was writing dev notes in my project tasks.&lt;/li&gt;
&lt;li&gt;I tested &lt;a href=&quot;https://vikunja.io&quot;&gt;Vikunja&lt;/a&gt; for self-hosting options.&lt;/li&gt;
&lt;li&gt;I am trying to use a simpler &lt;em&gt;tag-based&lt;/em&gt; workflow (ex: using &lt;code&gt;day&lt;/code&gt;, &lt;code&gt;week&lt;/code&gt;, &lt;code&gt;season&lt;/code&gt; and other tags instead of dates for management).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Limiting Mental Inputs&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Video&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I’ve dramatically pulled back on video content I consume. I don’t watch videos at lunch, and block YouTube for most of the day (and only &lt;em&gt;sometimes&lt;/em&gt; work around it XD )&lt;/li&gt;
&lt;li&gt;I’m trying to use systems like &lt;code&gt;Pinchflat&lt;/code&gt; for better intentional viewing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Podcasts&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I’m testing out blocking my podcast player in the morning (!). Podcasts are great and I love listening to them during the day, but given the topics I listen to, I often want to ‘look something up’, or spec out a configuration for tech I won’t buy (which is a big time-suck of mine). So, I’m at least delaying that during the day. It’s going well so far.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Internet&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Like podcasts, I’ve &lt;em&gt;started&lt;/em&gt; to dabble with periods of working offline.&lt;/li&gt;
&lt;li&gt;With the Unifi and home server changes, I can have my client devices blocked from the WAN, but still able to connect to the server. So, I can still sync my notes, or even push my code (I added git repo mirrors on my server, so I can push to that, and &lt;em&gt;the server&lt;/em&gt; will push the changes to &lt;a href=&quot;https://sr.ht&quot;&gt;source hut&lt;/a&gt; for me).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;After a reading lull at the start of the year, I’m reading more. This is partially due to replacing a bunch of my browsing and video habits with books.&lt;/li&gt;
&lt;li&gt;I also listen to audiobooks, often instead of podcasts when they’re blocked.&lt;/li&gt;
&lt;li&gt;In light of the yearly theme, I’m less worried about whether something will “count” for my reading. Those old thoughts of “Does an audiobook count? What about a zine?” are out the window, and I’m learning more because of it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Exercise&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Back to simple, quick workouts, and overall activity. Going well.&lt;/li&gt;
&lt;li&gt;Also trying out sandbag exercises.&lt;/li&gt;
&lt;li&gt;Not worried about hitting all my Apple Watch goals, or ensuring I use the various workout services we have (Apple Fitness+, Tempo). Do what works, not what I have sunk-cost guilt over.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Target Second Language&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I always go back to German whenever I am interested in learning a second language. Last time I did this, I realized it made more sense for me to learn Spanish, or even French, instead (although I stuck with German).&lt;/li&gt;
&lt;li&gt;Prompted by our Montreal trip and encouraged by the yearly theme to reconsider - I think I’ll finally switch my target language to &lt;s&gt;French&lt;/s&gt; Spanish (changed my mind while editing this post XD ).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Going Forward&lt;/h2&gt;
&lt;p&gt;Lastly… I might move my theme’s start to Q2 each year (April 1st)? It’s when I finally defined the name for my theme this year, and it also lines up with when I left Red Hat last year, so it actually works well. I don’t need to conflate it with the changing of the year, which is sort of the point of the themes in general. We’ll see.&lt;/p&gt;
&lt;p&gt;In summary though, I’ve enjoyed this theme thus far and hope to continue experimenting as the year moves on.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tech Got Expensive - Revisiting My Order History</title>
    <link href="https://ryan.himmelwright.net/post/tech-price-investigation-202603/" />
    <updated>2026-04-05T13:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/tech-price-investigation-202603/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/tech-price-investigation-202603/TGllROCRVC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/tech-price-investigation-202603/TGllROCRVC-1200.jpeg&quot; alt=&quot;a mini pc with an external SSD on top of it. They practically look the same.&quot; width=&quot;1200&quot; height=&quot;721&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My MeLE fanless mini PC, with an external SSD on top of it.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve known the last few months that &lt;a href=&quot;https://www.cnbc.com/2026/01/10/micron-ai-memory-shortage-hbm-nvidia-samsung.html&quot;&gt;largely due to AI companies&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/2024%E2%80%93present_global_memory_supply_shortage&quot;&gt;RAM prices are skyrocketing&lt;/a&gt;. I also knew that this would trickle down: SSD prices would also be affected, which would eventually cause HDDs to also rise. Still, I was shocked (but not surprised :-(  ) realizing just how bad it’s gotten even for devices like little mini PCs.&lt;/p&gt;
&lt;h2&gt;The Data&lt;/h2&gt;
&lt;p&gt;On a whim, I decided to comb through the last few years of my Amazon order history, looking at tech purchases and comparing the purchase price with the current listed price. Given that some of these purchases go back to 2021, I even adjusted the prices to today’s largely inflated dollars to make it appear like I know what I am doing &lt;em&gt;(Inflation calculations done using the &lt;a href=&quot;https://www.usinflationcalculator.com&quot;&gt;US Inflation Calculator&lt;/a&gt;)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That said, there is one big issue with this data to be aware of. My purchase prices are directly from my order history, which included tax and (sometimes) shipping. The &lt;em&gt;current&lt;/em&gt; prices, however, are just what I found when clicking on the current listing, which &lt;strong&gt;does not&lt;/strong&gt; include shipping or tax. So in practice, this table actually &lt;em&gt;understates&lt;/em&gt; the change.&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Purchase Date&lt;/th&gt;
&lt;th&gt;Purchase Price&lt;/th&gt;
&lt;th&gt;Inflation adj.&lt;/th&gt;
&lt;th&gt;Current Price&lt;/th&gt;
&lt;th&gt;Time (days)&lt;/th&gt;
&lt;th&gt;Price Delta (adj)&lt;/th&gt;
&lt;th&gt;Percent (adj)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Samsung 860 EVO 2TB SSD&lt;/td&gt;
&lt;td&gt;2021-01-11&lt;/td&gt;
&lt;td&gt;$217.17&lt;/td&gt;
&lt;td&gt;$261.90&lt;/td&gt;
&lt;td&gt;$291.30 (used)&lt;/td&gt;
&lt;td&gt;1904&lt;/td&gt;
&lt;td&gt;$74.13 ($29.40)&lt;/td&gt;
&lt;td&gt;+34% (11%)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.com/dp/B0786QNSBD?ref=ppx_yo2ov_dt_b_fed_asin_title&amp;amp;th=1&quot;&gt;link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seagate IronWolf 4TB 3.5&amp;quot; NAS HDD&lt;/td&gt;
&lt;td&gt;2021-07-07&lt;/td&gt;
&lt;td&gt;$112.86&lt;/td&gt;
&lt;td&gt;$136.11&lt;/td&gt;
&lt;td&gt;$215.00&lt;/td&gt;
&lt;td&gt;1727&lt;/td&gt;
&lt;td&gt;$102.14 ($78.89)&lt;/td&gt;
&lt;td&gt;+90% (58%)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.com/dp/B07H289S79?ref=ppx_yo2ov_dt_b_fed_asin_title&quot;&gt;link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Micron 8GB DDR4 SDRAM Memory&lt;/td&gt;
&lt;td&gt;2021-07-21&lt;/td&gt;
&lt;td&gt;$58.32&lt;/td&gt;
&lt;td&gt;$70.33&lt;/td&gt;
&lt;td&gt;$78.81&lt;/td&gt;
&lt;td&gt;1713&lt;/td&gt;
&lt;td&gt;$20.49 ($8.48)&lt;/td&gt;
&lt;td&gt;+35% (12%)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.com/dp/B01FROGWN2?ref=ppx_yo2ov_dt_b_fed_asin_title&quot;&gt;link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samsung EVO Plus 2TB NVMe&lt;/td&gt;
&lt;td&gt;2022-11-29&lt;/td&gt;
&lt;td&gt;$168.76&lt;/td&gt;
&lt;td&gt;$188.34&lt;/td&gt;
&lt;td&gt;$468.88&lt;/td&gt;
&lt;td&gt;1217&lt;/td&gt;
&lt;td&gt;$300.12 ($280.54)&lt;/td&gt;
&lt;td&gt;+178% (149%)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.com/dp/B07MFZXR1B?ref=ppx_yo2ov_dt_b_fed_asin_title&amp;amp;th=1&quot;&gt;link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silicon Power 2TB NVMe M.2&lt;/td&gt;
&lt;td&gt;2024-11-12&lt;/td&gt;
&lt;td&gt;$97.81&lt;/td&gt;
&lt;td&gt;$101.89&lt;/td&gt;
&lt;td&gt;$279.97&lt;/td&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;$182.16 ($178.08)&lt;/td&gt;
&lt;td&gt;+186% (175%)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.com/dp/B07ZQ97H3W?ref=ppx_yo2ov_dt_b_fed_asin_title&amp;amp;th=1&quot;&gt;link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MeLE Fanless Mini PC 4C, N100 16GB RAM 512GB&lt;/td&gt;
&lt;td&gt;2025-03-27&lt;/td&gt;
&lt;td&gt;$197.42&lt;/td&gt;
&lt;td&gt;$200.49&lt;/td&gt;
&lt;td&gt;$469.99&lt;/td&gt;
&lt;td&gt;368&lt;/td&gt;
&lt;td&gt;$272.57 ($269.50)&lt;/td&gt;
&lt;td&gt;+138% (135%)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.com/dp/B0CP3YL6J7?ref=ppx_yo2ov_dt_b_fed_asin_title&amp;amp;th=1&quot;&gt;link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SanDisk 128GB microSDXC Memory Card&lt;/td&gt;
&lt;td&gt;2025-11-20&lt;/td&gt;
&lt;td&gt;$14.95&lt;/td&gt;
&lt;td&gt;$15.17&lt;/td&gt;
&lt;td&gt;$29.99&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;$15.04 ($14.82)&lt;/td&gt;
&lt;td&gt;+100% (97%)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.com/dp/B0BDYVC5TD?ref=ppx_yo2ov_dt_b_fed_asin_title&amp;amp;th=1&quot;&gt;link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;(Current prices collected on 2026-03-31)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The item that caught my attention (and prompted this post in the first place) was the MeLE Fanless Mini PC. I was so excited to get a device like that for under $200 roughly a year ago, and now, it’s $500 with tax.&lt;/p&gt;
&lt;p&gt;There were a few items that I looked at but didn’t include in the chart. For example, the 2x12TB WD NAS HDD I bought last year didn’t really have a listing anymore, but similar models I found appeared to be double the price. I also didn’t include tech that doesn’t seem to be affected. Portable monitors, for instance, got cheaper.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So given all this data, what’s my big conclusion? I dunno. Mostly just that it sucks. I also find it unfortunate that at a time when so many people are feeling subscription fatigue, burned out by AI, and even returning to pirating content, these prices make solutions like &lt;a href=&quot;https://www.reddit.com/r/selfhosted/&quot;&gt;self-hosting&lt;/a&gt; even further out of reach for many. &lt;em&gt;(This all feels conspiracy-worthy…)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While writing this draft, there have been a few reports that &lt;a href=&quot;https://lifehacker.com/tech/ram-prices-might-go-down&quot;&gt;RAM prices have started to fall&lt;/a&gt;, but even so it will take quite some time before things truly stabilize again, if ever, across a very shaky global supply chain. Let’s hope for the best. Good luck everybody.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>BTW, My Chuwi Minibook X Died...</title>
    <link href="https://ryan.himmelwright.net/post/chuwi-minibookx-died/" />
    <updated>2026-03-05T16:25:34Z</updated>
    <id>https://ryan.himmelwright.net/post/chuwi-minibookx-died/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-died/lQYJm5Qy_9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-died/lQYJm5Qy_9-1200.jpeg&quot; alt=&quot;The insides of the minibook&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The internals of the Minibook X from when I tried to fix it.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;As a quick update, shortly after I &lt;a href=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/&quot;&gt;wrote about my Chuwi Minibook X&lt;/a&gt;… it died.&lt;/p&gt;
&lt;p&gt;After publishing that post, I did indeed purchase a &lt;a href=&quot;https://gpdstore.net/product/gpd-pocket-4/&quot;&gt;GPD Pocket 4&lt;/a&gt;, with the idea of it replacing both the minibook and my old &lt;a href=&quot;https://www.minisforum.com/products/minisforum-hx99g-hx100g&quot;&gt;Minisforum HX99g&lt;/a&gt;. When it arrived, I took a week or so to set it up and ensure it would fit my needs before selling the previous two devices. When I went to wipe the minibook… it wouldn’t turn on.&lt;/p&gt;
&lt;p&gt;As I started searching the internet for solutions, it quickly seemed like this is a &lt;a href=&quot;https://www.reddit.com/r/Chuwi/comments/1cb3bjh/did_i_killed_my_few_day_old_minibook_x_n100/&quot;&gt;common&lt;/a&gt; &lt;a href=&quot;https://forum.chuwi.com/t/minibook-x-n100-wont-turn-on-anymore-after-4-weeks-of-use/44118&quot;&gt;issue&lt;/a&gt; with the Chuwi Minibook, unfortunately.&lt;/p&gt;
&lt;h2&gt;Fixing Attempts&lt;/h2&gt;
&lt;p&gt;Periodically over the span of three months, I tried a number of the suggested fixes. At first, I tried the various &lt;em&gt;use charger &lt;strong&gt;X&lt;/strong&gt;, hold power button for &lt;strong&gt;Y&lt;/strong&gt; seconds, then charge for &lt;strong&gt;Z&lt;/strong&gt; hours&lt;/em&gt; solutions. They didn’t get me anywhere. Finally, one night I decided to crack it open and try some of the more invasive debugging methods. This included (but was not limited to) the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checked for visible damage (burn marks, loose components, disconnected cables)&lt;/li&gt;
&lt;li&gt;Identified the battery connector and disconnected the internal battery from the motherboard&lt;/li&gt;
&lt;li&gt;Performed a full embedded controller discharge (held power 45–60 seconds, waited several minutes)&lt;/li&gt;
&lt;li&gt;Reseated battery connector carefully and ensured full alignment&lt;/li&gt;
&lt;li&gt;Attempted proper charger-first boot sequence (plug in → wait → single press power)&lt;/li&gt;
&lt;li&gt;Attempted AC-only boot with battery disconnected&lt;/li&gt;
&lt;li&gt;Tested battery output using a multimeter (battery was fine and charged)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During these tests, there were no LEDs, fan twitches, or screen activity at any point. So, I concluded, with my limited knowledge, that it was likely something related to the connector or board logic.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Even if I missed something simple, or the fix was easy, at that point I was done wasting time on it. Instead of selling it, I pulled out the NVMe, and donated it to the &lt;a href=&quot;https://triangleecycling.com&quot;&gt;local e-cycling&lt;/a&gt; location. Hopefully they’re able to figure it out and give it a new life…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Now Update - 2026-02-24</title>
    <link href="https://ryan.himmelwright.net/now/2026-02-24/" />
    <updated>2026-02-24T22:43:32Z</updated>
    <id>https://ryan.himmelwright.net/now/2026-02-24/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/now/2026-02-24/1xPXClgUkT-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/now/2026-02-24/1xPXClgUkT-1200.jpeg&quot; alt=&quot;heavily edited picture of a power plug over an opened up laptop motherboard&quot; width=&quot;1200&quot; height=&quot;948&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The inside of the dead Chuwi Minibook X I tried to save.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I can’t believe that it’s almost two months into 2026 already! Anyway, enough has changed that I should probably do a Now update…&lt;/p&gt;
&lt;h3&gt;Indie Dev&lt;/h3&gt;
&lt;p&gt;For coding projects, I hinted that I had started a new project in the &lt;a href=&quot;https://ryan.himmelwright.net/now/2025-12-20/&quot;&gt;last now post&lt;/a&gt;, and I’ve continued chipping away at it since. It’s tentatively called &lt;a href=&quot;https://ryan.himmelwright.net/projects/parenlists&quot;&gt;ParenLists&lt;/a&gt; and is a simple, template-based, list app. It’s still rough around the edges, but the core functionality is getting there. I most recently wrote a simple sync server and added multi-user support, so my wife and I should be able to dogfood it now…&lt;/p&gt;
&lt;p&gt;I also made a minor update to &lt;a href=&quot;https://ryan.himmelwright.net/projects/perspectivepixels/&quot;&gt;Perspective Pixels&lt;/a&gt; to support changing the scaling percentages. I have the build in TestFlight, but haven’t completed the full process to push out an official release in the App Store.&lt;/p&gt;
&lt;h3&gt;Hardware Experiments&lt;/h3&gt;
&lt;p&gt;I continue to love using the GPD Pocket 4 running NixOS. I have been working on it most days, including to build out a homelab again on my tiny n100 NixOS server. However, I’ve started to use an even smaller, truly &lt;em&gt;pocketable&lt;/em&gt;, setup on the go.&lt;/p&gt;
&lt;p&gt;I grabbed a refurbished Pixel Fold (first gen) for roughly $350 USD. I wanted something to keep up with the current state of ‘Android’ (&lt;em&gt;or Android-alternatives&lt;/em&gt;) and try a foldable device for the first time. I have successfully installed &lt;a href=&quot;https://grapheneos.org&quot;&gt;GrapheneOS&lt;/a&gt; on it, set up the &lt;a href=&quot;https://en.wikipedia.org/wiki/Android_16#Linux_terminal&quot;&gt;Android Terminal app&lt;/a&gt;, and overwrote the default &lt;a href=&quot;https://www.debian.org&quot;&gt;Debian&lt;/a&gt; VM to use &lt;a href=&quot;https://nixos.org&quot;&gt;NixOS&lt;/a&gt;. I have it fully working with my flake configuration and everything! &lt;em&gt;(I actually wrote the rough draft for this on it)&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;It was nice to travel a bit over the holidays to see family, but it’s good to be back home for a short travel break. We’ve been busy most weekends with kids’ birthday parties, but those tend to be short, so our weekends aren’t &lt;em&gt;too&lt;/em&gt; crowded yet.&lt;/p&gt;
&lt;p&gt;I loved getting to watch the Olympics with my family again, but am honestly glad they’ve wrapped up as it was quite the time suck.&lt;/p&gt;
&lt;p&gt;Lastly, we finally got around to swapping out the legs on my home office desk to ones that support floor sitting all the way up to standing. Floor sitting at my main desk in the mornings and standing in the afternoons has been great for my flexibility.&lt;/p&gt;
&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;I haven’t finished a book yet this year, but that’s likely because I’m reading several in parallel and &lt;em&gt;all&lt;/em&gt; are essentially textbook-style non-fiction. This week, though, my library hold on &lt;em&gt;Apple in China by Patrick McGee&lt;/em&gt; finally became available. So hopefully the time limit and change of pace will help me finish it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Clojure Script for Project Git Logs</title>
    <link href="https://ryan.himmelwright.net/post/clojure-script-git-logs/" />
    <updated>2026-02-16T21:20:07Z</updated>
    <id>https://ryan.himmelwright.net/post/clojure-script-git-logs/</id>
    <content type="html">&lt;p&gt;As mentioned in &lt;a href=&quot;https://ryan.himmelwright.net/post/week-post-1797/&quot;&gt;my last week notes post&lt;/a&gt;, I wrote a simple Clojure script to help me review what I’ve completed across projects during a given time period.&lt;/p&gt;
&lt;p&gt;As of now, here’s my &lt;code&gt;weekly-commits.clj&lt;/code&gt;, which I execute using &lt;a href=&quot;https://babashka.org/&quot;&gt;babashka&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;!/usr/bin/env bb

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt; &#39;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;babashka.process &lt;span class=&quot;token symbol&quot;&gt;:refer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;shell&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt; &#39;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;babashka.fs &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt; &#39;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;clojure.string &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; since &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;seq&lt;/span&gt; *command-line-args*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;str/join&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt; *command-line-args*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;token string&quot;&gt;&quot;last Monday&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; home &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;System/getProperty&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user.home&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; projects
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; home &lt;span class=&quot;token string&quot;&gt;&quot;/code/home-manager&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; home &lt;span class=&quot;token string&quot;&gt;&quot;/code/ryan.himmelwright.net&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; home &lt;span class=&quot;token string&quot;&gt;&quot;/code/ParenProjects&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; home &lt;span class=&quot;token string&quot;&gt;&quot;/code/PerspectivePixels&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; git-log &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;git-dir&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:out&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shell&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:dir&lt;/span&gt; git-dir &lt;span class=&quot;token symbol&quot;&gt;:continue&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:out&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
               &lt;span class=&quot;token string&quot;&gt;&quot;git&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;--no-pager&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;log&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;--since=&quot;&lt;/span&gt; since&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;token string&quot;&gt;&quot;--pretty=format:%ad  %s&quot;&lt;/span&gt;
               &lt;span class=&quot;token string&quot;&gt;&quot;--date=format:%a %Y-%m-%d %H:%M&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; dirname &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dir-path&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;str/split&lt;/span&gt; dir-path &lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; project-header &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dir&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;=== &quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dirname&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; ===&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; git-log-string &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;git-dir&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;git-log &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;git-log&lt;/span&gt; git-dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;str/blank?&lt;/span&gt; git-log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;;; If blank log&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;project-header&lt;/span&gt; git-dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;No commits in time range.&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;;; Else: return normal message&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;project-header&lt;/span&gt; git-dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; git-log &lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;doseq&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dir projects&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fs/directory?&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;git-log-string&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;project-header&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Project dir (&quot;&lt;/span&gt; dir &lt;span class=&quot;token string&quot;&gt;&quot;) not on this computer.&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the script, I list out project directories (the &lt;code&gt;projects&lt;/code&gt; var) that contain git repos. By default, the script will print out all &lt;code&gt;git log&lt;/code&gt; results for each project since &lt;code&gt;last Monday&lt;/code&gt;. The logs are printed in a specific format that displays a timestamp to easily see what was completed and &lt;em&gt;when&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, I added the ability to change the &lt;code&gt;--since&lt;/code&gt; flag by providing different values as arguments to the script. For example, &lt;code&gt;bb weekly-commits.clj last Friday&lt;/code&gt; would show logs since last Friday (instead of the &lt;code&gt;last Monday&lt;/code&gt; default).&lt;/p&gt;
&lt;p&gt;For good measure, I added some conditional handling so that if there are no commits, or a project simply isn’t located on a system, it handles the issue gracefully with a message.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre class=&quot;language-txt&quot;&gt;&lt;code class=&quot;language-txt&quot;&gt;$&gt; bb bin/weekly-commits.clj
=== home-manager ===
Fri 2026-02-13 10:26  Moved PerspectivePixels project to ~/code/
Fri 2026-02-13 09:41  split out ghostty configs, added some elfeed updates
Thu 2026-02-12 17:37  adding immich
Wed 2026-02-11 21:59  Rewrote weekly-commits.clj myself to fix some edge cases
Wed 2026-02-11 21:58  Added zeal to porygon
Wed 2026-02-11 12:21  added weekly-commits script
Wed 2026-02-11 09:27  added vm support

=== ryan.himmelwright.net ===
Sun 2026-02-15 16:04  changed button style
Sat 2026-02-14 15:52  finished editing, publishing
Fri 2026-02-13 15:47  Merge branch &#39;main&#39; into week-post-1797
Fri 2026-02-13 14:10  added to archive
Fri 2026-02-13 14:10  Updated uses page
Fri 2026-02-13 13:05  Working through edit
Fri 2026-02-13 10:54  Started rough draft to links
Tue 2026-02-10 15:07  Template for week post

=== ParenProjects ===
Thu 2026-02-12 12:27  fixed client issues
Thu 2026-02-12 12:21  added sync support for cli
Thu 2026-02-12 12:02  Added skew automated testing
Thu 2026-02-12 11:49  Added conflict sync testing, and fixed issue with lists editing
Wed 2026-02-11 11:47  fixed bug so that pull sync doesn&#39;t run when sync isn&#39;t enabled
Wed 2026-02-11 11:41  Further cleanup, sync notifications
Wed 2026-02-11 10:46  refactor cleanup to consolidate app state
Tue 2026-02-10 11:20  Giant refactor to breakdown the clojure bridge in swift

=== PerspectivePixels ===
No commits in time range.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script is nothing fancy, and I don’t have much else to say about it. It’s just something I threw together one night, and I thought I’d share it. I love the ability to script simple solutions to little problems like this one.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Week Post #1797</title>
    <link href="https://ryan.himmelwright.net/post/week-post-1797/" />
    <updated>2026-02-14T20:55:29Z</updated>
    <id>https://ryan.himmelwright.net/post/week-post-1797/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/week-post-1797/6EtE4Qb8aj-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/week-post-1797/6EtE4Qb8aj-1200.jpeg&quot; alt=&quot;A mural of metal dinosaurs with iconic Durham landmarks&quot; width=&quot;1200&quot; height=&quot;691&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Welp, the weekly note officially isn’t a one-off now. Although, I purposefully refrained from posting one last week as I didn’t want to appear over-eager and set the bar for recurring posts too high out the gate.&lt;/p&gt;
&lt;p&gt;First, some quick home-life updates since &lt;a href=&quot;https://ryan.himmelwright.net/post/week-post-1795&quot;&gt;my previous week post&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Most&lt;/em&gt; of the snow and ice have melted&lt;/li&gt;
&lt;li&gt;I’ve loved watching the 2026 Olympics&lt;/li&gt;
&lt;li&gt;My wife and I finally paired up and re-applied debounce grease to our washer. So far, so good.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This weekend, we have several kids’ birthday parties and my parents are visiting. It should be busy, but fun.&lt;/p&gt;
&lt;h2&gt;Projects&lt;/h2&gt;
&lt;p&gt;A few days ago, I did write a quick Clojure script to help with the week-note endeavor, though I might write about that in its own post.&lt;/p&gt;
&lt;p&gt;Next, I made an update to &lt;a href=&quot;https://ryan.himmelwright.net/projects/perspectivepixels/&quot;&gt;PerspectivePixels&lt;/a&gt; for the first time since &lt;a href=&quot;https://ryan.himmelwright.net/post/perspectivepixels-1-1-0-release/&quot;&gt;the 1.1.0 release&lt;/a&gt;. I added the ability for users to customize the list of percentages used for scaling (ex: 200%, 250%). This has been a feature I’ve wanted for quite a while, and recently I’ve found myself curious about scaled resolutions &lt;em&gt;above&lt;/em&gt; the previous max of 200%… so it finally got added. &lt;em&gt;I’m realizing now the update is only in TestFlight still… so maybe I’ll push that over the weekend…&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For &lt;a href=&quot;https://ryan.himmelwright.net/projects/parenlists/&quot;&gt;ParenLists&lt;/a&gt;, I continued work on adding the MVP server and sync support. It’s going well and I have it mostly working and tested. I think the next phase will be building out user support and collaboration so that my wife and I can start using the app in our day-to-day life to really work out the issues. That’s quite the step up, but again… the MVP doesn’t have to be perfect. It just has to work for the two of us right now.&lt;/p&gt;
&lt;p&gt;Lastly, I’ve continued to enjoy working in my Linux setup, and have been curious about testing out other systems too. So, I’ve actually ordered a refurbished Pixel 6 that should arrive today. I’m not planning on using it for my main phone, but to test out several different things I have in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A physical Android test device for when I start to dabble more with ClojureDart/Flutter builds.&lt;/li&gt;
&lt;li&gt;Progress of Linux on Android with things like Termux and the official Android 16 Terminal&lt;/li&gt;
&lt;li&gt;GrapheneOS/LineageOS, etc.&lt;/li&gt;
&lt;li&gt;Building fun handheld devices, as seen on &lt;a href=&quot;https://reddit.com/r/writerdeck&quot;&gt;r/writerdeck&lt;/a&gt; and &lt;a href=&quot;https://reddit.com/r/cyberdeck&quot;&gt;r/cyberdeck&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oh yeah, and I also set up &lt;a href=&quot;https://nextcloud.com&quot;&gt;Nextcloud&lt;/a&gt; on our home server 😅…&lt;/p&gt;
&lt;h2&gt;Music&lt;/h2&gt;
&lt;p&gt;In addition to a ton of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Les_Mis%C3%A9rables:_Highlights_from_the_Motion_Picture_Soundtrack&quot;&gt;Les Misérables Soundtrack&lt;/a&gt;, I’ve been enjoying the new album, &lt;a href=&quot;https://ratboys.bandcamp.com/album/singin-to-an-empty-chair&quot;&gt;&lt;em&gt;Singin’ to an Empty Chair&lt;/em&gt;&lt;/a&gt;, by &lt;a href=&quot;https://www.ratboysband.com&quot;&gt;ratboys&lt;/a&gt; that was released last week.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;p&gt;I’ve logged some of the posts I’ve read over the last 2 weeks. Here are a few of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://baty.net/posts/2026/01/removing-analytics-from-baty-net/&quot;&gt;Removing analytics from baty.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kevquirk.com/blog/will-they-inherit-our-blogs/&quot;&gt;Will They Inherit Our Blogs?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://calnewport.com/the-dangers-of-vibe-reporting-about-ai/&quot;&gt;The Dangers of “Vibe Reporting” About AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sookocheff.com/post/time/hybrid-logical-clocks/&quot;&gt;Hybrid Logical Clocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mattgemmell.scot/the-fallen-apple/&quot;&gt;The Fallen Apple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://maxrozen.com/on-four-years-running-saas-competitive-market&quot;&gt;Four years of running a SaaS in a competitive market&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://maxrozen.com/getting-your-own-good-enough-laptop-for-under-500&quot;&gt;Getting your own good enough laptop for under $500&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.eddiedale.com/blog/switching-back-and-forth-between-my-mac-and-omarchy&quot;&gt;Switching back and forth between my Mac and Omarchy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.eddiedale.com/blog/back-to-simplicity&quot;&gt;Back to simplicity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kevquirk.com/the-internet-is-a-hamster-wheel&quot;&gt;The Internet is a Hamster Wheel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I rediscovered that Curtis McHale &lt;a href=&quot;https://curtismchale.ca/blog&quot;&gt;has a blog&lt;/a&gt;, and went on a bit of a reading spree last night.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://curtismchale.ca/2022/09/10/apple-watch-ultra-vs-garmin-i-dont-think-apple-pundits-get-it/&quot;&gt;Apple Watch Ultra vs Garmin - I Don’t Think Apple Pundits Get it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://curtismchale.ca/2026/02/06/owning-your-data-in-an-age-of-platforms&quot;&gt;Owning Your Data in an Age of Platforms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://curtismchale.ca/2026/01/26/facebook-is-run-by-entitled-idiots&quot;&gt;Facebook is Run by Entitled Idiots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://curtismchale.ca/2026/01/12/the-ipad-is-incredible-and-i-still-dont-use-it&quot;&gt;The iPad is Incredible, and I Still Don’t Use It&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://curtismchale.ca/2026/01/01/my-productivity-system-for-2026-is-boring-on-purpose&quot;&gt;My Productivity System for 2026 is Boring on Purpose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://curtismchale.ca/2025/12/29/2026-desk-tour&quot;&gt;2026 Desk Tour&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s it! Enjoy the weekend!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>I Asked Gemini Why My Linux Computer Feels Peaceful</title>
    <link href="https://ryan.himmelwright.net/post/ask-gemini-linux-peaceful/" />
    <updated>2026-02-06T16:12:16Z</updated>
    <id>https://ryan.himmelwright.net/post/ask-gemini-linux-peaceful/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ask-gemini-linux-peaceful/pumvPcgNba-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ask-gemini-linux-peaceful/pumvPcgNba-1200.jpeg&quot; alt=&quot;My desk and Linux computer, looking cozy&quot; width=&quot;1200&quot; height=&quot;912&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Linux setup working on this quick post.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Curious about what it would say, I asked &lt;a href=&quot;https://gemini.google.com&quot;&gt;Google Gemini&lt;/a&gt; &lt;em&gt;“Why does it feel so much more quiet/peaceful when I’m using my Linux computer compared to if I use one like my Mac or even when I’ve used Windows in the past?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I know I shouldn’t be, but I was surprised by its answer:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That “quiet” feeling isn’t just in your head—it is a byproduct of fundamentally different design philosophies. Windows and macOS are designed as services that want to engage you, whereas most Linux desktop environments are designed as tools that wait for you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Gemini then broke down a few, specific, reasoned examples. It covered common ones like building &lt;em&gt;your own&lt;/em&gt; environment on Linux vs. having it built &lt;em&gt;for you&lt;/em&gt;, and the increase of ‘Dark Patterns’ due to monetization on other OSes. These were fine, but I think my favorite statement was simply the concluding summary:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That “peace” you feel is the absence of a billion-dollar company trying to “optimize your engagement” while you’re just trying to write code or browse the web.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Probably the smartest thing I’ve ever seen Gemini say.&lt;/p&gt;
&lt;p&gt;Don’t worry though. It immediately followed that gem with the following question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Would you like to know how to further “silence” your COSMIC environment, such as disabling workspace animations or fine-tuning notification “Do Not Disturb” settings?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Eager for engagement, and apparently oblivious to its own role in the ‘Dark Patterns’ it had just thoroughly described above.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Week Post #1795</title>
    <link href="https://ryan.himmelwright.net/post/week-post-1795/" />
    <updated>2026-02-01T03:45:00Z</updated>
    <id>https://ryan.himmelwright.net/post/week-post-1795/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/week-post-1795/s8p31niZqQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/week-post-1795/s8p31niZqQ-1200.jpeg&quot; alt=&quot;A tuft of grass covered with ice&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Some ice in our yard this week -- photo credit to my wife.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This year, as a way to stay motivated with all the various projects I’m working on, I want to be better about reflecting on my weeks. One way to do that while also complying with my goal to write and share more is to publish it in a “week post”. I’m not committing to writing something every week, but I do want an outlet for when I &lt;em&gt;do&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So, I took the numbering idea &lt;a href=&quot;https://rknight.me/blog/weeknote-1949/&quot;&gt;Robb Knight uses&lt;/a&gt; (who I think &lt;a href=&quot;https://chrishannah.me/weeknote/1703/&quot;&gt;got from someone else&lt;/a&gt;). Basically, instead of worrying about continuously incrementing a weekly note counter, I simply number the post with the week number of &lt;em&gt;my&lt;/em&gt; life. I was born on a Friday, so it works out well enough.&lt;/p&gt;
&lt;p&gt;To make things easier, I coded a new option in my &lt;code&gt;webcli.clj&lt;/code&gt; script to calculate the week number and generate the post file for me:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;󱄅  [website] ⚡  scripts/webcli.clj new week

Select a week:
0. Last week: Week #1795 (2026-01-23 to 2026-01-29)
1. This week: Week #1796 (2026-01-30 to 2026-02-05)
2. Next week: Week #1797 (2026-02-06 to 2026-02-12)

Enter Choice (0-2): 0

Generating weekly post for 1795 [2026-01-23 to 2026-01-29]
Created week post: ~/code/ryan.himmelwright.net/src/post/2026/week-post-1795/index.md
You can now edit the content there.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’m testing it out for the first time with this post.&lt;/p&gt;
&lt;h2&gt;Projects&lt;/h2&gt;
&lt;p&gt;I accomplished a good bit of project work, despite the shorter week (an ice storm last weekend kept daycare closed the first few days).&lt;/p&gt;
&lt;p&gt;Before that storm, it was my turn to DM the session with my D&amp;amp;D group. I’m glad that 1) we managed to squeeze it in before the storm started, 2) the session went well, and 3) I don’t have to worry about planning it anymore XD.&lt;/p&gt;
&lt;p&gt;Next, I wrote and published &lt;a href=&quot;https://ryan.himmelwright.net/post/reenabled-contrast-accessibility-settings/&quot;&gt;a short post&lt;/a&gt;, along with some other backend work on the website.&lt;/p&gt;
&lt;p&gt;Regarding coding, I’ve been working on a list app and spent this week adding archiving functionality and performing some initial performance testing. I also planned out the preliminary design for building a sync server that I’ll start… next week.&lt;/p&gt;
&lt;h2&gt;Music&lt;/h2&gt;
&lt;p&gt;I listen to quite a bit of online radio during the day. One of the stations I frequently play, &lt;a href=&quot;https://www.wjrh.org&quot;&gt;WJRH&lt;/a&gt;, is actually the college station for my alma mater, &lt;a href=&quot;https://www.lafayette.edu&quot;&gt;Lafayette College&lt;/a&gt;. Over the past few months, they’ve been playing a Joyce Manor single that gets stuck in my head every time I hear it - &lt;a href=&quot;https://www.youtube.com/watch?v=NDmJDdFl_jI&amp;amp;list=RDNDmJDdFl_jI&amp;amp;start_radio=1&quot;&gt;All my friends are so depressed&lt;/a&gt;. This week, the full album (&lt;a href=&quot;https://joycemanor.bandcamp.com/album/i-used-to-go-to-this-bar&quot;&gt;&lt;em&gt;I used to go to this bar&lt;/em&gt; by Joyce Manor&lt;/a&gt;) was released, so I bought it. Love it.&lt;/p&gt;
&lt;p&gt;Also, if you haven’t listened to &lt;a href=&quot;https://www.youtube.com/watch?v=wWKSoxG1K7w&amp;amp;list=RDwWKSoxG1K7w&amp;amp;start_radio=1&quot;&gt;Streets of Minneapolis by Bruce Springsteen&lt;/a&gt; yet, give it a listen.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;p&gt;Lastly, just a few videos/links to share from this week:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=DEcwa68f-jY&quot;&gt;dotJS 2019 - James Long - CRDTs for Mortals (Youtube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=RbiGkdSGm4s&quot;&gt;Introduction to local-first applications (Youtube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://baty.net/posts/2026/01/removing-analytics-from-baty-net/&quot;&gt;Removing analytics from baty.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rldane.space/how-many-pixels-do-you-really-need.html&quot;&gt;How many pixels do you really need?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enjoy the rest of the weekend! If it’s snowing again where you live like it is here, stay safe and warm!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Re-enabled Contrast Accessibility Settings on my Apple Devices</title>
    <link href="https://ryan.himmelwright.net/post/reenabled-contrast-accessibility-settings/" />
    <updated>2026-01-30T03:25:51Z</updated>
    <id>https://ryan.himmelwright.net/post/reenabled-contrast-accessibility-settings/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/reenabled-contrast-accessibility-settings/vFQVlEbkc6-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/reenabled-contrast-accessibility-settings/vFQVlEbkc6-1200.jpeg&quot; alt=&quot;3 iPhone screenshots. First with settings toggles and the other two comparing a screenshot of Apple Notes&quot; width=&quot;1200&quot; height=&quot;794&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Settings I toggled (left) and one before -&gt; after compare (center, right).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Two weeks ago, I switched on some of the display accessibility settings. Specifically, I enabled the ones that focus on increasing contrast and readability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bold Text&lt;/li&gt;
&lt;li&gt;Larger Text&lt;/li&gt;
&lt;li&gt;Show Borders&lt;/li&gt;
&lt;li&gt;On/Off Labels&lt;/li&gt;
&lt;li&gt;Reduce Transparency&lt;/li&gt;
&lt;li&gt;Increase Contrast&lt;/li&gt;
&lt;li&gt;Differentiate Without Color&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;This isn’t my first time using these settings. I previously enabled them, probably around the time I was dabbling with &lt;a href=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/&quot;&gt;e-ink devices&lt;/a&gt; and was used to seeing blunt contrast in app UIs. However, I disabled the settings last year while coding the front-end of &lt;a href=&quot;https://ryan.himmelwright.net/projects/perspectivepixels/&quot;&gt;PerspectivePixels&lt;/a&gt;, and thought I was experiencing bugs &lt;em&gt;only on my devices&lt;/em&gt; as a result of the settings (my phone looked different from the simulator and my wife’s phone XD ).&lt;/p&gt;
&lt;p&gt;Afterward, I kept default settings during *OS 26 beta and release cycles which contained the &lt;a href=&quot;https://www.apple.com/newsroom/2025/06/apple-introduces-a-delightful-and-elegant-new-software-design/&quot;&gt;initial liquid glass implementation&lt;/a&gt;, so that I could follow the progress.&lt;/p&gt;
&lt;h2&gt;Why Switch Back&lt;/h2&gt;
&lt;p&gt;So why switch back now? Basically, I’ve given liquid glass a good amount of time, but think I’ve had enough of it for now. To be clear, I’m not one of those people who think it’s &lt;em&gt;‘the absolute worst thing Apple has ever done!’&lt;/em&gt;. Beyond the visibility issues, it does look… cool?.. &lt;em&gt;pretty&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;However, it represents a design progression that is moving in the complete opposite direction from where I am. I’ve always loved simplicity and contrast, and recently have started to double down on those aspects by dabbling with &lt;a href=&quot;https://www.neobrutalism.dev&quot;&gt;Neobrutalism&lt;/a&gt; design for &lt;a href=&quot;https://ryan.himmelwright.net/&quot;&gt;my website&lt;/a&gt; and my in-progress apps. Over time, my appreciation for readability, high contrast, and simplicity in applications has only increased, even at the expense of ‘&lt;em&gt;beauty&lt;/em&gt;’.&lt;/p&gt;
&lt;h2&gt;The Switch&lt;/h2&gt;
&lt;p&gt;On a whim one day, I toggled on the accessibility settings on my MacBook. However, not long after that I had them enabled on &lt;em&gt;all&lt;/em&gt; my Apple devices. Once it was set up, I immediately wanted it enabled whenever I switched to a device without the settings. It’s not perfect, and by all accounts ‘uglier’ than the default settings, but I much prefer it for my UIs.&lt;/p&gt;
&lt;p&gt;To try it out, go to &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Accessibility&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Display &amp;amp; Text Size&lt;/strong&gt; and play around with the different options. Over time, I ended up using most of them.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I just wanted to share a small but very satisfying change I’ve gone back to recently. It might not be for everyone… but I enjoy it. Try it out if you never have, especially if liquid glass isn’t your thing.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Timewarrior Trial - My iOS Shortcut</title>
    <link href="https://ryan.himmelwright.net/post/initial-timewarrior-ios-shortcut/" />
    <updated>2026-01-17T03:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/initial-timewarrior-ios-shortcut/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/initial-timewarrior-ios-shortcut/qP_JOJZkj--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/initial-timewarrior-ios-shortcut/qP_JOJZkj--1200.jpeg&quot; alt=&quot;Three iPhone screenshots showing a simple shortcut prompt and then text result&quot; width=&quot;1200&quot; height=&quot;794&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I made an iOS shortcut that, while overly simplistic, actually works quite well to connect with Timewarrior!&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the last year I’ve been using apps like &lt;a href=&quot;https://timeryapp.com&quot;&gt;timery&lt;/a&gt; and &lt;a href=&quot;https://timelines.app&quot;&gt;timelines&lt;/a&gt; to dabble with time tracking. Both of these apps have been great, and I highly recommend both of them. However…&lt;/p&gt;
&lt;p&gt;Equally, I’ve been lured back into &lt;a href=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/&quot;&gt;experimenting with text-based apps&lt;/a&gt; this year, after I fell once again to the infamous siren song of &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt;. To cut to the chase, I haven’t been able to get &lt;a href=&quot;https://timewarrior.net&quot;&gt;Timewarrior&lt;/a&gt; out of my head.&lt;/p&gt;
&lt;h2&gt;Desiring Timewarrior&lt;/h2&gt;
&lt;p&gt;Timewarrior, based on the great CLI task management app &lt;a href=&quot;https://taskwarrior.org&quot;&gt;Taskwarrior&lt;/a&gt;, is also a simple CLI app, but focused on time tracking. I’ve been eyeing it for some time but have never fully tried it. On occasion I’d clock projects while at my computer, but I also wanted to track house tasks or projects… times when I usually only have my phone on me. I know there are different Timewarrior servers and potential clients I could dive into, but that was more of a lift than I wanted to dedicate time to before I knew if the system even worked for me.&lt;/p&gt;
&lt;h2&gt;The Setup&lt;/h2&gt;
&lt;p&gt;Earlier this week I decided to use Timewarrior to track my computer projects, if only for a day. To simplify logging my time regardless of whether I was working on my MacBook or NixOS computer, I decided to actually use Timewarrior on &lt;em&gt;diglet&lt;/em&gt;, my tiny Intel N100-based computer we use as a home server.&lt;/p&gt;
&lt;h3&gt;Terminal Computers&lt;/h3&gt;
&lt;p&gt;I started out by just using an open terminal window with a persistent ssh/mosh connection which I could enter &lt;code&gt;timew&lt;/code&gt; commands into. It worked &lt;em&gt;fine&lt;/em&gt;, but I quickly got annoyed with having to switch windows (often on a different workspace) to enter the commands, when I was already working in a perfectly capable terminal. Eventually, I switched to passing my queries over ssh:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;ssh diglet.local timew summary :today&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that method working, I decided to abstract some of the repetitive parts, and added this alias to my zsh.nix config file:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;twdl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ssh diglet.local timew&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Local only&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that alias saved and applied to all my computers’ zsh configurations, I could now type &lt;code&gt;twdl &amp;lt;timewarrior commands&amp;gt;&lt;/code&gt;, and the command would execute on &lt;em&gt;diglet&lt;/em&gt; via ssh, from my device just as if I was using Timewarrior locally.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: the shorthand &lt;code&gt;twdl&lt;/code&gt; stands for “Timewarrior diglet local”&lt;/em&gt; 😉&lt;/p&gt;
&lt;h3&gt;My Phone&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/initial-timewarrior-ios-shortcut/6rhq6CzVlj-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/initial-timewarrior-ios-shortcut/6rhq6CzVlj-1200.jpeg&quot; alt=&quot;An Apple Shortcuts window open on the Mac, showing 3 simple steps&quot; width=&quot;1200&quot; height=&quot;831&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The shortcut was easy to configure, at least for a basic setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I finished up my computing tasks for the day, I wanted to continue logging with Timewarrior, but away from my computers. Passing commands over &lt;code&gt;ssh&lt;/code&gt; worked so seamlessly that I was sure I could set up something similar for my iPhone using Apple Shortcuts.&lt;/p&gt;
&lt;p&gt;There was only one “problem”… my ssh setup relied on being connected to my home network. However, when I want to track time from my phone, it is often when I am out and about. Luckily, this wasn’t a &lt;em&gt;real&lt;/em&gt; issue, as I already have &lt;a href=&quot;https://tailscale.com&quot;&gt;Tailscale&lt;/a&gt; configured and running on both my iPhone and diglet.&lt;/p&gt;
&lt;p&gt;I fired up the Shortcuts app and got to work. I started with getting the ‘Run script over SSH’ step working, as it was the most complicated and critical one. I set up the host using my diglet login and its Tailscale address. For the script part, I started with a hard-coded &lt;code&gt;timew summary&lt;/code&gt; until I confirmed the ssh commands were working.&lt;/p&gt;
&lt;p&gt;With the ssh section working, I moved on to adding a few extra steps to make a basic but flexible shortcut. First, I added a starting prompt that asks the user what &lt;code&gt;timew&lt;/code&gt; commands to execute. I saved that input to a variable, which I then passed to the ssh step: e.g., &lt;code&gt;timew $VAR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, I knew from running timew commands over ssh on the computer that the output is still returned. At first, I just displayed it in a text window, but it was too hard to read. I debated formatting the output, but it seemed like more effort than I wanted, and likely less flexible across commands. After a quick search, I learned that shell output displays more nicely when using ‘Quick Look’. &lt;em&gt;Good enough&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For ease of use, I bound the shortcut to the action button on my iPhone 17. Now all I have to do is hold the button for a second, and a window pops up to prompt me for Timewarrior commands (I don’t have to write the &lt;code&gt;timew&lt;/code&gt; part). I enter it, and then see the result on screen. That’s it. Simple, easy.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I’ve been using this for my time tracking throughout the week and honestly… it works &lt;em&gt;really well&lt;/em&gt;. At this point, the biggest issue I face is that I’m not familiar with the more advanced Timewarrior commands… &lt;em&gt;yet&lt;/em&gt;. There are more advanced ways to manage what I’m doing, and I could configure a proper server/client, but &lt;em&gt;this works great&lt;/em&gt;. The one thing I &lt;em&gt;might&lt;/em&gt; do is add additional options to the shortcut prompt to single-tap my most frequent commands, but still leave the custom input at the bottom.&lt;/p&gt;
&lt;p&gt;For now, I’m happy as is. Sometimes the best solution isn’t the fanciest one, but one that is simple and works&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2025 Recap</title>
    <link href="https://ryan.himmelwright.net/post/2025-year-recap/" />
    <updated>2025-12-30T02:30:00Z</updated>
    <id>https://ryan.himmelwright.net/post/2025-year-recap/</id>
    <content type="html">&lt;p&gt;I wanted to share and catalog what I did in 2025 (ex: books read and shows attended), but didn’t want to go crazy writing a full-blown post like &lt;a href=&quot;https://ryan.himmelwright.net/post/2023-review-picks/&quot;&gt;my 2023 review picks&lt;/a&gt; (notice I didn’t do it in 2024 XD). So, I’m dumping a bunch of lists into a post. Enjoy!&lt;/p&gt;
&lt;h2&gt;Home &amp;amp; Family&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2025-year-recap/NAt2NkF96q-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2025-year-recap/NAt2NkF96q-1200.jpeg&quot; alt=&quot;A bee on a flower&quot; width=&quot;1200&quot; height=&quot;841&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;We added a bunch of pollinator plants this year, and saw a huge increase in pollinators as a result :D&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;We’ve done quite a bit of traveling, and continued to transform and set up gardens outside our house. Some highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Several snow events in Durham, NC&lt;/li&gt;
&lt;li&gt;First year turning side yard into pollinator garden&lt;/li&gt;
&lt;li&gt;Added several garden beds&lt;/li&gt;
&lt;li&gt;Did a massive chip drop in the late spring&lt;/li&gt;
&lt;li&gt;Dog (Maggie) had double knee surgery&lt;/li&gt;
&lt;li&gt;Family vacations to Virginia Beach VA, Outer Banks NC&lt;/li&gt;
&lt;li&gt;Travel to see friends &amp;amp; family in Seattle/Everett WA, Columbia SC, PA&lt;/li&gt;
&lt;li&gt;First ‘Friends’ Birthday Party for son’s 3rd birthday&lt;/li&gt;
&lt;li&gt;Continued swim lessons and first time doing Soccer Shots with son&lt;/li&gt;
&lt;li&gt;Brother’s Wedding (Co-Best Man)&lt;/li&gt;
&lt;li&gt;Got my wife to finally play Stardew Valley&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Career&lt;/h2&gt;
&lt;p&gt;Big changes this year for my career. That about sums it up XD.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Left job @ Red Hat&lt;/li&gt;
&lt;li&gt;Built and released my first-ever app (&lt;a href=&quot;https://ryan.himmelwright.net/projects/perspectivepixels&quot;&gt;Perspective Pixels&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Website Changes/Improvements&lt;/h2&gt;
&lt;p&gt;I forgot I was still using &lt;code&gt;hugo&lt;/code&gt; for my website at the start of the year. I’ve done so much since that switch!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hugo -&amp;gt; Eleventy&lt;/li&gt;
&lt;li&gt;Nginx -&amp;gt; Caddy&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;/uses&lt;/code&gt; archive&lt;/li&gt;
&lt;li&gt;Added Now/Then Pages&lt;/li&gt;
&lt;li&gt;Added pages under &lt;code&gt;/explore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;/mumbles&lt;/code&gt;, &lt;code&gt;/POSSE&lt;/code&gt;, &lt;code&gt;/colophon&lt;/code&gt;, &lt;code&gt;/projects&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;/blogroll&lt;/code&gt;, &lt;code&gt;/reading&lt;/code&gt; page generators&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;/chipotle&lt;/code&gt;, &lt;code&gt;/defaults&lt;/code&gt;, &lt;code&gt;/guy&lt;/code&gt;, and &lt;code&gt;/save&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A smattering of /now updates and blog posts&lt;/li&gt;
&lt;li&gt;Theme tweaks&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Notable Tech Hardware Changes&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2025-year-recap/NNqXPth_jN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2025-year-recap/NNqXPth_jN-1200.jpeg&quot; alt=&quot;A desk setup with monitor, keyboard, and two laptops in a stand to the side.&quot; width=&quot;1200&quot; height=&quot;825&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;By the end of the year, I&#39;ve switched from a two-Mac to a Mac+Linux laptop setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While I feel like I did a bunch of hardware swapping as per usual, by the end of the year the net changes essentially boiled down to a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;16&amp;quot; M3 Pro MBP + 13&amp;quot; M3 MBA -&amp;gt; 14&amp;quot; M4 Pro (14-Core) MBP&lt;/li&gt;
&lt;li&gt;Minisforum HX99g -&amp;gt; GPD Pocket 4&lt;/li&gt;
&lt;li&gt;iPhone 15 -&amp;gt; iPhone 17&lt;/li&gt;
&lt;li&gt;Added MeLE Mini PC Quieter 4C as a home server&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Notable Tech Software Changes&lt;/h2&gt;
&lt;p&gt;I’ve continued to jump around with software quite a bit. Here are the most notable ones I can think of.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Obsidian -&amp;gt; Emacs&lt;/li&gt;
&lt;li&gt;TickTick -&amp;gt; Todoist -&amp;gt; Reminders/GoodTasks -&amp;gt; TickTick&lt;/li&gt;
&lt;li&gt;Readwise Reader -&amp;gt; newsboat -&amp;gt; newsraft -&amp;gt; Elfeed (emacs) for rss&lt;/li&gt;
&lt;li&gt;Fastmail webUI -&amp;gt; neomutt -&amp;gt; mu4e (emacs) for email&lt;/li&gt;
&lt;li&gt;Apple Music -&amp;gt; Mostly local music and online radio&lt;/li&gt;
&lt;li&gt;Daily driving NixOS in addition to macOS&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Concerts/Shows&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2025-year-recap/f_w9Wg-am8-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2025-year-recap/f_w9Wg-am8-1200.jpeg&quot; alt=&quot;A concert hall&quot; width=&quot;1200&quot; height=&quot;1010&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Meymandi Concert Hall, Raleigh, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A year or two ago, my wife and I made it a priority to attend concerts and shows when possible. This year we were fortunate enough to see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2025-02-01&lt;/code&gt; | Harry Potter and the Chamber of Secrets Orchestra – DPAC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-02-15&lt;/code&gt; | Symphony - Beethoven’s Piano Concerto #4 – NC Symphony&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-03-22&lt;/code&gt; | Alice in Wonderland Ballet – NC Ballet&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-05-31&lt;/code&gt; | Carnival of the Animals – NC Symphony&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-07-19&lt;/code&gt; | Les Misérables – DPAC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-09-03&lt;/code&gt; | Blink-182 w/ Alkaline Trio &amp;amp; Beauty School Dropout - Coastal Credit Union Music Park at Walnut Creek (Raleigh, NC)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-09-11&lt;/code&gt; | Beauty &amp;amp; The Beast – DPAC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-10-17&lt;/code&gt; | Stardew Valley: Symphony of Seasons – DPAC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-10-28&lt;/code&gt; | Lenny Pearce – The Ritz (Raleigh, NC)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-10-30&lt;/code&gt; | Six - The Musical – DPAC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-11-13&lt;/code&gt; | Neil deGrasse Tyson: An Astrophysicist Goes to the Movies (III) – DPAC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-11-23&lt;/code&gt; | Nutcracker Ballet – DPAC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-12-12&lt;/code&gt; | The Front Bottoms – The Ritz (Raleigh, NC)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-12-19&lt;/code&gt; | The Nutcracker Ballet &amp;amp; Symphony – NC Ballet/Symphony&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;(I think that’s at least all the big shows)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Books Read/Listened to&lt;/h2&gt;
&lt;p&gt;While it wanes at times, I think overall I’ve been able to maintain a good reading habit throughout most of the year.&lt;/p&gt;
&lt;p&gt;Still in Progress&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elements of Clojure by Zachery Tellman&lt;/li&gt;
&lt;li&gt;The Joy of Clojure (2nd edition) by Michael Fogus and Chris Houser&lt;/li&gt;
&lt;li&gt;Shadow Dark (TTRPG)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Completed&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wicked by Gregory Maguire (book)&lt;/li&gt;
&lt;li&gt;Code Name: Pale Horse by Scott Payne (audiobook)&lt;/li&gt;
&lt;li&gt;Shadow Dark Quickstart Guide (TTRPG)&lt;/li&gt;
&lt;li&gt;iOS and iPadOS 26 Review by Federico Viticci (ebook)&lt;/li&gt;
&lt;li&gt;macos 26 Tahoe by John Voorhees (ebook)&lt;/li&gt;
&lt;li&gt;Plato and a Platypus Walk Into a Bar - Understanding Philosophy Through Jokes (book)&lt;/li&gt;
&lt;li&gt;Beardy Moving away from Apple (Blog Collection)&lt;/li&gt;
&lt;li&gt;The 8 Minute Organizer by Regina Leeds (book)&lt;/li&gt;
&lt;li&gt;Fahrenheit-182 by Mark Hoppus (audiobook)&lt;/li&gt;
&lt;li&gt;Company of One - Paul Jarvis (ebook)&lt;/li&gt;
&lt;li&gt;Grow Wild - Katy Bowman (book)&lt;/li&gt;
&lt;li&gt;Life-changing Homes by Nicolás Boullosa, Kirsten Dirksen (eBook)&lt;/li&gt;
&lt;li&gt;Leonardo Da Vinci by Walter Isaacson (audiobook)&lt;/li&gt;
&lt;li&gt;The Almanack of Naval Ravikant - Eric Jorgenson, Jack Butcher, and Tim Ferriss (book)&lt;/li&gt;
&lt;li&gt;Only Say Good Things - Crystal Hefner (audiobook)&lt;/li&gt;
&lt;li&gt;Slow Productivity - Cal Newport (book)&lt;/li&gt;
&lt;li&gt;Everything I Know - Paul Jarvis (book)&lt;/li&gt;
&lt;li&gt;A System for Writing - Bob Doto (book)&lt;/li&gt;
&lt;li&gt;The Pathless Path - Paul Millerd (book)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Podcasts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;[2026-01-01] Update:&lt;/strong&gt; I didn’t include my podcast stats when I first published this post because they were still accumulating. But now, I have my completed &lt;a href=&quot;https://overcast.fm&quot;&gt;Overcast&lt;/a&gt; stats for 2025! Here’s the podcasts I logged at least an hour listening to this year (in Overcast).&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Podcast&lt;/th&gt;
&lt;th&gt;Listening Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Accidental Tech Podcast: Unedited Live Stream&lt;/td&gt;
&lt;td&gt;113:22:33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;The WAN Show&lt;/td&gt;
&lt;td&gt;91:03:13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Connected Pro &lt;em&gt;(Private)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;60:01:12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;The Rest Is History&lt;/td&gt;
&lt;td&gt;54:22:46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Upgrade&lt;/td&gt;
&lt;td&gt;49:59:14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Mac Power Users&lt;/td&gt;
&lt;td&gt;27:55:01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;LINUX Unplugged&lt;/td&gt;
&lt;td&gt;25:27:18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Hemispheric Views&lt;/td&gt;
&lt;td&gt;18:59:46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Analog(ue)&lt;/td&gt;
&lt;td&gt;12:49:37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Moretex &lt;em&gt;(Private)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;12:18:55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;The Rest Is Politics: US&lt;/td&gt;
&lt;td&gt;11:02:27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Under the Radar&lt;/td&gt;
&lt;td&gt;8:36:48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Ruminate&lt;/td&gt;
&lt;td&gt;8:06:05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;AppStories&lt;/td&gt;
&lt;td&gt;6:30:06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;The Glass Cannon Podcast&lt;/td&gt;
&lt;td&gt;5:26:22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Focused&lt;/td&gt;
&lt;td&gt;3:23:49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Crossover &lt;em&gt;(Private)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;3:20:12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Comfort Zone&lt;/td&gt;
&lt;td&gt;3:07:05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;The Rest Is Classified&lt;/td&gt;
&lt;td&gt;3:06:42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;MacStories Unwind&lt;/td&gt;
&lt;td&gt;3:05:32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Reconcilable Differences&lt;/td&gt;
&lt;td&gt;2:33:18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;The Rest Is Politics&lt;/td&gt;
&lt;td&gt;2:27:36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;Move Your DNA with Katy Bowman&lt;/td&gt;
&lt;td&gt;2:07:06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Conduit&lt;/td&gt;
&lt;td&gt;1:38:58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Syntax – Tasty Web Development Treats&lt;/td&gt;
&lt;td&gt;1:10:30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;defn&lt;/td&gt;
&lt;td&gt;1:04:09&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2025-year-recap/7yFtcxQ1i3-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2025-year-recap/7yFtcxQ1i3-1200.jpeg&quot; alt=&quot;My wife and I in a holiday lit space&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My wife Rebecca and I at the Museum of Natural Science, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’m sure I’ve missed a ton of stuff I should have probably listed. It’s been a busy year. However, at least I recorded &lt;em&gt;something&lt;/em&gt;. All in all, it’s been a great year, and I’m excited to keep things flowing into the next.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Now Update - 2025-12-20</title>
    <link href="https://ryan.himmelwright.net/now/2025-12-20/" />
    <updated>2025-12-21T03:41:19Z</updated>
    <id>https://ryan.himmelwright.net/now/2025-12-20/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/now/2025-12-20/UX2h1B7bxn-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/now/2025-12-20/UX2h1B7bxn-1200.jpeg&quot; alt=&quot;Looking up at Trees&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Museum of Life &amp; Science, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;… It’s been a few months since the last update. I’ve meant to do one several times, but kept tweaking various parts of the website first (which I wrote about in my &lt;a href=&quot;https://ryan.himmelwright.net/post/added-new-pages202511/&quot;&gt;latest post&lt;/a&gt;). Even prior to starting &lt;em&gt;this&lt;/em&gt; draft I went in and tweaked my config to get the &lt;code&gt;/now&lt;/code&gt; page functioning as a redirect like I wanted (so hopefully this won’t bust people’s &lt;code&gt;RSS&lt;/code&gt; feeds… fingers crossed). Anyway, let’s get to some updates.&lt;/p&gt;
&lt;h3&gt;Learning &amp;amp; Projects&lt;/h3&gt;
&lt;p&gt;Since wrapping up features for &lt;a href=&quot;https://ryan.himmelwright.net/projects/perspectivepixels/&quot;&gt;Perspective Pixels&lt;/a&gt;, I’ve moved on to some other coding projects. I’ve started work on a simple app, but I’m taking my time to truly learn and understand both the code and philosophies behind what I’m doing. I’m making the core (and CLI tooling) in &lt;a href=&quot;https://clojure.org&quot;&gt;Clojure&lt;/a&gt;, and I have loved getting back into functional programming with a LISP language. I’m finally getting to the rewarding point of my code actually building something … tangible.&lt;/p&gt;
&lt;p&gt;On the &lt;em&gt;tech&lt;/em&gt; front, I’ve doubled down on &lt;a href=&quot;https://nixos.org&quot;&gt;nixOS&lt;/a&gt; by experimenting with using it on &lt;a href=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/&quot;&gt;portable hardware&lt;/a&gt; to essentially make a better iPad+Keyboard setup for my needs. It’s gone very well and I’ve actually sold my old MinisForum HX99g to help purchase a GPD Pocket 4. It’s been great and not only functions as a tiny convertible computer, but is powerful enough to plug into my dock and use as a legitimate Linux device. So now I’m at least partially back on desktop Linux, and loving it.&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;Let’s start with the garden updates: Yes, it all died off by now XD. Not much else to report.&lt;/p&gt;
&lt;p&gt;As I noted in the last post, the end of the year has been busy, but fun. We’ve attended a smattering of shows, concerts (including a &lt;a href=&quot;https://www.lennypearce.com/&quot;&gt;toddler rave&lt;/a&gt;), and even my brother’s wedding. Each week was filled with swimming + soccer lessons, and a slew of other activities held together by intermittent colds and allergies.&lt;/p&gt;
&lt;p&gt;We’re preparing for a few days in PA to see family for the holidays, which should be nice. Assuming we don’t bring back any form of ‘cold’, that is.&lt;/p&gt;
&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;One of the website updates I completed was adding a &lt;a href=&quot;https://ryan.himmelwright.net/reading&quot;&gt;/reading&lt;/a&gt; page to the site. I’ve been keeping my reading list in an &lt;a href=&quot;https://orgmode.org/&quot;&gt;org&lt;/a&gt; note (of course), and have a script to generate the website page from the current state of that note. So I’m thinking I might retire this section of my &lt;a href=&quot;https://ryan.himmelwright.net/now&quot;&gt;/now&lt;/a&gt; updates.&lt;/p&gt;
&lt;p&gt;But as an update from last time anyway, I basically started and spent that entire time reading &lt;em&gt;Wicked&lt;/em&gt; by Gregory Maguire, along with an audio book or two. I’m currently working through the &lt;a href=&quot;https://www.thearcanelibrary.com/pages/shadowdark&quot;&gt;Shadowdark RPG Book&lt;/a&gt;, as well as some Clojure books (&lt;a href=&quot;https://www.manning.com/books/the-joy-of-clojure-second-edition&quot;&gt;The Joy of Clojure, 2nd edition&lt;/a&gt; and &lt;a href=&quot;https://elementsofclojure.com/&quot;&gt;Elements of Clojure&lt;/a&gt;).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Added New Pages to Website</title>
    <link href="https://ryan.himmelwright.net/post/added-new-pages202511/" />
    <updated>2025-12-01T02:45:00Z</updated>
    <id>https://ryan.himmelwright.net/post/added-new-pages202511/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/img/5sW5Dj7S8v-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/img/5sW5Dj7S8v-1024.jpeg&quot; alt=&quot;a cartoon guy with hat and beard&quot; width=&quot;1024&quot; height=&quot;1024&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Oh yeah, I also updated my favicon...&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A large motivation when I &lt;a href=&quot;https://ryan.himmelwright.net/post/switched-site-to-eleventy/&quot;&gt;switched my website to eleventy&lt;/a&gt; at the beginning of the year was to make modifying and tweaking much easier. So far, I can gladly say that’s been the case. This past month, I &lt;em&gt;finally&lt;/em&gt; got around to adding a bunch of pages I’ve had on my todo list for quite some time. With 11ty, the process was super smooth. Here’s a quick rundown of what I’ve added.&lt;/p&gt;
&lt;h1&gt;Explore Page&lt;/h1&gt;
&lt;p&gt;First up was figuring out how to manage navigation with this influx of pages on the website. I solved it by implementing a page I had created months ago, but never enabled: a site index page. This is simply a page where I can link to everything else on my website. While I could get fancy with drop-down navigation menus and other methods (and might one day), why over-complicate things just yet?&lt;/p&gt;
&lt;p&gt;The hardest problem I had was deciding what I wanted to name the page. At first it was simply named &lt;code&gt;more&lt;/code&gt;… but I didn’t love it. Then while browsing &lt;a href=&quot;https://rknight.me/&quot;&gt;robb knight’s&lt;/a&gt; website, I noticed his site referred to this as the &lt;code&gt;Explore&lt;/code&gt; page. I liked it, and took the idea &lt;a href=&quot;https://ryan.himmelwright.net/explore&quot;&gt;for my own website&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Mumbles&lt;/h1&gt;
&lt;p&gt;Another feature (inspired by Robb’s website, among others) that I’ve long wanted to implement is making a page where I can write short-form snippets that are then forwarded as posts to &lt;a href=&quot;https://mastodon.social/@himmallright&quot;&gt;my mastodon&lt;/a&gt; account. Setting it up in 11ty wasn’t too difficult: I just have an index page that takes all the dated entries and displays them in order. I also scripted the process of writing a new entry so I didn’t have to fuss with timestamps and other hindrances.&lt;/p&gt;
&lt;p&gt;Once again… naming was my biggest hurdle. Pages like this are often referred to as &lt;a href=&quot;https://indieweb.org/note&quot;&gt;notes on the indy-web&lt;/a&gt;. However… I didn’t want to use that name. To me, notes sound more like a page for a living document. For example, I could have a &lt;code&gt;note&lt;/code&gt; page about a project, or programming language. While I don’t define ‘notes’ for anything on my website currently, I wanted to keep the option open.&lt;/p&gt;
&lt;p&gt;Same as before, my solution surfaced while browsing the indy-web. I was reading a post on &lt;a href=&quot;https://thatalexguy.dev/&quot;&gt;thatalexguy.dev&lt;/a&gt; and realized he referred to these short online quips as ‘mumbles’. Again, I loved it and used it myself.&lt;/p&gt;
&lt;h2&gt;/POSSE&lt;/h2&gt;
&lt;p&gt;Once I had &lt;a href=&quot;https://ryan.himmelwright.net/mumbles&quot;&gt;my ‘mumbles’&lt;/a&gt; working, I set up a feed and linked it with &lt;a href=&quot;https://echofeed.app/&quot;&gt;echofeed&lt;/a&gt; to forward the mumbles as my Mastodon posts. Now that I was starting up a proper &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;, I added a &lt;a href=&quot;https://ryan.himmelwright.net/posse&quot;&gt;/posse slash-page&lt;/a&gt; for my strategy to be published if/as it expands.&lt;/p&gt;
&lt;h1&gt;Script-Generated Pages&lt;/h1&gt;
&lt;p&gt;My &lt;code&gt;new_mumble.clj&lt;/code&gt; wasn’t the only script I wrote during this process. For the next pair of pages, I wrote a Clojure script that generates the page from &lt;code&gt;.org&lt;/code&gt; files I already maintain in my Emacs setup. If I update either of those files, I simply run the script with &lt;a href=&quot;https://github.com/babashka/babashka&quot;&gt;babashka&lt;/a&gt; and the markdown is generated for me to push to the site.&lt;/p&gt;
&lt;h2&gt;Reading List&lt;/h2&gt;
&lt;p&gt;First up is a reading list/log. While writing my &lt;a href=&quot;https://ryan.himmelwright.net/now&quot;&gt;/now&lt;/a&gt; updates over the last few months, I usually included a small log at the bottom that listed what books I was currently reading, and recently finished. While writing it, I would often check my reading log note for the information and realized one day… this was stupid. If I have the log, why not just automate &lt;em&gt;that&lt;/em&gt; into an update. So I did.&lt;/p&gt;
&lt;p&gt;Instead of needing to include my reading progress in &lt;code&gt;/now&lt;/code&gt; updates, I just run a script to have the relevant sections from my reading list post to &lt;a href=&quot;https://ryan.himmelwright.net/reading&quot;&gt;/reading&lt;/a&gt;. Easy.&lt;/p&gt;
&lt;h2&gt;Blogroll&lt;/h2&gt;
&lt;p&gt;I’ve wanted to add a blogroll to my website for quite some time now, but didn’t want to deal with the hassle. However, now that I’ve been &lt;a href=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/&quot;&gt;using elfeed&lt;/a&gt; for &lt;code&gt;rss&lt;/code&gt;, I realized that after writing the reading list script, I could easily do something similar to generate a blogroll.&lt;/p&gt;
&lt;p&gt;Using the same approach, I have a Clojure script that reads in my &lt;code&gt;elfeed.org&lt;/code&gt; file, and parses the contents into the markdown page 11ty uses to generate &lt;a href=&quot;https://ryan.himmelwright.net/blogroll&quot;&gt;/blogroll&lt;/a&gt;. It’s not &lt;em&gt;curated&lt;/em&gt; and also contains some Reddit/YouTube feeds I’m experimenting with in elfeed, but it is &lt;em&gt;my blogroll&lt;/em&gt;.&lt;/p&gt;
&lt;h1&gt;Various Other Slash Pages&lt;/h1&gt;
&lt;p&gt;The last few pages I added are various other &lt;a href=&quot;https://slashpages.net/&quot;&gt;slash pages&lt;/a&gt; that I’ve wanted to add to my site for one reason or another.&lt;/p&gt;
&lt;h2&gt;/defaults&lt;/h2&gt;
&lt;p&gt;As a &lt;a href=&quot;https://hemisphericviews.com/&quot;&gt;Hemispheric Views Podcast&lt;/a&gt; listener, I feel obligated to have a &lt;a href=&quot;https://ryan.himmelwright.net/defaults&quot;&gt;/defaults&lt;/a&gt; page. I still don’t understand the scoring (who does?), and might consider tweaking the categories over time to better fit what software &lt;em&gt;I&lt;/em&gt; need and use. But for now, I don’t mention any software selections on my &lt;a href=&quot;https://ryan.himmelwright.net/uses&quot;&gt;/uses&lt;/a&gt; page, so this is a good home for that.&lt;/p&gt;
&lt;h2&gt;/guy&lt;/h2&gt;
&lt;p&gt;Another slash-page from Hemispheric Views. I really enjoy this page as a sort of quick and to the point &lt;a href=&quot;https://ryan.himmelwright.net/about&quot;&gt;/about&lt;/a&gt; supplement. It can be very similar to &lt;code&gt;/interests/&lt;/code&gt;, but I enjoy this format.&lt;/p&gt;
&lt;h2&gt;/save&lt;/h2&gt;
&lt;p&gt;Many services I use have referral programs. Not only do I never take advantage of them when telling others about a service… I often forget which services even have them. At the very least I hope maintaining &lt;a href=&quot;https://ryan.himmelwright.net/save&quot;&gt;/save&lt;/a&gt; will be a good reference for me 😂.&lt;/p&gt;
&lt;h2&gt;/chipotle&lt;/h2&gt;
&lt;p&gt;Last, but not least, we have probably the most important slash-page that exists: &lt;a href=&quot;https://ryan.himmelwright.net/chipotle&quot;&gt;/chipotle&lt;/a&gt;. While I’m mostly joking, being a burrito lover that has a go-to Chipotle order, it’s not as much of a joke as you may think…&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;So that’s the rundown of all the pages I’ve added in the past few weeks. I’ve also added a new &lt;code&gt;favicon&lt;/code&gt; image instead of just my initials (image included at top of the post). I’m happy with the results, and hope to continue updating and tweaking the pages over time. It’s the little things like this that make having a presence on the indy web so much fun. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Chuwi Minibook X - Initial Thoughts</title>
    <link href="https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/" />
    <updated>2025-11-14T16:50:00Z</updated>
    <id>https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/</id>
    <content type="html">&lt;p&gt;As I’ve continued to dig deeper into &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt; and &lt;a href=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/&quot;&gt;TUI life&lt;/a&gt; again, I naturally cultivated a new interest in &lt;a href=&quot;https://www.reddit.com/r/cyberDeck/&quot;&gt;cyberdecks&lt;/a&gt;. After researching cyberdeck options, I was drawn to the more realistic mini laptops. These devices are tiny, portable, and similar to the &lt;a href=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/&quot;&gt;netbooks that I used&lt;/a&gt; to supplement my main desktop.&lt;/p&gt;
&lt;p&gt;Additionally, I am still fascinated by the &lt;a href=&quot;https://www.macstories.net/stories/modular-computer/&quot;&gt;concept of modular computers&lt;/a&gt;. This is a device that can function as a tablet, laptop or desktop all in one. &lt;a href=&quot;https://www.macstories.net/stories/ipad-pro-for-everything/&quot;&gt;For some, this can be an iPad Pro&lt;/a&gt;. Unfortunately, I  have concluded that at least &lt;em&gt;for me&lt;/em&gt;, I don’t see the iPad fully fitting that role any time soon. I need a shell with full file system access, a good (ideally, declarative) package manager, and of course… Emacs.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/3ReQbnewnQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/3ReQbnewnQ-1200.jpeg&quot; alt=&quot;A tiny laptop with emacs opened.&quot; width=&quot;1200&quot; height=&quot;921&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Minibook X is a great Emacs machine...&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Luckily for me, the same technology that has enabled the modern mini-PCs I love… has also brought forth a renaissance in mini laptops that can be &lt;em&gt;far&lt;/em&gt; more powerful than the ‘netbooks’ of my college days.&lt;/p&gt;
&lt;p&gt;After a bit of research, I settled on the &lt;a href=&quot;https://us.chuwi.com/products/minibook-x?variant=51327604883755&quot;&gt;Chuwi Minibook X&lt;/a&gt;, which was ‘marked down’ to somewhere in the mid-$300 USD range at the time of purchase (about the base price of Apple’s cheapest iPad). I got the newer N150 model, and bumped the RAM from 12GB to 16GB for an extra $20 or so.&lt;/p&gt;
&lt;h1&gt;The good&lt;/h1&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/COZwo1iL16-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/COZwo1iL16-1200.jpeg&quot; alt=&quot;A folded laptop in &#39;tent&#39; mode and a controller, with Stardew Valley on the screen.&quot; width=&quot;1200&quot; height=&quot;1010&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Playing Stardew Valley - with a controller...&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After using this device for several weeks now, I have some initial thoughts, most of which are positive. Here are a few:&lt;/p&gt;
&lt;h2&gt;Build/value&lt;/h2&gt;
&lt;p&gt;At roughly $300-360 USD, it’s a solid machine. The build quality isn’t as good as my MacBook, or even the Thinkpads I used to own, but it is much better than any other ‘netbook-like’ device I’ve used in the past.&lt;/p&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;p&gt;In addition to being the best built laptop I’ve had at this size, it’s performed quite well and has even surprised me in some areas. Specifically, I was delightfully surprised that when I connected it to my Thunderbolt 4 Dock, with the Apple Studio Display attached… everything including the display just worked… at the full 5k resolution (2x scaled)!&lt;/p&gt;
&lt;p&gt;Similarly, while I know it’s likely possible to run Stardew Valley on a potato, I was still happy to fire it up without any issue and get playing.&lt;/p&gt;
&lt;p&gt;To be clear, it’s not a beast when it comes to performance… after all it only has an &lt;a href=&quot;https://www.intel.com/content/www/us/en/products/sku/241636/intel-processor-n150-6m-cache-up-to-3-60-ghz/specifications.html&quot;&gt;Intel N150&lt;/a&gt; in it. It definitely chugs a bit compared to my &lt;a href=&quot;https://www.minisforum.com/products/minisforum-hx99g-hx100g&quot;&gt;minisforum HX99g&lt;/a&gt;, especially if I’m managing windows on the 5k display. For general tasks however, like browsing, watching simple videos, or writing/coding around in Emacs, everything has largely just worked (and on &lt;a href=&quot;https://nixos.org/&quot;&gt;nixOS&lt;/a&gt; too)!&lt;/p&gt;
&lt;h2&gt;Size&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/UdRHxT2pzb-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/UdRHxT2pzb-1200.jpeg&quot; alt=&quot;An iPad Air + magic keyboard case, with an iPad mini on top (left), and the Chuwi Minibook X next to it (right.)&quot; width=&quot;1200&quot; height=&quot;603&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Comparing size with an 11&quot; iPad Air (M2) + Keyboard, and my iPad Mini (7th Gen).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, the size has been great. It’s noticeably smaller than my MacBook, yet large enough that it still feels like a laptop, and I don’t feel &lt;em&gt;too&lt;/em&gt; cramped on the keyboard. In fact, when I predominantly use it, I don’t feel like it’s small - I think everything else is large. Switching back to my 14&amp;quot; MBP it feels like I have a 16&amp;quot; again 😂.&lt;/p&gt;
&lt;p&gt;It doesn’t fit in my pocket, but I’m able to easily slide it into a small sling bag I already have with me when I’m out with my kid (my ‘dad bag’). This makes it easy to pull out the Minibook to work on some writing or research when waiting at swim lessons, for example. This was my primary motivation for trying out one of the machines, and so far, so good. It’s all the benefits of an 11&amp;quot; iPad… but with more useful software available to me (Emacs, nixOS, and a terminal).&lt;/p&gt;
&lt;h1&gt;What’s… &lt;em&gt;fine&lt;/em&gt; (but understandable at this price)&lt;/h1&gt;
&lt;p&gt;Not everything has been perfect, and there are a few things that are just &lt;em&gt;fine&lt;/em&gt;. However, I do want to re-state that at this price point… they still far surpass expectations 😆.&lt;/p&gt;
&lt;h2&gt;Screen and audio&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/TakODSPr8t-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/TakODSPr8t-1200.jpeg&quot; alt=&quot;An iPad Mini on the left, and the Minibook on the right, both playing Critical Role.&quot; width=&quot;1200&quot; height=&quot;651&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The screen, while okay, clearly isn&#39;t as vibrant as even my iPad Mini.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The screen is actually quite good for text and reading with its 215 PPI. However, when watching videos, I do notice that the colors are a bit flat. Again, it’s &lt;em&gt;fine&lt;/em&gt; but is noticable enough that I sometimes switch to my iPad Mini for watching videos instead.&lt;/p&gt;
&lt;p&gt;The other problem that usually forces me off of it while watching video is the audio. It doesn’t have much punch, but even worse… it just isn’t loud enough. Often I have to hook up headphones to it in order to hear something as simple as Critical Role while working on house tasks.&lt;/p&gt;
&lt;h2&gt;Trackpad&lt;/h2&gt;
&lt;p&gt;Another area of slight disappointment is the trackpad. It feels mushy and I don’t think taps always register. This was apparent when I was trying to fish in Stardew Valley. It was such a problem that I’ve started to use a gaming controller whenever playing on this laptop, as it’s simply a better experience (even with a low-quality controller). I think even just adding dedicated clicking buttons like on a Trackpad or GPD device would mitigate the worst issues, but the Minibook unfortunately only has the mediocre trackpad.&lt;/p&gt;
&lt;h1&gt;Annoying&lt;/h1&gt;
&lt;p&gt;There are just a few things that are flat out annoying about the Minibook, &lt;em&gt;despite&lt;/em&gt; the price point.&lt;/p&gt;
&lt;h2&gt;Tablet support setup…&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/BUmLVZ2uON-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/BUmLVZ2uON-1200.jpeg&quot; alt=&quot;The Minibook with only the screen visible, displaying a comic.&quot; width=&quot;1200&quot; height=&quot;950&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;While tablet mode is great for reading all my e-books, messing with the exposed trackpad + keyboard on the back makes it difficult to use in practice.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This isn’t really the fault of the Minibook, but is an annoyance I’ve had to deal with so I’m writing about it here. Essentially, the ‘tablet-mode’ has been a pain to get working. While it’s possible that this works great out of the box on Windows, I installed nixOS immediately. It seems like &lt;a href=&quot;https://github.com/knoopx/nix-chuwi-minibook-x&quot;&gt;others have worked out these issues&lt;/a&gt;, but I can’t seem to get &lt;em&gt;everything&lt;/em&gt; happily working together. When I manage to have the keyboard disable in tablet mode, the screen orientation doesn’t work (and the system seems less stable in general). When I have the rotation and stability all working, the keyboard is still active.&lt;/p&gt;
&lt;p&gt;So, I’ve settled somewhere in the middle. I use my config that leaves the system more stable, with the automatic screen orientation working, as well as binding a trackpad toggle to a key command. The trackpad is what I most often accidentally hit, and it is nice to still hit the physical keyboard buttons in tablet mode (volume, etc) compared to fiddling with a virtual keyboard setting, so the solution is &lt;em&gt;fine&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Flaky WiFi&lt;/h2&gt;
&lt;p&gt;By far the most annoying and detrimental to my use with the device is that the WiFi is &lt;em&gt;very flaky&lt;/em&gt;. It can often take minutes until it fully connects and I can ping a host. I’m not sure if it’s specific to my network (doesn’t seem to be as big an issue on the go, but I’ve only taken it to one or two places so far), but happens all the time at home. Super frustrating.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/nSJidOJQEy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/chuwi-minibookx-initial-thoughts/nSJidOJQEy-1200.jpeg&quot; alt=&quot;The Minibook in tent mode, with the ZSA Voyager split keyboard attached, editing code.&quot; width=&quot;1200&quot; height=&quot;909&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I like being able to use the laptop in &#39;tent mode&#39; with my split keyboard.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That’s about it. So far other than the WiFi flakiness, most of the problems I have are understandable, or I have figured out a workaround for them.&lt;/p&gt;
&lt;p&gt;My biggest takeaway is that I &lt;em&gt;love&lt;/em&gt; this kind of device setup. It’s small and easy to throw in a sling, I can flip back the screen to read, watch a video, or use with my split keyboard. When I’m home, I can connect to my desk setup and forget how small it is. Looking into more &lt;em&gt;expensive&lt;/em&gt; alternatives in this category, it seems these devices don’t even &lt;em&gt;need&lt;/em&gt; to fully sacrifice power.&lt;/p&gt;
&lt;p&gt;Which brings me to my ultimate conclusion: I love this type of device and it has me actually using nixOS more consistently again, something that my hx99g alone struggled to do. I don’t really want to own multiple linux computers though, especially since I also have a Mac. If I could somehow &lt;em&gt;combine&lt;/em&gt; the best parts of these two Linux boxes into one … (&lt;em&gt;cough&lt;/em&gt;… &lt;a href=&quot;https://gpdstore.net/product/gpd-pocket-4/&quot;&gt;GPD Pocket&lt;/a&gt;… or &lt;em&gt;cough&lt;/em&gt; &lt;a href=&quot;https://gpdstore.net/product/gpd-win-mini-2025/&quot;&gt;GPD WIN mini&lt;/a&gt;/&lt;a href=&quot;https://gpdstore.net/product/gpd-win-max-2-2025/&quot;&gt;max&lt;/a&gt;…), that would be wonderful.&lt;/p&gt;
&lt;p&gt;For now though, the Minibook has been a great experiment into this form of computing, especially at its price. And I’m thrilled to be back.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PerspectivePixels -- Version 1.1.0 Released</title>
    <link href="https://ryan.himmelwright.net/post/perspectivepixels-1-1-0-release/" />
    <updated>2025-09-29T16:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/perspectivepixels-1-1-0-release/</id>
    <content type="html">&lt;p&gt;Last week, I wrapped up some of the lingering features I wanted to add before the end of the month. Over the weekend, Apple approved the updates and they’re now live in the App Store.&lt;/p&gt;
&lt;h2&gt;What’s New in PerspectivePixels 1.1.0&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/perspectivepixels-1-1-0-release/UXd6V_kqQ3-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/perspectivepixels-1-1-0-release/UXd6V_kqQ3-1200.jpeg&quot; alt=&quot;Screenshots displaying the import, sorting, and delete all features&quot; width=&quot;1200&quot; height=&quot;807&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New features include import/exporting saved lists (left), saved list sorting (center), and delete all saved items (right).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This update introduces some key features I wanted to add once the MVP was out, as well as some tweaks for the new iOS 26 release.&lt;/p&gt;
&lt;h3&gt;Release Notes:&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;New Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Import/Export Your Monitor List:&lt;/strong&gt; You can now export your saved monitor list to a JSON file for backup or sharing. Use the new import feature to restore your list from a file. These options can be found in the new dropdown menu on the Saved Items tab.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bulk Delete:&lt;/strong&gt; A new option to delete all items at once has also been added to that dropdown menu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saved Items List Sorting:&lt;/strong&gt; You can now sort your saved list by name, screen size, PPI, or PPD in either ascending or descending order.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Improvements:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Updated App Icon:&lt;/strong&gt; The app icon has been updated to support liquid glass transparency in the latest iOS version.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Back-end Refinements:&lt;/strong&gt; I’ve made some under-the-hood changes to improve performance and compatibility with the latest iOS updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bug Fixes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixed a visual bug in the info pop-ups.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s all for this update. I just wanted to share the release notes here ;).&lt;/p&gt;
&lt;center&gt;
&lt;a href=&quot;https://apps.apple.com/us/app/perspective-pixels/id6748315710?itscg=30200&amp;itsct=apps_box_badge&amp;mttnsubad=6748315710&quot; style=&quot;display: inline-block;&quot;&gt;
    &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/perspectivepixels-1-1-0-release/c-ZETlXNwq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/perspectivepixels-1-1-0-release/c-ZETlXNwq-1200.jpeg&quot; alt=&quot;Download on the App Store&quot; style=&quot;width: 246px; height: 82px; vertical-align: middle; object-fit: contain;&quot; width=&quot;1200&quot; height=&quot;400&quot;&gt;&lt;/picture&gt;
&lt;/a&gt;
&lt;/center&gt;
</content>
  </entry>
  <entry>
    <title>Now Update - 2025-09-22</title>
    <link href="https://ryan.himmelwright.net/now/2025-09-22/" />
    <updated>2025-09-22T15:30:00Z</updated>
    <id>https://ryan.himmelwright.net/now/2025-09-22/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/now/2025-09-22/zChxBjME4F-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/now/2025-09-22/zChxBjME4F-1200.jpeg&quot; alt=&quot;A beach house walkway, with a sand dune in the foreground and house + sunset in the background&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Nags Head, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Life has remained busy since I last wrote. Between family vacations, I turned 34 at the beginning of the month and &lt;a href=&quot;https://ryan.himmelwright.net/post/perspectivepixels-first-release/&quot;&gt;released the MVP&lt;/a&gt; of my first app. It’s felt good though, as I’m finally making continued progress on my goals again.&lt;/p&gt;
&lt;h3&gt;Learning &amp;amp; Projects&lt;/h3&gt;
&lt;p&gt;After the last update, I released &lt;a href=&quot;https://ryan.himmelwright.net/projects/perspectivepixels/&quot;&gt;Perspective Pixels&lt;/a&gt; to the App Store. Since releasing it, I’ve mostly been working on wrapping up some new features I still want to add. I have a shortlist of items I’m hoping to wrap up soon, so I can start working on another project next month. Outside of the vacations, I’ve become a bit more consistant with coding most days, even if it’s for a short time. Generally, I feel happier and more productive during the rest of the day when I am able to sit down and focus on some code first.&lt;/p&gt;
&lt;p&gt;I’ve continued digging into my &lt;a href=&quot;https://github.com/doomemacs/doomemacs&quot;&gt;doom emacs&lt;/a&gt; configuration, although luckily the tweaking has slowed down a bit. I have also been playing around with &lt;a href=&quot;https://nixos.org/&quot;&gt;nixOS&lt;/a&gt; again, expanding my &lt;a href=&quot;https://github.com/nix-community/home-manager&quot;&gt;home-manager&lt;/a&gt; flake configuration to additionally manage a few nixOS systems. I must say, the combination of diving back into emacs &lt;em&gt;and&lt;/em&gt; nixOS at the same time really has me eyeing up &lt;a href=&quot;https://guix.gnu.org/&quot;&gt;Guix&lt;/a&gt; 😅.&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;For home life, we’ve had a few vacations, added soccer to the list of weekly activities for my 3 year old, and my wife and I attended a few shows/concerts including a &lt;a href=&quot;https://www.setlist.fm/setlist/blink182/2025/coastal-credit-union-music-park-at-walnut-creek-raleigh-nc-4359db1f.html&quot;&gt;blink-182 concert&lt;/a&gt; during my birthday week. Lots of fun.&lt;/p&gt;
&lt;p&gt;On the gardening front, we’ve mostly been letting it do its thing without much intervention. Most of it is starting to die off, as we’re nearing the end of the season, but it was a great experimental year.&lt;/p&gt;
&lt;p&gt;Looking at the family calendar, the rest of the year looks just as busy, but it’s all good. I’m enjoying the activity.&lt;/p&gt;
&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;I returned to my reading habit over the last month or so, and finished off several partially-read books. Currently, I’m working my way through the &lt;a href=&quot;https://www.macstories.net/&quot;&gt;MacStories.net&lt;/a&gt; OS reviews before starting my next book.&lt;/p&gt;
&lt;article&gt;
&lt;h4&gt;Currently Reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.macstories.net/stories/ios-and-ipados-26-the-macstories-review/&quot;&gt;iOS and iPadOS 26: The MacStories Review&lt;/a&gt; by Federico Viticci &lt;em&gt;(ebook version)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/6d235770-152e-49a3-8b1c-e0d2870a7105&quot;&gt;The Courage to Be Disliked: How to Free Yourself, Change Your Life, and Achieve Real Happiness&lt;/a&gt; by Fumitake Koga and Ichiro Kishimi &lt;em&gt;(Audiobook)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;article&gt;
&lt;h4&gt;Recently Finished&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.macstories.net/stories/macos-26-tahoe-the-macstories-review/&quot;&gt;macOS 26 Tahoe: The MacStories Review&lt;/a&gt; by John Voorhees &lt;em&gt;(ebook version)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/6be2221c-03c8-4a06-a311-1bcc95a2b233&quot;&gt;Plato and a Platypus Walk Into a Bar: Understanding Philosophy Through Jokes&lt;/a&gt; by Thomas Cathcart and Daniel Klein&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://beardystarstuff.net/posts/2025-03-16.html&quot;&gt;Beardy Guy Blog: Apple to Linux Journal Series&lt;/a&gt; (I exported all series posts into a giant PDF ebook)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/8c697e42-fc54-4768-ba14-8a8272f99005&quot;&gt;The 8 Minute Organizer: Easy Solutions to Simplify Your Life in Your Spare Time &lt;/a&gt; by Regina Leeds&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/37c8e93a-1593-46bc-8056-68a655cb9001&quot;&gt;Fahrenheit-182: A Memoir&lt;/a&gt; by Mark Hoppus (Audiobook)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/7a8d293a-5738-4a37-b97c-9c261045a425&quot;&gt;Company Of One: Why Staying Small Is the Next Big Thing for Business&lt;/a&gt; by Paul Jarvis&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;p&gt;See more of my reading history or connect with me via &lt;a href=&quot;https://app.thestorygraph.com/profile/himmallright&quot;&gt;my story graph profile&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Released My First iOS App  - PerspectivePixels</title>
    <link href="https://ryan.himmelwright.net/post/perspectivepixels-first-release/" />
    <updated>2025-09-02T16:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/perspectivepixels-first-release/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/perspectivepixels-first-release/yZzEuxiaPY-1000.webp 1000w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/perspectivepixels-first-release/yZzEuxiaPY-1000.jpeg&quot; alt=&quot;The perspective pixels log&quot; width=&quot;1000&quot; height=&quot;600&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A week and a half ago, I released my first-ever iOS app… actually, my first &lt;em&gt;real app&lt;/em&gt; in general. Before anyone gets too excited, I should mention that this is the first iteration of a basic app, and it probably shows 😅. Still, I’m happy to have passed that first milestone.&lt;/p&gt;
&lt;h1&gt;About&lt;/h1&gt;
&lt;p&gt;The app is called &lt;a href=&quot;https://ryan.himmelwright.net/projects/perspectivepixels&quot;&gt;PerspectivePixels&lt;/a&gt;, and it’s available &lt;a href=&quot;https://apps.apple.com/us/app/perspective-pixels/id6748315710?itscg=30200&amp;amp;itsct=apps_box_badge&amp;amp;mttnsubad=6748315710&quot;&gt;on the app store&lt;/a&gt;. It’s a simple iOS application that calculates several display metrics. For example, it calculates PPI, PPD, FOV, including their scaled values on HIDPI displays. You can also save display specs to tweak or compare later.&lt;/p&gt;
&lt;p&gt;That’s the general summary so far. I have ideas for future features, but this had everything I &lt;em&gt;needed&lt;/em&gt;, so it seemed like a good cutoff point for the MVP release.&lt;/p&gt;
&lt;h1&gt;Why&lt;/h1&gt;
&lt;p&gt;Why did I make this app in the first place?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;: I wanted it. Whenever I see the specs for a laptop or display, I inevitably have questions that require deeper calculations to answer:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Will this monitor have a high enough PPI for proper 2x scaling on macOS?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With &lt;code&gt;PerspectivePixels&lt;/code&gt;, I can enter the stats and immediately see the PPI. Additionally, I can even see what the ‘scaled’ resolution and PPI are if I would use it at a 2x scale&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“If I downsize from a 32” to 27&amp;quot; monitor, but move it 6 inches closer, will it effectively appear to be the same size to me?&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here, I enter the specs of the two displays in question, including the distance from my eyes and compare. I can look to see if the &lt;code&gt;field of view (FOV)&lt;/code&gt; is &lt;em&gt;roughly&lt;/em&gt; the same, and even toggle the distances to see how the values change. I can also look at the &lt;code&gt;ppd&lt;/code&gt; values to see how &lt;em&gt;sharp&lt;/em&gt; the device remains while moving it. It doesn’t matter if a moved monitor covers the same field of view if each pixel suddenly looks like a LEGO brick.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Will this device’s screen have a ‘usable’ resolution?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sometimes when I’m looking at unusual devices (for example mini-laptop/netbooks) it can be hard to tell what type of working space I will have. In this case, I can enter the specs and play around with the scaling values. For example I might see an 8&amp;quot; 1440x900 device. At 200 PPI, I’ll probably want to use 2x scaling, but with a scaled working resolution of 720x450, will it be worth it?&lt;/p&gt;
&lt;p&gt;Before building PerspectivePixels, I relied on a Python module I cobbled together years ago. Having an app that I can quickly open up and use, with saved items, is &lt;em&gt;much&lt;/em&gt; better.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/perspectivepixels-first-release/cbH35308Zc-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/perspectivepixels-first-release/cbH35308Zc-1200.jpeg&quot; alt=&quot;Three Screenshots of the app&quot; width=&quot;1200&quot; height=&quot;818&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Left: The Calculations Tab, Center: The Save Items List (with details), Right: Settings Pane.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;: I wanted to build my first app, and this was a great way to start. I had most of the back-end &lt;em&gt;logic&lt;/em&gt; from my python code and it was easy enough to port to &lt;a href=&quot;https://en.wikipedia.org/wiki/Swift_(programming_language)&quot;&gt;Swift&lt;/a&gt;. All the remaining tasks: designing + coding the UI, creating graphical assets (ex: app icons, screenshots), figuring out App Store Connect, &lt;a href=&quot;https://developer.apple.com/testflight/&quot;&gt;TestFlight&lt;/a&gt;, and App Store Review process… were &lt;em&gt;all&lt;/em&gt; things I had never done before.&lt;/p&gt;
&lt;p&gt;Being a simple, low-risk, app meant it was perfect as a learning project. At the end of the day, even if no one likes it, it’s something simple I wanted… and now have.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;So, feel free to check it out! I admit it’s not the best app out there, but I’ve already found it useful. I have a list of improvements I hope to make now that the MVP out, but I’m also always open to feedback.&lt;/p&gt;
&lt;center&gt;
&lt;a href=&quot;https://apps.apple.com/us/app/perspective-pixels/id6748315710?itscg=30200&amp;itsct=apps_box_badge&amp;mttnsubad=6748315710&quot; style=&quot;display: inline-block;&quot;&gt;
    &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/perspectivepixels-first-release/c-ZETlXNwq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/perspectivepixels-first-release/c-ZETlXNwq-1200.jpeg&quot; alt=&quot;Download on the App Store&quot; style=&quot;width: 246px; height: 82px; vertical-align: middle; object-fit: contain;&quot; width=&quot;1200&quot; height=&quot;400&quot;&gt;&lt;/picture&gt;
&lt;/a&gt;
&lt;/center&gt;
&lt;p&gt;Enjoy, and thanks if you give it a try!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Now Update - 2025-08-04</title>
    <link href="https://ryan.himmelwright.net/now/2025-08-04/" />
    <updated>2025-08-05T02:00:00Z</updated>
    <id>https://ryan.himmelwright.net/now/2025-08-04/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/now/2025-08-04/VSop6UGN5W-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/now/2025-08-04/VSop6UGN5W-1200.jpeg&quot; alt=&quot;A newly bloomed orange/yellow sunflower.&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My first ever sunflower bloom!&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With summer here, the last few weeks have been a bit different than the ones from when I previously wrote. It’s been &lt;strong&gt;hot&lt;/strong&gt; outside, and my wife’s work schedule has been a bit crazy in preparation for upcoming vacations we’re taking. I’ve spent more of my time working on one-off tasks and projects, including helping our dog recover from surgery in May (fully recovered now… but having some other issues 😅). All in all, things have been great, but it does feel a bit ‘off-track’ from what I imagined. I find summers typically feel that way though, so maybe it’s just normal.&lt;/p&gt;
&lt;h3&gt;Learning &amp;amp; Projects&lt;/h3&gt;
&lt;p&gt;I’m at the point to where I’m ready to release the MVP of my simple iOS app… so I’ve started to drag my feet. I’ve worked on cleaning it up, fixing some bugs, and setting up &lt;em&gt;basic&lt;/em&gt; testing. Progress has slowed down though for now.&lt;/p&gt;
&lt;p&gt;Part of the slowdown has also been my full dive into using tui-apps, and from there getting back into &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;emacs&lt;/a&gt; after a several year hiatus. The switch has been wonderful, but setting up a new config does take some up-front time. However, I’m starting to settle in now, only needing to make minor tweaks here and there. And I’m &lt;em&gt;loving&lt;/em&gt; it.&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;Home life has been a bit busy since my last update. My son turned 3, and we traveled up to Pennsylvania to celebrate with extended family, and then also had his first ‘kid/friend’ party when we returned.&lt;/p&gt;
&lt;p&gt;Project-wise, we’ve done a bunch of work on the yard/garden this summer. We added some new raised beds, &lt;s&gt;are still working through&lt;/s&gt; finished (today) a &lt;em&gt;massive&lt;/em&gt; chip-drop, and enjoyed the first year bloom of my wildflower garden!&lt;/p&gt;
&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;I fell off my reading habit, letting my WIP list balloon and not finishing anything. However, over the last week I’ve gotten back on track. Here’s what I’m reading:&lt;/p&gt;
&lt;article&gt;
&lt;h4&gt;Currently Reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/7a8d293a-5738-4a37-b97c-9c261045a425&quot;&gt;Company Of One: Why Staying Small Is the Next Big Thing for Business&lt;/a&gt; by Paul Jarvis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/2f43c64d-b120-41b5-8a4e-d02472db5608&quot;&gt;Daggerheart Core Set&lt;/a&gt; by Spenser Starke, Rowan Hall, Matthew Mercer (Darrington Press)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;article&gt;
&lt;h4&gt;Recently Finished&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/55bf44bb-3258-4f9f-8182-fc15add17777&quot;&gt;Grow Wild: The Whole-Child, Whole-Family, Nature-Rich Guide to Moving More&lt;/a&gt; by Katy Bowman&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/1a2c4184-b53c-4c45-9143-153b25a2f4b7&quot;&gt;Life-Changing Homes: Eco-Friendly Designs that Promote Well-Being&lt;/a&gt; by Nicolás Boullosa and Kirsten Dirksen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/b46883af-4662-46cf-b58c-17847d79542d&quot;&gt;Leonardo da Vinci&lt;/a&gt; by Walter Isaacson (&lt;em&gt;audiobook&lt;/em&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;p&gt;See more of my reading history or connect with me via &lt;a href=&quot;https://app.thestorygraph.com/profile/himmallright&quot;&gt;my story graph profile&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rethinking my RSS use</title>
    <link href="https://ryan.himmelwright.net/post/rethinking-my-rss-use/" />
    <updated>2025-08-01T02:47:00Z</updated>
    <id>https://ryan.himmelwright.net/post/rethinking-my-rss-use/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/SACjNjWJzS-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/SACjNjWJzS-1200.jpeg&quot; alt=&quot;An Emacs buffer with a list of RSS articles, and filter criteria in the mini buffer&quot; width=&quot;1200&quot; height=&quot;756&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Elfeed RSS reader&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Over the past month, I’ve been cleaning up and organizing my personal digital music library to bring it back to life after years of streaming era neglect. Oddly enough, that task has pushed me back into searching for and using TUI applications, as I now use &lt;a href=&quot;https://www.musicpd.org/&quot;&gt;mpd&lt;/a&gt; and &lt;a href=&quot;https://mierak.github.io/rmpc/&quot;&gt;rmpc&lt;/a&gt; to listen to my local library. I set up &lt;a href=&quot;https://neomutt.org/&quot;&gt;neomutt&lt;/a&gt; for email and continue looking for more TUI opportunities.&lt;/p&gt;
&lt;p&gt;When it comes to read-it-later and RSS clients, I have been using &lt;a href=&quot;https://readwise.io/read&quot;&gt;Readwise Reader&lt;/a&gt; for almost a year now. It’s an expensive, but amazing system, and one I’ve configured to automatically import all my highlights into &lt;a href=&quot;https://obsidian.md&quot;&gt;obsidian&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However… I haven’t been reading much with it lately. On top of that, I’ve been reconsidering how I take notes on what I read. I rarely use or refer back to all my blind highlighting, and the self-imposed pressure to import everything to my system has become a deterrent. It’s discouraged me from reading physical books, or even digital ones with limited Readwise import support.&lt;/p&gt;
&lt;p&gt;Without the &lt;em&gt;need&lt;/em&gt; to be using Readwise Reader, and the desire to have some TUI options, I started experimenting.&lt;/p&gt;
&lt;h2&gt;Trialing Apps&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/9uEzhOgdnD-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/9uEzhOgdnD-1200.jpeg&quot; alt=&quot;A terminal application (nom) list a bunch of RSS articles&quot; width=&quot;1200&quot; height=&quot;1206&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I loved nom&#39;s ease and simple UI, but it didn&#39;t support categorical lists like I wanted.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I started with &lt;a href=&quot;https://newsboat.org/&quot;&gt;Newsboat&lt;/a&gt;, a classic TUI feed reader, but I didn’t love its default configuration. So, I tried &lt;a href=&quot;https://codeberg.org/newsraft/newsraft&quot;&gt;Newsraft&lt;/a&gt; and really enjoyed it. But it didn’t support back-ends for feeds, which I needed to sync across my devices.&lt;/p&gt;
&lt;p&gt;Combining what I learned, I decided to set up a FreshRSS server and attempted to configure &lt;em&gt;Newsboat&lt;/em&gt; to work more like &lt;em&gt;Newsraft&lt;/em&gt;. I got the server up and running, but was unable to configure Newsboat to my liking.&lt;/p&gt;
&lt;p&gt;I tried more TUI applications, including &lt;a href=&quot;https://github.com/guyfedwards/nom&quot;&gt;nom&lt;/a&gt;. Nom’s defaults and simplicity were wonderful, but it didn’t list the feed source next to each article, which made it a bit more chaotic.&lt;/p&gt;
&lt;p&gt;I apparently couldn’t get a system that matched what I wanted…  But what did I &lt;em&gt;actually want&lt;/em&gt;?&lt;/p&gt;
&lt;h2&gt;Rethinking my needs&lt;/h2&gt;
&lt;p&gt;Reframing the problem, I thought “What &lt;em&gt;am&lt;/em&gt; I trying to get out of RSS… &lt;em&gt;Do I even NEED to worry about sync?&lt;/em&gt;”&lt;/p&gt;
&lt;p&gt;After some consideration I realized &lt;strong&gt;I mostly want RSS to be somewhere I can go to read and have articles come to me.&lt;/strong&gt; I don’t care about having highlights for every little thing I read anymore. If I &lt;em&gt;want&lt;/em&gt; to note something, I’ll spin up a note for it, but I don’t &lt;em&gt;need&lt;/em&gt; them by default. Similarly, I don’t intend to read &lt;em&gt;everything&lt;/em&gt; in my feed, as that just adds unneeded stress to my life.&lt;/p&gt;
&lt;p&gt;Given my new awareness, I reasoned that I didn’t actually &lt;em&gt;need&lt;/em&gt; my feeds to sync. Having consistent read history across devices, while nice, was no longer a necessity. I now see RSS feeds as a place to get curated articles to read, from my favorite sources.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/2mnfbV5ZVR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/2mnfbV5ZVR-1200.jpeg&quot; alt=&quot;A terminal app with a list of `RSS` articles&quot; width=&quot;1200&quot; height=&quot;756&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Newsraft worked perfect out of the box, but didn&#39;t support feed syncing&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With the requirements redefined, I was able to come to a new solution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I use Newsraft when on my MacBook.&lt;/li&gt;
&lt;li&gt;As for iPhone/iPad Mini, I started using Reeder (classic) again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I used the switch as an opportunity to clean out my feed list, and break it down into different categories for both applications. So far, things have been going well. I’ve found myself actually reading my feeds again, and am much less pressured while doing so. I am very happy with this setup for what it is.&lt;/p&gt;
&lt;h2&gt;~&lt;em&gt;Update&lt;/em&gt;~&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/AvdvRI85hC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rethinking-my-rss-use/AvdvRI85hC-1200.jpeg&quot; alt=&quot;A fullscreen Emacs window, with a thin buffer on the left showing a list of websites in Org Mode. In the right buffer, the exported opml file for the list&quot; width=&quot;1200&quot; height=&quot;750&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I can cleanly maintain my RSS feed list in an `org` file, and export it as an opml file to import on my mobile devices.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;It’s taken me so &lt;em&gt;long&lt;/em&gt; to finish editing and publishing this draft, that I’ve moved beyond playing with terminal apps… and have been happily consumed by &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt; once again after a multi-year hiatus. Consequently, I’m now using &lt;a href=&quot;https://github.com/skeeto/elfeed&quot;&gt;Elfeed&lt;/a&gt; for &lt;code&gt;RSS&lt;/code&gt; on the mac, and continue to use Reeder Classic on my mobile devices.&lt;/p&gt;
&lt;p&gt;Elfeed was a little weird to get used to at first, but I love it now. I am able to define my feed list in an &lt;a href=&quot;https://orgmode.org/&quot;&gt;org&lt;/a&gt; file, which has built-in support to then export that file as an &lt;code&gt;opml&lt;/code&gt; one which I use to easily import an updated feed list into Reeder. Amazing.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Been using the Aerospace Tiling WM on macOS</title>
    <link href="https://ryan.himmelwright.net/post/been-using-aerospace-wm/" />
    <updated>2025-06-23T14:12:30Z</updated>
    <id>https://ryan.himmelwright.net/post/been-using-aerospace-wm/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/MIOLDeHqG7-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/MIOLDeHqG7-1200.jpeg&quot; alt=&quot;A screenshot of my desktop with tiled windows. A terminal on the left and text editor on the right&quot; width=&quot;1200&quot; height=&quot;750&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Aerospace-tiled window setup while outlining this post.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Back when I used Linux as my main (and &lt;em&gt;only&lt;/em&gt;) desktop OS, I often enjoyed the plethora of great tiling window managers. I most commonly turned to &lt;a href=&quot;https://i3wm.org/&quot;&gt;i3/gaps&lt;/a&gt; (and later &lt;a href=&quot;https://swaywm.org/&quot;&gt;sway&lt;/a&gt;), but dabbled with others like &lt;a href=&quot;https://dwm.suckless.org/&quot;&gt;dwm&lt;/a&gt;, &lt;a href=&quot;https://awesomewm.org/&quot;&gt;awesomewm&lt;/a&gt;, and even &lt;a href=&quot;https://stumpwm.github.io/&quot;&gt;stumpwm&lt;/a&gt; (I had to try the &lt;a href=&quot;https://lisp-lang.org/&quot;&gt;common lisp&lt;/a&gt; one 😂).&lt;/p&gt;
&lt;p&gt;Since switching to the Mac as my primary desktop computer, I hadn’t found a comparable tiling alternative that worked for me. I tried &lt;a href=&quot;https://github.com/koekeishiya/yabai&quot;&gt;yabi&lt;/a&gt; and &lt;a href=&quot;https://github.com/ianyh/Amethyst&quot;&gt;Amethyst&lt;/a&gt;, but they didn’t stick. Eventually, I settled on using the window management commands in &lt;a href=&quot;https://www.raycast.com/&quot;&gt;Raycast&lt;/a&gt; to manually ‘tile’ my windows when needed.&lt;/p&gt;
&lt;h1&gt;Enter Aerospace&lt;/h1&gt;
&lt;p&gt;Several months ago (&lt;em&gt;I can’t believe it’s been that long already&lt;/em&gt;) I re-discovered &lt;a href=&quot;https://github.com/nikitabobko/AeroSpace&quot;&gt;Aerospace WM&lt;/a&gt; and gave it a shot.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/UFjVtFJJQX-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/UFjVtFJJQX-1200.jpeg&quot; alt=&quot;The iPhone Mirroing app and the XCode iOS simulator floating in the screen, without borders&quot; width=&quot;1200&quot; height=&quot;779&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I disabled jankyboarders for the Xcode simulator and iPhone-Mirroring.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Being an ‘i3-like tiling window manager’, I was able to easily drop in the default config and already have 90% of what I wanted . During the next few days/weeks, I slowly tweaked the rest to my needs.&lt;/p&gt;
&lt;p&gt;Some of these tweaks included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding the &lt;a href=&quot;https://github.com/FelixKratz/JankyBorders&quot;&gt;janky-borders&lt;/a&gt; config inside the aerospace one… and switching the colors to match.&lt;/li&gt;
&lt;li&gt;Defining a few applications to default to floating windows, and not have borders (i.e. iPhone-Mirroring app)&lt;/li&gt;
&lt;li&gt;Set secondary monitors to default to workspace &lt;em&gt;9&lt;/em&gt; when connected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I even got most of the configuration integrated into my nix home-manager configuration, which is awesome.&lt;/p&gt;
&lt;h3&gt;Bar config&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/3l-UBsuItN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/3l-UBsuItN-1200.jpeg&quot; alt=&quot;My menu bar, but floating halves, with curved corners and a green outline.&quot; width=&quot;1200&quot; height=&quot;34&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;When I couldn&#39;t get sktychybar to work with my nix config, I themed bartender to make the menu bar look good enough.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I tried setting up &lt;a href=&quot;https://github.com/FelixKratz/SketchyBar&quot;&gt;sketchybar&lt;/a&gt;, but was never able to convince it to play nice with my nix setup. While fighting with the config, I learned that &lt;a href=&quot;https://www.macbartender.com/&quot;&gt;bartender&lt;/a&gt; can actually modify its appearance to one that better fits what I wanted…so I’ve just been using that instead. It works.&lt;/p&gt;
&lt;h2&gt;What I Like&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Quick to get up and going&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The default config works well and the keybindings are largely what I want.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Easily customizable for things that need tweaking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modifying the configuration for the most part is rather straightforward, especially coming from a history using i3. Being able to add it to my &lt;code&gt;nix&lt;/code&gt; setup was a nice touch too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It just flows nicely&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I like how easy it is to move around the workspaces and windows. Even the multi-monitor approach, while simple, is straight-forward. I just hit &lt;code&gt;opt-shift-tab&lt;/code&gt; from a space that I want to move to a new monitor, and it jumps over. Easy.&lt;/p&gt;
&lt;h2&gt;What could be better&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Moving Around Window Layouts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One thing I think i3 (and other tiling managers) handled a bit better is moving around window layout configurations in a single workspace. It’s hard to dial in a good layout hierarchy as you open new windows. In i3, I was able to hit a key to tell the system that my next window should be either a horizontal or vertical split, which enabled me to build the structure as I went. In aerospace, the philosophy seems to be after-the-fact adjustments. That’s fair, but I still find it hard to organize the windows in complex nested trees. Likely, I haven’t fully made the mental adjustment on how to do this yet though.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Single (Mac) Workspace Quirks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/BkYIaONoph-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/BkYIaONoph-1200.jpeg&quot; alt=&quot;A screenshot of my desktop, with a zoomed in box of the bottom right corner. In the box there is a red arrow pointing to the small corner of a hidden window just barely in the box&quot; width=&quot;1200&quot; height=&quot;779&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Windows hidden *mostly* off screen. Sometimes they get stuck there.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I understand that Aerospace works better for me than alternatives I’ve tried because it forgoes trying to use Apple’s built-in spaces mechanism, and just manages everything on its own in a single workspace.&lt;/p&gt;
&lt;p&gt;While this is fine, I do miss being able to swipe between workspaces. This &lt;em&gt;might&lt;/em&gt; be something that I can configure a workaround for, but I haven’t gotten there yet.&lt;/p&gt;
&lt;p&gt;Additionally, because all the windows are managed in a single workspace, sometimes things get a bit off and you can see them hanging out waiting in the corner of the screen. Or, they become stuck off-screen and the only way I can get the application to show is to close it out and re-open it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Occasional Freeze/glitch&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every now and then, Aerospace will get clogged up while switching around and the WM will freeze for a bit. This isn’t too common and I seem to notice it less often now.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/NLB7jTZLfK-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/been-using-aerospace-wm/NLB7jTZLfK-1200.jpeg&quot; alt=&quot;A couple tiled ghostty terminal windows, playing music, displaying fast-fetch stats, and this post&#39;s markdown file in neovim.&quot; width=&quot;1200&quot; height=&quot;779&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A &#39;r/unixporn/&#39; style screenshot.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So far, Aerospace has been great! I’ve finally hit that sweet spot where I can’t remember the last time I’ve had to tweak the config. Best of all, most days I forget about Aerospace as a program… it’s just how I use my computer now. The ultimate goal for a window manager 🙂.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Now Update - 2025-05-05</title>
    <link href="https://ryan.himmelwright.net/now/2025-05-06/" />
    <updated>2025-05-06T14:30:00Z</updated>
    <id>https://ryan.himmelwright.net/now/2025-05-06/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/now/2025-05-06/qTAw75yDi0-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/now/2025-05-06/qTAw75yDi0-1200.jpeg&quot; alt=&quot;A landscape of a rocky beach, water, and some mountains in the distance.&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Just got back for a wonderful trip to Seattle/Everett WA to visit friends (photo: Richmond Beach, Shoreline WA USA)&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My days still generally involve a few hours of learning or project work over coffee in the morning, followed by gardening and house tasks in the afternoon. I might flip this order as the percentage of my outdoor tasks continues to steadily rise alongside the average afternoon temperature…&lt;/p&gt;
&lt;h3&gt;Learning &amp;amp; Projects&lt;/h3&gt;
&lt;p&gt;I’ve been slowly working through a simple iOS app I started months ago. It’s still ugly, but slowly getting… better. I’ve also been making small tweaks and updates to the website here and there.&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;General house tasks and maintenance still consume the majority of this time as I continue to learn how to organize and optimize the work. The main &lt;em&gt;projects&lt;/em&gt; right now involve setting up the gardening for this year, and converting parts of our yard to expand our growing space. I’m thinking about wood chipping the rest of our property once we get the garden established…&lt;/p&gt;
&lt;p&gt;We just returned a few days ago from an amazing trip to Seattle to visit friends.&lt;/p&gt;
&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;I’ve been building a better reading habit over the past year. Here’s what I’ve been working through recently:&lt;/p&gt;
&lt;article&gt;
&lt;h4&gt;Currently Reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/55bf44bb-3258-4f9f-8182-fc15add17777&quot;&gt;Grow Wild: The Whole-Child, Whole-Family, Nature-Rich Guide to Moving More&lt;/a&gt; by Katy Bowma&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/7a8d293a-5738-4a37-b97c-9c261045a425&quot;&gt;Company Of One: Why Staying Small Is the Next Big Thing for Business&lt;/a&gt; by Paul Jarvis&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/b46883af-4662-46cf-b58c-17847d79542d&quot;&gt;Leonardo da Vinci&lt;/a&gt;  by Walter Isaacson (&lt;em&gt;audiobook&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;article&gt;
&lt;h4&gt;Recently Finished&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/5e56f8de-4a4c-4a97-bcdd-db21664be614&quot;&gt;The Almanack of Naval Ravikant: A Guide to Wealth and Happiness&lt;/a&gt; by Eric Jorgenson&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://app.thestorygraph.com/books/85fe7b55-a819-473e-9c10-cf68e8a19b1d&quot;&gt;Only Say Good Things: Surviving Playboy and Finding Myself&lt;/a&gt; by Crystal Hefner (&lt;em&gt;audiobook&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;p&gt;See more of my reading history or connect with me via &lt;a href=&quot;https://app.thestorygraph.com/profile/himmallright&quot;&gt;my story graph profile&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Easily Ejecting macOS Disks</title>
    <link href="https://ryan.himmelwright.net/post/eject-disks-shortcut/" />
    <updated>2025-04-29T16:15:00Z</updated>
    <id>https://ryan.himmelwright.net/post/eject-disks-shortcut/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/eject-disks-shortcut/Ey1HMy2BlA-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/eject-disks-shortcut/Ey1HMy2BlA-1200.jpeg&quot; alt=&quot;A macOS dock with a purple finder icon selected&quot; width=&quot;1200&quot; height=&quot;249&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I can easily ejects my disk by clicking a single dock icon.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I particularly enjoy my laptop setup, which includes connecting it to a dock that supports multiple SSDs. This arrangement streamlines my backup process and provides additional storage space for infrequently used large files like games, local large language models (LLMs), and virtual machine disk images.&lt;/p&gt;
&lt;p&gt;However, I hate how macOS yells at me when I abruptly unplug the dock. It has the right to nag… I wouldn’t want to yank the cord in the middle of a Time Machine backup. Nevertheless, I’m still too lazy to manually eject all the drives each time I disconnect.&lt;/p&gt;
&lt;h1&gt;Solution&lt;/h1&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/eject-disks-shortcut/Ftn-D9GW1P-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/eject-disks-shortcut/Ftn-D9GW1P-1200.jpeg&quot; alt=&quot;An Apple Shortcuts edit window with an apple script for its only command&quot; width=&quot;1200&quot; height=&quot;952&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Editing the shortcut. It just runs the AppleScript.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So, I devised a solution to streamline this process. It’s not original, and in fact, I adapted elements from various online resources to create it. Unfortunately, too much time has passed, and I’ve lost track of the specific sources I used.&lt;/p&gt;
&lt;p&gt;In short, I configured an Apple Shortcut to disconnect known disks from my Mac using Apple Script:&lt;/p&gt;
&lt;pre class=&quot;language-applescript&quot;&gt;&lt;code class=&quot;language-applescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;tell&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Finder&quot;&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;-- Get a list of all ejectable disks&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; ejectableDisks &lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;every&lt;/span&gt; disk &lt;span class=&quot;token keyword&quot;&gt;whose&lt;/span&gt; ejectable &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;true&lt;/span&gt;
 
 &lt;span class=&quot;token comment&quot;&gt;-- Loop through each disk and check its name&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;repeat&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; aDisk &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; ejectableDisks
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; aDisk &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MasterBall&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; aDisk &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PokeBall&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
   eject aDisk
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;repeat&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;tell&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gets a list of ‘ejectable disks’ from Finder&lt;/li&gt;
&lt;li&gt;Iterates through the disk list&lt;/li&gt;
&lt;li&gt;Disconnects disks with names matching those I’ve specified (In my case, &lt;em&gt;MasterBall&lt;/em&gt; and &lt;em&gt;PokeBall&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach ensures the script doesn’t accidentally eject disk images or drives I might want to keep connected once undocked.&lt;/p&gt;
&lt;p&gt;Lastly, I added the shortcut to my dock. Now, I just tap an icon and those drives eject. Easy.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;So far, it has performed excellent. Occasionally, I have to wait a few seconds for it to complete, but that’s likely due to the system waiting for the disks to eject, and isn’t really avoidable.&lt;/p&gt;
&lt;p&gt;I have thought about adding a physical smart button to trigger the script, but the required complexity to accomplish that hasn’t been worth the effort yet 😅 . Right now, this is perfect.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Now Update - 2025-04-24</title>
    <link href="https://ryan.himmelwright.net/now/2025-04-24/" />
    <updated>2025-04-24T14:30:00Z</updated>
    <id>https://ryan.himmelwright.net/now/2025-04-24/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/now/2025-04-24/AVSupuGeII-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/now/2025-04-24/AVSupuGeII-1200.jpeg&quot; alt=&quot;A mix of plants on the ground.&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Lots of photos like this in my photo feed currently as I capture images to identify and log various plants.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’m still settling into a new routine after leaving my full-time job. My days generally involve a few hours of learning or project work over coffee in the morning, followed by gardening and house tasks in the afternoon. When my wife is home (her schedule has been quite busy lately!), we’ve been able to spend some quality time together again.&lt;/p&gt;
&lt;h3&gt;Learning &amp;amp; Projects&lt;/h3&gt;
&lt;p&gt;A few weeks ago, I started to learn some design principles, but quickly realized the best way for &lt;em&gt;me&lt;/em&gt; to learn is by doing. So, I pulled an iOS app I started months ago back of the shelf to tackle. It evolved from bunch of python scripts I’ve used for years, but would prefer in &lt;em&gt;proper&lt;/em&gt; app-form for my phone. It’s still ugly, but it’s slowly looking… better.&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;At home, much of our focus is still on adjusting to my career transition. The main projects right now involve setting up the gardening for this year, and converting parts of our yard to expand our growing space.&lt;/p&gt;
&lt;p&gt;We are also looking forward to a trip to see friends in Seattle WA next week!&lt;/p&gt;
&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;I’ve been building a better reading habit over the past year. Here’s what I’ve been working through:&lt;/p&gt;
&lt;article&gt;
&lt;h4&gt;Currently Reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/37570605-company-of-one&quot;&gt;Company Of One: Why Staying Small Is the Next Big Thing for Business&lt;/a&gt; by Paul Jarvis&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/34752954-leonardo-da-vinci&quot;&gt;Leonardo da Vinci&lt;/a&gt;  by Walter Isaacson (&lt;em&gt;audiobook&lt;/em&gt;)
&lt;ul&gt;
&lt;li&gt;I had to renew it at the library. It’s a long book…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;article&gt;
&lt;h4&gt;Recently Finished&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/54898389-the-almanack-of-naval-ravikant&quot;&gt;The Almanack of Naval Ravikant: A Guide to Wealth and Happiness&lt;/a&gt; by Eric Jorgenson&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/146492158-only-say-good-things&quot;&gt;Only Say Good Things: Surviving Playboy and Finding Myself&lt;/a&gt; by Crystal Hefner (&lt;em&gt;audiobook&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
</content>
  </entry>
  <entry>
    <title>Added &#39;now&#39; and &#39;then&#39; Pages</title>
    <link href="https://ryan.himmelwright.net/post/added-now-then-pages/" />
    <updated>2025-04-08T15:30:00Z</updated>
    <id>https://ryan.himmelwright.net/post/added-now-then-pages/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/added-now-then-pages/NYsTMqQxnb-640.webp 640w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/added-now-then-pages/NYsTMqQxnb-640.jpeg&quot; alt=&quot;A heavily filtered image of a keyboard, notebook, and two computer monitors&quot; width=&quot;640&quot; height=&quot;640&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;An old (filtered) photo of my computer setup in college. I thought it might fit the &#39;now and then&#39; topic...&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I didn’t plan on having back-to-back “I added &lt;em&gt;Y&lt;/em&gt; type of page to the site” announcement posts… but here we are. There’s not much to say for this one, but I’m about to publish the changes and figured I might as well include another short announcement post, similar to when I &lt;a href=&quot;https://ryan.himmelwright.net/post/added-uses-page-archive/&quot;&gt;added the&lt;/a&gt; &lt;a href=&quot;https://ryan.himmelwright.net/uses/archive/&quot;&gt;uses archive&lt;/a&gt; last month.&lt;/p&gt;
&lt;p&gt;I’ve wanted to create a &lt;a href=&quot;https://nownownow.com/about&quot;&gt;now page&lt;/a&gt; for a &lt;em&gt;very long time&lt;/em&gt;. However, I wasn’t really working on anything I wanted to share, and I wanted to implement the uses archive first. With that done, I’ve finally added now pages.&lt;/p&gt;
&lt;p&gt;The implementation is basically the same as the uses pages. I reused the same code from the ‘uses archive’ and created new collection to grab and list the ‘then’ pages:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Then Archive Collection&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;thenArchivePages&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;collectionApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collectionApi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilteredByGlob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src/pages/now/then/*/*.md&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;date&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;date&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(I know this probably still isn’t the best way to do it… but hey, it works)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After that, it was just some minor skeleton work and actually &lt;em&gt;writing the content&lt;/em&gt;. But that was it.&lt;/p&gt;
&lt;p&gt;You can find the &lt;a href=&quot;https://ryan.himmelwright.net/now/&quot;&gt;now page here&lt;/a&gt;, and eventually – once I have more than one entry – the &lt;a href=&quot;https://ryan.himmelwright.net/then/&quot;&gt;then pages will be listed here&lt;/a&gt;. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Now Update - 2025-04-08</title>
    <link href="https://ryan.himmelwright.net/now/2025-04-08/" />
    <updated>2025-04-08T15:00:00Z</updated>
    <id>https://ryan.himmelwright.net/now/2025-04-08/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/now/2025-04-08/kb64Shwvs_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/now/2025-04-08/kb64Shwvs_-1200.jpeg&quot; alt=&quot;A gutter with what looks like yellow paint next to it. It&#39;s actually pollen.&quot; width=&quot;1200&quot; height=&quot;873&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;We&#39;re in the peak of pollen season here in Durham NC, right now.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;About a week ago, I left my job at Red Hat to open up more flexible time for family life  and to grow my skills in various areas. So… right now I’m mostly figuring that all out 😅.&lt;/p&gt;
&lt;h3&gt;Learning/Projects&lt;/h3&gt;
&lt;p&gt;I’m starting out by dedicating some time to learn more about design - both general and specifically UI/UX design. I want to work on some projects, but they all tend to become blocked by design since I have &lt;em&gt;zero&lt;/em&gt; experience in it. So I’m working to fix that.&lt;/p&gt;
&lt;p&gt;I’ve also been editing my website to support now/then pages. I have put it off for some time, until I could safely share that much of what I’ve been working on was related to leaving my job.&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;On the home and family front, I’m setting up an organized calendar/task management system so we can all be (roughly) on the same page. Beyond that, the big timely project I’ve been dragging my feet on is prepping and setting up our garden for this year. We tarped our side yard over the winter, and I also plan to try spreading some wildflowers.&lt;/p&gt;
&lt;p&gt;My family is taking a trip to see friends in Seattle WA at the end of the month, so that’s my next big countdown item.&lt;/p&gt;
&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;I’ve been building a better reading habit over the past year. Here’s what I’ve been working through:&lt;/p&gt;
&lt;article&gt;
&lt;h4&gt;Currently Reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/54898389-the-almanack-of-naval-ravikant&quot;&gt;The Almanack of Naval Ravikant: A Guide to Wealth and Happiness&lt;/a&gt; Eric Jorgenson&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/34752954-leonardo-da-vinci&quot;&gt;Leonardo da Vinci&lt;/a&gt;  by Walter Isaacson (&lt;em&gt;audiobook&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;article&gt;
&lt;h4&gt;Recently Finished&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/197773418-slow-productivity&quot;&gt;Slow Productivity: The Lost Art of Accomplishment Without Burnout&lt;/a&gt; by Cal Newport&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/146492158-only-say-good-things&quot;&gt;Only Say Good Things: Surviving Playboy and Finding Myself&lt;/a&gt; by Crystal Hefner (&lt;em&gt;audiobook&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
</content>
  </entry>
  <entry>
    <title>Added a &#39;uses&#39;  Page Archive</title>
    <link href="https://ryan.himmelwright.net/post/added-uses-page-archive/" />
    <updated>2025-03-16T20:37:00Z</updated>
    <id>https://ryan.himmelwright.net/post/added-uses-page-archive/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/added-uses-page-archive/MWIXL3eDvO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/added-uses-page-archive/MWIXL3eDvO-1200.jpeg&quot; alt=&quot;A stack of MacBooks&quot; width=&quot;1200&quot; height=&quot;790&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A stack of my current and precious MacBooks.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve long wanted to dig through my website’s &lt;code&gt;git&lt;/code&gt; history to unearth some of my old &lt;code&gt;homelab&lt;/code&gt; and &lt;code&gt;/uses&lt;/code&gt; pages to resurrect them on an archive page… &lt;em&gt;someday&lt;/em&gt;. But with discussions about &lt;a href=&quot;https://rknight.me/blog/on-transient-slash-pages/&quot;&gt;transient slash pages&lt;/a&gt; popping up on other blogs lately, I finally decided last week that it was time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One&lt;/em&gt; of my motivations for &lt;a href=&quot;https://ryan.himmelwright.net/post/switched-site-to-eleventy/&quot;&gt;switching to Eleventy&lt;/a&gt; was to make website customization like this easier. So far,  I’m happy to say the switch was worth it, as implementing the archive wasn’t too difficult. I essentially added a subdirectory to store the archived &lt;code&gt;/uses&lt;/code&gt; pages, and defined new collection to grab the pages and populate the the list of a new &lt;a href=&quot;https://ryan.himmelwright.net/uses/archive/&quot;&gt;index page&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// Uses Page Archive Collection&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;usesArchivePages&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;collectionApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collectionApi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilteredByGlob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src/pages/uses/archive/*/*.md&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;date&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;date&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(I know this probably isn’t the best way to do it… I’m still finding my way around eleventy – but it seems to work 😉)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I combed through the history and pulled out a snapshot page from every few months - usually when my setup had changed &lt;em&gt;enough&lt;/em&gt; to justify a new entry. Overall, the selection provides a good enough historical timeline of my setup.&lt;/p&gt;
&lt;p&gt;I plan to follow a similar approach going forward. I won’t spin off every little update into its’ own archive page. However, once enough changes have accumulated - usually when there’s  a new photo, or I’ve swapped out a main device, I’ll make a new entry.&lt;/p&gt;
&lt;p&gt;There’s still more I want to refine, like cleaning up the page formatting, and optimizing duplicate images, but for now it’s good enough.&lt;/p&gt;
&lt;p&gt;Feel free to &lt;a href=&quot;https://ryan.himmelwright.net/uses/archive&quot;&gt;take a look&lt;/a&gt; if interested!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Embracing Notification Center Widgets</title>
    <link href="https://ryan.himmelwright.net/post/embracing-notification-center-widgets/" />
    <updated>2025-03-08T13:40:00Z</updated>
    <id>https://ryan.himmelwright.net/post/embracing-notification-center-widgets/</id>
    <content type="html">&lt;p&gt;When desktop widgets arrived in macOS Sonoma, reviewers were &lt;a href=&quot;https://www.macstories.net/stories/macos-sonoma-the-macstories-review/2/&quot;&gt;generally enthusiastic&lt;/a&gt; about the update. But for me, the excitement quickly faded, as I found that always-visible widgets made my desktop feel cluttered and overwhelming.&lt;/p&gt;
&lt;h2&gt;Desktop Widgets&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/embracing-notification-center-widgets/aZQVOnkX5J-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/embracing-notification-center-widgets/aZQVOnkX5J-1200.jpeg&quot; alt=&quot;Desktop with several widgets to the left, a window on the right&quot; width=&quot;1200&quot; height=&quot;775&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Previously using a few widgets on the desktop.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As a result, I didn’t use widgets much, except for a few on my &lt;em&gt;work&lt;/em&gt; computer. These widgets provided simple, but useful information: the weather, battery life on my devices, and a small calendar displaying my upcoming meetings. However, my personal computer desktop remained widget-free.&lt;/p&gt;
&lt;p&gt;With widgets permanently visible on the left side of my desktop, I noticed a shift in my window management habits. Similar to the behavior changes I observed when &lt;a href=&quot;https://ryan.himmelwright.net/post/large-display-paradox/&quot;&gt;my display was too large&lt;/a&gt;, I subconsciously positioned non-fullscreen windows to the right side of the desktop, or even sized them smaller than I normally would so that the widgets fit nicely next to it. I felt like I always &lt;em&gt;had&lt;/em&gt; to see them, even if they weren’t important to the task at hand.&lt;/p&gt;
&lt;h2&gt;What I want[ed]&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/embracing-notification-center-widgets/IHPxyddqAC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/embracing-notification-center-widgets/IHPxyddqAC-1200.jpeg&quot; alt=&quot;Seven different macOS virtual desktops viewed through Mission Control&quot; width=&quot;1200&quot; height=&quot;779&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;At first, I wished I could define desktop widgets to show on just one of my many virtual desktops.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Initially, I thought the solution was simply a missing feature: Optionally adding widgets to &lt;em&gt;specific&lt;/em&gt; virtual desktop(s). This would let me keep them in a space I already reserve for Activity Monitor and other status apps. Accessible when I needed it, while keeping the other workspaces clean and uncluttered.&lt;/p&gt;
&lt;p&gt;But alas, that’s not how desktop widgets were implemented. So, I carried on, accepting that I simply wouldn’t be satisfied.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sigh&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If only macOS had a &lt;em&gt;central&lt;/em&gt; place for both my &lt;em&gt;notifications and widgets&lt;/em&gt;… one that slid out of sight when I needed to focus (😂).&lt;/p&gt;
&lt;h2&gt;Notification Center&lt;/h2&gt;
&lt;p&gt;One day… I (finally) realized the solution had been sitting there this whole time, hiding just off the edge of my screen.&lt;/p&gt;
&lt;p&gt;While working, I absentmindedly opened Notification Center to clear out message alerts that had piled up over time. After dismissing them, I glanced down to the random assortment of widgets below… and it hit me: &lt;em&gt;“Hey! Widgets…here…”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I’d always known about the widgets in the Notification Center, and even clean up and re-organize the ones on my iPhone and iPad. But on the Mac? I had mostly just ignored them and hadn’t &lt;em&gt;intentionally&lt;/em&gt; thought about curating them, or that they could perfectly replace my desktop widgets.&lt;/p&gt;
&lt;h3&gt;Embracing Notification Center Widgets&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/embracing-notification-center-widgets/zJT-nuYqBt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/embracing-notification-center-widgets/zJT-nuYqBt-1200.jpeg&quot; alt=&quot;A macOS desktop with notification center opened on the side, containing several widgets&quot; width=&quot;1200&quot; height=&quot;1169&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My cleaned up and organized widgets in Notification Center.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I opened up the edit panel and got to work refining my setup. I removed widgets I didn’t need, added ones that I found useful, and positioning them in a way I found pleasing. With macOS able to use widgets from an iPhone, the pool of &lt;em&gt;useful&lt;/em&gt; options was better than ever.&lt;/p&gt;
&lt;p&gt;For the finishing touch, I added a custom keyboard shortcut (&lt;code&gt;CMD&lt;/code&gt; + &lt;code&gt;&#92;&lt;/code&gt;) to quickly toggle Notification Center.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It’s been several weeks since I made this switch and overall, it’s been great. I’ve tweaked my setup here and there as I experiment with different apps, and while I use it more on some days than others, it’s proven to be a fantastic solution. The keyboard shortcut makes all the difference. Being able to press two keys, glance at the info, and then dismiss it has been amazing.&lt;/p&gt;
&lt;p&gt;I’m glad that macOS allows desktop widgets for those that enjoy them. But for my workflow, this is a better fit.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>FYI - Switched Site to Eleventy</title>
    <link href="https://ryan.himmelwright.net/post/switched-site-to-eleventy/" />
    <updated>2025-02-21T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/switched-site-to-eleventy/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-site-to-eleventy/dM4dvFTqYS-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-site-to-eleventy/dM4dvFTqYS-1200.jpeg&quot; alt=&quot;Frozen river in American Tobacco Campus, Durham NC&quot; width=&quot;1200&quot; height=&quot;770&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;American Tobacco Campus, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Just a quick “heads up” post… Last night, I switched my website to a new version that is generated by &lt;a href=&quot;http://11ty.dev&quot;&gt;Eleventy&lt;/a&gt; instead of &lt;a href=&quot;http://gohugo.io&quot;&gt;Hugo&lt;/a&gt;. So, if things look a little “off” compared to before (fonts, spacing, navigation), or if I inadvertently &lt;em&gt;broke&lt;/em&gt; the &lt;a href=&quot;https://ryan.himmelwright.net/pages/feeds/&quot;&gt;RSS feeds&lt;/a&gt;…that’s why. (Sorry!)&lt;/p&gt;
&lt;p&gt;Additionally, while working on the conversion, I decided to switch my web-server from trusty old &lt;a href=&quot;https://nginx.org&quot;&gt;Nginx&lt;/a&gt; to &lt;a href=&quot;http://caddyserver.com&quot;&gt;Caddy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I won’t go into the detailed reasoning behind the switches, but the short version is I’ve been wanting to experiment and play around more with my website - but I often found myself fighting with Hugo. By contrast, Eleventy felt more like a “build from the ground up” system, which better aligns with what I’m aiming for right now.&lt;/p&gt;
&lt;p&gt;As far as the Caddy switch, I really don’t need the power of Nginx for this minimal site. I explored alternatives and thought Caddy seemed like a good match. I like the simplicity managing everything with a single &lt;code&gt;Caddyfile&lt;/code&gt; for configuration. So, I’m trying it out.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;might&lt;/em&gt; publish more detailed posts about the switch in the future, but I wanted to at least get this one out quick in case I &lt;em&gt;did&lt;/em&gt; break anything, or people noticed that things look… &lt;s&gt;worse&lt;/s&gt; &lt;em&gt;different&lt;/em&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Touch ID on the iPad Mini</title>
    <link href="https://ryan.himmelwright.net/post/ipad-mini-touch-id/" />
    <updated>2025-02-03T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/ipad-mini-touch-id/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ipad-mini-touch-id/74vlKijYjm-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ipad-mini-touch-id/74vlKijYjm-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;752&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last year, I sold my &lt;a href=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/&quot;&gt;11&amp;quot; M1 iPad Pro&lt;/a&gt; after experimenting with some &lt;a href=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/&quot;&gt;e-ink devices&lt;/a&gt;.  After some time and a bit of family device shuffling, I’ve found myself rocking an &lt;a href=&quot;https://www.apple.com/ipad-mini/&quot;&gt;iPad mini&lt;/a&gt; for over a month now.&lt;/p&gt;
&lt;p&gt;I often have issues using fingerprint readers over time (my hands peel due to allergies), so FaceID has been a key selling point and motivator for me to get the &lt;em&gt;Pro&lt;/em&gt; line of iPads. However, now that I’ve been using the mini for a while, I’ve realized that in fact, Touch ID may be ideal on a tablet, and the iPad mini &lt;em&gt;in particular&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here are just a few benefits I’ve noticed so far:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Touch ID works more reliably than I expected&lt;/strong&gt;. Nearly every time I touch it, it works. FaceID would often get upset if I didn’t ‘look at it’ correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It functions perfectly in low or even &lt;em&gt;no&lt;/em&gt; light&lt;/strong&gt;.  This is especially useful when reading in bed at night.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By scanning multiple fingers (and possibly your palm), &lt;strong&gt;TouchID works no matter how the device is orientated or how it’s being held&lt;/strong&gt;. By comparison, FaceID  doesn’t work when the camera is accidentally covered or oriented poorly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While I do think FaceID is a fantastic technology, there are still situations where TouchID has its advantages. In a perfect world, we’d have devices with &lt;em&gt;both&lt;/em&gt; methods, but I doubt that will happen due to cost. It would be nice though.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Disabling Safari on iOS and iPadOS</title>
    <link href="https://ryan.himmelwright.net/post/disable-safari-ios-ipados/" />
    <updated>2024-12-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/disable-safari-ios-ipados/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/rxoSdb4EtP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/rxoSdb4EtP-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;855&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Dowdy Park, Nags Head NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Why&lt;/h2&gt;
&lt;p&gt;As an easily distracted person, I sometimes set up limits and blockers on my tech to stay focused on what I’m trying to accomplish. This most commonly takes the form of restricting sites like YouTube and Reddit, or configuring &lt;a href=&quot;https://support.apple.com/guide/mac-help/manage-downtime-in-screen-time-mchl69510069/mac&quot;&gt;downtime settings&lt;/a&gt; when I repeatedly stay up too late.&lt;/p&gt;
&lt;p&gt;Earlier this year, I read &lt;a href=&quot;https://josebriones.org/store/p/low-tech-life-ebook-version&quot;&gt;&lt;em&gt;Low Tech Life&lt;/em&gt; by Jose Briones&lt;/a&gt;, which led me to reflect on my smartphone use. I concluded that a true “dumb phone” wouldn’t be a great solution for me personally. The biggest reason being that I &lt;em&gt;do&lt;/em&gt; want to carry a camera with me, which would mean I’d always have 2 devices instead of one… and currently, my only camera is my iPhone anyway.&lt;/p&gt;
&lt;p&gt;So, I switched to thinking about where it made sense to ‘dumb down’ my smart phone and identify the areas I wanted to limit. I deleted my video apps (YouTube, Plex, Nebula, etc.), and moved on to cleaning up the other apps I don’t use on my phone.&lt;/p&gt;
&lt;p&gt;I don’t use much social media, which is what people typically remove when going through an exercise like this. If not social media, what is the next activity I most often waste time on? Web browsing. It’s the “let me look up this random fact”, or “I want to verify something”, or “I need to search for and re-read the same forum posts about this tech product I’m never going to buy anyway” browsing that gets me.&lt;/p&gt;
&lt;p&gt;But removing a browser can be tricky. And a little scary.&lt;/p&gt;
&lt;h2&gt;How&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/rTprbp_lJD-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/rTprbp_lJD-1200.jpeg&quot; alt=&quot;iPhone frames showing screenshots of the settings menus from the steps below&quot; width=&quot;1200&quot; height=&quot;782&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Disabling Safari in the Screen Time settings.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Disabling Safari on the iPhone and iPad can be done through Screen Time settings. This means that Screen Time must first be enabled and setup. Once it is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open &lt;strong&gt;Settings&lt;/strong&gt; and navigate to the &lt;strong&gt;Screen Time&lt;/strong&gt; Section.&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;Content &amp;amp; Privacy Restrictions&lt;/strong&gt; under the &lt;strong&gt;Restrictions&lt;/strong&gt; section.&lt;/li&gt;
&lt;li&gt;Ensure the &lt;strong&gt;Content &amp;amp; Privacy Restrictions&lt;/strong&gt; toggle is enabled.&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;Allowed Apps &amp;amp; Features&lt;/strong&gt; below the toggle. This will open a list of built-in iOS/iPadOS apps, including Safari.&lt;/li&gt;
&lt;li&gt;To disable Safari, switch the toggle off.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once disabled, Safari should no longer appear as an app to open on the device.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Disclaimer: These steps were accurate when this post was written in December 2024, but may differ in the future.)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Concerns&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/fg9ZQDM9KM-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/fg9ZQDM9KM-1200.jpeg&quot; alt=&quot;An iPad mini Screen showing a web page containing the website of a restaurant, Luna&quot; width=&quot;1200&quot; height=&quot;837&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I was still able to view useful information, like restaurant web pages through in-app browsers. For example, in Google Maps.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After disabling Safari, there were a few concerns I had as I entered this experiment. Most of them revolved around not being able to look up information for “legitimate” uses. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Viewing store or restaurant hours while on the go&lt;/li&gt;
&lt;li&gt;Skimming linked documents in the show notes of a podcast&lt;/li&gt;
&lt;li&gt;Looking up the weather or news while out and about&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fortunately, it turned out that even with the Safari &lt;em&gt;app&lt;/em&gt; disabled, all of these tasks were still possible – through apps. While Safari is disabled, the built-in browser APIs that apps use still worked, &lt;em&gt;most&lt;/em&gt; of the time. To maintain required functionality, use specific apps in place of general Safari searching.&lt;/p&gt;
&lt;h2&gt;Issues&lt;/h2&gt;
&lt;p&gt;I said the built-in browsers work &lt;em&gt;most of the time&lt;/em&gt; because there were a few instances where I hit issues. Sometimes, a link wouldn’t even try to open when I tapped it in an app. I think this happens when an app immediately attempts to handoff a link to Safari, but nothing happens. Here’s two specific examples of this occurring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While trying to open links in an Apple Note. The preview card loads, but the link itself doesn’t open.&lt;/li&gt;
&lt;li&gt;A more concerning case was when connecting to Wi-Fi, where the connection normally directs a user to a captcha page or requires verifying certs that are expected to complete through Safari. I encountered this while attempting to connect to my work network after renewing my access credentials. I had to re-enable Safari to complete the process. I imaging this could be a significant headache in public locations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/1sHY-OWUX_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-safari-ios-ipados/1sHY-OWUX_-1200.jpeg&quot; alt=&quot;An iPad mini screen showing a screen zen window prompting to unlock Safari, and showing a notification for the unlock&quot; width=&quot;1200&quot; height=&quot;837&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I don&#39;t know if I&#39;ll continue this method or try something like ScreenZen. But as a built in method, it works.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Still, issues aside, for the most part, this is exactly what I wanted when I decided to test it. I still had access to most of my &lt;em&gt;non-distracting&lt;/em&gt; uses of a smartphone, without the ability to pull it out of my pocket and start browsing the web whenever I wanted. By intentionally installing apps for the uses I wanted  ahead of time (and optionally using Screen Time to disable installing new ones), I was able to dial in exactly how “dumb” my iPhone should be.&lt;/p&gt;
&lt;p&gt;I think I’ll keep Safari disabled by default, with maybe a few exceptions for trips or vacations. I’m also tempted to re-enable Safari on my iPad Mini… but I’m pretty sure I would just waste time on it if I did. I might be able to find a middle ground, using something like &lt;a href=&quot;http://screenzen.co&quot;&gt;ScreenZen&lt;/a&gt;, but I have yet to fully test it.&lt;/p&gt;
&lt;p&gt;Anyway, I highly recommend giving this a try if you also find yourself living in a browser all the time. There’s nothing to lose; Safari can always be added back if it doesn’t work out.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Switched to Todoist for Personal and Home Tasks</title>
    <link href="https://ryan.himmelwright.net/post/switched-to-todoist-tasks/" />
    <updated>2024-11-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/switched-to-todoist-tasks/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/aSvkcT_p0D-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/aSvkcT_p0D-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;809&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;P!NK Concert - Lenovo Center, Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;It’s been just shy of a year since I setup &lt;a href=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/&quot;&gt;the obsidian-tasks plugin&lt;/a&gt; to manage tracking my events and tasks. However, I recently switched to using &lt;a href=&quot;http://todoist.com&quot;&gt;Todoist&lt;/a&gt; for my personal and home task management.&lt;/p&gt;
&lt;h2&gt;Why the switch?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/LsvLzomU3H-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/LsvLzomU3H-1200.jpeg&quot; alt=&quot;A todoist task window, showing a task for this post with subtasks&quot; width=&quot;1200&quot; height=&quot;775&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The better support for sub-tasks is what initially prompted the switch from ticktick to todoist.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In that obsidian tasks post, I hinted at trying to use a ‘family’ obsidian vault for notes/planning/tasks. It didn’t work out. In the end, I was the only one that used it.&lt;/p&gt;
&lt;p&gt;Defeated, I moved our family tasks and projects back into &lt;a href=&quot;http://ticktick.com&quot;&gt;ticktick&lt;/a&gt;. As my wife and I tried to more consistently use ticktick, I worked to restructure our shared list to support not only general tasks, but organized projects as well. Unfortunately, ticktick just didn’t seem to support a structure that made sense for what I wanted. In particular, it had weird limitations with nested tasks.&lt;/p&gt;
&lt;p&gt;Researching work-arounds, I started to see evidence that &lt;a href=&quot;http://todoist.com&quot;&gt;todoist&lt;/a&gt; might actually be a better fit for our use. To test the idea, I spun up a proof-of-concept project… and it worked. So, only a day or two after moving &lt;em&gt;all of our family tasks&lt;/em&gt; from Obsidian -&amp;gt; TickTick… I did it all again, this time from TickTick -&amp;gt; Todoist 😅.&lt;/p&gt;
&lt;p&gt;After moving the family tasks, I went ahead and converted my personal ones as well. I had already been debating splitting out my &lt;em&gt;actionable&lt;/em&gt; tasks items from obsidian into TickTick for a few reasons anyway. First, I used TickTick to track my general one-off and home tasks, and Obsidian was used for project items, but some other general tasks. There wasn’t a clear line of when to put a task in one verses the other. This made it a pain to keep everything in order, and even caused duplication.&lt;/p&gt;
&lt;p&gt;Beyond that, with so many tasks built up over the past year, Obsidian was becoming quite sluggish. It was frustrating to work in, and multiplied the time required when planning in my notes. I knew if I could phase out the majority of the tasks items it was calculating, it should help with performance.&lt;/p&gt;
&lt;h2&gt;My Todoist Setup&lt;/h2&gt;
&lt;p&gt;As stated above, I started with moving over the family stuff first, and then almost immediately converted my personal tasks as well.&lt;/p&gt;
&lt;h3&gt;Home Workspace&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/-P-Jw_XzyO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/-P-Jw_XzyO-1200.jpeg&quot; alt=&quot;A Todoist window showing the home tasks project&quot; width=&quot;1200&quot; height=&quot;775&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Our HomeTasks Project, and the sections we group tasks into.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I created a shared workspace for our home, with two projects in it: &lt;code&gt;HomeTasks&lt;/code&gt; and &lt;code&gt;HomeProjects&lt;/code&gt; (I also later added a &lt;code&gt;Home_Inbox&lt;/code&gt; to dump rough task ideas).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;HomeTasks&lt;/code&gt; project is a list view that contains general family tasks. For example, taking out the trash, daycare drop off, bathing the dog, etc. It is divided into sections representing different family areas (house, pets, kid, etc). This organizes the tasks into their domain, for easy viewing&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HomeProjects&lt;/code&gt; is a board view containing different ‘Project’ task cards, organized in columns designating project status (ex: idea, planning, in progress, on hold, done). Each card represents a larger family project that contains many sub (or even sub-sub…) tasks for it.&lt;/p&gt;
&lt;h3&gt;Personal Workspace&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/33mjTypebn-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/33mjTypebn-1200.jpeg&quot; alt=&quot;A todoist window showing my recurring habits&quot; width=&quot;1200&quot; height=&quot;775&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My todoist habits project contains my common recurring tasks.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My personal setup is similar to the family one. I have a &lt;code&gt;Tasks&lt;/code&gt; project list that contains my general, non-project tasks that I dump into the system.&lt;/p&gt;
&lt;p&gt;I also have &lt;em&gt;my own&lt;/em&gt; &lt;code&gt;RyanProjects&lt;/code&gt; board, containing ‘project tasks’ (this post, for example) that move across the various board states. The project parent task contains all the relevant sub-tasks related to it.&lt;/p&gt;
&lt;p&gt;Compared to the Home workspace, I have an extra todoist project called &lt;code&gt;Habits&lt;/code&gt;. It started as a replacement for the excellent &lt;a href=&quot;https://help.ticktick.com/articles/7055781878401335296&quot;&gt;Habits Tracker in TickTick&lt;/a&gt;, but didn’t live up to my expectations for that role. So, I moved the tracking logs back to the goal notes I keep in Obsidian. The Todoist project remained, and now houses my daily/weekly recurring tasks, broken into sections, roughly grouping when each one occurs.&lt;/p&gt;
&lt;h3&gt;Filters&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/2MmPX45L2c-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/2MmPX45L2c-1200.jpeg&quot; alt=&quot;A todoist window showing the filters and tags pane&quot; width=&quot;1200&quot; height=&quot;775&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I have a handful of filters, 3 of which I have favorited.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So far, I have just a few filters I rely on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Remaining Habits&lt;/strong&gt;: Un-completed &lt;em&gt;Habit&lt;/em&gt; tasks that remain for the day,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overdue&lt;/strong&gt;: Un-completed tasks that are past their due date/time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Today’s assigned tasks&lt;/strong&gt;: Tasks for the given day that have an assignee. This lets me check if there is anything assigned to my wife (which is usually filtered out in my other views) that I might need to re-assign to myself.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I expect that as I become more familiar Todoist’s capabilities, I will continue to create more filters as needed. I also haven’t setup any labels, &lt;em&gt;yet&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/lk5vPv42Ts-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-todoist-tasks/lk5vPv42Ts-1200.jpeg&quot; alt=&quot;A window showing the TickTick habit tracker&quot; width=&quot;1200&quot; height=&quot;888&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;While I enjoy todoist, I will miss the habit tracker in TickTick.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So far, I’m happy with this new setup. I still use Obsidian with the obsidian-tasks plugin for my task manager at work, but it makes more sense there as my work-vault is isolated. For my very integrated personal and home life however, I think this switch has introduced some appreciated clarity. My current organizational system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Notes/Journal/Logs:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Personal: Obsidian&lt;/li&gt;
&lt;li&gt;Family: Shared Apple Notes Folder&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task Management:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Both: Todoist (With my personal workspace and a shared family workspace)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Events&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Both: Calendars (My wife and I each have one we share with each other, and I subscribe to some additional ones for extra information)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, I love Todoist’s flexibility, integrations, and efficiency. There are a few features I miss from TickTick, mostly the habit tracking. However, I’m glad to have some separation in my systems to re-enforce clear definitions of what I consider a task vs. event. vs. note. We’ll see how long it lasts this time 😆…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Thoughts on &#39;Portability&#39;</title>
    <link href="https://ryan.himmelwright.net/post/thoughts-on-portability-2024/" />
    <updated>2024-10-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/thoughts-on-portability-2024/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/bO3E86uNxq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/bO3E86uNxq-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Nags Head, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I find it interesting that reviews for tech products with two size options (ex: 14&amp;quot; vs 16&amp;quot; MacBook Pros), will often take a somewhat lazy approach when comparing the two variants. Usually, the argument is boiled down to “the smaller device is &lt;em&gt;‘more portable’&lt;/em&gt; but the bigger one, ‘has a larger screen, which is great for &lt;em&gt;multi-tasking&lt;/em&gt;’. I guess both statements are technically &lt;em&gt;true&lt;/em&gt;, but they assume “&lt;em&gt;portability&lt;/em&gt;” and &lt;em&gt;&amp;quot;multi-tasking&lt;/em&gt;” are universal, constant, traits that match every person and use case. Worse, I think this quick passing remark prevents consumers from digging a little deeper into how they will &lt;em&gt;actually&lt;/em&gt; use a device once they obtain it.&lt;/p&gt;
&lt;h2&gt;Thoughts&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/0iHN5bi6Hu-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/0iHN5bi6Hu-1200.jpeg&quot; alt=&quot;A laptop on a table, with an added portable monitor in stand, keyboard and mouse&quot; width=&quot;1200&quot; height=&quot;1000&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My 14-inch M1 Pro setup on Vacation in 2023. Despite being the 14-inch, it doesn&#39;t seem very portable...&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After several years using a &lt;a href=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/&quot;&gt;mix&lt;/a&gt; of &lt;a href=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/&quot;&gt;14&amp;quot;&lt;/a&gt; and &lt;a href=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/&quot;&gt;16&amp;quot;&lt;/a&gt; MacBooks Pros,  two 13&amp;quot; &lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/&quot;&gt;MacBook Airs&lt;/a&gt;, sampling my wife’s 15&amp;quot; MacBook Air, and a smattering of &lt;a href=&quot;https://ryan.himmelwright.net/post/my-t470/&quot;&gt;all&lt;/a&gt; &lt;a href=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/&quot;&gt;sizes&lt;/a&gt; of &lt;a href=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/&quot;&gt;laptops&lt;/a&gt; and &lt;a href=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/&quot;&gt;tablets&lt;/a&gt; before that, I’ve developed a couple of opinions about device sizes, that might seem counter-intuitive.&lt;/p&gt;
&lt;h3&gt;‘Multi Tasking’ and Screen Size&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/AXV7onwbqI-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/AXV7onwbqI-1200.jpeg&quot; alt=&quot;A cramped Xcode window sized roughly for a MacBook Air resolution&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;While doable for quick fixes, Xcode feels cramped on smaller laptop screens.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first lesson, is that the screen size requirement for “multi tasking” is heavily dependent on the workflow and tooling of a given task. Specifically, screen size only really matters when considering what needs to be displayed on the screen concurrently This may be all the panels and menus of a complex application UI, or the simultaneous use of many simple ones.&lt;/p&gt;
&lt;p&gt;For instance, I prefer a smaller screen when I am writing a post, as it helps keep my focus on the task at hand. However, I wouldn’t consider that process ‘mono-tasking’. I’m often flipping between windows writing the draft, editing photos, grabbing source links, checking words in the dictionary, etc. However, none of those tasks require an application with an extensive UI, and I don’t need to see them all at the same time.&lt;/p&gt;
&lt;p&gt;By contrast, when I’m coding using a heavier IDE like &lt;a href=&quot;https://developer.apple.com/xcode/&quot;&gt;Xcode&lt;/a&gt;, the UI of that &lt;em&gt;single&lt;/em&gt; application contains many sections I like to have open (the code editor, simulator preview pane, debugger pane, file tree, etc). This makes my 16&amp;quot; MacBook Pro &lt;em&gt;much&lt;/em&gt; more suited for the job. It should be noted that I didn’t simply state ‘the 16&amp;quot; is better at &lt;em&gt;coding&lt;/em&gt;’&#39;. If I’m coding some Clojure in Neovim, there’s not &lt;em&gt;much&lt;/em&gt; of a difference between sizes.&lt;/p&gt;
&lt;h3&gt;‘Portability’ Preferences&lt;/h3&gt;
&lt;p&gt;Portability can refer to a lot of things. Does it imply the ability to pull a device out of a bag with one hand to check a few emails at the airport or a coffee shop? Or does it represent a computer that travels in a suitcase, and is then setup on a desk to complete a week’s worth of full days while working from a remote office on a business trip? Both cases require a “portable” machine, but my recommendations would differ greatly for each of these roles.&lt;/p&gt;
&lt;p&gt;The best example I’ve heard discussing this difference is from &lt;a href=&quot;https://marco.org&quot;&gt;Marco Arment&lt;/a&gt; on the &lt;a href=&quot;https://atp.fm&quot;&gt;ATP podcast&lt;/a&gt;, when he describes living both the “laptop-laptop” and “desktop-laptop” lifestyle (a setup I also have…).&lt;/p&gt;
&lt;p&gt;The “&lt;em&gt;laptop-laptop&lt;/em&gt;” is a small computer to be used as a typical laptop, for “laptop-things”. This can be around the house for small tasks, when frequently moving from meeting room to meeting room at the office, or thrown in a bag for quick trips. It’s the grab and go computer.&lt;/p&gt;
&lt;p&gt;The “&lt;em&gt;desktop-laptop&lt;/em&gt;” by contrast, lives most of its life indistinguishable from a desktop computer. It remains at a desk, often tethered to an external monitor, mouse (trackball), keyboard, audio equipment, external hard drives, and other peripheral devices. However, it also functions as an all-in-one portable workstation, containing everything required (or close enough) to work when relocated away from that desk.&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/e4d_0q_Trt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/e4d_0q_Trt-1200.jpeg&quot; alt=&quot;a 16 inch MacBook Pro on a table&quot; width=&quot;1200&quot; height=&quot;927&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My setup on vacation in 2024. While the larger 16inch, I didn&#39;t feel as though I *needed* all the other stuff given the larger screen.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For example, let’s compare my computer setup during two back-to-back years of my family October vacation. On both, I planned to (and did) work on some projects. Last year, I worked on the massive &lt;a href=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/LINK&quot;&gt;Website Redo (2023)&lt;/a&gt;, and this year, a few programming projects, as well as starting &lt;em&gt;this&lt;/em&gt; post.&lt;/p&gt;
&lt;p&gt;Last year, I still had my &lt;a href=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/LINK&quot;&gt;14&amp;quot; M1 Pro MacBook Pro&lt;/a&gt;. However, given its smaller size, I felt I would need more periphery items for the setup to work comfortably. While I didn’t &lt;em&gt;need&lt;/em&gt; to go as far as bringing the mouse and &lt;a href=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/&quot;&gt;ferris sweep&lt;/a&gt;, once you pack a portable monitor and stand for the additional screen space, the other devices snowball quickly.&lt;/p&gt;
&lt;p&gt;By comparison, after switching to the &lt;a href=&quot;https://ryan.himmelwright.net/post/thoughts-on-portability-2024/LINK&quot;&gt;16&amp;quot; M3 Pro MacBook Pro&lt;/a&gt; as my main device this year, I didn’t feel I needed to bring everything else to get work done. The 16&amp;quot; MacBook Pro works great as a full &lt;em&gt;&lt;strong&gt;portable&lt;/strong&gt;&lt;/em&gt; (there’s that word again) workstation. I still have the option to bring the extra screen, keyboard, etc (and I might have considered it if I was working full days, or for more than a week), but I was fine without it, whereas the 14&amp;quot; felt cramped. Despite being the larger laptop, in the end the 16&amp;quot; setup took up much less volume in my bag.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hopefully, this didn’t sound &lt;em&gt;too&lt;/em&gt; much like a rant. With endless advice following the “portable vs good for multi-tasking” template, I wanted to put something out there to counter it. I’ve formed these thoughts through much trial and error, and they still continue to evolve over time.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>macOS Sequoia iPhone Mirroring</title>
    <link href="https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/" />
    <updated>2024-09-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/jYteRx4exh-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/jYteRx4exh-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Portland, ME&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With &lt;a href=&quot;https://www.apple.com/newsroom/2024/09/macos-sequoia-is-available-today/&quot;&gt;macOS 18 - Sequoia&lt;/a&gt; being released to the general public, I decided to finally update my MacBooks to it. As &lt;a href=&quot;https://www.macstories.net/stories/macos-sequoia-the-macstories-review/&quot;&gt;reviews&lt;/a&gt; indicate, with the focus largely on &lt;a href=&quot;https://www.apple.com/apple-intelligence/&quot;&gt;Apple Intelligence&lt;/a&gt; this year (&lt;em&gt;still to be released&lt;/em&gt;), there are few major items in the… &lt;em&gt;un-intelligent?&lt;/em&gt;… aspects of Sequoia. However, there is one feature I was interested in when announced at &lt;a href=&quot;https://developer.apple.com/news/?id=lfufk91n&quot;&gt;WWDC&lt;/a&gt;, and have enjoyed even more now that I have my hands on it: iPhone Mirroring.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What is iPhone Mirroring?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/NZG8grnBgq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/NZG8grnBgq-1200.jpeg&quot; alt=&quot;A screenshot of this post&#39;s draft and the iPhone Mirroring App opened on the desktop&quot; width=&quot;1200&quot; height=&quot;780&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;iPhone mirroring passes my phone&#39;s audio through my MacBook speakers. This enables me to listen to iOS only applications on my Mac. For example, the Apple Classical Music App.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As part of the &lt;a href=&quot;https://www.apple.com/macos/continuity/&quot;&gt;“continuity” features&lt;/a&gt; in the Apple Ecosystem, iPhone mirroring allows the user to access and control their iPhone via a macOS application. This is comparable to how the “Screen Sharing” app enables remotely controlling other Macs on the network. While connected, the phone remains locked, but can be fully interacted with through the app.&lt;/p&gt;
&lt;h2&gt;What I Like&lt;/h2&gt;
&lt;p&gt;Similar to the screen sharing updates released last year, iPhone mirroring has been implemented quite well. In particular, there are a few details that I didn’t expect, but am delighted to see.&lt;/p&gt;
&lt;h3&gt;Audio Passthrough&lt;/h3&gt;
&lt;p&gt;First, iPhone audio is passed through to the MacBook. While this is pretty obvious, the results are wonderful. As an example, this allows using iOS audio apps, like &lt;a href=&quot;http://overcast.fm&quot;&gt;Overcast&lt;/a&gt; or Apple Music Classical, “on the Mac” through its &lt;em&gt;much&lt;/em&gt; better speakers. It also nullifies common issues I experience with sync. I can listen to a podcast through my computer speakers, and then simply continue on the phone when I get up and walk away. It’s the same device controlling the audio, so syncing is not required.&lt;/p&gt;
&lt;h3&gt;Keyboard Shortcuts&lt;/h3&gt;
&lt;p&gt;A second nicety are the keyboard shortcuts. They cover common actions that are typically completed using swipe gestures on iOS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CMD&lt;/code&gt; + &lt;code&gt;1&lt;/code&gt;: Navigates to the Home Screen&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CMD&lt;/code&gt; + &lt;code&gt;2&lt;/code&gt;: Opens the app switcher&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CMD&lt;/code&gt; + &lt;code&gt;3&lt;/code&gt;: Launches a spotlight search&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, there’s &lt;code&gt;CMD&lt;/code&gt;+&lt;code&gt;+&lt;/code&gt;/&lt;code&gt;CMD&lt;/code&gt;+&lt;code&gt;-&lt;/code&gt; to increase/decrease the window size, as well as the usual edit/window shortcuts you know and love (cut, copy, paste, etc).&lt;/p&gt;
&lt;h3&gt;Performance&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/-ppL14VK0D-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/macos-sequoia-iphone-mirroring/-ppL14VK0D-1200.jpeg&quot; alt=&quot;A screenshot of an Xcode project and the iPhone Mirroring App opened on the desktop&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;iPhone Mirroring is useful for debugging &#39;on-device&#39; in Xcode.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, in my experience the performance has been great. Beyond improving user experience, the responsiveness encourrages other useful workflows. For example, this makes on-device testing in Xcode much nicer. You can build a project on the device, but test and debug it in the iPhone Mirror, essentially treating it as a better version of the iOS simulator.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While it’s still early days, I have enjoyed iPhone mirroring in macOS Sequoia. Like the screen sharing app, I imagine it will become a staple in my lazy toolbox, powering the use of all my devices from a single computer.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Obsidian Tasks Plugin - Events</title>
    <link href="https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/" />
    <updated>2024-08-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/tPMF0sFwnr-959.webp 959w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/tPMF0sFwnr-959.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;959&quot; height=&quot;683&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Bar Harbor, ME&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Earlier this year, I wrote about how I &lt;a href=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/&quot;&gt;started using the obsidian tasks plugin&lt;/a&gt; to manage my tasks, in both my work and personal &lt;a href=&quot;https://obsidian.md&quot;&gt;obsidian&lt;/a&gt; vaults. One topic I didn’t discuss in that post, is how I also track “&lt;em&gt;events&lt;/em&gt;” as tasks in my work vault.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Why&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/ZIBnzEwHW1-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/ZIBnzEwHW1-1200.jpeg&quot; alt=&quot;An obsidian note for a person named &#39;Jane Doe&#39;. There is a task list and a set of 1x1 meeting logs.&quot; width=&quot;1200&quot; height=&quot;1033&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I like to organize my meeting events in a log for that note.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After I switched to having each ‘task’ living in its related obsidian note, I wanted to also have &lt;em&gt;scheduled/timed&lt;/em&gt; items (events) housed there too. For example, I could make a note for each of my recurring meetings, and have a recurring task setup in it. This would allow me to see meetings listed in my daily/weekly notes, and remind me of them while planning out my work. Additionally, as I completed the recurring task over time, it would serve as a log of when I actually had each meeting, in the note for that meeting.&lt;/p&gt;
&lt;p&gt;This desire increased when I switched into a manager role, and the majority of my days became littered with meetings and 1x1s.&lt;/p&gt;
&lt;h2&gt;How&lt;/h2&gt;
&lt;p&gt;After initially exploring the possibility of defining multiple global task filters, I opted for an equally simple solution instead. Short answer: &lt;em&gt;regex&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Long answer: I defined a convention that if tasks started with &lt;code&gt;(HH:MM)&lt;/code&gt;, I would consider them to be events. Additionally, when I use a time of &lt;code&gt;(00:00)&lt;/code&gt;, I consider it an “all day” event. With the format decided, I was able to use regex expressions in the  filters of my tasks boards. Specifically, I used the following line to either include or exclude tasks that match this format (match in this example):&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;description regex matches /&#92;(&#92;d{2}&#92;:&#92;d{2}&#92;)/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I modified the &lt;a href=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/&quot;&gt;callout&lt;/a&gt; task boards in my daily and weekly note templates to split out “event” tasks from my normal “todo” ones. This makes it easier to see all my scheduled events without them getting buried in my normal task list. Additionally, since the naming convention &lt;em&gt;starts&lt;/em&gt; with  the time (in 24 hour format), I can use &lt;code&gt;sort by description&lt;/code&gt; to ensure the events are in chronological order:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!hint]+ Meetings &amp;amp; Events
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```tasks
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;(scheduled on {{date:YYYY-MM-DD}}) OR (due on {{date:YYYY-MM-DD}})
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;description regex matches /&#92;(&#92;d{2}&#92;:&#92;d{2}&#92;)/
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;short mode
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide tags
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide task count
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;sort by description
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was also able to apply this filter in the “Todo” and “Completed” tasks lists to remove the event tasks, since they are displayed in their own list:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!todo]+
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```tasks
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;not done
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;(scheduled on {{date:YYYY-MM-DD}}) OR (due on {{date:YYYY-MM-DD}})
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;description regex does not match /&#92;(&#92;d{2}&#92;:&#92;d{2}&#92;)/
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;short mode
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide tags
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide task count
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;group by filename
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;sort by priority
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/Yd51oOl5tF-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-tasks-plugin-events/Yd51oOl5tF-1200.jpeg&quot; alt=&quot;An obsidian note display several callout views with tasks in them. They&#39;re separated into meetings in one and tasks in the other.&quot; width=&quot;1200&quot; height=&quot;1064&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I wanted to have timed events show in my daily/weekly notes, separate from my todo list.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve been using this setup at work for probably 6 months now, and it’s worked extremely well. I have a note for each recurring meeting, and I added ‘Meeting Log’ sections to my people notes to track individual 1x1s with them. When I have a working group or one-off meeting related to a particular project, I just make the event in that note. This continues the benefit of having project notes tracking all their stuff, so it can be easily found in one place.&lt;/p&gt;
&lt;p&gt;In my personal vault, I use the same naming conventions for events, but haven’t updated my note filters to split them out, as I don’t use as many timed event tasks in that vault currently.&lt;/p&gt;
&lt;p&gt;Still, if you have a need to track or manage ‘events’ in the tasks plugin, this method works surprisingly well.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Boox Page Initial Thoughts</title>
    <link href="https://ryan.himmelwright.net/post/boox-page-initial-thoughts/" />
    <updated>2024-07-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/boox-page-initial-thoughts/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/1y6R0wPrxt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/1y6R0wPrxt-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;941&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the last few years, I have periodically wanted to replace my old kindle paperwhite. To mix things up (and for the good library support at the time), I planned to switch to a &lt;a href=&quot;https://www.kobo.com/&quot;&gt;kobo&lt;/a&gt; device. But, I never got one. During that same period, I also became increasingly interested in e-ink technology, and the android e-ink tablet market. After several months researching devices… I bought one.&lt;/p&gt;
&lt;h2&gt;Why the Page?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/XBYBnb1x8x-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/XBYBnb1x8x-1200.jpeg&quot; alt=&quot;A picture of an 11&quot; iPad=&quot;&quot; Pro=&quot;&quot; in=&quot;&quot; the=&quot;&quot; Magic=&quot;&quot; Keyboard=&quot;&quot; Case&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;1048&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The larger e-ink tablet use cases overlapped too much with the 11&quot; iPad Pro I already have.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Boox Tab Ultra C&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://shop.boox.com/collections/all/products/tabultrac&quot;&gt;Boox Tab Ultra C&lt;/a&gt; with its optional keyboard case, is what initially caught my attention. It was a small, e-ink tablet computer that seemed perfect to me at first. However, once I started seriously considering purchasing a device, I admitted I didn’t need to jump right to the high end. Being my first e-ink &lt;em&gt;tablet&lt;/em&gt; device, this was  especially true. What if I hated the experience? On top of that, much of the ideal use cases of the Tab Ultra C overlapped with the &lt;a href=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/&quot;&gt;2021 11&amp;quot; iPad Pro&lt;/a&gt; and &lt;a href=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/&quot;&gt;iPad magic keyboard&lt;/a&gt; that I already have. So I moved on.&lt;/p&gt;
&lt;h3&gt;Boox Note Air 3 ©&lt;/h3&gt;
&lt;p&gt;Next, I looked at the &lt;a href=&quot;https://shop.boox.com/collections/all/products/noteair3&quot;&gt;Boox Note Air 3&lt;/a&gt;, which seems to be considered the best all around device for most people. While researching the Air models, I realized I would likely prefer a black and white device instead of color. I wanted the clearest text possible, and to not &lt;em&gt;need&lt;/em&gt; a backlight most of the time. However, the current color eink screens are apparently dimmer and not as sharp compared to black and white.&lt;/p&gt;
&lt;p&gt;During this research period, I started using my &lt;em&gt;very old&lt;/em&gt; kindle paperwhite, just to confirm my desire for e-ink and to verify I would use it day to day. As interested as I was in larger tablets, the more I used the smaller kindle, the more I realized what I actually &lt;em&gt;needed&lt;/em&gt; was an e-reader. Something lightweight that I can grab and read on the go. So the practical choice would be to get a reader that I could also use to experiment with android on e-ink first. If I liked it, I could look into larger devices in the future if it made sense. (Also, my wife has been interested in how this goes for me, as she’s picked up reading again and would gladly accept a hand-me-down e-reader eventually 😂).&lt;/p&gt;
&lt;h3&gt;Boox Page&lt;/h3&gt;
&lt;p&gt;So, I started looking at 7” options. This was right when Boox announced the “go” series devices and while the go color 7 looked nice… I really didn’t want color (the go 10.3 however… I might still have my eye on it…). I also looked at the Mini C which was a good writing + reading option. Once again though… I wanted B+W e-ink, and the slightly smaller size of the page made it more likely to fit that handheld size I wanted. Apart from being an older device and running a slightly older version of Android, the Page had everything else I wanted: a Black and White 300ppi display, a front light in case I needed it, handheld size, and even page turn buttons. Buttons weren’t a requirement for me, but having never used them, it was something I was interested in trying.&lt;/p&gt;
&lt;p&gt;After a quick scramble researching all the competitor devices, I ordered a &lt;a href=&quot;https://shop.boox.com/collections/all/products/page?_pos=5&amp;amp;_fid=4b976ee1b&amp;amp;_ss=c&quot;&gt;Boox Page&lt;/a&gt; from BestBuy.&lt;/p&gt;
&lt;h2&gt;Initial Thoughts&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/o6MHLkm9rz-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/o6MHLkm9rz-1200.jpeg&quot; alt=&quot;The book page on a stand displaying a picture of a dog&quot; width=&quot;1200&quot; height=&quot;977&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I like to use the Boox Page as a photo frame when it&#39;s in standby.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When the Boox Page arrived, I set it up and started playing around. It took some time to figure out the configuration and my preferences for everything, but I eventually got it all dialed in. During the process, I periodically jotted down some notes about experience:&lt;/p&gt;
&lt;p&gt;The First Day:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The screen is (physically) smoother than I thought it would be. I’m sort of sad by this as I’ve loved the matte feel of the kindle the last few days.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I’m going to want a case… I don’t want the screen breaking when I throw it in my bag or walk around with it outside of the house.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refreshing the screen takes some getting used to, but going okay so far…&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Typing in Obsidian on it with the &lt;a href=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/&quot;&gt;ZSA Voyager Keyboard&lt;/a&gt; was actually nice. I thought the refresh lag would be unbearable, but I find it manageable.&lt;/p&gt;
&lt;p&gt;(The most annoying thing while typing is not being able to use the MacOS/iOS (emacs) keyboard navigation I love and am used to 😅).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Second Day:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;I ordered a case. Not sure when it will come.&lt;/li&gt;
&lt;li&gt;I managed to change how the page turn button taps and long holds work, which makes a huge difference in usability. I have a long hold on the top one set to open the multi-tasking view, and the bottom one to trigger a full screen refresh (to clear ghosting).&lt;/li&gt;
&lt;li&gt;I added some of my images for the screensavers, and it’s amazing. I like placing the Boox Page on a stand under the light on my desk when not in use. It’s basically a black and white photo frame. Delightful.&lt;/li&gt;
&lt;li&gt;I configured a few other apps, including setting up f-droid and getting termux light mode setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Several days later:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Things are going better with it. I am experimenting with different ways to be able to read on it, but have my progress sync with my other devices.&lt;/li&gt;
&lt;li&gt;I played with typing on it more using several keyboards.&lt;/li&gt;
&lt;li&gt;Battery life overall seems good.&lt;/li&gt;
&lt;li&gt;Honestly, I love e-ink…&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Summary thoughts/conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/QsZBY3nRic-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/boox-page-initial-thoughts/QsZBY3nRic-1200.jpeg&quot; alt=&quot;A boox Page on a stand, with a keyboard&quot; width=&quot;1200&quot; height=&quot;946&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Boox Page is a great e-reader, but also a way for me to experiment with e-ink.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Overall, I’ve been very happy with the purchase. The size is ideal to use as an e-reader, but yet large enough for me to experiment with using e-ink in different scenarios. And I love e-ink. It’s such a cool technology - a blend of our digital tech, but more adjacent to the analog world, with it’s magical ink pages that don’t require a backlight (or power once set) to read. It’s really cool.&lt;/p&gt;
&lt;p&gt;Anyway, I’m glad I went with the Page, as I think it was a perfect entry device into this world for me, but also filled a gap I had in my tech setup. I love reading on it, and hope to continue experimenting.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Started Browsing Geminispace</title>
    <link href="https://ryan.himmelwright.net/post/started-browsing-geminispace/" />
    <updated>2024-06-29T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/started-browsing-geminispace/</id>
    <content type="html">&lt;p&gt;Over the past year, I’ve been intrigued by the “small web” and have been trying to spend more of my online time there. I touched on this a little bit in my website redo 2023 post [1].&lt;/p&gt;
&lt;p&gt;While browsing some blogs in a web-ring recently, I noticed a few of them referenced and linked to ‘gemlogs’. I decided to dig a little deeper, and watched a few videos about gemini [3] [4]. One of them also talked about gopher, which I had heard about but never used.&lt;/p&gt;
&lt;p&gt;After these videos, I still wasn’t too interested in gopher… However, I DID want to look into Gemini. So, I downloaded the Lagrange gemini client, and started browsing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[1] &lt;a href=&quot;https://ryan.himmelwright.net/post/website-redo-2023/&quot;&gt;Website Redo 2023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[2] &lt;a href=&quot;https://en.wikipedia.org/wiki/Gopher_(protocol)&quot;&gt;Wikipedia: Gopher (protocol)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[3] &lt;a href=&quot;https://www.youtube.com/watch?v=LO2u4NR7PJA&quot;&gt;The Gemini Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[4] &lt;a href=&quot;https://www.youtube.com/watch?v=I2Q35uFCq8Q&quot;&gt;2022 - Rocking the Web Bloat: Modern Gopher, Gemini and the Small Internet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;So what is gemini?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-browsing-geminispace/iJBx2t6zqP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-browsing-geminispace/iJBx2t6zqP-1200.jpeg&quot; alt=&quot;Lagrange Gemini Client&quot; width=&quot;1200&quot; height=&quot;964&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Lagrange Gemini Client&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Gemini is a newer internet protocol, that serves as an alternative to the prevalent http(s). The project was started in June 2019, and really took off during COVID (I’m a little late to the party). It embraces the text-focused aspects Gopher, while improving the security and ease of use to more modern standards. Gemini approaches the internet with a  “keep it simple” and “less is enough” guiding philosophy. To learn more, the project has a wonderful FAQ [5].&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[5] &lt;a href=&quot;https://geminiprotocol.net/docs/faq.gmi&quot;&gt;Gemini FAQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How does it differ from the web?&lt;/h2&gt;
&lt;p&gt;The obvious main difference between gemini and the standard http(s) web is that gemini is a simplified protocol, by design. So what does this mean in actual use?&lt;/p&gt;
&lt;p&gt;To start, nothing is embedded in-line. That means no text formatting  (i.e. bold, italic, strikethrough), in-line code snippets, or links within the text. Everything takes place on its own line, and as result, the purpose of each line can be determined from the first 3 characters. In total, there are 6 types of lines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Text&lt;/li&gt;
&lt;li&gt;Link&lt;/li&gt;
&lt;li&gt;Heading&lt;/li&gt;
&lt;li&gt;List&lt;/li&gt;
&lt;li&gt;Quote&lt;/li&gt;
&lt;li&gt;Preformat toggle (code blocks)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“Gemtext” syntax [6] is similar to markdown, but again, much more stripped down. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Headers use ‘#’, but have a limit of H1 -&amp;gt; H3.&lt;/li&gt;
&lt;li&gt;Lists are supported and indicated by a leading ‘*’, but they are only allowed to be one level deep  (no nested lists).&lt;/li&gt;
&lt;li&gt;Quote lines also start with ‘&amp;gt;’&lt;/li&gt;
&lt;li&gt;‘Preformat toggle lines’ work similarly, using ‘```’ (three back ticks) to start and end a block.&lt;/li&gt;
&lt;li&gt;Links get their own line, using the syntax ‘=&amp;gt; LINK OPTIONAL-LABEL’. Links of any protocol are supported.&lt;/li&gt;
&lt;li&gt;Again, no in-line text formatting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s it. While this might seem extreme and overly limiting, it really does feel clean and focused. There is less to worry about while writing. It also has the added benefit of making parsers MUCH easier to code.&lt;/p&gt;
&lt;p&gt;In addition to gemtext, there are some simplifications in gemini on the server side. For example, the server should only send one response per request. This means that videos, scripts, fonts, style sheets, or images shouldn’t autoload, as just the page source is returned for the initial request of a page. Images and other files are still supported, but as links. Many clients will load images in the page after clicking them or optionally when scrolling. There are also different status codes and requirements found in the spec [7].&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[6] &lt;a href=&quot;https://geminiprotocol.net/docs/gemtext.gmi&quot;&gt;A quick introduction to “gemtext” markup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[7] &lt;a href=&quot;https://geminiprotocol.net/docs/protocol-specification.gmi&quot;&gt;Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Why does this post look different?&lt;/h3&gt;
&lt;p&gt;If you haven’t caught on by now, this post looks different from my others. I’ve formatted it to somewhat represent what browsing a ‘gem’ (page) looks like. There’s no inline formatting, no list nesting, and (probably most noticeably) links are on their own line, usually at the end of a paragraph or section.&lt;/p&gt;
&lt;p&gt;Please note, this isn’t a perfect recreation, as I have left some items in, like the images and captions, as just linking wouldn’t render them like I experience them in Lagrange anyway. I also didn’t include a header image for this post. It felt superfluous for a gemini ;) .&lt;/p&gt;
&lt;h2&gt;How to get started&lt;/h2&gt;
&lt;h3&gt;Get a client&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-browsing-geminispace/ks5yUNgUQF-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-browsing-geminispace/ks5yUNgUQF-1200.jpeg&quot; alt=&quot;Amfora Client&quot; width=&quot;1200&quot; height=&quot;845&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Amphora Client&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Getting started with gemini isn’t too difficult. You just need a browser that supports the protocol. Actually, you don’t even need that, as you can view gemini content through a proxy, like the SmolNet Portal [8].&lt;/p&gt;
&lt;p&gt;That being said, a client really does provide the best experience. I use Lagrange [9] as my GUI client, and was even able to install the mobile beta via TestFlight on my iPad and iPhone.&lt;/p&gt;
&lt;p&gt;For the cli, I adore amfora’s [10] user interface and design. However, it is in “maintenance mode”, so I’ve also tried telescope [11], but I am  having trouble getting it to look as glamorous as amfora. Telescope does seem highly configurable though, so it might be possible.&lt;/p&gt;
&lt;p&gt;For other operating systems, or for alternative options, take a look at the known software page [12].&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[8] &lt;a href=&quot;https://portal.mozz.us/&quot;&gt;https://portal.mozz.us/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[9] &lt;a href=&quot;https://github.com/skyjake/lagrange&quot;&gt;Lagrange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[10] &lt;a href=&quot;https://github.com/makew0rld/amfora&quot;&gt;Amfora&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[11] &lt;a href=&quot;https://codeberg.org/telescope-browser/telescope&quot;&gt;Telescope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[12] &lt;a href=&quot;https://geminiprotocol.net/software/&quot;&gt;Known Gemini Software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Browse&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-browsing-geminispace/UmL-LV4nbt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-browsing-geminispace/UmL-LV4nbt-1200.jpeg&quot; alt=&quot;Chilly Weather Page&quot; width=&quot;1200&quot; height=&quot;957&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;One of my favorite pages has been &#39;chilly weather&#39; (despite the misleading name for the current weather...)&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Once you have a client, start browsing! In addition to the ‘awesome gemini’ page [13], there are a few other landing pages that can be helpful. I mostly used the ‘Getting Started’ page bookmarked by default in the Lagrange client [14], but something like geminiquickst.art [15] could also work.&lt;/p&gt;
&lt;p&gt;Another great place to find content is by looking at one of the many aggregators. I occasionally take a peek at antenna[16] or cosmos [17] to see if any newly posted logs catch my interest.&lt;/p&gt;
&lt;p&gt;Lastly, you can always search gemini for something! I’ve used TLGS.one [18] for search, but there might be others out there.&lt;/p&gt;
&lt;p&gt;Basically, have fun! There’s all sorts of cool gems that people have made, despite the minimal protocol. For example, I’m quite fond of Chilly Weather [19].&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[13] &lt;a href=&quot;gemini://gemi.dev/awesome.gmi&quot;&gt;(gemini) Awesome Gemini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[14] &lt;a href=&quot;gemini://skyjake.fi/lagrange/getting_started.gmi&quot;&gt;(gemini) Lagrange Getting Started Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[15] &lt;a href=&quot;gemini://geminiquickst.art&quot;&gt;(gemini) geminiquickst.art&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[16] &lt;a href=&quot;gemini://warmedal.se/~antenna/&quot;&gt;(gemini) Antenna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[17] &lt;a href=&quot;gemini://skyjake.fi/~Cosmos/&quot;&gt;(gemini) Cosmos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[18] &lt;a href=&quot;gemini://tlgs.one/&quot;&gt;(gemini) TLGS.one&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[19] &lt;a href=&quot;gemini://gemi.dev/cgi-bin/chilly.cgi/&quot;&gt;(gemini) Chilly Weather&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, what have I thought browsing geminispace the last few months? Honestly, it’s been great. It feels different than reading on the normal web. Quieter, I guess. And the quiet isn’t just because there’s less people (and content), but due to the design limitations. There’s less crap screaming for my attention, because it simply isn’t allowed. With gemini, the author is mostly concerned with the content, and not so much the style of it. That is up to the clients to implement and control.&lt;/p&gt;
&lt;p&gt;This (client-side styling) can be a mixed trade off. While I do miss the creativity you can often find on http(s) blogs, it is nice having things all look concise and consistent in my gemini clients. Additionally, browsing gemipace is SO much better than the web when using alternative clients, like terminal based browsers, or if you have a poor internet connection.&lt;/p&gt;
&lt;p&gt;Overall, I love it, and hope to continue using it as an escape from the big web.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>New ZSA Voyager Keyboard</title>
    <link href="https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/" />
    <updated>2024-05-15T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/b3EH6l9RQ5-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/b3EH6l9RQ5-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;846&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Shipped ZSA Voyager Keyboard&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In February, I decided to finally order the &lt;a href=&quot;https://www.zsa.io/voyager&quot;&gt;ZSA Voyager&lt;/a&gt; keyboard. By mid-March, it had arrived. Here are some of my thoughts after using it for over 2 months now.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;The Ferris Sweep’s &lt;em&gt;few&lt;/em&gt; Downsides&lt;/h2&gt;
&lt;p&gt;While I still enjoy my &lt;a href=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/&quot;&gt;ferris sweep&lt;/a&gt; &lt;a href=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/&quot;&gt;keyboards&lt;/a&gt;, I did start running into a few problems with them. My biggest issue was the long term reliability. Over time, some keys would become sensitive and misfire constantly. My original sweep build has a misfiring &lt;code&gt;u&lt;/code&gt; key that is so bad (likely from my poor soldering skills) that I can’t really use it anymore. This is particularly bad given that I use vim-bindings in many of my apps, where &lt;code&gt;u&lt;/code&gt; is “&lt;em&gt;undo&lt;/em&gt;” 😬.&lt;/p&gt;
&lt;p&gt;Second, while I still think for &lt;em&gt;most&lt;/em&gt; uses, a 34-key layout is perfect for my needs, I admit that it would be nice to have a &lt;em&gt;little&lt;/em&gt; more wiggle room designing layouts for my less common activities (i.e. gaming). Additionally, the sweep is a little &lt;em&gt;too&lt;/em&gt; compact for someone with my sized hands.&lt;/p&gt;
&lt;h2&gt;Deciding to get the Voyager&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/xqgaiblAYj-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/xqgaiblAYj-1200.jpeg&quot; alt=&quot;The ferris sweep on size print outs of the voyager&quot; width=&quot;1200&quot; height=&quot;671&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The main body of the Voyager isn&#39;t too much larger than the sweep.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When the Voyager was announced, I was instantly interested. It looked like a well-built, well-designed, slightly larger version of the ferris sweep that I cherish so much. The main ‘con’ people seemed to have with it was that the thumb clusters &lt;em&gt;only&lt;/em&gt; had two keys. Coming from the Sweep, this wasn’t a problem for me.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My&lt;/em&gt; only issue with it was that, in contrast to what others were saying online, I thought it had &lt;em&gt;too many&lt;/em&gt; keys 😆. I didn’t want to sacrifice portability to have more than 34 keys. However, comparing with the size printout, the Voyager wasn’t much larger than my Sweep.&lt;/p&gt;
&lt;p&gt;I decided that when the stability of my ‘good’ sweep got worse, the voyager would eventually be my next keyboard. With time, my main sweep &lt;em&gt;did&lt;/em&gt; get worse, so I ordered the voyager mid February. After some debate, I finalized my configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Cosmic Nomad Black” Color&lt;/li&gt;
&lt;li&gt;“Blank” keycaps&lt;/li&gt;
&lt;li&gt;Kailh Choc &lt;em&gt;Pro&lt;/em&gt; Red switches (35gf)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://blog.zsa.io/kailh-pro-red/&quot;&gt;&lt;em&gt;Pro Red&lt;/em&gt; switches became available&lt;/a&gt; shortly before ordering my Voyager. Without my beloved Silvers (40gf) as an option, I decided to try the slightly &lt;em&gt;lighter&lt;/em&gt; Pro Reds (35gf) instead of the &lt;em&gt;heavier&lt;/em&gt; Reds (50gf). Note: these are all &lt;em&gt;liner&lt;/em&gt; switches.&lt;/p&gt;
&lt;p&gt;With that, the order was submitted and I eagerly waited for it to be built and shipped.&lt;/p&gt;
&lt;h2&gt;Setup (Flashing my Layout)&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/kkbRHaCg8J-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/kkbRHaCg8J-1200.jpeg&quot; alt=&quot;A screenshot of the Oryx layout configurator with the sweep to voyager layout&quot; width=&quot;1200&quot; height=&quot;981&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Setting up my layout in the Oryx configurator.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;About a month later, the Voyager arrived. After unboxing and checking it out, I immediately worked to flash &lt;a href=&quot;https://configure.zsa.io/voyager/layouts/Mq0mJ/latest/0&quot;&gt;my layout&lt;/a&gt; on it. This is something I was able to start before the Voyager even arrived, using ZSA’s &lt;a href=&quot;https://configure.zsa.io/home&quot;&gt;Oryx&lt;/a&gt; configuration tool.&lt;/p&gt;
&lt;p&gt;Despite the Voyager having 50% more keys… I still love &lt;a href=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/&quot;&gt;my 34-key layout&lt;/a&gt; on the sweep. So I mostly stuck to that when making this layout. I just added a number row to the base layer’s additional top row, and some lighting controls to that row on another layer. I figured I can always adjust it over time as I get used to the extra keys and have ideas for them. Additionally, it makes switching back to the sweep easier when I want to use it.&lt;/p&gt;
&lt;p&gt;With the firmware compiled, I easily flashed the board using the &lt;a href=&quot;https://configure.zsa.io/home&quot;&gt;ZSA flashing tools&lt;/a&gt;, and pressing the physical reset button on the board. No paper clip/tweezers required!&lt;/p&gt;
&lt;h2&gt;Initial Thoughts&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/G-HWlgJ9lY-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/G-HWlgJ9lY-1200.jpeg&quot; alt=&quot;Voyager Setup at at table with macbook and ipad&quot; width=&quot;1200&quot; height=&quot;839&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Voyager makes for a great desk *and* portable setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I started using the Voyager as my daily driver immediately once it was flashed. I adjusted rather quickly, alternating between the Voyager and my laptop keyboards. I took a few notes about the initial experience:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While typing, it feels good so far. Honestly, the weirdest thing has been the additional outer pinky column of keys, and the additional top row. It seems I’ve gotten used to using the edges when placing my fingers on the sweep.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I did a “&lt;em&gt;short burst&lt;/em&gt;” &lt;a href=&quot;http://monkeytype.com&quot;&gt;monketype&lt;/a&gt; test and got over 100 WPM, with only 1 error. It was a test without punctuation or caps, but using my normal 34 key layout is working well.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;My basic typing feels &lt;em&gt;really&lt;/em&gt; great after some (more) time. I do need to add the number layer still though. While I &lt;em&gt;can&lt;/em&gt; use that top row on the base layer… I’m used to (and apparently love) slamming down on those two thumb keys and just using the numbers above my home row fingers. I think it’s easier for my brain to &lt;em&gt;know&lt;/em&gt; where a number is that way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I’ve also noticed a few tweaks I need to fix. For example, I might need to edit the timings for the hold keys, as I’m noticing some SHIFT issues… so far though… it still feels excellent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Things I like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Magnetic. I love plopping this on my MacBook’s palm rest&lt;/li&gt;
&lt;li&gt;The tenting legs&lt;/li&gt;
&lt;li&gt;The choc keys, but with MX spacing. I didn’t know if I’d like it, but it feels great and by comparison my Ferris Sweep feels so tight whenever I use it now.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Longer Term Thoughts&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/YXS0WqyVDx-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/YXS0WqyVDx-1200.jpeg&quot; alt=&quot;Voyager keyboard floating off the sides of a macbook palm rest&quot; width=&quot;1200&quot; height=&quot;869&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I love how the metal case allows the Voyager to attach to the magnets in the MacBook&#39;s palm rests.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over a longer period, I continued to make a few layout changes, mostly minor tweaks here and there. I also added a layer to experiment with mouse controls one day when I forgot to bring my trackball to the office. After some extended use there are many things I appreciate. Here’s just a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sturdy Construction&lt;/strong&gt;: The Voyager feels like a premium board. There is no flex, and everything is designed and built &lt;em&gt;so well&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Magnetic&lt;/strong&gt;: I underestimated how much I would enjoy this. Having a magnetic case enables so many more tenting possibilities. Additionally, due to the magnets in my MacBook (that keep the lid closed), I can attach the keyboard halves to my laptop’s palm rests. This works so much better than trying to place it on top of the built-in keyboard!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Switches and keycaps&lt;/strong&gt;: Both the Choc Pro Red switches and the custom keycaps for the Voyager feel delightful. (They also &lt;em&gt;sound&lt;/em&gt; quite nice too).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MX spacing&lt;/strong&gt;: I was leery about the MX spacing with choc switches. I was worried that it would take away from what I enjoy so much about choc, but honestly it just made it better. I get all of the low profile, portable goodness, but without my hands cramping up.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Column Stagger&lt;/strong&gt;: The way the column stagger has fit my hands so perfectly on the sweep was largely what kept me from more mild ones on boards like the &lt;a href=&quot;https://github.com/foostan/crkbd&quot;&gt;corne&lt;/a&gt;. The layout and stagger of the Voyager seems to be spaced perfectly for my hands.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Portability&lt;/strong&gt;: The carrying case with integrated tenting feet really assist the portability of the keyboard. I just fold it up, and throw it in my bag, like a smaller pair of headphones.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all that said, there are a &lt;em&gt;few&lt;/em&gt; things that could be improved (seriously, I had to think &lt;em&gt;hard&lt;/em&gt; to even come up with these).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;s&gt;&lt;strong&gt;TRRS Cable Position&lt;/strong&gt;: Personally, I would prefer the TRRS ports to be  closer to the center of the board. It connects the two halves so I usually like it closer there.&lt;/s&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thinking about it now though, I think the ports on the outside is better for tenting the board. Fine… I’ll revoke this one 😆.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tenting Feet&lt;/strong&gt;: While the feet are great, they can be annoying to attached and remove when frequently packing up and traveling the board like I do. This isn’t really a fault of the design, and just of a pain point with how I use it. Additionally, a portable design that provides ability to adjust the level of tenting would be awesome.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/Z7qrfeYq6F-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-zsa-voyager-keyboard/Z7qrfeYq6F-1200.jpeg&quot; alt=&quot;The Voyager sitting nearly sideways on two tenting stands.&quot; width=&quot;1200&quot; height=&quot;836&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using two magnetic stands for tenting.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Moving forward, I’m planning to construct tenting setups for both my work and home office desks. I hope this will help increase the ease of commuting the Voayager even more, as I should be able to just pull it out of it’s case and plop it on magnets instead of messing with the feet each time. I’ve. tested this out in the office using two cheap magnetic phone stands, and so far it’s worked well.&lt;/p&gt;
&lt;p&gt;To conclude, I adore the Voyager. It is essentially my ideal keyboard. It’s a mix my previous Ergodox EZ and my Ferris Sweep. Comfortable, solid, portable… perfect.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Minisforum um560 Died</title>
    <link href="https://ryan.himmelwright.net/post/minisforum-um560-died/" />
    <updated>2024-04-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/minisforum-um560-died/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/minisforum-um560-died/PGlKLxEinj-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/minisforum-um560-died/PGlKLxEinj-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;1048&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last week while trying to do a fresh install  of &lt;a href=&quot;http://fedoraproject.org&quot;&gt;Fedora&lt;/a&gt;,  my &lt;a href=&quot;https://store.minisforum.com/blogs/blog/minisforum-announce-venus-series-um560&quot;&gt;Minisforum um560&lt;/a&gt; mini computer died. I plugged it in, heard a faint “&lt;em&gt;pop&lt;/em&gt;”, and haven’t been able to power it on since. Unfortunately, after a history of a &lt;em&gt;questionable&lt;/em&gt; power supply issues, this wasn’t much of a shock…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;I purchased the um560 in 2022, after researching several mini-computer models. I wanted a lower powered computer (compared to my desktop), that I could leave running as my “linux dev” machine, and remotely connect to from my MacBook.&lt;/p&gt;
&lt;p&gt;I was interested in the the um560 specifically, because it was Minisfourm’s first model to feature ‘alt-mode charging’. This meant that it could (in theory) be both powered and connected from the same cable via a thunderbolt dock. In other words, the ”one cable lifestyle”, just like I have with my MacBook. Even though I wanted the um560 to mostly run headless, I figured this feature would be convienient when I occasionally work on it with a display. Shutdown, plug in, and boot it up.&lt;/p&gt;
&lt;h2&gt;Previous Issues&lt;/h2&gt;
&lt;p&gt;In practice, this never truly worked. The um650 would shut off trying to load the OS after the boot screen. It didn’t seem to be getting enough power, despite using connections that had more than enough PD. On top of that, the general power delivery was very unstable and the computer would randomly shutdown while doing tasks like running updates (even when using the included adapter + cable).&lt;/p&gt;
&lt;p&gt;Eventually, I tweaked the bios settings to use a lower TDP threshold, hoping it would help. It made the system a &lt;em&gt;bit&lt;/em&gt; more &lt;em&gt;stable&lt;/em&gt; when running as a headless server, but I never really got the alt mode working consistently.&lt;/p&gt;
&lt;p&gt;I ran it for a year or so as a headless server, mostly serving Plex and building my website. It remained stable for longer periods of time, but was always a gamble of &lt;em&gt;when&lt;/em&gt; it would crash, not &lt;em&gt;if&lt;/em&gt;. Not a great feature for a server. I eventually spun it down to only use from time to time as a Linux test machine. That is until it broke.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s all. I wanted to log the issues I had with the um560, just in case others experience anything similar. Hopefully, it’s just an issue with &lt;em&gt;my&lt;/em&gt; unit or even just this (now discontinued) model. I haven’t had the same issues with my &lt;a href=&quot;https://store.minisforum.com/products/elitemini-hx90g&quot;&gt;Minisforum hx99g&lt;/a&gt;, although it doesn’t have TB power delivery, and admittedly I don’t have it powered on 24/7 or for very long periods of time.&lt;/p&gt;
&lt;p&gt;As for the um560, I’ve removed the SSD and 64 GB (!) of RAM, and will send the unit to &lt;a href=&quot;http://triangleecycling.com&quot;&gt;e-waste recycling&lt;/a&gt;. Bummer. I might hold onto the RAM and keep an eye open for a cheap small computer to use as a new test server, if I need to eventually. For now, I can just setup the Linux SSD in my hx99g finally…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Started Using the Obsidian Tasks Plugin</title>
    <link href="https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/" />
    <updated>2024-03-23T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/Y3WpG0D-Gk-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/Y3WpG0D-Gk-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Jumping back into work after the holidays this year, I realized how cumbersome my notes and task management system had become. I was copying lists of TODO checkboxes across weekly notes. Each day, I moved anticipated work items to my daily note, only to pop the unfinished items back onto the weekly note. It was stupid, inefficient, and &lt;em&gt;not&lt;/em&gt; how I usually handle task management. So, I finally tried the &lt;a href=&quot;https://github.com/obsidian-tasks-group/obsidian-tasks&quot;&gt;Obsidian Tasks plugin&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;I started with my ‘work’ vault because I desperately needed an improved task management system there. The current arrangement wasn’t working for me anymore. I also had a completely different setup for personal tasks already in use. However, after a few weeks with the Tasks Plugin at my day job… I &lt;em&gt;had&lt;/em&gt; to switch over my ‘personal’ vault too. At least for personal tasks and projects that is. Family tasks remain in &lt;a href=&quot;http://ticktick.com&quot;&gt;ticktick&lt;/a&gt;, but I’m testing a family sub-vault for project notes too. Let’s take a deeper look into this system.&lt;/p&gt;
&lt;h2&gt;Plugin Setup &amp;amp; Settings&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/hD4PKrcQey-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/hD4PKrcQey-1200.jpeg&quot; alt=&quot;An obsidian window with the settings panel opened to the tasks settings&quot; width=&quot;1200&quot; height=&quot;1081&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Tasks plugin settings. The global task filter is a must for my vaults.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first step was to install the Obsidian Tasks plugin. Like any &lt;a href=&quot;http://obsidian.md&quot;&gt;Obsidian&lt;/a&gt; community plugin, ensure “Restricted Mode” is disabled, and then search for and install the “Tasks” plugin. After enabling it, I realized there were 2 settings changes I &lt;em&gt;needed&lt;/em&gt; to make for my system to work properly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Set and use the “global task filter”. This option defines a string that is used to differentiate between a normal checkbox and an actual &lt;em&gt;task&lt;/em&gt;.  This was key, especially when converting an older vault that contains all sorts of historical opened/closed checkboxes littered throughout it. I used the tag &lt;code&gt;#task&lt;/code&gt; for my filter.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I also turned on the option: “Next recurrence appears on the line below”. This toggles the behavior so that when a recurring task is completed, the new recurring item appears &lt;em&gt;below&lt;/em&gt; the previous one, instead of the default setting of &lt;em&gt;above&lt;/em&gt; it. This is just a personal preference for me, although I can see why the default might make more sense, particularly with longer lists of completed recurring tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I made a few other tweaks, but those are the two &lt;em&gt;big&lt;/em&gt; items. I saw there’s a way to include global settings to auto append to every task block, which I might do in the future as I nearly always include &lt;code&gt;short mode&lt;/code&gt;, &lt;code&gt;hide tags&lt;/code&gt;, and &lt;code&gt;hide task count&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Vault Conversion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/-swYKAD1Kz-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/-swYKAD1Kz-1200.jpeg&quot; alt=&quot;An Obsidian window with two callout boxes, Monday and Tuesday, full of checked tasks.&quot; width=&quot;1200&quot; height=&quot;1081&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Tasks section of my weekly note.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Templates&lt;/h3&gt;
&lt;p&gt;With the plugin configured, I started the process of converting my vault(s). My first step was to add task views to my &lt;em&gt;current&lt;/em&gt; daily and weekly notes. Once I had what I initially wanted, I applied the formatting to the note &lt;em&gt;templates&lt;/em&gt;. This was an iterative process, and I ended up going back and tweaking the templates over the following weeks.&lt;/p&gt;
&lt;p&gt;After some time to soak, my template modifications slowed down, and I was left with the following (for my ‘personal’ vault):&lt;/p&gt;
&lt;h3&gt;Daily Note&lt;/h3&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!danger]- Overdue
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```tasks
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;not done
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;(scheduled before {{date:YYYY-MM-DD}}) OR (due before {{date:YYYY-MM-DD}})
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;short mode
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide tags
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide task count
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;group by filename
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;sort by priority
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```

&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!todo]+
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```tasks
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;not done
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;(scheduled on {{date:YYYY-MM-DD}}) OR (due on {{date:YYYY-MM-DD}})
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;short mode
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide tags
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide task count
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;group by filename
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;sort by priority
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```

&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!done]+
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```tasks
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;(done on {{date:YYYY-MM-DD}}) OR (cancelled on {{date:YYYY-MM-DD}})
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;short mode
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide tags
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide task count
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;group by filename
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;sort by priority
```&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is at the top of my daily note template, and lists tasks scheduled for the day in three &lt;a href=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/&quot;&gt;callout&lt;/a&gt; boxes: Overdue, Todo, and Completed. Tasks in each box are also grouped by &lt;em&gt;filename&lt;/em&gt;, which usually corresponds to the project the tasks are part of. As I complete tasks, they display in the different boxes accordingly.&lt;/p&gt;
&lt;h3&gt;Weekly Note&lt;/h3&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; Monday&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;Daily Log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;: [[{{monday:YYYY-MM-DD}}]]
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!tldr]+  Events &amp;amp; Tasks
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```tasks
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;(scheduled on {{monday:YYYY-MM-DD}}) OR (due on {{monday:YYYY-MM-DD}}) OR (done on {{monday:YYYY-MM-DD}}) OR (cancelled on {{monday:YYYY-MM-DD}})
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;short mode
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide tags
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;hide task count
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;sort by priority
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;sort by done
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;```&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the switch to using the tasks plugin, my previous &lt;em&gt;manual&lt;/em&gt; weekly note slimed down to just a bunch of task views 😅. I had to learn how to have a template set specific days for each day of the week, but after some research, I found out that you can use &lt;code&gt;{{DAY:FORMAT}}&lt;/code&gt; in weekly templates, and the date for that day that week will be swapped in. I &lt;em&gt;think&lt;/em&gt; this is handled by &lt;a href=&quot;https://github.com/liamcain/obsidian-periodic-notes&quot;&gt;periodic notes&lt;/a&gt;, but I’m not positive. I was also able to use this trick to &lt;em&gt;finally&lt;/em&gt; have each day’s link to its corresponding daily note be set automatically.&lt;/p&gt;
&lt;p&gt;The weekly template mostly consists of a list of blocks for each day of the week. For each day, I have a link to the day’s daily note, and then a simplified, single callout box for the day’s tasks. This makes it easier to see tasks across several days at a glance. If I want more detail or tasks split apart, I can go to a specific day’s note.&lt;/p&gt;
&lt;h3&gt;Monthly/Quarterly Templates&lt;/h3&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;tasks&lt;/span&gt;
&lt;span class=&quot;token code-block language-tasks language-tasks&quot;&gt;(scheduled in {{date:YYYY-MM}}) OR (due in {{date:YYYY-MM}}) OR (done in {{date:YYYY-MM}}) OR (cancelled in {{date:YYYY-MM}})
folder does not include Logs/Weekly Plans
folder does not include Logs/Daily Notes
short mode
group by filename
sort by scheduled&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My monthly/quarterly notes each have a section that lists all the tasks for that time period, again grouped by filename. This allows me to quickly see everything that I have scheduled, or completed in a given month or quarter, which is awesome when I want to review at the end. Yes, it does get long, but that’s usually a good thing (at the end of the time period, only completed items remain).&lt;/p&gt;
&lt;p&gt;The only real difference between the two is that the quarterly one uses &lt;code&gt;{{date:YYYY-[Q]Q}}&lt;/code&gt; to match the quarters instead of months (&lt;code&gt;{{date:YYYY-MM}}&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Note: I don’t currently have these in callout boxes, as I thought their size wouldn’t work well for being in one. However, I’m second-guessing that now as I write this post 😉)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Task Conversion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/voeIvpKFKl-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/voeIvpKFKl-1200.jpeg&quot; alt=&quot;An obsidian window with a bunch of checked list items in the middle of the note&quot; width=&quot;1200&quot; height=&quot;927&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My tasks live in the note they&#39;re related to. Like the note for this post, for example.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After setting up my periodic note templates with task views, my next step was to get my data into the system. This mostly involved finding all my current “task” items, converting them to proper obsidian tasks by adding the &lt;code&gt;#task&lt;/code&gt; tag to them, and setting a scheduled date. Many of these checkbox items lived in my monthly or quarterly notes, so I often created new project notes for tasks to live in, if there wasn’t one already. Lastly, I added a tasks section to my project note template, which made this procedure a bit easier.&lt;/p&gt;
&lt;p&gt;The whole process wasn’t too bad, as I only worried about converting current items, and a few from the last month or so. Moving forward, I’ve been creating tasks in the proper format, and when I bring up old items to work on, I convert them if they haven’t been for some reason. So far, it’s worked.&lt;/p&gt;
&lt;h2&gt;My Task Workflow&lt;/h2&gt;
&lt;p&gt;My workflow is much easier than it used to be. Now, I simply add tasks to project or item notes, and they appear when scheduled. If I want to know all tasks for a particular item, I just look at them in the note. I can also periodically spin up a view to see all tasks without scheduled dates, so nothing gets lost.&lt;/p&gt;
&lt;p&gt;If needed, I can (and do) add small, one-off tasks in my daily, weekly, or &lt;em&gt;“Reminders”&lt;/em&gt; notes, but I prefer having a specific note to log them in. Visiting the Library? Make a new “CITY library” note, and add a task section to it. Now you have a library ‘visit log’. The same applies to doctor’s appointments, restaurants, car maintenance, vet visits, etc. Adjusting my note structure and workflow for obsidian tasks has made my entire vault &lt;em&gt;much&lt;/em&gt; more useful as a knowledge base for my life.&lt;/p&gt;
&lt;p&gt;I mostly view tasks in my daily note, or directly in a project note as I work on it. As dates change, I either use the convenient button to push them to a future day, or use the edit window to make more complicated adjustments to the scheduling of the task. Very handy.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/fxjujoR6qR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-using-obsidian-tasks-plugin/fxjujoR6qR-1200.jpeg&quot; alt=&quot;An obsidian note about the library, containing completed checkboxes for visits.&quot; width=&quot;1200&quot; height=&quot;640&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Adding event &#39;tasks&#39; to notes about places has served as an easy way to maintain a visit log.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Like &lt;a href=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/&quot;&gt;callouts&lt;/a&gt;, the tasks plugin has completely changed how I use &lt;a href=&quot;http://obsidian.md&quot;&gt;obsidian&lt;/a&gt;.  There are a few improvements I still want to figure out, like using two global task filters (&lt;code&gt;#event&lt;/code&gt; in addition to my current &lt;code&gt;#task&lt;/code&gt;), but overall everything works pretty much as I want it to. The tasks plugin has been the last piece I’ve needed to make Obsidian the &lt;em&gt;perfect&lt;/em&gt; combined task+note system. I’ve been longing for this since &lt;a href=&quot;https://ryan.himmelwright.net/post/trying-notion/&quot;&gt;moving away from Emacs org-mode&lt;/a&gt;  several years ago. It’s quite the feat.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Reading my First Comic</title>
    <link href="https://ryan.himmelwright.net/post/read-first-comic/" />
    <updated>2024-02-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/read-first-comic/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/read-first-comic/HcF-QVFVyi-300.webp 300w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/read-first-comic/HcF-QVFVyi-300.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;300&quot; height=&quot;453&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;CRITICAL ROLE: THE MIGHTY NEIN ORIGINS--CALEB WIDOWGAST&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While watching the announcements portion of a &lt;a href=&quot;https://critrole.com/filter/campaign-3/&quot;&gt;Critical Role&lt;/a&gt; episode one night, the cast highlighted an upcoming issue to their comic book series . Listening in, I thought to myself “I really should look into reading that series. I bet I would enjoy it.” So I decided to give it a shot.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Obtaining a Copy&lt;/h3&gt;
&lt;p&gt;After some quick research on where I could find Critical Role comic books, it turned out that they are available on Hoopla through the &lt;a href=&quot;http://durhamcountylibrary.org&quot;&gt;Durham Library&lt;/a&gt;. I &lt;em&gt;love&lt;/em&gt; libraries. That night, I read some articles on &lt;em&gt;how&lt;/em&gt; to read comic books, in order to better recognize subtleties in the art, how the panels are organized, gutter spacing, and other techniques that are often used. &lt;a href=&quot;https://www.vox.com/2015/2/25/8101837/ody-c-comic-book-panels&quot;&gt;This article&lt;/a&gt; was very helpful in getting me going.&lt;/p&gt;
&lt;p&gt;After brushing up on how to read a comic book, I read the intro/sample to the issue I intended to checkout: &lt;a href=&quot;https://www.darkhorse.com/Books/3006-824/Critical-Role-The-Mighty-Nein-Origins--Caleb-HC&quot;&gt;Critical Role: The Mighty Nein Origins - Caleb&lt;/a&gt;. The next morning (there’s a daily limit on electronic checkouts), I was able to checkout the full version electronically from my library.&lt;/p&gt;
&lt;h3&gt;Reading&lt;/h3&gt;
&lt;p&gt;At first, I paged through the issue, skimming and taking in the art. I didn’t have a large dedicated time in that moment to properly &lt;em&gt;read&lt;/em&gt; it, but I was excited to give it a quick first pass. A week or two later, I sat down on a Sunday night while I was watching my son. He read his books next to me, and I cruised through the comic on my iPad. I was a lovely time.&lt;/p&gt;
&lt;h3&gt;Thoughts&lt;/h3&gt;
&lt;p&gt;Overall, I really enjoyed it. It was fun to go through the story and delightful to appreciate the art. Even more than ‘normal’ book reading, I can feel that comics are more likely to make me want to periodically go back and page through them, as there is so much visual complexity. The library issues are a great start, but for ones I really love and want to revisit, I might buy a few of my own copies. We’ll see.&lt;/p&gt;
&lt;p&gt;Moving forward, I want to start occasionally reading comics, probably with continuing the Critical Role series. I think it could be a nice treat to sit down and read through an issue once or twice across a few nights. If you’ve never read a comic book before, I recommend it. See what your library has to offer 😉.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Obsidian Callouts</title>
    <link href="https://ryan.himmelwright.net/post/obsidian-callouts/" />
    <updated>2024-02-15T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/obsidian-callouts/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/ud7Ub5Pwx5-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/ud7Ub5Pwx5-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;921&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Several months ago, I read about &lt;a href=&quot;https://help.obsidian.md/Editing+and+formatting/Callouts&quot;&gt;obsidian callouts&lt;/a&gt; in a post. At the time, I thought it was a neat feature, but didn’t know what I would use them for. “Why would I need an announcement box for in my personal notes?”&lt;/p&gt;
&lt;p&gt;Recently while testing out the &lt;a href=&quot;https://github.com/obsidian-tasks-group/obsidian-tasks&quot;&gt;Obsidian Tasks Plugin&lt;/a&gt;, I realized I could use callout boxes to clean up and organize tasks in my notes. Since that realization, I use callouts for all of my task views, and a few other places too.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What are Callouts?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/xWnLfnpaZI-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/xWnLfnpaZI-1200.jpeg&quot; alt=&quot;An obsidian window containing four callout boxes, representing each of the examples provided below&quot; width=&quot;1200&quot; height=&quot;1057&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My example callouts, in Obsidian.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Callouts are a way to easily break up an obsidian note. They are colored boxes that contain content, and can be similar to a &lt;a href=&quot;https://www.w3schools.com/tags/tag_details.asp&quot;&gt;html details element&lt;/a&gt;. They are in the announcement notes you see after updating Obsidian (which is how I was familiar with them).&lt;/p&gt;
&lt;p&gt;To create a callout in Obsidian, you just create a normal &lt;a href=&quot;https://help.obsidian.md/Editing+and+formatting/Basic+formatting+syntax#Quotes&quot;&gt;block quote&lt;/a&gt;, but have a callout tag for the first line:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!info]
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;This is the formatting for a basic callout.
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;Here is another line&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;!info&lt;/code&gt; defines the &lt;em&gt;type&lt;/em&gt; of callout (ex: &lt;code&gt;todo&lt;/code&gt;, &lt;code&gt;summary,&lt;/code&gt; &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;bug&lt;/code&gt;…). &lt;a href=&quot;https://help.obsidian.md/Editing+and+formatting/Callouts#Supported%20types&quot;&gt;There are many&lt;/a&gt;, and they determine the icon used, the color of the box, and the default title. I say &lt;em&gt;default&lt;/em&gt;, because the title can be changed by adding it after the tag. For example, to define “I’m a Callout” as the title for a &lt;em&gt;hint&lt;/em&gt; callout:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!hint] I&#39;m a Callout
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;This is the formatting for a callout.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, callouts can be made &lt;a href=&quot;https://help.obsidian.md/Editing+and+formatting/Callouts#Foldable%20callouts&quot;&gt;foldable&lt;/a&gt; by including a &lt;code&gt;+&lt;/code&gt; or &lt;code&gt;-&lt;/code&gt; immediately after the closing bracket. A foldable callout can be collapsed to just the title bar when clicking it.  A &lt;code&gt;-&lt;/code&gt; means that by default, the callout will be collapsed, while a &lt;code&gt;+&lt;/code&gt; will default it to opened. An opened callout:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;[!example]+ I&#39;m a Callout
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;I am a foldable callout. By default, I am opened. &lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How I use Them&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/WoXQMIlZ0t-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-callouts/WoXQMIlZ0t-1200.jpeg&quot; alt=&quot;An obsidian note window showing a daily note, with several callout boxes for overdue, todo, and done tasks. The todo window contains several tasks for the day&quot; width=&quot;1200&quot; height=&quot;1117&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The &#39;Plans&#39; section of my daily note, consisting of my task view callout boxes.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I use callouts for two purposes: 1) to create ‘dashboards’ of information in my notes and 2) to define a subsection of my note I can collapse.&lt;/p&gt;
&lt;p&gt;The ‘dashboard’ use is mostly partnered with the &lt;a href=&quot;https://github.com/obsidian-tasks-group/obsidian-tasks&quot;&gt;Obsidian Tasks Plugin&lt;/a&gt; I started using recently. In my new setup, I define tasks under different projects notes. Next, I have task views that let me see all the scheduled tasks for a given day, week, or month in my &lt;a href=&quot;https://github.com/liamcain/obsidian-periodic-notes&quot;&gt;periodic notes&lt;/a&gt;. These views are all defined &lt;em&gt;inside&lt;/em&gt;  callouts. This helps add some formality to task sections, and allows me to collapse the sections if it’s too overwhelming.&lt;/p&gt;
&lt;p&gt;The less common, but increasing use for callouts, is when I want to section a large chunk of information into a collapsed box. For example, I might have a note with several long lists that I need to reference occasionally, but don’t want them visually taking up the whole note. Using callouts, I can define the lists under nice named tabs that only expand when needed.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Despite thinking I would never use callouts when I first learned of them, I can’t imagine obsidian without them now. They allow me to use the tasks plugin in a way which isn’t a complete mess. This has been a wonderful improvement to both my work and personal vaults.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2023 Review Picks</title>
    <link href="https://ryan.himmelwright.net/post/2023-review-picks/" />
    <updated>2024-01-23T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/2023-review-picks/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2023-review-picks/2F6grGkthy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2023-review-picks/2F6grGkthy-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Brightleaf Square, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In 2022, I started keeping a yearly note in &lt;a href=&quot;http://obsidian.md&quot;&gt;Obsidian&lt;/a&gt;. Partway through that year, I also began tracking my favorite ‘picks’ for different categories. For example, my favorite book or tech purchase. I liked it so much that when we rolled into 2023, I dedicated a section of the note to track picks from the beginning. Instead leaving the results sitting in my Obsidian vault, I figured I might as well share them here this year.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What are ‘My Picks of the Year’?&lt;/h2&gt;
&lt;p&gt;So what exactly are my “picks of the year”? Simply put, they are items I select that stand out for a particular category. That’s really the only rule. There are a few other &lt;em&gt;guidelines&lt;/em&gt; I follow though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does the pick have to be from this year?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not really. I tend to prefer picks that relate to the year in some way, but that doesn’t mean they have to be from that year. I might pick a song from 10 years ago because I heard it for the first time this year, or because it is meaningful &lt;em&gt;now&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does the pick have to be my favorite for its category?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Nope. It might be the &lt;em&gt;most influential&lt;/em&gt;, even if it wasn’t my favorite.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Categories are fluid&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When I started making picks, I made each category as I thought of items I wanted to rank. Now that I’ve done this for more than a year… the categories change. There are some categories that I simply didn’t have strong nominations for (i.e. I didn’t really watch any notable movies). I also allow myself to add new ones.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;This is just for fun.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At the end of the day, this is just a silly thing I started tracking in my yearly note.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why do yearly picks?&lt;/h2&gt;
&lt;p&gt;I think I’ve enjoyed doing a yearly pick for 3 main reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is fun to look back at what I picked for different categories each year. I imagine if I continue this habit, I’ll enjoy looking back at my picks during various points of my life.&lt;/li&gt;
&lt;li&gt;Having picks in the back of my mind brings a new level of awareness around what I consume. It forces me to really think about how I feel about different experiences.&lt;/li&gt;
&lt;li&gt;Selecting final picks prompts me to reflect on &lt;em&gt;why&lt;/em&gt; something was meaningful/important to me &lt;em&gt;that year&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all the background information covered, let’s dig into my 2023 picks!&lt;/p&gt;
&lt;h2&gt;2023 Picks&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2023-review-picks/hf2VEb91wk-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2023-review-picks/hf2VEb91wk-1200.jpeg&quot; alt=&quot;MBP on a floor desk, with an Apple Studio Display above it&quot; width=&quot;1200&quot; height=&quot;985&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Apple Studio Display and my floor desk were some of my favorite purchases in 2023.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Tech Purchase&lt;/strong&gt;: Apple Studio Display&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mention&lt;/em&gt;: &lt;a href=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/&quot;&gt;16&amp;quot; M3 Pro MacBook Pro&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This category was hard this year. Looking back, I had an arguably obnoxious amount of great tech purchases, each which could win in less crowded years. Most of the year, I thought it would be the Apple Studio Display, but then I squeezed in my &lt;a href=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/&quot;&gt;16&amp;quot; M3 MacBook Pro&lt;/a&gt; right at the end of the year, making it a tough decision.&lt;/p&gt;
&lt;p&gt;In the end, I still choose the Studio Display. For my uses, it’s a great monitor and does everything I need it to. Having a large Retina display has been amazing, and its speakers are &lt;em&gt;good enough&lt;/em&gt; that I’ve really been able to simplify my desk this year, allowing it to be more flexible. Despite the cost, I’ve been very happy with this purchase. Also… +1 to the Apple Refurbished store 😉!&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Non-Tech Purchase&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/gp/product/B08CXPBZLD/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&amp;amp;th=1&quot;&gt;Bamboo Floor Desk&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mentions:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.vivobarefoot.com/us/primus-lite-iii-mens-ss22&quot;&gt;Vivobarefoot Primus Lite III&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wearpact.com&quot;&gt;p a c t&lt;/a&gt; shorts (The Backyard Weekend Short - Storm)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This category has a weird mix of all sorts of items. I switched to using barefoot shoes this year, and my Vivo’s have been a solid all around pair for that. Additionally, I’ve experimented with using more natural fibre clothes this year, and my linen+organic cotton p a c t shorts were very comfortable this summer.&lt;/p&gt;
&lt;p&gt;For my pick though, I think I’m going with my Bamboo Floor Desk. I use it all the time, all over the house, and can slide it under the couch if I want to store it away.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;‘House’ Purchase&lt;/strong&gt;: &lt;a href=&quot;https://www.schlage.com/en/home/smart-locks/encode-plus.html&quot;&gt;Schlage Encode Plus Smart Lock&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mention&lt;/em&gt;: &lt;a href=&quot;https://shop.epicgardening.com/products/birdies-large-raised-garden-bed-29-tall?variant=42495964152004&quot;&gt;Birdies Tall 8-in-1 Metal Raised Garden Bed - Mist Green&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a category I added last minute, so I’m sure there are items that could be here I forgot.&lt;/p&gt;
&lt;p&gt;The main reason I added this was to include our door lock as a pick. I knew it would be great to easily &lt;em&gt;unlock&lt;/em&gt; the door when we got home, but I didn’t anticipate how nice it would be while &lt;em&gt;leaving&lt;/em&gt; the house too. I just push the &lt;em&gt;lock&lt;/em&gt; button and go. No more fumbling with keys. We’ve wanted to make this upgrade for quite some time, but the locks models we wanted were always out of stock. My wife and I are both very happy with this purchase.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Music Album&lt;/strong&gt;: &lt;a href=&quot;https://www.youtube.com/watch?v=Zg-nbmCvCDk&amp;amp;list=PLxA687tYuMWii-wRcFmhPgQIHaqVOkYXm&quot;&gt;One More Time - Blink-182&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mention&lt;/em&gt;: The Land is Inhospitable and So Are We - Mitski](&lt;a href=&quot;https://www.youtube.com/watch?v=5WW7cuAm-7Y&amp;amp;list=PLB0BgxYHor0sb2T8992DeaKVaknn-771p&quot;&gt;https://www.youtube.com/watch?v=5WW7cuAm-7Y&amp;amp;list=PLB0BgxYHor0sb2T8992DeaKVaknn-771p&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I was a little skeptical when I saw the new Blink album released… until I listened to it. I love it. I’m sure there are many technical reasons to criticize this album, but I don’t care. ‘One More Time’ is a meaningful album, and &lt;a href=&quot;https://chorus.fm/reviews/blink-182-one-more-time/&quot;&gt;I’m not the only one that thinks that&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Song&lt;/strong&gt;: &lt;a href=&quot;https://www.youtube.com/watch?v=H1Tj-LVduTo&quot;&gt;I’m too small for this world - Mandelbro&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mentions&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=fSKQRDq3RkM&quot;&gt;One More Time - Blink-182&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=D2U1Bz6zPgo&quot;&gt;Cat Cat Frog Frog - Mike Phirman&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Earlier this year I had a &lt;a href=&quot;https://youtu.be/TARe4G-SXfk?feature=shared&amp;amp;t=205&quot;&gt;Max Reisinger video&lt;/a&gt; playing in the background while working. There’s a section of the video that uses &lt;em&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=H1Tj-LVduTo&quot;&gt;I’m too small for this world by Mandlebro&lt;/a&gt;&lt;/em&gt;, and by the time the song’s second verse hit, I was already hooked. I continued to put it on several times a week throughout the whole year. So good.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One More Time&lt;/em&gt; from the Blink album was also a powerful song that I wanted to include here. Lastly, as a parent… &lt;em&gt;Cat Cat Frog Frog&lt;/em&gt; got a bunch of playtime this year… although my son never asked for it. My wife and I both legitimately enjoy it and listen to it all the time 😆.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Game&lt;/strong&gt;: &lt;a href=&quot;https://melvoridle.com&quot;&gt;Melvor Idle&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mentions:&lt;/em&gt; &lt;a href=&quot;https://baldursgate3.game&quot;&gt;Baldur’s Gate 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As someone who adores &lt;a href=&quot;https://oldschool.runescape.com&quot;&gt;Old School Runescape&lt;/a&gt;, but doesn’t have enough time to truly feed that addiction… Melvor Idle is perfect. It’s based on OSRS, but an ‘idle’ game. &lt;em&gt;Most&lt;/em&gt; of my enjoyment in OSRS is planning out when and how to level up everything, and make money doing it. That’s basically what Melvor Idle is at its core. You select at task, log off, and check in a few hours to plan and switch to something else. It’s wonderful.&lt;/p&gt;
&lt;p&gt;I’ve had Baldur’s Gate 3 for years as a pre-release, but didn’t start to play the released version until the last few days of the year. I’m hoping to play it more in 2024.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Book&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/Born-Run-Hidden-Superathletes-Greatest/dp/B0028TY1D8&quot;&gt;Born to Run by Christopher McDougal (Audio)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I didn’t read/listen to too many &lt;em&gt;books&lt;/em&gt; this year, but one I did was &lt;em&gt;Born to Run&lt;/em&gt;. I heard of it in the past, but with me switching to barefoot shoes, and starting to go on &lt;em&gt;light&lt;/em&gt; jogs, I thought it was a good time to give it a read. I listened to the audiobook (thank you Durham Library!) and found it very engaging. It had a bunch of ‘non-fiction’ type content, packed into an entertaining story to drive everything. A perfect combination.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Exercise/Activity&lt;/strong&gt;: ”Jogging”&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mentions:&lt;/em&gt; Pilates&lt;/p&gt;
&lt;p&gt;As mentioned above, this year I occasionally went for what some would call a ‘light jog’. It sure as hell wasn’t running, but it was more than a walk. I largely blame my &lt;a href=&quot;https://www.earthrunners.com/products/alpha-adventure-sandals?variant=39575138533458&quot;&gt;Earthrunners&lt;/a&gt;, as their springy lightness made me want to keep going after jogging through an intersection one day. A few times during the year, when I had the time and someone else watching my son, I would go out and do a few miles at different paces as needed. Usually in the rain. (I love the rain).&lt;/p&gt;
&lt;p&gt;I also started doing more pilates classes on Apple Fitness+ and like it much more than I thought I would. I’m not sure why I’m surprised, as it combines all my favorite exercise focuses: strength, core work, flexibility, and balance.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Written Media/Website&lt;/strong&gt; (Added): &lt;a href=&quot;http://kevquirk.com&quot;&gt;Kevin Quirk’s Blog&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mention:&lt;/em&gt; &lt;a href=&quot;http://lowtechmagazine.com&quot;&gt;Low-Tech Magazine&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This was a new category I added this year. I redid my rss and read-later setup in 2023, and focused on consuming more ‘small web’ content. I followed more personal blogs this year as motivation for my website redo. One blog that kept popping up everywhere was one I already had in my feed: Kev Quirk’s website. Reading a bunch of his posts (and others’ he linked to) helped push me to finally do my redesign. Moreover, I used the &lt;a href=&quot;http://simplecss.org&quot;&gt;simplecss&lt;/a&gt; theme he created as a starting point for my website.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://lowtechmagazine.com&quot;&gt;Low-Tech Magazine&lt;/a&gt; has influenced me to rethink how I live, and how to adapt more sustainable practices. It &lt;em&gt;also&lt;/em&gt; influenced my website redesign, after reading about &lt;a href=&quot;https://solar.lowtechmagazine.com/2023/06/rebuilding-a-solar-powered-website/&quot;&gt;their redesign&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Hobby&lt;/strong&gt;: Small Web/Blogging&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Honorable Mention:&lt;/em&gt; Gardening/Learning permaculture &amp;amp; Ecology&lt;/p&gt;
&lt;p&gt;As mentioned above, the largest personal project I accomplished this year was &lt;a href=&quot;https://ryan.himmelwright.net/post/website-redo-2023/&quot;&gt;my website redo&lt;/a&gt;. This was prompted by the &lt;a href=&quot;https://thewebisfucked.com&quot;&gt;internet becoming shittier&lt;/a&gt;, and my desire to find better spaces on it. This lead me to the rings of the ‘small web’, personal blogging, and sustainable web design. I redid my website and want to continue exploring this space in the future.&lt;/p&gt;
&lt;p&gt;I also started gardening for the first time this year. Just a simple raised bed, but I also read about permaculture (and ecology) this year, and hope to grow the hobby over then next few years.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;Software/App&lt;/strong&gt;: &lt;a href=&quot;http://typora.io&quot;&gt;Typora&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Lastly, I ended up selecting &lt;a href=&quot;http://typora.io&quot;&gt;Typora&lt;/a&gt; for my software app this year. With the website redo, I can more easily use &lt;em&gt;normal&lt;/em&gt; markdown editors for my posts. After trying a few, Typora has become my favorite for writing some of my drafts (including this one). Additionally, it has been helpful at work. When I want to export a markdown note I wrote in &lt;a href=&quot;http://obsidian.md&quot;&gt;obsidian&lt;/a&gt;, but want it formatted nicer for an email, Typora’s formatting stays when I copy and paste to Gmail. This is a game changer.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/2023-review-picks/T9E9GF4inQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/2023-review-picks/T9E9GF4inQ-1200.jpeg&quot; alt=&quot;&quot; A=&quot;&quot; bunch=&quot;&quot; of=&quot;&quot; plants=&quot;&quot; in=&quot;&quot; a=&quot;&quot; garden=&quot;&quot; bed.&quot;&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I gardened for the first time in 2023 (with help from my wife)&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Those are my 2023 picks. Not terribly exciting, but they do create a snapshot of &lt;em&gt;my&lt;/em&gt; 2023, which I think is interesting. I’ve already setup my 2024 note, and have a few items to add as potential picks already. We’ll see how they fair as the entire year continues to fall into place.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Traded my 14&quot; M1 Pro MBP for a 16&quot; M3 Pro MBP</title>
    <link href="https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/" />
    <updated>2023-12-19T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/f8plBr9tCl-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/f8plBr9tCl-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;931&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Stacked 14&quot; and 16&quot; MacBook Pros&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Welp, I’ve done it again. I traded in &lt;a href=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro&quot;&gt;my 14&amp;quot; M1 Pro Macbook Pro&lt;/a&gt; for a 16&amp;quot; M3 Pro MacBook Pro, which I received a few weeks ago. Proving I could keep a Mac for over a year (2 years &lt;em&gt;and&lt;/em&gt; 1 day! 😆), I allowed myself to do a custom build. But more on that later.&lt;/p&gt;
&lt;h2&gt;Why Change from the 14&amp;quot; M1 Pro?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/nnXcqVeCqg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/nnXcqVeCqg-1200.jpeg&quot; alt=&quot;16&quot; M3=&quot;&quot; Pro=&quot;&quot; MBP=&quot;&quot; on=&quot;&quot; left=&quot;&quot; and=&quot;&quot; 14&quot;=&quot;&quot; M1=&quot;&quot; right&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;622&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;16&quot; M3 Pro MBP on left and 14&quot; M1 Pro MBP on right.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Overall, the 14&amp;quot; M1 Pro has served me very well during the last few years, and largely still does. The CPU and general performance are wonderful and I would &lt;em&gt;still&lt;/em&gt; recommend this model to those that want to find a nice discounted MacBook. My upgrade to the M3 Pro has definitely been more of a &lt;em&gt;want&lt;/em&gt; than &lt;em&gt;need&lt;/em&gt;. With that said, there were a few changes I wanted for my next computer.&lt;/p&gt;
&lt;p&gt;First, the 512GB SSD was a little tight and annoying to &lt;em&gt;always&lt;/em&gt; be cleaning up. Similarly, while the 16GB RAM was doable, the ‘Memory Pressure’ would climb when I would be working on several things, so I wanted a bit more head room there as well.&lt;/p&gt;
&lt;p&gt;Mainly, I wanted a bigger display. I’ve learned that I appreciate having ample screen space when using my computer away from my desk, and I do that more often now. The 14&amp;quot; is good compromise for most, but I still felt like I was using a &lt;em&gt;laptop&lt;/em&gt; with it solo on a table.&lt;/p&gt;
&lt;p&gt;I also realized that my laptop doesn’t &lt;em&gt;need&lt;/em&gt; to be crazy portable anymore. After getting the &lt;a href=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/&quot;&gt;iPad Magic Keyboard&lt;/a&gt; for my &lt;a href=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/&quot;&gt;11&amp;quot; M1 iPad Pro&lt;/a&gt;, I’ve been able to grab that whenever I wanted a truly small and portable device. If there is something the iPad can’t manage, I typically want a larger screen for that task anyway. The magic keyboard was a game changer, not only for my iPad, but also for shifting my computer’s requirements.&lt;/p&gt;
&lt;p&gt;After purchasing the magic keyboard, I figured my next personal computer upgrade would likely be a switch to the 16&amp;quot; MBP. Beyond fitting my work use case, a big reason I decided on the &lt;a href=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/&quot;&gt;16&amp;quot; at work&lt;/a&gt; was so that I could compare having it with the 14&amp;quot; I had at home.&lt;/p&gt;
&lt;h2&gt;What About the 15&amp;quot; Air?&lt;/h2&gt;
&lt;p&gt;When the 15&amp;quot; M2 Air was released, I thought “Oh wow that looks nice. I wish I could make &lt;em&gt;that&lt;/em&gt; could work for my needs”… which led me to evaluate: &lt;em&gt;could it?&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A larger screen was my &lt;em&gt;main&lt;/em&gt; motivation for wanting to switch things up from the 14&amp;quot;.&lt;/li&gt;
&lt;li&gt;I loved that the 15&amp;quot; Air had the larger screen, but maintained the weight and portability of my 14&amp;quot;. The best of both worlds.&lt;/li&gt;
&lt;li&gt;I wasn’t hurting for more performance. An M3 or even the M2 wouldn’t be too far off the performance of my binned 8-core M1 Pro.&lt;/li&gt;
&lt;li&gt;The significantly lower price point compared to a 16&amp;quot; Pro meant it was reasonable to max out the RAM and SSD (which is something I &lt;em&gt;did&lt;/em&gt; want), and still be below the  price of a 16&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the next few months, I was convinced that my next device would be a 15&amp;quot; Air. I just wanted to wait for M3 chips to be announced, and see if it was worth getting that or a discounted M2.&lt;/p&gt;
&lt;h2&gt;Deciding on the 16&amp;quot; Pro (again)&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/ffR4RLvieV-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/ffR4RLvieV-1200.jpeg&quot; alt=&quot;16&#92;&quot; M3=&quot;&quot; MBP=&quot;&quot; setup=&quot;&quot; on=&quot;&quot; table=&quot;&quot; with=&quot;&quot; 11&#92;&quot;=&quot;&quot; portrait=&quot;&quot; iPad=&quot;&quot; Pro=&quot;&quot; next=&quot;&quot; to=&quot;&quot; it&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;723&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 16&quot; MBP is a solid all in one computer.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When the full M3 MacBook Pro line was announced, I started thinking about what I actually wanted. With the M3 chip also released, I had a &lt;em&gt;rough&lt;/em&gt; idea of what to expect from an M3 15&amp;quot; air, likely available in a few months. I started comparing the two, and kept coming back to the 16&amp;quot; Pro being a better fit for all my needs and wants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The 16 has amazing speakers, which I do very much enjoy and use often.&lt;/li&gt;
&lt;li&gt;The screen is better. I didn’t care before, but I like having an HDR display for photos.&lt;/li&gt;
&lt;li&gt;Support for 2 external displays.&lt;/li&gt;
&lt;li&gt;Better IO - an extra TB port, HDMI, and SD can all be useful to have when tinkering.&lt;/li&gt;
&lt;li&gt;Some more performance overhead&lt;/li&gt;
&lt;li&gt;More RAM for the upgrade (36GB vs max of 24GB) … for a cost of course 😉.&lt;/li&gt;
&lt;li&gt;I don’t actually &lt;em&gt;need&lt;/em&gt; this computer to be the most portable. If anything, the 15 Air’s size would be a benefit for my &lt;em&gt;work&lt;/em&gt; laptop.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why not the M3 Max then?&lt;/h2&gt;
&lt;p&gt;If was looking to upgrade the M3 Pro to 36GB RAM and 1TB of storage, why not just consider the base model (14 Core CPU/30 Core GPU) M3 Max configuration? It starts with those specs as a “base model”. I did. Especially when I looked at the difference in shipping times 😆.&lt;/p&gt;
&lt;p&gt;But it was still a $400 upgrade (slightly less after using a discount). For that amount, I would get 2 more supported external monitors, better CPU and GPU performance, double the memory bandwidth, a second encoder engine, and I’m sure a few other things I’m missing. Oh, and an extra 0.1 lbs in weight.&lt;/p&gt;
&lt;p&gt;But the thing is… I don’t need most of that. I’ve already dedicated a whole section of this post talking about how performance-wise, I would be completely fine with a 15&amp;quot; Air. It was &lt;em&gt;mostly&lt;/em&gt; features that are not related to the performance of the SOC that swung me back to the Pro. So, the Max isn’t worth it in my case. Maybe if I used a ton of monitors at my desk, or &lt;em&gt;actually played&lt;/em&gt; games, things would be different.&lt;/p&gt;
&lt;p&gt;Besides, while it was criticized in all the reviews, the M3 Pro chip has a 6 efficiency/6 performance core setup that I am very interested by. As someone that watches CPU monitors way too much and is obsessed with energy efficiency, I know those tiny efficiency cores &lt;em&gt;get shit done&lt;/em&gt; for very little. I’m excited to have a system with 6 of them.&lt;/p&gt;
&lt;h2&gt;Why not a M1 or M2 Refurb/Discount?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/hlxz_oj0rt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/hlxz_oj0rt-1200.jpeg&quot; alt=&quot;16&quot; M3=&quot;&quot; MBP=&quot;&quot; box&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;851&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The shipped 16&quot; M3 Pro MBP.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is something I did consider. The main reason I didn’t get an older model is that I wanted to go through Apple and trade in my old base model 14 M1 Pro (8 core). I prefer using their trade-in system over the hassle of selling devices myself, and the $905 they offered wasn’t bad. That ruled out options like the B&amp;amp;H, BestBuy, and Amazon Black Friday sales. Additionally, the Apple refurbished store didn’t have a configuration I wanted at the time.&lt;/p&gt;
&lt;h2&gt;…Not Space Black?&lt;/h2&gt;
&lt;p&gt;If I went with the newer M3 Pro model, why didn’t I get its ‘&lt;em&gt;biggest feature&lt;/em&gt;’… the space black finish? From what I can tell (I haven’t seen it in person), the new finish looks lovely and I &lt;em&gt;was&lt;/em&gt; tempted. That being said, I really hate the Space &lt;em&gt;Grey&lt;/em&gt; of my work laptop and didn’t want to take the risk with my personal laptop.&lt;/p&gt;
&lt;p&gt;On top of that, I am really fond of silver. It’s simple, easier to keep clean, and looks really gorgeous on this generation of MacBook Pro, with the thin black bezel and black keyboard well.&lt;/p&gt;
&lt;h2&gt;What I will miss from the 14&amp;quot;&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/Ok_HFrtFDA-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/Ok_HFrtFDA-1200.jpeg&quot; alt=&quot;14&quot; MBP=&quot;&quot; on=&quot;&quot; a=&quot;&quot; table=&quot;&quot; with=&quot;&quot; split=&quot;&quot; keyboard=&quot;&quot; near=&quot;&quot; it&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;860&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I will miss  how the 14&quot; M1 Pro felt to simply pick up and walk around with while working on projects.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With my 16&amp;quot; M3 Pro MacBook Pro in hand for a few weeks, and my old 14&amp;quot; M1 Pro traded in, there are a few things I imagine I will miss about the 14:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The &lt;em&gt;Feel&lt;/em&gt;&lt;/strong&gt;: I’ll miss how the 14&amp;quot; feels when held. Walking over to a table, picking it up with one hand and walking away just &lt;em&gt;feels so good&lt;/em&gt;. The 16 &#39;s heft does take away from that a bit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Having my work and personal computers differ&lt;/strong&gt;: I liked that I had the 14 for personal use and 16 for work. Switching back and forth between them meant that I was always able to appreciate their strengths by comparison. The 14&amp;quot; always felt so easy to grab and go, while still having &lt;em&gt;just enough&lt;/em&gt; screen space for most tasks. Using it always made my 16&amp;quot; feel more expansive. Without the constant contrast of the 14&amp;quot;, I’m afraid I’ll eventually get used to the 16&amp;quot; and it will feel standard or even small to me. I’ll also miss the cascading look of the 14 and 16 in my double laptop stand.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m sure there will be more that I miss over time as nostalgia snowballs, but initially, these are what come to mind.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/WfVsxrYNWc-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1prombp14-for-m3prombp16/WfVsxrYNWc-1200.jpeg&quot; alt=&quot;16 MBP on desk hooked up to a monitor&quot; width=&quot;1200&quot; height=&quot;894&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My 16&quot; M3 Pro MBP on my desk, starting this post.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So far using the M3 Pro 16&amp;quot; MacBook Pro has been superb. Its size fits perfectly for how I use my personal computer at this point in my life. On top of that, the additional RAM and storage has made a huge quality of life improvement too. I haven’t traveled with it yet, but with the Holidays approaching I’m excited to see how that goes.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Website Redo 2023</title>
    <link href="https://ryan.himmelwright.net/post/website-redo-2023/" />
    <updated>2023-12-04T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/website-redo-2023/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-redo-2023/49g_rLasn0-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-redo-2023/49g_rLasn0-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Oregon Inlet, Outer Banks, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;If you frequently visit this website (do those people exist?), you might have
noticed things suddenly look a bit… different. After a long period long period of being eager to overhaul of my website, I finally did. Here’s the why and how.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-redo-2023/tYcU30xh-8-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-redo-2023/tYcU30xh-8-1200.jpeg&quot; alt=&quot;Ecograder report for a post of old website. It has a score of 57/100.&quot; width=&quot;1200&quot; height=&quot;818&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Ecograde report for a post on the old website&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have enjoyed my website’s theme and layout since I &lt;a href=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/&quot;&gt;switched to hugo&lt;/a&gt;, over six years ago. However, there are a few areas that I’ve wanted to improve, such as trimming down the website’s pointless bloat. This past year, I dug more into sustainability topics, including reading about &lt;a href=&quot;https://sustainablewebdesign.org&quot;&gt;sustainable web design&lt;/a&gt;, &lt;a href=&quot;https://ecograder.com&quot;&gt;ecograder&lt;/a&gt;, and how &lt;a href=&quot;https://ryan.himmelwright.net/post/website-redo-2023/&quot;&gt;low-tech magazine&lt;/a&gt; &lt;a href=&quot;https://solar.lowtechmagazine.com/2023/06/rebuilding-a-solar-powered-website/&quot;&gt;rebuilt their website&lt;/a&gt;. Expectedly, this site did &lt;em&gt;not score&lt;/em&gt; well on &lt;a href=&quot;https://ecograder.com&quot;&gt;ecograder&lt;/a&gt; or &lt;a href=&quot;https://gtmetrix.com&quot;&gt;gtmatrix&lt;/a&gt;, and I had to change that.&lt;/p&gt;
&lt;p&gt;In addition, I have become increasingly concerned with how &lt;a href=&quot;https://thewebisfucked.com&quot;&gt;terrible the web is becoming&lt;/a&gt;. This has led me to retreat a bit and rely more on &lt;code&gt;rss&lt;/code&gt;. I want to start consuming more of the web directly from curated personal blogs, rather than bloated websites and horrible AI-generated articles created solely to improve SEO. As I have found and added websites to my feed list, I have been inspired by the simplicity of many of these sites. Browse around &lt;a href=&quot;https://512kb.club&quot;&gt;the 512kb club&lt;/a&gt; list to see what I mean. People are focused on the content of the site, and not the ads. It is clear that many of these blogs serve as the author’s “home” on the web.&lt;/p&gt;
&lt;h2&gt;Goals&lt;/h2&gt;
&lt;p&gt;With those two areas of motivation, I wanted to finally redo my personal site. Before starting, I brain-dumped a list of goals for the project to help guide its design:&lt;/p&gt;
&lt;h3&gt;1. Lower environmental/carbon footprint&lt;/h3&gt;
&lt;p&gt;I wanted my site to be better optimized and not a resource hog. Websites from &lt;a href=&quot;https://512kb.club/&quot;&gt;The 512kb Club&lt;/a&gt; are a good example, and a potential goal for this one.&lt;/p&gt;
&lt;h3&gt;2. Smaller and easier image system&lt;/h3&gt;
&lt;p&gt;I wanted to use either dithered or compressed images throughout the site, at least for initial page loads (but maybe still &lt;em&gt;link&lt;/em&gt; to the higher quality originals).&lt;/p&gt;
&lt;p&gt;I also wanted it to be easy to mange. I would love to use normal markdown formatting to add images instead of hard coded html, or Hugo short codes. Compatibility with ‘normal’ markdown editors and previews would be another bonus.&lt;/p&gt;
&lt;h3&gt;3. No javascript&lt;/h3&gt;
&lt;p&gt;I don’t think I have a need for JS on my site. So why bother with it?&lt;/p&gt;
&lt;h3&gt;4. New theme to support the above items&lt;/h3&gt;
&lt;p&gt;A Minimal theme that focuses on content. Simplify colors, and fonts.&lt;/p&gt;
&lt;h3&gt;5. Remove/Replace comment system&lt;/h3&gt;
&lt;p&gt;(I had already done this. I couldn’t wait) Remove for now. Maybe add a static system in the future.&lt;/p&gt;
&lt;h3&gt;6. Switching from a strictly tech to a personal blog&lt;/h3&gt;
&lt;p&gt;I want this to be my &lt;em&gt;home&lt;/em&gt; on the internet too. As part of that, I wanted the possibly to write about interests beyond tech. Maybe gardening, life experiments, non-tech reviews, thoughts, etc. &lt;em&gt;(see rss items below)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;7. Improve rss feeds&lt;/h3&gt;
&lt;p&gt;I want to have rss feeds setup per category, so people can subscribe to individual topics (ex: tech only) if they wish.&lt;/p&gt;
&lt;p&gt;I also need to maintain support for &lt;a href=&quot;https://ryan.himmelwright.net/post/rssonly-posts-in-hugo/&quot;&gt;rss only posts&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;8. Easy to write/maintain&lt;/h3&gt;
&lt;p&gt;I’d love to be able to draft pages in something like obsidian or other editors without having to worry about the hugo server running during writing. Being able to draft or edit on my phone and/or iPad would also be great.&lt;/p&gt;
&lt;h3&gt;9. Change ‘Homelab’ to a ‘Uses’ Pages&lt;/h3&gt;
&lt;p&gt;This is basically what I want, and it seems to be the &lt;a href=&quot;https://uses.tech&quot;&gt;standard amongst personal tech blogs&lt;/a&gt;. My ‘Homelab’ page was a cluttered mess and had way too much stuff on it. I also don’t really maintain a “homelab” anymore.&lt;/p&gt;
&lt;h3&gt;10. Fix the CI/CD/Testing/Deployment pipeline process&lt;/h3&gt;
&lt;p&gt;I want to still run tests, but I want them to work without being too picky. The setup should be easy to do on any of my machines or pipelines. (CI needs to work with redo. Tests can come later)&lt;/p&gt;
&lt;h2&gt;The Redo Process&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-redo-2023/0iHN5bi6Hu-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-redo-2023/0iHN5bi6Hu-1200.jpeg&quot; alt=&quot;A laptop with split keyboard, ergo mouth, and portable monitor.&quot; width=&quot;1200&quot; height=&quot;1000&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My portable setup while working on the redo on vacation.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I began the redesign at the beginning of October (while on Vacation). I decided to start with the theme, so I created a new &lt;code&gt;hugo&lt;/code&gt; theme from scratch, and used &lt;a href=&quot;https://simplecss.org&quot;&gt;simple.css&lt;/a&gt; for the starting style sheet. I continued to tweak the style and layout a bit to have the site fit what I wanted.&lt;/p&gt;
&lt;p&gt;After some initial tweaks, I worked on adding a render image hook. This would allow me to use standard markdown formatting (ex:&lt;code&gt;![alt text](image_src_path &amp;quot;caption&amp;quot;)&lt;/code&gt;) in my content files, and &lt;code&gt;hugo&lt;/code&gt; would know how to convert it to a particular html formatting when rendering the site (including doing the image scaling and adding the caption). Eventually, with a bunch of trial and error and help from &lt;a href=&quot;https://stereobooster.com/posts/hugo-ideal-image/&quot;&gt;blog posts&lt;/a&gt;, I hit a point where the theme was good &lt;em&gt;enough&lt;/em&gt; and I had the image hooks working.&lt;/p&gt;
&lt;p&gt;Next, I moved onto converting all the content files to use the new hook. During this step, I also converted all the pages into &lt;a href=&quot;https://gohugo.io/content-management/page-bundles/&quot;&gt;leaf bundles&lt;/a&gt;. This took &lt;em&gt;&lt;strong&gt;FOREVER&lt;/strong&gt;&lt;/em&gt;. In hindsight, I should have attempted to script some of this process.&lt;/p&gt;
&lt;p&gt;Once everything was &lt;strong&gt;finally&lt;/strong&gt; converted, I worked on some remaining miscellaneous tasks, including redoing the rss feeds which involved adding new category-specific ones. I also cleaned up small issues with the theme, and added a &lt;code&gt;Hugo&lt;/code&gt; &lt;em&gt;partial&lt;/em&gt; to compress and convert images, which I applied to the header images in the layout code.&lt;/p&gt;
&lt;p&gt;Lastly, I updated the site content pages, and prepared to push all the changes.&lt;/p&gt;
&lt;h3&gt;Deployment Woes&lt;/h3&gt;
&lt;p&gt;With everything ready, I merged the &lt;em&gt;massive&lt;/em&gt; working branch into my website’s &lt;code&gt;main&lt;/code&gt; branch to deploy… and hit a string of issues.&lt;/p&gt;
&lt;p&gt;First, there were some build errors related to the image hooks. After investigating, I think it’s because they utilize a new feature not included in the older version of &lt;a href=&quot;https://hugo.io&quot;&gt;Hugo&lt;/a&gt; my &lt;a href=&quot;https://gitlab.com&quot;&gt;Gitlab&lt;/a&gt; runners had. I tried resolving this by swapping out the build container to different images with newer hugo versions, but it didn’t help. I eventually settled on configuring my Mac mini server to be a gitlab runner I could use until supported versions are in the CI images.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;builds&lt;/em&gt; worked using the Mac mini runner… but during the deployment step I hit another issue:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;ERROR: Uploading artifacts as &lt;span class=&quot;token string&quot;&gt;&quot;archive&quot;&lt;/span&gt; to coordinator&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;413&lt;/span&gt; Payload Too Large &lt;span class=&quot;token assign-left variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5531626753&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;responseStatus&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;413&lt;/span&gt; Payload Too Large &lt;span class=&quot;token assign-left variable&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;413&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;64_ptorz

FATAL: too large&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It turns out the limit for a &lt;a href=&quot;https://docs.gitlab.com/ee/user/project/pages/&quot;&gt;Gitlab Pages&lt;/a&gt; build is 1 GB. The redesign had grown my site (mostly due to generating several compressed image files per image) to… just over 1GB. I could have worked to minimize the size, but that would only be kicking the can down the road. So, I decided to instead switch where I host the site.&lt;/p&gt;
&lt;p&gt;I learned how to configure an ssl cert for my &lt;a href=&quot;https://getfedora.org&quot;&gt;fedora&lt;/a&gt; Linode, and pushed the build to the &lt;a href=&quot;https://www.nginx.com/&quot;&gt;nginx&lt;/a&gt; server running on it. This was actually the test server I used during the rebuilding process, so it was already setup to host the site.&lt;/p&gt;
&lt;p&gt;Finally… it was deployed.&lt;/p&gt;
&lt;p&gt;One &lt;em&gt;final_final&lt;/em&gt; step I decided to take was moving the source into a fresh git repo and pushed to a new gitlab project. I have the old one archived for its history, but wanted to start clean with this redo. I took some time to update the CI scripting for the new deployment process, but after that, I was &lt;em&gt;actually done&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-redo-2023/CeIjsrKeaO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-redo-2023/CeIjsrKeaO-1200.jpeg&quot; alt=&quot;Two windows of the website: light theme on left and dark theme on the right&quot; width=&quot;1200&quot; height=&quot;687&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The new website theme, in both light and dark modes.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While I was ‘&lt;em&gt;done&lt;/em&gt;’ with the process, I still had to write this post 😆. To be honest, I’ve pushed it off for a few weeks now. This redo was such a massive endeavor that this post only scratches the surface of everything I did. That said, I am happy with the result. There are still tweaks I want to make, but the site now accomplishes all the goals I defined when I set out on this project.&lt;/p&gt;
&lt;p&gt;The website is much leaner and scores significantly better in performance metrics. At the same time, it is easier for me to actually write the posts. In fact, I wrote much of this in &lt;a href=&quot;https://typora.io&quot;&gt;Typora&lt;/a&gt;, and even &lt;em&gt;some&lt;/em&gt; of the draft from my &lt;em&gt;iPad&lt;/em&gt; (and not just ssh’d to my computer)! I’m glad this work is finally done, and I’m excited to &lt;em&gt;use&lt;/em&gt; the new site now.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Quick Jira Card Links in Obsidian</title>
    <link href="https://ryan.himmelwright.net/post/quick-jira-links-obsidian/" />
    <updated>2023-09-25T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/quick-jira-links-obsidian/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/quick-jira-links-obsidian/Sm6rsFdfRf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/quick-jira-links-obsidian/Sm6rsFdfRf-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;771&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Duke Park, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I record notes in obsidian all day. I keep daily logs to plan out my days, and sometimes write separate meetings notes. In both cases, I usually refer to Jira cards. Recently, I grew tired of writing out the obsidian and url links every time I wanted to reference a Jira card. So I found a better way.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;In my daily log, I have a section where I &lt;a href=&quot;https://calnewport.com/deep-habits-the-importance-of-planning-every-minute-of-your-work-day/&quot;&gt;timeblock&lt;/a&gt; the day. I generally try to ensure what I am working on can be traced back to a Jira card. While working on a card, I also like to have a corresponding note in obsidian where I can keep a running log of my work and thoughts. When I reference a card in the daily planner, I link to the jira note for that card (even if it is just to get the benefit of auto complete 😆). For example, I might have:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token italic&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;Morning&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 08:30 Email and MR Review #general
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 09:30 [[Team Meeting]] #meeting
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 10:00 [[JIRA-9999]] #qe_dev
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 11:00 [[Project Meeting]] #meeting
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 12:00 Lunch #lunch&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, I link to common meetings, but more importantly, a Jira card note (&lt;code&gt;JIRA-9999&lt;/code&gt;) to track when I am working on that card (or at least plan to).&lt;/p&gt;
&lt;p&gt;I make obsidian links for any jira card referenced in a meeting, even if I don’t create a note for the card. That way, if I ever &lt;em&gt;do&lt;/em&gt; instantiate an obsidian note for the Jira card, obsidian will already have all the back links pointing at it. For example, during backlog refinement meetings, I write links for each card discussed. If someone has a question about the card, or I pick it up later to work on, I already have the note linked to &lt;em&gt;every&lt;/em&gt; time I’ve referred to the card in my notes.&lt;/p&gt;
&lt;p&gt;Over time, I learned that it’s nice to not only have the link for the obsidian note, but also a quick reference to the &lt;em&gt;url&lt;/em&gt; for the Jira card (ex: &lt;code&gt;[[JIRA-9999]]([link](https://jiraserver.com/JIRA-9999))&lt;/code&gt;, viewed as: &lt;code&gt;Jira-9999 (url))&lt;/code&gt; in the note).&lt;/p&gt;
&lt;h2&gt;The Quick-Add Plugin&lt;/h2&gt;
&lt;p&gt;After starting to include jira urls more often, I wanted to template the process. I knew it shouldn’t be too hard, as the only unique characteristic in both types of links was the Jira card number. This is especially true when considering that 90% of the cards I reference are from the same project.&lt;/p&gt;
&lt;p&gt;I first tried the built-in template system in Obsidian. However, I wanted a pop-up to prompt me for a card number that I could quickly type in. After browsing around the community plugins, I came across the &lt;a href=&quot;https://github.com/chhoumann/quickadd&quot;&gt;QuickAdd&lt;/a&gt; plugin and installed it.&lt;/p&gt;
&lt;h2&gt;Setting up Jira Links&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/quick-jira-links-obsidian/Kd5d8FPc3--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/quick-jira-links-obsidian/Kd5d8FPc3--1200.jpeg&quot; alt=&quot;Quick add settings to insert link at cursor of current file.&quot; width=&quot;1200&quot; height=&quot;1442&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Quick add settings to insert link at cursor of current file.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I found the QuickAdd plugin a little confusing to understand at first. It defines several types of shortcuts, which weren’t exactly clear to me how they worked. Regardless, for my needs, I settled on the “capture” type after playing around with the different options. I was able to use the following format to create a quick link for my team project jira cards:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;[[PROJECT-]](&lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;Jira link&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://JIRA-INSTANCE.com/browse/PROJECT-&lt;/span&gt;)&lt;/span&gt;) &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also made a more generic one to add cards/links from other teams when I need to. It’s the same format except that I provide the full card id (including project stub) in the pop-up prompt:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;[[]](&lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;Jira link&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://JIRA-INSTANCE.com/browse/&lt;/span&gt;)&lt;/span&gt;) &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also setup a shortcut in the Obsidian settings to launch quick-add. So now, I just hit CMD-CTRL-J and a pop-up window opens where I either select the project or generic template. Then, a second pop-up opens, where I can enter either the card ID for the generic template, or just the &lt;em&gt;number&lt;/em&gt; for my project’s specific one.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Ex: Entering &amp;quot;9999&amp;quot; in the project prompt will insert this into obsidian:

[[PROJECT-9999]]([Jira link](https://JIRA-INSTANCE.com/browse/PROJECT-9999)) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After hitting enter, my fully formatted obsidian + jira url markdown link is inserted at my cursor. Wonderful!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So far, this setup has been great. It’s allowed me to quickly enter proper links to jira cards while taking notes. I can add full links to the notes in real time, and not need to go back and clean things up. Also, having the url whenever I refer to jira cards in obsidian has been super handy when I’m reading through my notes and want to double check something in the real card. Another simple, but very effective solution!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting Up RssOnly Posts in Hugo</title>
    <link href="https://ryan.himmelwright.net/post/rssonly-posts-in-hugo/" />
    <updated>2023-08-27T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/rssonly-posts-in-hugo/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rssonly-posts-in-hugo/n-D7_x4gNG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rssonly-posts-in-hugo/n-D7_x4gNG-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Downtown Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Awhile ago, I wanted to write posts that would show up
in the website’s &lt;a href=&quot;https://ryan.himmelwright.net/post/index.xml&quot;&gt;rss feed&lt;/a&gt;, but not be linked to anywhere
else on the site. Unfortunately, this isn’t an option you can simply toggle on in
&lt;a href=&quot;https://gohugo.io&quot;&gt;hugo&lt;/a&gt; (that I could find). So, I implemented it myself in
my theme layout.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Marking a post as rss only&lt;/h2&gt;
&lt;p&gt;The first thing I needed was a way to mark which pages should be published only
in rss. For that, I created a marker which can be added in the header meta data
of those posts:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;Rssonly = &quot;True&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don’t think I have to define it anywhere. If it’s in the header of posts, it
can be used in the templates.&lt;/p&gt;
&lt;h2&gt;Adding Home Page Filters&lt;/h2&gt;
&lt;p&gt;With a way to mark posts as rss-only, I could start adding filters so that
those posts wouldn’t show elsewhere on the site. The first place was to remove
them from the listing on the homepage, so I went to it’s layout file:
&lt;code&gt;/layouts/index.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There, I took the following line of code, which grabs pages to list
on the home page:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;section &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; where &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RegularPages &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Section&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;in&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;mainSections &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and replaced it with this one:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;section &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; where &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;where &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RegularPages &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Section&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;in&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;mainSections&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Params.rssonly&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!=&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The change omits posts that have the &lt;code&gt;rssonly&lt;/code&gt; metadata set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Adding Archive Filters&lt;/h2&gt;
&lt;p&gt;Next, I wanted to remove the rss-only page from appearing on the posts archive
page (or any other post lists), so I went to my ‘list’ layout at
&lt;code&gt;/layouts/_default/list.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This time, I modified the &lt;code&gt;.Paginator.Pages&lt;/code&gt; to filter out rss-only posts:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; range &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paginator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Pages &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;modified to:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; range where &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paginator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Pages &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.Params.Rssonly&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!=&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Including a message at the top of &lt;code&gt;Rssonly&lt;/code&gt; posts&lt;/h2&gt;
&lt;p&gt;Lastly (so I thought), I wanted to add a special case for the layout to
automatically add a message at the top of rss-only pages, to declare they were
rss-only. So, I added the following snippet in my &lt;code&gt;/layouts/post/single.html&lt;/code&gt;
file, right above the &lt;code&gt;{{ .Content }}&lt;/code&gt; line:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;{{ if .Params.rssonly }}
    RSSONLY MESSAGE                    
{{ end }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At that point, I &lt;em&gt;thought&lt;/em&gt; I was done, so I started writing this post to document
and share my work.&lt;/p&gt;
&lt;h2&gt;Next/Prev post fix&lt;/h2&gt;
&lt;p&gt;However, as I was writing, I noticed an issue. Posts that were
before or after an rss-only post, linked to the “rss-only” post in the
next/previous links at the bottom of the page.&lt;/p&gt;
&lt;p&gt;I looked into a fix quickly, but wasn’t able to figure out a quick solution at
the time. On top of that, the more I looked into solutions, the more I second
guessed my method altogether. I probably should have learned how to make a new
&lt;em&gt;type&lt;/em&gt; of page in hugo, which didn’t show in any of these lists. But that was
hard to figure out. Instead, I decided to put the fix and post draft on my
backlog for a month or two.&lt;/p&gt;
&lt;p&gt;I eventually picked up the problem again and managed to figure out a solution
for my prev/next page code (found in &lt;code&gt;/layouts/post/single.html&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Next Post/ Previous Post Links --&gt;&lt;/span&gt;
{{ $pages := where (where .Site.RegularPages &quot;Section&quot; .Section) &quot;.Params.Rssonly&quot; &quot;!=&quot; true }}
{{ if $pages.Next . }}
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Next Post:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
{{ end }}
{{ if $pages.Prev . }}
     &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Prev Post:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
{{ end }}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;br&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
{{ with $pages.Next . }}
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{{ .Permalink }}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 40%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ .Title }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
{{ end }}
{{ with $pages.Prev . }}
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{{ .Permalink }}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 40%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ .Title }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
{{ end }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, I to added the following line to make a newly filtered list of possible
pages:&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $pages &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; where &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;where &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RegularPages &lt;span class=&quot;token string&quot;&gt;&quot;Section&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Section&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.Params.Rssonly&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;!=&quot;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, I replaced the &lt;code&gt;.PrevInSection&lt;/code&gt; and &lt;code&gt;.NextInSection&lt;/code&gt; lines using that
&lt;code&gt;$pages&lt;/code&gt; variable: &lt;code&gt;$pages.Prev&lt;/code&gt; and &lt;code&gt;$pages.Next&lt;/code&gt;, which seem to work exactaly
how I want it.&lt;/p&gt;
&lt;p&gt;With that fix, I started to work on this post again 😅.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As far as I am aware, I &lt;em&gt;think&lt;/em&gt; that’s it. As I stated above, I’m sure
there are many &lt;em&gt;better&lt;/em&gt; solutions to enable rss-only posts in hugo. But this
worked for me. If you want to do something similar… maybe do a quick search
to make sure nothing obvious comes up first. Now I need to figure out how
to add some tests to check that this continues to work…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Nearly headless VMs Using utmctl</title>
    <link href="https://ryan.himmelwright.net/post/utmctl-nearly-headless-vms/" />
    <updated>2023-07-29T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/utmctl-nearly-headless-vms/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/utmctl-nearly-headless-vms/IbBsM7QVJz-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/utmctl-nearly-headless-vms/IbBsM7QVJz-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve been using &lt;a href=&quot;https://mac.getutm.app&quot;&gt;UTM&lt;/a&gt; since I &lt;a href=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/&quot;&gt;switched
to&lt;/a&gt; &lt;a href=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/&quot;&gt;Apple silicon
macs&lt;/a&gt;, but I recently
discovered it’s cli tool, &lt;code&gt;utmctl&lt;/code&gt;. It’s completely changed how I mange UTM and
VMs on my macs.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;How to install&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;utmctl&lt;/code&gt; is included when you install UTM (via the AppStore or
package download available on &lt;a href=&quot;https://mac.getutm.app/&quot;&gt;their website&lt;/a&gt;). Once
installed, you should have the &lt;code&gt;utmctl&lt;/code&gt; command in a terminal:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;iryan@venusaur ~ $ utmctl
OVERVIEW: CLI tool for controlling UTM virtual machines.

USAGE: utmctl &lt;subcommand&gt;

OPTIONS:
  -h, --help              Show help information.

SUBCOMMANDS:
  list                    Enumerate all registered virtual machines.
  status                  Query the status of a virtual machine.
  start                   Start a virtual machine or resume a suspended virtual machine.
  suspend                 Suspend running a virtual machine to memory.
  stop                    Shuts down a running virtual machine.
  attach                  Redirect the serial input/output to this terminal.
  file                    Guest agent file operations.
  exec                    Execute an application on the guest.
  ip-address              List all IP addresses associated with network interfaces on the guest.
  clone                   Clone an existing virtual machine.

  See &#39;utmctl help &lt;subcommand&gt;&#39; for detailed help.&lt;/subcommand&gt;&lt;/subcommand&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Commands&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;utmctl&lt;/code&gt; options are all rather straightforward. I most often use the
commands &lt;code&gt;utmctl list&lt;/code&gt;, &lt;code&gt;utmctl start VMNAME&lt;/code&gt;, and &lt;code&gt;utmctl ip-address VMNAME&lt;/code&gt;.
However, some of the other options, such as &lt;code&gt;exec&lt;/code&gt; and &lt;code&gt;attach&lt;/code&gt;, look
interesting.&lt;/p&gt;
&lt;h2&gt;A quick example - Running &lt;em&gt;(nearly)&lt;/em&gt; ‘headless’ VMs&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/utmctl-nearly-headless-vms/hD-b2b8GPv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/utmctl-nearly-headless-vms/hD-b2b8GPv-1200.jpeg&quot; alt=&quot;The display device in the UTM settings. Right click to remove.&quot; width=&quot;1200&quot; height=&quot;905&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The display device in the UTM settings. Right click delete.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The main improvement that &lt;code&gt;utmctl&lt;/code&gt; has provided me is the ability to run my VMs
“headless” (mostly), which I am accustomed to doing on Linux. To do this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install and configure a VM in UTM, and ensure that &lt;code&gt;ssh&lt;/code&gt; is &lt;em&gt;enabled&lt;/em&gt; so it
will automatically start on boot. Also, verify that you can properly &lt;code&gt;ssh&lt;/code&gt; into
the VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Enable ssh to start. Likely this command on most linux systems:&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; sshd&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Remove the virtual display in the VM’s settings. This will prevent the VM
window from opening while it is running, as there is no GUI for the VM to run.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;code&gt;utmctl&lt;/code&gt; to list or check the status of the VM(s).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ryan@venusaur ~ $ utmctl list
UUID                                 Status   Name
D77A861B-61DD-483A-BA09-D647C77EB77A stopped  nixos
EADB0899-8B20-48B9-BD23-B179E3C258B3 stopped  fedora

-- or --

ryan@venusaur ~ $ utmctl status fedora
stopped&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;utmctl&lt;/code&gt; to start the VM:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ryan@venusaur ~ $ utmctl start fedora

ryan@venusaur ~ $ utmctl status fedora
started&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;utmctl&lt;/code&gt; to get the ip address from the running VM (it might take a minute
until the VM is fully started to work):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ryan@venusaur ~ $ utmctl ip-address fedora
&lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.64.33
fda6:d2b:ac2e:c75a:f87:fc14:d8fc:ef8d
fe80::6b82:d89b:3f27:9c3d&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssh&lt;/code&gt; into the VM using the ip-address:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ryan@venusaur ~ $ &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; ryan@192.168.64.33
Last login: Fri Jul &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; 05:43:17 &lt;span class=&quot;token number&quot;&gt;2023&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ryan@fedora ~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token comment&quot;&gt;# I&#39;m in the VM!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Although I usually shutdown the VM from inside it (which is recommended), you
can alternatively shut it down using &lt;code&gt;utmctl&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ryan@venusaur ~ $ utmctl status fedora
started
ryan@venusaur ~ $ utmctl stop fedora
ryan@venusaur ~ $ utmctl status fedora
stopped&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After moving the display device, running these steps may still leave the UTM
application open in the Dock, but the VM window should not pop up. It’s why I
consider this to be a “mostly headless” process.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I love a simple but useful cli tool, and &lt;code&gt;utmctl&lt;/code&gt; has been just that. I am glad
that the team added it, and that it continues to get updates. For
me, it immensely improves my experience using UTM. In fact, while writing this
post I finally went and bought the App Store version of UTM to help support the
developers. If you also love UTM, I encourage you to do the same.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trying Choc Sunset Switches</title>
    <link href="https://ryan.himmelwright.net/post/trying-choc-sunsets/" />
    <updated>2023-07-01T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/trying-choc-sunsets/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/dTD0MS0zBT-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/dTD0MS0zBT-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Downtown Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A couple weeks ago, I finally ordered a set of &lt;a href=&quot;https://keebd.com/en-us/products/sunset-tactile-choc-switches&quot;&gt;choc sunset
switches&lt;/a&gt; for my
&lt;a href=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/&quot;&gt;wireless ferris sweep&lt;/a&gt;. After using Kailh choc silver
switches daily for over a year, I wanted to try a &lt;em&gt;“good”&lt;/em&gt; choc tactile
switch. Here are some of my notes after several weeks of use.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;Before the ferris sweeps, my two previous keyboards (&lt;a href=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/&quot;&gt;HHKB
Pro&lt;/a&gt; and &lt;a href=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/&quot;&gt;ergodox ez&lt;/a&gt;)
each had tactile switches. Cherry MX Brown on the Ergodox and Topre (I know, but it’s close enough) on the HHKB. Before building my first &lt;a href=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/&quot;&gt;Ferris Sweep
Keyboard&lt;/a&gt;, I bought a sample pack of choc
switches, and the tactile options were all disappointing. They just felt like
scratchy linear switches.&lt;/p&gt;
&lt;p&gt;At the time, a new ‘sunset’ switch was being developed and had the community
hopeful for a better option, but it wasn’t quite ready yet. So I instead
decided to see how lighter, linear switches feel on a low profile board, and
picked choc silvers. I enjoyed the silvers enough that when I built my second
ferris sweep, I ordered them again to keep the experience between the two
boards similar.&lt;/p&gt;
&lt;h2&gt;Ordering the Sunsets&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/GEVYSACaRp-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/GEVYSACaRp-1200.jpeg&quot; alt=&quot;The package of choc sunset switches&quot; width=&quot;1200&quot; height=&quot;895&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The package of 40 delivered choc sunset switches.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After failing to get the batteries on my wireless ferris sweep to work, I was
looking around at various pre-built boards on the market (like the &lt;a href=&quot;https://lowprokb.ca/products/corne-ish-zen&quot;&gt;Corne-ish
Zen&lt;/a&gt;, for example). While searching,
realized I wasn’t sure what switches I’d want to get if I ever got a new choc
board (I haven’t yet).&lt;/p&gt;
&lt;p&gt;I love the silvers, but the now-available sunsets looked interesting. My
wireless ferris sweep has hot-swap sockets, so I decided to finally splurge
and get a set of choc sunsets for my board. I could try them out, and
have a spare set of choc switches on hand.&lt;/p&gt;
&lt;h2&gt;Setup &amp;amp; Initial Impressions&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/Tuxs1ROZ76-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/Tuxs1ROZ76-1200.jpeg&quot; alt=&quot;My wireless ferris sweep board with the sunset choc switches installed in&quot; width=&quot;1200&quot; height=&quot;976&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My wireless ferris sweep board with choc sunsets  installed.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I recorded my initial thoughts when first using the sunsets that I’ll include below. When they arrived, I pulled one out of the bag to hold:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“feels nice, slight bump, seemingly smooth…”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next, I placed the switch in the tester (with a key cap), adjacent to my normal
choc silver, to compare the two. My initial reaction surprised me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“… oh no!.. I’m not sure if like that… it feels dirty or something, and
not as smooth (duh)…”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, I swapped out the silvers for the sunsets on my board, and tested
typing on the full keyboard:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Better I guess… but man, it is different. The bump is noticeable, but it’s
not crazy when typing and I’m not overly thinking about it. I feel like I’m
‘popping’ the keys which I normally enjoy, (like on my 14” Macbook Pro), but
idk… I don’t love it  as much as I thought I would (at least initially). I
can see myself enjoying it more when I am used to it though. Right now, it
just feels heavier and weird from what I currently use.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;“It does feel &lt;em&gt;deeper&lt;/em&gt; or something too, more solid. That might be because the
travel feels heavier, or maybe the bump gives it the illusion of being a
bigger switch or something?.. I don’t know, but it is very different.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was surprised to learn just how much I’ve learned to love the &lt;em&gt;silvers&lt;/em&gt;
(considering how much I hated linears before using choc switches).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“My biggest take-away after using the sunsets for a few minutes though
is ‘huh, I think I actually &lt;em&gt;like&lt;/em&gt; (at least on low pro/choc switches), linear
switches rather than just &lt;em&gt;tolerating&lt;/em&gt; them…’. I think this is because I
bottom out regardless, and the silvers bottom out in a way which &lt;em&gt;feels&lt;/em&gt; good
to me. However, they are a nice med-light heaviness that I can type on
effortlessly and not bottom out if I want to.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After using linears for so long, I had some trouble initially getting used to the bump:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I can’t type as light, at least not yet on these (sunsets). Maybe when I
learn where the bump is eventually it will be better. I think after the bump I
just shoot to the bottom.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;“Also, I’m having trouble actuating some keys, especially things like the OSM shift and other mods. My brain might feel the bump and thinks it’s bottomed out or something on holds, idk. Going to keep playing with it and am interested to see how going back to the silvers feels…”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Despite my initial reactions, after using the sunsets a little more, specifically for some typing tasks, things got better:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;I’m warming up to them a bit more, which is nice to see.&lt;/li&gt;
&lt;li&gt;They feel fun, with the poppy-ness&lt;/li&gt;
&lt;li&gt;They are more fatiguing. My forearm feels a bit tired and I’m not sure if it’s related or not but I think it might be 😆&lt;/li&gt;
&lt;li&gt;Switching back and forth between this and the choc silvers is fine, but they feel &lt;em&gt;very&lt;/em&gt; light after.&lt;/li&gt;
&lt;li&gt;I like it’s lower sound. Some of it is the case, but even so it &lt;em&gt;feels&lt;/em&gt; less &lt;em&gt;tick-tick-tick&lt;/em&gt;-y…&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;After Some time with them&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/Bmgfo--LTp-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-choc-sunsets/Bmgfo--LTp-1200.jpeg&quot; alt=&quot;The choc silver and sunset switches&quot; width=&quot;1200&quot; height=&quot;853&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The choc silver (left) and sunset (right) switches.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After a few days using them at work, it got even better:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Much nicer today. I used them a bunch at work and tonight while writing. They’re closer to what the HHKB Pro or my 14&amp;quot; Macbook Pro have, with the ‘pop’. I do like it, but I also like the linears still. I think it might be a &#39;whatever mood I’m in will determine what I prefer at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;So now that I’ve used both choc silvers and sunsets on most days for several
months, what do I think? Well, I like the sunsets for their ‘pop’ and different
feel when typing. It feels more like using a larger mechanical keyboard to me,
which is a nice change. I also favor their lower sound compared to the
silvers.&lt;/p&gt;
&lt;p&gt;On these very small, low profile split keyboards though, I think I prefer the
silvers. They just pair nicely with how I use this type of board and
&lt;a href=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/&quot;&gt;layout&lt;/a&gt;. My fingers feel like they’re dancing
across the keys as I tap and hold home row modifiers, and switch between layers.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With that said, I still love the sunsets. They are a much better tactile choc
switch than any of the others I’ve tried. While using choc keyboards however, I
have learned that I prefer linear switches, and so would likely choose silvers
instead. I think the sunsets are a great switch and I highly recommend them to
anyone that wants a tactile choc switch. I’m glad I got mine and will continue
to enjoy the extra ‘pop’ in my board.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Enable Syntax Highlighting for .keymap Files in Neovim</title>
    <link href="https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/" />
    <updated>2023-06-06T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/vjvAmzCJu9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/vjvAmzCJu9-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;PNC Arena, Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve made a few &lt;em&gt;minor&lt;/em&gt; tweaks to my keyboard layout (both in qmk and zmk) in
the last few weeks. Each time, I’ve been annoyed with the non-highlighted text
of the &lt;code&gt;.keymap&lt;/code&gt; files when I edit them in neovim. I learned how to fix this,
and the solution is &lt;em&gt;very&lt;/em&gt; simple.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/VwtS3IMJ38-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/VwtS3IMJ38-1200.jpeg&quot; alt=&quot;The keymap file before syntax highlighting added.&quot; width=&quot;1200&quot; height=&quot;805&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My .keymap in neovim before fixing the syntax highlighting.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Neovim has been my editor of choice for awhile now, especially when in a terminal.
I converted my configuration to a &lt;code&gt;.lua&lt;/code&gt; setup, with treesitter for
syntax highlighting about a year ago. Generally, this works well.
However, the system didn’t recognize to use &lt;code&gt;c&lt;/code&gt; highlighting for my
keymap files.&lt;/p&gt;
&lt;h2&gt;:setfiletype&lt;/h2&gt;
&lt;p&gt;After some digging, I was reminded about the &lt;code&gt;setfiletype&lt;/code&gt; option in vim. This
lets you change the buffer’s file type. For example, &lt;code&gt;:setfiletype markdown&lt;/code&gt;.
When I tried setting the buffer to &lt;code&gt;c&lt;/code&gt;, everything looked &lt;em&gt;much&lt;/em&gt; better.&lt;/p&gt;
&lt;h2&gt;Adding to my Configuration&lt;/h2&gt;
&lt;p&gt;With a potential fix, I wanted to add it to the configuration to keep it
persistent. I didn’t feel like converting this to the equivalent &lt;code&gt;lua&lt;/code&gt;, so I
just used a &lt;code&gt;vim.cmd&lt;/code&gt; in the general file of my home-manager neovim
configuration:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Generally read .keymap files as C ones&lt;/span&gt;
vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cmd&lt;span class=&quot;token string&quot;&gt;[[autocmd BufNewFile,BufRead *.keymap setfiletype c]]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line tells vim to set the file type to &lt;code&gt;c&lt;/code&gt; for any file that ends in
&lt;code&gt;.keymap&lt;/code&gt;. Maybe one day I’ll convert it to &lt;code&gt;lua&lt;/code&gt;, but for now … it works 😄.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/eb-OvQxq7M-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/neovim-treesitter-keymap-syntax/eb-OvQxq7M-1200.jpeg&quot; alt=&quot;The keymap file with syntax highlighting added.&quot; width=&quot;1200&quot; height=&quot;805&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My .keymap in neovim after fixing the syntax highlighting.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After adding that line and rebuilding my home-manager config, neovim opens
&lt;code&gt;.keymap&lt;/code&gt; files with syntax highlighting on all my machines. Quick and simple!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>RSS-Only Posts</title>
    <link href="https://ryan.himmelwright.net/post/rss-only-test/" />
    <updated>2023-05-13T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/rss-only-test/</id>
    <content type="html">&lt;p&gt;I just figured out a way to enable ‘rss-only’ posts on my website. Basically,
it means if you’re getting and reading my posts via rss, you see this. If
you’re just browsing the website index… you won’t (however you can still
access the page via direct link).&lt;/p&gt;
&lt;p&gt;It’s a little nod and thank you to rss users out there 🙂.&lt;/p&gt;
&lt;p&gt;I came across the idea after adding &lt;a href=&quot;https://evantravers.com&quot;&gt;evantravers.com&lt;/a&gt;
to my feed reader. He’s part of something called the
&lt;a href=&quot;https://daverupert.com/rss-club/&quot;&gt;rss-club&lt;/a&gt;, and I love it. There are others
in the club that I also follow, but I never realized I was getting rss only
posts from them 😄.&lt;/p&gt;
&lt;p&gt;Anyway, I liked the idea and wanted to implement it on my website for a few
reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, I do love RSS and want to encurrage the use of it, especially those
that write and maintain personal blogs.&lt;/li&gt;
&lt;li&gt;Second, I have been thinking about ways to semi-publish less technical and
informal posts, without watering down the main content of the site… this
&lt;em&gt;might&lt;/em&gt; be a way to do that, but I’m still deciding.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Long story short, if you subscribe to this website via rss… thank you! I’ll
try to add quick and fun posts to the feed on occasion.&lt;/p&gt;
&lt;p&gt;– Ryan&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Required MacOs Apps (2023)</title>
    <link href="https://ryan.himmelwright.net/post/required-macos-apps-2023/" />
    <updated>2023-04-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/required-macos-apps-2023/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/bCYe4FZhP9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/bCYe4FZhP9-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now that my &lt;a href=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/&quot;&gt;work computer is a MacBook Pro&lt;/a&gt;, I’m
spending a lot of time on a Mac. Overall, I enjoy it… after some tweaks from
the default configuration. I rely on a few applications to really make macOS
work for me. Here are four that make my experience in macOS much more enjoyable
and productive.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Raycast&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/vRZHNv-iBZ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/vRZHNv-iBZ-1200.jpeg&quot; alt=&quot;Window Management via raycast&quot; width=&quot;1200&quot; height=&quot;815&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I use raycast to manage my windows.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve used &lt;a href=&quot;https://www.raycast.com&quot;&gt;raycast&lt;/a&gt; for years now, but I just recently
started heavily relying on it for a few specific commands. While this launcher is
extremely versatile and can be used for basically anything, I
mostly utilize three operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Toggle between light/dark system appearance&lt;/li&gt;
&lt;li&gt;Window Management (ex: setting windows to half, third, or fourth screen splits)&lt;/li&gt;
&lt;li&gt;Switching my set audio input/output devices (Great when jumping into a
meeting)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Easy Move+Resize&lt;/h2&gt;
&lt;p&gt;Being able to move or resize windows quickly and easily by holding the CMD key
plus right or left clicking the mouse is something I’ve always missed from
Linux while in macOS. One day, I was fed up and started searching the internet
for a solution because I figured I couldn’t be the only one missing it.&lt;/p&gt;
&lt;p&gt;I eventually stumbled on a Github repo for an application called &lt;a href=&quot;https://github.com/dmarcotte/easy-move-resize&quot;&gt;Easy
Move+Resize&lt;/a&gt;. It’s  simple
little app, allowing me to select a modifier key to pair with right/left clicks
to move &amp;amp; resize windows… exactly what I want. Now I can hold CMD and click a
window to move it with my mouse, or right click and drag to easily resize it. Finally!&lt;/p&gt;
&lt;h2&gt;BetterDisplay&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/4w9y24SE_T-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/4w9y24SE_T-1200.jpeg&quot; alt=&quot;Using an ipad in portrait sidecar via BetterDisplay&quot; width=&quot;1200&quot; height=&quot;907&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using a sidecar iPad in portrait mode via BetterDisplay.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Next, &lt;a href=&quot;https://github.com/waydabber/BetterDisplay&quot;&gt;BetterDisplay&lt;/a&gt; provides all
the additional display controls I desire. It allows me to use &lt;a href=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/&quot;&gt;sub
monitor&lt;/a&gt; configurations in macOS, and
also includes many nice to have features, like software brightness controls for
&lt;em&gt;all&lt;/em&gt; monitors.&lt;/p&gt;
&lt;p&gt;Most importantly, I can configure my displays in ways normally unallowed …
like disabling a connected display or using an iPad via sidecar in &lt;em&gt;portrait&lt;/em&gt;
orientation.&lt;/p&gt;
&lt;p&gt;Again, BetterDisplay is a very powerful tool that I highly recommend.&lt;/p&gt;
&lt;h2&gt;Hand Mirror&lt;/h2&gt;
&lt;p&gt;Lastly, &lt;a href=&quot;https://handmirror.app&quot;&gt;Hand Mirror&lt;/a&gt;. This might seem like a stupid
app, but I find myself using it all of the time. Basically, it puts an icon in
the menubar, and when you click on it (or under the notch with the pro
version), a preview of your webcam pops open. I use this to check my camera and
lighting before joining a meeting, or while in one (ex: I can check how
everything looks before turning my back camera on).&lt;/p&gt;
&lt;p&gt;It’s such a small feature, but &lt;em&gt;very&lt;/em&gt; convenient.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/xErf0WAlDG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/required-macos-apps-2023/xErf0WAlDG-1200.jpeg&quot; alt=&quot;Using my work macbook at the Ansible Office&quot; width=&quot;1200&quot; height=&quot;857&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using my work macbook at the Ansible office.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That’s about it. While there are many other apps that I use daily and love,
these are the core apps that help fill in the gaps and enable macOS to truely
work for me and my workflows.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Work Laptop Refresh - 16&#92;&quot; MacBook Pro M1 Pro</title>
    <link href="https://ryan.himmelwright.net/post/work-macbook-16m1pro/" />
    <updated>2023-04-02T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/work-macbook-16m1pro/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/xUnQOiIgpH-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/xUnQOiIgpH-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;685&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New and Old Work Laptops, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Near the end of last year (2022), I was eligible for a laptop refresh at work.
This time around, I chose to replace my  &lt;a href=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/&quot;&gt;7th generation X1
carbon&lt;/a&gt; with the 16&amp;quot; Macbook Pro option.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Laptop Specs&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/phfdlbC2Bs-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/phfdlbC2Bs-1200.jpeg&quot; alt=&quot;My x1 Carbon next to he 16 m1 pro mbp&quot; width=&quot;1200&quot; height=&quot;653&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Old work laptop: Gen.7 X1 Carbon Thinkpad (left). New work laptop: 16 M1 Pro Macbook Pro (right).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Before proceeding further, here are the specifications of each machine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x1 Carbon Thinkpad (gen7)
Intel i7-8665U [1.9 Ghz (4.8 Ghz Turbo), 2 cores, 8 threads]
16 GB RAM
250 GB NVME SSD
14&amp;quot; 1920x1080 IPS Display

~2.4 lbs.
12.71&amp;quot; x 8.54&amp;quot; x 0.58&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;2021 16&amp;quot; Macbook Pro
M1 Pro (10 CPU Cores [2 efficiency, 8 performance], 16 gpu cores)
16-core Neural Engine
Media engine
32 GB RAM
512 GB NVME SSD
16.2&amp;quot; 3456x2234 Liquid Retina XDR display

4.7 lbs.
14.01&amp;quot; x 9.77&amp;quot; x 0.66&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that available for reference, lets continue.&lt;/p&gt;
&lt;h2&gt;Why the switch from Thinkpad to Mac?&lt;/h2&gt;
&lt;p&gt;I did enjoy the x1 carbon quite a bit, especially compared to the p50 I had
previously. However, my work needs have changed over the last few years, and I
wanted something more powerful. On top of that, I wanted to try out using a Mac
at work. &lt;em&gt;Some&lt;/em&gt; of my reasons for the switch to Mac include:&lt;/p&gt;
&lt;h3&gt;Great Hardware&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/K4cJCnM_GM-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/K4cJCnM_GM-1200.jpeg&quot; alt=&quot;My x1 Carbon using an 4k external display&quot; width=&quot;1200&quot; height=&quot;1021&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Despite having a decent display, I often used my 4k external monitor for 200% scaling and a slightly larger text (14&quot; vs. 15.6&quot;).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The experience I’ve had with my personal Apple hardware has been very positive.
Apple Silicon has performed wonderfully, and I love how low power and silent the
chips run. Beyond processors, I have become accustomed to how clear and crisp
text appears on a display with 200% scaling. For this reason, I appreciate the
retina displays on MacBook Pros. I don’t feel obligated to hook a portable 4k
display to my MacBook to make text look great, which I do on the Thinkpad (I
know Thinkpads &lt;em&gt;can&lt;/em&gt; have 4k displays, but the models we have at work don’t, and
even when they did, there usually is a noticeable hit to battery life).
Lastly, the speakers in a MacBook Pro are really hard to beat in a laptop, and
the remaining periphery components (keyboard, trackpad, webcam, mic) range
anywhere from ‘good’ to ‘great’ for what I need in a work device.&lt;/p&gt;
&lt;p&gt;As a bonus, Macs work well with other devices in the Apple ecosystem
(although I &lt;em&gt;do&lt;/em&gt; wish was more open). This includes the Apple Watch, iPhone,
iPad, and any airplay 2 supported devices, which I’ve accumulated over the
years.&lt;/p&gt;
&lt;h3&gt;Focus&lt;/h3&gt;
&lt;p&gt;I need my work machine to help me &lt;em&gt;finish&lt;/em&gt; my work. I don’t work on a desktop
team, so while playing around and learning details about desktop Linux is
fun and can be useful, it doesn’t always help me stay on task with what I
&lt;em&gt;need&lt;/em&gt; to accomplish for my job. If I’m going to spend time ‘messing around’
learning something, it should probably be things like OpenShift, CICD, Coding,
and &lt;em&gt;I guess&lt;/em&gt; testing. All of those tasks I can do just fine from a Mac. I
don’t even run many local VMs for work currently. If I did, I’d likely stick
with Linux.&lt;/p&gt;
&lt;p&gt;The truth is, I tend to get distracted tweaking the desktop and underlying
system on Linux. While I still do this on macOS, there’s more restraint. In
Linux, &lt;em&gt;anything&lt;/em&gt; can be hacked together with enough effort (and it sometimes
&lt;em&gt;has&lt;/em&gt; to be), but in macOS, it is usually a little clearer when your rabbit
hole has slammed into a wall. For me to be productive, I need that… when it
comes to my &lt;em&gt;desktop&lt;/em&gt; OS. I’m still willing to mess around with servers all I
want 😆.&lt;/p&gt;
&lt;h3&gt;A Nice Change&lt;/h3&gt;
&lt;p&gt;I love Linux, but using it for my desktop OS has become a bit stale for me
lately. I do find this strange, because I think the Linux desktop has had many
interesting projects over the last few years. Still, it doesn’t allure me like
it use to. I don’t feel the desire to distro-hop around anymore (which is good,
as I currently have very limited time). While I might occasionally dabble with
Silverblue, NixOS, or Elementary OS, I ultimately hop back to Fedora.&lt;/p&gt;
&lt;p&gt;The areas I find most interesting about Linux (podman,
virtualization, nix) have nothing to do with the desktop.&lt;/p&gt;
&lt;h3&gt;Opportunity to Learn and Share with Others&lt;/h3&gt;
&lt;p&gt;Lastly, the Apple Silicon transition is a great time for me to dig into macOS
and learn. While core, ‘normal user’ workflows are fine, niche computing can be
a different story. Things don’t always work perfectly. Workarounds have to be
discovered. Hacks are often relied on. As a daily Linux user over the last 12+
years, this all sounds familiar. While many long-time Mac users might not want
to bang their heads inventing solutions to edge cases, I find it to be a great
opportunity jump in, investigate, and then share my knowledge with others.&lt;/p&gt;
&lt;p&gt;A perfect example of this already in action is my &lt;a href=&quot;https://ryan.himmelwright.net/post/install-podman-m1-mac/&quot;&gt;m1
podman&lt;/a&gt; post. I have had &lt;em&gt;several&lt;/em&gt; of my Red Hat
co-workers message me that they found and used that post while searching how to
setup podman their new MacBook. I want to do more of that.&lt;/p&gt;
&lt;h2&gt;Why the 16&amp;quot; over the 14&amp;quot;&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/PyVSSPuTbp-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/PyVSSPuTbp-1200.jpeg&quot; alt=&quot;My personal 14 MBP and work 16 MBP&quot; width=&quot;1200&quot; height=&quot;716&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My personal 14&quot; MBP and work 16&quot; MBP.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;At first, I thought the 14&amp;quot; was the obvious choice. It’s just as powerful as
the 16&amp;quot;, but more portable. Coming from a 2.4 pound 14&amp;quot; X1 Carbon, I didn’t
want the switch to be too drastic. Additionally, the computer would be docked
to a monitor both when working from home and the office, so there was no real
reason to carry a bigger device… right?&lt;/p&gt;
&lt;p&gt;Then, during the weeks leading up to when I actually made the decision… my
mind shifted and I decided on the 16&amp;quot;. Here’s a few reasons why:&lt;/p&gt;
&lt;h3&gt;Still Portable&lt;/h3&gt;
&lt;p&gt;The 16&amp;quot; MacBook Pro is still a portable computer. It might be 4.7 lbs, but
that’s still a pound lighter than the p50 work computer I had before the x1
carbon, and almost &lt;em&gt;half&lt;/em&gt; the weight of my previous personal desktop
replacement laptop, the 8.6 pound &lt;a href=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/&quot;&gt;Bonobo Extreme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As far as the added weight to my commute… as much as I’d love to walk/bike to
work most days, having an infant son that attends a daycare
miles from where I live or work means that at least the next few years,
my commute is sadly car-based. And my car doesn’t mind the extra size of the
16&amp;quot;.&lt;/p&gt;
&lt;h3&gt;Stand-alone Workstation&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/WkgImCQi4C-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/WkgImCQi4C-1200.jpeg&quot; alt=&quot;The stand-alone MacBook setup.&quot; width=&quot;1200&quot; height=&quot;1179&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 16&quot; Macbook Pro on a table.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Second, I wanted a machine that functioned well as a stand-alone workstation.
I’ve learned that in order to stay motivated, I need to occasionally mix up my
surroundings. So, I wanted a computer that I could work on away from my desk,
without any compromises. This might be in a meeting room, around the
office, or just at my kitchen table.&lt;/p&gt;
&lt;p&gt;When placed on a table in front of me, the 16&amp;quot; screen feels perfect. It covers
enough of my field of view, and has a resolution that doesn’t feel cramped.
Pair that with amazing speakers, and the 16&amp;quot; MBP truly does feel less like a
‘laptop’ and more like a ‘&lt;em&gt;portable workstation&lt;/em&gt;’. This is wonderful if I’m
tying to work in a meeting room, or anywhere else.&lt;/p&gt;
&lt;h3&gt;It’s different from what I have&lt;/h3&gt;
&lt;p&gt;Lastly, and this might be a silly reason, but it’s still something to consider:
I already own a 14&amp;quot; M1 Pro Macbook Pro for my personal computer. It’s the base
model with the binned cpu/gpu, and only has 16GB of RAM, but as far as the external
appearance, it looks the same (except silver) as a work issued one would.&lt;/p&gt;
&lt;p&gt;Not only would a 14&amp;quot; work macbook increase the likelihood of me sleepily
grabbing the wrong device in the morning, but I would also miss out on a chance
to experience working on ‘the other option’. I could learn what the 16&amp;quot; is like
to use daily, and how that compares to my 14&amp;quot;. I might hate it, but I would
&lt;em&gt;know&lt;/em&gt; that for a fact, rather than just speculation.&lt;/p&gt;
&lt;h2&gt;How’s it going?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/CrUBB4psuG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/work-macbook-16m1pro/CrUBB4psuG-1200.jpeg&quot; alt=&quot;The MacBook setup at the office&quot; width=&quot;1200&quot; height=&quot;1074&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My 16&quot; Macbook Pro setup at the office.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After nearly 3 months with the 16&amp;quot; M1 Pro MacBook Pro as my new work computer,
how has it been? Honestly? It’s been delightful. It took a little bit of time
to figure out how to setup my work on a Mac, but it wasn’t nearly as bad as I
imagined it would be. My experience using the 14&amp;quot; M1 Pro, and switching my
dotfiles + dev environments over to nix + home-manager made the transition
much smoother.&lt;/p&gt;
&lt;p&gt;The hardware has also been perfect. It’s a brilliant machine to use, wherever I
want to work. I never feel cramped when using it. In fact, because the screen
is so crisp compared to the 27&amp;quot; 1440p office monitor, the majority of the time
I’m at my desk I use the laptop display as my main one (on a stand, I still
need proper ergonomics), with my desk’s monitor in portrait as a secondary.&lt;/p&gt;
&lt;p&gt;To conclude, I am very happy with my decision to go with the 16&amp;quot; Macbook Pro
for my work laptop. It is a incredible portable workstation, and was easily the
right choice for me, &lt;em&gt;especially&lt;/em&gt; for my work device.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Converting my 34 Key layout from QMK to ZMK</title>
    <link href="https://ryan.himmelwright.net/post/qmk-to-zmk/" />
    <updated>2023-03-01T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/qmk-to-zmk/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/60oaD-ZYO0-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/60oaD-ZYO0-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Wireless Ferris Sweep Build, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After months loving my &lt;a href=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/&quot;&gt;ferris sweep&lt;/a&gt;, I
decided to build a second one. Specifically, I wanted a wireless version so I
could avoid all the wires when using it in portable setups. However, the
&lt;a href=&quot;https://nicekeyboards.com/nice-nano/&quot;&gt;nice!nano&lt;/a&gt; controllers used for
bluetooth keyboard builds, uses the &lt;a href=&quot;https://zmk.dev&quot;&gt;ZMK&lt;/a&gt; firmware instead of
&lt;a href=&quot;https://qmk.fm&quot;&gt;QMK&lt;/a&gt;, which I had previously used. So, I had to convert &lt;a href=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/&quot;&gt;my
ferris sweep layout&lt;/a&gt; to ZMK. Here’s my
experience.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/t7sZvmQ5md-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/t7sZvmQ5md-1200.jpeg&quot; alt=&quot;The wires on the ferris could be a bit of a mess.&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The ferris sweep&#39;s wires can be a bit of mess, especially on the go.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A couple weeks into using my ferris sweep, I decided if I ever built a second
one, that I would want it to be wireless. In September (on my birthday). I
finally ordered parts for a nice!nano based ferris sweep build. &lt;em&gt;Weeks&lt;/em&gt; after
ordering, they arrived.&lt;/p&gt;
&lt;p&gt;I finally had the parts, but there was still a road block: I was only a few
months into being a new parent, with a wife still in medical training.
Even when I managed to find time to &lt;em&gt;build&lt;/em&gt; the keyboard, it was never a large
enough chunk of time to also include the patience which my novice soldering skills
require. Unsurprisingly, I messed up. And got frustrated. So, I decided to order a
pre-built board.&lt;/p&gt;
&lt;p&gt;A few &lt;em&gt;more&lt;/em&gt; weeks later, &lt;em&gt;it&lt;/em&gt; arrived. I assembling it using the switches and
key caps I still had from my failed build. With the keyboard physically
complete, I needed to generate and flash it with the ZMK firmware.&lt;/p&gt;
&lt;h2&gt;Setting up ZMK&lt;/h2&gt;
&lt;p&gt;The process for flashing ZMK was quite different than &lt;a href=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/&quot;&gt;what I did for
QMK&lt;/a&gt;. First, I had to install ZMK, which was
easy enough to do by following &lt;a href=&quot;https://zmk.dev/docs/user-setup&quot;&gt;the
documentation&lt;/a&gt;. This involved running a script
which prompts the user with a few questions, and then generates a repo
containing the appropriate firmware templates for the keyboard model specified.
After that repo is pushed to Github, Github Actions are triggered to run
firmware builds.&lt;/p&gt;
&lt;p&gt;As simple as running the script was, it was where I hit my first snag. My
firmware builds kept outputting a single &lt;code&gt;.bin&lt;/code&gt; package, which was not expected
(I needed separate firmwares for each nice!nano: a left and right).&lt;/p&gt;
&lt;p&gt;After struggling with this problem for a bit, I realized my error. During the
board selection, I had been selecting &lt;code&gt;Ferris 0.2&lt;/code&gt; (which is the &lt;a href=&quot;https://github.com/pierrechevalier83/ferris&quot;&gt;original
ferris&lt;/a&gt;), I needed to select &lt;code&gt;Cradio/Sweep&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Keyboard Selection:
 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;% Milk		             &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Helix		       &lt;span class=&quot;token number&quot;&gt;47&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Preonic Rev3
 &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; A. Dux		             &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Hummingbird	   &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; QAZ
 &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; BAT43		             &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Iris			   &lt;span class=&quot;token number&quot;&gt;49&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Quefrency Rev. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; BDN9 Rev2		         &lt;span class=&quot;token number&quot;&gt;27&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Jian			   &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Redox
 &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; BFO-9000		         &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Jiran		       &lt;span class=&quot;token number&quot;&gt;51&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; REVIUNG41
 &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Boardsource 3x4 Macropad &lt;span class=&quot;token number&quot;&gt;29&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Jorne		       &lt;span class=&quot;token number&quot;&gt;52&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; REVIUNG5
 &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Boardsource 5x12	     &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Knob Goblin	   &lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Romac Macropad
 &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; BT60 V1 Hotswap	         &lt;span class=&quot;token number&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Kyria		       &lt;span class=&quot;token number&quot;&gt;54&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Romac+ Macropad
 &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; BT60 V1 Soldered	     &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Kyria Rev2		   &lt;span class=&quot;token number&quot;&gt;55&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; S40NC
&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Chalice		             &lt;span class=&quot;token number&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Leeloo		       &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; SNAP
&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Clog		             &lt;span class=&quot;token number&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Lily58		       &lt;span class=&quot;token number&quot;&gt;57&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Sofle
&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Contra		             &lt;span class=&quot;token number&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Lotus58		   &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; splitkb.com Aurora Corne
&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Corne		             &lt;span class=&quot;token number&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; MakerDiary m60	   &lt;span class=&quot;token number&quot;&gt;59&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; splitkb.com Aurora Lily58
&lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Corneish Zen v2	         &lt;span class=&quot;token number&quot;&gt;37&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Microdox		   &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; splitkb.com Aurora Sweep
&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Cradio/Sweep	         &lt;span class=&quot;token number&quot;&gt;38&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; MurphPad		   &lt;span class=&quot;token number&quot;&gt;61&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Splitreus62
&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; CRBN Featherlight	     &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Naked60		   &lt;span class=&quot;token number&quot;&gt;62&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; TG4x
&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; eek&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;		             &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Nibble		       &lt;span class=&quot;token number&quot;&gt;63&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Tidbit Numpad
&lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Elephant42		         &lt;span class=&quot;token number&quot;&gt;41&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; nice&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;		   &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Waterfowl
&lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Ergodash		         &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; nice&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;view		   &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ZMK Uno
&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Eternal Keypad	         &lt;span class=&quot;token number&quot;&gt;43&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; nice&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;view adapter &lt;span class=&quot;token number&quot;&gt;66&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Zodiark
&lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Eternal Keypad Lefty     &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Osprette		   &lt;span class=&quot;token number&quot;&gt;67&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Quit
&lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Ferris &lt;span class=&quot;token number&quot;&gt;0.2&lt;/span&gt;		         &lt;span class=&quot;token number&quot;&gt;45&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Pancake
&lt;span class=&quot;token number&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Fourier Rev. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;	         &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Planck Rev6
Pick a keyboard:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that switch, the system started building the firmware for the correct
keyboard.&lt;/p&gt;
&lt;h2&gt;Converting the layout&lt;/h2&gt;
&lt;p&gt;Now that I was building the &lt;em&gt;proper&lt;/em&gt; firmware for the ferris sweep, I needed to
convert my QMK layout over to ZMK. I think I started by manually copying my
layout over into the layout template (or maybe even tried a conversion tool).&lt;/p&gt;
&lt;p&gt;Whatever I attempted… it didn’t work. When I tried to build the firmware, it
erred on the keymap file:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;devicetree error: /__w/ferris-sweep-zmk/ferris-sweep-zmk/config/cradio.keymap:38 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;column &lt;span class=&quot;token number&quot;&gt;43&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;: parse error: expected number or parenthesized expression&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To isolate the problem, I commented out the layers and added them back in one
at a time. With each error, I figured out where it was happening and
what changes I needed to apply to make it ZMK-complaint.&lt;/p&gt;
&lt;p&gt;While I don’t know if I recorded &lt;em&gt;all&lt;/em&gt; my required changes, here are a few of
the big ones I had to convert while going from qmk -&amp;gt; zmk.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Any keys that used &lt;code&gt;()&lt;/code&gt; formatting needed to be replaced with a ZMK
alternative. Ex: &lt;code&gt;LCTL_T(KC_A)&lt;/code&gt; in QMK became &lt;code&gt;&amp;amp;mt LEFT_CONTROL A&lt;/code&gt; in ZMK (&lt;code&gt;A&lt;/code&gt; on tap, &lt;code&gt;Left CTRl&lt;/code&gt; on hold).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;OSM&lt;/code&gt; keys I used in qmk are &lt;code&gt;sticky keys&lt;/code&gt; in ZMK (&lt;a href=&quot;https://zmk.dev/docs/behaviors/sticky-key&quot;&gt;doc source&lt;/a&gt;), so I
converted those. Ex: &lt;code&gt;OSM(MOD_LCTL)&lt;/code&gt; became &lt;code&gt;&amp;amp;sk LCTL&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most symbols had to be converted to alternate abbreviations. &lt;em&gt;Some&lt;/em&gt; (but not all) of the changes include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KC_ESC&lt;/code&gt;  --&amp;gt; &lt;code&gt;&amp;amp;kp ESCAPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_TILD&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp TILDE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_LCBR&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp LBRC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_RCBR&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp RBRC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_GRV&lt;/code&gt;  --&amp;gt; &lt;code&gt;&amp;amp;kp GRAVE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_SCLN&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp SEMICOLON&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_LBRC&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp LBKT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_RBRC&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp RBKT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_LPRN&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp LPAR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_RPRN&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp RPAR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_EXLM&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp EXCl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_DLR&lt;/code&gt;  --&amp;gt; &lt;code&gt;&amp;amp;kp DLLR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_PERC&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp PERCENT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_MINS&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp MINUS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_CIRC&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp CARET&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_AMPR&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp AMPS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_BSLS&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp BACKSLASH&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_SLSH&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp SLASH&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_EQL&lt;/code&gt;  --&amp;gt; &lt;code&gt;&amp;amp;kp EQUAl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_ASTR&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp ASTRK&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_QUES&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp QUESTION&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_MUTE&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp C_MUTE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_VOLD&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp C_VOLUME_DOWN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_VOLU&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp C_VOLUME_UP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_PGUP&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp PAGE_UP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_PGDN&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp PAGE_DOWN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_HOME&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp HOME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_DEL&lt;/code&gt;  --&amp;gt; &lt;code&gt;&amp;amp;kp DELETE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_ENT&lt;/code&gt;  --&amp;gt; &lt;code&gt;&amp;amp;kp ENTER&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_CAPS&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp CAPSLOCK&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_MSTP&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp C_STOP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_MRWD&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp C_REWIND&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_MFFD&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp C_FAST_FOREWARD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_MPLY&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;amp;kp C_PLAY_PAUSE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lastly, there was at least one instance of a typo that threw me for a loop: &lt;code&gt;&amp;amp;sl&lt;/code&gt; instead of &lt;code&gt;&amp;amp;sk&lt;/code&gt; 😆.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once I got all the standard keys in my layout figured out, the last thing I
needed to switch was the keys to toggle between layouts. My normal thumb-key
layer-switch practically stayed the same, only going from &lt;code&gt;MO(NUM)&lt;/code&gt; to &lt;code&gt;&amp;amp;mo NUM&lt;/code&gt;. However, the keys that toggle my &lt;em&gt;base layer&lt;/em&gt; switched from a &lt;code&gt;DF(NUM)&lt;/code&gt;
to &lt;code&gt;&amp;amp;tog NUM&lt;/code&gt;, which seems to work the same. I also decided to remove some of
the alternate base layers I &lt;em&gt;never&lt;/em&gt; use on my QMK sweep.&lt;/p&gt;
&lt;h2&gt;Fixing home row mods&lt;/h2&gt;
&lt;p&gt;At this point, the firmware compiled and I was able to flash my layout to the
keyboard! After using it for a few seconds, it was immediately clear that the
home row mods were terrible. As I continued to type, they were so bad that I
thought I might have to disable them because of how often they misfired. I
tweaked several timings, but nothing resolved the problem.&lt;/p&gt;
&lt;p&gt;After some research, I learned about the &lt;code&gt;flavor = &amp;quot;tap-preferred&amp;quot;&lt;/code&gt;
setting that can be added to the keymap file:&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;mt &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  flavor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tap-preferred&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  tapping_term_ms &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;285&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I made this change, and the keyboard worked &lt;em&gt;exactly&lt;/em&gt; how I wanted it to. At
this point, the layout and timing felt just like my qmk ferris sweep! Success.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/CrUBB4psuG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-to-zmk/CrUBB4psuG-1200.jpeg&quot; alt=&quot;The new keyboard with ZMK is working great.&quot; width=&quot;1200&quot; height=&quot;1074&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New ZMK keyboard working great. It&#39;s been my daily driver at work.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;It has been over two months now and everything has worked great, without any issues.
I have been using the zmk sweep as my daily driver in the office, and seamlessly switch back
and forth between it and my QMK sweep at home. I haven’t had to make many tweaks to
the layout, but when I do (which I will at some point to add bluetooth
controls), it should be very easy now that I have the layout converted.
Hopefully this post can can help any others that need to make the QMK --&amp;gt; ZMK
switch. Good luck!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Home-Manager Dark Mode Toggle</title>
    <link href="https://ryan.himmelwright.net/post/home-manager-dark-mode/" />
    <updated>2023-01-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/home-manager-dark-mode/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/home-manager-dark-mode/OgFbplGbSW-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/home-manager-dark-mode/OgFbplGbSW-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Bodie Lighthouse, Outer Banks NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A few months ago, I took the plunge and migrated my &lt;a href=&quot;https://ryan.himmelwright.net/post/new-dotfiles/&quot;&gt;dotfiles
system&lt;/a&gt; into my
&lt;a href=&quot;https://github.com/nix-community/home-manager&quot;&gt;home-manager&lt;/a&gt;
(&lt;a href=&quot;https://nixos.org&quot;&gt;nix&lt;/a&gt;) setup. Around the same time, I also switched to a
desk in the office that is next to a window. I love all the natural light, but
need to use light colored themes during different times of the day due to the
brightness.&lt;/p&gt;
&lt;p&gt;Manually switching my terminal and neovim themes every time I wanted to toggle
between light and dark themes was tedious. So, I turned to my new dotfile
system to see if I could figure out a more automated approach.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;Home-manager allows you to declaratively define user specific packages and
dotfiles. In other words, I define everything in code (nix), and &lt;code&gt;home-manager&lt;/code&gt;
generates all the dotfiles, based on the functional &lt;code&gt;nix&lt;/code&gt; expressions. Many
applications allow you to easily set themes by declaring a variable in the
configuration. For example, the following to defines settings for the terminal
app, kitty:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;programs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kitty &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  extraConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;builtins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;./configs/kitty.conf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Gruvbox Material Dark Hard&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line ‘enables’ kitty, the second one pulls in my old dotfile to be
used for the majority of the configuration, and the last line sets which theme to use.&lt;/p&gt;
&lt;p&gt;With this in mind, I wondered if I could alter my home-manager configuration
to conditionally toggle between ‘light’ and ‘dark’ modes for my terminal apps.&lt;/p&gt;
&lt;h3&gt;Variables file&lt;/h3&gt;
&lt;p&gt;The first step took me a bit to figure out, but I eventually learned how to
create a variables file that can be read from other config files:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; types&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    darkmode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bool&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    darkmode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used &lt;code&gt;lib&lt;/code&gt; to add some typed options, that can be set and later
accessed elsewhere in the nix/home-manager configuration. I added a
boolean &lt;code&gt;darkmode&lt;/code&gt; option, which can be used to define if ‘darkmode’ is enabled
or not.&lt;/p&gt;
&lt;h3&gt;kitty theme set&lt;/h3&gt;
&lt;p&gt;The first application I wanted to toggle was the terminal. When I switched to
home-manager for dotfiles, I started using
&lt;a href=&quot;https://sw.kovidgoyal.net/kitty/&quot;&gt;kitty&lt;/a&gt; again, because of it’s
configurability in home-manager.&lt;/p&gt;
&lt;p&gt;For the darkmode toggle, I wanted to not only switch between a light
and dark theme, but also to define &lt;em&gt;which&lt;/em&gt; theme was used for each
case (ex: maybe Gruvbox for dark, but Ayu for  light). This turned
out to be quite easy to do in the kitty home-manager config file:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
  kitty&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;darkmode &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;## Dark Themes&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Ayu&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Dracula&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Gruvbox Material Dark Medium&quot;&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&quot;Gruvbox Material Dark Hard&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Flatland&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Kaolin Aurora&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Kaolin Ocean&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Monokai&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Nord&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Nova&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Obsidian&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;PaperColor Dark&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Obsidian&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Solarized Dark&quot;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &quot;Spacemacs&quot;&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;## Light Themes&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# &quot;Ayu Light&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# &quot;GitHub Light&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;Gruvbox Material Light Hard&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# &quot;Material&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# &quot;Piatto Light&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# &quot;Solarized Light&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# &quot;Tomorrow&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# &quot;Spring&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  programs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kitty &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    extraConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;builtins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;./configs/kitty.conf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; kitty&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, I make sure to import the config module, which contains our
&lt;code&gt;config.darkmode&lt;/code&gt; variable. I then defined a local variable, &lt;code&gt;kitty-theme&lt;/code&gt;
using a conditional expression. Basically, if the &lt;code&gt;config.darkmode&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;,
then &lt;code&gt;kitty-theme&lt;/code&gt; is set to the name of my dark theme, otherwise it is set to
the light theme name. To make it easy to change my preferred themes, I list
some of my favorites, and un-comment my selection for each category
(light/dark).&lt;/p&gt;
&lt;p&gt;With the local variable defined, it is used when setting the &lt;code&gt;programs.kitty&lt;/code&gt;
configuration: &lt;code&gt;theme = kitty-theme;&lt;/code&gt;. With this, home-manager builds the kitty
configuration, and selects the theme based on what the &lt;code&gt;darkmode&lt;/code&gt; variable set
to in the &lt;code&gt;variables.nix&lt;/code&gt; file!&lt;/p&gt;
&lt;h3&gt;neovim theme loading&lt;/h3&gt;
&lt;p&gt;Setting up the dark/light themes for my terminal isn’t very useful if neovim
doesn’t also follow the dress code. To make this work for neovim, I had to get a
little creative:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; 
  colors&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;darkmode &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;./configs/nvim/dark-theme.lua&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;./configs/nvim/light-theme.lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  imports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token url&quot;&gt;../variables.nix&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Just use my neovim config&lt;/span&gt;
  home&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.config/nvim/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;./configs/nvim/nvim-dotfiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  home&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.config/nvim/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;recursive &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Set color theme file to load&lt;/span&gt;
  home&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.config/nvim/lua/user/colortheme.lua&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; colors&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like the kitty setup, I import &lt;code&gt;config&lt;/code&gt; and define a local variable
(&lt;code&gt;colors-file&lt;/code&gt;), based on the condition of &lt;code&gt;config.darkmode&lt;/code&gt;. However, for the
neovim toggle, I need to change multiple variables (ex: set
&lt;code&gt;vim.opt.background&lt;/code&gt; and some theme-specific ones too). So &lt;em&gt;unlike&lt;/em&gt; kitty, the
local variable defines a &lt;em&gt;file path&lt;/em&gt; to a neovim configuration file, rather
than just a theme name. Whichever file path is selected, home-manager
copies it as a &lt;code&gt;colortheme.lua&lt;/code&gt; file when building the neovim configuration.&lt;/p&gt;
&lt;p&gt;Both &lt;code&gt;colortheme.lua&lt;/code&gt; options set required variables, load the colorscheme, and
throw an error if the theme is not found. Again, I list several themes that I
have installed and un-comment the one I want:&lt;/p&gt;
&lt;p&gt;Dark mode config (&lt;code&gt;dark-theme.lua&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;opt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dark&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Theme&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;ayu&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;everforest&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; colorscheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gruvbox&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;happy_hacking&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;nord&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;sonokai&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;sobrio&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;sobrio_ghost&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;sobrio_verde&quot;&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;-- Attempt to Load theme&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; status_ok&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pcall&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cmd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colorscheme &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; colorscheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; status_ok &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;colorscheme &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; colorscheme &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; not found!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Light Mode config (&lt;code&gt;light-theme.lua&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;opt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;light&quot;&lt;/span&gt;
vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ayucolor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;light&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Theme&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;ayu&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; colorscheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;everforest&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;gruvbox&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;nord&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;sobrio&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;sobrio_light&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- local colorscheme = &quot;sobrio_verde_light&quot;&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;-- Attempt to Load theme&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; status_ok&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pcall&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cmd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colorscheme &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; colorscheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; status_ok &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;colorscheme &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; colorscheme &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; not found!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After setting up the separate colorscheme files, I updated my previous
&lt;code&gt;colorscheme.lua&lt;/code&gt; neovim config file:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Everforest specific options&lt;/span&gt;
vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;everforest_background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hard&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Sonokai specific options&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- vim.g.sonokai_style = &quot;atlantis&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- vim.g.sonokai_style = &quot;espresso&quot;&lt;/span&gt;
vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sonokai_style &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;maia&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Try to load theme from dark/light colortheme fiUUE&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; status_ok&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amineeded &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pcall&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;require&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;user.colortheme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; status_ok &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; colorscheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gruvbox&quot;&lt;/span&gt;
  vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;colortheme file found. Using default (&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; colorscheme &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;) instead!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;-- Load &amp;amp; Check&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; status_ok&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pcall&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cmd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colorscheme &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; colorscheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; status_ok &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    vim&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;colorscheme &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; colorscheme &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; not found!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file sets some general (non dark/light specific) variables, and then tries
to load the &lt;code&gt;colortheme.lua&lt;/code&gt; file that home-manager placed. If it cannot load
it for some reason, it defaults to using &lt;code&gt;gruvbox&lt;/code&gt; (my favorite) and notifies
the user.&lt;/p&gt;
&lt;p&gt;In &lt;em&gt;summary&lt;/em&gt;, home-manager uses the &lt;code&gt;darkmode&lt;/code&gt; variable to decide &lt;em&gt;which&lt;/em&gt;
&lt;code&gt;colorscheme.lua&lt;/code&gt; file to include (dark or light) when it builds and deploys
the neovim dotfiles. Then, neovim attempts to load &lt;em&gt;that&lt;/em&gt; file for it’s
colorscheme settings, falling back to a default if it encounters any issues
(for example, if I’m not using home-manager on a system).&lt;/p&gt;
&lt;h2&gt;How it all works together&lt;/h2&gt;
&lt;p&gt;All of the changes made so far use dotfiles to define if an application uses a
dark or light theme. Now that I use home-manager to generate my dotfiles, this
means all I have to do is run home-manager, and it will apply whatever I have
the &lt;code&gt;darkmode&lt;/code&gt; variable set to:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;home-manager switch&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I want to switch modes, I just change the &lt;code&gt;darkmode&lt;/code&gt; boolean in the
&lt;code&gt;variables.nix&lt;/code&gt; file, re-run &lt;code&gt;home-manager switch&lt;/code&gt;, and the theme changes
are applied.&lt;/p&gt;
&lt;h3&gt;Script&lt;/h3&gt;
&lt;p&gt;While this system is already &lt;em&gt;much&lt;/em&gt; simpler than manually changing all the
themes, I still didn’t want to have to locate the &lt;code&gt;variables.nix&lt;/code&gt;, edit
the value, and then run &lt;code&gt;home-manager&lt;/code&gt; every time I wanted to toggle
between dark and light mode. So, I scripted it! (&lt;code&gt;darkmode-toggle.sh&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;VARIABLES_FILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/home/ryan/Documents/DevOps/home-manager/variables.nix

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;darkmode = true;&#39;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$VARIABLES_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Currently dark-mode. Switching to light-mode.&quot;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i.bak&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;s/darkmode = true/darkmode = false/g&#39;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$VARIABLES_FILE&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Currently light-mode. Switching to dark-mode.&quot;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i.bak&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;s/darkmode = false/darkmode = true/g&#39;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$VARIABLES_FILE&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Running home-manager switch to apply change...&quot;&lt;/span&gt;
home-manager switch
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Done!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script checks what &lt;code&gt;darkmode&lt;/code&gt; is set to in the variables file, and switches
it. It then runs &lt;code&gt;home-manager switch&lt;/code&gt; to apply the change. There are also a few
print messages to communicate what is happening.&lt;/p&gt;
&lt;p&gt;Lastly, I added a symlink of this file to &lt;code&gt;/usr/bin/darkmode-toggle&lt;/code&gt;, so now
I just have to type &lt;code&gt;darkmode-toggle&lt;/code&gt; (actually, &lt;code&gt;dar TAB ENTER&lt;/code&gt;)
in my terminal, and everything changes. Much better!&lt;/p&gt;
&lt;h2&gt;Wrap up/conclusion&lt;/h2&gt;
&lt;p&gt;While I have used &lt;code&gt;nix&lt;/code&gt;/&lt;code&gt;nixOS&lt;/code&gt; off and on for years, and switched my dotfiles
over to &lt;code&gt;home-manager&lt;/code&gt; months ago, this was really my first time playing with
nix expressions. While this might not be the &lt;em&gt;best&lt;/em&gt; approach to accomplish
this, I had a ton of fun figuring it out. On top of that, this method has
honestly been solid for months now.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using gTile</title>
    <link href="https://ryan.himmelwright.net/post/using-gtile/" />
    <updated>2022-12-22T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/using-gtile/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-gtile/V1Cvm1iBwd-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-gtile/V1Cvm1iBwd-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Downtown Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Years ago I enjoyed using minimal &lt;a href=&quot;https://ryan.himmelwright.net/post/started-using-i3blocks/&quot;&gt;tiling window
managers&lt;/a&gt;. Now, I prefer to use a
‘normal’, full desktop environment (ex: Gnome or Plasma). That said, I still
prefer to arrange and manage my windows from the keyboard. On macOS, I’ve been
using &lt;a href=&quot;https://folivora.ai&quot;&gt;betterTouchTool&lt;/a&gt; for improved window management
(&lt;a href=&quot;https://rectangleapp.com&quot;&gt;rectangle&lt;/a&gt; is also great).&lt;/p&gt;
&lt;p&gt;During my time using betterTouchTool, I’ve been annoyed that I didn’t have an
equivalent method to set windows quite the same way on Linux. While testing out
an &lt;a href=&quot;https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/&quot;&gt;ultra wide monitor&lt;/a&gt; at work, I
&lt;em&gt;really&lt;/em&gt; needed a better solution. So I searched a bit harder. It turns out there
&lt;em&gt;is&lt;/em&gt; an option that does exactly what I want in Linux (at least on Gnome):
&lt;a href=&quot;https://github.com/gTile/gTile&quot;&gt;gTile&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What is gTile &amp;amp; How to get it&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-gtile/xBv3gwuFgR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-gtile/xBv3gwuFgR-1200.jpeg&quot; alt=&quot;Setting how to have gTile place a window&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using gTile, you can specify where on the screen you would like to place a window (red) from a list of pre-set configurations you decide (the pop up box).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Basically, gTile is a tiling/window management extension for Gnome. It allows
users to define different grid systems (ex: 8x6, 4x4, 3x1) that they can use to
place windows. It has many additional features and can be quite customized.&lt;/p&gt;
&lt;p&gt;Being a Gnome extension, it is installed from the extension store:
&lt;a href=&quot;https://extensions.gnome.org/extension/28/gtile/&quot;&gt;link&lt;/a&gt;. Note, settings for
gTile are configured in the &lt;a href=&quot;https://flathub.org/apps/details/org.gnome.Extensions&quot;&gt;extensions
app&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How I use gTile&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-gtile/wjWnkOVMUh-966.webp 966w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-gtile/wjWnkOVMUh-966.jpeg&quot; alt=&quot;You can set margin gap&quot; width=&quot;966&quot; height=&quot;698&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I like how you can set the window margins (gaps).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Again, there are a ton of customization options for gTile, so the ways it
can be used is nearly endless. That said, here is how &lt;em&gt;I&lt;/em&gt; use it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I almost exclusively use a 1x6 layout for the following reasons:
&lt;ul&gt;
&lt;li&gt;1x6 allows me to select halves or thirds (usually a 2/3 &amp;amp; 1/3 configuration) easily&lt;/li&gt;
&lt;li&gt;I use 6ths (instead of 3rds) because I &lt;em&gt;sometimes&lt;/em&gt; have applications I like to use narrows windows with (ex: telegram).&lt;/li&gt;
&lt;li&gt;I don’t stack applications, so I only need 1 row.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;There are settings to use specific keyboard shortcuts, but I quickly became
accustomed to and enjoy the pop-up window. I have &lt;code&gt;alt&lt;/code&gt;+&lt;code&gt;Enter&lt;/code&gt; bound to open
the selection window, and then use my arrow keys (and sometimes shift) to do
the rest.&lt;/li&gt;
&lt;li&gt;I enjoy having a little gap between windows. It’s not the most efficient use
of space, but I enjoy the look better.&lt;/li&gt;
&lt;li&gt;I even tend to use a gTile section to setup my single ‘full screen’ windows
(by selecting all 6 sections for a window). This maximizes the window, but
with a little bit of padding around it. If I want to &lt;em&gt;entirely&lt;/em&gt; full screen
the window, I can use Gnome’s default &lt;code&gt;CMD&lt;/code&gt;+&lt;code&gt;Up&lt;/code&gt; keyboard shortcut.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-gtile/8cVjibsHfA-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-gtile/8cVjibsHfA-1200.jpeg&quot; alt=&quot;My windows after arranging with gtile&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My windows after arranging with gTile.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;gTile is exactly what I want for window management, and now defines how I
use Gnome. Specifically, I enjoy having the selection pop-up, and using my
keyboard to visually select how I want each window to be arranged. Figuring out
the gaps options was icing on the cake. Now, I want my &lt;em&gt;macOS&lt;/em&gt; workflow to catch
up 😅. Overall I’m &lt;em&gt;very&lt;/em&gt; happy with this solution.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Adding --baseurl Argument to My Website Pytests</title>
    <link href="https://ryan.himmelwright.net/post/website-pytest-baseurl-argument/" />
    <updated>2022-11-06T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/website-pytest-baseurl-argument/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-pytest-baseurl-argument/-2HDed8UEh-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-pytest-baseurl-argument/-2HDed8UEh-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Sarah P. Duke Gardens, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I run &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/&quot;&gt;the&lt;/a&gt;
&lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links/&quot;&gt;pytests&lt;/a&gt; for my website, they run against
whatever hugo server I have running. While writing a post, this is usually the
same machine I’m writing from. When I commit changes, the CI pipeline
automatically runs the tests on my &lt;a href=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/&quot;&gt;gitlab CI
runner&lt;/a&gt; node. The IP address of the server
to test is determined by a &lt;code&gt;BASE_URL&lt;/code&gt; constant variable defined in my
&lt;code&gt;constants.py&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Changing the variable in this file, and &lt;em&gt;not&lt;/em&gt; accidentally pushing it in &lt;code&gt;git&lt;/code&gt; is
super annoying. So, I finally sat down one night and moved this variable out of
the constants file, converting it to an optional pytest argument.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Steps&lt;/h2&gt;
&lt;p&gt;While this might not the &lt;em&gt;best&lt;/em&gt; approach, it was easy enough to implement
in a very short period of time, and seems to be working great so far. Here are
the steps I took to convert my tests from using a constant variable to the
optional argument.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Add &lt;code&gt;pytest_addoption()&lt;/code&gt; function to the &lt;a href=&quot;http://conftest.py&quot;&gt;conftest.py&lt;/a&gt; file&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pytest_addoption&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;addoption&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;--base-url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;store&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://ci-runner.website.net&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, I added a &lt;code&gt;pytest_addoption()&lt;/code&gt; function definition to my &lt;code&gt;conftest.py&lt;/code&gt;
file. This function allows me to add command line options to pytest. I added a single
option for now, &lt;code&gt;--base-url&lt;/code&gt;, with it’s action set to &lt;code&gt;&amp;quot;store&amp;quot;&lt;/code&gt;. Additionally,
I set the default value to the address of my CI runner node. This way, if I don’t
provide a value it defaults to using my CI runner.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Add &lt;code&gt;base_url&lt;/code&gt; fixture to the &lt;code&gt;conftest.py&lt;/code&gt; file&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getoption&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;--base-url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I added a new fixture (&lt;code&gt;base_url&lt;/code&gt;) to replace my old constant variable
(&lt;code&gt;BASE_URL&lt;/code&gt;) with. This fixture simply returns the value of the &lt;code&gt;--base-url&lt;/code&gt;
option just added.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Update other fixtures to reference the &lt;code&gt;base_url&lt;/code&gt; fixture&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;## Example Before&lt;/span&gt;
&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;POST_NAMES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;post_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the post urls for testing.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; BASE_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/post/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;param&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## Example After&lt;/span&gt;
&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;POST_NAMES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;post_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; base_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the post urls for testing.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; base_url &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/post/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;param&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I had to update any other fixtures in the &lt;code&gt;conftest.py&lt;/code&gt; file to use the
&lt;code&gt;base_url&lt;/code&gt; &lt;em&gt;fixture&lt;/em&gt;, instead of the old constant variable. I just added
&lt;code&gt;base_url&lt;/code&gt; to the parameter list, and replaced the variable name in the fixture
definitions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Update test functions to use &lt;code&gt;base_url&lt;/code&gt; fixture&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;## Example Before&lt;/span&gt;
&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@flaky&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_md_links&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Checks that the markdown links are not broken.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; post_md_link &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; SKIP_LINKS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;skip&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; in skip list&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post_md_link
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BASE_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The link &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is not found.&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The link &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is forbidden.&quot;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## Example After&lt;/span&gt;
&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@flaky&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_md_links&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;base_url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Checks that the markdown links are not broken.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; post_md_link &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; SKIP_LINKS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;skip&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; in skip list&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post_md_link
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; base_url &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The link &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is not found.&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The link &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is forbidden.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like the fixtures, I had to update the test functions to use
the &lt;code&gt;base_url&lt;/code&gt; fixture, instead of the &lt;code&gt;BASE_URL&lt;/code&gt; constant. Again, this was
done by adding the fixture name as a parameter to the test function, and swapping
out the references in the code.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Remove &lt;code&gt;BASE_URL&lt;/code&gt; from &lt;code&gt;constants.py&lt;/code&gt;, and imports of it in other files.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lastly, I did some cleanup and removed the &lt;code&gt;BASE_URL&lt;/code&gt; definition from the
&lt;code&gt;constants.py&lt;/code&gt; file, along with any &lt;code&gt;import&lt;/code&gt;s of that variable in the other
files.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;With those changes, what are the results? Well, I can still run my test
execution command the same as before, and it will default to running the tests
against my CI server:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pipenv run pytest &lt;span class=&quot;token parameter variable&quot;&gt;--workers&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, if I want to point the tests at a hugo server different from the
default, I can now define it with the &lt;code&gt;--base-url&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pipenv run pytest &lt;span class=&quot;token parameter variable&quot;&gt;--workers&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; --base-url &lt;span class=&quot;token string&quot;&gt;&quot;http://10.0.9.104:1313&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The best part of this change is that I don’t have to worry about forgetting to
switch that config back and forth when pushing to my repo. I simply set the
variable when running the tests. Done. I should have made this change years
ago.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Custom Resolutions in Gnome Wayland</title>
    <link href="https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/" />
    <updated>2022-11-01T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/ZZiALTtJd9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/ZZiALTtJd9-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Downtown Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The other week, I worked from Red Hat Tower (Raleigh). In that office, IT is
testing out a few flex-workstations with an ultra-wide monitor (&lt;a href=&quot;https://www.samsung.com/us/computing/monitors/uhd-and-wqhd/34-ultra-wqhd-monitor-with-1000r-curvature-ls34a650uxnxgo/#specs&quot;&gt;Samsung
S65UA&lt;/a&gt;), which
I decided to try out. It was the first time I used a &lt;em&gt;real&lt;/em&gt; ultrawide
monitor for an entire workday. Since then, I have continued to occasionally
experiment with working in a 21:9 aspect ratio… on my 16:9 monitors.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;My Ultrawide Experience:&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/hB5vvBPA7d-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/hB5vvBPA7d-1200.jpeg&quot; alt=&quot;Ultrawide monitor I used at work&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Ultrawide monitor I used at work.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While using the ultrawide monitor for work, I learned that it fits &lt;em&gt;many&lt;/em&gt; of my
workflows. This is mostly because I always have my
&lt;a href=&quot;https://obsidian.md&quot;&gt;obsidian&lt;/a&gt; notes opened. The extra width allowed me to
have a full window for whatever meeting or code I was working on, with the
remaining space a perfect size for my notes. It’s also nice to have 3
full-ish windows for other tasks.&lt;/p&gt;
&lt;p&gt;Since then, I’ve wanted to test the 21:9 workflow a bit more… but I
don’t have an ultrawide monitor. However, I &lt;em&gt;do&lt;/em&gt; still have my old, massive, 42.5&amp;quot; 4k
monitor that we now use as a bedroom TV and second workstation.&lt;/p&gt;
&lt;h2&gt;I’ve done this before…&lt;/h2&gt;
&lt;p&gt;Looking through my post archives, one might notice that I’ve written about
doing something very similar in the past. When the &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/&quot;&gt;lg ud4279b was my main
monitor&lt;/a&gt;, I used it as a canvas for what I called ‘&lt;a href=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/&quot;&gt;sub
monitor setups&lt;/a&gt;’. I even wrote a
script to make doing so easy. Unfortunately, the method I used only works for
X11, and not in Gnome+Wayland.&lt;/p&gt;
&lt;h2&gt;Setting a custom resolutions in Gnome Wayland&lt;/h2&gt;
&lt;p&gt;Now that I prefer using wayland (on Gnome) instead of x11, I have had a much
harder time creating custom resolutions to fake an ultrawide monitor on large
displays.&lt;/p&gt;
&lt;p&gt;Luckily, I recently found &lt;a href=&quot;https://davejansen.com/add-custom-resolution-and-refresh-rate-when-using-wayland-gnome/&quot;&gt;this wonderful
post&lt;/a&gt;,
that finally helped me setup a &lt;em&gt;workable&lt;/em&gt; solution on wayland gnome. They get
all the credit. Here’s a rough outline of what I did.&lt;/p&gt;
&lt;h3&gt;My steps:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;List/View ‘graphics’ devices and guess which one might be the one for your current display:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;➭ &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; /sys/class/drm/card*&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;From that output, you can see the known resolutions for each display by
looking at the &lt;code&gt;modes&lt;/code&gt; file, if it exists (if it doesn’t exist, you likely
picked the wrong device):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;➭ &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; /sys/class/drm/card1-DP-1/modes
3840x2160
3840x2160
3840x2160
3840x1600
2560x1440
1920x1080
1920x1080
1920x1080
1280x720
1280x720
1024x768
800x600
720x480
720x480
640x480
640x480
640x480&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;After confirming the monitor name, it can be used to add to the
&lt;code&gt;GRUB_CMDLINE_LINUX&lt;/code&gt; line in the grub config, adding a fake monitor
resolution for that device. On Fedora 36, I had to edit &lt;code&gt;/etc/default/grub&lt;/code&gt;.
For example, I appended &lt;code&gt;video=DP-1:3840x1600@60&amp;quot;&lt;/code&gt; to the line, which added a
3840x1600 @60Hz resolution option in my config:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;GRUB_CMDLINE_LINUX=&amp;quot;rd.luks.uuid=luks-5a3d262f-9081-41de-ab1d-0266cab3b7fc rhgb quiet video=DP-1:3840x1600@60&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: It’s a good idea to first backup this file before editing it.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After modifying the file, remember to rebuild the grub config:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; grub2-mkconfig &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /boot/grub2/grub.cfg&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Lastly, reboot and the resolution should be available in the display settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Things to note&lt;/h2&gt;
&lt;p&gt;While this &lt;em&gt;works&lt;/em&gt;, it isn’t a perfect solution and there are a fewt things to
be aware of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When connected to the configured display, the device will use the resolution
you defined when booting.&lt;/li&gt;
&lt;li&gt;It seems you can only set one resolution per display device (input). This is kind of annoying and limiting.&lt;/li&gt;
&lt;li&gt;I had at least one experience where the display changed my resolution to
something weird that I couldn’t fix until I disconnected and reconnected my
monitor. So you might experience minor issues like that from time to time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/NJ_qNiScws-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/custom-resolution-gnome-wayland/NJ_qNiScws-1200.jpeg&quot; alt=&quot;Fake ultrawide monitor resolution&quot; width=&quot;1200&quot; height=&quot;1033&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Fake ultrawide resolution on my large monitor, under Wayland Gnome.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While ultrawides might be well suited for my work tasks, I don’t think they are
ideal for the rest of my personal monitor needs. Still, it’s fun to have the
option to turn a large 16:9 into a fake ultrawide for when I just want to work
with that type of display. And now, I don’t even have to leave my wayland session
to do it!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The iPad Magic Keyboard Case</title>
    <link href="https://ryan.himmelwright.net/post/ipad-magic-keyboard/" />
    <updated>2022-09-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/ipad-magic-keyboard/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/WJlUvT4N-q-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/WJlUvT4N-q-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;City Hall, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As mentioned in my &lt;a href=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup&quot;&gt;previous post&lt;/a&gt;, I found
myself trapped on the couch, bed, or generally away from my desk when my son
was born. However, I still wanted to work on projects when possible. As a
result, I started grabbing my &lt;a href=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021&quot;&gt;11&amp;quot; iPad Pro&lt;/a&gt;
more than ever. Using the iPad for nearly everything, I began to
wonder… should I get the Magic Keyboard to go with it?&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background/Why&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/Hmvnm2lT_V-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/Hmvnm2lT_V-1200.jpeg&quot; alt=&quot;Keyboard and trackpad setup with old case&quot; width=&quot;1200&quot; height=&quot;899&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Keyboard and Trackpad setup with my old iPad case.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the past, I would sometimes use a keyboard and trackpad with the iPad, but
it was clunky and not what I would call ‘portable’. I’ve always wanted a
keyboard case for the iPad, but not at the expense of making it
an unwieldy device.&lt;/p&gt;
&lt;p&gt;Sitting on the couch in the middle of many nights, I conducted a bunch of research on
all the various cases out there, and eventually decided on the official Apple Magic
keyboard. All of the other options either didn’t have a trackpad, were massive,
or just didn’t seem as well matched for my needs.&lt;/p&gt;
&lt;p&gt;Yes, the magic keyboard is very expensive, as many reviews state. However, most
reviewers also claim that despite the high cost… they love the
magic keyboard. This is a ‘buy once, cry once’ item (quite common
with Apple hardware). So, one night at 3 AM, in a new parent daze, I ordered one.&lt;/p&gt;
&lt;h2&gt;Initial Thoughts&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/NW2_GXnUMw-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/NW2_GXnUMw-1200.jpeg&quot; alt=&quot;New magic keyboard case with sticker&quot; width=&quot;1200&quot; height=&quot;995&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New magic keyboard case, with sloth sticker.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When the keyboard arrived several days later, I recorded a few &lt;em&gt;initial&lt;/em&gt;
thoughts during the first day or two of use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It felt heavy&lt;/strong&gt;: All of the reviews I watched said this, but I blew it off
thinking they were being a bit dramatic. I knew it weighed more than the
iPad, but my iPad without a case feels &lt;em&gt;very&lt;/em&gt; light, so I thought that the
case couldn’t be &lt;em&gt;that&lt;/em&gt; bad. But when I first picked it up … I &lt;em&gt;was&lt;/em&gt;
surprised at the heft it had.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The keyboard felt great&lt;/strong&gt;: I liked the &lt;em&gt;‘pop’&lt;/em&gt; the keys had when typing,
and loved how low and flat the keyboard felt when on a desk. I did worry
that my palms sat right on the edge and that during longer typing
periods, it might be uncomfortable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It looked plain&lt;/strong&gt;: So I added a sloth sticker 😄.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Longer term Thoughts&lt;/h2&gt;
&lt;p&gt;Now that I’ve been using the iPad with the magic keyboard for several months,
here are some &lt;em&gt;refined&lt;/em&gt; thoughts.&lt;/p&gt;
&lt;h3&gt;The Good&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/8v1kkxn-zk-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/8v1kkxn-zk-1200.jpeg&quot; alt=&quot;the floating hinge makes it very easy to grab the ipad off the case for reading.&quot; width=&quot;1200&quot; height=&quot;788&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The floating hinge makes it very easy to pop the iPad off the case for reading.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The weight isn’t that bad&lt;/strong&gt;: It is something that might surprise you when
you first pick it up, but mostly from the sheer &lt;em&gt;density&lt;/em&gt;. It’s light for a
keyboard case while remaining quite slim. I learned that the iPad
and keyboard are &lt;em&gt;lighter&lt;/em&gt; than my old 11&amp;quot; netbook I used to love for
its portability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Perfect &lt;em&gt;terminal console&lt;/em&gt; device&lt;/strong&gt;: The portability, satisfying keyboard,
and beautiful screen make this a &lt;em&gt;perfect&lt;/em&gt; terminal console device. I love to
&lt;code&gt;ssh&lt;/code&gt; into systems to work from the iPad. I even updated my Neovim config
and switched back to using it as my main editor, mostly because it’s much
more usable on the iPad compared to VS Code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Easy Reading Setup&lt;/strong&gt;: Despite being a &lt;em&gt;keyboard&lt;/em&gt; case, the iPad still
functions as a great reading device. With my old case, I started to take it
off for reading as the case-less iPad feels so light and nice to hold. The
floating hinge design of the magic keyboard case makes it effortless to pop
off the iPad when I want to read. When done, I plop it down and the magnets
snap it back into place.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multiple functions in one&lt;/strong&gt;: The magic keyboard serves many roles for the
iPad. It’s a keyboard, trackpad, case, magnetic stand, &lt;em&gt;and&lt;/em&gt; additional
charging port. The iPad is at its best when viewed as a &lt;a href=&quot;https://www.macstories.net/stories/modular-computer/&quot;&gt;modular computing
device&lt;/a&gt;. The magic
keyboard is the accessory that brings that paradigm to life.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The ‘Could Be Better’&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/tsOJ2tllgv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/tsOJ2tllgv-1200.jpeg&quot; alt=&quot;&quot; Portrait=&quot;&quot; orientation=&quot;&quot; is=&quot;&quot; nice=&quot;&quot; when=&quot;&quot; writing.&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;1260&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The ability to connect the iPad in portrait orientation would be nice.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Yeah, just as I feared, the edge &lt;em&gt;does&lt;/em&gt; cut into my palms over time. It is
worst when typing with the iPad on my lap, but hardly a problem when on a
table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As far as I can tell, there is no way to use &lt;code&gt;FN&lt;/code&gt; keys with this keyboard. I
figured I’d be able to trigger them by combining the numbers with the globe
key or another modifier, but that doesn’t seem to be the case. &lt;em&gt;WHY!?!?&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is nowhere to &lt;em&gt;securely&lt;/em&gt; attach an Apple Pencil. It still magnetically
attaches to the side, but easily falls off when in a bag. As a result, I’ve
given my pencil to my wife to use with her iPad mini for now.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The case smudges easily and is hard to clean.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I wish I had the option to set or flip the iPad into a portrait orientation.
This would be nice while reading long documents or when writing.
Because the iPad only connects and powers the keyboard via the SMART
connector, it’s not possible to connect to it in alternative orientations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/_psL_B7O5m-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ipad-magic-keyboard/_psL_B7O5m-1200.jpeg&quot; alt=&quot;New magic keyboard.&quot; width=&quot;1200&quot; height=&quot;1063&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My magic keyboard setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I love the magic keyboard case and am so happy I took the plunge and bought it.
It has transformed my iPad from a video and reading consumption device, to a
portable, productivity, powerhouse. I can do all of my &lt;em&gt;portable&lt;/em&gt; computing
with the iPad now, and it has filled the role that my netbooks and other
&lt;a href=&quot;https://ryan.himmelwright.net/post/my-new-used-x230&quot;&gt;portable laptops did in the past&lt;/a&gt;. All of this,
while remaining a perfect &lt;em&gt;tablet&lt;/em&gt; for reading and watching videos.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Obsidian Mobile Setup</title>
    <link href="https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/" />
    <updated>2022-08-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/1t7FFBQJXH-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/1t7FFBQJXH-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;974&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Downtown Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I hate typing &lt;em&gt;anything&lt;/em&gt; on my mobile devices. If I need to write more than a
few words, I prefer to sit down at a computer. However… I recently became a
parent, and suddenly found myself trapped on a couch at all hours. From my
nest, I didn’t have enough space or mobility to use my laptop easily. So,
I needed to start using Obsidian on my mobile devices.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background &amp;amp; Motivation&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/p2DThTl3bM-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/p2DThTl3bM-1200.jpeg&quot; alt=&quot;My Desktop Obsidian Setup.&quot; width=&quot;1200&quot; height=&quot;764&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Desktop Obsidian Setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Two weeks into parenthood, I wanted to restart the habit of writing &lt;a href=&quot;https://help.obsidian.md/Plugins/Daily+notes&quot;&gt;obsidian
daily notes&lt;/a&gt; as the
day progressed, instead of catching up in bulk every few days. Finding time at a laptop was
hard, but I could &lt;em&gt;sometimes&lt;/em&gt; find moments during the day to type on my phone,
or even &lt;a href=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/&quot;&gt;my iPad&lt;/a&gt;. Unfortunately, while a big
motivator for &lt;a href=&quot;https://ryan.himmelwright.net/post/trying-notion/&quot;&gt;switching to Notion&lt;/a&gt;, and &lt;a href=&quot;https://ryan.himmelwright.net/post/obsidian-basics/&quot;&gt;later
Obsidian&lt;/a&gt; was that I could access my notes on mobile
devices, I only setup obsidian to &lt;em&gt;read&lt;/em&gt; my notes easily. I didn’t have a good
setup for &lt;em&gt;writing&lt;/em&gt; notes. So, I set out to make some improvements.&lt;/p&gt;
&lt;h2&gt;Sync/File Setup&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/v8aWZLoTC2-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/v8aWZLoTC2-1200.jpeg&quot; alt=&quot;My new daily note folder structure&quot; width=&quot;1200&quot; height=&quot;457&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My new daily note&#39;s folder structure.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/&quot;&gt;Previously&lt;/a&gt;, I used working-copy to sync my
obsidian files via git. Since writing that post, I have already switched to
syncing my personal notes with icloud.&lt;/p&gt;
&lt;p&gt;After using icloud sync with obsidian for several months, I’ve learned to open
the &lt;code&gt;Files&lt;/code&gt; application before even attempting to open Obsidian on my phone
or iPad. This helps to ensure icloud has synced all my files, and prevents
duplicate Daily Note files from being created.&lt;/p&gt;
&lt;p&gt;Another improvement that I learned was that you can set the Daily Note path to
include directories, (example: &lt;code&gt;YYYY/MM-MMMM/YYYY-MM-DD&lt;/code&gt;), and it will create
and store the notes in that folder hierarchy. The only problem I ran into, was
that the keyboard shortcuts for the &lt;code&gt;Today&lt;/code&gt;, &lt;code&gt;Previous&lt;/code&gt;, and &lt;code&gt;Next&lt;/code&gt; daily notes
didn’t work after the change. After some research, I learned that the &lt;a href=&quot;https://github.com/liamcain/obsidian-periodic-notes&quot;&gt;periodic
notes&lt;/a&gt;
plugin could fix the problem, as the plugin’s keyboard shortcuts worked. So I
switched to the Periodic Notes plugin, and love all the additional functionality
it provides, along with fixing my shortcut issue.&lt;/p&gt;
&lt;h2&gt;UI tweaks&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/mkhd-9mA4U-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/mkhd-9mA4U-1200.jpeg&quot; alt=&quot;The toolbar in my mobile obsidian&quot; width=&quot;1200&quot; height=&quot;270&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The command toolbar in my Mobile Obsidian.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I next made some changes to the navigation in the mobile app to better fit my
workflow. To do this, went to the &lt;code&gt;Mobile&lt;/code&gt; section of the settings. From there,
items can be dragged and dropped in the “Manage toolbar options” section (Note,
I also have the &lt;code&gt;Advanced Mobile Toolbar&lt;/code&gt; community plugin installed). Here are
the menu bar items I selected, in the order I placed them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Command palette: Open command palette&lt;/strong&gt;: run any Obsidian command, which is
helpful without having keyboard shortcuts on mobile.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Quick Switcher: Open quick Switcher&lt;/strong&gt;: search and open any note&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Periodic Notes: Open previous daily note&lt;/strong&gt;: open the previous daily note&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Periodic Notes: Open daily note&lt;/strong&gt;: Open today’s daily note&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Periodic Notes: Open next daily note&lt;/strong&gt;: Open the next daily note&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add internal link&lt;/strong&gt;: Quickly insert &lt;code&gt;[[]]&lt;/code&gt; to add a link with. This is nice
on the phone where the &lt;code&gt;[&lt;/code&gt;, &lt;code&gt;]&lt;/code&gt; keys are hard to get to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle bullet list&lt;/strong&gt;: Toggle having a list or not. Again, more of an ease
thing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle numbered list&lt;/strong&gt;: Same thing as the bullet list but with numbers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle checkbox status&lt;/strong&gt;: Check and un-check check-box items easily (or turn the
current line into a check-box item if not already)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Indent list item&lt;/strong&gt;: Indents the current line&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Un-indent list item&lt;/strong&gt;: Un-indents the current line&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Undo&lt;/strong&gt;: Un-does last action&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redo&lt;/strong&gt;: Redo previous action&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle keyboard&lt;/strong&gt;: Toggle the onscreen keyboard on and off&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle Live Preview/Source module&lt;/strong&gt;: Switch between the live preview and the
raw markdown&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure mobile toolbar&lt;/strong&gt;: Open the mobile toolbar settings&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These changes made using obsidian on my phone &lt;em&gt;much&lt;/em&gt; easier. It was quicker to
insert links and indents (since I don’t have a tab key), and having
the command button right up front helped me navigate without using
the keyboard commands that I normally rely on.&lt;/p&gt;
&lt;h3&gt;Remote Git snapshot backups&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/Bx8Loz_sD3-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/Bx8Loz_sD3-1200.jpeg&quot; alt=&quot;The output from my git backup script.&quot; width=&quot;1200&quot; height=&quot;746&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The output from my git backup script.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;What I love most about my &lt;a href=&quot;https://github.com/denolehov/obsidian-git&quot;&gt;obsidian-git&lt;/a&gt; plugin setup, is that the git commits act
as &lt;em&gt;snapshot&lt;/em&gt; backups of my vault. If anything gets messed up, I can easily
revert to a previous commit. However, the git plugin isn’t available
on iOS and iPadOS. Even worse, now that I use icloud sync for my personal
vault, it isn’t compatible with &lt;a href=&quot;https://workingcopy.app&quot;&gt;working copy&lt;/a&gt; either (and stand-alone working
copy doesn’t like the sub module setup I use for my vault either).&lt;/p&gt;
&lt;p&gt;Even though I am currently unable to use git from my mobile obsidian setups, I
still wanted an easy way to kick off these git ‘snapshots’ while writing. So, I
automated it into a script that I can execute remotely:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /Users/ryan/Library/Mobile&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt; Documents/iCloud~md~obsidian/Documents/ryan-vault
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git pull...&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; pull
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;git add *...&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; *
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;git commit...&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;vault-backup -- &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;date&lt;/span&gt; +%Y%m%d-%H%M&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt; (script)&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;git push...&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;Done!&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;date&lt;/span&gt; +%Y%m%d-%H%M&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script first pulls the latest commits to make sure the repo is up to date.
Then, it adds the changed files, creates a time-stamped commit message (similar
to what I have obsidian-git write), and pushes the commit. It’s not fancy, but
it works.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/mXh5DhAmIl-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/mXh5DhAmIl-1200.jpeg&quot; alt=&quot;The git log output of the backup git message.&quot; width=&quot;1200&quot; height=&quot;1278&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The git log messages of the backup commits. The `(script)` commits are 1 the ones generated using this script.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I spent a long time trying to kick off the remote script using an Apple
Shortcut, but it just didn’t want to work. So now, I just ssh to my MacBook via
the &lt;a href=&quot;https://blink.sh&quot;&gt;blink&lt;/a&gt; app, and run a aliased command
(&lt;code&gt;git-backup-ryan-vault&lt;/code&gt;) to execute the script. It works great!&lt;/p&gt;
&lt;h2&gt;Remaining Issues&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/Rl5AaAHexi-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/Rl5AaAHexi-1200.jpeg&quot; alt=&quot;The obsidian sync screen.&quot; width=&quot;1200&quot; height=&quot;275&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The obsidian sync screen.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While this system has allowed me to use obsidian fully from my iPhone and iPad,
there are still a few pain points I’d love to see fixed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I wish I could use both icloud &lt;em&gt;and&lt;/em&gt; working copy in tandem for vault
syncing.&lt;/li&gt;
&lt;li&gt;The Obsidian mobile client can take awhile to load, as it has to
sync all the config files.&lt;/li&gt;
&lt;li&gt;Often when I first open the app (especially on the iPhone), it freezes and I
need to close it and try again. It can take 2-3 attempts to get the app
opened and running on my phone which is very frustrating.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If those issues were fixed, Obsidian mobile would be a much smoother experience.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/E16QYRIKfg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-obsidian-mobile-setup/E16QYRIKfg-1200.jpeg&quot; alt=&quot;My Ipad Obsidian Setup&quot; width=&quot;1200&quot; height=&quot;838&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My iPad Obsidian Setup (In Obsidian Mobile).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;All in all, these changes have worked quite well. I am able to easily write in
Obsidian from my iPad, and even from my iPhone in a pinch. This has allowed me
to access the contents of my vault more readily, and keep up with my notes no
matter how busy life gets!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Flashing My Ferris Sweep Using QMK</title>
    <link href="https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/" />
    <updated>2022-07-16T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/mptFjumAaS-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/mptFjumAaS-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;American Tobacco Campus, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my previous post, I detailed the &lt;a href=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/&quot;&gt;build process for my ferris sweep
keyboard&lt;/a&gt;. After building it, I needed to
flash the keyboard with firmware. Like many custom keyboards, it uses
&lt;a href=&quot;https://qmk.fm&quot;&gt;qmk&lt;/a&gt;. Here’s how I flashed &lt;a href=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/&quot;&gt;my 34 key
layout&lt;/a&gt; on the ferris sweep.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Create the layout file&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/gu__ksQz5V-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/gu__ksQz5V-1200.jpeg&quot; alt=&quot;The qmk configurator tool&quot; width=&quot;1200&quot; height=&quot;1116&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The QMK configurator layout tool.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first step when flashing a QMK keyboard is to build a layout file. I
created my layout using the online &lt;a href=&quot;http://config.qmk.fm&quot;&gt;qmk config tool&lt;/a&gt;.
From the tool, I was able to select a ferris sweep base layout, and then assign
all the keys to match the layout I previously tested on &lt;a href=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/&quot;&gt;my
ergodox&lt;/a&gt; (with a few changes).  Setting all
the keys took some time, but I should only have to do that once. For future
changes, the layout file can be uploaded and edited from the configurator.
Once completed, I downloaded the layout file and saved it to my dotfile repo.&lt;/p&gt;
&lt;h2&gt;QMK Setup&lt;/h2&gt;
&lt;h3&gt;Install &amp;amp; Setup &lt;code&gt;qmk-toolbox&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;With a layout file, we can start using QMK to flash the keyboard. In general,
there is good documentation about how to do that
&lt;a href=&quot;https://docs.qmk.fm/#/getting_started_build_tools&quot;&gt;here&lt;/a&gt;. Regardless of your
setup, the first step is to install QMK:&lt;/p&gt;
&lt;p&gt;On MacOS:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; qmk/qmk/qmk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Linux, it’s suggested to install qmk using &lt;code&gt;pip&lt;/code&gt;. So on my Fedora computers:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; python3-pip
python3 &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--user&lt;/span&gt; qmk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; on Linux I hit a &lt;code&gt;qmk command not found&lt;/code&gt; bug. This is because
&lt;code&gt;$HOME/.local/bin&lt;/code&gt; was not part of the path. So, either add it to your path, or
just run the commend directly: &lt;code&gt;~/.local/bin/qmk COMMAND&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, you need to run the setup. This will take care of several setup tasks, like
downloading the firmware repo and installing dependencies. It will prompt you
with a few questions, but most can remain the default value it suggests.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;qmk setup&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will take a few minutes to download and install everything, but afterwards
should be all set.&lt;/p&gt;
&lt;h3&gt;Compile the hex file&lt;/h3&gt;
&lt;p&gt;To build the firmware file, I first created a new keymap directory and
downloaded my layout file to it:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Copy downloaded keymap file to compile location&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; ryan-test.json ~/Builds/qmk/keyboards/ferris/keymaps/ryan/keymap.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I compiled the new firmware, using the keymap I specified:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Compile layout&lt;/span&gt;
qmk compile &lt;span class=&quot;token parameter variable&quot;&gt;-kb&lt;/span&gt; ferris/sweep &lt;span class=&quot;token parameter variable&quot;&gt;-km&lt;/span&gt; ryan&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, I moved the compiled hex file to a location where the QMK toolbox would
be able to see it (out of a hidden directory).&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Move compiled hex file from build directory to one QMK Toolbox can see (non-hidden)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; ~/Builds/qmk/.build/ferris_sweep_ryan.hex ~/Builds/qmk/ferris_sweep_ryan.hex&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Flashing the Ferris Sweep&lt;/h2&gt;
&lt;p&gt;To flash the ferris sweep, make sure it is connected to &lt;em&gt;one&lt;/em&gt; of the
halves. From there, open up the QMK Toolbox application.&lt;/p&gt;
&lt;h3&gt;Load layout file&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/VrxiBFknvv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/VrxiBFknvv-1200.jpeg&quot; alt=&quot;The qmk toolbox tool&quot; width=&quot;1200&quot; height=&quot;1026&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The QMK toolbox, with my compiled hex file loaded. (Note: the orange device connected text is from hitting the reset, the next step).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the configuration tool, open your compiled hex file.&lt;/p&gt;
&lt;h3&gt;Reset&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/sL086kyJs_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/sL086kyJs_-1200.jpeg&quot; alt=&quot;Shorting the reset pin with a paperclip&quot; width=&quot;1200&quot; height=&quot;809&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using a paper clip to activate the reset pins.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In order to flash the keyboard, you have to switch it to reset mode. If you installed a reset
switch/button, use that. If not, you can reset the board by connecting
the two reset holes using a piece of metal. When I built my sweep, I used a pair
of metal tweezers, but I have since graduated to using a bent paper clip (so
fancy!). Use whatever works.&lt;/p&gt;
&lt;h3&gt;Select Half&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/zzfCwBM8WE-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/zzfCwBM8WE-1200.jpeg&quot; alt=&quot;Selecting the handedness&quot; width=&quot;1200&quot; height=&quot;484&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Selecting the handedness for the firmware.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Due to the reversible PCB for each of my ferris sweep halves, I need to select
the handedness for each half when I flash it. Otherwise, it doesn’t know which
board is for which hand. To do this in the toolbox, navigate to &lt;strong&gt;Tools&lt;/strong&gt; -&amp;gt;
&lt;strong&gt;EEPROM&lt;/strong&gt; in the menubar, and select the handedness you want. You might need
to wait a second or two for it to load.&lt;/p&gt;
&lt;h3&gt;Flash&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/8Yiemvw2DI-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/8Yiemvw2DI-1200.jpeg&quot; alt=&quot;Completed Flash&quot; width=&quot;1200&quot; height=&quot;1026&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Flash complete. (Note the text that specifies that it is done).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With everything set, flash the board by pressing the button! You might
want to watch the log to verify it’s actually done before unplugging it.&lt;/p&gt;
&lt;h3&gt;Repeat for the other side&lt;/h3&gt;
&lt;p&gt;After flashing the first side, repeat the process for the other (load the file,
reset, select handedness, and flash). Afterwards, all should be working!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I’m note positive if this step is required, but I always do it…&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Some Layout Changes&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/WIBnNY2dlw-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/qmk-flashing-ferris-sweep/WIBnNY2dlw-1200.jpeg&quot; alt=&quot;Sweep Layout&quot; width=&quot;1200&quot; height=&quot;1014&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My current ferris sweep keyboard layout.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Since writing the &lt;a href=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/&quot;&gt;post about my layout&lt;/a&gt;, I have made a
few changes after using the ferris sweep for a few more months. Most of these
changes revolve around adding different &lt;em&gt;base&lt;/em&gt; layers that I can swap in and
out using new keys added to the Navigation layer. In addition to my original
base layer, I’ve added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A second default base layer that does not have Home Row Modifiers&lt;/li&gt;
&lt;li&gt;A base layer that uses Colemark instead of QWERTY&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;mouse&lt;/em&gt; toggle (not base) layer that I forget I have&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over time, I’d like to turn my non-home row mod layer into a layout designed
for gaming, which is the one thing that this keyboard does &lt;em&gt;not&lt;/em&gt; do well right
now. But that’s about it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This process took me a little bit to figure out the first time, but now that I
know how it’s done, it isn’t that hard. Also, I don’t need to flash firmware as
often now that I have a stable layout. Luckily, when I &lt;em&gt;do&lt;/em&gt; finally make that
‘gaming’ layout, I’ll have this post to remind me how to flash it!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building My Ferris Sweep</title>
    <link href="https://ryan.himmelwright.net/post/building-my-ferris-sweep/" />
    <updated>2022-05-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/building-my-ferris-sweep/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/f36jyIPb2w-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/f36jyIPb2w-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;756&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Ferris Sweep, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After adapting to the &lt;a href=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/&quot;&gt;34 key layout&lt;/a&gt; on &lt;a href=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/&quot;&gt;my
ergodox&lt;/a&gt;, I felt comfortable enough to start
the process of choosing and building my new keyboard. Following some research and
deliberation, I selected and built a Ferris Sweep. Here’s how it went.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why the Ferris Sweep.&lt;/h2&gt;
&lt;p&gt;I started off by exploring all the different split 34-36 key designs I could find.
This included several of the more popular ones, like the
&lt;a href=&quot;https://www.gboards.ca/product/gergoplex&quot;&gt;gergoplex&lt;/a&gt;,
&lt;a href=&quot;https://github.com/foostan/crkbd&quot;&gt;corne&lt;/a&gt;,
&lt;a href=&quot;https://github.com/pierrechevalier83/ferris&quot;&gt;ferris&lt;/a&gt;, and it’s
derivative, the &lt;a href=&quot;https://github.com/davidphilipbarr/Sweep&quot;&gt;ferris sweep&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After digging into the details of each design, I ended up choosing the sweep for
a few reasons. First, I liked the layout. I got used to the 3x5 split with 2
thumb keys on my modified ergodox setup and it worked. I considered the corne,
but after observing where my pinkies rest on my ergodox, I realized they didn’t
line up with the keys very well. The corne’s columns are much more aligned,
whereas the Ferris designs use a much more aggressive stagger across the board
that I wanted to try. I figured even if the pronounced column stagger &lt;em&gt;didn’t&lt;/em&gt;
work for me, it was a chance to confirm that preference.&lt;/p&gt;
&lt;p&gt;With the general unavailability of pre-built boards at the time, I
knew I would likely have to solder my own. With this being my first keyboard build (and
my first time touching a soldering iron), I wanted a design that wouldn’t be too
complicated. The Ferris Sweep is special in that each key directly maps to an
input on the micro controllers, so &lt;a href=&quot;https://www.youtube.com/watch?v=fBPu7AyDtkM&quot;&gt;you don’t need to solder tiny diodes under
each switch&lt;/a&gt;, which I wanted to avoid.&lt;/p&gt;
&lt;p&gt;So, the sweep won out.&lt;/p&gt;
&lt;h2&gt;Selecting &amp;amp; Ordering the Components&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/2boOSkZGX_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/2boOSkZGX_-1200.jpeg&quot; alt=&quot;The PCB I ordered&quot; width=&quot;1200&quot; height=&quot;570&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Open Source Ferris Sweep PCB I had manufactured&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With my design selected, I compiled a list of all the components I needed for
the build and picked my specific parts for each one:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PCB&lt;/strong&gt;: I wanted to customize the shape and screen print on this, but decided to leave that as an option for a more advanced build down the road. I downloaded the sweep half swept plans (one of the options found &lt;a href=&quot;https://github.com/davidphilipbarr/Sweep&quot;&gt;here&lt;/a&gt;) and uploaded them to &lt;a href=&quot;https://jlcpcb.com&quot;&gt;jlbpcb&lt;/a&gt; to be printed. I picked the &lt;em&gt;half swept&lt;/em&gt; variation because it is a reversible design. This meant that I could use each of the 5 (minimum for order) PCBs for &lt;em&gt;either&lt;/em&gt; half of the board. I choose black purely for looks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Controllers&lt;/strong&gt;: I was tempted to get &lt;a href=&quot;https://nicekeyboards.com/nice-nano/&quot;&gt;nice!nano&lt;/a&gt; controllers for wireless bluetooth, but I didn’t want to figure out batteries, and zmk vs. qmk stuff during my first build (plus, they weren’t easily available at the time). So, I went with 2 usb-c based &lt;a href=&quot;https://deskthority.net/wiki/Elite-C&quot;&gt;Elite-C&lt;/a&gt; micro controllers instead. I also purchased some TRRS jacks, since the build would be wired.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Switches&lt;/strong&gt;: This specific PCB design only supports the low profile Kaih Choc switches, but I had no idea &lt;em&gt;which&lt;/em&gt; choc switch to get. Historically, I like heavier, tactile switches. I loved the topre switches on &lt;a href=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/&quot;&gt;my HHKB&lt;/a&gt;, and my ergodox has MX browns (which are &lt;em&gt;fine&lt;/em&gt;, although I wish it had clears). Even the laptop keyboards I love (thinkpads and my 14&amp;quot; MacBook Pro) all have a distinctive &lt;em&gt;bump&lt;/em&gt; at the start of the press. Choc switches however… aren’t really known for good tactile options. I ordered a switch tester and the choc browns were even more unsatisfying than MX Browns. They just felt like scratchy linears. I was &lt;em&gt;very&lt;/em&gt; tempted by clickly choc switches. They use a click-&lt;em&gt;bar&lt;/em&gt; that feels soooo good. I almost went with clicky white or even heavier &lt;a href=&quot;https://www.youtube.com/watch?v=8T5kjkf6y5o&quot;&gt;navy switches&lt;/a&gt;, but I wanted a keyboard that was &lt;em&gt;quieter&lt;/em&gt;, not louder than my current one (especially being my portable device). So, I decided to go with &lt;em&gt;linear&lt;/em&gt; switches. I actually like to bottom out (softly) when I type, so I thought the linear switches might not be too bad. I also read many people claim using light linear switches on these small, 30-some key, low profile boards pairs very nicely. So, I decided go with the even lighter &lt;em&gt;silver&lt;/em&gt; switches instead of the reds I tested (I also thought it would look nicer 😆). Many people use gChoc switches on these small boards, but the 40gf silvers was as light as I was willing to go.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Caps&lt;/strong&gt;: Lastly, I had to pick key caps. There aren’t &lt;em&gt;too&lt;/em&gt; many options for choc switches, and availability can be quite low for what is out there. My main option was the &lt;a href=&quot;https://mkultra.click/mbk-choc-keycaps&quot;&gt;MBK Choc keycaps&lt;/a&gt;, which come in only black or white. I originally wanted black, but the more I thought about it, the more I leaned towards the white. I thought the contrast of white on the black PCB would look great, and with these being blank PBT keys, I wasn’t as concerned about wearing and yellowing. So, I ordered about 40 1u white caps, and 2 with the positioning bump.&lt;/p&gt;
&lt;h2&gt;Building the Board&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/_VPjD9qrvT-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/_VPjD9qrvT-1200.jpeg&quot; alt=&quot;My Desk while soldering&quot; width=&quot;1200&quot; height=&quot;868&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My desk while soldering the keyboard&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After ordering all the components, I had to wait a few weeks for them to ship.
When everything arrived… I waited a few &lt;em&gt;more&lt;/em&gt; weeks to actually do the build
😆. This was &lt;em&gt;mostly&lt;/em&gt; due to hosting visiting family and friends a few weekends
in a row. The other delaying factor was that I was a bit nervous to start my
first build.&lt;/p&gt;
&lt;p&gt;When I did finally start, it wasn’t too bad. I took my time soldering, working
on the TRRS jacks first to get a hang of it, followed then by the micro
controllers.  The hardest part of the process was that there wasn’t a good way
for me to know if I was soldering correctly. After securing the elite-c
controllers, I was able to connect the boards to my laptop, open a keyboard
tester app, and use a pair of metal tweezers to check that the ‘keys’ were
recognized by the controller (and that I hadn’t fried it, or the pcb). Verifying
that I hadn’t horribly destroyed anything yet  gave me some much needed
confidence.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/wA2ANqaVw7-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/wA2ANqaVw7-1200.jpeg&quot; alt=&quot;The completed keyboard&quot; width=&quot;1200&quot; height=&quot;522&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My completed ferris sweep, before a few add-ons.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;From there, I continued on to solder the switches. When they were all done, I
once again tested that each switch worked. Finally, I popped on the key caps and was
done! (I spent some time figuring out how to flash &lt;em&gt;my&lt;/em&gt; layout to the board, but
that’s a post for another time). Other than a sensitive &lt;code&gt;u&lt;/code&gt; key, the
board to worked just as intended!&lt;/p&gt;
&lt;h2&gt;Additional Post-Build Components&lt;/h2&gt;
&lt;h3&gt;&lt;em&gt;Needed&lt;/em&gt; Enhancements&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/mt9XRMeLE--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/mt9XRMeLE--1200.jpeg&quot; alt=&quot;The keyboard with the new TRRS cable&quot; width=&quot;1200&quot; height=&quot;1074&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using the keyboard with the new TRRS cable and rubber feet.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After the finishing the core of my build, there were a few more components I
purchased to improve it. First, I wanted to get a new TRRS cable. The ones I had
were either too thick for such a small board, or too small in length (they
didn’t allow me to split the board to shoulder width like I prefer). So, ordered
a 1 meter, braided, sliver and black cable from &lt;a href=&quot;https://keeb.io&quot;&gt;keeb.io&lt;/a&gt;.
During the ordering process, I learned that they are based right here in Durham,
NC!&lt;/p&gt;
&lt;p&gt;Part of what makes the ferris sweep so thin is that doesn’t have a case. It’s
just a PCB. The problem with using a case-less board, is that you need a
solution to elevate/cover the bottom of the PCB to protect &lt;em&gt;it&lt;/em&gt; and the surfaces
it’s on.  I started with using little rubber feet, but it was very hard to
position them so they were all the same length (not on a solder joint), &lt;em&gt;and&lt;/em&gt;
have the board evenly supported, so edge keys didn’t tip it. I had a case 3D
printed… but it was sized too small and ultimately didn’t work. However, while
messing with the case, I started to play with tape mods and found that putting
painters tape on the bottom of the PCB actually solved the majority my problem.
It does slide a bit on a smooth surfaces, but I’ve been putting it on two
gripping carpet anti-slip triangles. Eventually, I plan to cut an old mouse pad to
fit under each keyboard half.  But so far, this is working well.&lt;/p&gt;
&lt;h3&gt;Nice to Haves&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/fgTilHgnlp-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/fgTilHgnlp-1200.jpeg&quot; alt=&quot;Using the keyboard at the office&quot; width=&quot;1200&quot; height=&quot;1028&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My &#39;bluetooth&#39; setup at the office, using the magnetic cables, battery, and bluetooth adapter.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For my first ‘nice-to-have’ modification, I made some alterations to my board’s
USB-C connectors. First, despite concern about the dangers using of USB-C
magnetic adapters, I ordered a few. These are little plugs that I’ve placed in
the USB-C port of each keyboard half, paired with a few magnetic adapters I keep on
the ends of my usb-c cables for the keyboard. This modification essentially gave my keyboard
‘mag-safe’ cables. Now, I can easily switch which half the board is connected to
(useful when flashing it), or immediately disconnect it from my desk to take with me. I don’t
have to tediously unplug the cable from the micro-controller (which is a pain
and feels so fragile to me). The adapters were an ugly blue color, so I wrapped
them in black electrical tape, an now they fit right in.&lt;/p&gt;
&lt;p&gt;Lastly, I mentioned previously how I decided to not use the Nice!Nano bluetooth
controllers. However, after using
the ferris sweep for a short period of time, I realized how wonderful it would
be to simply pull out the two keyboard halves without any wires, plop them on a
surface, and start typing. While I still agree with my decision to start with a
wired board (which works well at my desk), if/when I make another small split
board, I think I’ll go for a fully wireless build. To help hold of the desire
for a new board, I ordered the &lt;a href=&quot;http://handheldsci.com/kb/&quot;&gt;only bluetooth adapter of this kind I could find&lt;/a&gt;. It’s big and ugly, but works &lt;em&gt;very&lt;/em&gt; well. Using the adapter paired
with a battery bank, I’m able to wirelessly connect my keyboard to all my
bluetooth devices. This helps clean up the wires in my portable setup, and is
especially nice when I have my laptop on a stand, as I don’t need a bunch of
cables hanging all over the place. I love it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/Hoy_gwZbke-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-my-ferris-sweep/Hoy_gwZbke-1200.jpeg&quot; alt=&quot;The keyboard at my Desk setup&quot; width=&quot;1200&quot; height=&quot;802&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Ferris Sweep at my main desk setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Overall, I have been &lt;em&gt;very&lt;/em&gt; happy with my Ferris Sweep. The column stagger of
the ferris layout naturally fits my hands better than any other keyboard I have
used, and I have surprisingly become a fan of the lighter linear switches on a
low profile board like this one. Lastly, I love how this keyboard works as an
efficient daily driver board at my desk, but is still small enough that I can
simply pop it off the magnetic cable, slide it in my bag, and use it as my
portable keyboard as well.&lt;/p&gt;
&lt;p&gt;Despite this being my first full build, I am very pleased with my experience.  I
was nowhere near perfect putting it together, but I learned that you don’t have
to be (for the most part). It wasn’t that hard of project, and I have much more
confidence if I ever make a second build. But right now, I am thrilled to be using
this keyboard.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a 34 Key Keyboard Layout</title>
    <link href="https://ryan.himmelwright.net/post/building-34-key-layout/" />
    <updated>2022-04-16T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/building-34-key-layout/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/ZTmu3upKZS-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/ZTmu3upKZS-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Cameron Stadium, Duke University, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the past year and a half, I have been using an Ergodox EZ keyboard and
loving every minute of it. It does have a downside tough. As a  &lt;a href=&quot;https://deskthority.net/wiki/60%25&quot;&gt;60%
keyboard&lt;/a&gt; lover, it is &lt;em&gt;quite&lt;/em&gt; large.  So, I
have been on the look out for a smaller, split keyboard (I can’t go back to
non-split), that I can travel with and use for a portable setup. During this
research, I was introduced to 34-36 key split layouts, but wasn’t sure if I
could manage one. To test out the idea, I decided experiment with new layouts on
my Ergodox…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background Concepts&lt;/h2&gt;
&lt;p&gt;Before digging into layout design, lets establish a few key concepts we’ll
need to understand for the keyboard layout.&lt;/p&gt;
&lt;h3&gt;Layers&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/KTm0POZFvi-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/KTm0POZFvi-1200.jpeg&quot; alt=&quot;iOS keyboard layers&quot; width=&quot;1200&quot; height=&quot;239&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Like most mobile devices, iOS keyboards utilize layers.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;First, lets start with layers. This is something you are probably already
familiar with, as most people use keyboard layers every day without realizing
it. Your smartphone’s software keyboard likely has a small layout that utilizes
layers. For example, my iPhone has a base layer containing the letters of the
alphabet, along with a space bar, shift, backspace, enter, and… yep, layer toggle
keys! If you want to use numbers or symbols, you tap the &lt;code&gt;123&lt;/code&gt; key, and iOS switches
to the &lt;em&gt;number&lt;/em&gt; layer which contains number keys along with some symbols. From
here you can use whatever symbols you need, hit &lt;code&gt;ABC&lt;/code&gt; to return to the base
layer, or tap the &lt;code&gt;#+=&lt;/code&gt; key to enter &lt;em&gt;another&lt;/em&gt; layer with even more symbols.&lt;/p&gt;
&lt;p&gt;Keyboard layers are something we have all become accustomed to. They allow us
to use many more keys than a keyboard physically has.  With only 34 keys to work
with, good use of layers is required for my layout.  While toggling layers on a
phone is obnoxious and can be annoying, I have found that the task isn’t nearly
as bad on a physical keyboard, especially if it has thumb keys.&lt;/p&gt;
&lt;h3&gt;Tap vs. Holds&lt;/h3&gt;
&lt;p&gt;The next, but likely first &lt;em&gt;new&lt;/em&gt; concept to learn is how key &lt;em&gt;taps&lt;/em&gt; differ
from &lt;em&gt;holds&lt;/em&gt;.  In &lt;a href=&quot;http://qmk.fm&quot;&gt;qmk&lt;/a&gt; (the open source keyboard firmware), &lt;em&gt;tapping&lt;/em&gt;
and &lt;em&gt;holding&lt;/em&gt; a key can execute different functionality.  For
example, you can set the &lt;code&gt;Caps Lock&lt;/code&gt; key to work as normal when tapped, but
function as a &lt;code&gt;CTRL&lt;/code&gt; when you press and &lt;em&gt;hold&lt;/em&gt; it for a set period of time. I
already use this feature on the Ergodox, so it was a conept I’m already familiar
and comfortable with.&lt;/p&gt;
&lt;h3&gt;Home Row modifiers&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/iFb4Z06mPP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/iFb4Z06mPP-1200.jpeg&quot; alt=&quot;Testing home row modifiers on my ErgoDox&quot; width=&quot;1200&quot; height=&quot;575&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Testing 34 Key layouts on my Ergodox.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Above all else, what got me realizing that I might &lt;em&gt;actually&lt;/em&gt; pull off a 34 key
layout, were &lt;em&gt;home row modifiers&lt;/em&gt;. Home row mods are when the home row keys
(&lt;code&gt;ASDF&lt;/code&gt; and &lt;code&gt;JKL;&lt;/code&gt; on a QWERTY layout) are used for keyboard modifiers
(&lt;code&gt;Control&lt;/code&gt;, &lt;code&gt;Shift&lt;/code&gt;, &lt;code&gt;Alt/Opt&lt;/code&gt;, and &lt;code&gt;Windows/CMD/Super&lt;/code&gt;). This can be
accomplished using several methods. For example, using tap/holds, the home row
keys can function as they normally when tapped, but then double as a modifier
when held (ex: &lt;code&gt;a&lt;/code&gt; when taped, but &lt;code&gt;CTRL&lt;/code&gt; when held). Or, these keys might just be
defined as modifiers on a non-base layer, like a symbol layer.&lt;/p&gt;
&lt;p&gt;For someone that uses a bunch of keyboard shortcuts, this is a game changer.
It’s taking the practice of setting the &lt;code&gt;Caps Lock&lt;/code&gt; key to a &lt;em&gt;useful&lt;/em&gt; modifer (I
always set mine to &lt;code&gt;CTRL&lt;/code&gt;), and turning it up to a whole new level.&lt;/p&gt;
&lt;p&gt;I started my experimentation by adding home row ‘hold’ modifiers to my
normal Ergodox layout, just to try it out for a few days. I knew that if I
couldn’t adjust to home row mods, there was no way I would be able handle a 30-something
key keyboard. To my surprise, I adapted to it much quicker than I had expected.
After taking some additional time to test different orders of the modifiers, I
think I’ve settled on (&lt;code&gt;Control&lt;/code&gt;, &lt;code&gt;Shift&lt;/code&gt;, &lt;code&gt;Alt/Opt&lt;/code&gt;, &lt;code&gt;CMD/Super&lt;/code&gt;) for now.&lt;/p&gt;
&lt;p&gt;Switching to home row mods showed me that not only was a smaller layout
possible, but that I might end up &lt;em&gt;preferring&lt;/em&gt; it.&lt;/p&gt;
&lt;h2&gt;Creating the Layout&lt;/h2&gt;
&lt;p&gt;After adopting home row mods, I felt comfortable enough to &lt;em&gt;try&lt;/em&gt; a 34 key split
layout. I started by editing several modified layouts I found online from other
people.&lt;/p&gt;
&lt;h3&gt;Layer Toggle Setup&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/G1hiwiUTjn-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/G1hiwiUTjn-1200.jpeg&quot; alt=&quot;Main 34 key layout&quot; width=&quot;1200&quot; height=&quot;487&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My first 34 key layout on the ergodox.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Influenced by &lt;a href=&quot;https://www.youtube.com/watch?v=8wZ8FRwOzhU&quot;&gt;a popular video on 34 key
layouts&lt;/a&gt;, I started with a
toggle layer switching scheme. If I tapped a thumb key, it would switch to
another layer, and remain there until I hit a key to switch to a different layer.&lt;/p&gt;
&lt;p&gt;This setup worked &lt;em&gt;okay&lt;/em&gt;, but I had a real hard time with the layer switching. I
just couldn’t flow nicely while typing. In addition, the rest of the layout
wasn’t very well thought out, based on &lt;em&gt;how&lt;/em&gt; I use my keyboard. Despite these
issues, spending time with this setup did help me iron out my pain points for my
future revision.&lt;/p&gt;
&lt;h3&gt;Callum Layout&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/_97MUqFsm2-673.webp 673w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/_97MUqFsm2-673.jpeg&quot; alt=&quot;Callum Layout&quot; width=&quot;673&quot; height=&quot;897&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Callum Layout.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While struggling with my first design, I continued to do research. Eventually, I
found &lt;a href=&quot;https://blog.ffff.lt/posts/callum-layers/&quot;&gt;some references&lt;/a&gt; to the
&lt;a href=&quot;https://github.com/callum-oakley/qmk_firmware/tree/master/users/callum&quot;&gt;Callum
Layout&lt;/a&gt;
and decided to try it with my own spin.&lt;/p&gt;
&lt;p&gt;The Callum approach handles layers a little bit differently, opting to instead
toggle layers using key &lt;em&gt;holds&lt;/em&gt;. Rather than tapping a key and locking on a
new layer (like CAPs lock), the keyboard switches to the new layer as long as that
layer’s key is &lt;em&gt;held&lt;/em&gt; (like SHIFT). So, holding a left thumb key switches to the
navigation layer, holding a right hand thumb key switches to the symbol layer,
and holding &lt;em&gt;both&lt;/em&gt; activates a number layer. This method works surprisingly
well. It might sound like you need to be an octopus to use the number layer, but
because the layers are controlled using thumb keys, all of your typical keyboard
fingers are still available. This layer switching technique worked &lt;em&gt;much&lt;/em&gt; better
than the toggles for me.&lt;/p&gt;
&lt;p&gt;Another reason the Callum layout functions so well is that it allocates home row
modifiers on each layer. Specifically, the home row contains normal modifiers
that can be tapped &lt;em&gt;or&lt;/em&gt; held (well sort of, more on that in the next section).
Whichever hand is holding the layer key, is the side that the modifiers are on,
leaving the whole other half available for most of the layer’s keys (except the
number layer, which has modifiers on both sides). This helps you efficiently use
both of your hands.&lt;/p&gt;
&lt;p&gt;Lastly, the Callum layout enables me to easily use modifiers in other layers,
which I use all the time for navigation shortcuts (ex: &lt;code&gt;CTRL/CMD&lt;/code&gt; + &lt;code&gt;-&amp;gt;&lt;/code&gt; to
shift workspaces, or &lt;code&gt;CTRL/CMD&lt;/code&gt; + &lt;code&gt;NUM&lt;/code&gt; to switch browser tabs).&lt;/p&gt;
&lt;h3&gt;One Shot Modifiers&lt;/h3&gt;
&lt;p&gt;However, note that the &lt;em&gt;original&lt;/em&gt; Callum arrangement &lt;em&gt;doesn’t&lt;/em&gt; include home row
modifiers on the base layer (not even holds). So, you might be wondering “how
are the most basic keyboard shortcuts like copy/paste (usually &lt;code&gt;CMD/CTRL&lt;/code&gt;+&lt;code&gt;c&lt;/code&gt; and
&lt;code&gt;CMD/CTRL&lt;/code&gt;+&lt;code&gt;p&lt;/code&gt; respectively) accomplished with this configuration?!”&lt;/p&gt;
&lt;p&gt;Modifiers are still available on the base layer using something called &lt;em&gt;One Shot
Modifiers&lt;/em&gt;. One shot mods function most of the time as normal modifiers (you can
tap or hold them like any other &lt;code&gt;ctrl&lt;/code&gt; or &lt;code&gt;shift&lt;/code&gt; key), but if you tap them
without pressing any other keys, it will be queued up to be used with whatever
key is pressed next, even if that key is on a different layer.  So, if I wanted
to use &lt;code&gt;CTRL&lt;/code&gt;-&lt;code&gt;c&lt;/code&gt; in the Callumn layout, I could tap and let go of the &lt;code&gt;NAV&lt;/code&gt; and
&lt;code&gt;CTRL&lt;/code&gt; keys, which will then return to the base layout. From there, I hit &lt;code&gt;c&lt;/code&gt; on
the keyboard, and it’s effectively a &lt;code&gt;CTRL&lt;/code&gt;-&lt;code&gt;C&lt;/code&gt;. It’s not as complicated as it
sounds, but it does take getting used to for every day typing.&lt;/p&gt;
&lt;h3&gt;My Layout&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/hhvXJ-G6CO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/hhvXJ-G6CO-1200.jpeg&quot; alt=&quot;My Layout&quot; width=&quot;1200&quot; height=&quot;624&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My 34 key layout.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For my layout, I used the Callum design as my base and modified it to better fit
my preferences. Here are &lt;em&gt;some&lt;/em&gt; of my choices and alterations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I used One Shot Modifiers on my additional layers, but also added home row modifiers
(hold keys) to the base layer, as I find it’s just easier to work with.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I choose to use &lt;code&gt;Space&lt;/code&gt; and &lt;code&gt;Backspace&lt;/code&gt; for my two non-layer thumb keys. I tried
others like &lt;code&gt;Shift&lt;/code&gt; and &lt;code&gt;Tab&lt;/code&gt;, but I really had a hard time transitioning from my
old Ergodox layout without a dedicated &lt;code&gt;backspace&lt;/code&gt; key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I set my &lt;code&gt;Enter&lt;/code&gt; key to under my pinky on the navigation layer, again because
this was hard for me to adapt to it anywhere else. (This also dictated that I
use an ‘upside down T’ arrow key layout instead of VIM-style)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I &lt;code&gt;TAB&lt;/code&gt; a lot, so when I couldn’t have it as a dedicated thumb key, I set it
to a pointer finger key that’s easy to remember and ergonomically comfortable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As a programmer, I configured the various bracket pairs to be accessible and
easy to remember. I made &lt;code&gt;[]&lt;/code&gt; the easiest to use because I mostly code in python
and write notes in &lt;a href=&quot;https://ryan.himmelwright.net/post/obsidian-basics/&quot;&gt;obsidian&lt;/a&gt;, which both extensively
use square brackets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For most of the remaining symbols, I placed them on the opposite hand’s home
row, but in the same order they are on a normal keyboard’s number row, so it’s
easier to remember (ex: &lt;code&gt;!&lt;/code&gt;, &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;…). For the few symbols that extend
beyond the 5 keys (excluding &lt;code&gt;()&lt;/code&gt; because they’re already defined elsewhere), I
just continued the order in the next row.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For numbers, I learned I actually preferred a number row over a number pad in
this type of layout. The top row is not hard to reach, now that I only have to
extend my fingers up one row. Additionally, giving that the layout is only 10
keys wide, it’s easy to locate each number. I just count my fingers. This setup
also made it easy to incorporate the function keys in the same layer in a
meaningful order.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the other keys (but not all) were also thoughtfully placed in the
layout, but it’s not worth rambling on any more about each one.&lt;/p&gt;
&lt;h2&gt;Issues/Future Improvements&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/C4g1NChut_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/building-34-key-layout/C4g1NChut_-1200.jpeg&quot; alt=&quot;My ErgoDox with a 34 key layout&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Testing 34 Key layouts on my Ergodox.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While the layout works great for me most of the time, there are a few issues and
areas I’d like to improve over time:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some common symbol locations aren’t ideal&lt;/strong&gt; - I said that &lt;em&gt;most&lt;/em&gt;, but not &lt;em&gt;all&lt;/em&gt; of the symbol locations were well thought out. Over time, I would like to find better locations for symbols that I use a lot, but don’t feel right while using this layout. For example, &lt;code&gt;~&lt;/code&gt; and &lt;code&gt;;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad for gaming&lt;/strong&gt; - The home row mod’s mean that I can’t hold 3 out of 4 ‘WASD’ keys… so that’s problem. I’m thinking that I might be able to add a gaming layer at some point that I can toggle into when playing games.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m not able to do some actions one handed&lt;/strong&gt; - I can do a few tasks like change the volume with only one hand, but many of my navigation shortcuts require two now. This could be worked around if I &lt;em&gt;really&lt;/em&gt; did some usage analysis and thought about it. For now it’s not too much of an issue. Just something to think about improving in the future.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I guess I should cut it here before this post gets any longer. I am not done
tweaking my 34 key layout, and probably won’t be for some time, as I want to
keep learning more about my options using &lt;code&gt;qmk&lt;/code&gt;. However, using the Ergodox with
34 keys over the last few weeks has provided me with the confidence needed to
take the plunge and order parts to build a 34 key split keyboard. But more on
that later!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gnome Font Scaling Script</title>
    <link href="https://ryan.himmelwright.net/post/gnome-font-scaling-script/" />
    <updated>2022-02-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/gnome-font-scaling-script/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/oTBQvOdPO9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/oTBQvOdPO9-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Surf City, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Previously, I was using the &lt;a href=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/&quot;&gt;Pantheon desktop environment on Fedora 35&lt;/a&gt; for my work laptop. I liked how well Pantheon handled font scaling with my 4k monitor, and I figured out how to get the accessability menubar item working in Fedora. This allowed me to easily toggle the scaling factor as I connected and reconnected to different monitors. With that said, I have recently starting using Gnome on the laptop, and miss that feature of Pantheon.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/BLnC0Zk0-6-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/BLnC0Zk0-6-1200.jpeg&quot; alt=&quot;Unscaled 4k display&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;4k Display without scaling.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have been using Fedora Gnome on my work computer for the last month or so. The font scaling works well enough, and it seems to be a little less resource hungry/buggy than my cobbled-together Fedora Pantheon setup.&lt;/p&gt;
&lt;p&gt;Most of the day, my laptop is connected to a 32&amp;quot; 4k monitor, which I &lt;em&gt;need&lt;/em&gt; at
least 125% scaling, and usually &lt;em&gt;prefer&lt;/em&gt; to use 150% scaling. However, when I
disconnect the laptop and want to work elsewhere, the 14&amp;quot; 1080p screen doesn’t
have enough space at 150%, requiring me to tune down the scaling. I wanted a way
to once again easily toggle though different scaling factors, without needing to
navigate through the tweaks settings menus (which can also be difficult to do
when connecting to a monitor that isn’t properly scaling).&lt;/p&gt;
&lt;p&gt;So, I figured out how to script it.&lt;/p&gt;
&lt;h2&gt;Creating the Script&lt;/h2&gt;
&lt;h3&gt;Changing Font Scaling Setting&lt;/h3&gt;
&lt;p&gt;First, I had to learn out how to change the scaling factor using the command
line in Gnome. It turns out this is an item in &lt;code&gt;gsettings&lt;/code&gt;, so it is easy to get
the current value, and set a new one. I used these to commands to start my
script:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;SCALE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;gsettings get org.gnome.desktop.interface text-scaling-factor&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
gsettings &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; org.gnome.desktop.interface text-scaling-factor &lt;span class=&quot;token variable&quot;&gt;$SCALE_SWITCH&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line grabs the current scaling factor and saves it to the &lt;code&gt;SCALE&lt;/code&gt;
variable (for use later). The second line sets the scaling factor to the
value of a &lt;code&gt;SCALE_SWITCH&lt;/code&gt; variable (not defined yet).&lt;/p&gt;
&lt;h3&gt;Simple Logic&lt;/h3&gt;
&lt;p&gt;I wanted to be able to &lt;em&gt;toggle&lt;/em&gt; between my desktop monitor and laptop setups
using a keybinding, but realized I &lt;em&gt;ideally&lt;/em&gt; wanted 3 options. So, I used some
simple logic to flip through the 3 options in order, which would work just as
well:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Set what to toggle to&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;1.0&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;SCALE_SWITCH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.25&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;1.25&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;SCALE_SWITCH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;1.5&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;SCALE_SWITCH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code uses the &lt;code&gt;$SCALE&lt;/code&gt; value we grabbed in the section above, and matches it
to determine what the next value (&lt;code&gt;SCALE_SWITCH&lt;/code&gt;) should be.&lt;/p&gt;
&lt;h3&gt;Notify&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/6woqlrxN_H-649.webp 649w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/6woqlrxN_H-649.jpeg&quot; alt=&quot;Notification message&quot; width=&quot;649&quot; height=&quot;152&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Notification message.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Working in the CLI on the script, I liked having a debug message print out so I
could see the what scale it was switching to and from. After adding the script
to a key-binding, I still wanted that feedback. To accomplish this, I added a
&lt;code&gt;notify-send&lt;/code&gt; line in the script to launch a Gnome notification when it runs.&lt;/p&gt;
&lt;p&gt;This has the potential to be a bit annoying… but it can always commented out
if I really don’t like it anymore:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# (Optional) Message intentions to CLI and GNOME Notifications&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Previous Font Scale: &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt;, Switched to &lt;span class=&quot;token variable&quot;&gt;$SCALE_SWITCH&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;notify-send&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Previous Font Scale: &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt;, Switched to &lt;span class=&quot;token variable&quot;&gt;$SCALE_SWITCH&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These two lines print out a message to both the command line (first line) and
a gnome notification (second line) about what the previous scale was and
what it is being switched to.&lt;/p&gt;
&lt;h2&gt;With the press of a key&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/Ksc8Kd4ONS-503.webp 503w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/gnome-font-scaling-script/Ksc8Kd4ONS-503.jpeg&quot; alt=&quot;Add shortcut to run script&quot; width=&quot;503&quot; height=&quot;300&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Adding shortcut to run the script.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, I wanted the ability to execute the script by hitting a key binding. First, I made a symbolic link to the script location from &lt;code&gt;/usr/bin/toggle-font-scale&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /home/ryan/dotfiles/scripts/toggle-gnome-font-scale /usr/bin/toggle-font-scale&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I added a custom Gnome keyboard shortcut using gnome settings (for me, &lt;code&gt;CTRL&lt;/code&gt;+&lt;code&gt;SHIFT&lt;/code&gt;+&lt;code&gt;M&lt;/code&gt;) to run the script, which toggles the scaling factor.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;After some cleanup, I had my completed script:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Get Current Scailing factor&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;SCALE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;gsettings get org.gnome.desktop.interface text-scaling-factor&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Set what to toggle to&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;1.0&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;SCALE_SWITCH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.25&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;1.25&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;SCALE_SWITCH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;1.5&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;SCALE_SWITCH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# (Optional) Message intentions to CLI and GNOME Notifications&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Previous Font Scale: &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt;, Switched to &lt;span class=&quot;token variable&quot;&gt;$SCALE_SWITCH&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;notify-send&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Previous Font Scale: &lt;span class=&quot;token variable&quot;&gt;$SCALE&lt;/span&gt;, Switched to &lt;span class=&quot;token variable&quot;&gt;$SCALE_SWITCH&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Run switch command&lt;/span&gt;
gsettings &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; org.gnome.desktop.interface text-scaling-factor &lt;span class=&quot;token variable&quot;&gt;$SCALE_SWITCH&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s a simple solution and I’m very happy with it. I use it all the time,and at this point, it feels like it is just a feature baked into Gnome. I’m glad I took the 20 minutes to figure it out!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Intro to Python Type Hinting</title>
    <link href="https://ryan.himmelwright.net/post/python-type-hinting-intro/" />
    <updated>2022-01-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/python-type-hinting-intro/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/python-type-hinting-intro/SEEinnljmM-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/python-type-hinting-intro/SEEinnljmM-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Surf City, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have been trying to code daily the last several weeks.  Shortly after starting
a project in Python, I decided to refactor it to use &lt;a href=&quot;https://docs.python.org/3/library/typing.html&quot;&gt;Python type
hinting&lt;/a&gt;. Type hinting is a newer
feature of the language that I’ve known about, but hadn’t yet used much. So, I
gave it a shot.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;Before getting started, you might be asking yourself “Why bother with types in
Python? Isn’t &lt;em&gt;dynamic typing&lt;/em&gt; a feature that makes python a great language in
the first place?”. While dynamic typing can be easier to code, there
&lt;em&gt;are&lt;/em&gt; some benefits to a more static typing approach. To name a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Static types help clarify what a function expects for parameters, and what it
returns. You can look at a single line of code and immediately understand what
will be returned by a function, without having to skim through and follow the
code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Static typing increases the power compiling/linting has to find errors before
run-time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It forces the developer to fully think through code design a bit deeper.
Having to match up data types when chaining functions involves a bit more
planning compared to just throwing generic data around.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Basic Type Hinting&lt;/h2&gt;
&lt;p&gt;By default, python is a strongly typed, dynamic language. This means that while
Python does have types for everything, the system generally does all of this
under the hood, without requiring the programmer to define and keep track of the
types. However, ‘type hinting’ provides the ability to define types in python
code.&lt;/p&gt;
&lt;p&gt;For an example, lets start by looking at this &lt;s&gt;stupid&lt;/s&gt; simple function. It
takes an age for input, and returns a string that states how old you will be in
the future (it increment’s the age by 1 and adds it to a string 😑. I know,
riveting code here…):&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next_year_age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    new_age &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;In the future, you will be &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;new_age&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It can be assumed that &lt;code&gt;age&lt;/code&gt; is likely an &lt;code&gt;int&lt;/code&gt;, and that the return value is a
&lt;code&gt;str&lt;/code&gt;… but thats not a given. So, lets &lt;em&gt;declare&lt;/em&gt; the types using type hinting.&lt;/p&gt;
&lt;p&gt;To use type hinting in python, just specify the type when defining variables
using the format &lt;code&gt;var_name: type&lt;/code&gt;. Additionally, to define a return type for a
function, add a &lt;code&gt;-&amp;gt;&lt;/code&gt; symbol at the end of the function def pointing to the
return type. So, our example function would look something like this:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next_year_age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    new_age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;In the future, you will be &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;new_age&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we defined that the &lt;code&gt;age&lt;/code&gt; parameter, as well as the &lt;code&gt;new_age&lt;/code&gt; variable
inside the function, are both &lt;code&gt;int&lt;/code&gt;s. Additionally, we declared that the
function expects to return a string, by using the &lt;code&gt;-&amp;gt; str:&lt;/code&gt; in the function
definition.&lt;/p&gt;
&lt;h2&gt;MyPy&lt;/h2&gt;
&lt;p&gt;When using type hinting, it is strongly suggested to use
&lt;a href=&quot;http://mypy-lang.org&quot;&gt;Mypy&lt;/a&gt;, an optional static type checker for python, which
can be installed using &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;pip &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; mypy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To try &lt;code&gt;mypy&lt;/code&gt; out with our example function above, lets assume that it
is saved in a file named &lt;code&gt;example.py&lt;/code&gt;. To run a basic check:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_examples&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ➜  post_examples mypy example.py
Success: no issues found &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks good! Now, lets pretend we accidentally wrote that the function
would return a &lt;code&gt;float&lt;/code&gt; (even though it &lt;em&gt;actually&lt;/em&gt; returns a &lt;code&gt;str&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next_year_age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    new_age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;In the future, you will be &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;new_age&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mypy will catch the error for us:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_examples&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ➜  post_examples mypy example.py
example.py:3: error: Incompatible &lt;span class=&quot;token builtin class-name&quot;&gt;return&lt;/span&gt; value &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;got &lt;span class=&quot;token string&quot;&gt;&quot;str&quot;&lt;/span&gt;, expected &lt;span class=&quot;token string&quot;&gt;&quot;float&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
Found &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; error &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;checked &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Strict Checking&lt;/h3&gt;
&lt;p&gt;Up until now, &lt;code&gt;mypy&lt;/code&gt; has just told us if there are any issues with the type
hinting we added. It hasn’t been too pushy about ensuring we have types
declared for everything.&lt;/p&gt;
&lt;p&gt;To have &lt;code&gt;mypy&lt;/code&gt; enforce that we write code with un-ambiguous types (and get the
most out of static typing), we can use the &lt;code&gt;--strict&lt;/code&gt; flag. Looking at our
original example again, lets say we didn’t declare a return type at all:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next_year_age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    new_age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;In the future, you will be &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;new_age&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;mypy&lt;/code&gt; normally won’t care about this omission, because none of the
types we &lt;em&gt;did&lt;/em&gt; declare are wrong:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_examples&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ➜  post_examples mypy example.py
Success: no issues found &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, using the &lt;code&gt;--strict&lt;/code&gt; flag on the same file will let us know that we
forgot to define a return type:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_examples&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ➜  post_examples mypy &lt;span class=&quot;token parameter variable&quot;&gt;--strict&lt;/span&gt; example.py
example.py:1: error: Function is missing a &lt;span class=&quot;token builtin class-name&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; annotation
Found &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; error &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;checked &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;More Complex Types&lt;/h2&gt;
&lt;p&gt;If you need to use compound or more complicated types, you might have to import
from the &lt;a href=&quot;https://docs.python.org/3/library/typing.html&quot;&gt;typing&lt;/a&gt; module to do
so. For example, lets say we want to instead have the function return a list of
the two ages. We can do this by importing &lt;code&gt;List&lt;/code&gt; from &lt;code&gt;typing&lt;/code&gt;, and then define
the return type as a list of &lt;code&gt;int&lt;/code&gt;s:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; List

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next_year_age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; List&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    new_age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; new_age&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we want to instead use a 2-element tuple:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Tuple

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next_year_age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Tuple&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    new_age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; new_age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and so on.&lt;/p&gt;
&lt;p&gt;Lastly, to define your own class and type hint it:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Tuple

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next_year_age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Tuple&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    new_age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    me&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Person&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Ryan&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; new_age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mypy&lt;/code&gt; can check that as well:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_examples&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ➜  post_examples mypy example.py &lt;span class=&quot;token parameter variable&quot;&gt;--strict&lt;/span&gt;
Success: no issues found &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;typing&lt;/code&gt; module also supports types like the &lt;code&gt;Any&lt;/code&gt;, &lt;code&gt;Callable&lt;/code&gt;, &lt;code&gt;Union&lt;/code&gt;s and
others.&lt;/p&gt;
&lt;h2&gt;Issues&lt;/h2&gt;
&lt;p&gt;As I progressed further in my project, I started hitting more and more
limitations/frustrations specifically related to the type checking. Most
often, it was related to the crazy work-around I had to do to support &lt;code&gt;Optional&lt;/code&gt;
parameters and variables. Optionals &lt;em&gt;are supported&lt;/em&gt;, but a bit complicated.&lt;/p&gt;
&lt;p&gt;The biggest issue I had was being able to tell the system “I checked, and
definitely have this object, it’s not &lt;code&gt;None&lt;/code&gt;”. I would check for a value and
only run a case on it &lt;em&gt;if it existed&lt;/em&gt;, but the type system would still refuse to
run logic like &lt;code&gt;&amp;lt;&lt;/code&gt; on a &lt;code&gt;Optional(int)&lt;/code&gt; type. It didn’t catch on to the fact
that I had wrapped the variable in a case &lt;em&gt;that can only occur if the value was
an &lt;code&gt;int&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I eventually tried using the &lt;a href=&quot;https://github.com/dry-python/returns&quot;&gt;returns&lt;/a&gt;
library, which was great and can apparently help get around these issues by
introducing &lt;code&gt;Maybe&lt;/code&gt; and &lt;code&gt;Some&lt;/code&gt; types, but at that point I realized I had added
SO much overhead complexity to a &lt;em&gt;very tiny and simple&lt;/em&gt; side project, that it
really didn’t make sense.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Type hinting in python is probably best if you want to sprinkle it throughout
the code without &lt;code&gt;--strict&lt;/code&gt; checking, or if you have something where everything
is rather straight forward and there aren’t many ‘optional’ variables defined
and passed around. For projects where you want &lt;em&gt;really&lt;/em&gt; strict type checking…
you should probably use a different language (ex: Rust or Go) instead of forcing
Python to be something it wasn’t designed to be from the start.&lt;/p&gt;
&lt;p&gt;Still, I appreciate that type hinting exists in Python and I think I’ll
use it throughout my projects more often. If you haven’t used it before, I
recommend giving it a try!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Traded in my M1 MacBook Air for the 14&quot; MacBook Pro</title>
    <link href="https://ryan.himmelwright.net/post/traded-m1-air-14pro/" />
    <updated>2022-01-23T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/traded-m1-air-14pro/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/J0oIUwEveh-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/J0oIUwEveh-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;739&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;It’s been about a year since I &lt;a href=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/&quot;&gt;traded my 16&amp;quot; Macbook Pro for a M1 Macbook
Air&lt;/a&gt;. Well, I’ve done it again. Three months ago
I decided to trade in my M1 MacBook air for a 14&amp;quot; M1 Pro Macbook Pro (Ugh, these
names! 😂). Two months ago, the 14&amp;quot; was delivered and I finally made the
physical swap.  Similar to last time, I didn’t initiate this trade because I had
any issues with my current MacBook, or even &lt;em&gt;needed&lt;/em&gt; the newer one. But once
again…  I’m glad I did it.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/5kbQ_mUd5U-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/5kbQ_mUd5U-1200.jpeg&quot; alt=&quot;My previous two macbooks&quot; width=&quot;1200&quot; height=&quot;764&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My previous MacBooks: the M1 MacBook Air (left) and the 2019 16&quot; (Intel) MacBook Pro (right).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When rumors of new Apple Silicon MacBooks Pros started to surface, I really
wasn’t too tempted to swap.  I have been &lt;em&gt;very&lt;/em&gt; happy with my M1 MacBook air.
Having said that, as I watched Apple announce the new Pros… it occured to me
that the 14&amp;quot; pro was likely my &lt;em&gt;ideal&lt;/em&gt; MacBook. A perfect mix of all the things
I loved about my m1 air, while &lt;em&gt;adding&lt;/em&gt; the few things I missed from my 2019 16&amp;quot;
Intel-based Macbook Pro.&lt;/p&gt;
&lt;h2&gt;What I’ve previously said&lt;/h2&gt;
&lt;p&gt;To showcase this mingling, lets go through the list of what I have said about my
previous two MacBooks, and see how the 14&amp;quot; (m1 pro) stacks up against them:&lt;/p&gt;
&lt;h3&gt;Keyboard&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/M8dO3jdbI0-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/M8dO3jdbI0-1200.jpeg&quot; alt=&quot;The 14&quot; M1=&quot;&quot; Pro=&quot;&quot; Keyboard&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 14&quot; M1 Pro&#39;s Keyboard.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The 14&amp;quot; Pro has basically the same keyboard as both the 2019 16&amp;quot; and the m1 air.
The only difference is that the background behind the keys is all black, which I
think looks nice. As on the m1 air, the 14&amp;quot; has a row of physical function keys
(full sized this time), which I prefer over the touchbar that my 2019 16&amp;quot; had.&lt;/p&gt;
&lt;h3&gt;Trackpad&lt;/h3&gt;
&lt;p&gt;The trackpad remains the same, which is a good thing. It’s a different size than
the previous two devices (which makes sense), but it seems to fit this device
quite well.&lt;/p&gt;
&lt;h3&gt;Display&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/xpniJhR6vO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/xpniJhR6vO-1200.jpeg&quot; alt=&quot;The 14&quot; display,=&quot;&quot; with=&quot;&quot; notch&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;995&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 14&quot; M1 Pro&#39;s Display, including the notch.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While still smaller than the 16&amp;quot;, the 14&amp;quot; is a small but noticeable improvement
over the 13&amp;quot; m1 air. Additionally, the smaller bezels help minimize any
compromises on portability due to the size increase. This size maximizes screen space, without being too unwieldy.&lt;/p&gt;
&lt;p&gt;With the new XDR display, the 14&amp;quot; is also &lt;em&gt;technologically&lt;/em&gt; better than the
previous two. It has a 120hz refresh rate, with 1000 nit sustained and 1600 nit
peak brightness when watching HDR content (500 nit peak during normal usage).
With 10,000 mini-LEDs, the contrast is also much better (blacks look black, not
grey).  Lastly, the default resolution is a proper 200% scaling of native (which
I’ve been critical about on previous Macbooks). So out of the 3 laptops, the 14&amp;quot;
easily has the best display.&lt;/p&gt;
&lt;p&gt;‘&lt;strong&gt;How can you say that! The 14&amp;quot; has a notch!&lt;/strong&gt;’&lt;/p&gt;
&lt;p&gt;So what.&lt;/p&gt;
&lt;p&gt;While not ideal, Apple honestly did a great job making the best of the situation
by setting the space &lt;em&gt;under the notch&lt;/em&gt; to a 16:10 screen ratio. This means the
areas on either side of the notch are &lt;em&gt;extra&lt;/em&gt; screen space. If you absolutely
hate the notch, you can black out the top part of the screen and have the screen
from before (including a massive top bezel). There really isn’t much reason to
complain.&lt;/p&gt;
&lt;h3&gt;Speakers&lt;/h3&gt;
&lt;p&gt;The speakers on all these laptops are great for laptop speakers. However, it was
a real downgrade going from the 2019 16&amp;quot; Pro to the m1 Air. The 16&amp;quot; had
&lt;em&gt;amazing&lt;/em&gt; speakers. I’m not sure if the 14&amp;quot; speakers are better than the old 16&amp;quot;
speakers (I’m assuming not), but they are much better than the air and something
that I am very glad to have back.&lt;/p&gt;
&lt;h3&gt;Ports&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/bDKLzVxiLP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/bDKLzVxiLP-1200.jpeg&quot; alt=&quot;The 14&quot; display,=&quot;&quot; with=&quot;&quot; notch&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 14&quot; M1 Pro SOC supports two external displays (in addition to the internal display and sidecar).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This was another sacrafice when switching from the old 16&amp;quot; to the
m1 air. The 16&amp;quot; had 4 thunderbolt ports, while the m1 air had 2. Thats it. The
14&amp;quot; by comparison has 3 thunderbolt ports… but &lt;em&gt;also&lt;/em&gt; a mag safe charger (so
the charging cable doesn’t have to consume one of the thunderbolt ports).
Additionally, the 14&amp;quot; has an HDMI port, and an SD card reader.&lt;/p&gt;
&lt;p&gt;Another connectivity problem on the M1 Air was that it only supported 1 external
monitor. While the the base 14&amp;quot; doesn’t quite support the 2019 16&amp;quot;&#39;s 4 4k
displays (or 2 6k),  it does support up to 2 (6k@60Hz) external displays (note,
if you get the M1 MAX chip, that supports 4 external displays but I wasn’t
considering that configuration). So, while the 14&amp;quot; M1 Pro doesn’t connect to the
most devices, it’s small improvement over the air allows it to fully support
&lt;em&gt;my&lt;/em&gt; configuration.&lt;/p&gt;
&lt;h3&gt;Battery Life&lt;/h3&gt;
&lt;p&gt;The 14&amp;quot; might not beat the M1 air here, but it is &lt;em&gt;more&lt;/em&gt; than good enough, and
&lt;em&gt;substantially&lt;/em&gt; better than the old 16&amp;quot; was. I honestly don’t know how it
compares to the air because both exceed my needs.&lt;/p&gt;
&lt;h3&gt;Portability&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/iWwmVjLMY5-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/iWwmVjLMY5-1200.jpeg&quot; alt=&quot;The 14&quot; with=&quot;&quot; portable=&quot;&quot; external=&quot;&quot; display&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;916&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Dispite being slightly larger than the M1 air, the 14&quot; M1 Pro is still a very portable workstation.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The 16&amp;quot; is obviously the least portable. That’s a given.&lt;/p&gt;
&lt;p&gt;Comparing the 14&amp;quot; to the air is quite interesting though. Yes, it’s a bit
heavier and boxier than the tapered body of the air… but it’s
in essence just as portable. It’s 3.5 lbs (which is worth it for how
&lt;em&gt;solid&lt;/em&gt; it feels), with about the same foot print. It’s &lt;em&gt;technically&lt;/em&gt; less
portable, but in reality… it’s still a &lt;em&gt;very&lt;/em&gt; portable device.&lt;/p&gt;
&lt;h3&gt;Performance&lt;/h3&gt;
&lt;p&gt;The m1 air was surprisingly on par or better than the 2019 16&amp;quot;, at least for the
tasks I did. The 14&amp;quot; improves on that. The base model 14&amp;quot; has the same number of
CPU cores, but with 2 efficiency and 6 performance cores (compared to the 4 and
4 on the M1 air), which helps increase performance a bit. Additionally, the 14&amp;quot;
has almost double the GPU cores (14 vs the 8 that my upgrade air had), which is
very noticeable. Lastly, the 14&amp;quot; has crazy fast disk and memory speeds, which
contributes much more to general performance than people give credit to.&lt;/p&gt;
&lt;h3&gt;Heat &amp;amp; Noise&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/P44zwsOgix-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/P44zwsOgix-1200.jpeg&quot; alt=&quot;The fans running at 0 rpm&quot; width=&quot;1200&quot; height=&quot;660&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Most of the time, the fans sit at **0** RPM. When they do turn on, I can&#39;t hear them.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Again, the 2019 16&amp;quot; is the clear looser here. It was hot, and had noisy fans
that ran all the time.&lt;/p&gt;
&lt;p&gt;Comparing the 14&amp;quot; to the &lt;em&gt;fanless&lt;/em&gt; m1 air… they’re the same. Seriously.&lt;/p&gt;
&lt;p&gt;I guess the 14&amp;quot; technically &lt;em&gt;can&lt;/em&gt; get louder than the air, but I have hardly noticed
the fans turn on, and when they do the only way I know is by reading sensor
data. If anything, it means the 14&amp;quot; M1 pro stays &lt;em&gt;cooler&lt;/em&gt; than my air did, while
still remaining just as silent.&lt;/p&gt;
&lt;h2&gt;Deciding on the trade&lt;/h2&gt;
&lt;p&gt;In addition to the 14&amp;quot; being the perfect combination of everything I loved about my previous 2 MacBooks, there were a few other factors that eventually led me to make the trade-in.&lt;/p&gt;
&lt;h3&gt;Two ‘Upgrades’ for One&lt;/h3&gt;
&lt;p&gt;When looking up the trade-in value for my M1 Air, I noticed it was only valued
about $50 more than my wife’s base model M1 Air (which has &lt;em&gt;half&lt;/em&gt; as much RAM and SSD
storage). With that, I realized that we could trade in my wife’s computer and
“spend” that $50 lost in trade-in value to “upgrade” her M1 Air to 16GB RAM and
512GB storage (which is an amazing value for Apple hardware!). So, we were able
to &lt;em&gt;both&lt;/em&gt; upgrade our devices with this single trade-in.&lt;/p&gt;
&lt;h3&gt;Daily Driver&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/aZ6jGnm6CN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/aZ6jGnm6CN-1200.jpeg&quot; alt=&quot;The 14&quot; at=&quot;&quot; my=&quot;&quot; desk&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;865&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My MacBook setup at my desk. It&#39;s become my daily driver computer.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The final reason for this trade-in, was that after switching to my M1 Air, I
started daily driving my Mac again… and didn’t stop.&lt;/p&gt;
&lt;p&gt;I have used all my MacBooks as my &lt;em&gt;portable&lt;/em&gt; device. However, I started using
the air as my &lt;em&gt;main&lt;/em&gt; computer, even at my desk. I still have &lt;a href=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/&quot;&gt;my
desktop&lt;/a&gt; for when I need or want to run
Linux, but honestly, most of the time when I’m not working, it’s shut off. I
have started to enjoy using MacOS for my desktop computing needs, and Linux for
all my sever ones.  Another perfect combination.&lt;/p&gt;
&lt;p&gt;Being my main computing device, I wanted something that had &lt;em&gt;just a little bit
more&lt;/em&gt; to offer than the M1 air. And the 14&amp;quot; does. It has amazing interface specs
(display, speakers, keyboard, mic), a little bit more GPU power (which I was
hitting the limit of on the Air), and &lt;em&gt;still silent&lt;/em&gt; fans for when I &lt;em&gt;do&lt;/em&gt; push
the CPU.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/WHb8v_HZUA-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/traded-m1-air-14pro/WHb8v_HZUA-1200.jpeg&quot; alt=&quot;The 14&quot; pro=&quot;&quot; box&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My delivered, base model, 14&quot; MacBook Pro (M1 Pro).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With that, I traded in the m1 air. I decided on the cheapest base model (8 cpu
cores/14 gpu cores, 16GB RAM, and 512GB SSD) to not only keep the price down,
but also for an easier trade in the future. As I learned during this process,
base model devices have a much better value when using Apple trade-in (which is
convenient). Once I prove I can keep an MacBook for more than a year…
I’ll consider getting devices with upgrades again 😆.&lt;/p&gt;
&lt;p&gt;I also selected the base model because, again… I didn’t &lt;em&gt;need&lt;/em&gt; to trade the
air. It is an amazing computer. However, the 14&amp;quot; seemed to solve the &lt;em&gt;very
few&lt;/em&gt; problems I had with &lt;em&gt;both&lt;/em&gt; the 16 and air, giving it the potential to be my
ideal laptop. And so far, it has been.&lt;/p&gt;
&lt;p&gt;Still, for &lt;em&gt;most people&lt;/em&gt;… just get the MacBook Air. You won’t be let down.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Pantheon Desktop on Fedora 35</title>
    <link href="https://ryan.himmelwright.net/post/fedora-pantheon-install/" />
    <updated>2021-12-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/fedora-pantheon-install/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/0i3MazD_Od-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/0i3MazD_Od-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;865&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Surf City, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I recently wrote about how I started using &lt;a href=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/&quot;&gt;ElementaryOS 6 on my work
computers&lt;/a&gt;. While I love using elementaryOS, one
thing I mentioned in that post still holds true: right now, my ideal Linux setup
would be the ElementaryOS UI/UX, but on top of a &lt;a href=&quot;https://getfedora.org&quot;&gt;Fedora&lt;/a&gt;
base. So, this month I set out to achieve just that.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/RpCZhOkMwG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/RpCZhOkMwG-1200.jpeg&quot; alt=&quot;Elementary OS Desktop&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Elementary OS 6 with several applications opened.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The more I used ElementaryOS for work, the more my desire to have a Fedora base
grew. This was especially true for my &lt;em&gt;laptop&lt;/em&gt;, as earlier this year I &lt;a href=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/&quot;&gt;choose
the portable option&lt;/a&gt; for my laptop refresh. By
itself, it is fine. However, it is annoying to partition the laptop’s limited
resources for a fedora VM. Running Fedora as the &lt;em&gt;host&lt;/em&gt; OS (instead of
Elementary) would make it easier when I only have my laptop available (in the
office, for example).&lt;/p&gt;
&lt;p&gt;But… I still love the pantheon desktop environment. It’s clean, looks great,
and has wonderful support for global font-scaling (which is the best option I’ve
found for using a 4k monitor with ‘fractional scaling’ on Linux).&lt;/p&gt;
&lt;p&gt;I remembered there being a &lt;em&gt;pantheon desktop&lt;/em&gt; group in the &lt;code&gt;dnf group list&lt;/code&gt;
options a bit back, but there isn’t a dedicated &lt;a href=&quot;https://spins.fedoraproject.org&quot;&gt;Fedora
Spin&lt;/a&gt; for pantheon. So, here’s how I cobbled
together my own solution.&lt;/p&gt;
&lt;h2&gt;Installing Pantheon Desktop Environment&lt;/h2&gt;
&lt;p&gt;First, I used &lt;code&gt;dnf&lt;/code&gt; to install the &lt;code&gt;Pantheon Desktop&lt;/code&gt; package group:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf group &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;Pantheon Desktop&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That simple step took care of &lt;em&gt;most&lt;/em&gt; of what I needed. After installing the
group, you &lt;em&gt;should&lt;/em&gt; be able to reboot, and select the pantheon desktop
environment at the login screen.&lt;/p&gt;
&lt;h3&gt;Swapping gdm with lightdm&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/-miV4byk78-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/-miV4byk78-1200.jpeg&quot; alt=&quot;lightdm Pantheon Fedora&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Lightdm Display Manager in Fedora.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Speaking of the login screen…&lt;/p&gt;
&lt;p&gt;After installing pantheon, I wanted to take an additional step of switching the
display manager (the login screen). Specifically, I swapped gnome’s &lt;code&gt;gdm&lt;/code&gt; for
&lt;code&gt;lightdm&lt;/code&gt;, the display manager that elementaryOS uses. To do this, I first made
sure &lt;code&gt;lightdm&lt;/code&gt; was installed:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; lightdm&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, I disabled &lt;code&gt;gdm&lt;/code&gt; and enabled &lt;code&gt;lightdm&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl disable gdm
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; lightdm&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After rebooting, I as greeted with a lightdm login window, with the same layout
and theming I had on elementaryOS!&lt;/p&gt;
&lt;h2&gt;Building additional packages&lt;/h2&gt;
&lt;p&gt;As I started working in pantheon on Fedora, I did notice that there seemed to be a &lt;em&gt;few&lt;/em&gt; things were still missing…&lt;/p&gt;
&lt;h3&gt;Pantheon-Tweaks&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/TnwdFUzNjU-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/TnwdFUzNjU-1200.jpeg&quot; alt=&quot;Universal Access Settings&quot; width=&quot;1200&quot; height=&quot;769&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pantheon Tweaks Menu.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;First, I wanted to install
&lt;a href=&quot;https://github.com/pantheon-tweaks/pantheon-tweaks&quot;&gt;pantheon-tweaks&lt;/a&gt;. Like the
‘Tweaks’ tool for Gnome, this adds a few more settings to further customize
pantheon. However, &lt;code&gt;pantheon-tweaks&lt;/code&gt; doesn’t appear to be in the Fedora repos,
and I could only find &lt;code&gt;deb&lt;/code&gt; builds of it. So, I built it from source.&lt;/p&gt;
&lt;p&gt;First, I downloaded the source code and entered the directory:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;git clone https://github.com/pantheon-tweaks/pantheon-tweaks.git
cd pantheon-tweaks&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I determined what the required build dependencies were (and named), when
building in Fedora. After some sleuthing, I had the list figured out:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;sudo dnf install cmake vala ninja-build meson switchboard-libs switchboard-devel switchboard-plug-a11y granite-devel&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With all the dependencies installed, I was ready to run the build and install commands:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;meson build --prefix=/usr
cd build
ninja
ninja install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, I had the &lt;code&gt;Tweaks&lt;/code&gt; page available in the system settings.&lt;/p&gt;
&lt;h3&gt;Universal Access Settings in Wingpanel&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/i90qCpIq-8-1132.webp 1132w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/i90qCpIq-8-1132.jpeg&quot; alt=&quot;Universal Access Settings&quot; width=&quot;1132&quot; height=&quot;1059&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pantheon Universal Access Settings Page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Next, I wanted to flip the switch to show the Universal Access settings in
the top panel. However, every time I clicked on the Universal Access settings
tab, my settings app crashed! By launching the settings application from the
command line (&lt;code&gt;io.elementary.switchboard&lt;/code&gt;), I was able to observe it was
crashing because &lt;code&gt;wingpanel-idicator-a11y&lt;/code&gt; wasn’t installed. So, I
found &lt;a href=&quot;https://github.com/elementary/wingpanel-indicator-a11y&quot;&gt;that on Github&lt;/a&gt;
(I love open source!), and prepared to build it:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;git clone https://github.com/elementary/wingpanel-indicator-a11y.git
cd wingpanel-indicator-a11y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition to the build dependencies I had already installed for
&lt;code&gt;pantheon-teaks&lt;/code&gt;, I also needed to install the wingpanel libraries:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;sudo dnf install wingpanel-devel&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, I was able to build and install, just like before:&lt;/p&gt;
&lt;pre class=&quot;language-zsh&quot;&gt;&lt;code class=&quot;language-zsh&quot;&gt;meson build --prefix=/usr
cd build
ninja
sudo ninja install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once installed, I could view the Universal Access settings, and even enable the
dropdown setting in the top panel! (which I use to quickly change my font
scaling)&lt;/p&gt;
&lt;h2&gt;Concerns&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/MqsPjbgy3e-1065.webp 1065w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/MqsPjbgy3e-1065.jpeg&quot; alt=&quot;Pantheon Settings Menu&quot; width=&quot;1065&quot; height=&quot;767&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pantheon Settings Menu.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So far, this setup has worked surprisingly well. However, I do have one
main concern about the viability of using this long-term…&lt;/p&gt;
&lt;p&gt;Will this continue to work? If so, how much of a pain will it be to
maintain? I had to build some packages from source, and while I can pull the
latest and rebuild, I worry that they might have version compatibility problems
with the packages I’m able to pull from the repos, which would be an undesired
headache.&lt;/p&gt;
&lt;p&gt;I would &lt;em&gt;love&lt;/em&gt; to see a team in Fedora grow to make this an official and
maintained spin. I saw that there is a &lt;a href=&quot;https://fedoraproject.org/wiki/SIGs/Pantheon&quot;&gt;pantheon
sig&lt;/a&gt;, but I’m not sure how current
or active it is…&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/GuTBCqR_4n-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-pantheon-install/GuTBCqR_4n-1200.jpeg&quot; alt=&quot;Fedora with Pantheon&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Fedora 35 running the Pantheon Desktop.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Thus far, pantheon on Fedora 35 has worked fine. I’m going to try to keep using it
over the next few weeks, and keep an eye on it. In the best case scenario, maybe
it will be an offical spin one day. At the very least… I hope my cobbled
together version stays stable &lt;em&gt;enough&lt;/em&gt; for me to continue using!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Bitwarden CLI</title>
    <link href="https://ryan.himmelwright.net/post/bitwarden-cli/" />
    <updated>2021-12-10T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/bitwarden-cli/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/bitwarden-cli/FclmnL8kqt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/bitwarden-cli/FclmnL8kqt-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Linville Falls Trail, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have used &lt;a href=&quot;https://bitwarden.com&quot;&gt;Bitwarden&lt;/a&gt; for a couple of years now, but I
surprisingly haven’t utilized the CLI tool for anything. Recently, I was coding a
script that needed to store an API auth token to an environment variable, but I
didn’t want the token to be stored in plaintext. So, I setup and used the Bitwarden CLI for the first time to solve my problem!&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;CLI Install&lt;/h2&gt;
&lt;p&gt;To install the CLI tool (&lt;code&gt;bw&lt;/code&gt;), go to the &lt;a href=&quot;https://bitwarden.com/help/article/cli/&quot;&gt;CLI Documentation
page&lt;/a&gt;. There is a native executable for
each major OS, that you can download and install.&lt;/p&gt;
&lt;h3&gt;Linux&lt;/h3&gt;
&lt;p&gt;In Linux, I first installed it using node. After installing &lt;code&gt;npm&lt;/code&gt; on my
Fedora machine, I was able to install &lt;code&gt;bw&lt;/code&gt; using the command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; @bitwarden/cli&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To instead use the prebuilt Linux executable (which I tried later), first
download the executable package and unzip it.  Next, give the &lt;code&gt;bw&lt;/code&gt; file
executable permissions (&lt;code&gt;chmod +x bw&lt;/code&gt;). Lastly, move or link the &lt;code&gt;bw&lt;/code&gt; file to a
location in your &lt;code&gt;$PATH&lt;/code&gt; variable (ex: &lt;code&gt;sudo ln -s /home/Ryan/Builds/bw /usr/bin/bw&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;Mac&lt;/h3&gt;
&lt;p&gt;I also tested this out on my Mac. Here, I used the install method that I always
try use when on macOS: &lt;a href=&quot;https://brew.sh&quot;&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; bitwarden-cli&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Login and Sync&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;bw&lt;/code&gt; installed, you must first login with your account:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;☁  ~  bw login email@provider.com
? Master password: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;hidden&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Despite what the docs say, you don’t need to provide your password as part of
the &lt;code&gt;bw login&lt;/code&gt; command. If it is omitted, &lt;code&gt;bw&lt;/code&gt; will simply &lt;em&gt;prompt&lt;/em&gt; you for the
password, which is what I recommend doing, so that you don’t have your password
stored in the shell history 😉.&lt;/p&gt;
&lt;p&gt;After logging in, you should be greeted with a similar message:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;You are logged in&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;

To unlock your vault, &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; your session key to the &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;BW_SESSION&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt; environment variable. ex:
$ &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;BW_SESSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;WbiVLHrBEb2ktU7xVB4h1y40DhgFwV+vJ/lBw6EFk23wCxhUWsMTasXlXI3OEBS6xwaG638MepXdS7u1+FysNg==&quot;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$env&lt;/span&gt;:BW_SESSION&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;WbiVLHrBEb2ktU7xVB4h1y40DhgFwV+vJ/lBw6EFk23wCxhUWsMTasXlXI3OEBS6xwaG638MepXdS7u1+FysNg==&quot;&lt;/span&gt;

You can also pass the session key to any &lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; with the &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token parameter variable&quot;&gt;--session&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt; option. ex:
$ bw list items &lt;span class=&quot;token parameter variable&quot;&gt;--session&lt;/span&gt; WbiVLHrBEb2ktU7xVB4h1y40DhgFwV+vJ/lBw6EFk23wCxhUWsMTasXlXI3OEBS6xwaG638MepXdS7u1+FysNg&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The message provides the option of exporting the Bitwarden session token as an
environment variable, or appending it to your &lt;code&gt;bw&lt;/code&gt; commands using the
&lt;code&gt;--session&lt;/code&gt; flag. You don’t &lt;em&gt;have&lt;/em&gt; to include the session token when using &lt;code&gt;bw&lt;/code&gt;,
but it will make it much less annoying to work with. If the token is &lt;em&gt;not&lt;/em&gt;
defined or included, it will prompt for your password with each command.&lt;/p&gt;
&lt;p&gt;Also, it’s probably a good idea to run a &lt;code&gt;bw sync&lt;/code&gt; after logging in, just to
make sure everything is up to date.&lt;/p&gt;
&lt;h2&gt;Getting a Value&lt;/h2&gt;
&lt;p&gt;Now that we are logged in, lets work on grabbing some data from our vault.  The
&lt;code&gt;bw list&lt;/code&gt; command is used to list out contents from the password vault. This
requires an &lt;em&gt;object&lt;/em&gt; to be specified, which can be &lt;code&gt;items&lt;/code&gt;, &lt;code&gt;folders&lt;/code&gt;,
&lt;code&gt;collections&lt;/code&gt;, &lt;code&gt;org-collections&lt;/code&gt;, &lt;code&gt;org-members&lt;/code&gt;, and &lt;code&gt;organizations&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Flags, like &lt;code&gt;--search&lt;/code&gt; can be used to help narrow down the wall of results you
get.  For example, I can use &lt;code&gt;--search&lt;/code&gt; to narrow down the results to the specific
‘post-test’ object I want.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;☁  ~  bw list items &lt;span class=&quot;token parameter variable&quot;&gt;--search&lt;/span&gt; post-test
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;item&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;organizationId&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;folderId&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;:1,&lt;span class=&quot;token string&quot;&gt;&quot;reprompt&quot;&lt;/span&gt;:0,&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;post-test&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;notes&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;favorite&quot;&lt;/span&gt;:false,&lt;span class=&quot;token string&quot;&gt;&quot;login&quot;&lt;/span&gt;:&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ryan&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;terrible-password&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;totp&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;passwordRevisionDate&quot;&lt;/span&gt;:null&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;collectionIds&quot;&lt;/span&gt;:&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;revisionDate&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2021-11-30T11:44:25.846Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Use in Automation&lt;/h2&gt;
&lt;p&gt;Once we have found an object we want, we can use it’s &lt;code&gt;id&lt;/code&gt; to reference it
directly, instead of searching.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;☁  ~  bw get item aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;item&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;organizationId&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;folderId&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;:1,&lt;span class=&quot;token string&quot;&gt;&quot;reprompt&quot;&lt;/span&gt;:0,&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;post-test&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;notes&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;favorite&quot;&lt;/span&gt;:false,&lt;span class=&quot;token string&quot;&gt;&quot;login&quot;&lt;/span&gt;:&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ryan&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;terrible-password&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;totp&quot;&lt;/span&gt;:null,&lt;span class=&quot;token string&quot;&gt;&quot;passwordRevisionDate&quot;&lt;/span&gt;:null&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;collectionIds&quot;&lt;/span&gt;:&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;revisionDate&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2021-11-30T11:44:25.846Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Knowing the &lt;code&gt;id&lt;/code&gt; is helpful when setting up automation. Additionally, the object
data is returned as &lt;code&gt;json&lt;/code&gt;, so it is easy enough to parse.&lt;/p&gt;
&lt;p&gt;As an example, if we were writing a script and wanted to use Bitwarden to supply
my &lt;code&gt;post-test&lt;/code&gt; password, we could grab it with this one-liner combining &lt;code&gt;jq&lt;/code&gt; to
grab the value (&lt;code&gt;&amp;quot;terrible-password&amp;quot;&lt;/code&gt;) from the json output:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;☁  ~  bw get item 937b567a-a8a4-4ede-9fc1-adf000c17a4b &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;jq &lt;span class=&quot;token string&quot;&gt;&#39;.login | .password&#39;&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&quot;terrible-password&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I’ve used &lt;code&gt;bw&lt;/code&gt; once or twice at this point for automation scripts, and it has
worked great! One of the reasons I &lt;a href=&quot;https://ryan.himmelwright.net/post/switching-to-bitwarden/&quot;&gt;choose
Bitwarden&lt;/a&gt; when I last switched password managers
was &lt;em&gt;because&lt;/em&gt; it also had a CLI client. I’m glad to have finally used it!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Obsidian Basics</title>
    <link href="https://ryan.himmelwright.net/post/obsidian-basics/" />
    <updated>2021-11-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/obsidian-basics/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/geAMGOYeTw-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/geAMGOYeTw-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Linville Falls Trail, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over 7 months ago, I switched to &lt;a href=&quot;http://obsidian.md&quot;&gt;Obsidian&lt;/a&gt; and I love it.
Obsidian’s versatility allows it become whatever I &lt;em&gt;need&lt;/em&gt; it to be. However,
this adaptability comes at the cost of an initial learning curve, which can be
daunting for new users. While I can’t setup Obsidian for you, I can at least
help with those initial steps by explaining &lt;em&gt;what&lt;/em&gt; Obsidian is, and how to
navigate around it.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What is Obsidian&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/R6u5lsRobR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/R6u5lsRobR-1200.jpeg&quot; alt=&quot;Obsidian Graph View&quot; width=&quot;1200&quot; height=&quot;929&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Obsidian Graph Viewer.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;On the project’s &lt;a href=&quot;https://obsidian.md&quot;&gt;website&lt;/a&gt;, it is referred to as a “second
brain”, and summarized with the following statement:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Obsidian is a powerful knowledge base on top of a local folder of plain text Markdown files.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At it’s core, Obsidian is simply a markdown editor for your notes. What shapes
Obsidian to be different compared to other markdown editors, is that it was
designed, and continues to be developed, around 3 fundamental directions (again,
from the projects website):&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Local-first and plain text&lt;/li&gt;
&lt;li&gt;Link as first-class citizen&lt;/li&gt;
&lt;li&gt;Make it super extensible&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;In a world where nearly everything is hosted in the cloud, this first direction
reinstates control and portability over one’s notes. While the user can still
&lt;em&gt;choose&lt;/em&gt; to utilize the cloud to sync or backup notes, the raw data is still
&lt;em&gt;under your control&lt;/em&gt;. This directly addresses the
main reason &lt;a href=&quot;https://ryan.himmelwright.net/post/leaving-notion/&quot;&gt;why I left
Notion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The second direction is what makes Obsidian stand out from other markdown
editors. It’s superb support for easy linking and navigating notes raises
Obsidian to the top tier of note taking tools. This also enables the use of
alternative note-taking systems, like the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Zettelkasten&quot;&gt;Zettelkasten&lt;/a&gt; method.&lt;/p&gt;
&lt;p&gt;Lastly, the third direction (extensibility) is the dream of any power user, and
what allows Obsidian to function however you need it to. It also enables a
community to blossom around Obsidian, ensuring it’s future growth and success.&lt;/p&gt;
&lt;h2&gt;Obsidian Vaults&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/kaxyv34Qqk-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/kaxyv34Qqk-1200.jpeg&quot; alt=&quot;Obsidian Vault Setup&quot; width=&quot;1200&quot; height=&quot;1002&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Obsidian Vault Setup Screen.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first thing to know when getting started, is that obsidian notes are organized
into something called &lt;code&gt;vaults&lt;/code&gt;. This is a collection of files and folders that
make up an obsidian notebook. Multiple vaults can be defined, and the user can
switch between each vault as they wish.&lt;/p&gt;
&lt;p&gt;When starting Obsidian for the first time, you will be prompted to define a
vault location.&lt;/p&gt;
&lt;h2&gt;Markdown&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/beFktLiqtc-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/beFktLiqtc-1200.jpeg&quot; alt=&quot;Obsidian Note&quot; width=&quot;1200&quot; height=&quot;882&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A note in Obsidian. Left pane is edit mode, the right pane is the rendered preview.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After creating a vault, you can start writing note files. Notes are written in
&lt;a href=&quot;https://www.markdownguide.org&quot;&gt;markdown&lt;/a&gt;, which is easy to learn, but very
powerful. It is a plaintext file that supports formatting with
simple markup syntax. For example &lt;em&gt;italic&lt;/em&gt; (&lt;code&gt;*italic*&lt;/code&gt;),
&lt;strong&gt;bold&lt;/strong&gt; (&lt;code&gt;**bold**&lt;/code&gt;), and &lt;s&gt;strikethrough&lt;/s&gt; (&lt;code&gt;~~strikethrough~~&lt;/code&gt;) are all
supported. Beyond that, features like web-links (&lt;code&gt;[link](url)&lt;/code&gt;), inline
and full code blocks(&lt;code&gt;`code`&lt;/code&gt;), quote blocks (&lt;code&gt;&amp;gt;This is my quoted text&lt;/code&gt;),
and internal links (more on this next) are also supported.&lt;/p&gt;
&lt;p&gt;Using markdown means that all the notes/data, are in a human-readable and &lt;em&gt;very&lt;/em&gt;
transferrable format. If you want to read them without using obsidian, you still can
by using any other markdown reader (of which there are many), or by simply
&lt;em&gt;looking&lt;/em&gt; at the raw file. If you decide to
switch applications to manage your notes, markdown will be easy to
transfer/convert to whatever new system you want.&lt;/p&gt;
&lt;h2&gt;[[Links]]&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/2vGdlsYo2B-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/2vGdlsYo2B-1200.jpeg&quot; alt=&quot;Obsidian note links&quot; width=&quot;1200&quot; height=&quot;1009&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;An Obsidian note that links to many others (left pane). Right pane shows the *local* graph view of that note&#39;s links.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Obsidian is built on the concept of connecting everything using links. This is
done using the syntax &lt;code&gt;[[note name]]&lt;/code&gt;, or &lt;code&gt;[[note name|link text]]&lt;/code&gt;.  A few
things to know about Obsidian links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You only have to use the name of the note to link, and not the
absolute/relative path to it. Obsidian will keep track of where the files
actually located. You just worry about connecting everything.&lt;/li&gt;
&lt;li&gt;Links don’t &lt;em&gt;have&lt;/em&gt; to point to a page that is already created. If you click a link to
a page that doesn’t exist yet, it will create that note in your default
folder. Obsidian does keep track of all connections between notes, even if the page isn’t
created yet. If that note is ever instantiated, it will already have all of the
previous back-links to it already setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Speaking of back-links…&lt;/p&gt;
&lt;h3&gt;Back-links&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/lw3ZXDrEZy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/lw3ZXDrEZy-1200.jpeg&quot; alt=&quot;Obsidian back links&quot; width=&quot;1200&quot; height=&quot;1050&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The back-links pane for an Obsidian note. This displays all the linked references *to* the opened note.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When you link &lt;em&gt;to&lt;/em&gt; something, that connection is created bi-directionaly. For any given note,
you can open up the side pane and see every time another note has linked &lt;em&gt;to&lt;/em&gt;
the current one.&lt;/p&gt;
&lt;p&gt;Combined with something like the ‘Daily notes’ plugin, this is &lt;em&gt;very&lt;/em&gt; useful.
For example, when I work on a project, I usually mention (and thus link to) the
project’s page in my daily note. This allows me  later look at the backlinks for
that project note, and easily see all the days I worked on it. Additionally, I
often define a &lt;code&gt;Logs&lt;/code&gt; section in my project notes. If I sub-section the note
using links to the daily notes, it has the same effect (linking days to
projects, like I did in Notion).&lt;/p&gt;
&lt;h2&gt;#Tags&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/27mGaCNHQc-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/27mGaCNHQc-1200.jpeg&quot; alt=&quot;Obsidian tag pane&quot; width=&quot;1200&quot; height=&quot;950&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A note that uses `#tags` in the header, and the Obsidian tag pane opened on the right.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In addition to links, &lt;code&gt;#tags&lt;/code&gt; can also be used while writing notes. There is a tag
pane that displays all the tags with the number of occurrences for each one.
When a tag in the pane is selected, it pops open the search results for that tag.&lt;/p&gt;
&lt;p&gt;Personally, I use tags categorize and mark status on items (ex: &lt;code&gt;#archived&lt;/code&gt; or
&lt;code&gt;#today&lt;/code&gt;). This makes it easier to search and maintain my notes.&lt;/p&gt;
&lt;h2&gt;Organization (Files &amp;amp; Folders)&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/SV3oyG05kl-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/SV3oyG05kl-1200.jpeg&quot; alt=&quot;Obsidian Files and Folder navigation&quot; width=&quot;1200&quot; height=&quot;829&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Obsidian Files and Folders Navigation.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Like most note applications/computers in general, Obsidian can organize files
into folder hierarchies. Notes can be placed in folders, and those folders into
other folders.&lt;/p&gt;
&lt;p&gt;However, I recommend not being &lt;em&gt;too&lt;/em&gt; worried about folder organization in
Obsidian compared to other applications you might have used. Focus more on
grouping notes using #tags or links, rather that in folders. I know it is a
scary thought, but trust me, it will work out.&lt;/p&gt;
&lt;h2&gt;Searching/Navigation&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/0T4VM7X_lF-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/0T4VM7X_lF-1200.jpeg&quot; alt=&quot;Obsidian Search Menu&quot; width=&quot;1200&quot; height=&quot;773&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Obsidian Search Menu.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;One reason forgoing a large folder hierarchy in Obsidian is possible, is that
the search functionality works great. This is how most navigation will happen.
Simply hit the keyboard shortcut (&lt;code&gt;CMD&lt;/code&gt;/&lt;code&gt;CTRL&lt;/code&gt;-&lt;code&gt;o&lt;/code&gt;), and start typing. The pop
up window will filter out the results as you type. If the file doesn’t exist,
you can create a new one from the navigation.&lt;/p&gt;
&lt;h2&gt;Plugins&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/cDRHEvAq9I-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/cDRHEvAq9I-1200.jpeg&quot; alt=&quot;Installed Community Plugins&quot; width=&quot;1200&quot; height=&quot;887&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Installed Community Plugins.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;One of the fundamental goals of Obsidian is to &lt;em&gt;“Make it super extensible”&lt;/em&gt;.
This is achieved via &lt;em&gt;plugins&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There are two types of plugins: Core and Community. Core plugins are built into
the system, while community plugins are created and maintained by… you guessed
it, the community.  Plugins allow the user to mold Obsidian into whatever they want.&lt;/p&gt;
&lt;p&gt;For example, I use the Day Planner and Templates plugins to customize my
setup for keeping daily work notes and logs. I’ve also used a Kanban plugin
to better visualize monthly goals.&lt;/p&gt;
&lt;h2&gt;Tips/Suggestions&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/QcWB1ghPYE-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/QcWB1ghPYE-1200.jpeg&quot; alt=&quot;Settings to map key bindings to toggle the sidebar&quot; width=&quot;1200&quot; height=&quot;167&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I like to set keyboard shortcuts to easily toggle the side bars.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That should be it for the &lt;em&gt;basics&lt;/em&gt;. Before I wrap up, here are just few tips and
suggestions I have for using Obsidian:&lt;/p&gt;
&lt;h3&gt;Header information can be used to link up your notes using tags and/or ‘&lt;em&gt;Associations&lt;/em&gt;’&lt;/h3&gt;
&lt;p&gt;If there are other notes that you &lt;em&gt;know&lt;/em&gt; will be associated to others, add an
‘Associations’ header at the top. For example, all off the notes I have about
posts for this website contain a &lt;code&gt;Associations: [[my website]]&lt;/code&gt; as well as a
&lt;code&gt;Tags: #post_idea, #website&lt;/code&gt; line in a header at the top of the note.&lt;/p&gt;
&lt;h3&gt;Don’t be afraid to dump most of your notes into one folder&lt;/h3&gt;
&lt;p&gt;If you’re adding links and utilizing the search, you shouldn’t have any problems
locating notes, and it will make it easier to navigate if you ever view the
notes in something other than obsidian (ex: Gitlab/Github), as the links will be
better supported.&lt;/p&gt;
&lt;h3&gt;Use keybindings and setup your own.&lt;/h3&gt;
&lt;p&gt;A few of my suggestions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CMD&lt;/code&gt;+ &lt;code&gt;[&lt;/code&gt;/&lt;code&gt;]&lt;/code&gt; to toggle opening and closing the side panes&lt;/li&gt;
&lt;li&gt;One to insert a timestamp (I think this might be through the Today/Planner plugin?)&lt;/li&gt;
&lt;li&gt;If you use the Daily Note plugin, set shortcuts to navigate between the previous and next day&lt;/li&gt;
&lt;li&gt;Switch the theme to light/dark modes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Use some plugins&lt;/h3&gt;
&lt;p&gt;A few core ones (that I don’t think are on by default) that I highly suggest enabling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Templates&lt;/li&gt;
&lt;li&gt;Daily Notes&lt;/li&gt;
&lt;li&gt;Word Count&lt;/li&gt;
&lt;li&gt;VIM Mode – if you use &lt;code&gt;vim&lt;/code&gt; (not a plugin, but a setting)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A few of the community ones I love:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Obsidian Git&lt;/li&gt;
&lt;li&gt;Calendar&lt;/li&gt;
&lt;li&gt;Day Planner&lt;/li&gt;
&lt;li&gt;Kanban&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Save/sync your &lt;code&gt;.obsidian&lt;/code&gt; folder somewhere&lt;/h3&gt;
&lt;p&gt;This is useful if setting up on multiple machines, or just in case your setup gets whipped out.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-basics/4pIz6TQtGV-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-basics/4pIz6TQtGV-1200.jpeg&quot; alt=&quot;My Obsidian Setup&quot; width=&quot;1200&quot; height=&quot;889&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Obsidian Setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I could go on for hours about the details and intricacies surrounding my
obsidian use over the past months. However, that wouldn’t be very helpful. At
some point, you need to just dive in and start using Obsidian for yourself.
&lt;em&gt;Hopefully&lt;/em&gt;, I’ve covered enough that it shouldn’t be too hard to get started
now. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Install Podman on M1 Mac</title>
    <link href="https://ryan.himmelwright.net/post/install-podman-m1-mac/" />
    <updated>2021-10-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/install-podman-m1-mac/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/install-podman-m1-mac/dNBxWIjvwY-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/install-podman-m1-mac/dNBxWIjvwY-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Blowing Rock, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Ever since I &lt;a href=&quot;https://ryan.himmelwright.net/post/macos-challenge/&quot;&gt;started using macOS&lt;/a&gt;, and especially after
&lt;a href=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/&quot;&gt;purchasing&lt;/a&gt; an Apple Silicon Mac, I have been
closely watching &lt;a href=&quot;https://podman.io&quot;&gt;podman&lt;/a&gt; development around Mac support.
After waithing for several patchs to hit upstream dependency projects,
everything seems to now be in order. As a result of that work, I finally have
&lt;code&gt;podman&lt;/code&gt; running on my M1 MacBook Air.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;As a &lt;a href=&quot;https://getfedora.org&quot;&gt;Fedora&lt;/a&gt; user, I love how well it supports
&lt;a href=&quot;https://podman.io&quot;&gt;podman&lt;/a&gt;.  Over in macOS however, I was forced to use
docker to run containers, as &lt;code&gt;podman&lt;/code&gt; wasn’t well
supported yet. This was particularly true for apple silicon Macs,
like my &lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/&quot;&gt;m1 air&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/install-podman-m1-mac/98F43W4iNQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/install-podman-m1-mac/98F43W4iNQ-1200.jpeg&quot; alt=&quot;Docker Desktop&quot; width=&quot;1200&quot; height=&quot;722&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Docker Desktop Application.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Additionaly, Docker reacently announced that ‘Docker Desktop’ would no longer be
free for large enterprises. For mac users, the solution to this problem isn’t as
simple as “just don’t use the desktop app”. In macOS, &lt;code&gt;docker&lt;/code&gt; &lt;em&gt;only&lt;/em&gt; runs using
the desktop app.  It is not possible to install &lt;em&gt;just&lt;/em&gt; the command line service
like on Linux (at least that was my understanding).&lt;/p&gt;
&lt;p&gt;To be clear, this change is only for &lt;em&gt;enterprise&lt;/em&gt; users. Still, it has prompted
many people to look closer at alternatives (finally 😆).  Right around the time
of this announcement, I noticed much of the updates the podman team had been
waiting for upstream, started to release.&lt;/p&gt;
&lt;p&gt;So, I gave it a shot, and finally got podman working on my M1 MacBook Air!&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;Before getting started, first make sure you have &lt;a href=&quot;https://brew.sh&quot;&gt;Homebrew&lt;/a&gt;
installed. Then, install podman:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: I think you also need qemu and other dependencies installed, but
&lt;code&gt;homebrew&lt;/code&gt; &lt;em&gt;should&lt;/em&gt; take care of that.&lt;/p&gt;
&lt;h2&gt;Podman Machine&lt;/h2&gt;
&lt;p&gt;In order to run containers on macOS, podman needs to install a Linux VM to run
the containers in (I &lt;em&gt;think&lt;/em&gt; docker does something similar on macOS).  This is
what &lt;code&gt;podman machine&lt;/code&gt; is for. To create the VM, initalize &lt;code&gt;podman machine&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; machine init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the VM is created, start it up:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; machine start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note, the vm will have to be started to use podman, so this step might be
required after a reboot.&lt;/p&gt;
&lt;h3&gt;Side Note: &lt;em&gt;I&lt;/em&gt; had to use the &lt;code&gt;Next&lt;/code&gt; branch VM&lt;/h3&gt;
&lt;p&gt;At the time, I had issues with my &lt;code&gt;podman machine&lt;/code&gt; setup.  I had the required
version of podman (&lt;code&gt;3.4.0&lt;/code&gt;) installed on my mac, but the Fedora CoreOS image
&lt;code&gt;podman machine&lt;/code&gt; pulls down for the VM didn’t contain that verson of podman yet.
However, the &lt;em&gt;upcoming&lt;/em&gt; release did (Fedora-CoreOS pushes an update about every
2 weeks), so I was able instructuct &lt;code&gt;podman machine&lt;/code&gt; to use the &lt;code&gt;next&lt;/code&gt; branch
for the VM image, which worked.&lt;/p&gt;
&lt;p&gt;This shouldn’t be an issue anymore, as it has been a few weeks since I initially
did this and the current Fedora CoreOS release seems to have the correct version
of &lt;code&gt;podman&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To verify, you can &lt;code&gt;ssh&lt;/code&gt; into the VM using:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; machine &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then check the podman version installed inside the VM:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[core@localhost ~]$ podman --version
podman version 3.4.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should be at least &lt;code&gt;3.4.0&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Running Some Containers&lt;/h2&gt;
&lt;p&gt;To test out podman, I spun up a fedora container and played around in it a bit:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; fedora &lt;span class=&quot;token parameter variable&quot;&gt;--network&lt;/span&gt; bridge quay.io/fedora/fedora&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside it, I ran some updates and installed a few packages. Everything seemed
fine. Later on after my macbook had rebooted, I ensured that I could still start
and &lt;code&gt;podman exec&lt;/code&gt; into the same container:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; start fedora
&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exec&lt;/span&gt; fedora /bin/bash&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… which also worked perfectly!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/install-podman-m1-mac/WKN3_RR5al-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/install-podman-m1-mac/WKN3_RR5al-1200.jpeg&quot; alt=&quot;Fedora Podman container running on macOS&quot; width=&quot;1200&quot; height=&quot;775&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Fedora Podman Container running on my M1 MacBook Air.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While it feels like it’s been a long time coming, I am so glad to have &lt;code&gt;podman&lt;/code&gt;
working on macOS, and specifically on Apple Silicon. This is important to see,
especially at a time when Apple is pushing out such impressive hardware. It will
be interesting to see if there cotinues to be an increasing shift towards podman
over the next few years. I imagine being able to install it locally on Macs can
only help…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Pytest Parameter Tests</title>
    <link href="https://ryan.himmelwright.net/post/pytest-parameter-tests/" />
    <updated>2021-10-21T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/pytest-parameter-tests/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/pytest-parameter-tests/9mEeVBxsBe-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/pytest-parameter-tests/9mEeVBxsBe-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Moses H. Cone Memorial Park, Blowing Rock, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Recently, I’ve been converting a small python script I wrote into an
organized project. As part of that work, I started to setup an automated
testing framework (like a good QE 😆). While writing tests, I have been
parameterizing the code so that each test &lt;em&gt;function&lt;/em&gt; can be provided multiple
inputs, resulting in multiple tests (based from the same function). I talked
about this &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/&quot;&gt;previously&lt;/a&gt;, but used a
different method then. This time, I opted to try the &lt;code&gt;parametrize&lt;/code&gt; marker.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;&lt;a href=&quot;http://Conftest.py&quot;&gt;Conftest.py&lt;/a&gt; Method&lt;/h2&gt;
&lt;p&gt;The last time I wrote about &lt;a href=&quot;https://docs.pytest.org&quot;&gt;pytest&lt;/a&gt;, I used
parameterization. However, I implemented it a bit differently, using fixtures
defined in the &lt;code&gt;conftest.py&lt;/code&gt; file to do the parameterization.&lt;/p&gt;
&lt;p&gt;For example, this fixture function loops through a list of post names, and
returns the relative url for each post:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;POST_NAMES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;post_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the post urls for testing.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; BASE_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/post/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;param&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I used that fixture as the single parameter in my test function:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_post_served&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Checks that the desired posts are available&quot;&quot;&quot;&lt;/span&gt;
    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This causes the test to loop across each item the fixture returns from the
&lt;code&gt;POST_NAMES&lt;/code&gt; constant list, resulting in a test run for each one.&lt;/p&gt;
&lt;h2&gt;Using Pytest Parameterize&lt;/h2&gt;
&lt;p&gt;While that method works, tests can be parameterized without the overhead of
setting up fixtures for each test data set. Instead, the &lt;code&gt;parametrize&lt;/code&gt; pytest
marker can be used.&lt;/p&gt;
&lt;p&gt;I started by creating my set of test inputs:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;example_resolution_ratios &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1920&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1080&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2560&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3840&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2160&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1600&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5120&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2880&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6016&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3384&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3440&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Fraction&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;43&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Note: I did this step in the previous method too. That’s what &lt;code&gt;POST_NAMES&lt;/code&gt; was)&lt;/p&gt;
&lt;p&gt;Next, I added the &lt;code&gt;@pytest.mark.parametrize&lt;/code&gt; marker to my test function:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Example of parameters marker&lt;/span&gt;
&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parametrize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;horz_pixels,vert_pixels,expected_ratio&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    example_resolution_ratios&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    ids&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;pair&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;pair&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; pair &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; example_resolution_ratios&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_resolution_ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;horz_pixels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; vert_pixels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected_ratio&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Tests resolution ratio with set of known calculation results.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;horz_pixels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; vert_pixels&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; expected_ratio&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this marker looks intimidating (It’s larger than the actual test code!),
it does a lot for us. It requires at least two parameters, a string containing
parameter names to pass to the test (&lt;code&gt;&amp;quot;horz_pixels,vert_pixels,expected_ratio&amp;quot;&lt;/code&gt;
in this example), and the list of test items to iterate over
(&lt;code&gt;example_resolution_ratios&lt;/code&gt;). Notice, if the test items are a list of tuples,
pytest can match each tuple value to a different parameter to pass to the test
function.&lt;/p&gt;
&lt;p&gt;Additional options can be provided to the &lt;code&gt;parametrize&lt;/code&gt; marker. For example, I
used the &lt;code&gt;ids&lt;/code&gt; parameter to define how each parametrized test instance is
identified when run. This test function verifies a ratio calculator, so using a
list comprehension, I was able to have the resolution used for each test
displayed in the run output:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;1920x1080&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;2560x1440&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;3840x2160&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;1440x900&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;1600x900&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;5120x2880&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;6016x3384&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;tests&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;test_calcs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;py&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;test_resolution_ratio&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;3440x1440&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; PASSED &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is extremely helpful when a few tests are failing, as you can easily
identify what specific inputs are failing.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s really all there is to it. If you’re using pytest and want to
parameterize the test functions, the &lt;code&gt;parametrize&lt;/code&gt; marker is a simple, but
&lt;em&gt;powerful&lt;/em&gt; tool to do so. We use it often at work for some complicated setups,
but I enjoyed getting to try here in a &lt;em&gt;much&lt;/em&gt; simpler case!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Importing ZFS Pools on Unraid</title>
    <link href="https://ryan.himmelwright.net/post/import-zfs-pool-unraid/" />
    <updated>2021-09-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/import-zfs-pool-unraid/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/S475vzNqqg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/S475vzNqqg-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Moses H. Cone Memorial Park, Blowing Rock, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve been running Ubuntu on my home server, mostly because it supports running
&lt;a href=&quot;https://en.wikipedia.org/wiki/ZFS&quot;&gt;zfs&lt;/a&gt; out of the box. ZFS has been my
&lt;a href=&quot;https://ryan.himmelwright.net/tags/zfs/&quot;&gt;filesystem of choice&lt;/a&gt; for our home server for years.  However, I’ve
recently had the itch to switch away from Ubuntu and move to Fedora or CentOS.
While comparing distros, a major consideration was if I would be able to
transfer my zpools, or if I’d have to wipe and restart. I’ve now switched to
&lt;em&gt;Unraid&lt;/em&gt;… and got to keep ZFS. Here’s how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;While waiting for the centOS stream 9 to release, I started looking
at other distributions built specifically for NAS/Server use. First, I
checked out how &lt;a href=&quot;https://www.truenas.com/truenas-core/&quot;&gt;Truenas&lt;/a&gt; is doing these
days. While Truenas has &lt;em&gt;great&lt;/em&gt; ZFS support (it’s built around it), its
VM/container support might be a bit lacking for what I want. I’ll
admit, the new &lt;a href=&quot;https://www.truenas.com/truenas-scale/&quot;&gt;TrueNAS Scale&lt;/a&gt; alpha
caught my eye, but it is still in development and intended for much larger
setups than my single HP Microserver.&lt;/p&gt;
&lt;p&gt;Next, I decided to look at another home NAS OS I know of,
Unraid. By default, Unraid doesn’t use ZFS, but has a
&lt;em&gt;much&lt;/em&gt; better framework around running VMs and Container applications.  In
addition, it seemed like creating NFS/SMB shares should be easy, which was my
main motivation to try a NAS distro instead of vanilla CentOS/Fedora installs.
The downside of Unraid, was that I was afraid I might have to forgo ZFS and wipe
my data pools.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/DWs_n1D8nG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/DWs_n1D8nG-1200.jpeg&quot; alt=&quot;ZFS Apps to install&quot; width=&quot;1200&quot; height=&quot;818&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The available ZFS plugins to install via the Community App Store.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Fortunately, after reading around, I learned that there are plugins to enable
ZFS on Unraid. I decided to at least &lt;em&gt;try&lt;/em&gt; it. If ZFS didn’t work, &lt;em&gt;then&lt;/em&gt; I
could switch to wiping the disks and starting from scratch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This might not be the best practice, or even a &lt;em&gt;correct&lt;/em&gt; way to do
use ZFS on Unraid. It is just how &lt;em&gt;I&lt;/em&gt; did it. It is working &lt;em&gt;so far&lt;/em&gt;, but I have
no guarantees on the long term stability of this setup.&lt;/p&gt;
&lt;h2&gt;Installing the Plugins&lt;/h2&gt;
&lt;p&gt;When I first booted into Unraid, I still needed to provide a disk to be used
for the array (Note: this might change. See &lt;a href=&quot;https://www.youtube.com/watch?v=TWRvB8fh8T8&quot;&gt;this LTT
video&lt;/a&gt; to hear more). I started
with a test SSD connected via USB, but quickly switched to using the &lt;em&gt;real&lt;/em&gt; SSD
I have in the sever containing the Ubuntu install (no turning back huh?). After
assigning the SSD, I started the array and was able to install plugins.&lt;/p&gt;
&lt;p&gt;I started by installing the Community Applications
Plugin.
I went to the &lt;strong&gt;Plugins&lt;/strong&gt; page in Unraid, clicked the &lt;em&gt;Install Plugins&lt;/em&gt; tab, and
entered the url for the plugin found on the Community Applications Webpage
post.
Afterwards, had a new &lt;strong&gt;Apps&lt;/strong&gt; page listed in my Unraid navigation bar.&lt;/p&gt;
&lt;p&gt;To Install the ZFS plugins, I went to the &lt;strong&gt;Apps&lt;/strong&gt; page, and searched for
&lt;em&gt;“ZFS”&lt;/em&gt;. Several plugins appeared, and I installed both &lt;strong&gt;ZFS&lt;/strong&gt; and &lt;strong&gt;ZFS
Companion&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Importing the ZPools&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/ypXEfrMlK7-927.webp 927w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/ypXEfrMlK7-927.jpeg&quot; alt=&quot;The Zpools imported&quot; width=&quot;927&quot; height=&quot;502&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My ZFS Pools imported, and data available.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After installing the &lt;strong&gt;ZFS&lt;/strong&gt; plugin, I jumped to the command line and poked
around. Sure enough, I now had &lt;code&gt;zfs&lt;/code&gt; and &lt;code&gt;zpool&lt;/code&gt; commands I could use.  First, I
checked to see what pools the system detected by running &lt;code&gt;zpool import&lt;/code&gt;. It
returned both my &lt;code&gt;Backups&lt;/code&gt; and &lt;code&gt;Data&lt;/code&gt; pools, so I proceeded to import them:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;zpool &lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; Backups
zpool &lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; Data&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, the pools were mounted to their normal locations, with all the data
intact. On top of that, the ZFS companion widget in my dashboard also listed the
two pools, and their status. &lt;em&gt;Much&lt;/em&gt; easier than I anticipated.&lt;/p&gt;
&lt;h2&gt;Linking Shares&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/Iijaax1P80-941.webp 941w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/Iijaax1P80-941.jpeg&quot; alt=&quot;Unraid Share Symlinks&quot; width=&quot;941&quot; height=&quot;570&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I used symbolic links to point Unraid shares to my mounted ZPools.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The next step I wanted to tackle was creating Unraid shares, configured with
samba so that my wife and I could mount them on our computers. While making the
shares… I didn’t know how to instruct the system to use disk locations on my
zpools…&lt;/p&gt;
&lt;p&gt;After some investigation, I learned that each share was being created as a
directory under &lt;code&gt;/mnt/user/&lt;/code&gt;. So, my immediate ‘solution’ was to simply remove
each share folder (they were still empty), and replace them with symbolic links
to the desired directory on my mounted zpools. For example, my &lt;code&gt;Music&lt;/code&gt; share at
&lt;code&gt;/mnt/user/Music/&lt;/code&gt; points to &lt;code&gt;/Data/Music/&lt;/code&gt;. I didn’t see any alternative
solutions online, and so far… it seems to work 🤷🏻‍♂️. I was able to mount
and access all the shares from my desktop and laptop computers, so… that’s it I
guess?&lt;/p&gt;
&lt;h2&gt;Issues&lt;/h2&gt;
&lt;p&gt;I did hit &lt;em&gt;one&lt;/em&gt; issue during this process that scared me. Later that
night after a reboot, I noticed my containers weren’t starting. Then I saw
that the pools weren’t mounting on boot, and worse of all… I couldn’t find
them when I tried to import. I thought the system had eaten my data.&lt;/p&gt;
&lt;p&gt;Then I remembered that while I was poking around the motherboard settings to
switch the default boot drive to the Unraid USB, I also enabled SATA IOMMU
(because why not?). Well, ZFS &lt;em&gt;did NOT&lt;/em&gt; like that change and refused to recognize
my drives with it enabled. Disabling the setting resolved the problem and my
zpools auto-mounted on the next boot. All good.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/JuTZKL2YcN-559.webp 559w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/import-zfs-pool-unraid/JuTZKL2YcN-559.jpeg&quot; alt=&quot;ZFS Companions shows Zpool help in dashboard&quot; width=&quot;559&quot; height=&quot;235&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The ZFS Companion shows the health of the ZFS Pools in the Unraid Dashboard.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Thus far, things seem to work well. While ZFS pools aren’t as nicely integrated
into the UI compared to the Unraid Array, it isn’t a deal breaker for me. I was
previously working with ZFS 100% in the CLI, and I’m fine doing it now. As long
as the system persists through updates, and my symlinks hold up, I should be
fine. I guess we’ll see…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trying ElementaryOS 6</title>
    <link href="https://ryan.himmelwright.net/post/trying-elementaryos-6/" />
    <updated>2021-09-25T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/trying-elementaryos-6/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/DsORcfqCOK-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/DsORcfqCOK-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Moses H. Cone Memorial Park, Blowing Rock, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The recent release of &lt;a href=&quot;https://blog.elementary.io/elementary-os-6-odin-released/&quot;&gt;Elementary OS
6&lt;/a&gt; (Odin) really
piqued my interest. It has been a few years since I last tried Elementary,
(probably around the previous release), and I was curious to see all the
improvements that the team have made. So, I installed it to my test laptop…
and I was not let down.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/RpCZhOkMwG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/RpCZhOkMwG-1200.jpeg&quot; alt=&quot;Elementary OS Desktop&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Elementary OS 6 with several applications opened.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve used &lt;a href=&quot;https://elementary.io&quot;&gt;Elementary OS&lt;/a&gt; in the past, particularly as a
solid &lt;a href=&quot;https://ubuntu.com&quot;&gt;Ubuntu&lt;/a&gt;-based option. Lately, I have increasingly
enjoyed the core system of &lt;a href=&quot;http://fedoraproject.org&quot;&gt;Fedora&lt;/a&gt; so much, that I am
rarely persuaded to leave my podman-enriched, selinux-secured distro of choice
to try any flashy new Ubutnu (or even &lt;a href=&quot;https://archlinux.org&quot;&gt;Arch&lt;/a&gt;) based
ones.&lt;/p&gt;
&lt;p&gt;With that said, being an “on the edge” distro, the Fedora desktop can sometimes
have a few niggles to deal with. It might be a screen-sharing issue with
Wayland, sound devices not suddenly not reliably switching in the settings, or
bluetooth being finicky after an update. None of these are show-stoppers, and
are the small price of being on the latest and greatest, but they can quickly
become tiresome during the workday.&lt;/p&gt;
&lt;p&gt;To poke around on something beyond Fedora, I installed ElementaryOS 6 onto my
test laptop (my T470 Thinkpad)… and I loved what I saw (quite literally).&lt;/p&gt;
&lt;h2&gt;What I like&lt;/h2&gt;
&lt;h3&gt;Appearance/UX&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/Wnfe7M0SFw-684.webp 684w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/Wnfe7M0SFw-684.jpeg&quot; alt=&quot;Elementary OS Overview&quot; width=&quot;684&quot; height=&quot;522&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Close terminal tab notification window.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first thing I love about ElementaryOS is its appearance, and I don’t just
mean &lt;em&gt;“it looks pretty”&lt;/em&gt;. The &lt;em&gt;tiniest&lt;/em&gt; details of the design are all well
reasoned and executed. The window drop shadows, color selection, and even fonts
all combine to create a desktop environment that oddly &lt;em&gt;feels&lt;/em&gt; easier to use.
For example, while messing around in the &lt;code&gt;terminal&lt;/code&gt; app, I attempted to
close a tab while &lt;code&gt;htop&lt;/code&gt; was running and had a notification window pop up
to warn me that the &lt;code&gt;htop&lt;/code&gt; process would end when the tab is closed. When I saw
it the first time, I was very impressed with just how &lt;em&gt;clean&lt;/em&gt; the pop up looked.
The elementaryOS desktop has a level of polish that, while getting better, can
still be quite rare on the Linux desktop.&lt;/p&gt;
&lt;p&gt;Additionally, I found the fonts sizing and UI to be exactly what I want.  I hate
when window chrome has giant gaps wasting space, or when everything is packed so
tightly together that it’s hard to read. The pantheon desktop in ElementaryOS
finds a superb balance between the two.&lt;/p&gt;
&lt;h3&gt;System Settings&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/HzPV_fg-8F-898.webp 898w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/HzPV_fg-8F-898.jpeg&quot; alt=&quot;Elementary keyboard shortcut settings&quot; width=&quot;898&quot; height=&quot;797&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Keyboard Shortcut Settings.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The control settings in ElementaryOS are easy to navigate and have some
additional items I don’t normally see in the standard Gnome settings menu.  For
example, the notifications section is layed out in a way that is much simpler to
understand. ElementaryOS has also added Parental Controls/Screentime options
built into the settings menu which I appreciate, as Linux is severely lacking in
this area compared to other desktop OSes.&lt;/p&gt;
&lt;p&gt;What I like most about the settings though, is their &lt;em&gt;defaults&lt;/em&gt;. In particular,
I found many of the default keyboard shortcuts already set to what I wanted. For
example, I like to switch workspaces and move windows to them using &lt;code&gt;CMD-N&lt;/code&gt; and
&lt;code&gt;SHIFT-CMD-N&lt;/code&gt; respectively (where N is the number of the workspace to move it
to). In both KDE &lt;em&gt;and&lt;/em&gt; Gnome I have to painstakingly go through and set this up
by hand. Even worse, both DEs are hit-or-miss of if they actually respect the
changes, and Gnome doesn’t &lt;em&gt;really&lt;/em&gt; support setting this past 4 workspaces.&lt;/p&gt;
&lt;p&gt;In ElementaryOS, these shortcuts are already set (along with many others I
like). For the few that I did have to change, it was simple to do so, and
the changes &lt;em&gt;actually worked&lt;/em&gt; when applied.&lt;/p&gt;
&lt;h3&gt;System Coherence&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/9un9VWCK81-890.webp 890w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/9un9VWCK81-890.jpeg&quot; alt=&quot;Elementary OS Appearance Settings&quot; width=&quot;890&quot; height=&quot;770&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Elementary OS Appearance Settings.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The last item to highlight about Elementary OS, is that the entire system feels
cohesive. The default apps all follow the same look and style. Some applications
deviate from this, but I understand that these rogue apps are out of the
Elementary team’s hands. As a heap of loosely connected software packages, it
can be quite difficult to make a Linux desktop seem like it all belongs
together. While not perfect, I think ElementaryOS is one of the best examples of
this being accomplished.&lt;/p&gt;
&lt;p&gt;Everything appears to be well integrated. If I change my sound settings in the
menubar, they change. Same if I connect a bluetooth device from there. I can’t
always say the same with some of my recent Gnome Fedora installs.  Everything is
well connected, and there is very little &lt;em&gt;random crap&lt;/em&gt; floating around the
system.&lt;/p&gt;
&lt;h2&gt;What I don’t Like&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/58Q2vhMzQR-780.webp 780w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/58Q2vhMzQR-780.jpeg&quot; alt=&quot;Elementary Calendar dropdown&quot; width=&quot;780&quot; height=&quot;408&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I actually don&#39;t like the elementary calendar dropdown compared to Gnome.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This section is a bit smaller because honestly, there wasn’t &lt;em&gt;too&lt;/em&gt; much that I
didn’t like, and my biggest complaints are mostly petty.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu Base&lt;/strong&gt;: This is ridiculous, but I think the biggest downside &lt;em&gt;I&lt;/em&gt; have
with using ElementaryOS is that it is based on Ubuntu 😆 . While there are many
benefits about being on an Ubuntu based system… I just prefer Fedora. Also,
given that I work at Red Hat, using a Fedora/CentOS/RHEL base helps me keep up
and passively learn the systems we use at work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I prefer the Gnome Calendar Dropdown&lt;/strong&gt;: After using Gnome on Fedora Silverblue
for several months, I got used to Gnome’s drop-down calendar menu. In
particular, I enjoyed how it displayed the weather and world times from my
&lt;em&gt;Clocks&lt;/em&gt; app right there. Having the various time zones easily available is
actually extremely helpful when working on a world-wide distributed team. I bet
this is an area that the Elementary team could easily implement something
spectacular.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limited Customization&lt;/strong&gt;: There are limited preference options for many of the
default applications. For example. the terminal app has 3 pre-configured themes
you can select from, but that is &lt;em&gt;all&lt;/em&gt; you can set
the theme to. I &lt;em&gt;have&lt;/em&gt; been able to change it using
&lt;a href=&quot;https://github.com/Gogh-Co/Gogh&quot;&gt;gogh&lt;/a&gt;, but it would be nice to have the
ability to deviate from the default settings, &lt;em&gt;even though&lt;/em&gt; I think the
ElementaryOS defaults are all great.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Occasional Random Crash&lt;/strong&gt;: The biggest &lt;em&gt;actual&lt;/em&gt; problem I’ve had with
ElementaryOS, is that the desktop environment can occasionally crash.  However,
each crash has happened gracefully. The system will pause, the wallpaper goes
black behind the windows, and everything disappears for a second before
returning back to normal. So beyond having to wait a few seconds, the crashes
have been largely harmless. (No logouts or shutdowns so far)&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/6eY6lWI0HN-890.webp 890w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-elementaryos-6/6eY6lWI0HN-890.jpeg&quot; alt=&quot;Elementary OS About Window on my work x1 Carbon&quot; width=&quot;890&quot; height=&quot;510&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Elementary OS 6 on my work X1 Carbon.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;ElementaryOS feels like a weird middle ground between my normal Linux setup and
macOS. When I’m on macOS, I like the user experience, but wish I had full linux
under the hood. With elementaryOS, the ends of this spectrum both fall somewhere
closer to the middle: The user experience is cleaner than most Linux desktops
and generally more supported (due to it’s Ubuntu base), but not as much as
on macOS.  For example, it is more common to find a &lt;code&gt;deb&lt;/code&gt; compared a &lt;code&gt;rpm&lt;/code&gt;
package, but both are &lt;em&gt;much&lt;/em&gt; more rare than finding a &lt;code&gt;dmg&lt;/code&gt; installer.  On the
other end, it &lt;em&gt;is&lt;/em&gt; Linux under the hood, but not Fedora, which means some of the
system stuff I enjoy isn’t as well supported (podman, toolbox, selinux).&lt;/p&gt;
&lt;p&gt;Regardless, I have loved using ElementaryOS 6. In fact, when the Silverblue
install on my &lt;a href=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/&quot;&gt;work laptop&lt;/a&gt; started misbehaving, I
decided to install ElementaryOS 6 on it instead. To get a better &lt;em&gt;work&lt;/em&gt; setup,
I also installed a Fedora server VM that auto-starts when the laptop boots 😆.&lt;/p&gt;
&lt;p&gt;Using ElementaryOS 6 for my user-land and a Fedora VM for my dev work has been a
great Linux combo over the past month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Obsidian Notes iOS + Working Copy Setup</title>
    <link href="https://ryan.himmelwright.net/post/obsidian-ios-setup/" />
    <updated>2021-08-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/obsidian-ios-setup/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/_Coqi6E7fW-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/_Coqi6E7fW-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Eno State Park, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After deciding to &lt;a href=&quot;https://ryan.himmelwright.net/post/leaving-notion/&quot;&gt;leave Notion&lt;/a&gt;, I have been using
&lt;a href=&quot;https://obsidian.md&quot;&gt;obsidian.md&lt;/a&gt; for all of my notes and loving every moment of it.
The &lt;em&gt;one&lt;/em&gt; thing I have missed after the switch has been the ability to view or
lightly edit notes on my iPhone and iPad. However, I saw that there was an
&lt;a href=&quot;https://obsidian.md/mobile&quot;&gt;Obsidian mobile app&lt;/a&gt; in development, and I have
been eagerly waiting for it. Now that the app has been released, here is how I
set it up on my iOS devices (with some help from &lt;a href=&quot;https://workingcopyapp.com&quot;&gt;working
copy&lt;/a&gt; 😉).&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Obsidian Notes&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/mt7x5e4jvv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/mt7x5e4jvv-1200.jpeg&quot; alt=&quot;Obsidian Desktop&quot; width=&quot;1200&quot; height=&quot;860&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Obsidian Desktop Application.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Compared to Notion, I really appreciate that all of Obsidian’s data is simply a
collection of raw markdown files. The downside to this is that I am responsible
for setting up a syncing solution. At first, I was using
&lt;a href=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/&quot;&gt;seafile&lt;/a&gt; to sync the files, which worked &lt;em&gt;okay&lt;/em&gt; (but
&lt;a href=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/&quot;&gt;could be annoying&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;However, because obsidian vaults are just text files… they can be stored in
version control systems, like a git repository. So, I setup a private
&lt;a href=&quot;https://gitlab.io/&quot;&gt;Gitlab&lt;/a&gt; repo to backup my vault to. This allowed me to
&lt;em&gt;snapshot&lt;/em&gt; my notes whenever I wanted. Wonderful.&lt;/p&gt;
&lt;p&gt;Eventually, I started using the
&lt;a href=&quot;https://github.com/denolehov/obsidian-git&quot;&gt;obsidian-git&lt;/a&gt; plugin to more easily
take ‘&lt;em&gt;snapshots&lt;/em&gt;’ of the vault. After a few trial weeks with the plugin, I
finally made the jump and configured it to &lt;em&gt;automatically&lt;/em&gt; take snapshots
throughout the day. Now, I’ve entirely switched to using git to sync the vault
across all my devices. The only options for syncing on iOS are to use git or
iCloud. Luckily, I’m already set.&lt;/p&gt;
&lt;h2&gt;Working Copy (Git Repo)&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/mXTJ7TcltQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/mXTJ7TcltQ-1200.jpeg&quot; alt=&quot;Working Copy Hosts&quot; width=&quot;1200&quot; height=&quot;838&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Working Copy Host Setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have used &lt;a href=&quot;https://workingcopyapp.com&quot;&gt;working-copy&lt;/a&gt; in the past to pull down
git repos on my ipad. You can pull repos with the free version, but I think
you need the pro version to push to a repo. For this setup, we ideally want
the ability to push changes, so the pro version is recommended.&lt;/p&gt;
&lt;p&gt;(If you don’t have a hosted git repo setup for the obsidian vault, you’ll want
to do that first.)&lt;/p&gt;
&lt;h2&gt;Setting up Obsidian Mobile&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/1E39YU30HY-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/1E39YU30HY-1200.jpeg&quot; alt=&quot;Create vault in Obsidian iOS&quot; width=&quot;1200&quot; height=&quot;838&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Creating a new Obsidian vault in iOS.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To start, download and install the obsidian app from the app store. Next,
open it up, and select &lt;code&gt;Create New Vault&lt;/code&gt;. On the creation screen, give the vault
a name and make sure that &lt;code&gt;Store in iCloud&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; selected. We’ll come back
to obsidian in a minute.&lt;/p&gt;
&lt;h3&gt;Setup&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/E3oi6rdhiM-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/E3oi6rdhiM-1200.jpeg&quot; alt=&quot;Configuring repo in working copy&quot; width=&quot;1200&quot; height=&quot;702&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Working Copy Repo Setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Next, switch to working copy and setup the credentials for whatever git host you
used for the vault repo (ex: Gitlab for Github). Once that is done, clone the repo.&lt;/p&gt;
&lt;p&gt;Open the repo and select the “share menu” at the top right of the
application. Select &lt;code&gt;Setup Folder Sync&lt;/code&gt;. We want to sync the repo with the
Obsidian vault we already made. Under the &lt;code&gt;On My iPad&lt;/code&gt; section of the browser, there should be an
&lt;code&gt;Obsidian&lt;/code&gt; folder that contains an empty sub-folder with the name of the vault
created. Select it to sync the repo there. Afterwards, Obsidian and working copy
should be linked up!&lt;/p&gt;
&lt;h2&gt;Using Obsidian Mobile&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/ELd3VXDt2C-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/ELd3VXDt2C-1200.jpeg&quot; alt=&quot;Obsidian iOS Command Menu&quot; width=&quot;1200&quot; height=&quot;838&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Obsidian iOS Command Menu.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When using obsidian on mobile, always remember to first &lt;strong&gt;pull&lt;/strong&gt; down the latest
repo changes in working copy!  Afterwards, you can open up obsidian and use it
as you normally would. Just remember to then
&lt;strong&gt;commit and push&lt;/strong&gt; any changes you make copy afterward!&lt;/p&gt;
&lt;p&gt;The mobile UI can take some getting used to compared to the desktop application.
One tip I have is to use the command prompt to do basically &lt;em&gt;everything&lt;/em&gt;.
Opening notes, closing them, splitting panes. Just use the command prompt for it
all.&lt;/p&gt;
&lt;p&gt;(And once again, remember to &lt;strong&gt;push&lt;/strong&gt; any changes!)&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/Eo3fyhE46y-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/obsidian-ios-setup/Eo3fyhE46y-1200.jpeg&quot; alt=&quot;Obsidian iOS&quot; width=&quot;1200&quot; height=&quot;838&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Note in Obsidian iOS App.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;It is still early days, but having a mobile app helps smooth out one of the
&lt;em&gt;very few&lt;/em&gt; downsides I’ve faced using obsidian. I’m glad to see the project
progress, and even happier to be using it now. Congrats to the devs for getting
this release out!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>New Work Laptop - X1 Carbon (gen7) Thinkpad</title>
    <link href="https://ryan.himmelwright.net/post/x1-carbon-gen7-review/" />
    <updated>2021-08-27T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/x1-carbon-gen7-review/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/PmnoauLtFQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/PmnoauLtFQ-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;700&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A few months ago I became eligible to trade-in my work laptop, a Lenovo p50
Thinkpad, for a newer one. After experiencing problems related to the laptop’s
dedicated Nvidia graphics card, I jumped on the opportunity to swap it. Compared
to being asked the simple “Thinkpad or Macbook” question I faced when starting
at Red Hat, I had some additional decisions to make this time around.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background Information&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/b-8Px1_dw_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/b-8Px1_dw_-1200.jpeg&quot; alt=&quot;The old p50 outside&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The old laptop (p50) outside at the office.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When selecting laptops, we can choose from a few different default options,
across a range of size and performance. My p50 for example, was the &lt;em&gt;high
performance&lt;/em&gt; option, with it’s 32GB of RAM, 512GB nvme disk, and fast i7 cpu.
The current
&lt;em&gt;high performance&lt;/em&gt; Thinkpad is an even more powerful version of the p50, with a
&lt;em&gt;newer 6 core, 12 thread CPU. However, I decided to select the compact&lt;/em&gt; option
*instead - a Lenovo Thinkpad X1 Carbon.&lt;/p&gt;
&lt;h2&gt;Reasons for Switching&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/9MsqQsuKOM-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/9MsqQsuKOM-1200.jpeg&quot; alt=&quot;The p50 and x1 carbon side by side&quot; width=&quot;1200&quot; height=&quot;715&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The p50 (left) and x1 carbon (right).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So… why did I switch to the &lt;em&gt;compact&lt;/em&gt;, portable option, especially now that I
mostly work from home? The main reason is that the carbon has a better screen.
It is significantly brighter, with more accurate colors than the p50. While I
don’t do much work that requires color accuracy, having a bright screen means I
can work in brighter areas. This includes outside on the porch when the weather
is pleasant, which is an improvement while working from home. I get to spend a
little more time outside, which helps mix up my work environment, even if I
rarely leave my house.&lt;/p&gt;
&lt;p&gt;The second reason I selected the most &lt;em&gt;portable&lt;/em&gt; laptop is &lt;em&gt;shockingly&lt;/em&gt;… for
the portability! I might not be lugging the laptop to the office every day, but
having a smaller, lighter laptop makes it easier to pick up and move to another
room when I want to change my environment (as mentioned in the paragraph above).
Additionally, when I do return to an office in the future, or visit
&lt;em&gt;other&lt;/em&gt; Red Hat offices, the lighter laptop will be easier to transport,
allowing my to carry less, or have more space in my bag for other traveling gear
(like &lt;a href=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/&quot;&gt;my keyboard&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Lastly, while I did take a hit in performance with the switch, it isn’t much of
a problem.  Most of my heavy performance work is done in CI/CD pipelines hosted
on servers anyway, and the X1 Carbon still has &lt;em&gt;enough&lt;/em&gt; power to handle any of
the local development I need to do (as long as I’m careful about how many VMs
I’m running at the same time).&lt;/p&gt;
&lt;h2&gt;What I Like&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/mLBZnsGyKI-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/mLBZnsGyKI-1200.jpeg&quot; alt=&quot;The new x1 carbon outside&quot; width=&quot;1200&quot; height=&quot;1247&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Enjoying the x1 carbon outside.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Thin and &lt;em&gt;very&lt;/em&gt; light&lt;/strong&gt;:  It is even lighter than my Macbook Air,
despite being slightly larger (It has a larger screen).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screen&lt;/strong&gt;: The screen is &lt;em&gt;much&lt;/em&gt; better than the p50, and it’s still a
matte display, which I prefer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keyboard&lt;/strong&gt;: Even though it is a thin laptop, the x1 carbon still has a wonderful keyboard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrated Intel graphics&lt;/strong&gt;: Not much GPU power, but I’m not gaming on my
work computer. Best of all, it is &lt;em&gt;much&lt;/em&gt; simpler to work with than the Nvidia+Intel graphics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thunderbolt Dock&lt;/strong&gt;: The TB dock that works well for my setup. It
is smaller than the p50 dock, and being a standardize connection (TB3), I can
use my personal laptops with it when I’m not working, as I use the same periphery
devices for my personal and work setups.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NVME&lt;/strong&gt;: Like my old laptop, it still has fast nvme storage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, it’s small, portable, with a better screen, and still a
Thinkpad. That’s all I really need.&lt;/p&gt;
&lt;h2&gt;What could be better&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/6eY6lWI0HN-890.webp 890w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/6eY6lWI0HN-890.jpeg&quot; alt=&quot;x1 carbon specs&quot; width=&quot;890&quot; height=&quot;510&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The x1 carbon system specs.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Heat &amp;amp; Noise&lt;/strong&gt;: The carbon can get quite hot and will kick up the fans when
in video calls. To be fair, it’s not too loud, but I’ve been spoiled by my &lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/&quot;&gt;M1
Macbook Air&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More RAM and Storage&lt;/strong&gt;: While I can get away with the 256GB HD and 16GB of RAM, it would be nice to have more. This is especially true if I want to be running &lt;em&gt;multiple&lt;/em&gt; VMs at the same time, or to have several VM images saved on the device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Meh CPU&lt;/strong&gt;: It’s not the most powerful, but I knew that when I selected it.
For the most part it’s been fine for everything I do, and I’d rather have a more
power efficient CPU than a power hungry one. I wish it had a &lt;em&gt;bit&lt;/em&gt; more oomph
though for all the heat it gives off 😒.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essentially, all of my &lt;em&gt;dislikes&lt;/em&gt; are simply reasonable trade-offs I knew
when selecting this type of device.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/1LoOrWPzT3-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/x1-carbon-gen7-review/1LoOrWPzT3-1200.jpeg&quot; alt=&quot;The new x1 carbon with coffee&quot; width=&quot;1200&quot; height=&quot;795&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The x1 carbon with coffee.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That’s really it. I’m glad I made the trade, as the x1 carbon fits my current
work needs much better. I love it’s bright screen, solid keyboard, and how
portable it is. When I’m not moving around, I can easily dock it at my desk
setup and pretend it’s a desktop. The x1 carbon is a solid, portable,
Linux laptop.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Introduction to Toolbox</title>
    <link href="https://ryan.himmelwright.net/post/intro-to-toolbox/" />
    <updated>2021-07-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/intro-to-toolbox/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/7AQDWGed3E-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/7AQDWGed3E-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As I use &lt;a href=&quot;https://podman.io&quot;&gt;podman&lt;/a&gt; more often, I continue to rely more heavily
on &lt;a href=&quot;https://docs.fedoraproject.org/en-US/fedora-silverblue/toolbox/&quot;&gt;toolbox&lt;/a&gt;.
In particular, on &lt;a href=&quot;https://silverblue.fedoraproject.org&quot;&gt;Fedora Silverblue&lt;/a&gt;,
toolbox becomes &lt;em&gt;the&lt;/em&gt; command line environment(s) that I use. &lt;em&gt;Everything&lt;/em&gt; is
done in a toolbox on Silverblue.  So then… what is toolbox and how can you
start using it?&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;About Toolbox&lt;/h2&gt;
&lt;h3&gt;What is it&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/ts59S7lEAx-863.webp 863w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/ts59S7lEAx-863.jpeg&quot; alt=&quot;Podman Logo&quot; width=&quot;863&quot; height=&quot;231&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Podman Logo&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;At its basic core, &lt;code&gt;toolbox&lt;/code&gt; is a fancy wrapper around &lt;code&gt;podman&lt;/code&gt; that makes it
&lt;em&gt;much&lt;/em&gt; simpler to work with. When you create and enter a
container using toolbox, you will find your existing username, user
permissions, home directory (and a few other locations), system journal
and more, are all already setup and waiting for you.&lt;/p&gt;
&lt;p&gt;Toolbox does all of this in a single command, without you needing
to know all of the crazy &lt;code&gt;podman&lt;/code&gt; arguments it would take to do it yourself!&lt;/p&gt;
&lt;h3&gt;Advantages&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/4_10MQy2tO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/4_10MQy2tO-1200.jpeg&quot; alt=&quot;Toolbox list of containers&quot; width=&quot;1200&quot; height=&quot;560&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I use different toolboxes for each of my dev environments.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;What are the advantages of using something like &lt;code&gt;toolbox&lt;/code&gt;? Using containers for
testing out different tools or programming languages helps keep your system
clean, by installing all the dependencies just within that toolbox. For example,
if I am trying out a new program that needs to be compiled from source, I will
create a toolbox, install the dependencies inside it and build the application.
Afterwards, when I’m done testing out that build, I can delete the toolbox,
leaving my host system untouched.&lt;/p&gt;
&lt;p&gt;Additionally, read-only operating systems like Silverblue, really benefit from
utilizing containers. If I want to install a package in Silverblue, I usually
have to add it as a layer to the base image, and then reboot into that new
image.  Having something like toolbox allows me to have a read-only system for
my core, but still work in a normal &lt;code&gt;dnf&lt;/code&gt;-managed command line environment. It’s
the best of both worlds.&lt;/p&gt;
&lt;h3&gt;How to get it&lt;/h3&gt;
&lt;p&gt;Toolbox is installed by default on Fedora Silverblue. If it isn’t installed on
the base image you are using for some reason, it can be installed as a layer
using &lt;code&gt;rpm-ostree&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rpm-ostree install toolbox
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On other fedora systems (ex: Workstation or even Server), toolbox can be
installed with &lt;code&gt;dnf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install toolbox
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Toolbox Functions&lt;/h2&gt;
&lt;p&gt;Now that we know what &lt;code&gt;toolbox&lt;/code&gt; is and have it installed, lets learn some of
it’s commands!&lt;/p&gt;
&lt;h3&gt;Toolbox Create&lt;/h3&gt;
&lt;p&gt;First, let’s create a toolbox container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox create
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, this will create a container with a default name based on your
distro and version. For example, when run on Fedora 34, this creates a toolbox
named &lt;code&gt;fedora-toolbox-34&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To create a container with a specific name, the &lt;code&gt;-c&lt;/code&gt; flag can be used. For
example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox create -c website
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a toolbox container named &lt;code&gt;website&lt;/code&gt; (which is where I install
&lt;code&gt;hugo&lt;/code&gt; to work on this website 😉).&lt;/p&gt;
&lt;h3&gt;Toolbox image&lt;/h3&gt;
&lt;p&gt;In addition to the name, a toolbox can be created with a specific container
image, using the &lt;code&gt;-i&lt;/code&gt; flag (assuming it is a docker/podman image that is
compatible with toolbox). For example, the following command will create a
toolbox from the Fedora 33 toolbox image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox create -i fedora-toolbox:33
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of providing a full image name, you can also declare a distro with the
&lt;code&gt;-d&lt;/code&gt; flag (ex: fedora), paired with the &lt;code&gt;-r&lt;/code&gt; to specify a release.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox create -d fedora -r 35
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a Fedora 35 toolbox (even though I am running on Fedora 34).&lt;/p&gt;
&lt;h3&gt;Toolbox List&lt;/h3&gt;
&lt;p&gt;Now that some containers containers have been created, we can list them all with
&lt;code&gt;toolbox list&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox list
---
IMAGE ID      IMAGE NAME                                    CREATED
e6d38a7d896c  registry.fedoraproject.org/fedora-toolbox:34  2 weeks ago
30e2dd6cf22e  registry.fedoraproject.org/fedora-toolbox:35  2 weeks ago

CONTAINER ID  CONTAINER NAME     CREATED     STATUS   IMAGE NAME
94a91110021a  fedora-toolbox-34  4 days ago  running  registry.fedoraproject.org/fedora-toolbox:34
f91f8b4a3a51  website            4 days ago  running  registry.fedoraproject.org/fedora-toolbox:34
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command displays all of the toolbox containers, but also the container
images that are downloaded. The &lt;code&gt;-i&lt;/code&gt; flag can be used with &lt;code&gt;toolbox list&lt;/code&gt; to
only display the images, or the &lt;code&gt;-c&lt;/code&gt; flag to only list the toolbox containers.&lt;/p&gt;
&lt;h3&gt;Toolbox enter&lt;/h3&gt;
&lt;p&gt;It’s time to finally &lt;em&gt;enter&lt;/em&gt; our toolbox. Like the &lt;code&gt;toolbox create&lt;/code&gt; command, the
&lt;code&gt;toolbox enter&lt;/code&gt; one will enter that default toolbox, and named toolboxes can be
entered by using the &lt;code&gt;-c&lt;/code&gt; flag. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox enter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will enter the &lt;code&gt;fedora-toolbox-34&lt;/code&gt; toolbox on my Fedora 34 machine. If I want to
enter my website container, I can call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox enter -c website
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you enter a toolbox, you are dropped into a new shell (as the same user),
with access to your home directory. In the toolbox, you can install applications
that will then exist in the container, but not on your host system. Toolbox has
become so refined over the years, that it even runs GUI apps now!&lt;/p&gt;
&lt;p&gt;To exit the container, use the &lt;code&gt;exit&lt;/code&gt; command, just like you would to exit a
shell.&lt;/p&gt;
&lt;h3&gt;Toolbox Run&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/BD7W96ZPyH-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/intro-to-toolbox/BD7W96ZPyH-1200.jpeg&quot; alt=&quot;Gitg GUI app running in toolbox&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Gitg GUI app running in toolbox&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Here’s a fun tip: you don’t actually have to be inside a toolbox to run commands
in it. The &lt;code&gt;toolbox run&lt;/code&gt; command can be used to pass commands to the toolbox
container. Again, no args will run the command in the default toolbox, but the
&lt;code&gt;-c&lt;/code&gt; flag can be used to run commands in named containers. For example, this
command runs &lt;code&gt;guvcview&lt;/code&gt; in my default toolbox.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toolbox run guvcview
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember, I don’t have &lt;code&gt;guvc&lt;/code&gt; installed on my host. And yes, it can pass my
webcam into the container!&lt;/p&gt;
&lt;p&gt;The next example runs the &lt;code&gt;hugo version&lt;/code&gt; command in my &lt;code&gt;website&lt;/code&gt; toolbox:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  ~ toolbox run -c website hugo version
Hugo Static Site Generator v0.80.0/extended linux/amd64 BuildDate: unknown
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s really it! A lot can be done with these few commands. I have been using
&lt;code&gt;toolbox&lt;/code&gt; for years, and I’ve been very impressed with how far it has come in
that time and how much more stable it is now (&lt;code&gt;podman&lt;/code&gt; has also become much more
stable). So, if you haven’t used &lt;code&gt;toolbox&lt;/code&gt; before, why not give it a try?&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Website Updates - Moved to Gitlab Pages</title>
    <link href="https://ryan.himmelwright.net/post/website-updates-gitlab-pages/" />
    <updated>2021-07-25T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/website-updates-gitlab-pages/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/IMFhw-DIcL-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/IMFhw-DIcL-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham Bulls Athletic Park, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I recently made some major changes to my website. After playing around testing
the site on &lt;a href=&quot;https://docs.gitlab.com/ee/user/project/pages/&quot;&gt;Gitlab Pages&lt;/a&gt; for
a few months, I finally sat down to figure out how to make the official switch.
In the end, this involved a bunch of additional side projects, including
replacing my ‘cloud node’ with a new one, setting up a Gitlab runner, removing
all analytic trackers from the website, and completely re-doing the the DNS for
my domain name. It’s been quite the overhaul…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/P93etOQ0nN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/P93etOQ0nN-1200.jpeg&quot; alt=&quot;An older version of my website&quot; width=&quot;1200&quot; height=&quot;603&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;An older version of my website.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;First, lets start with some background information. I have been using &lt;a href=&quot;https://pages.github.com&quot;&gt;Github
pages&lt;/a&gt; to host my website for… ummm… a long time.
I probably started using it about the time I graduated from college (2014)… way
before I &lt;a href=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/&quot;&gt;switched to hugo&lt;/a&gt;, or even &lt;a href=&quot;https://ryan.himmelwright.net/post/website-switched-to-cryogen/&quot;&gt;jekyll
and cryogen&lt;/a&gt; before that.&lt;/p&gt;
&lt;p&gt;I have used many static website generators to build and deploy the website over
the years. However, the one thing has remained consistent the whole time, is
that I use two repos, one for the source, and one for the built website.
This is something I’ve wanted to consolidate for years now, but just haven’t
gotten around to. This was one of the main reasons motivating me to make a
switch.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/aax4TUu2Kt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/aax4TUu2Kt-1200.jpeg&quot; alt=&quot;My old website Jenkins pipelines&quot; width=&quot;1200&quot; height=&quot;389&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Old Jenkins Website Pipelines.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Another reason was to have a better CI setup. I’ve used Jenkins off and on for
my &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-docker-nodes/&quot;&gt;website&lt;/a&gt;
&lt;a href=&quot;https://ryan.himmelwright.net/post/pytest-parallel-website-tests/&quot;&gt;testing&lt;/a&gt; and
&lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/&quot;&gt;CI&lt;/a&gt; pipeline, but haven’t maintained a
Jenkins server for quite awhile now. This is something I’ve wanted to improve in
my workflow, but haven’t had the desire to spin up and host a full CI server*,
just for my website. With &lt;a href=&quot;https://docs.gitlab.com/ee/ci/&quot;&gt;Gitlab CI&lt;/a&gt;… I
wouldn’t have to.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*PS: I know this is something I probably don’t have to do with Github pages
anymore.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Playing with Gitlab&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/eapQAY3qZq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/eapQAY3qZq-1200.jpeg&quot; alt=&quot;Gitlab Profile&quot; width=&quot;1200&quot; height=&quot;624&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Gitlab Page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve slowly started migrating my personal projects over to gitlab over the last
year. This started with wanting one or two repositories to be private (Github didn’t
allow free private repos at the time), but eventually I started to prefer how
Gitlab has changed over the years compared to Github. Plus, I don’t feel as much
pressure to &lt;em&gt;have&lt;/em&gt; to be on Github, where discover-ability is higher. Also,
I’ve started to use Gitlab more at work so having my personal repositories there helps
me learn for work and vice versa.&lt;/p&gt;
&lt;p&gt;The only projects that still &lt;em&gt;needed&lt;/em&gt; to be on Github, were my website repos.
So, I added a remote on Gitlab, and have been pushing the website source there
as a backup location, but to additionally start testing out what was needed
to make the switch.&lt;/p&gt;
&lt;h2&gt;Moving to Gitlab&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/nHdCOumuhH-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/nHdCOumuhH-1200.jpeg&quot; alt=&quot;Gitlab Repos&quot; width=&quot;1200&quot; height=&quot;644&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Gitlab Repos.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Two weeks ago, I finally decided decided to jump in and make the switch.  I
started by I renaming the website repo in Gitlab to be the default gitlab pages
site for my account (ex: &lt;code&gt;username.gitlab.io&lt;/code&gt;). &lt;em&gt;This took me an embarrassingly
long time to figure out 😆.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, I left both websites up for a few days to compare stability.
Everything seemed fine. My &lt;a href=&quot;http://gitlab.io&quot;&gt;gitlab.io&lt;/a&gt; site was basically the same as the ‘real’
(Github) one.  The only issue stopping me from an full switch at this point was
that I wanted to configure a CI setup, with tests, running in a Gitlab pipeline.
If I could figure that out, it would be worth it to officially switch.&lt;/p&gt;
&lt;h2&gt;Gitlab Runner&lt;/h2&gt;
&lt;p&gt;I spent a long time trying to get the tests to work with the shared CI runners.
Eventually, I realized it would just be easier if I set up my own private
runners, which I have &lt;a href=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/&quot;&gt;done before&lt;/a&gt;.  I wanted the
runner to be hosted outside my network, so I wouldn’t have to worry or rely on
my desktop being on and running the VM when I needed a pipeline to pass.&lt;/p&gt;
&lt;p&gt;So, I decided to put it on my cloud node. I had been meaning to migrate my cloud
VM from Digital Ocean to Linode, and this seemed like the time to do it. That
VM was several years old, and drastically needed to be rebuilt from the ground up.
However, I had been dragging my feet for years because I didn’t want to go
through the hassle of migrating piwik (what I used for website analytics).&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/UXh6RB2XBu-750.webp 750w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/UXh6RB2XBu-750.jpeg&quot; alt=&quot;Matomo Logo evolution&quot; width=&quot;750&quot; height=&quot;350&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I used Piwik (now Matomo) for web analytics for years.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To solve, the Matomo/Piwik issue… I simply decided to forego all analytics
on the website. It’s a decision I’ve been debating recently.  Similar to
how I don’t feel the need to have my personal projects discoverable by using
Github, I don’t need to know how many people are visiting my website, and what
posts they are reading. Knowing that information won’t change what I write
about, nor should it. So, I made sure to remove all analytics code from the
website source: my piwik code, as well as a poorly implemented Google Analytics
bit (It never properly linked the data to my account). As a result, this website
is now free of any tracking, at least on my part 🙂.&lt;/p&gt;
&lt;p&gt;With that ethical and technical mess of my chest, I was able to easily spin up a
new linode, and configure a gitlab runner on it. Ideally, I want to use
containers as the executors, but for now I am running straight on the host,
controlling a &lt;code&gt;nginx&lt;/code&gt; service. Switching to a containerized test workflow on
the runner might be in the cards for a future post, but currently, this works.&lt;/p&gt;
&lt;h2&gt;Domain Switch&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/Kfjr2Q1KH7-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/Kfjr2Q1KH7-1200.jpeg&quot; alt=&quot;Moved my domain management to linode&quot; width=&quot;1200&quot; height=&quot;221&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Moved my domain management to Linode.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With the gitlab runner configured, and my testing CI working, there was only one
more thing to do: switch my domain name to point to the Gitlab Pages site.  Like
the cloud node, I wanted to move management of my domain name from Digital
Ocean to Linode too. I figured this was as good of a time as any, as I was
already ripping everything up. I switched the nameservers, and started
re-creating my records from scratch (they needed to be cleaned up anyway). After
starting, I was quickly reminded… messing with DNS sucks.&lt;/p&gt;
&lt;p&gt;The problem with moving the DNS for a domain around, is that it can take hours
or even days to propagate the change to all of the nameservers around the world.
This means that while setting things up, I never knew if something wasn’t
configured correctly, or if it just hadn’t taken effect yet. After a day or so,
my domain records seemed to be working and back up, pointing to the Gitlab Pages
site. Finally.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/qG6QeGbOzW-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-updates-gitlab-pages/qG6QeGbOzW-1200.jpeg&quot; alt=&quot;The only noticeable change on the website is that there are no trackers.&quot; width=&quot;1200&quot; height=&quot;568&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The only noticeable change on the website is that there is no trackers.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That was a lot. To summarize, I did a bunch work, all around the main task of
updating the backend hosting of my website:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Moved the website source repos from Github to Gitlab&lt;/li&gt;
&lt;li&gt;Switched from using Github Pages to Gitlab Pages for hosting the actual website&lt;/li&gt;
&lt;li&gt;Replaced my persistent cloud instance, on a new provider&lt;/li&gt;
&lt;li&gt;Configured that instance as a private gitlab runner&lt;/li&gt;
&lt;li&gt;Configured my website testing and deployment CI to run in gitlab pipelines&lt;/li&gt;
&lt;li&gt;Removed all trackers from the website&lt;/li&gt;
&lt;li&gt;Moved the management of my domain name&lt;/li&gt;
&lt;li&gt;Pointed &lt;code&gt;ryan.himmelwright.net&lt;/code&gt; to the new Gitlab Pages site.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, what does all that work mean for you, my wonderful reader? Well, it
&lt;em&gt;meant&lt;/em&gt; that the site was down for a day or two while the domain switched,
but now that everything is finished… it really shouldn’t mean anything.  For a
reader, the website is basically the same as before. Oh, except no more
tracking. So pages &lt;em&gt;might&lt;/em&gt; load a little faster.  Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Multi-hop ssh Connections</title>
    <link href="https://ryan.himmelwright.net/post/multi-hop-ssh-connect/" />
    <updated>2021-06-27T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/multi-hop-ssh-connect/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/multi-hop-ssh-connect/0ZRTL6zqSD-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/multi-hop-ssh-connect/0ZRTL6zqSD-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;839&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Karsh Alumni Center (Duke University), Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last weekend, I wanted to ensure that I could work on a personal project using
my typical setup, while I was away for a few hours. This consists of running VS
Code on my &lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/&quot;&gt;macbook air&lt;/a&gt;, but &lt;a href=&quot;https://ryan.himmelwright.net/post/vscode-remote/&quot;&gt;remotely
connected&lt;/a&gt; to my &lt;a href=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/&quot;&gt;Linux
desktop&lt;/a&gt;. All of the coding actually
happens on the Linux computer, even though I am running VS code on my macbook.
However, when outside of my home network, connecting to the desktop requires
multiple &lt;code&gt;ssh&lt;/code&gt; hops. Fortunately, configuring &lt;code&gt;ssh&lt;/code&gt; to handle a multi-hop setup
is actually quite easy &lt;em&gt;and&lt;/em&gt;…  it works with the VSCode remote plugin.
Here’s how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Problem&lt;/h3&gt;
&lt;p&gt;To restate the problem: If I am outside of my home network, I need to make
multiple &lt;code&gt;ssh&lt;/code&gt; ‘&lt;em&gt;hops&lt;/em&gt;’ in order to get to my desktop (charmeleon).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Laptop -&amp;gt; Ponyta (SSH Entry Node) -&amp;gt; Charmeleon (Desktop)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normally, this requires me to first &lt;code&gt;ssh&lt;/code&gt; into my home network (&lt;code&gt;ponyta&lt;/code&gt;), and
then from &lt;em&gt;there&lt;/em&gt;, &lt;code&gt;ssh&lt;/code&gt; &lt;em&gt;again&lt;/em&gt; to whichever device I want to connect to (In
this example, &lt;code&gt;Charmeleon&lt;/code&gt;). Out of pure laziness, I want to run these steps as
a single command. Additionally, for tasks that are automated, or outside of a
shell, connecting to my desktop truly &lt;em&gt;needs&lt;/em&gt; to be a single step in order to
work.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;An simple solution is to create a new host item in the ssh config file. In this
file, we can utilize the &lt;code&gt;ProxyCommand&lt;/code&gt; option to setup a multi-hop scenario.
So, I added the following my &lt;code&gt;~/.ssh/config&lt;/code&gt; (make sure to swap out your &lt;code&gt;PORT&lt;/code&gt;
and &lt;code&gt;HOSTNAME&lt;/code&gt; values accordingly):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host remote-charmeleon
	Hostname CHAMELEON-IP
	User ryan
	ProxyCommand ssh -p PORT ryan@PUBLIC_FACING_PONYTA_HOSTNAME -W %h:%p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I only need to run &lt;code&gt;ssh remote-charmeleon&lt;/code&gt; to kick off the the full &lt;code&gt;Laptop -&amp;gt; (Ponyta) -&amp;gt; Charmeleon&lt;/code&gt; sequence. Easy.&lt;/p&gt;
&lt;h2&gt;Setting it up in VSCode&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/multi-hop-ssh-connect/YsMiqfKLQY-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/multi-hop-ssh-connect/YsMiqfKLQY-1200.jpeg&quot; alt=&quot;Connecting via a multi-hop ssh connection in vscode&quot; width=&quot;1200&quot; height=&quot;820&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Connecting to a multi-hop system via ssh in VS Code.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now, how can this be used in a VS Code session?&lt;/p&gt;
&lt;p&gt;All I had to do was add the config sequence to the ssh config file that &lt;em&gt;VSCode&lt;/em&gt;
uses, which I usually have set to be different from my default one.&lt;/p&gt;
&lt;p&gt;To do this, call the &lt;code&gt;Remote-SSH: Open SSH Configuration File&lt;/code&gt; in VS Code to
open the proper config file. Next, add the same code from above to the config.
From then on, you can select that host item when running &lt;code&gt;Remote-SSH: Connect to Host...&lt;/code&gt; in VS Code.&lt;/p&gt;
&lt;p&gt;For example, I can now select &lt;code&gt;remote-charmeleon&lt;/code&gt; when VSCode prompts for the
host to connect to.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s all. It’s a simple solution that I very much appreciate. I always forget
how powerful &lt;code&gt;ssh&lt;/code&gt; is, especially when you start setting up custom configs. It’s
worth taking a look at! As always, &lt;code&gt;man ssh&lt;/code&gt; is a great place to start 😉.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Disable Seafile Notifications on Gnome 40</title>
    <link href="https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/" />
    <updated>2021-06-17T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/X9u_vtRtcO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/X9u_vtRtcO-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Jockey&#39;s Ridge Stage Park, Nags Head, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Quick post here. Basically, I keep figuring out and then forgetting how to
disable Seafile notifications on Gnome, because it doesn’t allow tray icons.
This time, I’m recording the process.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;First, why do I need to disable &lt;em&gt;Seafile&lt;/em&gt; notifications specifically?  Because I
sync my &lt;a href=&quot;https://obsidian.md&quot;&gt;obsidian&lt;/a&gt; vaults using
&lt;a href=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/&quot;&gt;Seafile&lt;/a&gt;. While this sync method
works wonderfully, it unfortunately has the side effect of whenever I type
in obsidian, seafile… uh… &lt;em&gt;politely notifies me&lt;/em&gt;… that it has synced my
changing notes. Which is incredibly annoying, to say the least.&lt;/p&gt;
&lt;p&gt;However, I don’t want to solve the problem by simply disabling &lt;em&gt;all&lt;/em&gt; system
notifications. Luckily, seafile has a setting to do this… if you can get to
it.&lt;/p&gt;
&lt;h3&gt;Install Tray Icons: Reloaded extension&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/gBr86dsYYf-1103.webp 1103w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/gBr86dsYYf-1103.jpeg&quot; alt=&quot;Installing the gnome extension&quot; width=&quot;1103&quot; height=&quot;755&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Installing &#39;Tray Icons: Reloaded&#39; from the Gnome extensions website&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first step is to install an extension that allows application tray icons.
One such extension that seems to “work” currently (with a catch, more on that
below), is &lt;a href=&quot;https://extensions.gnome.org/extension/2890/tray-icons-reloaded/&quot;&gt;Tray Icons:
Reloaded&lt;/a&gt;. So,
to install it, open up the page in
&lt;a href=&quot;https://www.mozilla.org/en-US/firefox/new/?redirect_source=firefox-com&quot;&gt;Firefox&lt;/a&gt;,
and click the slider tab on the top right, to switch it from &lt;code&gt;OFF&lt;/code&gt; to &lt;code&gt;ON&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: If this is the first extension being installed, Firefox may ask you to
install a plugin. This is normal. Just hit accept and it will do it for you.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;(Optionally) Install Gnome Extensions app&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/CP_IhEmuk2-958.webp 958w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/CP_IhEmuk2-958.jpeg&quot; alt=&quot;Installing gnome-extensions app&quot; width=&quot;958&quot; height=&quot;792&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Installing the Gnome extensions app.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Next, while optional, it is ideal to have the &lt;em&gt;Gnome Extensions&lt;/em&gt; app
installed. So, open up Software Center, and install it from there.
Alternatively, install it as a flatpak from the command line:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; flatpak &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; org.gnome.Extensions&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This app allows you to configure and enable gnome extensions. With Gnome
Extensions opened, make sure &lt;code&gt;Tray Icons: Reloaded&lt;/code&gt; is turned on. &lt;em&gt;Note, icons
likely won’t show up yet, even with the extension enabled&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Switch to an Xorg Session&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/8q4ySNm6Bf-1026.webp 1026w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/8q4ySNm6Bf-1026.jpeg&quot; alt=&quot;Switching to Xorg Session&quot; width=&quot;1026&quot; height=&quot;765&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Switching to Xorg Session&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is the step I always forget (and hence why I’m writing this post). While
&lt;code&gt;Top Icons: Reloaded&lt;/code&gt; “works” in Gnome 40… it only does in Xorg sessions. So,
log out, switch the session to &lt;code&gt;GNOME on Xorg&lt;/code&gt; (gear icon at the bottom right of
the login screen), and log back in. Once logged in, the top icons extension should now work, &lt;em&gt;hopefully&lt;/em&gt; displaying the Seafile icon (assuming
&lt;code&gt;seafile-client&lt;/code&gt; is running).&lt;/p&gt;
&lt;h3&gt;Edit the Seafile Settings&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/dAfKiIkj-s-812.webp 812w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/dAfKiIkj-s-812.jpeg&quot; alt=&quot;Accessing seafile settings from toolbar icon&quot; width=&quot;812&quot; height=&quot;352&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Accessing seafile settings from toolbar icon.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To open up the Seafile settings, right click on the icon and select &lt;code&gt;Settings&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/5ph3pSHXUF-562.webp 562w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/disable-seafile-notifications-gnome40/5ph3pSHXUF-562.jpeg&quot; alt=&quot;Changing notification setting&quot; width=&quot;562&quot; height=&quot;429&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Changing notification setting.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;From there, notifications can be &lt;strong&gt;disabled&lt;/strong&gt; by &lt;strong&gt;unchecking&lt;/strong&gt; the &lt;code&gt;Notify when libraries are synchronized&lt;/code&gt; checkbox. Hit &lt;code&gt;OK&lt;/code&gt;, and that’s it. Feel free to log out and back into a &lt;code&gt;Wayland&lt;/code&gt; session 🙂.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Short post, but I forgot how to do this simple process twice now, so I wrote it.
Hopefully this helps that &lt;em&gt;one&lt;/em&gt; other person also experiencing this issue, or at
least a few more that are confused for similar reasons. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Upgrading My iPad Pro</title>
    <link href="https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/" />
    <updated>2021-05-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/XI932Li3NO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/XI932Li3NO-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;795&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Bodie Island Lighthouse, Nags Head, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A few years ago, &lt;a href=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/&quot;&gt;I purchased my first iPad&lt;/a&gt;, a 10.5&amp;quot;
iPad Pro. I use it just about every day. With the release of the newest 2021
iPad Pro, I decided it was finally time to trade mine in for an upgrade. Here’s
why.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/shLtREImm8-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/shLtREImm8-1200.jpeg&quot; alt=&quot;The box of my 10.5 iPad Pro&quot; width=&quot;1200&quot; height=&quot;561&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The box of my 10.5&quot; iPad Pro.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I got my 10.5&amp;quot; iPad Pro, the 2018 11&amp;quot; iPad Pro had already been released.
At the time, I didn’t think the extra features justified the increased
price of the new iPad. The discount price on the &lt;em&gt;old&lt;/em&gt; pro was more appealing to
me. I was already upselling myself, after originally looking into the base
(non-pro) iPad, so the discounted 10.5&amp;quot; seemed like a good compromise between
features and price.&lt;/p&gt;
&lt;p&gt;The 10.5&amp;quot; pro was a good starting point for my first iPad. It was a great
device, but not too over the top in case I ended up not using it much. If I
&lt;em&gt;did&lt;/em&gt; use it, I could always upgrade/swap it out in the future.&lt;/p&gt;
&lt;h2&gt;Why upgrade?&lt;/h2&gt;
&lt;p&gt;After having my ipad for several months, I started to understand how the
improved features of the 2018 pro could be nice to have. In particular, the
USB-C port, smaller bezels, and FaceID were all very appealing to me.  Still, my
10.5&amp;quot; suited my needs just fine. I did decide though that I &lt;em&gt;would&lt;/em&gt; likely
upgrade my iPad sometime in the future. I enjoyed having it.&lt;/p&gt;
&lt;p&gt;Which brings me to the one thing I &lt;em&gt;did&lt;/em&gt; want to add to my iPad: the Apple
Pencil.  Being able to hand draw is a special feature unique to tablet computers
that I cannot easily reproduce on my other devices. It’s a foundational reason
to even &lt;em&gt;consider&lt;/em&gt; owning a tablet. Additionally, in the years since I purchased
my iPad, new iPadOS releases have started to polish the use of the ‘magic’
pencil with the iPad.&lt;/p&gt;
&lt;p&gt;Unfortunately, my 10.5&amp;quot; ipad only supported the 1st generation Apple Pencil.
Knowing that I would &lt;em&gt;eventually&lt;/em&gt; upgrade my iPad, I didn’t want to buy an old
pencil, only to have to &lt;em&gt;also&lt;/em&gt; replace it when I upgraded. So, I decided to wait
to get the Pencil until it was time to upgrade my device. Now feels like a good
time.&lt;/p&gt;
&lt;h2&gt;Why this generation?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/PBlt4IjHoe-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/PBlt4IjHoe-1200.jpeg&quot; alt=&quot;new ipad pro cameras&quot; width=&quot;1200&quot; height=&quot;866&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 2021 iPad Pro includes a ultra wide front camera, to use with the new center stage feature.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;There are a few factors that pushed me to upgrade to &lt;em&gt;this&lt;/em&gt; generation of the
iPad Pro. First, I can still get a decent trade-in value for it.  Considering
it’s been about 4 years since the 10.5&amp;quot; model was released, I’m not sure how
long that value last with my iPad. Second, if I want to trade it in with Apple
(and I do, it’s probably the easiest method), I am limited to spending the
credit value in their store. While I could try to get a refurbished device,
there hasn’t been many used iPad Pros in stock since the pandemic started. So
the latest model is my best bet.&lt;/p&gt;
&lt;p&gt;Beyond ease of trade-in, there are a few new features in this latest generation
that I am interested in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The M1 chip is complete overkill, but I wouldn’t mind having one in my iPad
considering how much I love it  &lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/&quot;&gt;my MacBook
Air&lt;/a&gt;. Additionally, it provides more
computational overhead (and increased RAM) incase Apple ever decides to finally
push the capabilities of iPadOS further (which they need to do).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While I thought it was a bit of a gimmick at first, the wide-angle
front-facing camera paired with the new “Center Stage” feature on the 2021 iPad
Pro could actually be useful.  My wife and I use FaceTime to video chat with my
family (we live in another state from everyone), and my ipad is usually what we
use to do it. Being able to more easily fit the two of us in frame, and have it
self-adjust as my wife comes and goes would be delightful.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lastly, while USB-C is great, Thunderbolt is better. I already have all of my
periphery gear hooked up to a thunderbolt dock to connect my work and personal
laptops to. The increased bandwidth also helps to “future proof” (again,
assuming Apple can improve the OS).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why the 11&amp;quot; over the 12.9&amp;quot;?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/kuuvl34cHE-891.webp 891w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/kuuvl34cHE-891.jpeg&quot; alt=&quot;iPad Pro Size Comparison&quot; width=&quot;891&quot; height=&quot;485&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The iPad Pro Size Comparison.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Whenever I would think about upgrading my iPad Pro, I always figured I would
switch to a 12.9&amp;quot; version. So why then have I instead opted for the 11&amp;quot; when the
12.9&amp;quot; is clearly the more exciting option this year, having the new “Liquid
Retina XDR” display? Well, as much as I’d enjoy seeing that new display, that
price bump makes it a bit more unreasonable for a feature I &lt;em&gt;truly do not need&lt;/em&gt;.
I don’t do any visual work, and the display on the 11&amp;quot; Pro is more than enough
for anything I need.&lt;/p&gt;
&lt;p&gt;The real reason for the 11&amp;quot;, is that I now have a great portable laptop.  I was
previously looking at the bigger ipad to be my travel/portable device, but now
that I have an m1 air, a 12.9&amp;quot; ipad with a keyboard would actually weigh &lt;em&gt;more&lt;/em&gt;.
I want my ipad to be more portable than my mac. As a bonus, if
the combined size and weight remains low, it’s easy enough to travel with &lt;em&gt;both&lt;/em&gt;
devices. For reference, a 11&amp;quot; iPad Pro (without a case) and my macbook Air have
a &lt;em&gt;combined weight&lt;/em&gt; of only 3.6 lbs, whereas my previous &lt;a href=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/&quot;&gt;16&amp;quot; Macbook
Pro&lt;/a&gt; alone weighed 4.3lbs! I would like to keep this portability.&lt;/p&gt;
&lt;h2&gt;Why not the new iPad Air?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/VCyvCT_Bs3-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/VCyvCT_Bs3-1200.jpeg&quot; alt=&quot;iPad Air Colors&quot; width=&quot;1200&quot; height=&quot;630&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The iPad Air Color Options.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, if I am contemplating getting the 11&amp;quot; iPad Pro, why not consider the
2020 iPad Air? I struggled with this decision for awhile, particularly before
the latest generation pro was announced. In general, I think the air would be
&lt;em&gt;fine&lt;/em&gt; and meet all of my needs. The reality is that &lt;em&gt;both&lt;/em&gt; of these devices
are currently overkill for most iPad tasks. &lt;em&gt;With that said&lt;/em&gt;… I think the
additional features of the 2021 pro are actually worth the $200 difference (in
my case). Here are a few of those feature differences, and my justification for
the pro:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Display&lt;/strong&gt;: I like the 120hz pro-motion display. I read all the time on
my ipad, and I love how smooth it is. In addition, if my plan is to get an Apple
pencil right away, the high refresh rate can help &lt;em&gt;the pencil’s&lt;/em&gt; responsiveness also feel better.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(furthermore, while not a &lt;em&gt;big&lt;/em&gt; deal, the screen is a little brighter
on the pro, which is appreciated when I want to read outside)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cameras&lt;/strong&gt;: As I said above, the ultra wide front camera is great for
FaceTime, as it is my FaceTime device.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Better speakers&lt;/strong&gt;: I love having nice speakers on my systems, especially
my travel devices. I’ve already downgraded my laptop speakers going &lt;a href=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/&quot;&gt;from the
16&amp;quot; to the m1 air&lt;/a&gt;, and I don’t want to do it
again. (The pro has 4 speakers, whereas the air only has 2).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FaceID&lt;/strong&gt;: TouchID is great, but my finger tips tend to peel due to
allergies, and FaceID simply works more consistently for me.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Thunderbolt&lt;/strong&gt;: Not a deal breaker, but if I want to play around with testing an
&lt;a href=&quot;https://www.macstories.net/stories/modular-computer/&quot;&gt;ipad as a modular
computer&lt;/a&gt; at all, I
already have TB3 docks setup to use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt;: The base version pro has twice as much storage as the air. I
didn’t think this was an issue, but then I looked at the usage on my ipad and
saw I had consumed 60/64GB. I expect my usages will only continue to grow if I
begin to sketch diagrams and notes more often. The air also starts at 64GB,
where the pro begins with 128GB of storage. To upgrade the air, you need to jump
to all the way up to the 256gb hard drive.  After that upgrade, the air is less
than $50 away from the price of the pro.  Considering everything else I listed
above, I think it is definitely worth that additional cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LiDar&lt;/strong&gt; (seriously 😆): This is more of a novelty than a feature for most
people. Still, as someone that likes to play with AR, uses their iPad to scan
floor plans of their house, or to take rough measurements… the LiDar would
help the iPad improve all those tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/_6HEpLkU0X-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/upgrading-ipad-pro-2021/_6HEpLkU0X-1200.jpeg&quot; alt=&quot;My ipad pro order status: processing&quot; width=&quot;1200&quot; height=&quot;368&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My iPad Pro order status: Processing.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;There you go. Those were &lt;em&gt;most&lt;/em&gt; of my reasons for why I concluded to finally
trade in my 10.5&amp;quot; iPad Pro, for a new 11&amp;quot; model. I have ordered the 2021
11&amp;quot; iPad Pro (in silver), along with an Apple Pencil 2, and selected to trade in
my old one with the purchase. Now, all I can do is wait for it to ship. It’s
been stuck
&lt;em&gt;processing&lt;/em&gt; for over a week, but
could &lt;em&gt;arrive&lt;/em&gt; as early as two days ago 😐. Hopefully it ships soon…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Leaving Notion</title>
    <link href="https://ryan.himmelwright.net/post/leaving-notion/" />
    <updated>2021-05-20T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/leaving-notion/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/leaving-notion/HlS04Bxo1e-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/leaving-notion/HlS04Bxo1e-1024.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1024&quot; height=&quot;768&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Kill Devil Hills, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After using &lt;a href=&quot;https://notion.so&quot;&gt;Notion&lt;/a&gt; for my notes and daily planning over
the &lt;a href=&quot;https://ryan.himmelwright.net/post/trying-notion/&quot;&gt;last year&lt;/a&gt;, there are a few issues with it that have
worn on me.  Over time, my uneasiness with the application has built up to the
point where I have decided to switch away Notion completely. Here’s why.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/leaving-notion/sXiBZPo7t_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/leaving-notion/sXiBZPo7t_-1200.jpeg&quot; alt=&quot;my notion dashboard&quot; width=&quot;1200&quot; height=&quot;865&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Notion Dashboard Page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Before getting into it, I want to first state that for the most part, I have
&lt;em&gt;loved&lt;/em&gt; using Notion. It took a few iterations of figuring out how I wanted
everything organized, but after my most recent system overhaul, I have it
arranged perfectly.&lt;/p&gt;
&lt;p&gt;I have everything stored in a handful of databases: Areas, Projects, Tasks, and
Notes.  The items in each of these DBs are all linked. For
example, &lt;em&gt;tasks&lt;/em&gt; and &lt;em&gt;notes&lt;/em&gt; are usually attached to a
&lt;em&gt;project&lt;/em&gt;, which is all associated with a particular &lt;em&gt;area&lt;/em&gt; (&lt;code&gt;Home&lt;/code&gt;, &lt;code&gt;Learning&lt;/code&gt;,
*&lt;code&gt;Social&lt;/code&gt;, and so on).&lt;/p&gt;
&lt;p&gt;In the most recent iteration, I moved away from linking tasks to a daily journal
object. Instead, I assigned &lt;em&gt;everything&lt;/em&gt; a date, and created dashboard pages
with date filters to see the &lt;em&gt;Weekly&lt;/em&gt; and &lt;em&gt;Daily&lt;/em&gt; boards of my tasks.  All in
all, it was a remarkably efficient system… most of the time…&lt;/p&gt;
&lt;h2&gt;What I liked about Notion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/leaving-notion/k0iAhnUgGg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/leaving-notion/k0iAhnUgGg-1200.jpeg&quot; alt=&quot;My tasks of a goal&quot; width=&quot;1200&quot; height=&quot;921&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My linked tasks in a goal project.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ll run through these quickly, as many of the statements echo my &lt;a href=&quot;https://ryan.himmelwright.net/post/trying-notion/&quot;&gt;original
notion post&lt;/a&gt;…&lt;/p&gt;
&lt;h3&gt;Easy to Setup and Use&lt;/h3&gt;
&lt;p&gt;I can have Notion easily configured on &lt;em&gt;all&lt;/em&gt; my devices. All I have to do for
setup, is install the app and log in. I even have the option to access it in a
web browser, if I cannot install it as an app on a device.&lt;/p&gt;
&lt;h3&gt;Flexibility&lt;/h3&gt;
&lt;p&gt;As described in the &lt;em&gt;Background&lt;/em&gt; section above, the flexibility of Notion
allowed me to tailor it to whatever I needed. Notion wasn’t a
&lt;em&gt;system&lt;/em&gt; itself, as much as it was a &lt;em&gt;tool&lt;/em&gt; that let me build my own system.
It felt like a modern evolution of emacs org-mode for ‘normal’ people, which is
a good thing.&lt;/p&gt;
&lt;h3&gt;Templates&lt;/h3&gt;
&lt;p&gt;With such a malleable system, it helps when the more intricate components can be
automated and simplified. Luckily, Notion has a template system, which is what
really allowed my to custom system to be &lt;em&gt;usable&lt;/em&gt;. My frequent tasks were all
templated to save time when entering them into the system. Without templates,
Notion would have been a giant pain to maintain.&lt;/p&gt;
&lt;h3&gt;Everything can be linked&lt;/h3&gt;
&lt;p&gt;I enjoyed being able to link everything together. Seeing all the associated
tasks and notes of a project in a dashboard was game changing for how I think
about organizing information in a system. As an example, I would plan out
monthly goals and link each task to it. That way, at the end of a month I was
able to go back and see &lt;em&gt;exactly&lt;/em&gt; if and when I had completed each task for that
goal.&lt;/p&gt;
&lt;h2&gt;What I didn’t like about Notion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/leaving-notion/vvFqNZyz_i-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/leaving-notion/vvFqNZyz_i-1200.jpeg&quot; alt=&quot;Exporting Notion Data&quot; width=&quot;1200&quot; height=&quot;854&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Exporting Notion Data.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Requires Notion Servers Being Available Notion is completely hosted online&lt;/h3&gt;
&lt;p&gt;and does not have a true solution for working offline.  While this can be
&lt;em&gt;convienient&lt;/em&gt; because I don’t have to worry about syncing data, it does mean
that if the Notion service goes down… I can’t access &lt;em&gt;anything&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I used Notion for my tasks, notes, and everything else. When it went down, I
couldn’t do &lt;em&gt;basic&lt;/em&gt; actions like check my TODO list, or jot down a quick note
in my system. I had to record everything somewhere else, and then go back later to
transfer it into Notion &lt;em&gt;after&lt;/em&gt; the service was available. While &lt;em&gt;Notion&lt;/em&gt;
didn’t go down often, I still faced this problem whenever my internet went
out, or I was traveling and didn’t have a connection. Combined, this happened
often enough to be annoyed by it.&lt;/p&gt;
&lt;h3&gt;Access to the data&lt;/h3&gt;
&lt;p&gt;On a similar note, all of the data and content resides on Notion servers, and in
a non-standard format. As a result, any important data-related responsibility
fall’s entirely on their shoulders, and I have to accept whatever level of
security they choose.&lt;/p&gt;
&lt;p&gt;While data security is very important, my biggest concern is that the
database data is in a weird format. The page content is in markdown-ish,
but the only way to view everything organized properly in the databases (without
hassle, as far as I know), is within Notion.  There are no raw files I can
easily open or convert. They have an exporting system, but every time I tried to
use it, it froze. So even while in theory you &lt;em&gt;can&lt;/em&gt; export everything to &lt;code&gt;md&lt;/code&gt;
and &lt;code&gt;csv&lt;/code&gt; files…  I couldn’t. (&lt;em&gt;Update on this below&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;In particular, I am most worried about data formatting. If Notion ever disbands
as a company, or is acquired by a company that slowly shifts direction (or
honestly, if Notion themselves shift the product over time)… I’m stuck. If I
want to move to a different system, I can’t easily reformat/import the data into
a new system. This alone has been a nagging hesitancy in the back of my head,
the entire time I’ve used Notion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/leaving-notion/9VZzz9osgz-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/leaving-notion/9VZzz9osgz-1200.jpeg&quot; alt=&quot;Exported data in Obsidian&quot; width=&quot;1200&quot; height=&quot;861&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Exported Notion Data in Obsidian Notes.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So while taking screenshots, I tried again to export the data. This time, it
made it all the way through the process successfully. I was then able to take
the output and run it through the &lt;a href=&quot;https://github.com/connertennery/Notion-to-Obsidian-Converter&quot;&gt;Notion-to-Obsidian-Converter
script&lt;/a&gt; (spoiler
for what I’m using now 😆), and import it into an Obsidian vault. While this is
nice to have as an archive, by default it isn’t useful in how it’s formatted and
would be a lot of work to truly convert all the data. This is mostly due to how
I’ve organized by Notion notes, but still… it is a mess and I don’t even know
where to begin in understanding it.&lt;/p&gt;
&lt;h3&gt;Buggy Issues. Particularly With Templates&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/leaving-notion/zg85FL3IZR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/leaving-notion/zg85FL3IZR-1200.jpeg&quot; alt=&quot;My Notion templates&quot; width=&quot;1200&quot; height=&quot;1054&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Notion templates when creating a new task.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, what ultimately pushed me to the edge and confirmed my decision to
switch away from Notion, was the annoying bugs and glitches I kept experiencing.
Unfortunately, some of my favorite features of Notion, were also my biggest
obstacles.&lt;/p&gt;
&lt;p&gt;There were many times when templates just did not work.  I would create a new
item and apply a template… but the content would
&lt;em&gt;not&lt;/em&gt; load. This was an issue I experienced very frequently. Considering how
much I relied on templates, I would have to either wait till a later time when
it would work again, or manually setup everything. Neither of these choices
were productive.&lt;/p&gt;
&lt;p&gt;The last straw, finally inciting action to switch, happened at the end of a week
not too long ago. I noticed mid-week that all of the database links for my
week’s items (linking tasks to their projects, or even the link for what &lt;em&gt;area&lt;/em&gt;
a task belonged to)… were missing. Nothing was connected.&lt;/p&gt;
&lt;p&gt;Normally, this is set automatically with the templates, so I chalked it up to
template issues, &lt;em&gt;again&lt;/em&gt;, and manually connected everything. That Friday, I
had off from work, so I sat down to plan out my vacation day. I quickly noticed
that the items weren’t linked… &lt;em&gt;again&lt;/em&gt;. That was enough. I spent the rest of
my vacation day researching alternatives for Notion. (and trying to export my
data… but I already stated how that (originally) went 😖).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/leaving-notion/_RDwx8FkR1-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/leaving-notion/_RDwx8FkR1-1200.jpeg&quot; alt=&quot;Notion task board&quot; width=&quot;1200&quot; height=&quot;862&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Notion Task Board.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I think Notion is a great product, but with several trade-offs.  I recognize
that many of the items I feel strongly about, won’t be concerns for others.
In fact, I was able to overlook many of them &lt;em&gt;because&lt;/em&gt; of how great a tool it
was.&lt;/p&gt;
&lt;p&gt;Over the past year however, my concerns about access to the data have really
been nagging in the back of my mind. Every day that passed, I felt the guilt of
having an ever-growing collection of notes that was equally growing in how
painful it would be to loose/convert if I switched away from Notion. On top of
that, every time I experienced issues with my templates, I wanted to replace
Notion. So now I finally have.&lt;/p&gt;
&lt;p&gt;It’s been a great year using a great tool. Notion is an excellent tool if you
can manage not always having access to it, and if you are okay with the chance
it might all go away someday. But for my planning and notes system, I just can’t
afford to use something that doesn’t give me ownership of the raw files it uses.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using kid3 Audio Tag Editor</title>
    <link href="https://ryan.himmelwright.net/post/using-kid3/" />
    <updated>2021-04-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/using-kid3/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-kid3/Vb87KTs_zl-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-kid3/Vb87KTs_zl-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Jockey&#39;s Ridge Stage Park, Nags Head, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A few months ago, I set out to finally clean up and organize my music library.
My goal was to get rid of all the random files, and ensure that every item was
properly tagged so that it would accurately self-organize in the music players I
use. This would not have been possible without the open source tool,
&lt;a href=&quot;https://kid3.kde.org&quot;&gt;kid3&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;In order to clean up my music library, I needed a method to edit the metadata
tags of the files. Music applications use these tags to grab information about a
song. For example, a song’s title, artist, album name, and release year are all
contained in the a music file’s tags.  Using this information, the music
player is able to organize and sort the music by artist, album, year, genre, or
by any other bit of data it knows.&lt;/p&gt;
&lt;p&gt;In the past, I used a tool called &lt;code&gt;easytag&lt;/code&gt; to edit the metadata of my files.
However, after not being able to initially find it (I had the wrong name 😆), I
started to look for a new tool. I needed something easy to use, as I did not
want to waste a bunch of time learning a complicated new tool. After very
little searching, I found &lt;a href=&quot;https://kid3.kde.org&quot;&gt;kid3&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Kid3&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-kid3/yOgfCBrojn-1135.webp 1135w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-kid3/yOgfCBrojn-1135.jpeg&quot; alt=&quot;kid3 window&quot; width=&quot;1135&quot; height=&quot;856&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;kid3 window.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As a &lt;a href=&quot;https://spins.fedoraproject.org/kde/&quot;&gt;Fedora Plasma&lt;/a&gt; user, I was happy to
learn that kid3 appears to be maintained directly by the KDE team.  To use kid3,
just drag and drop the files into it, add/edit any of the tag info, and save.
New tags can also be added if they aren’t listed.&lt;/p&gt;
&lt;p&gt;I started using kid3 on my Linux desktop… but one night I decided to work in
the other room on my macBook. I sadly assumed I would have to find a different
tool to use on macOS. However, while searching a new tool, I learned that kid3
has a macOS build! In fact, in addition to Linux, the KDE team has official kid3
builds for macOS, Windows, and Android! I love a good cross-platform tool.&lt;/p&gt;
&lt;h2&gt;Efficiently Editing Album Tags&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-kid3/YtkAUHBSbp-1135.webp 1135w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-kid3/YtkAUHBSbp-1135.jpeg&quot; alt=&quot;Editing multiple files in kid3&quot; width=&quot;1135&quot; height=&quot;856&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Editing multiple files in kid3.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After using kid3 it for a bit, I fell into a workflow that made the tedious task
of grouping multiple files into single albums easier to manage. Specifically, I
learned that I could select multiple files, and edit a field to apply to all of
the selected items at one time.  Using this technique, I was able to write the
artist, album, and year only once, instead of 10+ times for each album. This
made it &lt;em&gt;much more&lt;/em&gt; efficient than methods I had previously used while editing
metadata.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/using-kid3/TrIsHMYf9q-975.webp 975w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/using-kid3/TrIsHMYf9q-975.jpeg&quot; alt=&quot;A custom album tagged in kid3&quot; width=&quot;975&quot; height=&quot;710&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A &#39;home-made&#39; file tagged in kid3, now properly sorted in my music player.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That’s all I have. This was a short post, but I think it was worth it. I love
when I find a great open source, cross platform tool that does &lt;em&gt;exactly&lt;/em&gt; what I
need. Kid3 is a gem application. If you need to edit some audio
tags, check it out. It certainly made my life much easier 🙂.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting up NFS</title>
    <link href="https://ryan.himmelwright.net/post/setup-nfs/" />
    <updated>2021-04-21T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/setup-nfs/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-nfs/OLiO0Skkfy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-nfs/OLiO0Skkfy-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pocosin Lakes Wildlife Refuge, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Thanks to VFIO passthrough, I find myself sitting in front for a virtualized
Linux system on a daily basis. While this setup is unbelievable, it does come
with a few complications. One such hurdle is sharing files located on the
host system with the VMs. After trying a few methods, I determined that nfs
was the simplest to get up and running. Here’s how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-nfs/eHq_mu3GmC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-nfs/eHq_mu3GmC-1200.jpeg&quot; alt=&quot;Not Music in the VM&quot; width=&quot;1200&quot; height=&quot;840&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;There&#39;s no music files in the VM and the player errors looking for the missing files.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My desktop is my main workstation. It is the hub that holds all of the data I
use, including documents, music, and videos. When I am working in a VM, I
often want access to that data. I like listening to my music
while working in VMs 😉.&lt;/p&gt;
&lt;p&gt;It would be inefficient, both in time and disk space, to copy all the files I
desired to each VM that I use. Setting up a network filesystem is an can get
around this. After some trial and error, I eventually concluded that for &lt;em&gt;my
use&lt;/em&gt; (linux guests on a linux host), NFS worked. If you want to share with
Windows guests, something like samba might work better.&lt;/p&gt;
&lt;h2&gt;Server Setup&lt;/h2&gt;
&lt;p&gt;First, we want to setup the sever. Be sure to run the following steps on the
&lt;em&gt;host&lt;/em&gt; machine.&lt;/p&gt;
&lt;h3&gt;Install Dependencies&lt;/h3&gt;
&lt;p&gt;Being on a Fedora host, I installed the dependencies using the following
&lt;code&gt;dnf&lt;/code&gt; command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; nfs-utils &lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Configure The Domain Name&lt;/h3&gt;
&lt;p&gt;Next, open up the file &lt;code&gt;/etc/idmapd.conf &lt;/code&gt; and make sure that the &lt;code&gt;Domain = ...&lt;/code&gt; line (usually line 5) is un-commented, and set to your machine’s
hostname. If not, edit it to configure the &lt;code&gt;idmapd&lt;/code&gt; domain name:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;Domain&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;charmeleon&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Define the Exports File&lt;/h3&gt;
&lt;p&gt;After everything is installed, we need to create the &lt;em&gt;exports&lt;/em&gt; file. This
file will define all of the nfs shares we want to make available. Create and
open the file &lt;code&gt;/etc/exports&lt;/code&gt;, and add something similar for each directory
you want to share:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/home/ryan/Seafile      10.0.7.0/24(rw,all_squash,insecure)
/home/ryan/Music        10.0.7.0/24(rw)
/Data/Videos            10.0.7.0/24(rw)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I like to share my Music and Video folders, along with my Seafile library (so
I don’t have to configure it on each VM).&lt;/p&gt;
&lt;p&gt;Each line breaks down as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The directory location to share (ex: &lt;code&gt;/home/ryan/Music&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The range of addresses that are allowed to access the share (I use
&lt;code&gt;10.0.7.0/24&lt;/code&gt;, which allows devices on my home network)&lt;/li&gt;
&lt;li&gt;The properties for each share. I use &lt;code&gt;(rw)&lt;/code&gt; to allow read and write
permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That should be it. Feel free to dig deeper into all the nfs share settings if
you need them, especially if you want to be a bit more secure in your setup!&lt;/p&gt;
&lt;h3&gt;Firewall/other permissions&lt;/h3&gt;
&lt;p&gt;After creating the exports file, we need to allow the service through the
firewall. To do so, permanently allow the nfs service:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd --add-service&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nfs &lt;span class=&quot;token parameter variable&quot;&gt;--permanent&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t forget to reload the firewall!&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd &lt;span class=&quot;token parameter variable&quot;&gt;--reload&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Start the service&lt;/h3&gt;
&lt;p&gt;Last but not least, make sure the &lt;code&gt;rpcbind&lt;/code&gt; and &lt;code&gt;nfs-sever&lt;/code&gt; services are
started and enabled to autostart after a reboot.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable --now rpcbind nfs-server
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;VM Client Setup&lt;/h2&gt;
&lt;p&gt;With the server setup, we should be able to mount the shares in the VMs
now. First, ensure the &lt;code&gt;nfs-utils&lt;/code&gt; package is installed there too.&lt;/p&gt;
&lt;h3&gt;Mount&lt;/h3&gt;
&lt;p&gt;Now, use the &lt;code&gt;mount&lt;/code&gt; command with &lt;code&gt;nfs&lt;/code&gt; for the &lt;code&gt;-t&lt;/code&gt; flag to mount the
shares:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mount&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; nfs &lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt;.7.82:/home/ryan/Music /home/ryan/Network/Music&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you experience weird issues when mount the shares, a &lt;code&gt;reboot&lt;/code&gt; &lt;em&gt;may&lt;/em&gt; help.
I occasionally hit permission issues that seem to be resolved after a
restart.&lt;/p&gt;
&lt;h3&gt;fstab&lt;/h3&gt;
&lt;p&gt;It is possible to add the share to your &lt;code&gt;/etc/fstab&lt;/code&gt; file, so that it
auto-mounts during boot. However, I don’t usually do this. I like to
make the conscious decision to mount data from the host machine,
as I don’t always need it available in the VMs.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-nfs/Oc_wKLL-Su-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-nfs/Oc_wKLL-Su-1200.jpeg&quot; alt=&quot;Listening to mounted Music in the VM&quot; width=&quot;1200&quot; height=&quot;840&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Listening to music in a VM, mounted from a nfs share on the host.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That’s about it. This was just the basics of setting up a nfs share, and I’m
sure I do &lt;em&gt;something&lt;/em&gt; wrong, but so far… it has worked great for my simple
use case of sharing data between the host and guests on the same computer.
Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using Remote VSCode</title>
    <link href="https://ryan.himmelwright.net/post/vscode-remote/" />
    <updated>2021-03-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/vscode-remote/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/vscode-remote/2NqyhjqTlW-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/vscode-remote/2NqyhjqTlW-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;899&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Duke University, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I mostly work on &lt;a href=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/&quot;&gt;my desktop&lt;/a&gt;, but
usually &lt;em&gt;from&lt;/em&gt; another computer, like my
&lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/&quot;&gt;laptop&lt;/a&gt;. Historically, the most common
method I use for this has been combining &lt;code&gt;neovim&lt;/code&gt; and &lt;code&gt;tmux&lt;/code&gt; (with
&lt;a href=&quot;https://github.com/tmux-python/tmuxp&quot;&gt;tmuxp&lt;/a&gt;, for ease). However, more often
than not, I now find myself using &lt;a href=&quot;https://code.visualstudio.com&quot;&gt;VS Code&lt;/a&gt;
with it’s &lt;a href=&quot;https://code.visualstudio.com/docs/remote/remote-overview&quot;&gt;remote development
plugin&lt;/a&gt;. Here’s
how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Install the Remote Development Plugin&lt;/h2&gt;
&lt;p&gt;To get started, first make sure that VS code is installed on your machine.
This can be found on the project’s &lt;a href=&quot;https://code.visualstudio.com&quot;&gt;website&lt;/a&gt;,
or even as a &lt;a href=&quot;https://flathub.org/apps/details/com.visualstudio.code&quot;&gt;flathub
flatpak&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, to instal the plugin, click the Extensions tab in the side bar (The
icon with four squares). From there, search for the extension by using
something like ‘remote development’ as the search term. The &lt;code&gt;Remote Development&lt;/code&gt; extension should appear in the list. Select it, and click &lt;code&gt;Install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/vscode-remote/install_remote_vscode.mp4&quot;&gt;(Click for an example video)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This extension is actually a package that bundles three extensions: &lt;code&gt;Remote - SSH&lt;/code&gt;, &lt;code&gt;Remote - WSL&lt;/code&gt;, and &lt;code&gt;Remote - Containers&lt;/code&gt;. Alternatively, you can just install the one you need.&lt;/p&gt;
&lt;h2&gt;Connect to the Server&lt;/h2&gt;
&lt;p&gt;With the remote plugin installed, we can now connect to a remote machine. I
mostly do this using &lt;code&gt;ssh&lt;/code&gt;. Make sure you first have ssh keys copied to the
device you want to link to. Then, open the command pallet (&lt;code&gt;CMD/CTRL-Shift-P&lt;/code&gt;), and search for the &lt;code&gt;Remote-SSH: Connect to Host...&lt;/code&gt; command, and run it.&lt;/p&gt;
&lt;p&gt;This will prompt you for an ssh host to connect to. Just enter a &lt;code&gt;host&lt;/code&gt; like
you would when using &lt;code&gt;ssh&lt;/code&gt;. For example, &lt;code&gt;ryan@example.com&lt;/code&gt;. If it’s the
first time adding a host, the plugin will ask where it can save a config file
for the hosts. I usually just select one of the defaults it offers me.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/vscode-remote/remote_vscode_config.mp4&quot;&gt;(Click for example video)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/vscode-remote/ROjgGDfyzg-501.webp 501w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/vscode-remote/ROjgGDfyzg-501.jpeg&quot; alt=&quot;Remote Window connected status&quot; width=&quot;501&quot; height=&quot;244&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The connected status is displayed in the lower left of the window (top window: remote, bottom: local).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When connecting to a remote machine, VS code will usually pop open a new
window. To verify, the ip address of the machine that the window is connected
to can be found in lower left corner.&lt;/p&gt;
&lt;h2&gt;Working on Projects, Remotely&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/vscode-remote/VMM4gIwQ39-1028.webp 1028w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/vscode-remote/VMM4gIwQ39-1028.jpeg&quot; alt=&quot;Opening a remote dir&quot; width=&quot;1028&quot; height=&quot;802&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Opening a directory in the remote window.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When working in a remote VS Code window, everything is passed on from the
remote machine, as if you were sitting down at that computer. When opening a
new file or folder, you browse the contents of the &lt;em&gt;remote filesystem&lt;/em&gt;, not
the local one.&lt;/p&gt;
&lt;p&gt;Even the built in terminal runs on the remote machine. I still work in
&lt;code&gt;tmux&lt;/code&gt;, so I have my vscode terminal attach whatever &lt;code&gt;tmux&lt;/code&gt; session I need.
This provides me with the flexibility to switch to a new machine and pickup
where I left off. Using this setup, I get a full graphical IDE window, while
still reaping the benefits of &lt;code&gt;vim&lt;/code&gt; + &lt;code&gt;tmux&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/vscode-remote/iUDWrAX2dR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/vscode-remote/iUDWrAX2dR-1200.jpeg&quot; alt=&quot;Working on a post in vs code with vim and tmux opened in the shell&quot; width=&quot;1200&quot; height=&quot;644&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Working on this post, with `vim` opened in `tmux` in the built-in terminal.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;And yes… you &lt;em&gt;can&lt;/em&gt; run &lt;code&gt;vim&lt;/code&gt;, &lt;em&gt;inside&lt;/em&gt; &lt;code&gt;tmux&lt;/code&gt;, &lt;em&gt;in&lt;/em&gt; the built-in VScode
terminal, &lt;em&gt;in&lt;/em&gt; a VScode remote window. This is something I actually do quite
often XD.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I am almost always working &lt;em&gt;from&lt;/em&gt; a different machine than the one which my
files and work are on. This setup has been idea for how I work. It is easy to
use, yet very powerful.&lt;/p&gt;
&lt;p&gt;Sitting in front a gruvbox-themed VSCode window, connected to my desktop, and
typing using vim keybindings (via a vim plugin) has become my new default, no
matter what computer (or VM) I am physically working from. I love it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Jenkins Parallel Stashing</title>
    <link href="https://ryan.himmelwright.net/post/jenkins-parallel-stashing/" />
    <updated>2021-03-19T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/jenkins-parallel-stashing/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/jenkins-parallel-stashing/rwv7nk3_Gd-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/jenkins-parallel-stashing/rwv7nk3_Gd-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;973&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pennsburg, PA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I recently hit a snag while developing a Jenkins pipeline at work. I was
having difficulty preserving files across the entire pipeline run. In the
end, my solution was to use the stash feature. However, I found little
support for my &lt;em&gt;specific&lt;/em&gt; type of issue online, so I figured I might as well
write a short post about my experience.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The pipeline runs an integration test suite I am working on. It contains
mostly sequential stages, but there are a few that run in parallel.
Specifically, there are provisioning and tear-down stages, one for each
of the components we want to use during the testing.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/jenkins-parallel-stashing/SBwkofj_fA-1066.webp 1066w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/jenkins-parallel-stashing/SBwkofj_fA-1066.jpeg&quot; alt=&quot;Pipeline stages&quot; width=&quot;1066&quot; height=&quot;348&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pipeline stages.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The Jenkins instance is hosted on
&lt;a href=&quot;https://www.openshift.com&quot;&gt;Openshift&lt;/a&gt;, and uses containers for the job
nodes. The pipeline uses several containers across the different stages,
which means that the local filesystem does not persist throughout the entire
pipeline. This was a problem, because we wanted to maintain metadata files
from each of the provisioning stages, to use in dynamic tests, as well
as during each tear down stage.&lt;/p&gt;
&lt;p&gt;The issue is further complicated by the parallel provisioning stages. Each
provisioner runs in its own container, all at the same time. On top of that,
the number of provisioner/tear-down stages is dynamic. The pipeline might run
using data provisioners for components &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;, and &lt;code&gt;D&lt;/code&gt;, in one run, and
&lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;G&lt;/code&gt;, and &lt;code&gt;H&lt;/code&gt; for during next. It all depends on what
integration tests we want to perform. This meant that my solution also had to be dynamic and flexible.&lt;/p&gt;
&lt;h2&gt;My Plan&lt;/h2&gt;
&lt;p&gt;I looked briefly into changing the containers’ volume configuration, but my
proof of concept pipeline hit a silent failure right away (the pipeline would
endlessly hang, with no error). I decided to revisit that approach &lt;em&gt;if&lt;/em&gt;
my next plan (implementing stash and un-stash) didn’t work.&lt;/p&gt;
&lt;p&gt;To simplify &lt;em&gt;what&lt;/em&gt; to stash, and to have it work with the dynamically
changing parallel stages, I decided to try a &lt;em&gt;single&lt;/em&gt; stash location. I made
a &lt;code&gt;metadata&lt;/code&gt; directory that the stages could all write to, and stashed it. My
thought was that I could then un-stash it at the start of the first stage in
each subsequent node, and re-stash the contents at the end of a node.&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;sh &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mkdir metadata&quot;&lt;/span&gt;&lt;/span&gt;
sh &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;touch metadata/metadata_init&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Stash won&#39;t work with empty dirs&lt;/span&gt;
stash includes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metadata/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metadata&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; allowEmpty&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;unstash &lt;span class=&quot;token string&quot;&gt;&#39;metadata&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added the calls to my pipeline, having everything write to a
&lt;code&gt;metadata&lt;/code&gt; stash. It was a good idea, but there was a problem…&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;My&lt;/em&gt; Problem&lt;/h2&gt;
&lt;p&gt;Because the stash is executed individually in parallel, only the files from
the &lt;em&gt;latest&lt;/em&gt; parallel stage were being saved and stored.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;As many issues in software, both the problem &lt;em&gt;and&lt;/em&gt; the fix were rather
simple. All I really did was change my stash call to used
&lt;em&gt;as documented&lt;/em&gt;. Still, I had trouble finding &lt;em&gt;anything&lt;/em&gt; online talking about
&lt;em&gt;using&lt;/em&gt; stash the way I needed to, so I thought I’d share.&lt;/p&gt;
&lt;p&gt;My solution was to continue to use the single directory method, but
&lt;em&gt;dynamically change the name of the stash&lt;/em&gt; during that initial parallel
stage. It turns out, that the data was overwritten because each stash call had the same &lt;em&gt;stash name&lt;/em&gt;, not because it was stashing the same directory.&lt;/p&gt;
&lt;p&gt;I only need to parallel stash during the initial parallel stage, as the
second one was at the end of the pipleline where the metadata would no longer
be appended. So my plan was to dynamically stash each provisioner metadata,
and then merge all the stashes into a single &lt;em&gt;metadata&lt;/em&gt; stash to be used
throughout the rest of the pipeline.&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Stash each builder&#39;s metadata if success&lt;/span&gt;
stash includes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metadata/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metadata-${testLabel}&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; allowEmpty&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Un-stash all successful builder metadata into single dir&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; testLabel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    unstash &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;metadata-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before making things complicated by unmerging to a different location from
where I wanted &lt;code&gt;metadata&lt;/code&gt; to be, I first decided to see what happens if I
simply &lt;code&gt;unstash&lt;/code&gt;ed everything directly to the &lt;code&gt;metadata&lt;/code&gt; dir. Luckily,
everything merged just fine with no overwrites. I even didn’t have to write
a merging function! Problem solved.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So that’s it. Often, the solution to a seemingly complicated problem is a
simple one. This can be made more difficult if all of the online discussion
seems tangential, but not exactly what you need. In those moments, it is
probably best to pull aside a buddy to bounce ideas off of. Even if they
don’t know how to immediately solve your problem, they can highlight problem
areas that you might be able to find a solution in.&lt;/p&gt;
&lt;p&gt;That is exactly how this solution surfaced. My friend
&lt;a href=&quot;https://elyezer.com&quot;&gt;Elyezer&lt;/a&gt; pointed out that the stashes were likely being
over-written &lt;em&gt;because&lt;/em&gt; they had the same stash name. Even though I &lt;em&gt;knew&lt;/em&gt;
that, having him re-emphasize it helped me zone in and work out the idea
of dynamically making different stashes and merging them for the rest of the
pipeline.&lt;/p&gt;
&lt;p&gt;Teamwork. It makes a difference.&lt;/p&gt;
&lt;p&gt;(P.S. Thanks Elyezer!)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2020 M1 Macbook Air Initial Thoughts</title>
    <link href="https://ryan.himmelwright.net/post/m1-air-initial-thoughts/" />
    <updated>2021-03-02T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/m1-air-initial-thoughts/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/NnMFJRpgxK-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/NnMFJRpgxK-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As discussed in my &lt;a href=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/&quot;&gt;previous post&lt;/a&gt;, I decided
to trade-in my 2019 16&amp;quot; MacBook Pro and replace it with a new 2020 M1 MacBook
Air. So far, I think making the swap was a terrific idea. Here are
my initial thoughts…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background (what I ordered)&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/Zf1IVeVnQo-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/Zf1IVeVnQo-1200.jpeg&quot; alt=&quot;The M1 Air Box&quot; width=&quot;1200&quot; height=&quot;744&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The MacBook Air Box.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To recap, I got the ‘second tier’ 2020 M1 MacBook Air (Silver, 8 gpu
cores, 512GB ssd) and upgraded the RAM to 16Gb. I wanted to maintain the same
RAM and storage that my MBP had. After an eternity (3+ weeks), the air
was delivered.&lt;/p&gt;
&lt;h2&gt;What I Like&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/Y9Zc6gI4lx-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/Y9Zc6gI4lx-1200.jpeg&quot; alt=&quot;The M1 Air Side&quot; width=&quot;1200&quot; height=&quot;606&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Side of the MacBook Air.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;First, lets start with the good. There is a lot to say here, but these are my
main thoughts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It’s cool and &lt;s&gt;quiet&lt;/s&gt; &lt;em&gt;silent&lt;/em&gt;. It hardly heats up, and when
it does, it is &lt;em&gt;barely&lt;/em&gt; noticeable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It is very portable. I can easily pick it up with one hand, and set it on my
lap nearly anywhere.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I like having physical function keys again. I even learned
how to utilize &lt;a href=&quot;https://folivora.ai&quot;&gt;BetterTouchTool&lt;/a&gt; for window management
using keyboard shortcuts, instead of the touch bar.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/wake_compare.mp4&quot;&gt;Click for A video comparing instant wake&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Instant wake is actually delightful. I initially thought my sleep settings
were misconfigured, and that the laptop wasn’t turning off when I shut
the lid. It’s a feature that I thought “&lt;em&gt;who cares&lt;/em&gt;” when it was announced, but
after I tried it, my 16&amp;quot; suddenly felt unbearably slow for the few days I had
both.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rosetta II is amazing at what it does. Most apps just work because of it,
even if they haven’t been ported to Apple Silicon yet. This has made the transition
rather seamless.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It works surprisingly well at my desk on the laptop stand, possibly even
better than the 16&amp;quot;. The laptop is about the same size as the stand, so I’m
able to push it further back, out of the way, and it is much sturdier. The
smaller screen hasn’t been an issue with the content I usually play on it at
my desk (background videos or music). With how cool it runs, I also feel
safer closing it to focus solely on my external monitor, as if I was using a
mac mini.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/terminal_compare.mp4&quot;&gt;Click for a video comparing opening teminal tabs&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;My terminal instantly opens! For some reason on my mac, it always took
a second to load the shell prompt in iterm with my config. On the air,
it is instant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Garage band works much better. It opens immediately and is ready. My 16&amp;quot;
would freeze while loading, then sometimes beach-ball and crash.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The battery life is great. I don’t really think about it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What Could be Better&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/MsBtHI9m0E-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/MsBtHI9m0E-1200.jpeg&quot; alt=&quot;My old MacBook Pro and the new M1 Air&quot; width=&quot;1200&quot; height=&quot;720&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Air has thicker bezels compared to my old 16-inch MacBook Pro.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The screen bezels are rather thick, even compared to the 16&amp;quot; and could use
some slimming.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;More TB ports. The air has two Thunderbolt ports, on only &lt;em&gt;one&lt;/em&gt; side of the
laptop. I use hubs, so I don’t usually require more than the two ports, but
it can be an issue to only have them on one side depending on where I want to
place my laptop. Having one port on either side wouldn’t solve the issue
either, because I wouldn’t like wrapping the power cord all the way around to
the other port when my hub is plugged in. The only &lt;em&gt;true&lt;/em&gt; solution is to have
more than 2 TB ports, split across both sides of the laptop, &lt;em&gt;even if&lt;/em&gt; you
don’t need them all at the same time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some applications are still tricky to install. For example, I had to
compile &lt;code&gt;neofetch&lt;/code&gt; in &lt;code&gt;homebrew&lt;/code&gt; (which was simple enough to do). I imagine
this issue should resolve with time. Overall though, I haven’t really had any
major installation issues.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What’s… Different?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/OQudOib2Oe-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/OQudOib2Oe-1200.jpeg&quot; alt=&quot;The M1 Air Keyboard&quot; width=&quot;1200&quot; height=&quot;840&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The MacBook Air does not have a touchbar.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;There were a few things that were neither good nor bad, just &lt;em&gt;different&lt;/em&gt; coming
from an Intel 16&amp;quot; MBP:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Surprisingly, not having the touch bar. Not that I &lt;em&gt;miss&lt;/em&gt; it, but I
apparently got used to checking the time there and had to slowly break that
habit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Efficiency cores are weird at first. I would look at my CPU usage when
only running background tasks and wonder why the first four cores had such
high usage, especially when compared to the other four. Eventually, I
realized it was because the tasks were running using mostly the efficiency
cores. So while it &lt;em&gt;looked&lt;/em&gt; like there was a heavy load that it wasn’t being
properly balanced across the cores… the work was actually shifted to be
more efficient. This is the desired functionality, but weird to get used to
at first if you’re someone that is constantly watching CPU usage like I do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The smaller screen space. I got used to the smaller screen quicker than I
anticipated, and I think the trade-off is worth it for the portability of
this laptop. But it &lt;em&gt;is&lt;/em&gt; different.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The speakers. The air has &lt;em&gt;great&lt;/em&gt; speakers… but the 16&amp;quot;
had &lt;em&gt;truely amazing&lt;/em&gt; speakers. Like the screen resolution, this is mostly a physical
size limitation, and I think the trade-off is well worth it here as well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/-UGId7NLKd-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/m1-air-initial-thoughts/-UGId7NLKd-1200.jpeg&quot; alt=&quot;The M1 Air with Coffee&quot; width=&quot;1200&quot; height=&quot;829&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The MacBook Air with my coffee.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This laptop has been remarkable. It reminds me of some of my previous
&lt;em&gt;portable&lt;/em&gt; laptops, like the x230 thinkpad and my x201e. That is a good thing.
I &lt;em&gt;loved&lt;/em&gt; both of those computers and their ability to be thrown in a bag on
the go. It is a setup that pairs nicely with a powerful workstation, which &lt;a href=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/&quot;&gt;I
currently have&lt;/a&gt;. While the air has
about the same footprint as both of those previous laptops, it is thinner,
lighter, has more screen space, and is
&lt;em&gt;magnitudes&lt;/em&gt; more powerful. I can’t complain with that.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Traded my 2019 16&quot; MacBook Pro for a M1 MacBook Air</title>
    <link href="https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/" />
    <updated>2021-02-11T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/5kbQ_mUd5U-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/5kbQ_mUd5U-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;764&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over a year has passed since I purchased a &lt;a href=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/&quot;&gt;base model 2019 16&amp;quot; MacBook
Pro&lt;/a&gt;. My first Mac. With it’s large “retina”
display, resurrected magic keyboard, and truly &lt;em&gt;amazing&lt;/em&gt; speakers, it might
be the most pleasurable laptop I have ever owned. However, I decided to trade
it in… for a new MacBook &lt;em&gt;air&lt;/em&gt;. Here’s why.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/rqQ22A3IqY-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/rqQ22A3IqY-1200.jpeg&quot; alt=&quot;Installing macOS Cataline on old air&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Installing macOS Catalina on my wife&#39;s old MacBook Air for my Mac challenge.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the Fall of 2019, I decided to do a &lt;a href=&quot;https://ryan.himmelwright.net/post/macos-challenge/&quot;&gt;30 day macOS&lt;/a&gt;
challenge. During the challenge, I learned that while I think Linux is still
my go-to and &lt;em&gt;indispensable&lt;/em&gt; OS, I did enjoy having a mac available for doing
more &lt;a href=&quot;https://www.youtube.com/watch?v=oKiAnxjM8Nc&quot;&gt;“normal life computing”&lt;/a&gt;.
Web browsing, writing emails, managing calendars, personal finance, planning
home projects with my wife, ssh’ing into servers to code… that type of
stuff.&lt;/p&gt;
&lt;p&gt;After the challenge, I decided to acquire a mac for myself. I choose the 16&amp;quot;
MBP &lt;em&gt;mostly&lt;/em&gt; because it was the only MacBook at the time with the new magic
keyboard. That was a hard requirement I had. I wouldn’t even consider buying
a butterfly MacBook. Keyboard aside, as my &lt;em&gt;only&lt;/em&gt; Mac, I did want it
to be powerful enough to be my ‘workstation’ mac/laptop. And it was.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/RCYB8QrKbU-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/RCYB8QrKbU-1200.jpeg&quot; alt=&quot;the mbp had better cooling&quot; width=&quot;1200&quot; height=&quot;583&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;While the 16&quot; MacBook Pro did improve cooling compared to it&#39;s predecessor, it was still too warm for my tastes.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After a few months of use, a problem emerged regarding that power… I didn’t utilize it. As I discovered
&lt;a href=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/&quot;&gt;the last time I had a more “workstation laptop”&lt;/a&gt;, I
don’t like when my computers overheat and spin up their fans. The 16&amp;quot; macbook
was relatively thin for its specs (did you see the size of
my previous workstaion laptop!?!?). However, that sleek design came at a
cost. While the re-designed cooling on the 16&amp;quot; was considered quite good for
a MacBook… it didn’t meet my standards.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/BSU_wfUsZA-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/BSU_wfUsZA-1200.jpeg&quot; alt=&quot;Importing photos, turbo boost made temps 100C&quot; width=&quot;1200&quot; height=&quot;926&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;While using the CPU (importing photos), the temps stayed at 80C with turbo boost disabled. After enabling it, the temps shot up to 100C!&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I eventually purchased and installed &lt;a href=&quot;http://tbswitcher.rugarciap.com&quot;&gt;turbo boost
switcher&lt;/a&gt;, to &lt;em&gt;disable turbo boost on my
cpu&lt;/em&gt; This runs the cpu at it’s base clock, without boosting it up for more
‘bursty’ performance. This didn’t seem to hinder my small day-to-day usage,
and in my mind, was more than worth the trade to keep the computer a little
bit cooler, and prevent the fans from spinning up all the time. It was just a
shame to pay so much money for such powerful computer… and then
purposefully hinder it.&lt;/p&gt;
&lt;h2&gt;The M1 Macs are Released&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/lkbi8h72pu-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/lkbi8h72pu-1200.jpeg&quot; alt=&quot;Apple&#39;s M1 Announcement&quot; width=&quot;1200&quot; height=&quot;648&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Apple announced it&#39;s first Apple Silicon CPU, the M1.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Then, in November, Apple announced the new M1 Macs, the first
computers in the company’s planned 2 transition to switch from Intel CPUs to
Apple’s own “Apple Silicon” arm processors. And they were a hit.&lt;/p&gt;
&lt;p&gt;While I was skeptical about the smoothness of a cpu architecture switch, early
reviewers indicated that Apple handled it extremely well. This had me
tempted to immediately swap my MBP in for one of the newer ones. But, I decided to
hold out for a bit, reasoning that my 16&amp;quot; computer was still an &lt;em&gt;amazing&lt;/em&gt;
laptop that was more than capable of what I needed it for.&lt;/p&gt;
&lt;p&gt;Still, the seed of questioning &lt;em&gt;how&lt;/em&gt; I use a mac had been planted.&lt;/p&gt;
&lt;h2&gt;How I use a Mac&lt;/h2&gt;
&lt;p&gt;I started to map out my Mac use-cases in two broad categories: &lt;em&gt;What&lt;/em&gt; I
use my mac for, and &lt;em&gt;where&lt;/em&gt; I use it. After some brainstorming, I had the
following lists:&lt;/p&gt;
&lt;h3&gt;&lt;em&gt;What&lt;/em&gt; I use a Mac For&lt;/h3&gt;
&lt;p&gt;I most commonly use my Mac for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Utilizing Apple apps and services (Messages, FaceTime, Notes, Reminders,
Photos, GarageBand, Music, iCloud, Etc)&lt;/li&gt;
&lt;li&gt;Web Browsing&lt;/li&gt;
&lt;li&gt;Online Courses/Proctored Exams&lt;/li&gt;
&lt;li&gt;SSH and/or VS Code (I do coding, writing, and all those other tasks on my workstation, and remote in when on my MacBook)&lt;/li&gt;
&lt;li&gt;Personal Planning/organizing, emails&lt;/li&gt;
&lt;li&gt;Listen to podcasts or music&lt;/li&gt;
&lt;li&gt;Watching videos or livestreams&lt;/li&gt;
&lt;li&gt;Chatting with Friends and Family&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Maybe&lt;/em&gt; a light game if I’m on the go or not at my desk (possibly streamed from my gaming VM)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;em&gt;Where&lt;/em&gt; I use a MacBook&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/CQ7C-xswgw-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/CQ7C-xswgw-1200.jpeg&quot; alt=&quot;Macbook on a stand&quot; width=&quot;1200&quot; height=&quot;1010&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;As my main laptop, my MacBook occasionally has to serve as my portable workstation.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Being both my &lt;em&gt;only&lt;/em&gt; Mac, as well as my main portable computer, there is
actually quite a range of &lt;em&gt;where&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; I setup my MacBook. Some examples
include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Portable: I use it as my portable machine, in a few types of locations:
&lt;ul&gt;
&lt;li&gt;Sitting on the couch&lt;/li&gt;
&lt;li&gt;At a Table, by itself&lt;/li&gt;
&lt;li&gt;At a table on a stand w/ Keyboard, and possibly other external displays&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;As my single portable workstation if I travel or visit family&lt;/li&gt;
&lt;li&gt;As my Mac ‘desktop’, connected to all the devices at my desk via a thunderbolt hub.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Best Tool for the Job&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/6l6fREhPXv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/6l6fREhPXv-1200.jpeg&quot; alt=&quot;Using my macbook while running benchmarks on my desktop&quot; width=&quot;1200&quot; height=&quot;862&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using my MBP while running benchmarks on my desktop after [some upgrades](/post/selecting-charmeleons-upgrades/). Dispite its pwer, my Mac is a *secondary* computer.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A disparity stood out to me while makings these lists. I
generally try to use appropriate hardware for my need (at least within
reason). Looking over what I wrote, it felt weird to have a $2000+ laptop for
what I use it for. I did &lt;em&gt;not&lt;/em&gt; need that much for my &lt;em&gt;secondary computer&lt;/em&gt;,
even if I loved the speakers and screen.&lt;/p&gt;
&lt;p&gt;If this was my &lt;em&gt;main&lt;/em&gt; computer, it would be fine. But it isn’t. I have a
&lt;a href=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/&quot;&gt;powerful linux workstation&lt;/a&gt; for my
heavy lifting and complicated, virtual workloads. Hell, macOS isn’t even my
main OS. I do all my power-user computing on Linux… even if it’s &lt;em&gt;from&lt;/em&gt; my
mac (as previously stated, I &lt;code&gt;ssh&lt;/code&gt; into my desktop for nearly everything…
including writing these posts). The MBP &lt;em&gt;met&lt;/em&gt; the role I needed it for, but
it didn’t necessarily
&lt;em&gt;fit&lt;/em&gt; that role.&lt;/p&gt;
&lt;h2&gt;16&amp;quot; MBP vs. Air&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/G5Hd8NucJ_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/G5Hd8NucJ_-1200.jpeg&quot; alt=&quot;Both Macs, closed on table&quot; width=&quot;1200&quot; height=&quot;625&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 2019 16&quot; MacBook Pro and the 2020 M1 Macbook Air.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After hammering out my MacBook use cases, I theorized how a M1 MacBook Air
might compare to my 16&amp;quot; MBP in meeting those needs. I came up with the following
conclusions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While I do occasionally use some of the mac’s heavier software (garageband,
imovie, Photos, and others), the performance of the M1 is more than enough
for what I would need it to do, and even &lt;em&gt;beats&lt;/em&gt; my Intel MBP on some tasks.&lt;/li&gt;
&lt;li&gt;It wouldn’t benefit me to wait for more powerful Apple Silicon Macs,
as my needs are already passed for what I need this device to do. Even
if an Apple Silicon 16&amp;quot; MBP &lt;em&gt;was&lt;/em&gt; available, tempting as it might be, I’ve
already established it isn’t a good fit for this device.&lt;/li&gt;
&lt;li&gt;While I would miss the screen space and speakers, the air would be &lt;em&gt;much&lt;/em&gt;
more portable. Not only would this be great when traveling, but it would also
be appreciated when picking it up with one hand to take into another room.&lt;/li&gt;
&lt;li&gt;The dedicated GPU in the MBP doesn’t really benefit me, as I do all my
gaming in a Windows VM on my workstation. In fact, it may actually &lt;em&gt;hurt&lt;/em&gt; my
experience by generating excess heat and kicking up the fans.&lt;/li&gt;
&lt;li&gt;And on that note… the air doesn’t even &lt;em&gt;have&lt;/em&gt; a fan to spin up, and
doesn’t appear to get too warm either. Unlike the M1 MBP, it &lt;em&gt;will&lt;/em&gt; throttle
to cool the CPU during sustained workloads, but we’ve already established
that I am &lt;em&gt;currently&lt;/em&gt; doing that on my 16&amp;quot; anyway ☺︎.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, I concluded that the Air should be able to do most of the tasks my
16&amp;quot; MBP does, at around the same level of perfomrance, if not better. In the
case of portability and noise, the air would be &lt;strong&gt;much&lt;/strong&gt; better suited than
my Pro. Additionally, most of the workloads the MBP &lt;em&gt;is&lt;/em&gt; better at,
aren’t as important for my needs.&lt;/p&gt;
&lt;h2&gt;Decided to Go for it&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/MoFWaG2_Gc-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/MoFWaG2_Gc-1200.jpeg&quot; alt=&quot;Current trade-in-value&quot; width=&quot;1200&quot; height=&quot;868&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I knew my trade-in value would only get worse, and I didn&#39;t need to wait for the new 16&quot; Pro to come out.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Eventually, I decided to make the trade-in. My main reasons were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The current trade-in price would cover the cost of the “higher end” M1
Macbook Air, and I would only have to pay tax and for any other updgrades I
wanted to make.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I knew I would slowly get less and less for my MBP. I figured once a new
16&amp;quot; MBP was released, the value for mine would likely plummet.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, I ordered the higher tier Air ( with 8gpu cores + 512GB of storage),
and bumped up the RAM to 16GB. If I was going to be be “downgrading”
from a MBP to an Air, I wanted to at least keep the same amount of RAM and
storage that I had before. It took almost a month to arrive, but now it’s
here, and I’m packing up the MBP to send back.&lt;/p&gt;
&lt;p&gt;I need this secondary computer to function as my portable laptop, and only
(physical) Mac. I think the m1 air will fill both of these roles extremely
well. If I needed a laptop to be my &lt;em&gt;main&lt;/em&gt; machine… I would have probably
kept the MBP and upgrade after a newer Apple Silicon version is released.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/PGUa2bud7C-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trading-mbp16-for-m1air/PGUa2bud7C-1200.jpeg&quot; alt=&quot;My mbp.&quot; width=&quot;1200&quot; height=&quot;964&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My old 16&quot; MacBook Pro.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The 2019 16&amp;quot; MacBook Pro was, and still is, an amazing machine. I &lt;em&gt;will&lt;/em&gt; miss
it. Ultimately though, a combination of the M1 Macs being &lt;em&gt;very&lt;/em&gt; good, and
the fact that a smaller, thin and light laptop fit my use case best,
finalized my decision to switch.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Automatically Create and Run a Podman Container Using Systemd</title>
    <link href="https://ryan.himmelwright.net/post/auto-podman-run-systemd/" />
    <updated>2021-01-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/auto-podman-run-systemd/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/auto-podman-run-systemd/U-rHvqPy70-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/auto-podman-run-systemd/U-rHvqPy70-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Emerald Outback, Beech Mountain, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;No, this is not the &lt;em&gt;same&lt;/em&gt; as &lt;a href=&quot;https://ryan.himmelwright.net/post/create-podman-systemd-services/&quot;&gt;my last
post&lt;/a&gt;, but is a continuation of it.
While the basic &lt;code&gt;podman generate systemd&lt;/code&gt; generated file works for many
cases… it wasn’t a good long-term solution for my jellyfin container. So, I
made a small tweak.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;The Issue&lt;/h2&gt;
&lt;p&gt;My issue was that the container initially worked, but would occasionally
break when it grew old. By itself, it isn’t a problem. Containers are
designed to be ephemeral, and arguably &lt;em&gt;should&lt;/em&gt; be blown away before being
instantiated again. The difficulty is that creating a new container
results in having to edit the container uuids in the service file, otherwise
the service breaks… which seems to sometimes then break the &lt;em&gt;new&lt;/em&gt; container.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/auto-podman-run-systemd/iCROynYWLT-1170.webp 1170w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/auto-podman-run-systemd/iCROynYWLT-1170.jpeg&quot; alt=&quot;Failed Jellyfin Webpage&quot; width=&quot;1170&quot; height=&quot;642&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This requires me to then &lt;em&gt;disable&lt;/em&gt; the service, blow-away and make the new
container, update the service file, and &lt;em&gt;finally&lt;/em&gt; start the service… again.&lt;/p&gt;
&lt;h2&gt;A Different Approach&lt;/h2&gt;
&lt;p&gt;I started to think about how I could better the process. I knew that a
&lt;em&gt;newly&lt;/em&gt; created container seemed to work each time, as I previously used the
&lt;code&gt;--rm&lt;/code&gt; flag when starting it manually. From there, I wondered if I could
write &lt;em&gt;my own&lt;/em&gt; systemd service file using the &lt;code&gt;podman run&lt;/code&gt; command instead.
This would both create and kick off a &lt;em&gt;new&lt;/em&gt; container after boot, instead of
re-starting a persistent one.&lt;/p&gt;
&lt;h2&gt;Testing it out&lt;/h2&gt;
&lt;p&gt;To test out my plan, I decided to edit the file created by the &lt;code&gt;podman generate system&lt;/code&gt; command in the last post and go from there. I opened
the service file (&lt;code&gt;~/.config/systemd/user/jellyfin.service&lt;/code&gt;) in my editor and
changed the following line under the &lt;code&gt;[Service]&lt;/code&gt; section:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/podman start 2ba0f86b0fc53cb2fe43abb20215680982800c1bf53421e1a3a90855fa79f030&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I swapped &lt;code&gt;ExecStart&lt;/code&gt; to use my manual &lt;code&gt;podman run&lt;/code&gt; command, with a &lt;code&gt;--rm&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/podman run --name jellyfin --rm -d -v /home/ryan/Network/jellyfin/config:/config -v /home/ryan/Network/jellyfin/cache:/cache -v /home/ryan/Music:/media/music:ro -v /home/ryan/Videos:/media/videos:ro --net=host --privileged jellyfin/jellyfin:latest&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also edited the &lt;code&gt;ExecStop&lt;/code&gt; and &lt;code&gt;ExecStopPost&lt;/code&gt; values:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;ExecStop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/podman stop -t 10 2ba0f86b0fc53cb2fe43abb20215680982800c1bf53421e1a3a90855fa79f030&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ExecStopPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/podman stop -t 10 2ba0f86b0fc53cb2fe43abb20215680982800c1bf53421e1a3a90855fa79f030&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I switched them to use the container name (&lt;code&gt;jellyfin&lt;/code&gt;) instead of uuids:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;ExecStop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/podman stop -t 10 jellyfin&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ExecStopPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/podman stop -t jellyfin&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After saving the changes, I removed my old jellyfin container, reloaded the
unit files (using &lt;code&gt;systemctl --user daemon-reload&lt;/code&gt;, as described in the
previous post), and restarted the machine to determine what elements would
need tweaking when writing &lt;em&gt;my&lt;/em&gt; service file.&lt;/p&gt;
&lt;h2&gt;Another Shortened Post…&lt;/h2&gt;
&lt;p&gt;However, I never made my own service file, or even edited the generated one
again. It appeared to work fine after making those few tweaks. For the
second time in a row, I have a much simpler post than originally intended
due to the tooling around &lt;code&gt;podman&lt;/code&gt;. I’m okay with that.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While the solution still isn’t 100% &lt;em&gt;perfect&lt;/em&gt; (containers sometimes don’t
destroy themselves, and I still have to login for the service to autostart)
it has overall been working great for me. I no longer have to worry about
uuids. When I encounter an issue, I stop the container, it kills
itself, and then the service starts up a fresh one. Much better.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Create Podman Systemd Services</title>
    <link href="https://ryan.himmelwright.net/post/create-podman-systemd-services/" />
    <updated>2021-01-15T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/create-podman-systemd-services/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/create-podman-systemd-services/SX1ZrF1WGU-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/create-podman-systemd-services/SX1ZrF1WGU-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Emerald Outback, Beech Mountain, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;On my Linux workstation, I have started to host a
&lt;a href=&quot;https://jellyfin.org/&quot;&gt;jellyfin&lt;/a&gt; server using podman. I have also started to
shutdown my computer when I go to bed, as all of our important services (ex:
home automation) are hosted on my home server. With that said, there is one
remaining problem with this configuration. When I boot up my computer the
next morning, my containers do not automatically start… and I &lt;em&gt;always&lt;/em&gt;
forget to start them up myself. Let’s fix that, using systemd.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Create the container&lt;/h2&gt;
&lt;p&gt;In order to define a systemd service for a container, the container needs to
already be created and running. The &lt;code&gt;podman run&lt;/code&gt; command is used to start
a new container. For example, to run my jellyfin server, I used the
following command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; jellyfin &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; /home/ryan/Network/jellyfin/config:/config &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; /home/ryan/Network/jellyfin/cache:/cache &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; /home/ryan/Music:/media/music:ro &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; /home/ryan/Videos:/media/videos:ro &lt;span class=&quot;token parameter variable&quot;&gt;--net&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;host &lt;span class=&quot;token parameter variable&quot;&gt;--privileged&lt;/span&gt; jellyfin/jellyfin:latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should be noted, that because we will want the container to persist, even
if it is stopped, the &lt;code&gt;--rm&lt;/code&gt; flag should &lt;em&gt;not&lt;/em&gt; be used here. I normally add
this flag to keeps things clean, but had to remove it for the service file.&lt;/p&gt;
&lt;h2&gt;Root vs User containers?&lt;/h2&gt;
&lt;p&gt;Before getting started, I just want to mention user vs. root containers. In my
first attempt to start podman containers with systemd, I hit some errors
and then I realized… &lt;em&gt;systemd&lt;/em&gt; was running as &lt;em&gt;root&lt;/em&gt; but the container ran
under my username. For example, running &lt;code&gt;sudo podman ps -a&lt;/code&gt; didn’t list my
container, but &lt;code&gt;podman ps -a&lt;/code&gt; did.&lt;/p&gt;
&lt;p&gt;Once solution could be to switch over and run the container as root using
sudo, but that didn’t feel right. A benefit of podman is that it is able to
run rootless, and to not take advantage of that feature would be a shame. So,
I started running the systemd steps as the user, but providing the &lt;code&gt;--user&lt;/code&gt;
flag and it resolved my issues.&lt;/p&gt;
&lt;h2&gt;Creating the Service File&lt;/h2&gt;
&lt;p&gt;At first, I started creating the systemd service files manually, just as I
have done &lt;a href=&quot;https://ryan.himmelwright.net/post/autostarting-application-systemd-service/&quot;&gt;in previous
posts&lt;/a&gt;. Although, after
reading an example in &lt;a href=&quot;https://www.redhat.com/sysadmin/podman-shareable-systemd-services&quot;&gt;this
article&lt;/a&gt;,
I was reminded that there’s actually a &lt;code&gt;podman generate systemd&lt;/code&gt; command.
This command will assemble and output a unit file for a container. I ran
it, and saved the generated service file to my user local systemd location.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; generate systemd jellyfin &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ~/.config/systemd/user/jellyfin.service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple! This file could be altered if needed, but after quickly skimming it I
thought it looked good.&lt;/p&gt;
&lt;h2&gt;Starting &amp;amp; Enabling the Service&lt;/h2&gt;
&lt;p&gt;Before starting the service, it is a good idea to have systemd reload the
user unit files:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl &lt;span class=&quot;token parameter variable&quot;&gt;--user&lt;/span&gt; daemon-reload&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, I started the service, and checked the status to confirm that the
service started up without issue.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl &lt;span class=&quot;token parameter variable&quot;&gt;--user&lt;/span&gt; start jellyfin.service
systemctl &lt;span class=&quot;token parameter variable&quot;&gt;--user&lt;/span&gt; status jellyfin.service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, I &lt;code&gt;enabled&lt;/code&gt; the service, again with the &lt;code&gt;--user&lt;/code&gt; flag so that it
would automatically start on boot:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl &lt;span class=&quot;token parameter variable&quot;&gt;--user&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; jellyfin.service&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Testing it out&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/create-podman-systemd-services/1RrT_b_wME-1110.webp 1110w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/create-podman-systemd-services/1RrT_b_wME-1110.jpeg&quot; alt=&quot;Auto started Jellyfin serving running in a podman container&quot; width=&quot;1110&quot; height=&quot;815&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Jellyfin service running in a podman container auto-started at boot.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With the service setup and enabled, I rebooted my computer to test it out.
After booting up, I used &lt;code&gt;podman ps&lt;/code&gt; to prove that the container was started:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the container &lt;em&gt;running&lt;/em&gt;… I next opened up my web browser to verify that
jellyfin was actually &lt;em&gt;working&lt;/em&gt;… and it was!&lt;/p&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;While this solution works for the most part, I did hit two small annoyances:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Running as a user service, the service won’t start until the
user has logged in. This makes sense, and can be resolved by quickly ssh’ing
into the machine. However, it should be known and worked around if using
something like WOL startup, as sending the magic packet won’t be enough to
get the service containers up and running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If something breaks with the container and it has to be reset, the service
file should also be regenerated and replaced. The file references container
&lt;code&gt;uuid&lt;/code&gt;s, and if that changes, the file needs to reflect that. It’s not a big
problem. Containers break and that’s okay (remember, they’re designed to be
ephemeral). Just remember to remove and regenerate the service file when it
happens.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;When I set out to write this post, I though there would be a bit more to it,
requiring me to create the service file manually. However, I quickly stumbled
on the &lt;code&gt;podman generate systemd&lt;/code&gt; command and I am glad I did. It is one more
feature to add to the ever-growing list of reasons while I love podman and
the other &lt;a href=&quot;https://docs.fedoraproject.org/en-US/fedora-silverblue/_attachments/container-commandos.pdf&quot;&gt;container
commandos&lt;/a&gt;.
Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Selecting Charmeleon&#39;s Upgrades</title>
    <link href="https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/" />
    <updated>2020-12-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/VqIcSUpckb-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/VqIcSUpckb-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;849&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After months of planning and price tracking, I have finished upgrading
several major components in my Linux workstation. What started
as a planned cpu and ram upgrade, eventually ballooned to also include a new
motherboard and a secondary gpu. Let me explain…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/Ibq4rm1IDu-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/Ibq4rm1IDu-1200.jpeg&quot; alt=&quot;Inside charmeleon before upgrades&quot; width=&quot;1200&quot; height=&quot;974&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Charmeleon Internals before the upgrades.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over two years ago, I &lt;a href=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/&quot;&gt;built and designed
charmeleon&lt;/a&gt;, my Linux desktop computer. In
that post, I explained how I wanted to upgrade charmeleon over time, and
designed it with that in mind. Since the initial build, I have only made a
few minor enhancements: I added a nvme ssd and &lt;a href=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/&quot;&gt;upgraded the
gpu&lt;/a&gt; after the original one stopped working. The next
change I hoped to make was adding more RAM. However, as I started down that path,
things became a bit more… complicated…&lt;/p&gt;
&lt;h2&gt;Part Selection&lt;/h2&gt;
&lt;h3&gt;RAM&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/zjBHSz9oHs-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/zjBHSz9oHs-1200.jpeg&quot; alt=&quot;Two 32GB sticks of DDR4 3200 ram in&quot; width=&quot;1200&quot; height=&quot;764&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;RAM upgrade: 2x32 GB of 3200Mhz DDR4, CL 16.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;An easy upgrade I prepared for during the inital build was RAM. My
motherboard had 4 slots with a max capacity of 64GB. So, I made sure to
populate it with 2x16gb sticks instead of 4x8gb ones, so the capacity could
be effortlessly doubled when RAM became cheaper. But, the release of Ryzen
3000 cpus changed things. Overnight, a Ryzen 3000 serries processor paired
with my motherboard could support up to 128gb of RAM.&lt;/p&gt;
&lt;p&gt;This made me wonder if I should instead fill those blank spaces with the
&lt;em&gt;new&lt;/em&gt; max dimm size sticks. The only problem was, if I wanted to upgrade RAM
to the new max size, I also needed to upgrade to a newer CPU. Oh, and also
buy twice the anticipated capacity of RAM.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Spoiler alert: I did also buy a new cpu. More on that below.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Eventually, I settled on a Ripjaws V 2x32GB 3200Mhz kit with 16-18-18-38
timings. There wasn’t a 2x32gb pair that matched the model of my original
RAM, and but this one had all the same timings while still being cheaper than
other alternatives. A total of 96GB of RAM is definitely overkill for most
uses (arguably, even my own). However, I tend to run large, automated VM
deployments and often use my &lt;code&gt;/tmp/&lt;/code&gt; dir as a large working folder, so the
extra overhead is nice to have.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; I always planned to expand my RAM. Ryzen 3000 allowed an even higher
max RAM capacity with my MB, but I would have to also upgrade my CPU. I upgraded both,
and now have 96GB of RAM.&lt;/p&gt;
&lt;h3&gt;CPU&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/udUn3KWjsv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/udUn3KWjsv-1200.jpeg&quot; alt=&quot;Ryzen 9 3900x Box&quot; width=&quot;1200&quot; height=&quot;1042&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;CPU upgrade: Ryzen 9 3900x, 12 core/24 thread cpu.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My &lt;em&gt;origonal&lt;/em&gt; plan when I built charmeleon was to get the 2600, and later
upgrade to a 8 core 2700x or a 3000 cpu that was rumored to potentially have
even more cores. I made sure my motherboard had good VRMs for the cost, so it
could support a future upgrade with more cores.&lt;/p&gt;
&lt;p&gt;Months later the 3000 cpus launched, and I was set on getting a 3900x.
However, I didn’t &lt;em&gt;need&lt;/em&gt; a new cpu yet and I knew AMD was going to be releasing
the “4000” cpus in the Fall. I figured I would wait until they were
released, and then buy a new one, or a discounted 3000 serries one.&lt;/p&gt;
&lt;p&gt;When the 5000 series cpus were released, I thought the 5900x would be
perfect. Unfortunately, like ever other tech release of 2020, it was nearly
impossible to buy one. I didn’t feel like waiting even longer, and I don’t
actually &lt;em&gt;need&lt;/em&gt; the increased performance. The new gains are particularly
observed in gamming… but I’m rocking an rx580 and am clearly gpu bottlenecked
anyway 😆. I can &lt;em&gt;manage&lt;/em&gt; just fine with slower single core speeds…&lt;/p&gt;
&lt;p&gt;With the 5900x not available anytime soon, I became tempted by some
crazier options… like the 3950x. The 3950x is an amazing cpu, and honestly
I’d still love to have one. However, even if I &lt;em&gt;could&lt;/em&gt; get it on sale… I
knew it would still cost a ton and I couldn’t justify the price difference. While a
16 core cpu is amazing, a 12 core still easily covers my needs. The money
&lt;em&gt;“saved”&lt;/em&gt; on the price difference could then be used to get the bigger ram
upgrade I wanted.&lt;/p&gt;
&lt;p&gt;So, I looked back at the 3900x and wondered if it would go on sale. It did
occasionally, but seemed like there might start to be shortage issues with
people jumping to &lt;em&gt;it&lt;/em&gt; after not being able to get a 5900x. The next time I
saw it go on sale, I snagged one just as they sold out again. I didn’t get it
for the cheapest it’s &lt;em&gt;ever&lt;/em&gt; been, but the sale was quite close to the all
time low. And it was &lt;em&gt;much&lt;/em&gt; cheaper than what it has remained at ever since.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; I planned to upgrade to a higher core count CPU. After
waiting, I wanted a 5900x, but you can’t buy them. After being tempted by the
3950x, I purchased the more reasonably priced 3900x on sale.&lt;/p&gt;
&lt;h3&gt;CPU Cooler&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/50OZ_0s-z4-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/50OZ_0s-z4-1200.jpeg&quot; alt=&quot;Noctua nh-u14s box&quot; width=&quot;1200&quot; height=&quot;1089&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;CPU Cooler upgrade: Noctua NH-U14s.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the summer I upgraded all the fans in my case to Noctua ones and love
them. At the time, I wanted to also get a new cpu cooler to match, but with plans
to upgrade my cpu soon, I figured I would wait and order both at the same time so
I wouldn’t have to go through the hassel of repasting the CPU twice.&lt;/p&gt;
&lt;p&gt;I switched what specific model I wanted once or twice, after being confused
about compatibility. I didn’t want the massive NH-D15, but did want a cooler
that used a 140mm fans instead of 120mm ones, because I much prefer their
lower sounding “woosh”. After I confirmed it should fit in the Meshify C and
with my RAM, I selected the NH-U14s.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; I wanted a new CPU Cooler with a 140mm fan. I love Noctua.&lt;/p&gt;
&lt;h3&gt;Motherboard&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/E8VWCVCF_7-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/E8VWCVCF_7-1200.jpeg&quot; alt=&quot;x570 Aorus Elite Wifi Box&quot; width=&quot;1200&quot; height=&quot;771&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Motherboard upgrade: x570 Aorus Elite Wifi.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Unlike the CPU and RAM, I never intended to get a new motherboard when
building Charmeleon. When I was looking at getting a 5000s cpu, I knew my
B450 thomahawk wouldn’t get the required bios upgrade until January… maybe.
I also knew the upgrade might not be reversable which I didn’t like, so I
started to look at B550 and x570 boards.&lt;/p&gt;
&lt;p&gt;I realized x570 fit my needs a bit better when compared to my b450, and even
the new b550 (which I think &lt;em&gt;most&lt;/em&gt; people should probably get). The x570
paired well with the other upgrades I was making. Particular features I liked
compared to the b450 tomahawk were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gen4 PCIe support&lt;/li&gt;
&lt;li&gt;Better iommu groupings for VFIO passthrough&lt;/li&gt;
&lt;li&gt;Often had a second m.2&lt;/li&gt;
&lt;li&gt;More &lt;em&gt;usable&lt;/em&gt; sata ports (2 of mine were disabled because they shared lanes with my m.2),&lt;/li&gt;
&lt;li&gt;wifi (mostly for bluetooth)&lt;/li&gt;
&lt;li&gt;In general, more pcie lanes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I looked at mother boards for months while waiting for Ryzen 5000 to
officially be announced. I decided that I wanted a x570 Gigabyte Aorus board, but
wasn’t sure which one. The Aorus Master seemed to be one of &lt;em&gt;the defacto
recommend&lt;/em&gt; boards for VFIO builds, but it was very expensive. Much of what made
it so good however for iommu seemed to still be present in the cheaper Aorus models. In
particular, the Aorus boards had great iommu groupings and support,
&lt;em&gt;and&lt;/em&gt; a bios feature that allows you select which x16 slot to use for the
primary gpu. This makes it easier to boot into a secondary gpu, saving the
beefier one to pass through to VMs.&lt;/p&gt;
&lt;p&gt;I went back and forth trying to decide between the elite wifi, pro, ultra…
and sometimes even the master, for weeks. When I started making
purchases, the elite wifi went on sale and I decided to grab it. It had
everything I needed, and most of the upgraded features on the other boards I
either wouldn’t use or were just small nice to haves.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Looking at Ryzen 5000 CPUs had me looking at newer motherboards.
Even after dropping back down to a 3000 CPU, I still wanted a motherboard
upgrade for better VFIO support and expandability.&lt;/p&gt;
&lt;h3&gt;GPU&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/dBlmSU6b2g-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/dBlmSU6b2g-1200.jpeg&quot; alt=&quot;rx550 box&quot; width=&quot;1200&quot; height=&quot;884&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Second GPU: RX 550 4GB.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With a motherboard that better supported vfio passthrough, I wanted to get a
smaller gpu that I could use for host machine’s graphics. I read that the rx550 and
1030gt were both good, sub $100 gpus often used for the host.&lt;/p&gt;
&lt;p&gt;I wasn’t thrilled about using a nvida gpu on a linux host, but I loved how
small it was (although that could translate more fan moise). In comparison,
the 550 seemed to be slightly more powerful, was amd, and well rated. In
fact, I had read several gpu passthrough guides using the rx550 and passing
through a rx580 (my current gpu). I purchased the 550.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; I wanted an easier time with VFIO GPU passthrough. Having a second
card for the host or a second VM makes this easier. The rx550 is a common card
for this use.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/AVJXbi__c4-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/selecting-charmeleons-upgrades/AVJXbi__c4-1200.jpeg&quot; alt=&quot;Inside charmeleon after upgrades&quot; width=&quot;1200&quot; height=&quot;976&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Charmeleon Internals after upgrades.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Overall, I am extremely happy with my selections. It’s surreal how powerful of a
machine can be built with a modest budget these days. With the upgrades,
Charmelon’s specs now stand at:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ryzen 9 3900x [3.8 GHz (4.6GHz Boost), 12 Cores, 24 Threads)
Noctua NH-U14s CPU Cooler
Aorus Elite Wifi x570 Motherboard
96 GB (2x16GB, 2x32GB) DDR4-3200 Mhz, CL 16 RAM
Sapphire Radeon Pulse RX 580 8GB GPU
Sapphire Radeon Pulse RX 550 4GB GPU
500 GB Samsung 970 EVO NVME SSD
500 GB Samsung 850 EVO SATA SSD
120 GB Kingston SSD (Dedicated windows VM)
EVGA SuperNOVA G4 650w 80+ Gold, fully modular PSU
Fractal Design Meshify C Dark TG ATX Mid Tower Case
2 x Noctua NF-A14 PWM 140mm Fans
1 x Noctua NF-F12 PWM 120mm Fan
Fedora 33 (KDE Plasma)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not only have these changes drastically improved the performance of my
workstation, but they have allowed me to run virtual machines at the next
level. When passing hardware directly to my VMs, I cannot tell that the
computer I’m sitting at is virtualized. I have been living inside Vms, and I
love it.&lt;/p&gt;
&lt;p&gt;That’s it! I still want to get a second nvme sdd, but other than that I think
upgrades to Charmeleon will slow down for a bit…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Opening Ports with firewall-cmd</title>
    <link href="https://ryan.himmelwright.net/post/firewall-cmd-open-ports/" />
    <updated>2020-11-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/firewall-cmd-open-ports/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/firewall-cmd-open-ports/gfiuRmYy4t-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/firewall-cmd-open-ports/gfiuRmYy4t-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Emerald Outback, Beech Mountain, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I use a handful containerized services on my workstation.
&lt;a href=&quot;https://jellyfin.org/&quot;&gt;Jellyfin&lt;/a&gt; and &lt;a href=&quot;https://github.com/itzg/docker-minecraft-server&quot;&gt;minecraft
servers&lt;/a&gt; are two examples.  Many of these
self-hosted applications require ports to be opened in order to work. However,
I often forget this. While I am getting better about remembering to &lt;em&gt;open&lt;/em&gt; the ports…
sometimes I forget &lt;em&gt;how&lt;/em&gt;. Not anymore.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;The Problem&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/firewall-cmd-open-ports/cy8uZ2SRBe-1126.webp 1126w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/firewall-cmd-open-ports/cy8uZ2SRBe-1126.jpeg&quot; alt=&quot;Firefox unable to connect to a page&quot; width=&quot;1126&quot; height=&quot;736&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Firefox unable to connect to my hugo page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To reiterate: The problem is simple. The services need a port opened, but my firewall is
blocking it. The solution is just as simple: &lt;s&gt;disable the firewall&lt;/s&gt; open the port.&lt;/p&gt;
&lt;h3&gt;Determine Zones&lt;/h3&gt;
&lt;p&gt;Before opening ports, lets first determine what &lt;a href=&quot;https://www.linuxjournal.com/content/understanding-firewalld-multi-zone-configurations&quot;&gt;firewalld
zone&lt;/a&gt;
to apply the change to. The following command will show all active
zones, and which devices are in each one:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd --get-active-zones&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additionally, I use the next command to help figure out what zone is
my default.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd --list-all&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is usually &lt;code&gt;public&lt;/code&gt;. The &lt;code&gt;--list-all&lt;/code&gt; command will also show which ports are already opened
in the &lt;code&gt;ports&lt;/code&gt; section, so use it to verify that they aren’t already opened.&lt;/p&gt;
&lt;h3&gt;Opening the Port&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/firewall-cmd-open-ports/WrjgJFd3l6-1149.webp 1149w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/firewall-cmd-open-ports/WrjgJFd3l6-1149.jpeg&quot; alt=&quot;Firefox connected to a page&quot; width=&quot;1149&quot; height=&quot;868&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Connected to Hugo after opening the port.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lets open that port. Change the port value in the following command to whatever
you need. I used &lt;code&gt;1313&lt;/code&gt; here to open a port for &lt;code&gt;hugo&lt;/code&gt;. Remember to also set
the zone to whatever was found in the previous step:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd &lt;span class=&quot;token parameter variable&quot;&gt;--zone&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;public --add-port&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1313&lt;/span&gt;/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;firewall-cmd –-list-ports&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, try connecting to the service again. If it still does not work, it is
possible that additional or different ports may need to be opened.&lt;/p&gt;
&lt;h3&gt;Make it Persistant&lt;/h3&gt;
&lt;p&gt;If everything &lt;em&gt;does&lt;/em&gt; work, the change can be made persistent by running the same command
again, but this time using the &lt;code&gt;--permanent&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd &lt;span class=&quot;token parameter variable&quot;&gt;--zone&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;public &lt;span class=&quot;token parameter variable&quot;&gt;--permanent&lt;/span&gt; --add-port&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1313&lt;/span&gt;/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Reload&lt;/h3&gt;
&lt;p&gt;When running the command with the &lt;code&gt;--permanent&lt;/code&gt; flag from the start, the changes
might not take affect until firewalld is reloaded. This command should apply the
changes:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd &lt;span class=&quot;token parameter variable&quot;&gt;--reload&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That’s it. It’s a short post, but it’s one I will use. I am trying not rage
&lt;code&gt;sudo systemctl stop firewalld&lt;/code&gt; anymore. This should help with that.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My new Ergodox-EZ Keyboard -- Initial Thoughts</title>
    <link href="https://ryan.himmelwright.net/post/ergodox-ez-initial-review/" />
    <updated>2020-11-27T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/ergodox-ez-initial-review/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/Bc9HpElhcd-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/Bc9HpElhcd-1024.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1024&quot; height=&quot;768&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Several weeks ago, I wrote about how I &lt;a href=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/&quot;&gt;decided to purchase an
ergodox-ez&lt;/a&gt; split keyboard. In that
post, I omitted any details about what configuration I ordered,
as well my impressions about the board. Now that I have been an
ergodox owner for… wow, over two months… I guess this post is
due. Here are my ‘&lt;em&gt;initial&lt;/em&gt;’ thoughts of the ergodox-ez…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Ordering and shipping&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/4SnP_mTUwD-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/4SnP_mTUwD-1200.jpeg&quot; alt=&quot;The Boxes my ergdox came in&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The boxes the ergodox-ez shipped in.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So, what ergodox did I order? After a few hours toggling the
configuration tool and endless internal debating, I eventually settled on an
ergodox-ez &lt;em&gt;glow&lt;/em&gt; (backlit keys), with a black body, and black printed, LED
compatible, key caps.  With a narrowed selection of switch options (to maintain
backlit key compatibility), I landed on Cherry MX Browns as my best choice. I
wanted a noticeable tactile bump, but didn’t want “click-y” keys. I prefer a
lower “&lt;em&gt;Clack&lt;/em&gt;”, while typing and find the higher pitch of MX Blues annoying.
I decided that I might as well go all in with this keyboard, so I ordered the
tenting poles and black wrist rests as well.&lt;/p&gt;
&lt;p&gt;With everything decided, I placed my order and prepared myself to wait
in anticipated glee for… an average of 3 weeks shipping time ಠ_ಠ. &lt;em&gt;Luckily&lt;/em&gt;,
I was fortunate and about a week later, I had a large, flat box show up at my
door!&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/2B2X_uJ5jN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/2B2X_uJ5jN-1200.jpeg&quot; alt=&quot;Opened the ergodox box&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Opening the main box.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Everything was very well packaged. The keyboard came in one box, and the wrist
rests had their own. The two boxes had the same width, so they could be shipped
as one unit. Lifting the lid on the larger box, I was greeted by the keyboard
looking back at me through a plastic panel, with it’s accessories (cables, key
switch puller, screws) neatly pinned around it.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;The initial &lt;em&gt;physical&lt;/em&gt; setup was quite simple. I took everything out of the
box, connected the two halves of the board together with the included TRRS
cable, and then used the mini usb cable (also included) to connect right half
of the board to my laptop’s usb port. I positioned the wrist-rests and tweaked
the tenting poles to some arbitrary position, acting as if I knew what I was
doing.  That was it. After that point, I was able to type… &lt;em&gt;stuff&lt;/em&gt;… into my
computer.&lt;/p&gt;
&lt;h3&gt;Initial Layout Changes&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/HFZK5BAMuZ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/HFZK5BAMuZ-1200.jpeg&quot; alt=&quot;The ergodox default layout&quot; width=&quot;1200&quot; height=&quot;861&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The default ergodox layout.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I say &lt;em&gt;stuff&lt;/em&gt;, because I wasn’t able to just pick up the board
and feel 100% comfortable like one might with a “normal”
keyboard. There was definitely a learning curve (more on that later).  However,
the main problem was that I didn’t even know the &lt;em&gt;default layout&lt;/em&gt;. The
back-lit QWERTY keys were labeled, but all of the modifier keys on the sides
and in the thumb clusters were not. So, I navigated online to the &lt;a href=&quot;https://ergodox-ez.com/pages/oryx&quot;&gt;oryx
configurator&lt;/a&gt; with two goals in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To learn the standard layout that the board shipped with, and&lt;/li&gt;
&lt;li&gt;To alter some of the defaults with changes I &lt;em&gt;knew&lt;/em&gt; I would want (caps lock as a ctrl, for example)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After a few minutes of scanning and tweaking, I had my first layout to flash.
I downloaded the file… and ran into my only &lt;em&gt;true&lt;/em&gt; issue I’ve experienced with the
ergodox…&lt;/p&gt;
&lt;h3&gt;Issues with Wally&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/K8dnoBDSUF-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/K8dnoBDSUF-1200.jpeg&quot; alt=&quot;Using the ergodox with my Macbook Pro&quot; width=&quot;1200&quot; height=&quot;954&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using the ergodox-ez with my MacBook Pro.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While I was able to flash the firmware using my macbook, for some reason my
Fedora laptops and desktop had issues at first. I would open up
&lt;a href=&quot;https://ergodox-ez.com/pages/wally&quot;&gt;wally&lt;/a&gt; (ZSA’s firmware flashing tool), load the
firmware file, and press the tiny ‘reset’ button on my keyboard with a
paper-clip… and it would freeze. The back lights stopped their colorful
dancing and key presses rendered no response. The only way
to get the board to respond again was to unplug and replug it back in.&lt;/p&gt;
&lt;p&gt;I had heard that ZSA had good customer support, so I emailed them about my problem.
They responded right away and helped to debug what might be happening.  It
appeared to be some sort of permissions issue, although to this day I’m not
100% sure what it actually was. Eventually, it started working. Since then I
have updated most of my computers to Fedora 33, and I use a newer version of
Wally. I haven’t had trouble since, even on fresh OS installs. It just works.&lt;/p&gt;
&lt;p&gt;Oh well, problem resolved I guess? My main takeaways from the experience were
that 1) wally definitely requires &lt;code&gt;sudo&lt;/code&gt; root permissions, and 2) that ZSA’s customer
support is wonderful.  Oh, and &lt;a href=&quot;https://github.com/zsa/wally&quot;&gt;open source
firmware&lt;/a&gt; is the best, but I already knew that.&lt;/p&gt;
&lt;p&gt;(Note: ZSA has since updated the Linux README instructions to contain the udev
instructions they gave me)&lt;/p&gt;
&lt;h2&gt;Getting used to and learning the board&lt;/h2&gt;
&lt;p&gt;As previously stated, I knew I would have to put some effort into learning how
to type on the ergodox. When I got it, I did a fair bit of
&lt;a href=&quot;https://www.keybr.com&quot;&gt;typing&lt;/a&gt; &lt;a href=&quot;https://play.typeracer.com/&quot;&gt;practice&lt;/a&gt; and
used it for work everyday. Within a few days, I was at a passable typing speed,
and after a few more, and I reached a &lt;em&gt;decent&lt;/em&gt; speed. I am not sure if I am any
&lt;em&gt;faster&lt;/em&gt; than on my old keyboard, but I can type fast for much longer (I
could only burst speed-type on the HHKB before starting to cramp up, mostly due to
poor form).&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/C9J3xBLfbC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/C9J3xBLfbC-1200.jpeg&quot; alt=&quot;Oryx Training Tool&quot; width=&quot;1200&quot; height=&quot;1025&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Oryx Training Tool.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I haven’t done any typing practice the last few weeks and I’ve started to
notice &lt;em&gt;some&lt;/em&gt; of my bad habits creeping back in. I want to start up
again and might try to use the trainer that is included in the Oryx tool. It is
able to connect to the keyboard to show your layout, and provides several types
of content to type through (code, books, symbols). I might use it to
help determine an optimized coding layer places my most used symbols in easy to
reach places.&lt;/p&gt;
&lt;h2&gt;What I like&lt;/h2&gt;
&lt;h3&gt;Customizability&lt;/h3&gt;
&lt;p&gt;This board is fully customizable on both the hardware &lt;em&gt;and&lt;/em&gt; software side.&lt;/p&gt;
&lt;h3&gt;Firmware&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/hH5GeTNpMo-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/hH5GeTNpMo-1200.jpeg&quot; alt=&quot;Ergodox layout&quot; width=&quot;1200&quot; height=&quot;634&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;One of my custom layer layouts.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Being able to tweak the layout of multiple layers means there are an endless
amount of ways I can tailor my keyboard to fit my needs. My tweaking has
slowed down a bit, but I love that I can make quick changes to my layout if I
decide there is a better way to do something. For example, today I realized I wanted to add
a key on one of my alternate layers, so I popped open oryx and wally, and had
the change finished in about a minute.&lt;/p&gt;
&lt;p&gt;Having an open firmware keyboard is reminiscent of when I &lt;a href=&quot;https://ryan.himmelwright.net/post/back-on-arch/&quot;&gt;used vanilla arch
linux&lt;/a&gt;. I would go weeks
assuming I had everything required for a typical desktop environment, when all
of sudden, I would try to open a pdf and realize I never installed a pdf
reader… or a &lt;em&gt;file browser&lt;/em&gt;. Last week while working, I noticed that
my ergodox layout didn’t have a ‘delete’ key. So I quickly added one. Done.&lt;/p&gt;
&lt;p&gt;(Note: Be aware that I haven’t even touched on the advanced layout options,
like macros and long vs short key presses)&lt;/p&gt;
&lt;h3&gt;Hardware&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/KpIl6pRw3--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/KpIl6pRw3--1200.jpeg&quot; alt=&quot;Key switches&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Testing swapping out a switch with one from my switch tester.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In addition to the firmware, It’s nice to know that there is quite a bit I can
&lt;em&gt;physically&lt;/em&gt; change about my keyboard. Specifically, the key caps can be
changed and the switches themselves are hot-swappable.&lt;/p&gt;
&lt;p&gt;I’ve already looked around for some of the parts I could swap out on my board
in a few years if I want to mix things up. For example, I am interesting in
swapping to mx clear switches someday. The problem with clears is the lights
would not (ironically) shine through anymore. I guess that wouldn’t be too big
of an issue though, because many of the alternative key caps I like are opaque
ツ.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Anyway&lt;/em&gt;… Despite talking about all the hardware upgrades I &lt;em&gt;could&lt;/em&gt; make, I
actually do enjoy it’s current configuration. But I do love that I have the
option to change it over time.&lt;/p&gt;
&lt;h3&gt;Ortholinear&lt;/h3&gt;
&lt;p&gt;While it was more difficult to get used to than I expected, I have found using
an orthlinear layout to be considerably easier to type on. My fingers no longer
fight and trip over each other like Black-Friday shoppers trying to grab the
last hot-ticket item. The &lt;em&gt;worst&lt;/em&gt; thing about using an ortholinear layout is
that I now want it to become a standard option when buying laptops: ISO or ANSI,
Ortho or Staggard? Unfortunately I don’t see that happening anytime soon…&lt;/p&gt;
&lt;h3&gt;Split Design&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/AgYZ3F7tIy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/AgYZ3F7tIy-1200.jpeg&quot; alt=&quot;Ergodox around a laptop stand&quot; width=&quot;1200&quot; height=&quot;896&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A split design keyboard pairs nicely with a laptop stand.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Like the ortholinear layout, a fully split keyboard is a considerable
ergonomic boost.  I can lay my arms on the desk in whichever way is most
comfortable… and then position the keyboard to match &lt;em&gt;them&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As a bonus, the split design pairs nicely with laptop stands, allowing me to
slide the stand closer. This makes it easier to view smaller screens at a
proper height.&lt;/p&gt;
&lt;h3&gt;Thumb cluster keys&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/1zsNSZ2xZj-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/1zsNSZ2xZj-1200.jpeg&quot; alt=&quot;ergodox on wood desk&quot; width=&quot;1200&quot; height=&quot;711&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The thumb cluster of keys has allowed me to better utilize my thumbs, which has been a nice change.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Another feature I immediately miss on other keyboards, is thumb keys. It is a
relief to provide my thumbs control over more keys than just the spacebar. The
thumb clusters enable me to offload responsibility for backspace, tab, enter,
delete, and a few other keys from my weakest fingers (pinkies), to my strongest
(thumbs). This makes a huge difference while typing, but is probably the
biggest difficulty I have when switching to a laptop keyboard from my ergodox.
I keep finding myself smashing the spacebar when trying to backspace or enter.&lt;/p&gt;
&lt;h2&gt;What I don’t love&lt;/h2&gt;
&lt;p&gt;Honestly, there is not much that I don’t &lt;em&gt;like&lt;/em&gt; about the ergodox-ez. That
being said, if forced to be picky, here is a thing or two that I don’t &lt;em&gt;love&lt;/em&gt;
about it…&lt;/p&gt;
&lt;h3&gt;Learning curve&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/GzOF5bw2SQ-654.webp 654w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/GzOF5bw2SQ-654.jpeg&quot; alt=&quot;typing test results&quot; width=&quot;654&quot; height=&quot;287&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Results from one of my typing practices.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The biggest con to getting a keyboard like the ergodox is the learning curve.
It isn’t something that you will be able to pick up and immediately start
using.  It will require time and deliberate daily practice to really start to
feel comfortable. If you know this when purchasing, it isn’t that big of a
problem, but can be extremely frustrating at first.&lt;/p&gt;
&lt;h3&gt;Cost&lt;/h3&gt;
&lt;p&gt;It is expensive. Especially for a unique board that you might end up hating.
With that said, if the ergodox works for you, the cost isn’t as bad as it might
initially appear.  The ergodox-ez is a solid board, with the ability to
customize both it’s hardware and firmware over a span of many years. On top of
that, when compared to other ‘ergonomic’ split keyboards, the ergodox is priced
rather fairly.&lt;/p&gt;
&lt;h3&gt;It’s difficult to switch back to my HHKB…&lt;/h3&gt;
&lt;p&gt;Switching to keyboards on my laptops is mostly fine, but for whatever
reason … I can’t type on my HHKB. It feels so squished, and I can’t seem to
naturally position my fingers on the correct keys anymore. I’m sure if I
dedicate a few hours to using the HHKB this might go away, but I haven’t done
that yet.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/65KE2lzl6r-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ergodox-ez-initial-review/65KE2lzl6r-1200.jpeg&quot; alt=&quot;The ergodox in my home office&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The ergodox-ez in my home office.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Overall, I love this keyboard and am very satisfied with my choice. Now that I am used
to it, it feels normal to type on and I don’t have to think about it.
The Split design and ortholinear layout make it the most natural feeling
and comfortable board I have used. As a bonus, any layout issues can be stomped
out with a 2 minute config change and flash, ensuring that I should &lt;em&gt;continue&lt;/em&gt;
to love this board even as my preferences change. As long as you are aware of
what you are getting into, I highly recommend the ergodox-ez.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Automated Nativefier App builds</title>
    <link href="https://ryan.himmelwright.net/post/automated-nativefier-app-builds/" />
    <updated>2020-10-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/automated-nativefier-app-builds/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/oVGbKn3wka-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/oVGbKn3wka-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Kill Devil Hills, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Most applications we use today are fancy web pages, wrapped up in a desktop
shell. Many people even forgo desktop builds, instead opting to run webapps
simply as another tab in their web browser (Ex: Slack,
Discord, Notion). Personally, I prefer to have dedicated windows opened for my
essential tools. As a result, I love using
&lt;a href=&quot;https://github.com/jiahaog/nativefier&quot;&gt;nativefier&lt;/a&gt; to create desktop versions
of my favorite web-based applicatons. The only problem is… it can be a pain to
setup. Lets fix that.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What Apps I use nativefier it for&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/IB6DBzP-G3-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/IB6DBzP-G3-1200.jpeg&quot; alt=&quot;Using Pocketcasts as a nativefier build&quot; width=&quot;1200&quot; height=&quot;931&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using the Pocketcasts web app in a desktop window via nativefier&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I use nativefier for all of the web services that are missing a Linux client,
or web tools that I use often and want a dedicated desktop client for. On most days,
I use the following tools via a nativefier desktop build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pocket&lt;/strong&gt; - to read articles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pocketcasts&lt;/strong&gt; - to listend to synced podcasts between my phone and computer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fastmail&lt;/strong&gt; - For my personal email and calendar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;homeassistant&lt;/strong&gt; - To control parts of my house (mostly lights and temp)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notion&lt;/strong&gt; - My &lt;a href=&quot;https://ryan.himmelwright.net/post/trying-notion/&quot;&gt;notes and planning tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Soundcloud&lt;/strong&gt; - For music while working&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, while not used every day, I have made nativefier builds for &lt;strong&gt;twitch&lt;/strong&gt; and
&lt;strong&gt;icloud&lt;/strong&gt;.  These are both apps that exist on other desktops but don’t have an
official Linux build.&lt;/p&gt;
&lt;h2&gt;How I make them &lt;em&gt;feel&lt;/em&gt; like normal apps&lt;/h2&gt;
&lt;p&gt;There are a few steps I take to help make these builds &lt;em&gt;feel&lt;/em&gt; more like native
ones.&lt;/p&gt;
&lt;h3&gt;Desktop Files&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/KnIZ7PjxrF-561.webp 561w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/KnIZ7PjxrF-561.jpeg&quot; alt=&quot;The builds show up as normal applications&quot; width=&quot;561&quot; height=&quot;360&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Pocket and Pocketcasts builds showing as normal applications in search&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first, and argualy most important step I take, is creating a &lt;a href=&quot;https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html&quot;&gt;desktop
entity&lt;/a&gt;
for each nativefier build. A desktop entity defines an application
in Gnome and KDE Plasma desktop environments, allowing it to show up in menus and
launchers. Defining an entity for the nativefier builds tells the system to to
treat the build as a normal application, which is what we want.&lt;/p&gt;
&lt;h3&gt;Icons&lt;/h3&gt;
&lt;p&gt;The second action I’ve started doing is making sure that I use proper icons
when I &lt;em&gt;run&lt;/em&gt; nativefier. I already needed the icon for creating the desktop
entity, but I noticed the icon displayed in the running app often didn’t match
the one defined in the desktop file. This extra step resolves this issue and
helps make everything look more cohesive.&lt;/p&gt;
&lt;h2&gt;How I &lt;em&gt;usually&lt;/em&gt; create nativefier builds&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/0gyCYggCkN-796.webp 796w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/0gyCYggCkN-796.jpeg&quot; alt=&quot;Coping to a new computer often requires editing all the application files&quot; width=&quot;796&quot; height=&quot;531&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Coping to a new computer often requires editing all the application files&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While these additional steps help nativefier builds behave like real desktop applications,
it adds a bunch extra work to the creation process. When I want to make a new
nativefier app, I usually have to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install nativefier and it’s dependencies (usually in a podman container)&lt;/li&gt;
&lt;li&gt;Build the application to a build directory&lt;/li&gt;
&lt;li&gt;Find an icon for the application and add it to my &lt;code&gt;~/.local/share/icons/&lt;/code&gt;
folder&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;.desktop&lt;/code&gt; file for the application, which involves:
&lt;ul&gt;
&lt;li&gt;Filling out the description&lt;/li&gt;
&lt;li&gt;Changing the exec paths.&lt;/li&gt;
&lt;li&gt;Adding the icon path&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Verify it works (something is usually missing)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To save some time when setting up a new system, I often copy the build
directories along with the desktop and icon files. However, even that can be
quite tedious. For example, if the new system uses a different username (&lt;code&gt;ryan&lt;/code&gt; vs.
&lt;code&gt;rhimmew&lt;/code&gt;), I have to update all of the desktop files to reflect the change.&lt;/p&gt;
&lt;p&gt;We can do better. Lets automate it.&lt;/p&gt;
&lt;h2&gt;Using a podman container instead&lt;/h2&gt;
&lt;p&gt;Before diving into the automating the steps, there is one improvement
to my normal process I want to make. Instead of installing nativefier from a
&lt;code&gt;npm&lt;/code&gt; install, I want to switch to using an ephemeral nativefier
container for the builds. This has several benefits, including not having to install
dependencies, and always using the latest version for the automation. Plus…
containers are fun.&lt;/p&gt;
&lt;p&gt;After tweaking the suggested &lt;a href=&quot;https://github.com/jiahaog/nativefier#usage-with-docker&quot;&gt;docker
instructions&lt;/a&gt;, I was
able to get a nativefier container building apps on my desktop using podman:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Pull the Image&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; pull jiahaog/nativefier

&lt;span class=&quot;token comment&quot;&gt;# Run a build for a container&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; ICONS_DIR src &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; NATIVEFIER_BUILD_DIR target jiahaog/nativefier &lt;span class=&quot;token parameter variable&quot;&gt;--icon&lt;/span&gt; /src/ICON &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; NAME &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; linux &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; x64 URL /target&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command had a few parameters to swap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ICONS_DIR&lt;/code&gt;: the directory that contains the application icons to build
with.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NATIVEFIER_BUILD_DIR&lt;/code&gt;: the directory to save the application builds to&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ICON&lt;/code&gt;: The filename of the image to use for the icon&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NAME&lt;/code&gt;: The name of the application (ex: &lt;code&gt;pocket&lt;/code&gt; or &lt;code&gt;homeassistant&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;URL&lt;/code&gt;: The &lt;em&gt;url&lt;/em&gt; of the webapp to build&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This command spins up a podman container, passes our variables through, and builds a
nativefier app. After finishing, the container automatically deletes itself.&lt;/p&gt;
&lt;h2&gt;Automating the process&lt;/h2&gt;
&lt;p&gt;Now that we’ve figured out how to run nativefier with podman, we can focus on
writing the automation. Like I have &lt;a href=&quot;https://ryan.himmelwright.net/post/foundryvtt-service-ansible-role/&quot;&gt;done
previously&lt;/a&gt;, I will be implementing the
automation by creating a new role to use in my ansible playbooks. If you are
unfamilar with roles, checkout the &lt;a href=&quot;https://ryan.himmelwright.net/post/ansible-quickstart/&quot;&gt;ansible quickstart
post&lt;/a&gt; I wrote earlier this year.&lt;/p&gt;
&lt;p&gt;To start, I’ll create a folder for the new role, with &lt;code&gt;tasks&lt;/code&gt;, &lt;code&gt;defaults&lt;/code&gt;, &lt;code&gt;files&lt;/code&gt;, and
&lt;code&gt;templates&lt;/code&gt; sub directories.&lt;/p&gt;
&lt;h3&gt;Vars&lt;/h3&gt;
&lt;p&gt;First, lets define default values for the variables we will use in tasks and template files:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# User to run as&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;nativefier_build_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/tmp/&quot;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# Location to build apps at&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;nativefier_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/home//Builds/&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Location to move completed builds&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;icons_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/home//.local/share/icons/&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# App Icon location&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;applications_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/home//.local/share/applications/&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# desktop entry location&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will define one more variable which won’t have a default, &lt;code&gt;nativefier_apps&lt;/code&gt;. However, I’ll
talk about that later when we use the role in a playbook.&lt;/p&gt;
&lt;h3&gt;Templates&lt;/h3&gt;
&lt;p&gt;Next, lets create the one template file we need for this role: an
&lt;code&gt;application.desktop&lt;/code&gt; file. Create a new file in the &lt;code&gt;templates&lt;/code&gt; directory
named &lt;code&gt;nativefier.desktop.j2&lt;/code&gt; and paste in the following contents:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Desktop Entry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;Nativefier app for&lt;/span&gt; 
&lt;span class=&quot;token key attr-name&quot;&gt;Exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/-linux-x64/&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Terminal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;Application&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Icon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Categories&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;Multimedia;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;TryExec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/-linux-x64/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This template will take the variables for each nativefier app we define in the
playbook, and use them to fill out a desktop file for each app.&lt;/p&gt;
&lt;h3&gt;Files&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/Ho40P2znLo-713.webp 713w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/Ho40P2znLo-713.jpeg&quot; alt=&quot;The icons folder&quot; width=&quot;713&quot; height=&quot;515&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The role&#39;s files folder, containing the app icons&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Before writing tasks, there is one more role directory to fill: &lt;code&gt;files&lt;/code&gt;.  We
want to include the icon files for each application in this role. So,  fill the
&lt;code&gt;files&lt;/code&gt; sub-directory with the icon files to use.&lt;/p&gt;
&lt;h3&gt;Tasks&lt;/h3&gt;
&lt;p&gt;Time to write the tasks. Create and open up a &lt;code&gt;main.yml&lt;/code&gt; file in the
tasks sub-directory.&lt;/p&gt;
&lt;p&gt;The first few tasks will check and ensure that the directories we intend to use
exist. This is generally a good practice to prevent playbooks from breaking due
to missing folders:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Ensure Icon dir exists&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; directory

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Ensure applications dir exists&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; directory

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Ensure  exists&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; directory

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Ensure  exists&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; directory&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Managing Icon Files&lt;/h3&gt;
&lt;p&gt;Next, add a small task that will copy the icons we included with the role,
to the user’s local icon folder:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Move icons to local folder
  &lt;span class=&quot;token key atrule&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; src=&quot;&quot; dest=&quot;/&quot;
  &lt;span class=&quot;token key atrule&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will ensure that all the icons are already in place when we build both the
nativefier app and the &lt;code&gt;.deskop&lt;/code&gt; files.&lt;/p&gt;
&lt;h3&gt;Automating the podman builds&lt;/h3&gt;
&lt;p&gt;We can now define a few tasks that will pull and run the nativefier container
using podman. &lt;em&gt;(Note: This could be done with docker… but I prefer podman XD)&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Pull down nativefier container image
  &lt;span class=&quot;token key atrule&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;podman pull jiahaog/nativefier&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build Nativefier app via podman
  &lt;span class=&quot;token key atrule&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;podman run --rm --security-opt label=disable -v :/src -v :/target jiahaog/nativefier --icon /src/ --name  -p linux -a x64  /target&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Clean out nativefier dir
  &lt;span class=&quot;token key atrule&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/-linux-x64&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absent
  &lt;span class=&quot;token key atrule&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# I don&#39;t love this, but copy was too slow&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Move Builds to Nativefier Location
  &lt;span class=&quot;token key atrule&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;True&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mv /-linux-x64 /-linux-x64&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These tasks also move the built app to the final location.&lt;/p&gt;
&lt;h3&gt;Manaing Application Files&lt;/h3&gt;
&lt;p&gt;Lastly, with icons in place and the application builds complete, we can create the application
entry files by adding this final task:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Generate Application Desktop Files
  &lt;span class=&quot;token key atrule&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; templates/nativefier.desktop.j2
    &lt;span class=&quot;token key atrule&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/.desktop&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there we go, that’s it! Well… sort of.&lt;/p&gt;
&lt;h2&gt;Selinux woes&lt;/h2&gt;
&lt;p&gt;While this ‘&lt;em&gt;worked on my computer&lt;/em&gt;’, when I tested it on my laptop and
in some VMs… it failed.&lt;/p&gt;
&lt;h3&gt;Issues&lt;/h3&gt;
&lt;p&gt;Every time I ran the playbook, I hit this error:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Error during build. Run with &lt;span class=&quot;token parameter variable&quot;&gt;--verbose&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; details. &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Error: EACCES: permission denied, &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/target/linux-x64-template&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  errno: -13,
  code: &lt;span class=&quot;token string&quot;&gt;&#39;EACCES&#39;&lt;/span&gt;,
  syscall: &lt;span class=&quot;token string&quot;&gt;&#39;mkdir&#39;&lt;/span&gt;,
  path: &lt;span class=&quot;token string&quot;&gt;&#39;/target/linux-x64-template&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was able to tell the error occurred while running the podman container. I
assumed it might be &lt;code&gt;selinux&lt;/code&gt; related, but was unable to sort out a solution
right away.&lt;/p&gt;
&lt;h3&gt;The Fix&lt;/h3&gt;
&lt;p&gt;Eventually after browsing the internet, I learned that my easiest solution was
to add the option &lt;code&gt;--security-opt label=disable&lt;/code&gt; to my &lt;code&gt;podman run&lt;/code&gt; command, to
turn off label separation for the container. I’m sure there is a better, more
secure soltion I could do. However, I figured this was at least a good compromise of
being easy to implement, but more secure than the common (and wrong) suggestion
of “&lt;em&gt;just disable selinux&lt;/em&gt;”.&lt;/p&gt;
&lt;h2&gt;One last fix…&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;drwxr-xr-x. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100999&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100999&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;560&lt;/span&gt; Oct &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;:40 jellyfin-linux-x64&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, I wanted to add one more cleanup task. My builds had the ugly uid/gid
pair of &lt;code&gt;100999 100999&lt;/code&gt;, so I a task to change ownership to the &lt;code&gt;user&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Change Permissons of Nativefier Dirs
  &lt;span class=&quot;token key atrule&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;True&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/-linux-x64&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that change:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;drwxr-xr-x. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; ryan ryan  &lt;span class=&quot;token number&quot;&gt;560&lt;/span&gt; Oct &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;:40 jellyfin-linux-x64&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Much better!&lt;/p&gt;
&lt;h2&gt;Example: adding it to my playbooks&lt;/h2&gt;
&lt;p&gt;With the role complete, it is &lt;em&gt;finally&lt;/em&gt; time to add it to a playbook. I define
playbooks to provision all of my machines, so I will just add it to them. First
make sure the role is added to the list of roles used by the playbook.  For
example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  roles:
    - apps/nativefier
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, define a new var named &lt;code&gt;nativefier_apps&lt;/code&gt;. This variable is a list of
dictionaries, with each dictionary providing the values for a different
nativefier application. Each nativefier build requires three variables to be
defined:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: The name of the application&lt;/li&gt;
&lt;li&gt;&lt;code&gt;icon&lt;/code&gt;: The filename (including ext) of the icon file to for the application&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;: The address for the webpage to build as a nativefier app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, to build my &lt;code&gt;pocket&lt;/code&gt;, &lt;code&gt;fastmail&lt;/code&gt;, and &lt;code&gt;homeassistant&lt;/code&gt; apps, I added the
following to my playbook:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;nativefier_apps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pocket
    &lt;span class=&quot;token key atrule&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pocket.png
    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://app.getpocket.com&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fastmail
    &lt;span class=&quot;token key atrule&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fastmail.png
    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://www.fastmail.com&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; homeassistant
    &lt;span class=&quot;token key atrule&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; homeassistant.png
    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://homeassistant.local:8123&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should be it! Afterwards, my playbooks could build and configure all
my nativefier applications automatically!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/pcpKYe2xzf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/automated-nativefier-app-builds/pcpKYe2xzf-1200.jpeg&quot; alt=&quot;My desktop covered in nativefier apps&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My desktop covered in some of my nativefier apps.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have wanted to create this role for a very long time and am glad I finally
did. Nativefier is such an amazing tool, and paring it with podman and ansible
has somehow managed to make it shine even more. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Decided to get an Ergodox</title>
    <link href="https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/" />
    <updated>2020-10-20T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/jc0dHcXIMp-1029.webp 1029w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/jc0dHcXIMp-1029.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1029&quot; height=&quot;764&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For the past month, I have been learning to type… on my new keyboard!  I
decided to purchase an &lt;a href=&quot;https://ergodox-ez.com&quot;&gt;ergodox-ex&lt;/a&gt; and have been using
it ever since. I still &lt;em&gt;love&lt;/em&gt; &lt;a href=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/&quot;&gt;my HHKB&lt;/a&gt;, so why did I get
a new keyboard? And why… the ergodox?&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Previous Keyboard&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/i8x_CSq7CN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/i8x_CSq7CN-1200.jpeg&quot; alt=&quot;Getting the HHKB Keyboard&quot; width=&quot;1200&quot; height=&quot;1200&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Happy Hacking Pro Keyboard&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For a few years, I have been gladly using a
&lt;a href=&quot;https://www.hhkeyboard.com/uk/products/pro2&quot;&gt;Happy Hacking Pro 2&lt;/a&gt; keyboard.  Before that, I was
on a &lt;a href=&quot;https://deskthority.net/wiki/HHKB_Lite&quot;&gt;Happy Hacking Lite&lt;/a&gt;, which I
started using while in college. I love the layout of HHK and still think it’s
an amazing keyboard. The years I spent on HKBs are why I think &lt;em&gt;every&lt;/em&gt; keyboard
should have &lt;code&gt;ctrl&lt;/code&gt; instead of a &lt;code&gt;caps&lt;/code&gt; key, or at least switch to two.&lt;/p&gt;
&lt;h2&gt;Rationale for a new one&lt;/h2&gt;
&lt;p&gt;While I love my HHKB and have no &lt;em&gt;real&lt;/em&gt; complaints, there were a few things that
pushed me to start looking at new boards again.&lt;/p&gt;
&lt;h3&gt;I was getting the itch to &lt;em&gt;build&lt;/em&gt; one&lt;/h3&gt;
&lt;p&gt;My initial motivation was the slow, growing itch to build a mechanical
keyboard.  It is an itch that I’ve felt before. I purchased my HHKB Pro as a
&lt;em&gt;backup&lt;/em&gt; plan when the order for a
&lt;a href=&quot;https://olkb.com/collections/planck&quot;&gt;planck&lt;/a&gt; keyboard kit via  &lt;a href=&quot;http://mass.com&quot;&gt;mass.com&lt;/a&gt; was
unfortunately cancelled. My &lt;em&gt;original&lt;/em&gt; intention was to learn how to solder (I
even brought a soldering iron), and build the keyboard from scratch. Ultimately buying the
HHKB, I never got to scratch that itch. While I &lt;em&gt;technically&lt;/em&gt; still haven’t…
this is what had me looking at keyboards and browsing
&lt;a href=&quot;https://www.reddit.com/r/MechanicalKeyboards/&quot;&gt;/r/MechanicalKeyboards&lt;/a&gt; again in the
first place…&lt;/p&gt;
&lt;h3&gt;Wanted a program-able board&lt;/h3&gt;
&lt;p&gt;More important than &lt;em&gt;physically&lt;/em&gt; building a keyboard, I wanted to be able to
&lt;em&gt;programatically&lt;/em&gt; build it. That is to say, play with custom firmware. This is
something that my HHKB doesn’t have, but the plank did. The pok3r, which I’ve
considered in the past, also has programmable layers. Even some of &lt;a href=&quot;https://system76.com/laptops&quot;&gt;System76’s
laptops&lt;/a&gt; now allow the ability to remap their
keyboards using open firmware (which is awesome!). The &lt;em&gt;biggest&lt;/em&gt; thing I wanted
from a new keyboard was the ability to customize and program the layers and
layouts.&lt;/p&gt;
&lt;h3&gt;After starting to learn proper touch typing, I wanted to try an ortholinear layout&lt;/h3&gt;
&lt;p&gt;Working in mostly the same space for much of this year, I’ve noticed that my
wrists and arms have really started to tighten up. So, I put my foot down
and decided it was time once again to try and force myself to &lt;em&gt;proper&lt;/em&gt;
touch type… using all of my fingers. Normally, I slide my hands across the board
and have just a few fingers do the bulk of the work.  This not only creates
unnecessary movement, but also over-strains parts of my hands, building up tension
in my wrists.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/4cORUomtgf-585.webp 585w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/4cORUomtgf-585.jpeg&quot; alt=&quot;Proper finger key map for touch typing on standard layout&quot; width=&quot;585&quot; height=&quot;410&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;&#39;Proper&#39; finger key map for touch typing on a standard layout&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As I started typing with proper finger use, I noticed my hands &lt;em&gt;really&lt;/em&gt; have a
hard time hitting some keys. Particularly the bottom row, especially with my
left hand, gave me trouble. My other fingers commonly blocked the path that the
“correct” finger needed to reach a key. It became obvious why I naturally
adapted to sliding across the board over the years. I wondered if an
ortholinear layout would better allow me to touch type properly, using all of
my fingers. My hope was that the straight grid would let my fingers
slide directly up and down to the other rows, without crossing over and
impeding my other fingers.&lt;/p&gt;
&lt;h3&gt;My shoulder pain increased&lt;/h3&gt;
&lt;p&gt;The last straw, promoting my keyboard urgency to something I &lt;em&gt;needed&lt;/em&gt; to order
&lt;em&gt;now&lt;/em&gt;, was that my shoulder was beginning to hurt… all the time. During my
search, I noticed split keyboards and thought they might
help to alleviate some of my pain. The ability to type with my arms
positioned at any width would at least allow me to vary my shoulder angle as
it got sore. So, I switched from looking at DIY kits, to pre-built split
keyboards that I could have shipped and under my fingers much quicker than I
could order and build one.&lt;/p&gt;
&lt;h2&gt;Keyboard Considerations&lt;/h2&gt;
&lt;p&gt;With requirements decided, I started searching for my keyboard. Both of the
keyboards that I considered are made and sold from the same company,
&lt;a href=&quot;https://zsa.io&quot;&gt;ZSA&lt;/a&gt;. From what I read online, they were usually highly
recommended, with people rating both ZSA’s products and customer service very
well. Personally, I enjoyed reading about their focus on
&lt;a href=&quot;https://ergodox-ez.com/pages/sustainability&quot;&gt;sustainability&lt;/a&gt; and creating open
source products.&lt;/p&gt;
&lt;h3&gt;Moonlander&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/_Mr00EDLsE-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/_Mr00EDLsE-1200.jpeg&quot; alt=&quot;The Moonlander&quot; width=&quot;1200&quot; height=&quot;414&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Moonlander&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first keyboard that caught my eye was the new
&lt;a href=&quot;https://www.zsa.io/moonlander/&quot;&gt;Moonlander&lt;/a&gt;. When I first saw it, it had
&lt;em&gt;just&lt;/em&gt; been released. I admired it’s floating keys and slim profile.
Apparently, it was designed to be at least somewhat portable, as it comes with
a carrying case. The board seemed to be a fresh new take on the ergodox, with a
clever folding design and some newer features like usb-c.&lt;/p&gt;
&lt;p&gt;I had to dig deep to learn anything about the moonlander as there were only a
handful of people that had them at the time. As I dug, I passively learned
more and more about the ergodox…&lt;/p&gt;
&lt;h3&gt;Ergodox-EZ&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/YPuPLak5lp-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/decided-to-get-an-ergodox/YPuPLak5lp-1200.jpeg&quot; alt=&quot;&quot; The=&quot;&quot; Ergodox-EZ=&quot;&quot; Configuratior&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;925&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Ergodox-EZ Configurator&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In addition to the moonlander, ZSA also builds and sells the
&lt;a href=&quot;https://ergodox-ez.com&quot;&gt;ergodox-ez&lt;/a&gt; (and the &lt;a href=&quot;https://www.zsa.io/planck/&quot;&gt;planck
ez&lt;/a&gt;, but I wanted a split board). The
ergodox-ez is a pre-built version of the well-known (to keyboard nerds…),
&lt;a href=&quot;https://github.com/Ergodox-io&quot;&gt;open-source&lt;/a&gt;, ortholinear, split keyboard, the
&lt;a href=&quot;https://deskthority.net/wiki/ErgoDox&quot;&gt;ergodox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I loved the Moonlander’s features and design, but I ultimately
decided to go with the ergodox-ez for a couple of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Moonlander is brand-new and there wasn’t much feedback for it
yet, &lt;a href=&quot;https://www.youtube.com/watch?v=LALQsqZP1nA&quot;&gt;whereas&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=1GPHiJMS7eo&quot;&gt;there&lt;/a&gt; &lt;a href=&quot;https://www.nytimes.com/wirecutter/reviews/comfortable-ergo-keyboard/&quot;&gt;is&lt;/a&gt; &lt;a href=&quot;https://thenextweb.com/insider/2016/03/03/why-this-300-ergonomic-keyboard-might-actually-be-worth-the-money/&quot;&gt;plenty&lt;/a&gt; &lt;a href=&quot;https://www.rtings.com/keyboard/reviews/ergodox/ez&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://matteeyah.com/ergodox-ez-keyboard&quot;&gt;information about&lt;/a&gt; the ergodox-ez.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On a similar note, because the ergodox has been a popular design for years
now, 3rd-party parts like specialized keycaps sets
can easily be found on &lt;a href=&quot;https://www.mass.com/&quot;&gt;mass.com&lt;/a&gt; and &lt;a href=&quot;http://www.mechsupply.co.uk&quot;&gt;other
websites&lt;/a&gt;. This makes the ergodox a bit easier
to customize than the moonlander, at least currently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The ergodox is great starting board for testing out the
orthlinear+split design. If there were parts of it I really hated over the
next few years, I could always switch to a moonlander or another split board that better fits my needs,
based on what I learn from the ergodox.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;However, if I instead &lt;em&gt;love&lt;/em&gt; the ergodox design and want &lt;em&gt;another&lt;/em&gt; in a few
years for some reason, I still have the option to &lt;em&gt;build&lt;/em&gt; one… and &lt;em&gt;finally&lt;/em&gt;
scratch that itch XD.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s a win-win situation.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I ordered the Ergodox-ez and have had it for a over a month now. If you are
wondering what configuration I selected, and what I think about it so far…
stay tuned for my next post ;) .&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setup Optfine with Minecraft Flatpak Install</title>
    <link href="https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/" />
    <updated>2020-09-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/S3EcLEcVjQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/S3EcLEcVjQ-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Kitty Hawk, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the past few months, I started to play Minecraft (Java) on my desktop
again.  After &lt;a href=&quot;https://ryan.himmelwright.net/post/rx580-upgrade&quot;&gt;upgrading my graphics card&lt;/a&gt;, I wanted to
install some shaders. However, unlike when I was in college, I now install and
play minecraft using &lt;a href=&quot;http://flatpak.org&quot;&gt;flatpak&lt;/a&gt;. While flatpak makes
installing minecraft convenient, it also complicates enabling mods like
Optifine. So…  here’s how it’s done :) .&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;em&gt;Note: Before we start, I have only done this on Fedora Linux. It is possible
that paths and locations may differ on other distros&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Install the Minecraft Flatpak&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/f4rDrYm1Yw-1046.webp 1046w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/f4rDrYm1Yw-1046.jpeg&quot; alt=&quot;The Minecraft Flathub Page&quot; width=&quot;1046&quot; height=&quot;884&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Minecraft Flathub page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;First, lets install the minecraft flatpak. Ensure that &lt;a href=&quot;https://flatpak.org/setup/Fedora&quot;&gt;flathub is
enabled&lt;/a&gt;, then run the following command
to install Minecraft:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;flatpak &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; com.mojang.Minecraft&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When it finishes, open up the launcher and login to verify that everything is
working.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Logging into the official launcher at least once is required to install
Optifine in the later steps).&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Optifine&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://optifine.net/home&quot;&gt;Optifine&lt;/a&gt; is a Minecraft optimiziation mod which
supports installing shader and texture packs. Simply put, this makes minecraft
look better. Additionally, some shaders can optimize the game to &lt;em&gt;perform&lt;/em&gt;
better too. I use a shader that renders some textures more realistically
from the default, but nothing too fancy.&lt;/p&gt;
&lt;h3&gt;Download&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/vG5XCtcx71-981.webp 981w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/vG5XCtcx71-981.jpeg&quot; alt=&quot;Optifine Download Page&quot; width=&quot;981&quot; height=&quot;778&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Optifine Download page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To obtain optifine, go to the &lt;a href=&quot;https://optifine.net/downloads&quot;&gt;downloads page&lt;/a&gt;
and download the version which corresponds to the minecraft version you are
using. If you are running a version which is more recent, you might have to try
a &lt;em&gt;Preview version&lt;/em&gt; of optifine. In my experience, the previews have worked
without issue.&lt;/p&gt;
&lt;h3&gt;Finding the minecraft folder&lt;/h3&gt;
&lt;p&gt;Before we install Optifine, there is one piece of information we need to know:
the &lt;code&gt;.minecraft&lt;/code&gt; folder location. This is where running a flatpak verion
diverges from a normal minecraft install, as the folder will not be at
&lt;code&gt;~/.minecraft/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is because flatpak applications are sandboxed from the system. While good
for security, it means that the &lt;em&gt;‘home directory’&lt;/em&gt; observed inside the application
is different from the user’s (like a &lt;code&gt;chroot&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/rfTDuFVIqz-1037.webp 1037w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/rfTDuFVIqz-1037.jpeg&quot; alt=&quot;The flatpak minecraft folder&quot; width=&quot;1037&quot; height=&quot;736&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The flatpak minecraft folder.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;On my computer, the flatpak applications are located at &lt;code&gt;/home/ryan/.var/app/&lt;/code&gt;,
making my ‘&lt;code&gt;/home/ryan/.minecraft/&lt;/code&gt;’ folder &lt;em&gt;actually&lt;/em&gt; at
&lt;code&gt;/home/ryan/.var/app/com.mojang.Minecraft/data/minecraft/&lt;/code&gt;. Find and remember
this location. &lt;em&gt;(Hint: it should be similar to mine)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Install Optifine&lt;/h3&gt;
&lt;p&gt;Back to Optifine…&lt;/p&gt;
&lt;p&gt;To run the optifine installer, open a terminal, navigate to the downloaded
&lt;code&gt;jar&lt;/code&gt; file, and execute it using java:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;## If java is not installed:&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; java-openjdk-latest
&lt;span class=&quot;token comment&quot;&gt;## Then:&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; ~/Downloads
&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; preview_OptiFine_1.16.3_HD_U_G3_pre1.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will open the installer.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/1CGSbz4FNW-534.webp 534w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/1CGSbz4FNW-534.jpeg&quot; alt=&quot;The optifine installer&quot; width=&quot;534&quot; height=&quot;363&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Optifine installer window.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Switch the &lt;em&gt;Folder&lt;/em&gt; to the &lt;code&gt;minecraft&lt;/code&gt; one we found previously, and click
&lt;em&gt;Install&lt;/em&gt;. Note: If the official minecraft launcher was not already logged
into, this step will &lt;em&gt;not&lt;/em&gt; work as it will fail to find minecraft (the launcher
downloads content on the first login).&lt;/p&gt;
&lt;h3&gt;Configure The Optifine Launcher&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/J1GH8wyy06-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/J1GH8wyy06-1200.jpeg&quot; alt=&quot;The minecraft launcher, selecting optifine&quot; width=&quot;1200&quot; height=&quot;920&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Select Optifine in the Minecraft Launcher.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With Optifine installed, we need to select it in the minecraft launcher. If
Optifine was correctly installed, it should now be an option at the
bottom left of the window (Next to settings).&lt;/p&gt;
&lt;h3&gt;Add your own installation&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/m7lqlQnMb1-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/m7lqlQnMb1-1200.jpeg&quot; alt=&quot;Creating a new minecraft launcher install&quot; width=&quot;1200&quot; height=&quot;920&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Creating a Minecraft launcher for Optifine.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;If for some reason optifine &lt;em&gt;is not&lt;/em&gt; an option, a custom launcher can be
added. To create one, click &lt;em&gt;Installations&lt;/em&gt; at the top of the
launcher, and hit the &lt;em&gt;New&lt;/em&gt; button. From that window, give the install a name
and select your latest Optifine version from the drop-down menu. If optifine
isn’t an option, it likely wasn’t installed correctly or in the right location.&lt;/p&gt;
&lt;p&gt;Lastly, remember to &lt;em&gt;once again&lt;/em&gt; switch the &lt;em&gt;Game Directory&lt;/em&gt; to the location of
the flatpak minecraft folder we’ve been using. Hit &lt;em&gt;Create&lt;/em&gt; and switch to the
new profile.&lt;/p&gt;
&lt;h2&gt;Shaders&lt;/h2&gt;
&lt;p&gt;The main motivation behind adding Optifine is the ability to use shaders. With
Optifine all configured, lets finish what we came here for.&lt;/p&gt;
&lt;h3&gt;Download&lt;/h3&gt;
&lt;p&gt;First, find a shader. I have been using the
BSL shader pack and love it. It keeps a
more ‘classic’ minecraft style, without changing too much. However, it improves
key visuals like the lighting, water, and  swaying plants.&lt;/p&gt;
&lt;p&gt;When you find a shader you want, just download the package and move it to the
&lt;em&gt;shaders&lt;/em&gt; folder in our flatpak minecraft folder.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;FYI, not every shader I tried worked.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Enable in game&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/Pb_2Zl82u9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/Pb_2Zl82u9-1200.jpeg&quot; alt=&quot;Minecraft Video Settings&quot; width=&quot;1200&quot; height=&quot;756&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Minecraft Video Settings, with Shaders setting.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With the shader package in place, we should be able to enable it in the game.
Fire up minecraft (using the optifine install), and select &lt;em&gt;Options&lt;/em&gt;, then &lt;em&gt;Video
Settings&lt;/em&gt;. From there, you should now see a newly available &lt;em&gt;Shaders&lt;/em&gt; option.
Click it to open up the shaders menu.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/p0ek-U9Lts-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/p0ek-U9Lts-1200.jpeg&quot; alt=&quot;Minecraft Shader Settings&quot; width=&quot;1200&quot; height=&quot;756&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Minecraft Shader Settings.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;If the shader package was moved to the correct location, it &lt;em&gt;should&lt;/em&gt; show up
here. If so, click the desired shader (the laucher might restart when
a new shader is selected). If not, open the &lt;em&gt;Shaders Folder&lt;/em&gt;
to double check the location.&lt;/p&gt;
&lt;p&gt;After picking the shader, hit &lt;em&gt;Done&lt;/em&gt; and check it out!&lt;/p&gt;
&lt;h2&gt;Conlusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/E_hBYGVKeW-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-optifine-minecraft-flatpak/E_hBYGVKeW-1200.jpeg&quot; alt=&quot;Minecraft with Shaders&quot; width=&quot;1200&quot; height=&quot;756&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Minecraft using the BLS Shaders.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Much better! Shaders can make minecraft feel like a whole new game, and are a
blast to play with. While they might seem difficult to setup with a flatpak
install, it really isn’t bad &lt;em&gt;if&lt;/em&gt; you know (and remember XD) &lt;em&gt;where&lt;/em&gt; the
minecraft folder is located.  Enjoy!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;PS: For those that read &lt;a href=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/&quot;&gt;my previous post&lt;/a&gt;: yes, I
did use a virgil VM to get clean install screenshots for this post… including
the gameplay one!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Running VMs with VirtIO 3D Acceleration</title>
    <link href="https://ryan.himmelwright.net/post/virtio-3d-vms/" />
    <updated>2020-09-10T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/virtio-3d-vms/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/IstkUG5ci4-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/IstkUG5ci4-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;782&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Kitty Hawk, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While on vacation the other week, the only laptop I brought along was &lt;a href=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/&quot;&gt;my
macbook pro&lt;/a&gt;. That
being said, I still wanted the ability to jump into Linux while I was away.  I
needed to virtualize. I’ve used VirtualBox in the past, and while it’s &lt;em&gt;fine&lt;/em&gt;
for running headless installs, I find that it is not a great experience on
macOS when trying to run a full desktop environment. So I downloaded the free
trial of &lt;a href=&quot;https://www.parallels.com/blogs/just-released-parallels-desktop-for-mac/&quot;&gt;parallels desktop
15&lt;/a&gt;,
and honestly… it was great. Afterwards, I was motivated to improve the
experience of VMs running on my (Linux) desktop.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/EKG17gSRoG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/EKG17gSRoG-1200.jpeg&quot; alt=&quot;Parallels Desktop on MacOS&quot; width=&quot;1200&quot; height=&quot;971&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Parallels desktop on MacOS.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Parallels handled graphics on both my Windows and Linux guests wonderfully. I
would full-screen the VM and it felt like I was running that OS directly on my
macbook (for the most part). I was sad when the trial was over, but decided not
to buy the subscription. My main reason being… my VM computer is &lt;em&gt;not&lt;/em&gt; my
macbook, it’s my Linux desktop.&lt;/p&gt;
&lt;p&gt;I love libvirt and virt-manager, but using parallels showed me that I really
needed to figure out how to improve the graphics situation of my VMs to catch
up. I wanted to be able to work in guest VMs, without a lagging
UI or &lt;em&gt;feeling&lt;/em&gt; like I was &lt;em&gt;in&lt;/em&gt; a VM.&lt;/p&gt;
&lt;h2&gt;VFIO - GPU Passthrough&lt;/h2&gt;
&lt;p&gt;Prior to my trip, I had been experimenting &lt;em&gt;once again&lt;/em&gt; with VFIO… and
actually got it working* for the first time. If you don’t know what this means,
basically in Linux it is possible to pass a full device, like a GPU, through to
a VM to be used directly. The technology was made more widely known a few years
ago from the &lt;a href=&quot;https://www.youtube.com/watch?v=LuJYMCbIbPk&amp;amp;embeds_referring_euri=http%3A%2F%2F10.0.7.3%3A1313%2F&amp;amp;source_ve_path=MjM4NTE&amp;amp;feature=emb_title&quot;&gt;“X Gamers, 1 CPU” serries of videos Linux Tech Tips
made&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was able to temporarily run both Proxmox and Unraid on my desktop, and pass
my gpu directly to Fedora VMs.  While it was a neat experiment, I realized it
can also be quite a pain.&lt;/p&gt;
&lt;p&gt;I currently only have one GPU in my desktop, so to pass it to the VM I had to
disable it on the host. Not having a GPU for the host system meant that I had
to start the VMs from another device, which honestly isn’t &lt;em&gt;that&lt;/em&gt; bad. The much
worse issue however, was that the gpu would often get stuck while being passed
around, so I had to reboot my host whenever the VM restarted… which defeated
the point of using a VM instead of multi-booting.&lt;/p&gt;
&lt;p&gt;So to &lt;em&gt;properly&lt;/em&gt; utilize VFIO and have a more stable setup, I would want a
second GPU. In truth though, I realized this setup wasn’t what I was looking
for. KVM gpu passthrough is a great option if you run Linux as your main
computer, but want to be able to boot into a Windows VM to play games. But GPU
passthrough is slowly becoming overkill even for &lt;em&gt;that&lt;/em&gt;, as &lt;a href=&quot;https://www.youtube.com/watch?v=6T_-HMkgxt0&amp;amp;embeds_referring_euri=http%3A%2F%2F10.0.7.3%3A1313%2F&amp;amp;source_ve_path=MjM4NTE&amp;amp;feature=emb_title&quot;&gt;gaming support on Linux keeps getting better&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Aside from having an occasional Windows VM to play around with, I mostly want
to run Linux VMs on a Linux host. I needed to figure out how to let my VMs
utilize the &lt;em&gt;power&lt;/em&gt; of my GPU, without the complications of GPU passthrough…&lt;/p&gt;
&lt;h2&gt;Virgil&lt;/h2&gt;
&lt;p&gt;Introducing, &lt;a href=&quot;https://virgil3d.github.io&quot;&gt;virgil&lt;/a&gt;. According to the
project’s website:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Virgil is a research project to investigate the possibility of creating a virtual 3D GPU for use inside qemu virtual machines, that allows the guest operating system to use the capabilities of the host GPU to accelerate 3D rendering. The plan is to have a guest GPU that is fully independent of the host GPU.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, Virgil essentially allows libvirt to create &lt;em&gt;virtual 3D GPUs&lt;/em&gt;, that work
with the host GPU for 3D acceleratuon.  This means, that I &lt;em&gt;should&lt;/em&gt; be able to
get high performing VMs, including the graphics, on my Linux desktop.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Enabling virgil for a VM using virt-manager actually ended up be much easier
that I had anticipated. Here is how to do it:&lt;/p&gt;
&lt;h3&gt;Create a VM&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/23sqKvnGFA-1066.webp 1066w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/23sqKvnGFA-1066.jpeg&quot; alt=&quot;Newly Created Fedora VM&quot; width=&quot;1066&quot; height=&quot;904&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Newly created Fedora VM.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;First, create and install a new virtual machine using &lt;code&gt;virt-manager&lt;/code&gt;. I
recommend going through your normal install process and then boot into the
installed system once, just to ensure that everything works as expected. If
things look good, shutdown the VM and open up the settings window.&lt;/p&gt;
&lt;h3&gt;Enable VirtIO and 3D Acceleration - Spice Settings&lt;/h3&gt;
&lt;p&gt;In the settings menu, open up the &lt;em&gt;Display Spice&lt;/em&gt; section.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/euNCmmDvLg-1066.webp 1066w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/euNCmmDvLg-1066.jpeg&quot; alt=&quot;Virt-manager&#39;s Spice settings for a VM&quot; width=&quot;1066&quot; height=&quot;904&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;In the VM&#39;s &#39;Display Spice&#39; config, select &#39;None&#39; for Listen type, and check the box for OpenGL.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Select &lt;code&gt;Spice server&lt;/code&gt; for &lt;code&gt;Type:&lt;/code&gt;, and &lt;code&gt;None&lt;/code&gt; for &lt;code&gt;Listen type:&lt;/code&gt; (virgil only
works on local VMs right now). Lastly, make sure that the &lt;code&gt;OpenGL&lt;/code&gt; checkbox
&lt;em&gt;is&lt;/em&gt; checked. Hit &lt;code&gt;Apply&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Virtio Settings&lt;/h3&gt;
&lt;p&gt;Next, select the &lt;em&gt;Video Virtio&lt;/em&gt; section.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/n8rq_i_JfJ-1066.webp 1066w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/n8rq_i_JfJ-1066.jpeg&quot; alt=&quot;Virt-manager&#39;s Video settings for a VM&quot; width=&quot;1066&quot; height=&quot;904&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;In the VM&#39;s video settings, switch to Virtio and select 3D acceleration.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Switch to &lt;code&gt;Virtio&lt;/code&gt; for &lt;code&gt;Model:&lt;/code&gt;, and make sure to check the &lt;code&gt;3D acceleration&lt;/code&gt;
checkbox. That’s it! Hit apply, start up the VM, and verify that it is working.&lt;/p&gt;
&lt;h2&gt;Testing and Comparisons&lt;/h2&gt;
&lt;h3&gt;Youtube Video Playback&lt;/h3&gt;
&lt;p&gt;While configuring this for the first time, there were a few benchmarks I used
to tell if and how well it was working. The first one, was simply opening up
Firefox and trying to play a fullscreen Youtube Video (at 1440p).&lt;/p&gt;
&lt;p&gt;Now to be fair, the video playback was still &lt;em&gt;okay&lt;/em&gt; before I made this change.
This wasn’t a good measure, but rather an initial test.&lt;/p&gt;
&lt;h3&gt;Unigine Heaven Benchmark&lt;/h3&gt;
&lt;p&gt;My next step was to give the VM a &lt;em&gt;real&lt;/em&gt; GPU test, so I downloaded the &lt;a href=&quot;https://benchmark.unigine.com/heaven&quot;&gt;Unigine
Heaven Benchmark&lt;/a&gt;. First I got my
baseline by running the benchmark directly on my host system. &lt;em&gt;It should be
noted that I did have other applications running during all of these benchmarks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/mcfbbPhfhT-588.webp 588w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/mcfbbPhfhT-588.jpeg&quot; alt=&quot;Unigine Heaven Benchmark - Host&quot; width=&quot;588&quot; height=&quot;702&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Unigine Heaven Benchmark running directly on the host (my desktop).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So not bad, and roughly what I expected. Next, I tried running it in the VM…&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/QQ1fFbgXQQ-617.webp 617w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/QQ1fFbgXQQ-617.jpeg&quot; alt=&quot;Unigine Heaven Benchmark - VM&quot; width=&quot;617&quot; height=&quot;589&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Unigine Heaven Benchmark running inside the VM with *shared* graphics.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While not as good as on the host, it is a &lt;em&gt;very&lt;/em&gt; respectable result. Especially
after remembering that the VM had less cores and RAM assigned to it compared to
the host.&lt;/p&gt;
&lt;p&gt;Additionally, my default VM &lt;em&gt;wouldn’t even run&lt;/em&gt; the benchmark. I was
able to open it, but the load screen ran frame-by-frame. Turning on the
3D acceleration settings definitely made a difference.&lt;/p&gt;
&lt;h3&gt;Portal 2&lt;/h3&gt;
&lt;p&gt;With the Unigine Heaven benchmark working in my VM, I decided to attempt the
next test… playing a video game. Now, I have &lt;em&gt;zero&lt;/em&gt; intentions of running games in
my VMs, but this is the most common use case for having a GPU-passthrough
setup, so I wanted to see how this system compared.&lt;/p&gt;
&lt;p&gt;I decided to try running a classic game: Portal 2. It’s not a very resource
intensive game, but one that certainly does &lt;em&gt;not&lt;/em&gt; normally run well in my VMs.
I installed the game, started a level…  and was immediately forced to stare
at the ground.&lt;/p&gt;
&lt;p&gt;Staring at my character’s feet, I was reminded that I was playing in a VM, and
that the game didn’t actually have my proper mouse capture. It only had the
input clicks from my host to the VM spice window, which wasn’t enough to
actually play the game.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/WMODo2ZKxL-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/WMODo2ZKxL-1200.jpeg&quot; alt=&quot;Playing Portal 2 in a Fedora VM&quot; width=&quot;1200&quot; height=&quot;806&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Playing Portal2 at 2560x1440 resolution, inside my Fedora VM.(I had it full screen but windowed it to show it was indeed in the VM).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Anyway, after one VM shutdown and usb mouse pasthrough later… I was playing
Portal 2! It was a little glitchy for the first few seconds, but afterwards ran
well.  I think the initial lag was likely due to the system heavily hitting the
&lt;em&gt;virtualized&lt;/em&gt; disk while loading the level. Regardless, I was thrilled.&lt;/p&gt;
&lt;h2&gt;Drawbacks/Limitations&lt;/h2&gt;
&lt;p&gt;While I’m very happy with the performance of my virtio VM, there are a few
drawbacks or limitations to keep in mind:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Need to pass through hardware devices&lt;/strong&gt;: … like a mouse when gaming XD. This
could also be a webcam, microphone, flash drive, or even a full ssd if you need
better IO in the VM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linux Guests Only:&lt;/strong&gt; While researching, I saw &lt;a href=&quot;https://www.youtube.com/watch?v=aBgYNDLXuyg&quot;&gt;conference talks about getting
virgl working on Windows&lt;/a&gt; , but I
&lt;em&gt;think&lt;/em&gt; it currently only works with Linux guests.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/8uytMrKryb-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/8uytMrKryb-1200.jpeg&quot; alt=&quot;Both Manjaro and Pop_OS! worked fine&quot; width=&quot;1200&quot; height=&quot;1406&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Both my Manjaro and Pop_OS! VMs worked just fine with virgl.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Even on Linux, some Distros might freak out a bit&lt;/strong&gt;: I had weird issues
when trying it on some of my RHEL 8 VMs. The mouse pointer wouldn’t show
and the screen would glitch and flicker when running high graphics tasks. I’m
Not sure why it’s happening, but I’m guessing it might be related to older
software packages/kernel. I have tried it with Pop_OS! and Manjaro VMs and they
seem to work just fine.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/QJPMNsEaKq-1064.webp 1064w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/virtio-3d-vms/QJPMNsEaKq-1064.jpeg&quot; alt=&quot;My Fedora VMs didn&#39;t work on my T470 Thinkpad&quot; width=&quot;1064&quot; height=&quot;909&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Fedora VMs on my T470 Thinkpad (intel graphics only) didn&#39;t seem to like virgl.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not entirely sure about hardware support&lt;/strong&gt;: So far my working VMs have all
been on my desktop, which has an AMD RX580 GPU. I tried using a Fedora guest
on my T470 Thinkpad which has integrated Intel graphics, but the screen was
all crazy at login. I don’t know what hardware is supported and
what isn’t. It is possible that different hardware might just need different
settings selected, but I have not played around enough yet to know for sure.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Overall, I love this setup. It allows me to utilie my desktop to it’s full
potential, by running VMs I can jump into and forget that I am in a
virtalized system. This works well when testing fedora packages, or maintaining
a &lt;a href=&quot;https://fedoraproject.org/wiki/Releases/Rawhide&quot;&gt;Rawhide&lt;/a&gt; machine. Virgl is
an awesome projects and I hope it continues to progress over time. If you
haven’t tried it yet, give it a shot!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PIA Client Interfering with Podman Containers</title>
    <link href="https://ryan.himmelwright.net/post/pia-client-podman-issues/" />
    <updated>2020-08-23T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/pia-client-podman-issues/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/pia-client-podman-issues/a97iEE3OWQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/pia-client-podman-issues/a97iEE3OWQ-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;820&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;U.S. Route 64, Jamesville, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Earlier this month, I woke up and tried to start working on my &lt;a href=&quot;https://ryan.himmelwright.net/post/pytest-parallel-website-tests/&quot;&gt;previous
post&lt;/a&gt;, but quickly hit a snag.
I was unable to start the
&lt;a href=&quot;https://docs.fedoraproject.org/en-US/fedora-silverblue/toolbox/&quot;&gt;toolbox&lt;/a&gt;
container I use while working on my website. In fact, &lt;em&gt;none&lt;/em&gt; of my podman
containers would start.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Was Podman Broken?&lt;/h2&gt;
&lt;p&gt;Specifically, when I tried to start a container I encountered this error
message:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; start website
Error: unable to start container &lt;span class=&quot;token string&quot;&gt;&quot;f8ab31d42b9d04d051b23c65604e19748a9496f17bd3baab8e6f947eee8f3692&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; creating cgroup directory &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;/sys/fs/cgroup/net_cls/user.slice/user-1000.slice/user@1000.service/user.slice/libpod-f8ab31d42b9d04d051b23c65604e19748a9496f17bd3baab8e6f947eee8f3692.scope/container&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; No such &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; or directory: OCI runtime &lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; not found error&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, I attempted to use  podman on a different computer. It worked fine. I
compared version numbers and noticed that the second computer had a newer
version of podman installed. I figured that I had hit a bug that must now be fixed,
so I waited for the update to reach my desktop (it wasn’t available on that
machine yet for some reason).&lt;/p&gt;
&lt;p&gt;A day later when I ran my updates, the new version of &lt;code&gt;podman&lt;/code&gt; was installed, which
I thought would surely fix my problem. It didn’t. (ಠ_ಠ)&lt;/p&gt;
&lt;h2&gt;The Issue&lt;/h2&gt;
&lt;p&gt;I started to scour the internet again to look for answers. Eventually, I
found &lt;a href=&quot;https://www.reddit.com/r/Fedora/comments/hqbo34/podman_cgroup_issues_on_f32/&quot;&gt;this reddit
post&lt;/a&gt;.
While reading it, the poster’s experience sounded &lt;em&gt;very&lt;/em&gt; similar to my own.
After reading some of the comments that connected the &lt;a href=&quot;https://www.privateinternetaccess.com&quot;&gt;private internet
access&lt;/a&gt; client to the original poster’s
issues, I suddenly remembered… I had installed the &lt;a href=&quot;https://www.privateinternetaccess.com/pages/download&quot;&gt;PIA
client&lt;/a&gt; on my machine
earlier that week!&lt;/p&gt;
&lt;p&gt;Sure enough, when I checked the ownership of my &lt;code&gt;net_cls&lt;/code&gt; files (as suggested
in the thread), it looked like &lt;code&gt;piavpn&lt;/code&gt; was claiming group ownership of the
files:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ll /sys/fs/cgroup/net_cls
total &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root piavpn &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 cgroup.clone_children
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root piavpn &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 cgroup.procs
-r--r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root piavpn &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 cgroup.sane_behavior
drwxr-xr-x. &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; root root   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 machine.slice
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root piavpn &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 net_cls.classid
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root piavpn &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 notify_on_release
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root piavpn &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 release_agent
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root piavpn &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 tasks&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some commenters in the thread stated that the conflict went away after they
removed the PIA client.&lt;/p&gt;
&lt;h2&gt;Removing the PIA Client&lt;/h2&gt;
&lt;p&gt;As a result, I decided to un-install my PIA client. It wasn’t a major loss for
me, as I hadn’t used it in months. I only installed it to double check if it
was a service I wanted, or if I should cancel my subscription before it
auto-renewed later that month.&lt;/p&gt;
&lt;p&gt;At first, I couldn’t find an &lt;em&gt;un&lt;/em&gt;-install option, but eventually found it deep
in the settings. After removing the client, the &lt;code&gt;piavpn&lt;/code&gt; group went away… sort
of. It still had a &lt;code&gt;1004&lt;/code&gt; gid, which I’m guessing &lt;em&gt;was&lt;/em&gt; the previous &lt;code&gt;piavpn&lt;/code&gt;
group.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ll /sys/fs/cgroup/net_cls
total &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root &lt;span class=&quot;token number&quot;&gt;1004&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 cgroup.clone_children
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root &lt;span class=&quot;token number&quot;&gt;1004&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 cgroup.procs
-r--r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root &lt;span class=&quot;token number&quot;&gt;1004&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 cgroup.sane_behavior
drwxr-xr-x. &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; root root &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 machine.slice
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root &lt;span class=&quot;token number&quot;&gt;1004&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 net_cls.classid
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root &lt;span class=&quot;token number&quot;&gt;1004&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 notify_on_release
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root &lt;span class=&quot;token number&quot;&gt;1004&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 release_agent
-rw-r--r--. &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root &lt;span class=&quot;token number&quot;&gt;1004&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; Aug  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;:21 tasks&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whatever the case… podman still didn’t work.&lt;/p&gt;
&lt;h2&gt;… Don’t forget to Reboot!&lt;/h2&gt;
&lt;p&gt;I was furious. After calming down, I reasoned it probably still wasn’t working
due to cruft from the client lingering on my system (like the &lt;code&gt;1004&lt;/code&gt; group for
example), so I rebooted my desktop… and it worked!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In conclusion… why did I write this post? This complication was a huge pain
to troubleshoot.  It was only by chance that I stumbled on that reddit post,
and would have had an even &lt;em&gt;harder&lt;/em&gt; time without it. I assume having at least
&lt;em&gt;one&lt;/em&gt; more page on the internet stating that podman and the PIA client don’t
play nice, might help others find the solution quicker. Hence, this post.&lt;/p&gt;
&lt;p&gt;Before I end, it is worth nothing that some users reported that configuring the PIA openvpn
profiles and using &lt;em&gt;them&lt;/em&gt; to connect to the VPN works without issue. It is just
the &lt;em&gt;client&lt;/em&gt; that breaks containers. So if you want to still use PIA (I let
mine expire. I don’t use it enough), give it a try!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Running my Website Tests in Parallel with Pytest-Parallel</title>
    <link href="https://ryan.himmelwright.net/post/pytest-parallel-website-tests/" />
    <updated>2020-08-16T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/pytest-parallel-website-tests/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/pytest-parallel-website-tests/ewUiDKo8Yq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/pytest-parallel-website-tests/ewUiDKo8Yq-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Not too long ago, I added some basic testing for my website. Now when I push
changes to my website’s source repo, &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci&quot;&gt;automated
tests&lt;/a&gt; run to verify that the site’s &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/&quot;&gt;pages
are being served&lt;/a&gt;, and that the (markdown)
&lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links&quot;&gt;links are not broken&lt;/a&gt;. It works well
enough through the 450 or so generated tests. However, one recent afternoon I
realized… I should parallelize them.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why Parallelize?&lt;/h2&gt;
&lt;p&gt;Not all tests can be parallelized, but if they &lt;em&gt;can&lt;/em&gt; handle concurrent runs, it
might be worth trying to figure it out.  All of my current website tests do a
single thing: check if a page can be reached, or not. Furthermore, each check
does not interfere with the others. Checking if one link is available does not
change the state of a second one. Ultimately, this means that I could in
theory, run all of the tests at the same time.&lt;/p&gt;
&lt;h2&gt;Adding pytest-parallel&lt;/h2&gt;
&lt;p&gt;It turns out getting my website tests to run in parallel wasn’t actually that
hard. I just added a new pip package. I found a few, but the most
promising one seemed to be &lt;code&gt;pytest-parallel&lt;/code&gt;, which simply adds the option to
start pytest runs using parallel workers. To test it out, I installed the package
using the following pip command:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;pip install pytest&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;parallel &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;user&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once I verified that it &lt;em&gt;worked&lt;/em&gt;, I updated the &lt;code&gt;Pipfile&lt;/code&gt; in the website test
repo to include the &lt;code&gt;pytest-parallel&lt;/code&gt; package:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;packages&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
pytest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;
requests &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;
pytest&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;parallel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! To run the tests in parallel, just add the &lt;code&gt;--workers&lt;/code&gt; flag with a
number to the &lt;code&gt;pytest&lt;/code&gt; call. For example:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest &lt;span class=&quot;token parameter variable&quot;&gt;--workers&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-vv&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run my normal test run (&lt;code&gt;pytest -vv .&lt;/code&gt;), across 12 parallel workers.&lt;/p&gt;
&lt;h2&gt;Improvements&lt;/h2&gt;
&lt;p&gt;So, did this actually improve anything? Yep, quite a bit ☺.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/pytest-parallel-website-tests/aax4TUu2Kt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/pytest-parallel-website-tests/aax4TUu2Kt-1200.jpeg&quot; alt=&quot;Time decrease in Jenkins test pipline&quot; width=&quot;1200&quot; height=&quot;389&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Time decreases in Jenkins Test Pipeline.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Using 12 workers, my test pipeline in Jenkins went from taking around 4.5-5
minutes, all the way down to under 1.5 on average, and sometimes only a minute!
These times include some other steps, but the tests themselves went from
taking a few minutes to under 30 seconds! All 450+ of them!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s all I have. One day I thought I should parallelize my website tests, and
it turned out to be quite easy to do. Considering the results, I’m glad I did.
Again, while it is not always possible to run tests concurrently, if it is for
yours… try it out!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating Systemd Unit Files using Ansible</title>
    <link href="https://ryan.himmelwright.net/post/foundryvtt-service-ansible-role/" />
    <updated>2020-07-29T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/foundryvtt-service-ansible-role/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/foundryvtt-service-ansible-role/b33XQFP9Oq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/foundryvtt-service-ansible-role/b33XQFP9Oq-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;UNC-Chapel Hill Campus, Chapel Hill, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://ryan.himmelwright.net/post/autostarting-application-systemd-service/&quot;&gt;previous post&lt;/a&gt;, I
created a systemd unit file to define an application as a service, and
configured it to auto-start on my server. I’ve been making a big push to define
the provisioning of all my homelab machines/VMs using automation. So the last
step in setting up my FoundryVTT server, is to &lt;em&gt;automate&lt;/em&gt; the process.
Fortunately, creating a systemd unit file is quite easy to do using
&lt;a href=&quot;https://www.ansible.com&quot;&gt;ansible&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Creating the Ansible Role&lt;/h2&gt;
&lt;p&gt;Let’s start by creating a role (if you don’t know what an ansible role
is, checkout &lt;a href=&quot;https://ryan.himmelwright.net/post/ansible-quickstart/&quot;&gt;this guide&lt;/a&gt; I wrote not too long ago).
First, create some directories:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; roles
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; foundryvtt/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;defaults,tasks,templates&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One difference from roles I’ve created previously, is that this one contains a
&lt;code&gt;templates&lt;/code&gt; directory. That is because &lt;em&gt;this&lt;/em&gt; role will use a &lt;code&gt;j2&lt;/code&gt; template to
define the systemd unit file, but more on that later.&lt;/p&gt;
&lt;h2&gt;Defining Variables&lt;/h2&gt;
&lt;p&gt;Lets create some files, starting with one to define the default variables.
Open &lt;code&gt;roles/foundryvtt/defaults/main.yaml&lt;/code&gt; and add the following:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;service_description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;A service to run the Foundry VTT node app&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;foundryvtt_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/home//foundryvtt/&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;foundrydata_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/home//foundrydata&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This defines a few default veriables that will be used in the service file
template, as well as in the tasks file. The variables can optionally be
over-ridden when running a playbook, but they will default to these values if
not specified.&lt;/p&gt;
&lt;h2&gt;Making a Template&lt;/h2&gt;
&lt;p&gt;Now that the varibles are defined, we can create the unit file template. So,
open a new file (&lt;code&gt;roles/foundryvtt/templates/foundryvtt.service.j2&lt;/code&gt; in my
case), and insert the unit file from the previous post.&lt;/p&gt;
&lt;p&gt;Next, walk through the file and substitute any values for the variables defined
in the previous section:&lt;/p&gt;
&lt;pre class=&quot;language-INI&quot;&gt;&lt;code class=&quot;language-INI&quot;&gt;[Unit]
Description=
Documentation=https://foundryvtt.com
After=network.target

[Service]
Environment=NODE_PORT=30000
Type=simple
User=
ExecStart=/usr/bin/node /resources/app/main.js --dataPath=
Restart=on-failure

[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great! This template is now ready to be used in our role.&lt;/p&gt;
&lt;h2&gt;Ansible Tasks&lt;/h2&gt;
&lt;p&gt;Last but not least, time to write some Ansible tasks. Open a new file
(&lt;code&gt;roles/foundryvtt/tasks/main.yaml&lt;/code&gt;), and lets start by adding any additional
tasks &lt;em&gt;this particular role&lt;/em&gt; needs, outside of the service file. For my example,
this includes creating the defined data directories, unzipping the application source
package, and opening required ports in the firewall:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;## Note, you likely don&#39;t need these tasks. They are just for my particular&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;## example...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Create foundryvtt dir at 
  &lt;span class=&quot;token key atrule&quot;&gt;become_user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; directory

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Create foundrydata dir at 
  &lt;span class=&quot;token key atrule&quot;&gt;become_user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; directory

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Send Foundry Package
  &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; foundryvtt_send_src is defined and foundryvtt_send_src is not none
  &lt;span class=&quot;token key atrule&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Extract package
  &lt;span class=&quot;token key atrule&quot;&gt;unarchive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;remote_src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; yes

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Start firewalld
  &lt;span class=&quot;token key atrule&quot;&gt;systemd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; firewalld
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; started

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Open FoundryVTT Ports (firewalld)
  &lt;span class=&quot;token key atrule&quot;&gt;firewalld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 30000/tcp
    &lt;span class=&quot;token key atrule&quot;&gt;permanent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; yes
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; enabled

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; reload service firewalld&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; in all cases
  &lt;span class=&quot;token key atrule&quot;&gt;systemd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; firewalld
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; reloaded&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With all that defined, lets finally define a task to create our unit file using
the template:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Create foundryvtt systemd service file
  &lt;span class=&quot;token key atrule&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; templates/foundryvtt.service.j2
    &lt;span class=&quot;token key atrule&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /lib/systemd/system/foundryvtt.service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;src&lt;/code&gt; is set to the relative location (to the role) of the template we
defined earlier, and the &lt;code&gt;dest&lt;/code&gt; is set to where we would like the generated
file to be copied to. This template is a unit file for a systemd service, so
I’m going to copy it to &lt;code&gt;/lib/systemd/system/foundryvtt.service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Last but not least, lets start the newly created service:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Start foundryvtt service
  &lt;span class=&quot;token key atrule&quot;&gt;systemd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; foundryvtt
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; started&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… and… our role is finished!&lt;/p&gt;
&lt;h2&gt;Playbook&lt;/h2&gt;
&lt;p&gt;To run the role, it is easiest to have it run in a playbook. I try to define
the provisioning of all my systems in their own playbooks, including my foundry
server, so I use this role there. Just remember to call it in a &lt;code&gt;roles:&lt;/code&gt;
section of the playbook, like so:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foundryvtt&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it. We’ve easily automated setting up the systemd unit file from the
previous post using ansible. This makes defining and reproducing unit roles
very simple. In addition, knowing how to use templates in ansible is &lt;em&gt;very&lt;/em&gt;
powerful, and I will definitely be utilizing them much more moving forward.
Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Auto-starting Applications with Systemd Services</title>
    <link href="https://ryan.himmelwright.net/post/autostarting-application-systemd-service/" />
    <updated>2020-07-14T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/autostarting-application-systemd-service/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/autostarting-application-systemd-service/J3UakGipFw-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/autostarting-application-systemd-service/J3UakGipFw-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;UNC-Chapel Hill Campus, Chapel Hill, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Recently, I’ve been hosting a &lt;a href=&quot;http://foundryvtt.com&quot;&gt;Foundry VTT&lt;/a&gt; server (a
nodejs app) in a virtual machine on my home network. I would start the
application inside a
&lt;a href=&quot;https://ryan.himmelwright.net/post/setting-up-tmuxinator/&quot;&gt;&lt;code&gt;tmux&lt;/code&gt;&lt;/a&gt; session, by
executing a CLI command which worked… fine.  However, if the VM restarted or
the applications crashed, I had to ssh in and manually run the command again. So,
to better automate this tedious task, I created a unit file to
define the foundry server as a systemd service. Here’s how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Unit Files&lt;/h3&gt;
&lt;p&gt;With so many distributions utilizing
&lt;a href=&quot;https://en.wikipedia.org/wiki/Systemd&quot;&gt;systemd&lt;/a&gt;, unit files have become a new
standard for auto-starting, or re-starting, applications in Linux. Simply put,
unit files are used to define resources to be managed by systemd. This includes
&lt;em&gt;services&lt;/em&gt;. So to run FoundryVTT &lt;em&gt;as&lt;/em&gt; a service, we need to create a new systemd
&lt;code&gt;.service&lt;/code&gt; unit file.&lt;/p&gt;
&lt;h3&gt;Creating the service file&lt;/h3&gt;
&lt;p&gt;To create a unit file, I opened &lt;code&gt;/lib/systemd/system/foundryvtt.service&lt;/code&gt; in
&lt;code&gt;vim&lt;/code&gt; and filled it with the following contents:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Unit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;A service to run the Foundry VTT node app&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Documentation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;https://foundryvtt.com&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;network.target&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;NODE_PORT=30000&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;simple&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;ryan&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/node /home/user/foundryvtt/resources/app/main.js --dataPath=/home/user/foundrydata&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Restart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;on-failure&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;WantedBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;multi-user.target&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file tells systemd all the information it needs to run the service. The
contents of the &lt;code&gt;[Unit]&lt;/code&gt; section define some basic information about the unit
file. The variables which define our &lt;em&gt;service&lt;/em&gt;, are appropriately listed in the
&lt;code&gt;[Service]&lt;/code&gt; section and include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Environment=NODE_PORT=30000&lt;/code&gt; sets an environment variable&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Type=simple&lt;/code&gt; states that our service is executing a single command, and is
“started” when that command runs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;User=ryan&lt;/code&gt; tells the service to run under the &lt;code&gt;ryan&lt;/code&gt; user&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ExecStart&lt;/code&gt; defines which command to run when the service is started
(this is the command I had to manually type in &lt;code&gt;tmux&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Restart=on-failure&lt;/code&gt; tells the service to automatically restart on any
failures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save and exit the file.&lt;/p&gt;
&lt;h3&gt;Start and Enable the Service&lt;/h3&gt;
&lt;p&gt;With the unit file created, the new service can be started:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl start foundryvtt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition to starting the service, I also &lt;em&gt;enabled&lt;/em&gt; it so that it will
automatically launch whenever the system reboots:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; foundryvtt&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Stopping, Restarting, and Status&lt;/h3&gt;
&lt;p&gt;To check that the service is running, use the command &lt;code&gt;systemctl status foundryvtt&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl status foundryvtt
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ryan@magmar dotfiles&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl status foundryvtt
● foundryvtt.service - A &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; to run the Foundry VTT &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; app
     Loaded: loaded &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/usr/lib/systemd/system/foundryvtt.service&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; disabled&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; vendor preset: disabled&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     Active: active &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;running&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; since Sun &lt;span class=&quot;token number&quot;&gt;2020&lt;/span&gt;-07-12 &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;:34:29 EDT&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 2s ago
       Docs: https://foundryvtt.com
   Main PID: &lt;span class=&quot;token number&quot;&gt;1070&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      Tasks: &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;limit: &lt;span class=&quot;token number&quot;&gt;2327&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     Memory: &lt;span class=&quot;token number&quot;&gt;93&lt;/span&gt;.8M
        CPU: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.227s
     CGroup: /system.slice/foundryvtt.service
             └─1070 /usr/bin/node /home/ryan/foundryvtt/resources/app/main.js &lt;span class=&quot;token parameter variable&quot;&gt;--dataPath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/home/ryan/foundrydata

&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. *A Bunch of Logs I removed*&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output contains &lt;code&gt;Active: active (running)&lt;/code&gt;, which means the service is
running. We can also restart or stop the service using the &lt;code&gt;systemctl restart foundryvtt&lt;/code&gt; and &lt;code&gt;systemctl stop foundryvtt&lt;/code&gt; commands, respectively.&lt;/p&gt;
&lt;p&gt;For example, I can stop the service and then check the status to verify it is
killed (&lt;em&gt;Note the &lt;code&gt;Active: inactive (dead)&lt;/code&gt; in the status output&lt;/em&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ryan@magmar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl stop foundryvtt
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ryan@magmar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl status foundryvtt
● foundryvtt.service - A &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; to run the Foundry VTT &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; app
     Loaded: loaded &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/usr/lib/systemd/system/foundryvtt.service&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; disabled&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; vendor preset: disabled&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     Active: inactive &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dead&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
       Docs: https://foundryvtt.com&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That’s about it. Systemd unit files might seem complicated at first, but
after writing, one they aren’t that bad. Additionally, it turns out that using
automation to &lt;em&gt;create&lt;/em&gt; them isn’t too difficult either… but I’ll show that in
another post. Until then, enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Three Required Gnome Extensions</title>
    <link href="https://ryan.himmelwright.net/post/three-required-gnome-extensions/" />
    <updated>2020-06-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/three-required-gnome-extensions/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/h22PhrtWs6-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/h22PhrtWs6-1024.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1024&quot; height=&quot;768&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gnome.org&quot;&gt;Gnome&lt;/a&gt; is the default desktop environment for many
Linux distributions, including my distro of choice,
&lt;a href=&quot;https://getfedora.org&quot;&gt;Fedora&lt;/a&gt;. When Gnome 3 was first released, I had to
install a bunch of &lt;a href=&quot;https://extensions.gnome.org&quot;&gt;extensions&lt;/a&gt; to get a
functional experience. These days, between becoming more familar with the Gnome
workflow, and the Gnome team ironing out the rougher edges of the DE, I don’t
need nearly as many extensions to get going. However, there are still three
which I do not think I could live without.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Gnome Extensions&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/ALONlh51zN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/ALONlh51zN-1200.jpeg&quot; alt=&quot;Gnome Extensions Website&quot; width=&quot;1200&quot; height=&quot;1009&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Gnome Extensions website.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Gnome extensions are installable enhancements to the Gnome desktop environment.
They are obtained by going to the &lt;a href=&quot;https://extensions.gnome.org&quot;&gt;gnome extensions
website&lt;/a&gt; with a supported browser, installing the
plugin, and switching the tab of desired extensions to “On”. Extensions can
be further configured using the
&lt;a href=&quot;https://wiki.gnome.org/action/show/Apps/Tweaks?action=show&amp;amp;redirect=Apps%2FGnomeTweakTool&quot;&gt;Tweaks&lt;/a&gt;
desktop application.&lt;/p&gt;
&lt;h2&gt;My Must-Have 3&lt;/h2&gt;
&lt;h3&gt;Caffeine&lt;/h3&gt;
&lt;p&gt;My first required extension is Caffeine, which adds a little coffee cup icon to
the status bar. When the cup is empty, the system behaves as normal. However,
when clicked, the cup ‘fills up’ and caffeine will prevent the system from
suspending the display.&lt;/p&gt;
&lt;p&gt;This is useful when giving presentations to stop the screen from dimming off
during longer slides, or during Q&amp;amp;A time. I also use this if I have a secondary
computer displaying information, to prevent it from repeatedly logging me out. I
can temporarily pause my sleep settings, without having to actually go
in and change them.&lt;/p&gt;
&lt;h3&gt;Top Icons Plus&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/aqbZgJAiAH-422.webp 422w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/aqbZgJAiAH-422.jpeg&quot; alt=&quot;Gnome top bars with and without extensions enabled&quot; width=&quot;422&quot; height=&quot;200&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The right side of my top bar, without extensions (top), and with the *caffeine* and *top icons plus* extensions enabled (bottom).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Many applications still expect an icon to be visible, in a task bar, where the
application can to store settings in. However, Gnome will often hide these
icons, making them less accessible. Top icons plus moves these icons back to
the top status bar, like many people expect.&lt;/p&gt;
&lt;p&gt;I use this extension so that I can more easily see and control applications on
my system which &lt;em&gt;assume&lt;/em&gt; the icons to be visible. Top Icons plus also allows me
to choose where on the bar I want them located (left, center, right). I
usually just set them to the ‘right’ side of the screen, with all the other
junk.&lt;/p&gt;
&lt;h3&gt;Sound Input &amp;amp; Output Device Chooser&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/zfZTalRGxq-1159.webp 1159w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/three-required-gnome-extensions/zfZTalRGxq-1159.jpeg&quot; alt=&quot;Dropdown Menu, with and without sound device chooser&quot; width=&quot;1159&quot; height=&quot;618&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Top bar dropdown menu, with (right) and without (left) the *Sound Input &amp; Output Device Chooser* extension enabled.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, the &lt;em&gt;Sound Input &amp;amp; Output Device Chooser&lt;/em&gt; plugin adds a drop-down menu
to the top bar where audio input and output devices can be easily switched.
Otherwise, I usually need to do this from the &lt;em&gt;sound&lt;/em&gt; section of the system
settings.&lt;/p&gt;
&lt;p&gt;As someone that switches between speakers and headphones all day, has 100%
remote meetings, and usually more than one microphone device connected to their
computer, this is probably my most &lt;em&gt;required&lt;/em&gt; plug-in. It makes my life
easier, and allows me to react quicker in a meeting if I notice that an
incorrect audio device is selected.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While I still use other extensions, these are the big three. I think default gnome
has gotten surprisingly usable, but I would probably struggle to use it if
these extensions were no longer available to me.  So, to the Gnome team…
please consider adding the functionality of these items as defaults in Gnome.
If not, I understand. However, if that is the case… Gnome community, please
don’t let these extensions become deprecated! Thanks :)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trying out Notion</title>
    <link href="https://ryan.himmelwright.net/post/trying-notion/" />
    <updated>2020-06-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/trying-notion/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/qdhlhS1-7K-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/qdhlhS1-7K-1024.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1024&quot; height=&quot;768&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the past year or two, my notes and planning systems have been a bit of a
jumble.  It started when I attempted to switch everything to &lt;a href=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/&quot;&gt;joplin
notes&lt;/a&gt;… only to eventually switch &lt;a href=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/&quot;&gt;back to
org-mode (for my work notes)&lt;/a&gt; a few months
later. Around that same time, I also started using &lt;a href=&quot;https://trello.com&quot;&gt;trello&lt;/a&gt;
to organize my personal and home life task boards. This system has worked well, but
feels very disjointed. My notes, goals, and tasks are spread all over the place.
This might all be about to change. I’ve started using
&lt;a href=&quot;https://notion.so&quot;&gt;Notion&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Quick History&lt;/h2&gt;
&lt;p&gt;Before diving into my experience with Notion, lets first describe in more
detail the system I’m coming from.&lt;/p&gt;
&lt;h3&gt;Previous System&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/li8YPN-BT4-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/li8YPN-BT4-1200.jpeg&quot; alt=&quot;Trello Weekly Board&quot; width=&quot;1200&quot; height=&quot;632&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Trello Weekly Task Board.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Most recently, my personal and professional notes, and task planning systems
have consisted of three applications: Trello, Emacs (org-mode), and Joplin.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trello&lt;/strong&gt;: I had several boards for planning my weekly, monthly, and
multi-month personal tasks (including household ones). I also maintained a few
boards to keep track of goals and projects ideas (ex: future website posts, books to
read, training courses to take, etc).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Emacs org-mode&lt;/strong&gt;: For my professional work life, I completed all the same tasks I
listed under the trello section, using emacs instead.  I would write down my
tasks, and log quick notes and thoughts under each one while I was working on
it. Logging helps me think through problems, and serves as a reference
later. While I &lt;em&gt;could&lt;/em&gt; log notes into Trello cards, it wasn’t nearly as easy to
quickly open up and record a stream of consciousness, as it is was in org mode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Joplin&lt;/strong&gt;: I previously attempted to make Joplin my universal planning system,
but it failed. It was a bit messy for a universal system, and lacked a
board style layout for  my goals and task planning (what Trello or even my &lt;a href=&quot;https://github.com/himmAllRight/ry-org-scrum&quot;&gt;ry-org-scrum&lt;/a&gt;
package provide). That said, it was a great cross-platform notes application…
so I continued to use it for that. Any personal or work &lt;em&gt;note&lt;/em&gt; that wasn’t
directly related to a single task card was stored in Joplin.&lt;/p&gt;
&lt;h2&gt;Issues with the system&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/lvwPJTuJvQ-1056.webp 1056w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/lvwPJTuJvQ-1056.jpeg&quot; alt=&quot;Emacs org mode&quot; width=&quot;1056&quot; height=&quot;887&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Emacs org-mode task/notes, using the task-board package I wrote.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While this system &lt;em&gt;worked&lt;/em&gt;, it does some issues.&lt;/p&gt;
&lt;h3&gt;Couldn’t share items across applications&lt;/h3&gt;
&lt;p&gt;The biggest issue I had with this framework was that it was made from several
&lt;em&gt;disconnected&lt;/em&gt; systems. This meant that my notes and information were spread
all over the place, and not linkable. I tried to keep notes and task logs
separate, but while working on projects I often wanted to create longer,
stand-alone, reference &lt;em&gt;notes&lt;/em&gt; related to a task. With this setup, I couldn’t
easily have the two items reference each other.&lt;/p&gt;
&lt;h3&gt;Had to configure each application… on every device&lt;/h3&gt;
&lt;p&gt;I have &lt;a href=&quot;https://ryan.himmelwright.net/post/org-babel-setup/&quot;&gt;automated my emacs
setup&lt;/a&gt;, but both joplin and
emacs still required me to manually configure their sync solutions.
Additionally, while Trello’s minimal setup of ‘just login’ seems simple enough,
even this can quickly become tedious when doing it for multiple applications at a
time… on several devices… some of them phones.&lt;/p&gt;
&lt;h3&gt;Not accessible beyond my computers&lt;/h3&gt;
&lt;p&gt;Lastly, my main issue was that apart from Trello, these systems weren’t really
accessable from devices beside my own. In fact Emacs wasn’t even available on
my mobile devices. While good from a security standpoint, I don’t like having
all my notes and information only available when I sit down at a desktop. If
I’m working on a family member’s machine, or in a fresh VM (without a shared
clipboard), I want to be able to access my notes from a private browser tab. I
also like to move cards across my task board from my phone as I complete them
throughout the day.&lt;/p&gt;
&lt;h2&gt;Desires&lt;/h2&gt;
&lt;p&gt;With these issues defined, what is it that I actually want to see in my note
taking and task organization system?&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/bi2LEhhLUP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/bi2LEhhLUP-1200.jpeg&quot; alt=&quot;Joplin GUI&quot; width=&quot;1200&quot; height=&quot;763&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Joplin Notes.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consolidated into one system&lt;/strong&gt;: I don’t want to setup multiple apps
and logins on all my systems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross platform&lt;/strong&gt;: Despite only wanting a single system, I want it supported
on all my devices, including mobile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task board support&lt;/strong&gt;: I really enjoy moving and displaying  my task items
in a board view.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Normal notes too&lt;/strong&gt;: In addition to task cards, I also want support for
traditional, stand-alone notes. Ideally tasks &lt;em&gt;can&lt;/em&gt; be linked to
notes, but the notes don’t &lt;em&gt;have&lt;/em&gt; to be task-bound.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to jump in, add a log note, and get out&lt;/strong&gt;: I love this in org-mode. I need
the ability to quickly jump into a task, hit a keyboard shortcut to auto-insert
a time-stamp, and write a log note.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Notion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/2IlfhToegR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/2IlfhToegR-1200.jpeg&quot; alt=&quot;Notion in iOS&quot; width=&quot;1200&quot; height=&quot;798&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Notion window in macOS, displaying my page for this post. The UI looks the same on the web and in my &#39;Nativfier&#39; app in Linux.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Notion might be my solution. The &lt;a href=&quot;https://notion.so&quot;&gt;Notion website&lt;/a&gt; states
that it is an “&lt;em&gt;All-in-one workspace.  Write, plan, collaborate, and get
organized — all in one tool&lt;/em&gt;”. Having used it as my lone system over the
past several weeks, I think agree.&lt;/p&gt;
&lt;h2&gt;What I like so far&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FLEXIBLE&lt;/strong&gt;: This cannot be emphasized enough. Notion is &lt;em&gt;extremely&lt;/em&gt;
flexible, providing the &lt;em&gt;tools&lt;/em&gt; to setup your own system, rather than being
a defined system itself. Want just a pile of markdown-ish notes? Fine. Want
to create endless clusters  of relational databases? Go ahead. This endless
flexibility makes Notion what I would consider to be the Emacs org-mode for
&lt;s&gt;normal&lt;/s&gt; &lt;em&gt;reasonable&lt;/em&gt; people.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Easy logging using &lt;code&gt;@now&lt;/code&gt;&lt;/strong&gt;:

      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/eNewMkqYnO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/eNewMkqYnO-1200.jpeg&quot; alt=&quot;logging using @now&quot; width=&quot;1200&quot; height=&quot;250&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Easy logging using `@now`.&lt;/figcaption&gt;
      &lt;/figure&gt;
    
Notion has the ‘easy logging’ feature I
wanted. I just have to type &lt;code&gt;@now&lt;/code&gt;, hit enter, and it auto auto-inserts a
dynamic date and time (the ‘today’ changes to ‘yesterday’, and eventually
the date as time passes).&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Databases&lt;/strong&gt;: Databases are a compelling tool that are the backbone of a
powerful notion setup. They allow collections of data to be linked, sorted,
and filtered. Databases themselves have several notable features worth
mentioning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multiple view methods for the same data&lt;/strong&gt;: Database items can be displayed in
different &lt;em&gt;views&lt;/em&gt;. These include tables, boards, galleries,
calendar, and lists.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Views can be filtered and sorted using property rules&lt;/strong&gt;:
Properties can be also marked as hidden, and any custom
changes to the data view can be saved. This makes it easy to flip
through different views to get a better picture of the information.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linkable&lt;/strong&gt;: A database can be embedded in a page as a ‘linked database’.
This is a database that points to an already existing
one, and any changes to it also occur in the original DB. However,
the linked databases have their own saved and default views.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Templates&lt;/strong&gt;: Templates are another convenient feature. A specific page
layout can be defined as a template, which new pages can then be created
from. For example, I have weekly templates that
contain all the tasks and linked databases for that week automatically
configured. So when setting up a new week, I can simply create a new page
from the template and fill in some information. Done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Accessible from all my devices&lt;/strong&gt;:

      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/L8HWkqTct7-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/L8HWkqTct7-1200.jpeg&quot; alt=&quot;Notion in iOS&quot; width=&quot;1200&quot; height=&quot;626&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Notion views in iOS. Navigation, my &#39;Areas&#39; card view, a daily log page&#39; properties, and a task &#39;board&#39; view.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Notion has desktop applications for macOS and Windows, as well as
mobile apps for iOS and Andriod. Additionally, it functions well as a webapp, which
made it easy for me to create a
&lt;a href=&quot;https://github.com/jiahaog/nativefier&quot;&gt;nativefier&lt;/a&gt; build of it to run on my Linux
systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Free, and affordable for pro&lt;/strong&gt;: When I started using notion, the free tier
was limited to 1000 blocks, but since then they have made the personal tier
free &lt;em&gt;without&lt;/em&gt; a use limit! There is still a Pro version with fancy features
(like revision history), but even that is offered at a reasonable price.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Downsides/Concerns&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-notion/EO2vEtXShn-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-notion/EO2vEtXShn-1200.jpeg&quot; alt=&quot;Notion in iOS&quot; width=&quot;1200&quot; height=&quot;358&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Notion makes it easy to export an entire workspace.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All my eggs in one basket&lt;/strong&gt;: My biggest concern with Notion, is that I am
throwing everything into a single service which I now rely on. That is a
&lt;em&gt;huge risk&lt;/em&gt;. Fortunately, Notion does make it extremely easy to export a
workspace, which eases my fears a bit. I need to test this out, and maybe
make it a habit to periodically do it as a backup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A bit of a steep learning curve&lt;/strong&gt;: All the flexibility
and power means it can take a bit of trial and error to setup a system that
works for you. While overwhelming at first, I don’t find it to be not that
big of an problem. I &lt;em&gt;am&lt;/em&gt; coming from using mostly emacs after all :P&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lacks a cheaper ‘Family’ Plan&lt;/strong&gt;: I’m ecstatic that they made the personal plan
free, but I would still love to see a ‘Family’ plan that would allow my
wife and I to have shared collaborative pages. I know this is what the
teams version provides, but we don’t need all the other advanced features,
and the price ends up being a bit steep for what we would want.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In summary, I’ve found Notion to be amazing. I’ve had to redo my setup twice
already as I’ve started to better understand more powerful features, but every
time it has been worth it. Notion appears to be fitting my needs perfectly, and
it keeps getting better every day as I continue to tweak and perfect it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Switching Hugo&#39;s Markdown Handler</title>
    <link href="https://ryan.himmelwright.net/post/switching-hugo-md-handler/" />
    <updated>2020-05-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/switching-hugo-md-handler/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switching-hugo-md-handler/23m48cRVl_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switching-hugo-md-handler/23m48cRVl_-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Duke Park, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While writing my &lt;a href=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/&quot;&gt;previous post&lt;/a&gt;, I hit a frustrating
issue. After saving a large chunk of the draft, it appeared that hugo wasn’t
rendering the new additions. I verified on several computers, including fresh
installs. None of them  would generate the updated post, despite the source
files containing the changes. Ugh.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;The Actual Issue&lt;/h3&gt;
&lt;p&gt;When I first noticed the problem, I &lt;em&gt;though&lt;/em&gt; hugo wasn’t rendering &lt;em&gt;any&lt;/em&gt; of my
recent changes, which consisted of adding some images and a paragraph or two.
However, after inspecting the page in greater detail, I did see that the
updated text &lt;em&gt;was&lt;/em&gt; present. Only the images were missing.&lt;/p&gt;
&lt;p&gt;With this information, I was able to search for a solution.
After spending a few minutes reading some irrelevant stackoverflow
posts, I eventually found &lt;a href=&quot;https://jdhao.github.io/2019/12/29/hugo_html_not_shown/&quot;&gt;this wonderful blog
post&lt;/a&gt;. The problem it
described matched my issue exactly.&lt;/p&gt;
&lt;p&gt;It explained that recent versions of &lt;a href=&quot;https://gohugo.io&quot;&gt;hugo&lt;/a&gt; had switched
from using &lt;a href=&quot;https://github.com/russross/blackfriday&quot;&gt;blackfriday&lt;/a&gt; to
&lt;a href=&quot;https://github.com/yuin/goldmark/&quot;&gt;goldmark&lt;/a&gt;, to render the markdown content.
Unlike blackfriday, by default goldmark does not render raw html tags.  I use
raw html to define the images in my post, and thus they were no longer
rendering.&lt;/p&gt;
&lt;h3&gt;A Simple Solution&lt;/h3&gt;
&lt;p&gt;The post offered two solutions to fix the problem. For now, I just applied the
easier of the two: reverting back to blackfriday to render my markdown.  To
make the switch, I added the following line to my &lt;code&gt;config.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;markup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;defaultMarkdownHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;blackfriday&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The alternative solution, is to override goldmark’s default setting and allow
raw html tag rendering. This can be accomplished by instead adding these lines
to the &lt;code&gt;config.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;markup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;defaultMarkdownHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;goldmark&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;markup.goldmark&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;markup.goldmark.renderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token key property&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’m sure that the hugo team had valid reasons for the switch. With that said,
until I have time to read up on those reasons, and to verify that goldmark
doesn’t break rendering anything else in my website, I’m going to stick with
blackfriday.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;With that change the problem was solved. While the solution was easy, it
took a few days of head banging to figure out exactly what was happening.
Hopefully this post will help others figure out the fix quicker than I did. Or,
at the very least… it will remind me what I did when I eventually switch to
goldmark.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rx-580 Upgrade</title>
    <link href="https://ryan.himmelwright.net/post/rx580-upgrade/" />
    <updated>2020-05-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/rx580-upgrade/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/btbLf6_2-C-1098.webp 1098w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/btbLf6_2-C-1098.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1098&quot; height=&quot;715&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Earlier this year, I noticed that my desktop seemingly did not connect to my
monitor. I used the computer mostly as a server, remoting in via &lt;code&gt;ssh&lt;/code&gt;, and
didn’t think much of it.  I assumed it was either a mis-plugged cable or a
configuration issue. A few months later, I started to game a bit more and
wanted to use my desktop as a gaming machine again. That is when I realized…
it was an &lt;em&gt;actual&lt;/em&gt; issue. Long story short, my desktop now has an rx580 instead
of it’s old rx560.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;My Old Card - The rx560&lt;/h2&gt;
&lt;p&gt;Before diving into the issues, lets recap. Why did I select the rx560 in the
first place? What did I like about it?&lt;/p&gt;
&lt;h3&gt;I wanted an AMD GPU&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/jsVVuNY-RJ-500.webp 500w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/jsVVuNY-RJ-500.jpeg&quot; alt=&quot;rx580 price history&quot; width=&quot;500&quot; height=&quot;142&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I &lt;a href=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/&quot;&gt;designed and built my desktop&lt;/a&gt;, I
knew I wanted to get an AMD GPU because their open source drivers were great,
and would allow me to skip all the headaches I’ve had over the years messing
around with the proprietary Nvidia drivers. I remain &lt;em&gt;very&lt;/em&gt; pleased with that
decision, and have had zero issues getting games up and running. No hassle,
period.&lt;/p&gt;
&lt;h3&gt;I needed a card to handle &lt;em&gt;modest&lt;/em&gt; gaming&lt;/h3&gt;
&lt;p&gt;In the post about my desktop, I stated that while I wasn’t a big pc gamer, I
did enjoy the &lt;em&gt;occasional&lt;/em&gt; game. I play many games on consoles, especially the
ones I play with my wife, but desktop gaming is still my favorite. That said, I
don’t need all my graphics settings tuned to ultra, and I tend to not play the
&lt;em&gt;newest&lt;/em&gt; games.&lt;/p&gt;
&lt;p&gt;The rx560 was a perfect little card that give me &lt;em&gt;enough&lt;/em&gt; performance to play
most games, without costing too much. GPUs were still coming down from steep
prices due to crypto mining at the time, and saving some money on the GPU
allowed me to buy more RAM (which I needed more), while remaining under budget.&lt;/p&gt;
&lt;h3&gt;I planned to eventually upgrade&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/VxMkfqt-9N-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/VxMkfqt-9N-1200.jpeg&quot; alt=&quot;The rx560 and 580 side-by-side&quot; width=&quot;1200&quot; height=&quot;1182&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The rx560 (bottom) next to my new rx580 (top).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;One of the reasons I wanted to build a desktop computer again was for the
ability to upgrade it down the road. The build I designed had enough power
to fit all of my needs, but I configured it so that if any part lacked enough
performance over time, I could easily upgrade it.&lt;/p&gt;
&lt;p&gt;I knew one of the components I might eventually want to upgrade would be the
GPU. I figured that the rx560 should last me awhile, but if I started playing
more games, I could easily get a better one, and hopefully prices would be
lower. I could always upgrade to an rx580, or an even newer card if needed.&lt;/p&gt;
&lt;h2&gt;Issues&lt;/h2&gt;
&lt;h3&gt;Debugging&lt;/h3&gt;
&lt;p&gt;Okay, back to the problem. As I said before, my desktop wouldn’t connect to my
monitor. When I &lt;code&gt;ssh&lt;/code&gt;’ed into it, everything worked as normal, and all of my
VMs and containers spun up without issue, as if there was nothing wrong.  I ran
commands like &lt;code&gt;lspci | grep &#39;VGA&#39;&lt;/code&gt; and &lt;code&gt;neofetch&lt;/code&gt;, and they still correctly
detected and listed the rx560, confirming the system &lt;em&gt;knew&lt;/em&gt; the card was there.
I did some more testing and confirmed that I couldn’t get a signal on &lt;em&gt;any&lt;/em&gt;
monitor in the house, using a large assortment of cables.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/rSQ2Qk6Aad-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/rSQ2Qk6Aad-1200.jpeg&quot; alt=&quot;Displays would not connect&quot; width=&quot;1200&quot; height=&quot;510&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;No display would &#39;connect&#39;, even when plugged in.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I attempted to boot into live cds and other os installs, but the system
wouldn’t even display the BIOS when starting.  Investigating further, I saw in
the logs that the &lt;code&gt;$DISPLAY&lt;/code&gt; was trying to connect via all the various inputs
(DVI, HDMI, etc) during bootup, but would eventually fail stating that there
was &lt;code&gt;No Display Attached&lt;/code&gt;, even when it was plugged in. I opened up the case,
ensured everything was connected properly, and even tried different cables.
Nothing.&lt;/p&gt;
&lt;h3&gt;The Possible ‘Solution’&lt;/h3&gt;
&lt;p&gt;I eventually narrowed down that the problem was being caused by issues with the
GPU, despite it showing up fine in my pci devices. If it wasn’t the GPU, it was
likely the motherboard… So I figured the next best (and easier) step was to
swap in another card and see if it worked. Not having a spare graphics card on
hand, and not being able to borrow one due to COVID-19 lockdown, my only option
was to order one.&lt;/p&gt;
&lt;p&gt;Fixing the display issues on my workstation was not an &lt;em&gt;urgent&lt;/em&gt; issue.
However, without the ability to connect to a display I couldn’t easily
re-install or debug my system if something went wrong. This was compounded by
the fact that I was running &lt;a href=&quot;https://silverblue.fedoraproject.org&quot;&gt;Fedora
Silverblue&lt;/a&gt;, and needed to reboot every
time I wanted to update my packages, never quite sure if I would be able to
&lt;code&gt;ssh&lt;/code&gt; back in when it was done. Lastly, I didn’t know what availability or
shipping times would be, given the pandemic. So, I decided it was best to just
order a new card now, rather than wait until the problem &lt;em&gt;did&lt;/em&gt; become urgent.&lt;/p&gt;
&lt;h2&gt;My New Card: The RX-580&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/oz-n9UUSND-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/oz-n9UUSND-1200.jpeg&quot; alt=&quot;The rx560 box arrived a few days later&quot; width=&quot;1200&quot; height=&quot;727&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The rx580 box arrived a few days after ordering.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After some brief researching, I decided on the rx580. I also considered the 570
and 590, as well as glanced at some of the newer AMD cards. The truth is, my
rx560 worked fine for what I needed. &lt;em&gt;If&lt;/em&gt; I was getting a new card
though, a small spec bump would be nice. The 580 seemed to provide more than
enough performance.&lt;/p&gt;
&lt;p&gt;So far, my experience confirms this. I am able to play my games (mostly
Divinity Original Sin II, Minecraft, and City Skylines, right now) generally
above 60 fps, and at mostly high or better settings. Additionally, because the
card isn’t being &lt;em&gt;too&lt;/em&gt; taxed during gameplay, it doesn’t seem to be heating up
too badly…  which I also appreciate.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/ehel54dYrm-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/rx580-upgrade/ehel54dYrm-1200.jpeg&quot; alt=&quot;rx580 price history&quot; width=&quot;1200&quot; height=&quot;524&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;rx580 price history.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, the card dropped in price since I built my desktop. I purchased the
rx580 for only a little bit more than I originally paid for the rx560, and over
$100 cheaper than what it cost when I built the desktop. For what I need, the
price and performance are perfect.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So far, I’m loving it. The rx580 does everything that I need it to and honestly
doesn’t give me any problems. Once again, I was able to plug in the card, boot
up the computer, open Steam, and start playing my games. No additional drivers,
no weird configuration, no bloatware. All on Linux. I am &lt;em&gt;very&lt;/em&gt; happy with the
purchase, and it has once again reminded me why I truly do love having a
desktop/workstation under my desk, even if it is used as a server &lt;em&gt;most&lt;/em&gt; of the
time.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Ansible Quickstart</title>
    <link href="https://ryan.himmelwright.net/post/ansible-quickstart/" />
    <updated>2020-04-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/ansible-quickstart/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ansible-quickstart/mydSDJ7Fw--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ansible-quickstart/mydSDJ7Fw--1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Ansible Office Balcony, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;long&lt;/em&gt; time ago, I briefly explained how to configure
&lt;a href=&quot;https://www.ansible.com&quot;&gt;Ansible&lt;/a&gt;, in &lt;a href=&quot;https://ryan.himmelwright.net/post/ansible-on-pi-cluster/&quot;&gt;a post about building a raspberry pi
cluster&lt;/a&gt;. All in all… it was by no means a
great introduction to the basics of ansible.&lt;/p&gt;
&lt;p&gt;A month ago, I drafted a progression of examples with notes, to teach a
co-worker the &lt;em&gt;basics&lt;/em&gt; of writing and using ansible roles and playbooks. After
reading through them, I realized it wouldn’t take much to turn
them into an &lt;em&gt;actual&lt;/em&gt; Ansible quickstart post. So here we are.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;I am not an Ansible genius, and reading this will not make you one either. However,
the goal of this post is to provide enough understating to get started with writing
some ansible playbooks.&lt;/p&gt;
&lt;h2&gt;Installing&lt;/h2&gt;
&lt;p&gt;Lets start by installing ansible. It should be in most distro’s main repos these
days:&lt;/p&gt;
&lt;p&gt;Fedora Linux:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install ansible
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MacOS:&lt;/p&gt;
&lt;p&gt;… I have no idea. I usually always &lt;code&gt;ssh&lt;/code&gt; to Linux boxes from my macbook.&lt;/p&gt;
&lt;p&gt;I think it can be installed with &lt;code&gt;pip&lt;/code&gt; though, so possibly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip3 install ansible
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Remote Node Requirements&lt;/h2&gt;
&lt;p&gt;In order for ansible to connect to a remote node, that node usually needs 3
things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Python installed&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;password-less sudo permissions&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;ssh&lt;/code&gt; keys configured (if running against remote hosts. Not needed if
just running playbooks against &lt;code&gt;localhost&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;python&lt;/h3&gt;
&lt;p&gt;Python should already be installed on most systems. If not, check your package
manager, or try searching the documentation on &lt;a href=&quot;https://python.org&quot;&gt;python.org&lt;/a&gt;
to learn the best install method for your system.&lt;/p&gt;
&lt;h3&gt;Passwordless &lt;code&gt;sudo&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This will allow a user to run &lt;code&gt;sudo&lt;/code&gt; commands, without having to type in a
password each time. I shouldn’t have to say this, but… &lt;em&gt;please use with care!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Granting password-less sudo permissions are most easily accomplished with
&lt;code&gt;visudo&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; visudo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will open up the &lt;code&gt;sudo&lt;/code&gt; settings in your &lt;code&gt;$EDITOR&lt;/code&gt;. Once opened, find the
following line and uncomment it (it’s usually near the bottom of the file).&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;## Same thing without a password&lt;/span&gt;
%wheel        &lt;span class=&quot;token assign-left variable&quot;&gt;ALL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ALL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;       NOPASSWD: ALL&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ssh&lt;/h3&gt;
&lt;p&gt;Lastly, exchange ssh-keys with the remote node. This will allow ansible to ssh
into the node without having to deal with those pesky passwords. The easiest way
to exchange keys is using the &lt;code&gt;ssh-copy-id&lt;/code&gt; command, as such:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;ssh-copy-id username@hostname&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ansible Basics&lt;/h2&gt;
&lt;h3&gt;Hosts File&lt;/h3&gt;
&lt;p&gt;A host inventory file is a yaml file that defines hosts ansible can connect to.
The default file is located at &lt;code&gt;/etc/ansible/hosts&lt;/code&gt;. An alternative inventory
file may be provided using the &lt;code&gt;-i&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;Example file:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;VMs&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;## Server VMs&lt;/span&gt;
192.168.10.50
192.168.10.71
192.168.10.118

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Hosts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;## Hosts&lt;/span&gt;
192.168.10.12

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;cluster&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
192.168.112.205
192.168.112.206
192.168.112.207&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Modules&lt;/h3&gt;
&lt;p&gt;Modules are premade functionality used in ansible that can be imported into a
playbook. Simply, they &lt;em&gt;do&lt;/em&gt; what you want &lt;em&gt;done&lt;/em&gt;. Some examples are &lt;code&gt;ping&lt;/code&gt;,
&lt;code&gt;dnf&lt;/code&gt;, &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;redhat_subscription&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Feel free to search the &lt;a href=&quot;https://docs.ansible.com&quot;&gt;ansible documentation&lt;/a&gt; to
learn more.&lt;/p&gt;
&lt;h3&gt;Ad-hoc Ansible Commands&lt;/h3&gt;
&lt;p&gt;Simple and straight ansible executions can be called with the &lt;code&gt;ansible&lt;/code&gt; command.
Ad-hoc commands are usually called with a module, using the &lt;code&gt;-m&lt;/code&gt; flag. For
example, &lt;code&gt;ping&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ansible &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ping&lt;/span&gt; localhost

localhost &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; SUCCESS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;changed&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; false,
    &lt;span class=&quot;token string&quot;&gt;&quot;ping&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pong&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a more complicated example, lets use the &lt;code&gt;dnf&lt;/code&gt; module to install &lt;code&gt;htop&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ansible &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; dnf &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name=htop state=latest&quot;&lt;/span&gt; localhost &lt;span class=&quot;token parameter variable&quot;&gt;--become&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This module requires some parameters to be defined. We are able to supply
them using the &lt;code&gt;-a&lt;/code&gt; flag, followed by a string of the key/values pairs.&lt;/p&gt;
&lt;p&gt;Also, because the &lt;code&gt;dnf&lt;/code&gt; module requires root permissions to function, we supply
the &lt;code&gt;--become&lt;/code&gt; flag, to become &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note, if I want to run this against another machine (beyond &lt;code&gt;localhost&lt;/code&gt;), it
has to be defined in whatever inventory file we are using.&lt;/p&gt;
&lt;p&gt;So, if I define an inventory file (&lt;code&gt;./hosts.yaml&lt;/code&gt;) containing my desktop
computer:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;charmelon&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
192.168.1.5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can install &lt;code&gt;htop&lt;/code&gt; on &lt;em&gt;my desktop&lt;/em&gt;, using:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ansible &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; ./hosts &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; dnf &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name=htop state=latest&quot;&lt;/span&gt; charmeleon &lt;span class=&quot;token parameter variable&quot;&gt;--become&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… and it works!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;192.168.1.5 | CHANGED =&amp;gt; {
    &amp;quot;ansible_facts&amp;quot;: {
        &amp;quot;discovered_interpreter_python&amp;quot;: &amp;quot;/usr/bin/python&amp;quot;
    },
    &amp;quot;changed&amp;quot;: true,
    &amp;quot;msg&amp;quot;: &amp;quot;&amp;quot;,
    &amp;quot;rc&amp;quot;: 0,
    &amp;quot;results&amp;quot;: [
        &amp;quot;Installed: htop-2.2.0-8.fc32.x86_64&amp;quot;
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Playbooks&lt;/h2&gt;
&lt;p&gt;As you can imagine, doing everything from the command line isn’t always
helpful, or easily reproducible. That’s what playbooks are for. In a nutshell,
playbooks are ansible scripts. They are a yaml file which ansible runs, instead
of running a series of ad-hoc commands.&lt;/p&gt;
&lt;p&gt;To demonstrate, lets convert the &lt;code&gt;dnf&lt;/code&gt; command from above, into a simple
playbook named &lt;code&gt;install-htop.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; charmeleon
  &lt;span class=&quot;token key atrule&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;token key atrule&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install Htop
      &lt;span class=&quot;token key atrule&quot;&gt;dnf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; htop
        &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Being a &lt;code&gt;yaml&lt;/code&gt; file, the first line starts with &lt;code&gt;---&lt;/code&gt;. Next, we define some
meta information for the entire playbook. For example, this is were we put the
&lt;code&gt;--become&lt;/code&gt; flag, by turning it into &lt;code&gt;become: true&lt;/code&gt;. This is also where we
define what hosts the playbook will run against. If I’m providing a hosts file,
I can alternatively use &lt;code&gt;hosts: all&lt;/code&gt; to run against &lt;em&gt;all&lt;/em&gt; hosts defined in the
inventory file.&lt;/p&gt;
&lt;h3&gt;local connections&lt;/h3&gt;
&lt;p&gt;If the playbook is to run only locally, the connection type can be set to
&lt;code&gt;local&lt;/code&gt; (by default, it is set to &lt;code&gt;ssh&lt;/code&gt;.)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  hosts: 127.0.0.1
  connection: local
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tasks&lt;/h3&gt;
&lt;p&gt;Below the header information, we can define a set of tasks to run. In the
&lt;code&gt;tasks&lt;/code&gt; section, a block is defined for each task, usually by calling a module
with parameters. It is best practice to describe each task using &lt;code&gt;name:&lt;/code&gt;. This
will make it easier to trace the logs.&lt;/p&gt;
&lt;p&gt;For example, lets add the&lt;code&gt;ping&lt;/code&gt; module to the playbook so we have more than one
task…&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 127.0.0.1
  &lt;span class=&quot;token key atrule&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; local
  &lt;span class=&quot;token key atrule&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;token key atrule&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ping host first&lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
	  &lt;span class=&quot;token key atrule&quot;&gt;ping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install Htop
      &lt;span class=&quot;token key atrule&quot;&gt;dnf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; htop
        &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the playbook will run both tasks, using &lt;code&gt;name&lt;/code&gt; as the header for the output
of each one:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  /tmp ansible-playbook install-htop.yaml

PLAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;all&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Gathering Facts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.1.5&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Ping &lt;span class=&quot;token function&quot;&gt;host&lt;/span&gt; first&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.1.5&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Install Htop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.1.5&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

PLAY RECAP ****************************************
&lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.1.5                &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Variables&lt;/h3&gt;
&lt;p&gt;We can define sections other than &lt;code&gt;tasks&lt;/code&gt;.  A useful section to add is &lt;code&gt;vars:&lt;/code&gt;,
which defines variables for use in the playbook. To illustrate, lets replace
the hard-coded &lt;code&gt;htop&lt;/code&gt; in the &lt;code&gt;dnf&lt;/code&gt; task, to a variable named &lt;code&gt;package&lt;/code&gt;. We can
even use the &lt;code&gt;package&lt;/code&gt; variable in the &lt;code&gt;name&lt;/code&gt; string, to dynamically change the
output in the log:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 127.0.0.1
  &lt;span class=&quot;token key atrule&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; local
  &lt;span class=&quot;token key atrule&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;token key atrule&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; htop

  &lt;span class=&quot;token key atrule&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ping host first&lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;ping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install 
      &lt;span class=&quot;token key atrule&quot;&gt;dnf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the output:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ansible-playbook install-htop.yaml

PLAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Gathering Facts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Ping &lt;span class=&quot;token function&quot;&gt;host&lt;/span&gt; first&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Install htop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
changed: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

PLAY RECAP ****************************************
&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1                  &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One great feature of variables is that they can be swapped out when calling the
playbook. The &lt;code&gt;-e&lt;/code&gt; flag allows you to provide an alternative value for a
variable.  For example, lets say we want to install &lt;code&gt;nano&lt;/code&gt; instead of &lt;code&gt;htop&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ansible-playbook install-htop.yaml &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nano

PLAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Gathering Facts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Ping &lt;span class=&quot;token function&quot;&gt;host&lt;/span&gt; first&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Install nano&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
changed: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

PLAY RECAP ****************************************
&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1                  &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note how the task name has changed accordingly in the output. So Fancy!&lt;/p&gt;
&lt;h2&gt;Creating some structure - Roles&lt;/h2&gt;
&lt;p&gt;As nice as scripts are, they don’t scale well. To combat impending chaos, we
break functionality down into &lt;code&gt;roles&lt;/code&gt;. Roles are collections of tasks,
variables, and other resources that can be mixed and matched in playbooks.&lt;/p&gt;
&lt;p&gt;A role is defined by a directory of it’s name, and usually contains a &lt;code&gt;tasks&lt;/code&gt;
sub-directory, where all of it’s tasks are defined. Each sub-directory requires
a &lt;code&gt;main.yaml&lt;/code&gt; to be the root file for that directory. So, at the vary least, a
tasks directory will have a file named &lt;code&gt;tasks/main.yaml&lt;/code&gt; which contains the
role’s tasks.&lt;/p&gt;
&lt;p&gt;If there are a BUNCH of tasks defined, they can be broken out into seperate
files, and included in the &lt;code&gt;main.yaml&lt;/code&gt; task file.&lt;/p&gt;
&lt;p&gt;In addition to &lt;code&gt;tasks&lt;/code&gt;, a role might include a &lt;code&gt;defaults&lt;/code&gt; or &lt;code&gt;vars&lt;/code&gt; sub
directory. These are again structured with a &lt;code&gt;main.yaml&lt;/code&gt; file that may, or may
not, import other files, depending on the size and organization of the role.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;## Example structure of a &#39;subscriptions&#39; role&lt;/span&gt;
roles
└── subscriptions
    ├── defaults
    │   └── main.yaml
    ├── README.md
    └── tasks
        └── main.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is important to note that these yaml files contain &lt;em&gt;just&lt;/em&gt; their item. For
example, the task files contain just tasks. This is because when a role is
imported into a playbook, its items are simply inserted accordingly.&lt;/p&gt;
&lt;h3&gt;ansible.cfg&lt;/h3&gt;
&lt;p&gt;Before we start writing some roles, it is important to know that if you are using
roles, you need to tell &lt;code&gt;ansible&lt;/code&gt; where to find them. The easiest way to do
this is to define an &lt;code&gt;ansible.cfg&lt;/code&gt; file in the directory you will run
&lt;code&gt;ansible-playbook&lt;/code&gt; from. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[defaults]
roles_path = roles/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Our role&lt;/h3&gt;
&lt;p&gt;As it stands, our example playbook is a &lt;em&gt;massive 13 lines long&lt;/em&gt;! I can hardly
open the file without crashing my text editor. So, lets try to break up the
functionality into roles.&lt;/p&gt;
&lt;p&gt;First, lets make the directories:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; roles/install-htop/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;tasks,defaults&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we can add our variables to a default file,
&lt;code&gt;roles/install-htop/defaults/main.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; htop&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;code&gt;package&lt;/code&gt; variable set, lets create the tasks. To demonstrate
including other files in the &lt;code&gt;main.yaml&lt;/code&gt;, I’m going to be overly-complicated and
extract our &lt;code&gt;ping&lt;/code&gt; task into its own file, and then include it in the &lt;code&gt;main.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So first, &lt;code&gt;roles/install-htop/tasks/ping.yaml&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ping host first&lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then, &lt;code&gt;roles/install-htop/tasks/main.yaml&lt;/code&gt;, which will also include our
&lt;code&gt;dnf&lt;/code&gt; install task…&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;include_tasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ping.yaml

&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install 
  &lt;span class=&quot;token key atrule&quot;&gt;dnf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congrats, we have an &lt;code&gt;install-htop&lt;/code&gt; role defined!&lt;/p&gt;
&lt;h3&gt;Including roles in playbooks&lt;/h3&gt;
&lt;p&gt;Just as we included &lt;code&gt;vars&lt;/code&gt; and &lt;code&gt;tasks&lt;/code&gt; in the playbook, if we already have
tasks and vars defined in a &lt;em&gt;role&lt;/em&gt;, we can instead include that &lt;em&gt;role&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 127.0.0.1
  &lt;span class=&quot;token key atrule&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; local
  &lt;span class=&quot;token key atrule&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;token key atrule&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; install&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;htop&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy. Let’s run it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ansible-playbook install-htop.yaml
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;WARNING&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: Ansible is being run &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; a world writable directory &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/tmp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, ignoring it as an ansible.cfg source. For &lt;span class=&quot;token function&quot;&gt;more&lt;/span&gt; information see
https://docs.ansible.com/ansible/devel/reference_appendices/config.html&lt;span class=&quot;token comment&quot;&gt;#cfg-in-world-writable-dir&lt;/span&gt;

PLAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Gathering Facts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;install-htop &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; include_tasks&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
included: /tmp/roles/install-htop/tasks/ping.yaml &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;install-htop &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; Ping &lt;span class=&quot;token function&quot;&gt;host&lt;/span&gt; first&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

TASK &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;install-htop &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; Install htop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; ****************************************
ok: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

PLAY RECAP ****************************************
&lt;span class=&quot;token number&quot;&gt;127.0&lt;/span&gt;.0.1                  &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it still works :)&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While only the tip of the iceburg, I think we have covered enough basics
to make &lt;em&gt;something&lt;/em&gt; useful. Using this small amount of Ansible
knowledge, I have been able to create playbooks that configure applications,
update all my computers, and setup each of my machines when I reformat them.
However, don’t let that stop you from learning even more! Ansible is a powerful
tool and worth any amount of time invested into it. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>TSQA 2020</title>
    <link href="https://ryan.himmelwright.net/post/tsqa-2020/" />
    <updated>2020-04-22T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/tsqa-2020/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/tsqa-2020/hp5HQG_Zsh-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/tsqa-2020/hp5HQG_Zsh-1024.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1024&quot; height=&quot;768&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;TSQA 2020 - Durham Convention Center, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Several weeks ago I attend TSQA 2020, a conference presented every two years by
the &lt;a href=&quot;https://tsqa.org&quot;&gt;Triangle Software Quality Association&lt;/a&gt; (TSQA). Despite
being hosted by my local software testing group, the speakers and
attendees were from all over the country. While only a single-day conference,
it was packed full with solid advice and ideas I left with. Here are a few.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;em&gt;Just to clarify: TSQA occured in February, right before COVID-19 really started spreading
in the US. I’m just very late in this post.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;TSQA 2020&lt;/h2&gt;
&lt;h3&gt;Getting There&lt;/h3&gt;
&lt;p&gt;TSQA was held at the Durham Convention
Center, which is located in the middle
of downtown Durham (NC, USA). Being so close to our office, I managed to help
convince/remind several of my co-workers to register last minute. On the
morning of the event, I drove the one mile to the office parking garage, then
hustled coat-less through the cold morning wind to the conference center.&lt;/p&gt;
&lt;p&gt;As I walked inside, I immediately saw my manager at the entrance handing out
bandages, and knew I was in the right place (he was a TSQA volunteer).  After
saying hello and checking in, I made my way to the main ballroom to grab some
food. After awhile, my co-workers started to trickle in, so we found a table
near the front and gathered for the opening statements.&lt;/p&gt;
&lt;h3&gt;Keynote&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/tsqa-2020/IicTkDb2vZ-750.webp 750w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/tsqa-2020/IicTkDb2vZ-750.jpeg&quot; alt=&quot;The Jetsons&quot; width=&quot;750&quot; height=&quot;500&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Jetsons.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;They keynote, presented by &lt;a href=&quot;http://angiejones.tech&quot;&gt;Angie Jones&lt;/a&gt; was an
entertaining look at the history and future of technology. She took ideas that
were shown in &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Jetsons&quot;&gt;The Jetsons&lt;/a&gt;, and
compared them to how close (or far) we have come to many of them. She then used
this as an example of how we should be planning to test &lt;em&gt;future&lt;/em&gt; technology,
because like it or not, it’s coming. She concluded by providing some examples
of what testing in the future could look like.&lt;/p&gt;
&lt;h2&gt;Talks&lt;/h2&gt;
&lt;p&gt;For the rest of the day, I attended several talks interspaced with a lunch and
snack break. I mainly focused on attending talks around automation, but also
went to a few about developing test cases or UI testing tools. Similar to my
&lt;a href=&quot;https://ryan.himmelwright.net/post/ato2019/&quot;&gt;All Things Open 2019 post&lt;/a&gt;, instead of narrating each talk I
went to, I picked out few lessons I learned and will share them below.&lt;/p&gt;
&lt;p&gt;After the conference ended, my co-workers and I made our way back to the office
to share experiences and debate our thoughts over a drink.  It’s always great
to hash out ideas with others after a conference, while they are still fresh.
Also… it’s fun :) .&lt;/p&gt;
&lt;h2&gt;Lessons Learned/Strengthened&lt;/h2&gt;
&lt;p&gt;Now to summarize a few of the many lessons I picked up while at TSQA 2020. I’ve
heard many of these suggestions before, but the speakers presented them so
well, I really want to ensure I start implementing the at work.  Lets get
started.&lt;/p&gt;
&lt;h3&gt;No failing tests&lt;/h3&gt;
&lt;p&gt;It is all too easy to let a backlog of failing tests build up. This may be
due to the test being out of date, a low priority issue, or worse of
all… just a flaky test. Regardless of the reason, failing tests really should
be mitigated &lt;em&gt;immediately&lt;/em&gt;, for several reasons.&lt;/p&gt;
&lt;p&gt;Leaving failing tests to continuously run causes failure fatigue. This
normalizes the failing tests and causes a team to ignore &lt;em&gt;other&lt;/em&gt; failing tests in
the future. Basically, it decreases the competence in the test suite, and thus
the QE team as a whole.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/tsqa-2020/2IAD7mKWMY-500.webp 500w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/tsqa-2020/2IAD7mKWMY-500.jpeg&quot; alt=&quot;Failing tests meme&quot; width=&quot;500&quot; height=&quot;484&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;This is supposed to be a joke, but it&#39;s actually the point I&#39;m sharing. If tests are failing, disable or get rid of them, *AFTER* filing an issue!.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So, if a project has continuously failing tests, try to fix them as quick
as possible. If there currently isn’t time to fix it (we’ve all been there),
that’s fine, but file an issue to &lt;em&gt;remember&lt;/em&gt; to fix it later. Then disable it.
It will make a test suite more meaningful, because each failure
&lt;em&gt;means something important&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;One last statement here: &lt;strong&gt;No&lt;/strong&gt; flaky tests either. If we can’t depend on them,
they aren’t worth having.  Either figure out how to fix them so they are
consistent, or remove them.&lt;/p&gt;
&lt;h3&gt;Write tests to pass for known issues&lt;/h3&gt;
&lt;p&gt;Now, lets expand that suggestion of disabling failing tests, and write tests
that &lt;em&gt;pass&lt;/em&gt; when a &lt;em&gt;known&lt;/em&gt; defect happens. While sounding counter-intuitive at
first, this idea makes more sense if you accept the fact that tests don’t
actually &lt;em&gt;find&lt;/em&gt; defects. Rather, automated tests depict the &lt;em&gt;state&lt;/em&gt; of a
system, and fail when &lt;em&gt;something has changed&lt;/em&gt;. A passed or failed test simply
acts as a &lt;em&gt;data point&lt;/em&gt;. When a quality engineer goes and investigates the test
results, &lt;em&gt;they&lt;/em&gt; determine if there is a defect based on the data.&lt;/p&gt;
&lt;p&gt;For example, lets assume I have a test that asserts that an api call returns a
&lt;code&gt;200&lt;/code&gt; status. However, due to a known issue, it is currently returning &lt;code&gt;404&lt;/code&gt;s.
I &lt;em&gt;know&lt;/em&gt; that in its current state, the system returns a &lt;code&gt;404&lt;/code&gt; for that api
call, so I can change/add a test to assert that it is indeed, returning &lt;code&gt;404&lt;/code&gt;s.
This technique &lt;em&gt;removes a failing test&lt;/em&gt; (as suggest in the previous section),
while allowing us to maintain that data point. The test will pass while it
continues to return &lt;code&gt;404&lt;/code&gt;’s, but will fail and notify us of any related state
changes.&lt;/p&gt;
&lt;p&gt;Those changes could be from the developers merging a bug fix… or a
&lt;em&gt;new&lt;/em&gt; defect popping up, resulting in the api call now returning &lt;code&gt;500&lt;/code&gt; errors.
Regardless, we know that something has changed again, and we should investigate. By
comparison, an always-failing or fully removed test would not have demanded our
attention so easily.&lt;/p&gt;
&lt;h3&gt;Better Documentation&lt;/h3&gt;
&lt;p&gt;So… if we have our tests setup to &lt;em&gt;pass&lt;/em&gt; for known defects, lets make one
thing clear… we need to make sure we have &lt;strong&gt;amazing documentation&lt;/strong&gt;. This is
imperative.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/tsqa-2020/j2x_zViczf-610.webp 610w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/tsqa-2020/j2x_zViczf-610.jpeg&quot; alt=&quot;Documentation Meme&quot; width=&quot;610&quot; height=&quot;259&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Any test designed to pass on a known defect should contain a few pieces of
information in its documentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Why the test is there&lt;/em&gt;: If a test is checking for undesired
behavior, it is a good idea to quickly document something along the lines
of ‘This test is checking for behavior we don’t want. It is just here
until an issue is fixed’.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;The issue number&lt;/em&gt;. If a test exists until a known issue is resolved…
please include the issue/bug number in the documentation. This will make it
much easier for others (or you!) to find more information, or check if the
issue is already closed once the test starts failing (due to a fix).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;What we expect from the test&lt;/em&gt;: Try to document why the test is
currently passing, and what we expect when it starts failing again.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Continuing with our example from above:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“This test has been altered until issue #23483 is resolved. Currently, this api
call returns a 404 status. If the issue is resolved, it should return a 200
status, at which point this test needs to be updated.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lastly, remember to periodically clean up the documentation.  When filing an
issue, state &lt;em&gt;were&lt;/em&gt; a failing test can be found if it is being updated to match
the behavior. This way when the issue is closed, it will be easier to go update
the test. Temporary test states can also be marked with a tag, such as &lt;code&gt;TODO&lt;/code&gt;
to make them easier to search through.  Honestly, whatever works for your team.
The goal is to create enough context so that someone else is able to know what
is going on and fix it without having to find you.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Overall, TSQA 2020 turned out to be a wonderful conference and was a desirable
size. It was not crazy and over-crowded, but also not so small that it felt
awkward.  It had a diverse mix of people from all over the country, and the
highest percentage of women I have ever seen at a tech conference. I had a
great time and definitely plan to attend the next one…  even if I have to
travel more than a mile next time.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating Tests For This Website - Docker Jenkins Nodes</title>
    <link href="https://ryan.himmelwright.net/post/creating-website-tests-docker-nodes/" />
    <updated>2020-03-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/creating-website-tests-docker-nodes/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-docker-nodes/sX6ujKl1yD-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-docker-nodes/sX6ujKl1yD-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;PNC Arena (Parking Lot), Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Okay, quick post! Previously, I wrote about how I &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/&quot;&gt;automated my website
tests&lt;/a&gt; using Jenkins. When I wrote that post,
I had the tests run on &lt;code&gt;any&lt;/code&gt; node. I &lt;em&gt;wanted&lt;/em&gt; to have the tests
run inside a fedora docker container, but ran into issues configuring it.
With the problem now long fixed, I decided I would write a quick update post about
switching the pipeline to use container nodes.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Why Switch&lt;/h3&gt;
&lt;p&gt;When I first defined the pipeline, I had it use &lt;code&gt;any&lt;/code&gt; node for the agent:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;pipeline &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    agent any

    stages &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// This is where the stages will be defined //&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This just runs the job on any available node, which for me was just the same VM
server I was running Jenkins on. This was fine, but for testing I want to make
sure &lt;em&gt;everything&lt;/em&gt; in my automation is configured and up to date. The best
way to ensure that, is with &lt;em&gt;clean&lt;/em&gt; runs.&lt;/p&gt;
&lt;p&gt;When using docker for the pipeline agent, a new container is created to run the
pipeline in, and then destroyed when completed. This means all packages and
dependencies &lt;em&gt;must&lt;/em&gt; be defined correctly, or the run will fail. This is what we
want.&lt;/p&gt;
&lt;h3&gt;Using Docker Nodes&lt;/h3&gt;
&lt;p&gt;First, the obvious: make sure &lt;code&gt;docker&lt;/code&gt; is installed on the desired
Jenkins nodes. I won’t cover this as it can be different for
every user (and I already had &lt;code&gt;docker&lt;/code&gt; installed on my Jenkins host).&lt;/p&gt;
&lt;p&gt;With docker installed, next make sure the Jenkins server has the &lt;a href=&quot;https://plugins.jenkins.io/docker-slaves/&quot;&gt;Docker
Slaves&lt;/a&gt; (and possibly &lt;a href=&quot;https://plugins.jenkins.io/docker-workflow/&quot;&gt;Docker
Pipeline&lt;/a&gt;) plug-in(s) installed.&lt;/p&gt;
&lt;p&gt;Lastly, with some docker plug-ins enabled, switch the &lt;code&gt;agent&lt;/code&gt; statement to use a
container image. I choose to use the &lt;code&gt;fedora:31&lt;/code&gt; image:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;pipeline &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    agent &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        docker &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            image &lt;span class=&quot;token string&quot;&gt;&#39;fedora:31&#39;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// rest of the pipeline&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Fixing root/sudo error&lt;/h3&gt;
&lt;p&gt;When I first set the pipeline to use the fedora image, it kept failing.
Specifically, the &lt;code&gt;sudo dnf&lt;/code&gt; steps would fail because the &lt;code&gt;sudo&lt;/code&gt; command didn’t
exist in the container. If I removed &lt;code&gt;sudo&lt;/code&gt; from the command…  it still
failed because I didn’t have permissions to run &lt;code&gt;dnf&lt;/code&gt; inside the container ಠ_ಠ
(yes, the user was &lt;code&gt;root&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;After some research, I learned that it wasn’t passing the root permissions to
the container, and I could “solve” this issue by providing the &lt;code&gt;-u&lt;/code&gt; flag with
&lt;code&gt;0:0&lt;/code&gt; as an arg to the docker agent:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;pipeline &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    agent &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        docker &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            image &lt;span class=&quot;token string&quot;&gt;&#39;fedora:31&#39;&lt;/span&gt;
            args &lt;span class=&quot;token string&quot;&gt;&#39;-u 0:0&#39;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// rest of the pipeline&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don’t &lt;em&gt;love&lt;/em&gt; this solution… but it seems to work.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Like I said, this was just a quick update about switching my
test nodes to use docker containers. Honestly, I’d much rather try to use
&lt;a href=&quot;http://podman.io&quot;&gt;podman&lt;/a&gt; containers for my test agents, but I’m sure that
would be much more complicated currently. Maybe in the future…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating Tests For This Website - Links</title>
    <link href="https://ryan.himmelwright.net/post/creating-website-tests-links/" />
    <updated>2020-03-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/creating-website-tests-links/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links/NbchCcy2g8-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links/NbchCcy2g8-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;PNC Arena, Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my previous two posts, I created a &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/&quot;&gt;test framework for my
website&lt;/a&gt;, and &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/&quot;&gt;automated it using
Jenkins&lt;/a&gt;. But we can do better. One of the
most annoying things when maintaining (or even reading) something on the
internet, are broken links. While I cannot control the &lt;em&gt;availability&lt;/em&gt; of
content outside the website, I &lt;em&gt;can&lt;/em&gt; choose to remove links if they are
broken. So, in this post, we will add tests to ensure that links in our posts
are working. Well, at least the markdown ones.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What to Test&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links/SghGmtosAi-815.webp 815w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links/SghGmtosAi-815.jpeg&quot; alt=&quot;Google 404 Error Page&quot; width=&quot;815&quot; height=&quot;474&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Google 404 Error Page&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For this test set, we will be scanning the content files of all of the posts, and
grabbing every markdown link defined in them. With the links known, we
will then make a request to each one to check that it is available. If we
can connect, the test passes. If not (ex: we get a 404), it fails.&lt;/p&gt;
&lt;h2&gt;Adding Utility Functions&lt;/h2&gt;
&lt;p&gt;Before we are able to write the test function, we first need to add to the
utility functions. These will allow us to get the post’s file paths, grab their
content, and extract all the markdown links from that content.&lt;/p&gt;
&lt;h3&gt;get_file_paths&lt;/h3&gt;
&lt;p&gt;First, lets define a new helper function, &lt;code&gt;get_file_paths&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_file_paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; extension&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Collects the paths of all files of a directory&quot;&quot;&quot;&lt;/span&gt;
    file_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    root_path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expanduser&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; listdir&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root_path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# If extension provided, check file has that extension&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; extension&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;extension&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                file_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# Otherwise, add everything&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            file_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; file_list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When provided a directory path (&lt;code&gt;src&lt;/code&gt;), this function will return a list of all the
file paths in that directory. Optionally, the &lt;code&gt;extension&lt;/code&gt; parameter can be
supplied to only return files of that extension type (in our case, &lt;code&gt;md&lt;/code&gt;). This
will be used to grab the paths of all of the website post source files.&lt;/p&gt;
&lt;h3&gt;get_file_content&lt;/h3&gt;
&lt;p&gt;Now lets define &lt;code&gt;get_file_content&lt;/code&gt;. This function will take the file lists
generated from &lt;code&gt;get_file_paths&lt;/code&gt;, grab the content from those files,
and return a dictionary of all the data.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_file_content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_list&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Grabs all the content from a list of file paths.&quot;&quot;&quot;&lt;/span&gt;
    content_all_files &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; file_list&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        file_content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;read&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        content_all_files&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;basename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file_content
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content_all_files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The returned dictionary uses the filename as the key, and the content set to the
value. For example:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;post1.md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;This is the text of post1.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;post2.md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is the text of post2. Basically the same ol&#39; stuff.&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;get_md_links&lt;/h3&gt;
&lt;p&gt;Lastly, lets define &lt;code&gt;get_md_links&lt;/code&gt;. This function takes the content dictionary
returned by &lt;code&gt;get_file_content&lt;/code&gt;, and uses some &lt;a href=&quot;https://en.wikipedia.org/wiki/Regular_expression&quot;&gt;regular
expression&lt;/a&gt; magic to match
the markdown links:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_md_links&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content_dict&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; regex&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;[.*?&#92;]&#92;((.*?)&#92;)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Parses the dictionary of content strings, and pulls out the url of any links.&quot;&quot;&quot;&lt;/span&gt;
    p &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;regex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    all_links &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; content_dict&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content_dict&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replace&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        match_iter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;finditer&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; match_iter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# Regex can&#39;t properly match urls with parens in them, so skip.&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;(&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                all_links&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; all_links&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, the function compiles the regular expression defined by the &lt;code&gt;regex&lt;/code&gt;
parameter. Next, it loops through all the data in the content dictionary,
strips the newline characters, and then grabs all the regex matches.&lt;/p&gt;
&lt;p&gt;Unfortunately, the regex expression can’t properly match markdown formated urls
with parenthesis in them, so we have to check if each match has a &lt;code&gt;(&lt;/code&gt; in it. If
it does, the url is thrown away because we cannot be sure we matched the full
&lt;code&gt;url&lt;/code&gt;. If there are no parenthesis, the url is added to our saved list. After
parsing all the values of the content dictionary, a list of the matched urls is
returned.&lt;/p&gt;
&lt;h2&gt;Adding to &lt;a href=&quot;http://conftest.py&quot;&gt;conftest.py&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With our new utility functions defined, we can next add a new fixture (and it’s
helper function) to the &lt;code&gt;conftest.py&lt;/code&gt; file. Lets start with the fixture’s
helper function, &lt;code&gt;post_md_link()&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;post_md_links&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the md_link object of the md links in all the posts.&quot;&quot;&quot;&lt;/span&gt;
    all_post_files &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_file_paths&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;POST_DIR&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    all_post_contents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_file_content&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_post_files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    all_post_md_links &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_md_links&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_post_contents&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Return de-dup list&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_post_md_links&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function uses the utility functions we just wrote above, to extract
all of the markdown links from the post files found at the
&lt;code&gt;POST_DIR&lt;/code&gt; constant location. It then returns a de-duplicated list of all
the links.&lt;/p&gt;
&lt;p&gt;Now, we can define the fixture, &lt;code&gt;post_md_link&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;post_md_links&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;post_md_link&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the md_link object for a md link found in a post.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;param&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar to the fixtures in the &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/&quot;&gt;pages
tests&lt;/a&gt;, this one will allow tests to map
across all the links found in the markdown pages, so a test will run for each
link.&lt;/p&gt;
&lt;h2&gt;Writing the markdown link test&lt;/h2&gt;
&lt;p&gt;Finally, time to write the &lt;em&gt;one and only test function&lt;/em&gt; in this post:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_md_links&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Checks that the markdown links are not broken.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post_md_link
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BASE_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; post_md_link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The link &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is not found.&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The link &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post_md_link&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is forbidden.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because I link to both internal and external pages in my posts, I have to prep
my urls a bit. So, I first check if the link starts with &lt;code&gt;http&lt;/code&gt; (which would
also match ones starting with &lt;code&gt;https&lt;/code&gt;). If it does, we can leave the link as
is. If it doesn’t, we can assume the link is an internal one (ex:
&lt;code&gt;/post/creating-website-tests-links/&lt;/code&gt;), and we need to prepend it with the
&lt;code&gt;BASE_URL&lt;/code&gt; constant.&lt;/p&gt;
&lt;p&gt;With a proper url, we can use &lt;code&gt;requests.get()&lt;/code&gt; to attempt to retrieve a
response code from the page. If we get a response, I then assert that the
&lt;code&gt;status_code&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; &lt;code&gt;404&lt;/code&gt; or &lt;code&gt;403&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side Note:&lt;/em&gt; I started by asserting that each link returned a &lt;code&gt;200&lt;/code&gt; status, but
quickly learned that it was a bad idea, because I was testing mostly external
links.  I never got all the tests to pass because they would often return
odd 500-level errors for issues that quite frankly, doesn’t matter to me. For
example, one site kept return a 500-level error I think because their servers
were ‘under a slightly higher load’… but when I went to the link, the page
loaded fine.&lt;/p&gt;
&lt;p&gt;In the end, I decided I wasn’t trying to test the issues the websites I linked
to were having, but instead just wanted to make sure that &lt;em&gt;my links&lt;/em&gt; weren’t
&lt;em&gt;broken&lt;/em&gt;.  So, I now just ensure that I’m not getting &lt;code&gt;404&lt;/code&gt; or &lt;code&gt;403&lt;/code&gt;’s, and I’m
happy with that.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;While I am very happy with the coverage these tests provide, they do have some
limitations to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They cannot match urls with parentheses&lt;/li&gt;
&lt;li&gt;Currently, they only check that pages do not return &lt;code&gt;403&lt;/code&gt; and &lt;code&gt;404&lt;/code&gt; errors. This means I
could possibly still have broken links due to permission errors or other
issues. I plan to expand this assert list in the future to cover more
cases.&lt;/li&gt;
&lt;li&gt;I’m currently only testing markdown links. This doesn’t grab any html links I
have in my posts.
&lt;ul&gt;
&lt;li&gt;On a similar note, because I link most of my images with html, it also
isn’t testing if my images are broken.&lt;/li&gt;
&lt;li&gt;I’d like to add tests for both of these issues eventually, but
decided testing the markdown links was the best place to start.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sometimes tests fail because a site is down. No biggie. I just usually wait
a bit and then run the tests again before deciding to remove the link.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links/ry5FnDHyJ6-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-links/ry5FnDHyJ6-1200.jpeg&quot; alt=&quot;Passing tests, including new link tests&quot; width=&quot;1200&quot; height=&quot;361&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Passing tests, including the new markdown link tests&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;That’s it. By adding a few easy helper functions, a new fixture, and a &lt;em&gt;single&lt;/em&gt;
test function, We’ve expanded my test results from 70 to over 420 tests (and
growing).&lt;/p&gt;
&lt;p&gt;More important than the number of tests, is &lt;em&gt;what&lt;/em&gt; the results tell us. A
failing test tells me that one of my markdown links &lt;em&gt;might&lt;/em&gt; be broken. These
tests have &lt;em&gt;already&lt;/em&gt; been beneficial to me, as I ended updating/removing
probably about 100 or so bad links from my archived posts while implementing
this test. So, I’d say it was worthwhile!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating Tests For This Website - CI</title>
    <link href="https://ryan.himmelwright.net/post/creating-website-tests-ci/" />
    <updated>2020-02-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/creating-website-tests-ci/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/wUmApzniyo-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/wUmApzniyo-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;PNC Arena, Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/&quot;&gt;last post&lt;/a&gt;, I setup some simple
testing for my website builds to that ensure that pages were being served
correctly.  However, I can’t trust myself to always manually run the tests
before merging a branch into &lt;code&gt;master&lt;/code&gt;. Luckily, I have
&lt;a href=&quot;https://jenkins.io&quot;&gt;Jenkins&lt;/a&gt; to take care of all the “&lt;em&gt;responsible&lt;/em&gt;” tasks. In
this post, we will take the test framework created in the previous post…  and
automate it.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What I’m Using&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/PZLlbajFlW-185.webp 185w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/PZLlbajFlW-185.jpeg&quot; alt=&quot;The jenkins logo&quot; width=&quot;185&quot; height=&quot;256&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Jenkins Logo&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have started using &lt;a href=&quot;https://gitlab.com&quot;&gt;Gitlab&lt;/a&gt; for more of my projects
recently, but have decided to keep my website source hosted on Github for the time
being. So, I won’t be using Gitlab’s CI/CD tools for &lt;em&gt;this&lt;/em&gt;, but I wanted it
to be known that this automation is &lt;em&gt;very&lt;/em&gt; straight forward and could be easily
accomplished there as well.&lt;/p&gt;
&lt;p&gt;For this project, because my website is hosted on Github, and I &lt;em&gt;already&lt;/em&gt;
have &lt;a href=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/&quot;&gt;my own Jenkins server&lt;/a&gt; configured… I will be
using a Jenkins pipeline. First, we will create the pipeline file to add
to the git repo. Then, we will use the pipeline to configure a new
&lt;em&gt;multi-branch&lt;/em&gt; pipeline in Jenkins.&lt;/p&gt;
&lt;h2&gt;Jenkinsfile&lt;/h2&gt;
&lt;p&gt;Lets start by creating the Jenkinsfile. Create a new file named &lt;code&gt;Jenkinsfile&lt;/code&gt;
in the project directory. Next, lets create a new pipeline and get ready to add
stages:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;pipeline &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    agent any

    stages &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// This is where the stages will be defined //&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(Make sure to add the closing &lt;code&gt;}&lt;/code&gt;&#39;s!)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Stages&lt;/h2&gt;
&lt;p&gt;Now, lets add the stages. A stage is a named, functional chunk in our pipeline.
Breaking the pipeline into stages will help keep all the various steps
organized, as well as make it easier to follow along as the pipeline runs.&lt;/p&gt;
&lt;p&gt;Each of the following stage definitions will be placed inside the &lt;code&gt;stages { .. }&lt;/code&gt; section we defined above (&lt;em&gt;in the same order as they are listed!&lt;/em&gt;).&lt;/p&gt;
&lt;h3&gt;Setup Deps&lt;/h3&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Setup Deps&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    steps &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        sh &lt;span class=&quot;token string&quot;&gt;&#39;sudo yum update -y&#39;&lt;/span&gt;
        sh &lt;span class=&quot;token string&quot;&gt;&#39;sudo yum install -y epel-release&#39;&lt;/span&gt;
        sh &lt;span class=&quot;token string&quot;&gt;&#39;sudo yum install -y hugo python36-pytest&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first stage is ‘Setup Deps’. This stage handles installing any dependencies
our Jenkins node will need installed to run the tests. Our tests will require
&lt;code&gt;hugo&lt;/code&gt;, &lt;code&gt;pytest&lt;/code&gt;, and &lt;code&gt;python3&lt;/code&gt;. It will also require some &lt;code&gt;pip&lt;/code&gt; packages, but
we will get to that later.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: My current Jenkins node is running CentOS, so my package manager commands
are specific to that. As always, adjust accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Start Hugo Server&lt;/h3&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Start Hugo Server&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    steps &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        sh &lt;span class=&quot;token string&quot;&gt;&#39;hugo serve &amp;amp;&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;hugo&lt;/code&gt; installed (and presumably being inside the website’s git repo…),
we can start the web server. This is done with &lt;code&gt;hugo serve&lt;/code&gt;. The &lt;code&gt;&amp;amp;&lt;/code&gt; is used to
have the server run as a background process so that it won’t be killed when the
pipeline to continues on.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Make sure the tests are set to point to &lt;code&gt;localhost:1313&lt;/code&gt;, as that is
where &lt;code&gt;hugo&lt;/code&gt; will try to serve the website by default.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Setup Python&lt;/h3&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Setup Tests&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    steps &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        sh &lt;span class=&quot;token string&quot;&gt;&#39;pip3 install pipenv --user&#39;&lt;/span&gt;
        sh &lt;span class=&quot;token string&quot;&gt;&#39;python3 -m pipenv install&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, lets configure &lt;code&gt;python&lt;/code&gt; by setting up an environment and installing the
packages we need in it. First, I use &lt;code&gt;pip3&lt;/code&gt; to install &lt;code&gt;pipenv&lt;/code&gt;. Then, I have
&lt;code&gt;pipenv&lt;/code&gt; install the tests’ required python packages, which are defined in the
repo’s &lt;code&gt;Pipfile&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Run Tests&lt;/h3&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Run Tests&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    steps &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        sh &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&#39;
            set +e
            python3 -m pipenv run pytest -v --junit-xml himmallright-source-test-report.xml .
            set -e
        &#39;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stripIndent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test time. I again utilize &lt;code&gt;pipenv&lt;/code&gt; here, by having it call the test command
(&lt;code&gt;pytest -v --junit-xml himmallright-source-test-report.xml .&lt;/code&gt;) so that it runs
in the pipenv virtual environment.&lt;/p&gt;
&lt;p&gt;Two things to note here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;--junit-xml&lt;/code&gt; flag defines a xml filename to write the junit test report
to. This will be used by Jenkins to collect the test results.&lt;/li&gt;
&lt;li&gt;The test command is wrapped between &lt;code&gt;set +e&lt;/code&gt; and &lt;code&gt;set -e&lt;/code&gt; commands, which
allows tests to fail but without triggering a &lt;em&gt;pipeline&lt;/em&gt; failure in
Jenkins.  This way even if tests fail, we make it all the way through
collection so we can see &lt;em&gt;what&lt;/em&gt; tests failed and &lt;em&gt;why&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Collect Test Results&lt;/h3&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Collect Test Resuts&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    steps &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        archiveArtifacts &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;himmallright-source-test-report.xml&quot;&lt;/span&gt;&lt;/span&gt;
        junit &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;himmallright-source-test-report.xml&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we archive the junit report xml file as a Jenkins artifact (just in
case). Finally, we have the &lt;a href=&quot;https://plugins.jenkins.io/junit/&quot;&gt;junit&lt;/a&gt; plugin
collect the report.&lt;/p&gt;
&lt;h3&gt;Save &amp;amp; Commit&lt;/h3&gt;
&lt;p&gt;That should be it for the pipeline file! Commit and push it to the git repo,
and we can start working with it in Jenkins!&lt;/p&gt;
&lt;h2&gt;Multibranch Pipelines&lt;/h2&gt;
&lt;p&gt;With the &lt;code&gt;Jenkinsfile&lt;/code&gt; in the repo, we can create the pipeline! Specifically,
we will be creating a mult-branch pipeline. A &lt;em&gt;multi-branch&lt;/em&gt; job scans a git
project, and creates a separate pipeline for each branch or PR in the repo.
This is beneficial for testing, as it will automatically instantiate a test
pipeline against a newly created PR, so we can verify that the PR passes the
tests before merging it into the &lt;code&gt;master&lt;/code&gt; branch. Additionally, it lets us make
sure we aren’t breaking anything &lt;em&gt;while&lt;/em&gt; working in a new branch.&lt;/p&gt;
&lt;h3&gt;Creating the Pipeline&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/cilBH-AmPz-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/cilBH-AmPz-1200.jpeg&quot; alt=&quot;Creating a new Jenkins Item&quot; width=&quot;1200&quot; height=&quot;877&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Select Multibranch Pipeline from the new item menu in Jenkins.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To create a Multi-Branch pipeline, select &lt;em&gt;New Item&lt;/em&gt; in the menu on the left.
Next, enter a name for the pipeline and select &lt;em&gt;Multibranch Pipeline&lt;/em&gt; at the
bottom. Click &lt;em&gt;Ok&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Configuring the Pipeline&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/5r7rmPLs5z-807.webp 807w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/5r7rmPLs5z-807.jpeg&quot; alt=&quot;Multibranch pipeline config options&quot; width=&quot;807&quot; height=&quot;1257&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Configuration options when creating the multibranch pipeline.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;On the pipelines configuration page, start by filling out the &lt;em&gt;Display Name&lt;/em&gt;
and &lt;em&gt;Description&lt;/em&gt; text boxes. Next, go down to &lt;em&gt;Branch Sources&lt;/em&gt; and click on
&lt;em&gt;Add source&lt;/em&gt;. My website is currently hosted on Github, so I will select that.
However, select &lt;em&gt;Git&lt;/em&gt; if your project is hosted on another &lt;code&gt;git&lt;/code&gt; service.&lt;/p&gt;
&lt;p&gt;Add the Repository URL and choose the pipeline Behaviors. The Behaviors define
how the pipeline will split up branches. For example, it can be selected to
only discover branches that are also PRs.&lt;/p&gt;
&lt;p&gt;Next, in the &lt;em&gt;Build Configuration&lt;/em&gt; section, be sure that the &lt;em&gt;Script Path&lt;/em&gt;
defines the path where the &lt;code&gt;Jenkinsfile&lt;/code&gt; is located. If it’s in the root
directory (and named &lt;code&gt;Jenkinsfile&lt;/code&gt;), the default should work.&lt;/p&gt;
&lt;p&gt;Lastly, select an interval to automatically check the repo for changes in the
&lt;em&gt;Scan Repository Triggers&lt;/em&gt; section. This step is optional, but I highly
recommend it.&lt;/p&gt;
&lt;p&gt;That’s all we &lt;em&gt;need&lt;/em&gt; to setup, but feel free to research more options. I mostly
have defaults selected for the rest. When complete, hit &lt;em&gt;Save&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Running Pipelines&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/frWw-hTy-v-1168.webp 1168w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/frWw-hTy-v-1168.jpeg&quot; alt=&quot;The multibranch pipeline overview page&quot; width=&quot;1168&quot; height=&quot;396&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The multibranch pipeline overview page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Once the multibranch pipeline is created, it should scan the repo to detect any
branches or pull requests that have defined &lt;code&gt;Jenkinsfile&lt;/code&gt;s. It will create an
job item in the list for each branch/PR it detects (and kick off runs for
each).&lt;/p&gt;
&lt;p&gt;To manually start a scan, select &lt;em&gt;Scan Repository Now&lt;/em&gt; in the menu on the left,
and it will scan all the branches again, looking for changes, and kicking off
pipeline runs for any branch or pr that has changed.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/pDCZ5Xnz9--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/pDCZ5Xnz9--1200.jpeg&quot; alt=&quot;The overview page of a single branch&quot; width=&quot;1200&quot; height=&quot;677&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The overview page of a single branch.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To manually start a specific branch run, click on the branch name to enter the
branch’s job overview page. Then, simply click &lt;em&gt;Build Now&lt;/em&gt; on the left. Once
the run starts, it runs like a normal jenkins job and can be viewed by clicking
the job’s run number to the bottom left. The job’s progress can then be viewed
on that page, or using &lt;em&gt;Blue Ocean&lt;/em&gt; (Recommended).&lt;/p&gt;
&lt;h2&gt;Viewing Results&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/Ongkbn5CTZ-1173.webp 1173w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-ci/Ongkbn5CTZ-1173.jpeg&quot; alt=&quot;The overview page of a single branch&quot; width=&quot;1173&quot; height=&quot;610&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The overview page of a single branch.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Personally, I prefer to always view the test results using the Blue Ocean
viewer. Once a job completes, select the &lt;em&gt;Tests&lt;/em&gt; tab at the top of the viewer
to see the collected test results (this is what the junit steps in our pipeline
does). If all the tests passed, the page will be green and list all the
completed tests. If some failed, it will be yellow. When there are failed
tests, they can be clicked, and the row will expand to show the failed test’s
error message and stack trace. For my test set, this makes it easy to see which
page failed, and even know what status code was &lt;em&gt;actually&lt;/em&gt; returned in the
response (by looking at the stack trace). Very helpful!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There we go! Not only do we now have the website tests automated as a pipeline,
but as a &lt;em&gt;multibranch&lt;/em&gt; pipeline. This should help automatically ensure that
nothing breaks as I edit and add to the website. It will even run the tests
against all my PRs, so I can be confident that when I merge to master, it won’t
slowly degrade my website over time. I have one more post about these tests
planned, but in the meantime… enjoy Jenkins!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating Tests For This Website - Pages</title>
    <link href="https://ryan.himmelwright.net/post/creating-website-tests-pages/" />
    <updated>2020-02-23T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/creating-website-tests-pages/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/xdhrEr39_s-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/xdhrEr39_s-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;PNC Arena, Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As this website grows, there is an increasing amount of complexity. More posts,
more images, and more links. I’ve gotten better at breaking work up into
separate branches (instead of pushing everything straight to &lt;code&gt;master&lt;/code&gt;), but
even that isn’t enough to ensure everything works as expected when publishing
something new. Then, I thought of something obvious… I could setup some
simple testing… for my website.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What to Test&lt;/h2&gt;
&lt;p&gt;After editing a page or drafting a new post, I often wonder “how can I be
&lt;em&gt;sure&lt;/em&gt; everything will still work when I publish this change”? I question if
every post file is &lt;em&gt;actually&lt;/em&gt; being served as a web page. Or worse… I fear
that a post that isn’t &lt;em&gt;ready&lt;/em&gt; to be published might &lt;em&gt;accidentally&lt;/em&gt; get pushed
with an unrelated website fix.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Yes, this is a completely unreasonable fear given that ALL of my
website source files, drafts included, are publicly hosted on Github.
Nonetheless, the fear exists)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This will be a multi-post serries, so in this first one we will focus on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configuring the test environment&lt;/li&gt;
&lt;li&gt;Building up the testing framework&lt;/li&gt;
&lt;li&gt;Writing some basic tests to ensure:
&lt;ul&gt;
&lt;li&gt;The pages I &lt;em&gt;want&lt;/em&gt; to be served are&lt;/li&gt;
&lt;li&gt;Pages and posts that are not ready, are &lt;em&gt;not&lt;/em&gt; being served&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As my website is currently compiled using &lt;a href=&quot;https://gohugo.io&quot;&gt;hugo&lt;/a&gt;, the tests
will be centered around that framework. However, most of the information can be
applied to testing websites using other static website generators, are they are
all quite similar.&lt;/p&gt;
&lt;h2&gt;Setting up the env&lt;/h2&gt;
&lt;p&gt;I will be using
&lt;a href=&quot;https://docs.pytest.org/en/latest/contents.html&quot;&gt;pytest&lt;/a&gt; for the testing
framework, and to make all the
python dependencies a bit easier to manage, I will also use
&lt;a href=&quot;https://github.com/pypa/pipenv&quot;&gt;pipenv&lt;/a&gt;. Lastly, I usually work on a
&lt;a href=&quot;https://getfedora.org&quot;&gt;Fedora&lt;/a&gt; computer, VM, or at the very least in a Fedora
&lt;a href=&quot;https://podman.io&quot;&gt;podman&lt;/a&gt; container. So, some of my instructions use &lt;code&gt;dnf&lt;/code&gt;,
but feel free to adjust to your package manager accordingly.&lt;/p&gt;
&lt;h3&gt;Install &lt;code&gt;pipenv&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;First, lets install &lt;code&gt;pipenv&lt;/code&gt;, which is easy enough in Fedora:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; pipenv&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Install needed packages inside &lt;code&gt;pipenv shell&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;After installing, create a pipenv shell and enter it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pipenv shell&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install &lt;code&gt;pytest&lt;/code&gt; and &lt;code&gt;requests&lt;/code&gt; in the pip environment:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pip &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; pytest requests&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Creating the Test Framework&lt;/h2&gt;
&lt;p&gt;With the environment setup, we can start building up the test framework. We
will start by defining come constants, then use those when building helper
functions. Lastly, we will use the helper functions to piece together the
&lt;code&gt;conftest.py&lt;/code&gt; and &lt;code&gt;test_pages.py&lt;/code&gt; files.&lt;/p&gt;
&lt;h3&gt;Defining Constants&lt;/h3&gt;
&lt;p&gt;First, lets define some constants we can use throughout the test framework. In
the future, I might switch these to be set optionally with  CLI arguments, but
for now… they’re just static constant variables defined in a file.&lt;/p&gt;
&lt;p&gt;So first, create a new file in the &lt;code&gt;tests&lt;/code&gt; directory named &lt;code&gt;constants.py&lt;/code&gt;. In
that file, lets dump our contants:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;BASE_URL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:1313&quot;&lt;/span&gt;

SITE_PAGES &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/pages/about/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/pages/homelab/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

POST_DIR &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./content/post/&quot;&lt;/span&gt;
POST_NAMES &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;25-days-of-c&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Removed middle of &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt; because it&#39;s &lt;span class=&quot;token builtin&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;ZFS-Backups-To-LUKS-External&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, in my &lt;code&gt;constants.py&lt;/code&gt; file I have 4 variables defined:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BASE_URL&lt;/code&gt;: this is the base url for the website when running &lt;code&gt;hugo serve&lt;/code&gt;.
For most, it will default to &lt;code&gt;http://localhost:1313&lt;/code&gt;, but I have this as
a constant because I usually run my &lt;code&gt;hugo serve&lt;/code&gt; command with the &lt;code&gt;-b&lt;/code&gt; to
change it to an ip address so I can view it from other computers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SITE_PAGES&lt;/code&gt;: This is a list of paths that come &lt;em&gt;after&lt;/em&gt; the baseurl for pages that
we well be testing. For example, I want to make sure that my “about me”
page is being served, which is at &lt;code&gt;baseurl/pages/about/&lt;/code&gt;, so
&lt;code&gt;/pages/about/&lt;/code&gt; is one of the values in this constant.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST_DIR&lt;/code&gt;: This is the directory for where the post &lt;em&gt;files&lt;/em&gt; are located.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST_NAMES&lt;/code&gt;: This is a list of the names of the post &lt;em&gt;files&lt;/em&gt; (without the
&lt;code&gt;.md&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add in your values for the variables, and remember to save the file.&lt;/p&gt;
&lt;h2&gt;Writing Some Helper Utility Functions&lt;/h2&gt;
&lt;p&gt;With those constants defined, we should be ready to write some helper
functions. These are normal python functions that will be called from tests or
even test fixture functions.&lt;/p&gt;
&lt;p&gt;First, lets create &lt;code&gt;utils.py&lt;/code&gt;. The helper functions will need to use
&lt;code&gt;listdir&lt;/code&gt;, as well as the &lt;code&gt;path&lt;/code&gt; function from the &lt;code&gt;os&lt;/code&gt; module. They will also
need the regex functions. So, lets make those imports at the top of the file:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; os &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; listdir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; re&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;get_file_names&lt;/h3&gt;
&lt;p&gt;Lets define a helper function named &lt;code&gt;get_file_names&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_file_names&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; extension&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Collects the names of all files of a directory&quot;&quot;&quot;&lt;/span&gt;
    file_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    root_path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expanduser&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; listdir&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root_path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# If extension provided, check file has that extension&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; extension&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;extension&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                file_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# Otherwise, add everything&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            file_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; file_list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When provided a file path (&lt;code&gt;src&lt;/code&gt;), this function will return a list of all the
file names in the directory. Optionally, the &lt;code&gt;extension&lt;/code&gt; parameter can be
supplied to only return files of that extension type (for exapmple, &lt;code&gt;md&lt;/code&gt;). This
function will be used to grab the names of all the post source files.&lt;/p&gt;
&lt;p&gt;… and that’s all we need in &lt;code&gt;utils.py&lt;/code&gt;… for now!&lt;/p&gt;
&lt;h2&gt;Conftest&lt;/h2&gt;
&lt;p&gt;Now lets start digging into test-related stuff, by first creating a
&lt;code&gt;conftest.py&lt;/code&gt; file. This file will mostly hold the fixtures we will use for the
tests. In our particular setup, they will gather lists of pages to run multiple
calls of each test against by using &lt;code&gt;@pytest.fixture(params)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But first, lets import a few things at the top of &lt;code&gt;conftest.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; pytest
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; os &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; path

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; constants &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; BASE_URL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SITE_PAGES&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; POST_DIR&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; POST_NAMES
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; utils &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; get_file_names&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The imports include the &lt;code&gt;os.path()&lt;/code&gt; function, some of the constants we
defined, and the &lt;code&gt;get_file_names()&lt;/code&gt; helper function. Oh, And of course
&lt;code&gt;pytest&lt;/code&gt; ;) .&lt;/p&gt;
&lt;h3&gt;page_url&lt;/h3&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;SITE_PAGES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;page_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the page urls for testing.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; BASE_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;param&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first fixture, &lt;code&gt;page_url&lt;/code&gt;, is very basic. It creates a list of all of the
website pages (not posts), by combining the &lt;code&gt;BASE_URL&lt;/code&gt; with each of the values
defined in the &lt;code&gt;SITE_PAGES&lt;/code&gt; constant. This list will later be used to
paramaterize a single test across all of the page links.&lt;/p&gt;
&lt;h3&gt;post_url&lt;/h3&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;POST_NAMES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;post_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the post urls for testing.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; BASE_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/post/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;param&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next fixture, &lt;code&gt;post_url&lt;/code&gt; is basically the same as &lt;code&gt;page_url&lt;/code&gt;, except it
creates a list of all the &lt;em&gt;posts&lt;/em&gt; using the &lt;code&gt;POST_NAMES&lt;/code&gt; constant. Again, this
will be used to expand a single test into many, one for each post.&lt;/p&gt;
&lt;h3&gt;non_live_post_url&lt;/h3&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@pytest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;non_live_post_urls&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;non_live_post_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the url of a non-defined post file&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; BASE_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/post/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;param&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, we have &lt;code&gt;non_live_post_url&lt;/code&gt; with its accompanying helper function,
&lt;code&gt;non_live_post_urls&lt;/code&gt;. This pair creates a list of posts that have a markdown
file in the &lt;code&gt;/post/&lt;/code&gt; directory, but are &lt;em&gt;not&lt;/em&gt; listed in the &lt;code&gt;POST_NAMES&lt;/code&gt;
constant (so in practice, not really to be published).&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;non_live_post_urls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Returns the urls of md files that should not be live.&quot;&quot;&quot;&lt;/span&gt;
    all_post_md_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;split&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.md&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; get_file_names&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;POST_DIR&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    live_post_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; POST_NAMES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    non_live_post_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_post_md_names&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;difference&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;live_post_names&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;non_live_post_names&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, the &lt;code&gt;non_live_post_urls&lt;/code&gt; helper function returns a list of non-listed
post files. That  list is then used in &lt;code&gt;non_live_post_url&lt;/code&gt; as the
&lt;code&gt;pytest.fixture(params)&lt;/code&gt; object, much like &lt;code&gt;SITE_PAGES&lt;/code&gt; and &lt;code&gt;POST_NAMES&lt;/code&gt; were
for the previous
fixtures.&lt;/p&gt;
&lt;h2&gt;Finally… Some Tests!&lt;/h2&gt;
&lt;p&gt;Phew. Okay. With &lt;em&gt;all of that&lt;/em&gt; defined… lets create the first test file. When
&lt;code&gt;pytest&lt;/code&gt; runs, it will try to grab tests recursively from all the files down
the current directory, starting with &lt;code&gt;test&lt;/code&gt;. This first set of tests will be
checking whether a web page is being served (or not), so lets name the file
&lt;code&gt;test_pages.py&lt;/code&gt;. Again, start with the required imports. This time we only need
&lt;code&gt;pytest&lt;/code&gt; and &lt;code&gt;requests&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; pytest
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; requests&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Testing Pages&lt;/h3&gt;
&lt;p&gt;The first test will check that each page defined in the &lt;code&gt;SITE_PAGES&lt;/code&gt; constant
is being served. More specifically, we will use the &lt;code&gt;requests&lt;/code&gt; module to ensure
not only that the page is served, but returns a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200&quot;&gt;200
status&lt;/a&gt;. This
actually requires very little code to accomplish (Gotta love python) :&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_page_served&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;page_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Checks that the website pages are available&quot;&quot;&quot;&lt;/span&gt;
    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;page_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We simply define a function, &lt;code&gt;test_page_served()&lt;/code&gt;, and because it is in
&lt;code&gt;test_pages.py&lt;/code&gt;, it will be assumed to be a test by &lt;code&gt;pytest&lt;/code&gt;. We provide the
&lt;code&gt;page_url&lt;/code&gt; fixture we previously defined in &lt;code&gt;conftest.py&lt;/code&gt; as the only
parameter. This will call the &lt;code&gt;test_page_served&lt;/code&gt; test for each url
in the list generated by &lt;code&gt;page_url&lt;/code&gt;. Next, we use &lt;code&gt;requests.get()&lt;/code&gt; to make a
page request. Lastly, we &lt;code&gt;assert&lt;/code&gt; that the &lt;code&gt;status_code&lt;/code&gt; from our response is
&lt;code&gt;200&lt;/code&gt;. If it is, the test passes, if not, it fails.&lt;/p&gt;
&lt;h3&gt;Testing Posts&lt;/h3&gt;
&lt;p&gt;Next, lets test that all of the &lt;em&gt;posts&lt;/em&gt; are being served. This test works
&lt;em&gt;exactly&lt;/em&gt; the same as &lt;em&gt;test_page_served&lt;/em&gt;, except we are using the &lt;code&gt;post_url&lt;/code&gt;
fixture instead of &lt;code&gt;page_url&lt;/code&gt; to supply the links to test:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_post_served&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Checks that the desired posts are available&quot;&quot;&quot;&lt;/span&gt;
    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(While I could combine these cases into a single test function, I decided to
keep them separate for flexibility in the future)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Lets Get Fancy: Testing Unapproved Posts Are &lt;em&gt;NOT&lt;/em&gt; Served&lt;/h3&gt;
&lt;p&gt;For the last test, lets get a little bit more complicated and ensure that post files
&lt;em&gt;not&lt;/em&gt; listed in the approved list are &lt;em&gt;not&lt;/em&gt; being served. Well… it
turns out all the “fancy” code required for this test case already occured in
the &lt;code&gt;non_live_post_urls&lt;/code&gt; helper function. The &lt;em&gt;test&lt;/em&gt; function itself, is
essentially the same as what we’ve already encountered &lt;em&gt;except&lt;/em&gt; that we are
now checking for a &lt;code&gt;404&lt;/code&gt; return status instead of &lt;code&gt;200&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_non_defined_posts_not_served&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;non_live_post_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;Checks that a non-defined post is NOT available&quot;&quot;&quot;&lt;/span&gt;
    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;non_live_post_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status_code &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That defines all of the tests for this first set! Don’t let only having three
test functions fool you, they should generate over 70 test results when run!
(For my website, at the time of writing this post)&lt;/p&gt;
&lt;h2&gt;Lets Run Some Tests!&lt;/h2&gt;
&lt;p&gt;Finally, we should be able to run the tests. To do so, first ensure that you
are in the pipenv by running &lt;code&gt;pipenv shell&lt;/code&gt;, &lt;em&gt;or&lt;/em&gt; you can run the tests from
outside the pipenv using &lt;code&gt;pipenv run COMMAND&lt;/code&gt;. Next, call:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;pytest &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-v&lt;/code&gt; flag runs pytest in &lt;em&gt;‘verbose’&lt;/em&gt; mode, which I like to do as it shows
the results for each test run, rather than each &lt;em&gt;file&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/passing-tests.mp4&quot;&gt;Example video of tests running and passing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So it looks like all the tests are passing! To be sure, Lets do a quick check to
make sure they work as expected… I’ll mark this post with &lt;code&gt;draft = &amp;quot;False&amp;quot;&lt;/code&gt;,
but &lt;em&gt;not&lt;/em&gt; add it to the approved lists, and the test for this page &lt;em&gt;should&lt;/em&gt;
fail…&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/85PWCjBDlY-1028.webp 1028w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-website-tests-pages/85PWCjBDlY-1028.jpeg&quot; alt=&quot;Checking a test fails when we want it to&quot; width=&quot;1028&quot; height=&quot;506&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Checking that a test fails when we want it to.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Awesome, it failed! I guess all there is left to do is to finish up this post, so I
can add it to the approved posts lists and publish it! Stay tuned!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Authorizing Thunderbolt 3 on Fedora Plasma</title>
    <link href="https://ryan.himmelwright.net/post/fedora-kde-tb3/" />
    <updated>2020-01-25T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/fedora-kde-tb3/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/PzRxArmPCG-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/PzRxArmPCG-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After buying a &lt;a href=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/&quot;&gt;16&amp;quot; MacBook Pro&lt;/a&gt; the other month,
I’ve been using a Thunderbolt 3 hub to connect it to my periphery devices.
Luckily, in addition to the macbook, I am able to use the hub with my my work
laptop when working from home. Normally, it works fine. However, last week I
reformatted the work laptop with the &lt;a href=&quot;https://spins.fedoraproject.org/kde/&quot;&gt;&lt;em&gt;KDE Plasma&lt;/em&gt; spin&lt;/a&gt; of
Fedora Workstation 31… and my TB3 hub stopped playing nicely with it.
Here’s why.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;The Issue&lt;/h2&gt;
&lt;p&gt;Due to a NVIDIA Quadro M1000M in my lenovo P50 work laptop, I’ve been using
Gnome with Wayland, because that combination seems to have the least amount of
annoying trade-offs. While doing a system refresh, I figured I would see if
things are any different with a clean Fedora 31 Plasma install (they weren’t,
but that’s beyond this post).&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/1DGaAqhYYS-1032.webp 1032w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/1DGaAqhYYS-1032.jpeg&quot; alt=&quot;Gnome Thunderbolt Settings&quot; width=&quot;1032&quot; height=&quot;739&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Gnome desktop environment now has a thunderbolt pane in the settings GUI.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Booting into the live cd, and later the installed system, I noticed that my
Thunderbolt 3 hub did &lt;em&gt;not&lt;/em&gt; work while I was in Plasma. That was fine. I
expected it really, because for security reasons Linux distros tend to disable
thunderbolt ports by default.  In gnome, there are now settings to authorize
it (which I guess I had done on my previous install, and forgot about).  In
Plasma however, I needed to install and configure &lt;code&gt;bolt&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Bolt&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/vFq0a2VK4n-1036.webp 1036w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/vFq0a2VK4n-1036.jpeg&quot; alt=&quot;Installing bolt with dnf&quot; width=&quot;1036&quot; height=&quot;755&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;`bolt` is in the Fedora repos and is easily installed with
`dnf`&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In Fedora, &lt;code&gt;bolt&lt;/code&gt; is in the repos (at least in Fedora 31+), so installing it is
easy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install bolt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once &lt;code&gt;bolt&lt;/code&gt; is installed, it isn’t a bad idea to check to make sure it is
running: &lt;code&gt;sudo systemctl bolt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/Hp37eQbF_B-1036.webp 1036w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/Hp37eQbF_B-1036.jpeg&quot; alt=&quot;bolt systemctl status&quot; width=&quot;1036&quot; height=&quot;857&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Checking `bolt` is running using `systemctl status`&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;If the status shows that it isn’t running for some reason, it can be started
using &lt;code&gt;systemctl start bolt&lt;/code&gt;. Check the status again, and if it it &lt;em&gt;still&lt;/em&gt; is not
running… sorry?&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/hbVMU_c8S2-1036.webp 1036w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/hbVMU_c8S2-1036.jpeg&quot; alt=&quot;boltctl&quot; width=&quot;1036&quot; height=&quot;857&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With bolt installed, the command &lt;code&gt;boltctl&lt;/code&gt; is available to use. The base command will
show information about attached thunderbolt devices. To see more &lt;code&gt;boltctl&lt;/code&gt;
commands, run &lt;code&gt;boltctl --help&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Authorizing&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/MIsG3iTvbe-1036.webp 1036w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/MIsG3iTvbe-1036.jpeg&quot; alt=&quot;boltctl authorize&quot; width=&quot;1036&quot; height=&quot;857&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Authorizing the device.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In order to &lt;em&gt;use&lt;/em&gt; the thunderbolt 3 device, it needs to be authorized. To
authorize a device, first use a plain &lt;code&gt;boltctl&lt;/code&gt; command to get the &lt;code&gt;uuid&lt;/code&gt; of
the device. Next, call &lt;code&gt;boltctl authorize&lt;/code&gt; using the &lt;code&gt;uuid&lt;/code&gt; to authorize it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;boltctl authorize &amp;lt;UUID HERE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The device should now be authorized. When I authorized mine, some of my devices
started showing up automatically, while others needed to be disconnected and
replugged.&lt;/p&gt;
&lt;h2&gt;Enrolling&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/hN_7_MvRoA-1036.webp 1036w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/fedora-kde-tb3/hN_7_MvRoA-1036.jpeg&quot; alt=&quot;boltctl enroll&quot; width=&quot;1036&quot; height=&quot;857&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Enrolling the device for permanent use.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Authorizing a device enables it to be used, but doesn’t guarantee persistence.
While this may be desired to grant &lt;em&gt;temporary&lt;/em&gt; authorization to a device, many
users tend want their device to &lt;em&gt;always&lt;/em&gt; work, without having to manually
authorize it. In this case, the device should be &lt;em&gt;enrolled&lt;/em&gt; (like &lt;code&gt;systemctl enable&lt;/code&gt;).
Simply call &lt;code&gt;boltctl enroll&lt;/code&gt;, with the device UUID:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;boltctl enroll &amp;lt;UUID HERE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once enrolled, the device’s UUID will be recorded and added to a database. By
default, the device will now automatically be authorized whenever it is
connected.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it. While the solution isn’t very difficult, it can be frustrating to
figure out when it appears that the thunderbolt device simply is not working.
Regardless, I’m glad that I now know about &lt;code&gt;bolt&lt;/code&gt; and how to use it!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My New 16-inch Macbook Pro - Initial Thoughts</title>
    <link href="https://ryan.himmelwright.net/post/new-2019-16inch-mbp/" />
    <updated>2020-01-12T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/new-2019-16inch-mbp/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/AV97IzulaZ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/AV97IzulaZ-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;740&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://ryan.himmelwright.net/post/macos-challenge/&quot;&gt;previous post&lt;/a&gt;, I &lt;em&gt;mentioned&lt;/em&gt; that I had
purchased a new 2019 16&amp;quot; Macbook Pro by the end of my macOS challenge.  I have
had the laptop for over a month now, but I made sure to take notes on some of
my initial thoughts during the first few days. Here are those thoughts.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Selection&lt;/h2&gt;
&lt;p&gt;But first, &lt;em&gt;a word from our sponsor!..&lt;/em&gt; Just kidding. However, I &lt;em&gt;do&lt;/em&gt; want to
take a minute to talk about how I selected &lt;em&gt;my first&lt;/em&gt; macbook.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/VDNyO_JUNI-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/VDNyO_JUNI-1200.jpeg&quot; alt=&quot;MBP on my desk&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New 16&quot; MBP on my desk.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While completing the macOS challenge, I decided I wanted to buy a
macbook for personal use. However, I wanted to hold out and wait to hear if the
rumors of a new scissor-switch macbook pro were true, or if I
should be considering a used 2015 Macbook Pro on eBay. Fortunately, the rumor
turned to fact, and Apple released the new 16&amp;quot; Macbook Pro. This news coupled
with a $300-off, Thanksgiving (US) sale, meant that I had a new macbook
shipping to me the first week of December.&lt;/p&gt;
&lt;h3&gt;The Base Model Specs&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/wTXMbpdAP1-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/wTXMbpdAP1-1200.jpeg&quot; alt=&quot;Macbook Pro closed on the table&quot; width=&quot;1200&quot; height=&quot;613&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;16&quot; Macbook Pro closed on the table.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I chose the base-spec MBP (the cheapest one) for several reasons. First, it
saved me a bunch of money because 1) Apple upgrades tend to be expensive, and 2)
the base models can be found on sale more easily.&lt;/p&gt;
&lt;p&gt;Additionally, the “low-end” base model specs of the 16&amp;quot; Macbook Pro were
increased to include 16 GB of RAM, and a 512GB SSD. This meant that it already
had everything I would normally &lt;em&gt;need&lt;/em&gt; to upgrade. So, the specs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- 2.6 GHz (4.5 GHz Turbo Boost) 6-Core (12 thread) i7 CPU
- 16 GB 2666MHz DDR4 RAM
- 512 GB SS
- 16&amp;quot; &amp;quot;Retina&amp;quot; display w/ True Tone
- New Keyboard design, with touchbar and Touch ID
- Four Thunderbold 3 ports
- Headphone Jack
- New speakers and mic
- Silver
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a perfect world, I &lt;em&gt;might&lt;/em&gt; have wanted 32 GB of RAM. But if I think
about it, I &lt;em&gt;mostly&lt;/em&gt; need RAM to run clusters of VMs… which I do on Linux
workstation/servers… not my laptop. So 16GB should be plenty for the needs of
&lt;em&gt;this machine&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Initial Thoughts&lt;/h2&gt;
&lt;p&gt;Okay, now lets get to my initial thoughts. Remember, these are the
(reformatted) notes I took within about a day or so of receiving the Macbook.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/2e1tYSRhyZ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/2e1tYSRhyZ-1200.jpeg&quot; alt=&quot;Macbook Pro from above&quot; width=&quot;1200&quot; height=&quot;902&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I love both the new keyboard and speakers on the 16&quot; Macbook Pro.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keyboard&lt;/strong&gt; - Well, I’m surprised. I think I actually &lt;em&gt;really&lt;/em&gt; like it. The
first few minutes were a little weird, because I was used to the (2014) MacBook
Air (Also, I had been using &lt;a href=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/&quot;&gt;my
HHKB&lt;/a&gt; all day). After using
it for about an hour now, I think it feels close to the air (which I like),
with &lt;em&gt;enough&lt;/em&gt; travel. It definitely does not have as much travel as my
Thinkpads, but it is acceptable. The keys feel like they “pop” as I type on
them, with a very satisfying bump. Overall, I very much enjoy the typing
experience. As long as it proves to be sturdy and reliable, I’d say this is
a great laptop keyboard. I am very happy with it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trackpad&lt;/strong&gt; - Little weird at first (I had trouble sliding my fingers across
it.  I guess I needed time to oil it a bit lol), but now it seems to be working
well. It is massive, but doesn’t seem to register accidental touches much.
Really, there isn’t much to say. It does what I need it to, basically &lt;em&gt;all&lt;/em&gt; the
time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Screen&lt;/strong&gt; - Wonderful. It’s big, bright and the colors look great. Using
the default scaling still, but actually think it’s big enough. It’s a beautiful
screen large enough to get work done, on a still small-ish laptop.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Touch Bar&lt;/strong&gt; - Meh. It’s whatever. I have already installed better touch tool
😂. I’ll play with that later to figure out how I can switch the
audio/brightness to buttons instead of slides (the main thing I don’t like so
far), and maybe add weather. In summary, with better touch tool, I don’t &lt;em&gt;mind&lt;/em&gt;
having the touch bar, but probably wouldn’t miss it much if it was gone.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speakers&lt;/strong&gt; - Amazing. Simple as that. They aren’t going to replace a
high-end pair of studio monitors, but are probably the best laptop speakers out
there, and likely better than most cheap desktop ones.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ports&lt;/strong&gt; - Just charging so far. I like being able to charge on
both sides. Side note: the power brick feels like an &lt;em&gt;actual&lt;/em&gt; brick. It is
solid and heavy, which is fine, but did surprise me a bit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt; - As far as I can tell, great. It seems like it can handle
most of the work loads &lt;em&gt;I&lt;/em&gt; will need it for.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Issues/Annoyances/Concerns&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/-ldaNYDpIz-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/-ldaNYDpIz-1200.jpeg&quot; alt=&quot;Macbook Pro from side&quot; width=&quot;1200&quot; height=&quot;590&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;As for ports, the 16&quot; Macbook Pro has two USB C/Thunderbolt3 ports and a headphone jack on one side (pictured), and 2 more USB C/Thunderbolt3 ports on the other side.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Heat&lt;/strong&gt; - As I’m typing this, I do notice the metal at the top of the laptop
(above the Touch Bar) is getting quite toasty. In fact, the keys on the number
row are even a bit warm. I understand that this is where the heat is exhausted,
and that the cooling system probably relies on the aluminum body a bit as a
heat sink. I just hope it’s normal. I should note that this heat up happened
while I was installing Xcode… which seems to be a beast. Once Xcode finished
installing, it did cool down fast. The entire time, the palm rest remained nice
and cool to the touch. Also, the fans never kicked up for this. So it seems
that the cooling system is &lt;em&gt;very&lt;/em&gt; good on this laptop. I just wish it would
kick in a &lt;em&gt;little&lt;/em&gt; bit sooner to prevent it from heating up.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ports/Adaptor Annoyances&lt;/strong&gt; - Everyone is complaining that there still isn’t
an SD card reader, but honestly I could care less. I generally think having
powerful TB3 ports is a good plan, as it allows the user to craft their system
to have whatever ports &lt;em&gt;they need&lt;/em&gt;. For example, I &lt;em&gt;don’t&lt;/em&gt; usually need an SD
card reader, but I &lt;em&gt;do&lt;/em&gt; use USB3 and HDMI daily. With a dongle/adaptor, I can
have that, and the TB3 connector has enough bandwidth to support it. However, the
annoying part is buying into the TB3 life. I &lt;em&gt;had&lt;/em&gt; to buy an expensive
dongle for my new expensive laptop in order to use &lt;em&gt;anything&lt;/em&gt; with it. Also,
the functionality of the adaptors seems to be hit or miss. I got one that
works okay, but only after having to return the first one I bought because it
just wouldn’t connect my devices. Very Annoying.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wrists Rest&lt;/strong&gt; - While I really enjoy the solid feel of the aluminum my
hands rest on (and how it tends to stay cool even when the computer is heating
up), the sharp edge of the laptop cuts into my wrist over time. I will admit
that this is something I have experience on other laptops too. Maybe I just
type wrong.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/_NxCix744_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/_NxCix744_-1200.jpeg&quot; alt=&quot;Macbook Pro on table&quot; width=&quot;1200&quot; height=&quot;889&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The 16&quot; Macbook Pro is a great all in one portable workstation.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In conclusion, I love this laptop. It is powerful enough to do everything I
need, and I think Apple has done a great job addressing the complaints users
have had with Macbooks over the last few years. Considering the speaker,
microphone, keyboard, and screen upgrades this is an amazing portable
workstation. As much as I love having a full desk setup, the “built-in
periphery devices” on this laptop are &lt;em&gt;so good&lt;/em&gt;, I really don’t mind working
straight from the laptop. Which coming from &lt;em&gt;me&lt;/em&gt;, is saying something.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Challenge - 30 Days with macOS</title>
    <link href="https://ryan.himmelwright.net/post/macos-challenge/" />
    <updated>2019-12-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/macos-challenge/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/macos-challenge/dDSe2NT2cZ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/macos-challenge/dDSe2NT2cZ-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;American Tobacco Campus, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have used many operating systems on my computers over the years, including
decades worth of Windows versions, tons of Linux distros, several BSDs, iOS,
Android, and even chromeOS. While I am familiar with using macOS
(I often used the school Macs in college, and my wife had a Macbook Air
throughout medical school), it is the one OS I have never tried as my &lt;em&gt;personal&lt;/em&gt;
daily driver. After switching to an iPhone and &lt;a href=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/&quot;&gt;getting an
iPad&lt;/a&gt; this year… I
decided to give macOS a spin.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;em&gt;(Fun fact: I first learned about Linux while attempting to Hackintosh my
laptop in high school.)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The Challenge&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Using a mac as the interface device during my personal computing, for 30 days.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here, “interface computer” means the device that I sit at and interact with.
For example, I might be sitting at macOS and using it for my web browsing and
music, but working on a project on my linux workstation via a mosh/tmux session
in iTerm. I can use remote Linux computers as much as I please, but should
connect to them through macOS.&lt;/p&gt;
&lt;p&gt;One thing to note is that I still used Desktop Linux as my “interfacing
computer” for work.&lt;/p&gt;
&lt;h2&gt;Why&lt;/h2&gt;
&lt;p&gt;At home, I don’t mind iOS and macOS teaming up to make my “normal person”
computing (web browsing, video chatting, emails, and online courses)
easier. I still deeply care about working with Linux at home, but exclusively
on the backend. So having the front end get out of the way, and not tempt me
with the ability to fine-tune it, is a good thing (&lt;em&gt;for me&lt;/em&gt;, currently).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(While I’ve slowed down &lt;a href=&quot;https://www.urbandictionary.com/define.php?term=distrohopper&quot;&gt;distro
hopping&lt;/a&gt; the last
few years,… I have still been swapping my desktop environment every other
month).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Right now, I am really only interested in using macOS for my personal
computing.  At work, I don’t need my computer to be in step with my personal
phone and/or ipad. In fact, I like having that barrier there. Also, Linux works
REALLY well for the work I do, especially when it comes to running VMs (I love
&lt;code&gt;libvirt&lt;/code&gt;). So, there currently isn’t a reason (or even a desire) to switch
there.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Side Note:&lt;/strong&gt;&lt;/em&gt; I actually wouldn’t even consider this challenge if I didn’t
get to use Linux so much at work every day, or if I didn’t have my Linux
desktop/workstation at home. I already ssh into my desktop to work
from my other devices. In fact, I technically remote into my desktop even when
I’m working &lt;em&gt;at&lt;/em&gt; it, since I do everything in a &lt;a href=&quot;https://podman.io&quot;&gt;container&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Observations&lt;/h2&gt;
&lt;p&gt;Now that I’ve spent a few weeks in macOS, lets break down some observations I
had.&lt;/p&gt;
&lt;h3&gt;Stuff I didn’t like&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The elephant in the room… it’s very proprietary. I still love open source and
using something that’s as locked down and secretive as macOS (and Apple
products in general) feels, well… &lt;em&gt;dirty&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/macos-challenge/DgHP2QpUgl-532.webp 532w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/macos-challenge/DgHP2QpUgl-532.jpeg&quot; alt=&quot;joplin error&quot; width=&quot;532&quot; height=&quot;323&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Error while trying to run Joplin.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I had trouble getting some applications like &lt;a href=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/&quot;&gt;Joplin&lt;/a&gt; to work. I later learned that it was likely due to issues with signing the applications in Catalina (I think). Apparently, there is a work-around for this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There surprisingly isn’t a great solution for &lt;a href=&quot;https://virt-manager.org&quot;&gt;virt-manager&lt;/a&gt;. I was able to install it using homebrew, but have to launch it from the terminal. I guess I might be able to setup an application launcher for it? I’m just surprised there isn’t a better solution on mac, or at least one that’s easy to find…&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On that note, it is &lt;em&gt;wonderful&lt;/em&gt; that &lt;a href=&quot;https://brew.sh&quot;&gt;brew&lt;/a&gt; exists, but it confuses the hell out of me (What’s a &lt;code&gt;cask&lt;/code&gt;? Is it like a &lt;a href=&quot;https://dnf.readthedocs.io/en/latest/command_ref.html#group-command-label&quot;&gt;&lt;code&gt;dnf group&lt;/code&gt;&lt;/a&gt; install?). This is most likely because I am new to it, but in my opinion, I still think it is nowhere near as nice as default Linux package managers. This is one area where using Linux for so long has definitely spoiled me.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Working with filesystems is a pain, especially when connecting to network devices. I couldn’t use Finder to do an easy network connection without first setting up samba or nfs on my Linux computers. While I ideally want to setup a proper samba/nfs server on my desktop/server in the long run anyway, it was annoying that it couldn’t just mount the system in the file browser, using something like &lt;code&gt;sftp&lt;/code&gt; under the hood like Linux does…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/macos-challenge/network-connect&quot;&gt;Click for example video connecting to a remote file system in Gnome&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Window management isn’t great out of the box. I had to buy &lt;a href=&quot;https://apps.apple.com/us/app/magnet/id441258766?mt=12&quot;&gt;magnet&lt;/a&gt; to snap/tile my windows. (Alternatively for touch-bar mac users, note that &lt;a href=&quot;https://folivora.ai&quot;&gt;better touch tool&lt;/a&gt; has window snapping and other features built in). Also, I can’t seem to hold a key and click a window anywhere to resize/move like I can in most linux DEs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My desktop workspaces keep changing their order on me (ಠ_ಠ).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deviating from the stock experience can become painful.  Don’t like using a global menubar, and a dock for applications?.. sorry?
I’m sure there are third-party ways to get around it, but they are likely
hacky and not guaranteed to always work across new versions of macOS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Stuff I liked&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I  am actually starting to like the CMD-centric shortcuts. They work everywhere and are nice when working in a terminal (which I do all the time).&lt;/li&gt;
&lt;li&gt;Default keybindings, while different from what I’m used to are intuitive, and consistent across the system. There are not many that are crazy. For example, on Windows/Linux systems, the keybinding to close a window usually defaults to &lt;code&gt;Alt+F4&lt;/code&gt;, so I normally change it to &lt;code&gt;Super+Shift+Q&lt;/code&gt;. While not the exact same as what I do, the macOS default of &lt;code&gt;CMD+Q&lt;/code&gt; is close enough that I’ve been able to easily adapt to it, without feeling the need to figure out how to change it. It’s a similar story across most of the default keybindings. They aren’t exactly how I’ve been setting mine up, but close enough that I’m fine with accepting them as is.&lt;/li&gt;
&lt;li&gt;iCloud syncing has been nice. Specifically for photos, TODOs, and notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/macos-challenge/NTWnmimH2M-393.webp 393w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/macos-challenge/NTWnmimH2M-393.jpeg&quot; alt=&quot;Receiving and answering a phonecall in macOS&quot; width=&quot;393&quot; height=&quot;314&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I like being able to answer &lt;em&gt;all&lt;/em&gt; phone calls from my computer.
I hate talking on the phone. Oddly
enough, I don’t mind voice chat on the computer, so this works out really
well for me. Synced iMessage and Facetime are also nice, but universal
&lt;em&gt;normal&lt;/em&gt; phone calls is what I particularly enjoy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Profiles. Specifically, setting up something like my
Fastmail account in the mail app
was SUPER easy because of this. I basically just downloaded the profile
from fastmail and opened it with mail, and everytihng was setup. That’s it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I find the UI to be really space respecting. In addition to a global menu, title bars on windows are thin and minimal. Combined with scaled font sizes, a lot less space is wasted, especially compared to Gnome.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/macos-challenge/WX8r3HIZYy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/macos-challenge/WX8r3HIZYy-1200.jpeg&quot; alt=&quot;macOS header bars compared to Gnome&quot; width=&quot;1200&quot; height=&quot;1609&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I find macOS to be much more space efficient than Gnome.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Videos just work without configuring anything/switching back and forth between Wayland and X sessions. I have faith that wayland will mature to this point eventually, but its nice to already have it in macOS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gestures are fluid and intuitive. Even on the old MacBook air, the trackpad
is amazing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Settings like screentime work across all my “front-end devices”. If I set a downtime for 8:30pm, it boots me off all my devices… and I get to bed earlier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;macOS comes with a set of well-made core apps (calendar, mail, notes, music,
photos, keynote, garage band, etc). They work for everything I need. If I
want something more advanced, I generally have the full selection of open source and
proprietary apps at my disposal. For example, I’ve been using Gimp just
fine on the macbook.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Security does actually seem to be a focus on the system. For example, someone
tried to log into my icloud account from a random location the other week.
In under a minute, from my phone, I was able to unlink my account from all
my devices and change the password.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Future Plans&lt;/h2&gt;
&lt;p&gt;Overall, I’d say this challenge was a success. In fact, it was so successfull,
that I’m actually writing up this post on a new &lt;a href=&quot;https://ryan.himmelwright.net/post/new-2019-16inch-mbp/&quot;&gt;base model 16&amp;quot; MacBook
Pro&lt;/a&gt; (but more on &lt;em&gt;that&lt;/em&gt; at another time).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[ Well, I’m really writing it on my desktop, but from the macbook pro ;) ]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My plan is to use macOS and my iOS
devices to handle my interactive/client computing. I’ve enjoyed having my
apple devices take care of all my “normal” person computer needs over the last
few weeks. It has allowed me to finish up required, but mundane tasks easily
and efficiently, so I can better focus when diving into iTerm to work on
complicated Linux server projects.&lt;/p&gt;
&lt;p&gt;While I do have some reservations about this plan (mostly that my desktop Linux
skills will fade, and that I’ll loose respect in the Linux community), I don’t
think it should be too much of an issue. At the end of the day, it is most
important that I am able to spend my limited time learning and building in the
areas I want to focus on, which is back-end, rather than front-end Linux. I
want to love all technology, and be able to help people find the best solution
to their problems, no matter what they use. Maybe one day the OS flame wars
will die down and we can all just get along and build cool shit together… Till
then, wish me luck!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Resolving Issue</title>
    <link href="https://ryan.himmelwright.net/post/issue26/" />
    <updated>2019-12-15T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/issue26/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/issue26/xoSCzz1woC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/issue26/xoSCzz1woC-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Lowes, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While working on a post a couple of weeks ago, I noticed that for some unknown
reason, my website wasn’t rendering correctly. After some back-tracking, I
remembered that I had updated the container I work in to a Fedora 31 base
image, which has a newer version of &lt;code&gt;hugo&lt;/code&gt;.  So, I filed the problem as &lt;a href=&quot;https://github.com/himmAllRight/himmAllRight-source/issues/26&quot;&gt;issue
#26&lt;/a&gt;, and
finished my post in a Fedora 30 container for the time being.  Here is a quick
explanation of how I &lt;em&gt;eventually&lt;/em&gt; came back and resolved issue #26.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;The Problem&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/issue26/CQ8jlt38JU-1096.webp 1096w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/issue26/CQ8jlt38JU-1096.jpeg&quot; alt=&quot;The correct website homepage index&quot; width=&quot;1096&quot; height=&quot;1255&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;What the website homepage is *supposed* to look like.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My website has two types of content: &lt;em&gt;posts&lt;/em&gt;, and &lt;em&gt;pages&lt;/em&gt;. &lt;em&gt;Pages&lt;/em&gt; are the
‘normal’ content of the website, like the &lt;a href=&quot;https://ryan.himmelwright.net/pages/about/&quot;&gt;About&lt;/a&gt; and
Homelab pages. &lt;em&gt;Posts&lt;/em&gt; on the other hand, are the dated
‘blog’ posts I write (like the one you are currently reading). When everything
is working, my hugo template takes the newest &lt;code&gt;x&lt;/code&gt; number of post files from the
&lt;em&gt;posts&lt;/em&gt; directory, and lists them on the homepage with a small summary. (See
image above)&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/issue26/sYODZ59Hiz-1096.webp 1096w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/issue26/sYODZ59Hiz-1096.jpeg&quot; alt=&quot;What the broken homepage&quot; width=&quot;1096&quot; height=&quot;1255&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The broken homepage.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;However, after the &lt;code&gt;hugo&lt;/code&gt; version in my website container updated from
&lt;code&gt;v0.56.6&lt;/code&gt; to &lt;code&gt;v0.58.3&lt;/code&gt;, the post list on the homepage broke.  Specifically,
instead of listing the recent posts, the homepage just listed a single “post”
named…  “Posts” (see image above).&lt;/p&gt;
&lt;p&gt;This indicated that something changed between those two versions that made my
template out of date. However, I wasn’t sure if the problem was in the
website’s page content, or if it was from an issue in the template I used.&lt;/p&gt;
&lt;h3&gt;Finding a Fix&lt;/h3&gt;
&lt;p&gt;To find out, I grabbed some other &lt;a href=&quot;https://themes.gohugo.io&quot;&gt;hugo templates&lt;/a&gt;,
and temporarily switched my config to render the website with them. This was
actually a bit more complicated than I thought it would be. Hugo templates
rely on a all sorts of configuration variables being defined in order to work properly.
Additionally, not every template had a recent post section like I did, so they
didn’t help to isolate the issue at all. Lastly, if the template was older…
it had the same rendering issue mine did. Eventually, I found a newer, but
rather bare-bones template that had a similar home page layout to my own. When
I rendered my site with it… it had a working recent posts list on the home
page!&lt;/p&gt;
&lt;p&gt;This proved 1) the issue was somewhere in my &lt;em&gt;template code&lt;/em&gt; and 2) a fix was
possible.&lt;/p&gt;
&lt;h3&gt;Coding a Solution&lt;/h3&gt;
&lt;p&gt;Studying the &lt;code&gt;layouts/index.html&lt;/code&gt; file in each template, I noticed the working
template used a different method to gather and display the recent posts. My
template used a basic in-line piece of code that grabbed a range of the latest
posts to be rendered, right then and there.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          {{ $posts := or .Site.Params.PostSummariesFrontPage 4 }}
          {{ range first $posts (where .Data.Pages &quot;Type&quot; &quot;post&quot;) }}
              {{ .Render &quot;summary&quot;}}
          {{ end }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By comparison, the the newer template used an improved setup. It first
defined variables at the top of the layout page to calculate all the recent
post lists, and then simply referred to &lt;em&gt;that&lt;/em&gt; when rendering the summaries. So,
I read up a bit on the hugo documentation to better learn how variables and
some of the basic functions worked.&lt;/p&gt;
&lt;p&gt;Eventually I was able to come up with my own solution, defining the following
variables at the top of the page:&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $mainSections &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mainSections &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slice &lt;span class=&quot;token string&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $section &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; where &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RegularPages &lt;span class=&quot;token string&quot;&gt;&quot;Section&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;in&quot;&lt;/span&gt; $mainSections &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $section_count &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt; $section &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then referred to the variables further down where I render the recent
post summaries:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      {{ range (first $n_posts $section) }}
          {{ .Render &quot;summary&quot;}}
      {{ end }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and… it worked! Done!&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;For as quick and easy as this post implies, the fix was actually a giant pain.
It took a long time to figure out where the problem was occurring, but in the
end I’m happy with the solution. In fact, I used a &lt;a href=&quot;https://github.com/himmAllRight/himmAllRight-source/pull/30&quot;&gt;similar
approach&lt;/a&gt;
afterwards to fix &lt;a href=&quot;https://github.com/himmAllRight/himmAllRight-source/issues/28&quot;&gt;issue
#28&lt;/a&gt;! Assuming
hugo doesn’t suddenly break me again, see you in the next post!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setup Mosh</title>
    <link href="https://ryan.himmelwright.net/post/setup-mosh-shell/" />
    <updated>2019-12-04T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/setup-mosh-shell/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-mosh-shell/IiLeUq8m23-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-mosh-shell/IiLeUq8m23-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;900&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;North Buchanan Boulevard, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/&quot;&gt;builing my desktop&lt;/a&gt;, whenever I work
on another machine, I usually end up ssh’ing back to it to work remotely. It
has my files, more power, and much of my work flow is done from a terminal
window anyway, so why not?  The only issue I have with &lt;code&gt;ssh&lt;/code&gt; is that if I have
a spotty internet connection, or if I sleep/suspend my laptop while moving
around, the &lt;code&gt;ssh&lt;/code&gt; session will occasionally time out.  &lt;a href=&quot;https://ryan.himmelwright.net/post/scripting-tmux-workspaces/&quot;&gt;Tmux&lt;/a&gt;
and &lt;a href=&quot;https://ryan.himmelwright.net/post/setting-up-tmuxinator/&quot;&gt;tmuxinator&lt;/a&gt; make this less of an issue,
since I can re-attach my session, but I still wish my remote sessions were a
bit more seamless. They can be… using &lt;code&gt;mosh&lt;/code&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Mosh&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setup-mosh-shell/yGyGFjy1B0-740.webp 740w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setup-mosh-shell/yGyGFjy1B0-740.jpeg&quot; alt=&quot;Using mosh to conenct to a server&quot; width=&quot;740&quot; height=&quot;769&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using `mosh` to connect to one of my servers.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mosh.org&quot;&gt;Mosh&lt;/a&gt; is a more robust replacement for interactive ssh
terminals. It automatically roams and continues to work even as the computer
switches networks or is put to sleep. It also responds to typing, even on a bad
connection, which cuts down on lag. Lastly, it’s free and open source software,
licenced under the GPLv3.&lt;/p&gt;
&lt;h2&gt;Mosh Install&lt;/h2&gt;
&lt;p&gt;Mosh should be in most Linux repos, and is also available on BSD, Mac, Windows,
and basically everything else.  For more information on how to install it on
your platform, head over to the &lt;a href=&quot;https://mosh.org/#getting&quot;&gt;getting mosh&lt;/a&gt;
page.  For me, it was a simple &lt;code&gt;dnf&lt;/code&gt;/&lt;code&gt;yum&lt;/code&gt; install to get it on both my laptop
and
server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install mosh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;*Note: My centos server required me to first enable the epel repos to get
access to &lt;code&gt;mosh&lt;/code&gt;. Fedora might also, but I already had it enabled on my
laptop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo yum install -y epel-release
sudo yum install -y mosh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Open Firewall Ports&lt;/h2&gt;
&lt;p&gt;After installing &lt;code&gt;mosh&lt;/code&gt;… it might not immediately work. If so, it is likely
due to not having the required ports open. Mosh uses UDP ports 60000-61000 for
it’s connections. Enable these ports and optionally restart the firewall,
before trying mosh again.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: If you want to connect from outside the network, remember to also
forward these ports on the network.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Connect&lt;/h2&gt;
&lt;p&gt;We should now be able to connect to the server using mosh. Typical connections
look very similar to &lt;code&gt;ssh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mosh ryan@centos-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a specific mosh UDP port needs to be specified (for port-forwarding, for example)
use the &lt;code&gt;-p&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mosh -p 1234 ryan@centos-server
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SSH Options&lt;/h2&gt;
&lt;p&gt;Mosh uses ssh for the initial connection. Occasionally, particular ssh options
might be required in order for mosh to initialize a connection.  For
example, I typically &lt;code&gt;ssh&lt;/code&gt; home on a particular port, so that my router knows
which VM to transfer me to.  Options like this can be passed to &lt;code&gt;mosh&lt;/code&gt; using
the &lt;code&gt;--ssh&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mosh --ssh=&amp;quot;ssh -p 1234&amp;quot; ryan@centos-network
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s really it. There is a bunch of cool &lt;a href=&quot;https://mosh.org/#techinfo&quot;&gt;technical
stuff&lt;/a&gt; going on under the hood of mosh, but on the
surface… it is simply useful. For an even &lt;em&gt;better&lt;/em&gt; experience with mosh, be
sure to check out tmux. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>All Things Open 2019</title>
    <link href="https://ryan.himmelwright.net/post/ato2019/" />
    <updated>2019-11-09T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/ato2019/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ato2019/ArGhhbN6nF-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ato2019/ArGhhbN6nF-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Downtown Raleigh, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Two weeks ago, I attended &lt;a href=&quot;https://allthingsopen.org&quot;&gt;All Things Open (2019)&lt;/a&gt;
for the second year in a row. ATO is an annual conference that explores and
celebrates… well, all things &lt;em&gt;open&lt;/em&gt;. Open source, open tech, and open
government are all main topics at the conference. Best of all, it’s right here
in the triangle (Raleigh NC). Here are some of my overall take-aways from this
year.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;While the talks are always full of information, the &lt;em&gt;main&lt;/em&gt; benefit I receive
from attending the conference is leaving with a list of technologies and
ideas I want to test out. So, rather than go into boring detail about each talk
I went to, I will elaborate on some common topics/themes I experienced and what
I am excited to dig into as a result.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Sorry there aren’t any visuals in this post. I’m still a terrible event
participant and didn’t take any pictures)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Tech&lt;/h2&gt;
&lt;p&gt;Let’s get tech out of the way, right up front. Here are some ideas I left the
various technical talks with:&lt;/p&gt;
&lt;h3&gt;I am going to keep using ZFS for my server&lt;/h3&gt;
&lt;p&gt;It happened again. Over the last few months I’ve entertained the idea of moving
off of ZFS on my home server for something like mergerfs + snapraid. Then I
went to a &lt;a href=&quot;https://jrs-s.net/&quot;&gt;Jim Salter&lt;/a&gt; ZFS talk, and just like last time…
I want to ZFS &lt;em&gt;all the things&lt;/em&gt;. So, it looks like I’ll be sticking with it on the
server. Also… it might be time for a ZFS tune-up to make sure I have the
appropriate options configured correctly…&lt;/p&gt;
&lt;h3&gt;I want to start building Flatpaks&lt;/h3&gt;
&lt;p&gt;As a &lt;a href=&quot;https://silverblue.fedoraproject.org/&quot;&gt;Fedora Silverblue&lt;/a&gt; user, I rely
&lt;em&gt;heavily&lt;/em&gt; on flatpaks for most of my desktop applications these days. While I
have only built the “hello world” demo when playing with flatpak in the past, this
is something I think I want to dig deeper into. Knowing how to build and tweak
flatpaks would be extremely useful. It would enable me to package up software I
need to use, but then also contribute my efforts back to the community.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;sudo&lt;/code&gt; is awesome.&lt;/h3&gt;
&lt;p&gt;I already knew this, but I always forget &lt;em&gt;how much&lt;/em&gt; &lt;code&gt;sudo&lt;/code&gt; can do. Like &lt;code&gt;ssh&lt;/code&gt;,
the possibilities of being well informed about the advanced options of &lt;code&gt;sudo&lt;/code&gt;
are endless. I think I’m going to dig deeper into some of the settings, and see
what I can find.&lt;/p&gt;
&lt;h3&gt;I need to automate my cloud provisioning&lt;/h3&gt;
&lt;p&gt;This has been on my list for awhile now, but after seeing so many
demonstrations of people using terraform and chef to spin up their cloud
environments… I need to start adding some provisioning roles to my ansible
playbooks. I think using the Digital Ocean module to spin up/tear down
temporary project droplets might be a good start…&lt;/p&gt;
&lt;h2&gt;Mentoring/Teaching&lt;/h2&gt;
&lt;h3&gt;Large Scale Mentoring&lt;/h3&gt;
&lt;p&gt;One of my favorite talks of the conference was co-done by two kubernetes
contributers (from Red Hat and Google). They explained how the kubernetes
project tackles the big problem of mentoring… specifically how it can even be
done in a several-thousand contributer project that also has hundreds of fly-by
contributors.&lt;/p&gt;
&lt;p&gt;What I enjoyed most about their mentoring system was how it was organized.
First, they went through and defined all the leadership roles, &lt;em&gt;documenting&lt;/em&gt;
what the role was, and how it is done. Next, they made a mentoring chart for
all the defined roles, listing the current person for each role in one column,
and the person(s) next-in-line that are training for that role in the next
column. I think this is a great system for communities, and I might see if I
can take some lessons from this structure and help apply it to some volunteer
organizations I work with.&lt;/p&gt;
&lt;h3&gt;Gamifing Education&lt;/h3&gt;
&lt;p&gt;Another interesting education talk I went to was about gamifying education.  In
the past, whenever I encountered topics trying to “gamify” education, they
usually involved how to add gaming aspects to current methods of teaching
(points for answering questions, leveling up in difficulty, etc). This talk
however, focused on actually making games (ex: board games) that just happen to
teach children as they play. It went in depth about what to keep in mind while
designing such a game, and gave great examples about challenges I wouldn’t
normally think of. For instance, the presenter suggested designing the games so
that any random items could be used as the game pieces (rocks, beans, spoons).
This lowers the barrier of entry so that children in all types of living situations
are able to play, and I think it is great advice.&lt;/p&gt;
&lt;p&gt;Best of all, the talk concluded with the suggestion to host all game
sources/documentation in a public repo on a website like Github.  This not only
makes the game accessible to people all over the world, but allows the
community to improve and expand on it. If there is something confusing in the
documentation, another person can clarify and fix it. Open sourcing the game
also enables the community to modify and invent new variations, improving it
over time. The talk showcased how we can take the best aspects of open source,
and use them to hopefully make learning a bit more fun and accessible.&lt;/p&gt;
&lt;h3&gt;Those who can Do, Should Also Teach&lt;/h3&gt;
&lt;p&gt;As someone that struggled to focus in traditional lecture-style classes, but
thrived in hands-on lab courses, I am always looking for new methods and ways
to help others learn. One talk I observed, encouraged the audience to start
teaching others as they continue grow their skills. The talk provided many useful tips
and approaches for teaching both technical and non-technical “students”.&lt;/p&gt;
&lt;p&gt;A big motivation behind this website is that it allows me to share my
experiences so others can (&lt;em&gt;hopefully&lt;/em&gt;) learn from them.  As I sat listening to
the talk, I started to brain-storm what I could do to better extend my ability
to teach. I first wondered what changes I could make to the website, but then
started thinking about even &lt;em&gt;new&lt;/em&gt; avenues I could pursue. I haven’t finalized
any ideas &lt;em&gt;yet&lt;/em&gt;, but it is something I will keep in mind as I start planning my
goals for the new year.&lt;/p&gt;
&lt;h2&gt;Communities/Diversity and Inclusion&lt;/h2&gt;
&lt;p&gt;I first listed “&lt;em&gt;Communities&lt;/em&gt;” and “&lt;em&gt;Diversity and Inclusion&lt;/em&gt;” as too separate
topics. However, as I started to sort talks into the categories, I realized
these two overlapped all the time. So I merged them.&lt;/p&gt;
&lt;p&gt;Honestly, this topic was so prevalent at ATO, that it’s hard to summarize even
a little bit. From learning about the &lt;em&gt;curb cutting effect&lt;/em&gt;, to
diversifying recruiters and continually switching up who leads meetings, there
were all sorts of facts and tips about how to build more diverse teams and even
communities.&lt;/p&gt;
&lt;p&gt;This topic sparked my curiosity, and I ended up walking away with two
new books added to my reading list: &lt;em&gt;The Culture Map&lt;/em&gt; by Erin Meyer, and Jono
Bacon’s upcoming book, &lt;em&gt;People Powered&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Burn-out/Team Dynamics&lt;/h2&gt;
&lt;p&gt;Last but not least, there were talks about another related topic pair: team
dynamics and burn-out. Every talk I went to that focused on either these topics
was &lt;em&gt;really well delivered&lt;/em&gt;, which was a relief as both topics are very
important in the tech industry. To summarize, I’ll list 3 items which I already
subscribe to, but came up &lt;em&gt;again and again&lt;/em&gt; in the presentations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-tasking isn’t real&lt;/li&gt;
&lt;li&gt;Stop blaming others&lt;/li&gt;
&lt;li&gt;A balanced life &lt;em&gt;outside&lt;/em&gt; of work is required to be productive &lt;em&gt;at&lt;/em&gt; work (eat healthy, exercise, get more sleep, have a hobby)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With my second year at All Things Open finished… would I recommend it?
Absolutely.  No matter why someone is interested in open source (the tech, the
community aspects, sharing with others), there will be something there for
them. See you next year!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Experience with the Large Display Paradox</title>
    <link href="https://ryan.himmelwright.net/post/large-display-paradox/" />
    <updated>2019-10-21T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/large-display-paradox/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/large-display-paradox/RWrDmuI76y-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/large-display-paradox/RWrDmuI76y-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Crimson View Rooftop Bar, Washington DC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;About a year ago, I &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/&quot;&gt;switched to the LG ud4379b&lt;/a&gt;, a 42.5&amp;quot;
4k IPS monitor. My initial motivation for &lt;em&gt;upgrading&lt;/em&gt; was to convert to an IPS
panel. However, my &lt;em&gt;selection of monitor&lt;/em&gt; was an attempt to simplify my
hardware, by pairing down my dual 1080p setup. While I wanted a single monitor,
I didn’t want to drop my total resolution because everyone
knows
&lt;a href=&quot;https://www.techradar.com/news/5-reasons-you-need-dual-monitors-in-2017&quot;&gt;that multiple
monitors&lt;/a&gt;
are
required
&lt;a href=&quot;https://www.business.com/articles/increasing-productivity-how-dual-monitors-can-save-you-time-and-money/&quot;&gt;to get any real
work done,&lt;/a&gt;
&lt;a href=&quot;https://www.entrepreneur.com/article/322640&quot;&gt;especially programming&lt;/a&gt;.
Although, after a year of using my massive display, I started to tire from the
&lt;em&gt;large display paradox&lt;/em&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Before I dive into what exactly the &lt;em&gt;large display paradox&lt;/em&gt; is, lets quickly
refresh on what I have said in the past. In my &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/&quot;&gt;post about choosing the 43&amp;quot;
monitor&lt;/a&gt;, I went into depth about my history of using dual
monitors, and &lt;em&gt;why&lt;/em&gt; I wanted to switch to a single monitor:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While I like having the screen real estate of two monitors, I was never able to optimally use them because of the bezels between the monitors. While working, I would naturally shift over and use one monitor as a primary display, and the other as a secondary display.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wanted to be able to view my monitor straight on, without any
bezels getting in the way. I also knew that a single 1080p monitor was just a
bit too cramped.&lt;/p&gt;
&lt;p&gt;In that same paragraph, I provided an additional reason for the switch. If I
had been paying closer attention, I might have noticed it
foreshadowing issues I would face with a large display:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This was okay for some tasks, but it usually meant that I just used the primary monitor, and half of the secondary one, &lt;strong&gt;because it was too hard to see the far end of the screen.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/large-display-paradox/DAUVwPZ9Ii-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/large-display-paradox/DAUVwPZ9Ii-1200.jpeg&quot; alt=&quot;Wayland logo&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I was excited to use a single monitor as a 4x 21-inch 1080p bezel-less grid.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I was aware that a large screen may not be optimal, but the alure of having
four monitors in one was too strong. This is emphasized in the section where I
compare the LG 43&amp;quot; with a Dell ultrawide:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Being so large, I could still have auxiliary windows opened on either side. One advantage the ud4379 had over the ultra-wide however, was that being a massive 16:9 screen, it could easily be divided into 4 1080p screens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Despite the nagging voice in the back of my head saying the ultrawide might be
more &lt;em&gt;reasonable&lt;/em&gt;, or reminding me that a &lt;em&gt;single&lt;/em&gt; 27&amp;quot; 1440p monitor was also an
option… I ignored it and picked up the 43&amp;quot; LG (which nonetheless, &lt;em&gt;is&lt;/em&gt; a great
monitor!).&lt;/p&gt;
&lt;h2&gt;The Large Display Paradox&lt;/h2&gt;
&lt;p&gt;So then. What &lt;em&gt;is&lt;/em&gt; the &lt;em&gt;“Large Display Paradox”&lt;/em&gt;? As far as I can find, the
term was coined by Jeff Atwood in a &lt;a href=&quot;https://blog.codinghorror.com/the-large-display-paradox/&quot;&gt;blog
post&lt;/a&gt; titled with the
same name. In the post, Atwood refers to &lt;a href=&quot;http://www.dansdata.com/3007wfp-hc.htm&quot;&gt;another
post&lt;/a&gt; in which Dan Rutter states the
following while reviewing his new Dell UltraSharp 3007WFP-HC monitor:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Users of 30-inch monitors face the terrible, terrible problem of how to
effectively use all of that space. You don’t often want to maximise a folder or
document window on a screen this big; either you’ll end up with a lot of white
space and important program buttons separated by a vast expanse of nothing, or
you’ll get lines of text 300 or more characters long, which are difficult to
read.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Atwood summarizes that this is a perfect example of what he deems to be the
&lt;em&gt;Large Display Paradox&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That’s the large display paradox. Having all that space can make you less
productive due to all the window manipulation excise you have to deal with to
make effective use of it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He further explains how once a display grows beyond the point of being able to
efficiently maximize windows without too much whitespace (above 1600x1200, by
his estimates), it becomes &lt;em&gt;less productive&lt;/em&gt; due to the user having to
continuously manage the windows.&lt;/p&gt;
&lt;p&gt;While these posts are on the older side (2007!), I think most people are more
likely to experience this issue &lt;em&gt;today&lt;/em&gt;, due to cheaper large monitor options.&lt;/p&gt;
&lt;h2&gt;My Experience&lt;/h2&gt;
&lt;p&gt;With the &lt;em&gt;Large Display Paradox&lt;/em&gt; finally defined… did I experience it using
my 43&amp;quot; monitor? Yes.&lt;/p&gt;
&lt;p&gt;At first, it was great. I loved throwing everything I wanted up on a single
screen, without any applications overlapping.&lt;/p&gt;
&lt;p&gt;In particular, during the first few months using the monitor, I still worked
remotely at my previous job and needed to use a Windows machine. I appreciated
that I could RDP to my work computer using an ultrawide resolution (3440x1440),
but &lt;em&gt;still&lt;/em&gt; have extra pixels to keep my notes and chat windows below it.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/large-display-paradox/G9hMAhK9bU-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/large-display-paradox/G9hMAhK9bU-1200.jpeg&quot; alt=&quot;Widescreen work RDP session&quot; width=&quot;1200&quot; height=&quot;787&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Remoting into my Windows work computer using an ultrawide resolution, and still having space for my notes and chat applications below.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Then, I switched jobs and used Linux 100% for home &lt;em&gt;and&lt;/em&gt; work. Instead of
having to RDP into a second machine… I could just &lt;code&gt;ssh&lt;/code&gt; using a small
terminal window. I no longer &lt;em&gt;had&lt;/em&gt; to view multiple machine desktops to get work
done.&lt;/p&gt;
&lt;h2&gt;Issues&lt;/h2&gt;
&lt;p&gt;At first, I didn’t notice any issues, especially when using it on my personal
computer which I mostly used for leisure anyway. It didn’t matter much if I had
a video and chat window next to a personal project I was working on.&lt;/p&gt;
&lt;p&gt;Regardless, over time I started to notice that when  I tried to actually &lt;em&gt;focus&lt;/em&gt; on a
task, the “usefulness” of the large screen really started to fall apart. Below
are a few issues I started to run up against when trying to be productive with
what was seemingly one of “the best productivity monitors on the market”.&lt;/p&gt;
&lt;h3&gt;Always wanting to fill the space.&lt;/h3&gt;
&lt;p&gt;The tricky thing about having all that space… is you want to fill it, even if
you don’t &lt;em&gt;need&lt;/em&gt; to for the task at hand. So for example, if I wanted to work
on writing a blog post, all I &lt;em&gt;need&lt;/em&gt; is a text editor. In addition, I like to
have a preview of the post open in a browser so I can see how it renders.  I
sometimes like to also have a photo editor and picture folders open, but I find
it best if I can have them in a separate &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_workplace&quot;&gt;virtual
workspace&lt;/a&gt;. With the large
display, I would open up &lt;em&gt;all&lt;/em&gt; those windows on the same screen. In reality,
having anything beyond my text editor and post preview in front of me, really
starts to detract from my ability to focus on &lt;em&gt;writing&lt;/em&gt; the post.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/large-display-paradox/hGHrxEEej2-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/large-display-paradox/hGHrxEEej2-1200.jpeg&quot; alt=&quot;Filling up the monitor&#39;s space&quot; width=&quot;1200&quot; height=&quot;781&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;With so much space, I felt obligated to fill it with windows unrelated to the task at hand.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;People like filling space. How often do you find that a
surface in the house seems to &lt;em&gt;always&lt;/em&gt; collect random crap? Large
monitors are the same. Even when I &lt;em&gt;knew&lt;/em&gt; I only needed a one small terminal
in from of me… the vast space surrounding it made me feel uneasy. So, I
would fill the space by either extending the editor window to a comical size,
or litter the surround space with random applications, unrelated to the work I
was trying to focus on.&lt;/p&gt;
&lt;h3&gt;I didn’t use the entire space, just focused on what was in my field of view in front of me.&lt;/h3&gt;
&lt;p&gt;Even though I felt obligated to fill all the space, &lt;em&gt;I still didn’t use all of
it&lt;/em&gt;. When I had two monitors, I would center myself on one to prevent looking at
bezels, and then usually only utilize &lt;em&gt;half&lt;/em&gt; of the secondary monitor, because
the far end of it was out of my sight. This didn’t change with a single large
monitor. I usually found my self using an “appropriately sized” window, smack
in the middle of the screen. If I needed to switch to working in a different
application, I would move &lt;em&gt;that&lt;/em&gt; application to the center and focus on it. The
outer spaces of the monitor were never fully used.&lt;/p&gt;
&lt;h3&gt;I couldn’t fullscreen (or even split-screen) my windows&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/large-display-paradox/7xw0r0l_FO-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/large-display-paradox/7xw0r0l_FO-1200.jpeg&quot; alt=&quot;Using Split Screen Windows&quot; width=&quot;1200&quot; height=&quot;676&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Fullscreen, and even split screen windows were unwieldy. Most of the time I just wanted to work from a normal sized window.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Speaking of focusing on a single application… fullscreen… it isn’t really
an option on such a big monitor. While a maximized window might look awesome at
first, it wasn’t very practical for day-to-day use. This issue was compounded
by the “shadow” that prevented me from seeing the first few characters near the
edge of the screen. The size of the screen combined with how far I sat from it,
meant that all but the center was out of my field of view. As such, I didn’t
maximize my windows, and usually just wanted a normal-sized one in the center.&lt;/p&gt;
&lt;h3&gt;Bad Keyboard Window Management Experience&lt;/h3&gt;
&lt;p&gt;Without being able to view windows in full or split screen configurations, all
of the quick window-snapping key bindings I’ve become accustomed to were
useless.  I was forced to switch over to my mouse more than I would like to.
While &lt;a href=&quot;https://www.youtube.com/watch?v=DuIK-NuN3aY&quot;&gt;some might point to using window management
software&lt;/a&gt;, or a &lt;a href=&quot;https://web.archive.org/web/20180320052505/http://www.jonblack.me/large-display-paradox-resolved/&quot;&gt;tiling window
manager&lt;/a&gt;, I don’t think
this would solve &lt;em&gt;my&lt;/em&gt; issues. You &lt;em&gt;might&lt;/em&gt; be able to get away with using a
window manager like &lt;a href=&quot;https://github.com/Airblader/i3&quot;&gt;i3-gaps&lt;/a&gt; (or
&lt;a href=&quot;https://github.com/swaywm/sway&quot;&gt;sway&lt;/a&gt;) on a 1440p ultrawide monitor, but a
full unscaled 4k space is just too much. Unless you &lt;em&gt;always&lt;/em&gt; have 5+ tiled
windows open at a time, they’re just too big.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I think the large display paradox could be a real modern tech issue. Well, more
accurately, a major &lt;a href=&quot;https://en.wikipedia.org/wiki/First_World_problem&quot;&gt;first world
problem&lt;/a&gt;. Given the
increased affordability of large displays, what might &lt;em&gt;seem&lt;/em&gt; like an great setup,
might not actually be optimal for one’s workload. At it’s core, technology
is a tool. When choosing tech hardware, we should always focus on what
the best tool &lt;em&gt;for the job&lt;/em&gt; is, and not just what is the best tool.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Switching to Bitwarden</title>
    <link href="https://ryan.himmelwright.net/post/switching-to-bitwarden/" />
    <updated>2019-09-13T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/switching-to-bitwarden/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switching-to-bitwarden/H26aG7RGZr-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switching-to-bitwarden/H26aG7RGZr-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;TROSA Thrift Store and Donation Center, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After years of using &lt;a href=&quot;https://www.passwordstore.org/&quot;&gt;pass&lt;/a&gt; as my password
manager, I am mixing things up. Over vacation, I started importing all of my
passwords into &lt;a href=&quot;https://bitwarden.com/&quot;&gt;Bitwarden&lt;/a&gt;. With all my passwords
finally transferred, I have now switched to using Bitwarden full-time. Here
are my thoughts so far.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why Switch?&lt;/h2&gt;
&lt;p&gt;Before I start, I want to point out that I had already started my switch &lt;em&gt;before&lt;/em&gt;
the large Bitwarden segment on the &lt;a href=&quot;https://linuxunplugged.com/316&quot;&gt;recent episode of Linux
Unplugged&lt;/a&gt;. That was just a re-assuring
coincidence &lt;code&gt;:)&lt;/code&gt; .&lt;/p&gt;
&lt;p&gt;So, why did I decide to switch?&lt;/p&gt;
&lt;h3&gt;Easier Setup&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switching-to-bitwarden/sgVjejIBBR-795.webp 795w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switching-to-bitwarden/sgVjejIBBR-795.jpeg&quot; alt=&quot;Bitwarden on Flathub Page&quot; width=&quot;795&quot; height=&quot;602&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Bitwarden on Flathub.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The main reason is that when configuring a new system, Bitwarden is simply
easier to setup. All I need to do is install a Bitwarden client and login. In
fact, using the hosted service I don’t even need to do that. I can just login
using the &lt;a href=&quot;https://vault.bitwarden.com/&quot;&gt;web vault&lt;/a&gt;. Installing is made even
easier by the fact that Bitwarden is packaged as a &lt;a href=&quot;https://flathub.org/apps/details/com.bitwarden.desktop&quot;&gt;Flatpak on
flathub&lt;/a&gt;. So on my
&lt;a href=&quot;https://silverblue.fedoraproject.org/&quot;&gt;Fedora Silverblue&lt;/a&gt; computers, it works
out of the box and already fits in with the design philosophy behind Silverblue
(running all user apps in containers). Perfect.&lt;/p&gt;
&lt;h3&gt;Mobile Support&lt;/h3&gt;
&lt;p&gt;Over the past few months I have been &lt;a href=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/&quot;&gt;switching up the tools I
use&lt;/a&gt;, at least the ones I use &lt;a href=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/&quot;&gt;outside of
work&lt;/a&gt;. The big driving force behind many of
these switches is better mobile support. While I still prefer to keep as much
data off of my phone as possible, there are some things I need to have on the
go. My password manager is unfortunately one of them.&lt;/p&gt;
&lt;p&gt;Additionally, as I start to test out different workloads on alternative devices
like my ipad, having a mobil-friendly work flow is a godsend. In fact, trying to
&lt;a href=&quot;https://ryan.himmelwright.net/post/setting-up-pass/&quot;&gt;setup pass&lt;/a&gt; on my iPad is what eventually frustrated
me enough to give Bitwarden a chance.&lt;/p&gt;
&lt;p&gt;Setup on both my phone and ipad was dead-simple. Install the app, and login.
Done.&lt;/p&gt;
&lt;h3&gt;Wayland Friendly&lt;/h3&gt;
&lt;p&gt;One of the best features of my pass system was how well it was integrated with
my desktop environment. &lt;em&gt;However&lt;/em&gt;, I want to switch to wayland now more than
ever, but my pass desktop integration is the biggest blocker preventing me from
using it on my machines. I access pass using launcher applications like &lt;code&gt;dmenu&lt;/code&gt;
and &lt;code&gt;rofi&lt;/code&gt;… but they do not function correctly in wayland. This forced me to
use &lt;code&gt;pass&lt;/code&gt; from a terminal window while in a wayland session. Bitwarden, at
least the limited GUI interactions I’ve used thus far, appears to work just
fine in wayland.&lt;/p&gt;
&lt;h3&gt;Easier to share/expand&lt;/h3&gt;
&lt;p&gt;Lastly, if my wife eventually wants to switch to Bitwarden, I &lt;em&gt;think&lt;/em&gt; we can
share our joint passwords with each other, even being on just the free personal
plan. If not, we can upgrade to the “family plan” which is currently $1/mo and
allows unlimited collections across 5 accounts &lt;em&gt;and&lt;/em&gt; optional self hosting. For
shared family password management… that’s a great deal.&lt;/p&gt;
&lt;h2&gt;What I am still learning&lt;/h2&gt;
&lt;p&gt;Normally, this is where I list the few things that I “don’t like”. However,
this still is all new and I &lt;em&gt;think&lt;/em&gt; the majority of my current issues are
solvable, but I just haven’t figured it out yet. So it’s a “What I’m still
learning” section this time (•‿•) .&lt;/p&gt;
&lt;h3&gt;Better keyboard/DE integration&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;main feature&lt;/em&gt; I still seem to be missing is better keyboard
support/integration with the desktop environment. The GUI app is nice, and
Bitwarden has &lt;em&gt;much&lt;/em&gt; better support for browser extensions and tools. Yet, I
haven’t found a good way to open up Bitwarden (give my master password),
search for an item, and copy the password… all from the keyboard.&lt;/p&gt;
&lt;p&gt;By comparison, &lt;code&gt;pass&lt;/code&gt; let me integrate with launchers like &lt;code&gt;dmenu&lt;/code&gt; and &lt;code&gt;rofi&lt;/code&gt;
to efficiently grab my passwords which was &lt;em&gt;awesome&lt;/em&gt;. As mentioned a few
sections above, &lt;code&gt;passmenu&lt;/code&gt; unfortunately falls apart for me these days now that I am
mostly on wayland… but I don’t need a full launcher. Honestly, all I
&lt;em&gt;need&lt;/em&gt; is the ability to copy a password (and maybe other fields) using my
keyboard once I have selected an item. I can already search using &lt;code&gt;ctrl-f&lt;/code&gt; +
&lt;code&gt;tab&lt;/code&gt;, and then &lt;code&gt;arrow&lt;/code&gt; my way down the list, but once there I cannot copy
unless I continue to tab through all the options. This is not efficient and
defeats the purpose of remaining on the keyboard in the first place.&lt;/p&gt;
&lt;p&gt;This is a highly requested feature, and I’m sure someone has hacked &lt;em&gt;some
solution&lt;/em&gt; together… I just have yet to find it… or implement it myself.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That is it. &lt;strong&gt;TLDR;&lt;/strong&gt; I’ve switched to Bitwarden and have been using it as my
main password manager for almost a month and I love it. It works on &lt;em&gt;all&lt;/em&gt;
my devices and doesn’t give me too many headaches. My only real complaint is
that I wish it had better keyboard support, but I’m sure I’ll be able to figure
out a work-around eventually.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setup a Runner VM for Gitlab</title>
    <link href="https://ryan.himmelwright.net/post/create-gitlab-runner/" />
    <updated>2019-09-04T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/create-gitlab-runner/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/VV1e7hhRIo-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/VV1e7hhRIo-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Sugar Creek Restaurant, Nags Head NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I play around with CI/CD pipelines quite a bit, both at home and at work. I have
mostly used Jenkins, but I wanted to see how Gitlab’s CI/CD tooling has
progressed over the last year. So, I decided to try to use Gitlab to manage the
automated build and deployments of a personal project I’ve been working on.
The first step of the process was to setup a runner my Gitlab instance could
use for the builds.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Setup a Machine/VM&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/-egPfzXCk2-640.webp 640w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/-egPfzXCk2-640.jpeg&quot; alt=&quot;Installing a new Fedora30 VM in Virt-Manager&quot; width=&quot;640&quot; height=&quot;586&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Installing a new Fedora 30 VM in Virt-Manager for my runner.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This will be a BYOG post (bring your own Gitlab). I already
“&lt;em&gt;had one laying around&lt;/em&gt;”, so I won’t cover setting that up.&lt;/p&gt;
&lt;p&gt;Your runner needs may differ, but in this post I am installing runner on a
Fedora 30 VM. I will also be using both &lt;a href=&quot;https://buildah.io/&quot;&gt;buildah&lt;/a&gt; and
&lt;a href=&quot;https://podman.io/&quot;&gt;podman&lt;/a&gt; for this project.&lt;/p&gt;
&lt;h3&gt;Some things to note/consider during VM setup:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Install packages required for pipeline tasks (ex: &lt;code&gt;podman&lt;/code&gt; and &lt;code&gt;buildah&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;sudo&lt;/code&gt; is required, manage the &lt;code&gt;gitlab-runner&lt;/code&gt; user/group using &lt;code&gt;visudo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If using docker runners, &lt;code&gt;docker-machine&lt;/code&gt; needs to be installed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Install runner&lt;/h2&gt;
&lt;p&gt;First, install the &lt;code&gt;gitlab-runner&lt;/code&gt; package. This can be done using the
instructions found
&lt;a href=&quot;https://docs.gitlab.com/runner/install/linux-repository.html&quot;&gt;here&lt;/a&gt;.
&lt;em&gt;However&lt;/em&gt;, I encountered issues installing it on my Fedora VMs, as this install
method isn’t supported for 30 yet.  (Check out &lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab-runner/issues/4401&quot;&gt;this
issue&lt;/a&gt; for more info).&lt;/p&gt;
&lt;h3&gt;Add GitLab’s Repo&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Install gitlab runner&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install gitlab-runner
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;(Alternative) Copr install&lt;/h2&gt;
&lt;p&gt;For now, I have been using the copr install posted in the comments
of that issue (linked above). I recommend checking if the issue is resolved first, as it
might change from the time of writing this post. To install:&lt;/p&gt;
&lt;p&gt;First enable the copr repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf copr enable snecker/gitlab-runner -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, install:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install gitlab-runner -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Register the Runner&lt;/h3&gt;
&lt;p&gt;Once installed, register the runner. Instructions on how to register a runner
can be found &lt;a href=&quot;https://docs.gitlab.com/runner/register/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo gitlab-runner register
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enter the coordinator URL (ex: &lt;code&gt;https://gitlab.com&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Next, a &lt;em&gt;gitlab-ci&lt;/em&gt; token must be shared with the runner.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/riLMsypEwC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/riLMsypEwC-1200.jpeg&quot; alt=&quot;Gitlab Runner Settings&quot; width=&quot;1200&quot; height=&quot;522&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Gitlab Runners Settings Page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To obtain a gitlab-ci token, got to &lt;strong&gt;Admin Area&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Overview&lt;/strong&gt; -&amp;gt;
&lt;strong&gt;Runners&lt;/strong&gt;. On the right, there should be a token to use during setup.&lt;/p&gt;
&lt;p&gt;When the runner registrations asks for the token, use the “registration token”
listed in the “Set up a shared Runner manually” section.&lt;/p&gt;
&lt;p&gt;Next, provide a short description, and add a tag or two (when prompted).&lt;/p&gt;
&lt;p&gt;Lastly, enter the executor (the system on the runner that executes commands). For
now, I’ve been using &lt;code&gt;&amp;quot;shell&amp;quot;&lt;/code&gt; for my needs, as these VMs are fully
dedicated to be used as the runners for a single project.&lt;/p&gt;
&lt;p&gt;Congrats, the runner should be registered! Now to set it up…&lt;/p&gt;
&lt;h2&gt;Link to CI/CD Builds&lt;/h2&gt;
&lt;p&gt;It is time to link up the runner to a CI/CD job. This can be done with
tagging, but I currently just have one pipeline using my runners, so haven’t
used the tags as much. Edit the runner by clicking its  &lt;code&gt;edit&lt;/code&gt; icon.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/VZV2Si1UfR-1042.webp 1042w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/VZV2Si1UfR-1042.jpeg&quot; alt=&quot;Gitlab Runner Settings&quot; width=&quot;1042&quot; height=&quot;998&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Gitlab Runner Edit Page&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the runner edit menu, ensure that the “&lt;code&gt;Active&lt;/code&gt;” checkbox is checked. I’ve
also checked the “&lt;code&gt;Run untagged jobs&lt;/code&gt;” box for this runner, which will allow it
to pick up any job that does &lt;em&gt;not&lt;/em&gt; have a tag. If the runner is to be assigned
to a &lt;em&gt;specific&lt;/em&gt; project, that can be enabled/assigned below in the “&lt;code&gt;Restrict projects for this Runner&lt;/code&gt;” section.&lt;/p&gt;
&lt;h2&gt;Test Run&lt;/h2&gt;
&lt;p&gt;To test out the runner, start a new build in a project! (Note, if there are
several runners already setup, 1. why are you reading this, and 2. it might be a good idea
to pause the others to ensure the new one will run with the test).&lt;/p&gt;
&lt;p&gt;I won’t detail how to write a &lt;code&gt;gitlab-ci.yml&lt;/code&gt; now, but for my test I made an empty
demo repo with the following pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;before_script:
  - whoami
  - pwd
  - sudo dnf update -y

build-base:
  stage: build
  script:
    - echo &amp;quot;Hello world!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After committing it, a build kicked off with the new runner and finished
successfully!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Notice that the job indeed ran on &lt;code&gt;post-runner&lt;/code&gt;, the runner I setup
specifically for this post&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/Ehx1EfKQ5m-1034.webp 1034w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/create-gitlab-runner/Ehx1EfKQ5m-1034.jpeg&quot; alt=&quot;Gitlab Demo Run Results&quot; width=&quot;1034&quot; height=&quot;1142&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Gitlab Demo Job Run Results&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;If the job is more complicated, more runs might have to be manually started
after tweaking the runner settings again. Pipelines can be started by going to
the project’s &lt;code&gt;CI/CD-&amp;gt;Pipelines&lt;/code&gt; page via the side menu, and hitting the &lt;code&gt;Run Pipeline&lt;/code&gt; button.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it. We should now have a connected runner! So far, the runners have been working
&lt;em&gt;(mostly)&lt;/em&gt; fine. When they &lt;em&gt;do&lt;/em&gt; break, it is usually because I’ve let the disk
fill up or allowed some other system-related negligence to build up
&lt;code&gt;¯&#92;_(ツ)_/¯&lt;/code&gt;. I might add some ‘runner maintenance’ steps to my pipeline… but
some other time. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Back On org-mode For Work</title>
    <link href="https://ryan.himmelwright.net/post/back-on-org-mode-for-work/" />
    <updated>2019-08-07T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/back-on-org-mode-for-work/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/2UhAuEd_MW-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/2UhAuEd_MW-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Eno State Park, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last month, I wrote about my &lt;a href=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/&quot;&gt;switch to Joplin&lt;/a&gt;
for both my personal and work notes. While I enjoyed many features in Joplin, I
also had a few concerns about using the system long-term for all my notes.
As of last week, I am still using Joplin for all of my personal notes, but have switched
back to &lt;code&gt;org-mode&lt;/code&gt; for my notes at work. Why?&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Use Cases&lt;/h2&gt;
&lt;p&gt;To better understand why Joplin works well for me at home but can’t effectively
replace org-mode at work, lets first list some different features between Joplin and
emacs org-mode.&lt;/p&gt;
&lt;h2&gt;Joplin Features&lt;/h2&gt;
&lt;h3&gt;“Notes”-Style Focused&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/bi2LEhhLUP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/bi2LEhhLUP-1200.jpeg&quot; alt=&quot;Joplin GUI&quot; width=&quot;1200&quot; height=&quot;763&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Joplin uses a traditional &#39;note&#39; layout, with blank text boxes organized into notebooks.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Joplin organizes notes in a traditional “notes” style. Each note starts as a
big, blank text-box that the user fills in. These notes are further organized
into “notebooks”, which are simply a list of one or more notes. The notes are
formatted with markdown, but the application doesn’t seem to support features
like collapsible sections.&lt;/p&gt;
&lt;h3&gt;Simple Markdown Format – Sharable&lt;/h3&gt;
&lt;p&gt;The markdown editor in Joplin may not support collapsing sections, but it does
a great job at supporting other features, like simple code highlighting and web
links. Additionally, being formated in markdown, the note contents can be easily
exported, transferred, or even shared with other applications and/or people.
This is handy when grabbing snippets from the web, which may already be in a
markdown friendly format.&lt;/p&gt;
&lt;h3&gt;Shared across Computer and Mobile Devices&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/6udE7aj0GN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/6udE7aj0GN-1200.jpeg&quot; alt=&quot;Joplin Android GUI&quot; width=&quot;1200&quot; height=&quot;588&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Notebook and Notes views on the Android client.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, the Joplin application is available on all platforms (including
mobile), and manages the system to sync the notes across all devices. This
makes it a breeze to write a note at a computer, and then have it immediately
available on a phone afterwards.&lt;/p&gt;
&lt;h2&gt;Org-mode use cases&lt;/h2&gt;
&lt;h3&gt;Logging-Style Notes&lt;/h3&gt;
&lt;p&gt;By comparison, Emacs’ org-mode can be very &lt;em&gt;task&lt;/em&gt; oriented, allowing for a more
‘log-style’ note-taking system. It encourages outline organization of notes,
organized under “tasks” which can be collapsed in a hierarchy. The
tasks can be marked “TODO” or “DONE” using the built in TODO system, or
tagged for organization. While org-mode can be used when writing “normal” notes, it really
shines when working as a task/note system.&lt;/p&gt;
&lt;h3&gt;Quick Notes, Key-bindings&lt;/h3&gt;
&lt;p&gt;Another feature in org mode is the note drawer. By pressing a key combination
(as one tends to do in Emacs), a new buffer will open up to take a note in. The
contents of the note may be text, or even a code snippet (&lt;em&gt;in the emacs mode of
that language&lt;/em&gt;). When done, I can simply press &lt;code&gt;C-c C-c&lt;/code&gt; and the buffer will
close, inserting the note with a time-stamped entry as part of the &lt;code&gt;logbook&lt;/code&gt;
for the task I opened the note in.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/note-demo.gif&quot;&gt;Click for a demo animation logging a quick note&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This quick logging feature, along with the power of all the key-binding
customization, makes note logging fast, efficient, and enjoyable.&lt;/p&gt;
&lt;h3&gt;TODO/SCRUM Board tasks&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/lvwPJTuJvQ-1056.webp 1056w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/lvwPJTuJvQ-1056.jpeg&quot; alt=&quot;Joplin Android GUI&quot; width=&quot;1056&quot; height=&quot;887&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I wrote a package that lets me view org tasks in a scrum board.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As mentioned, org-mode has a powerful
&lt;a href=&quot;https://orgmode.org/manual/TODO-items.html&quot;&gt;todo&lt;/a&gt; system. Users can configure
how task items should be grouped beyond just a basic &lt;code&gt;TODO&lt;/code&gt;/&lt;code&gt;DONE&lt;/code&gt;. Using this
flexability (and some lisp knowledge), I wrote an &lt;a href=&quot;https://github.com/himmAllRight/ry-org-scrum&quot;&gt;scrum board generator for
org-mode&lt;/a&gt;. This package takes all
of my &lt;code&gt;TODO&lt;/code&gt; items, and organizes them into scrum task board. This feature
helps organize my daily and weekly task lists.&lt;/p&gt;
&lt;h3&gt;Exporting/Archiving Weekly Logs&lt;/h3&gt;
&lt;p&gt;The big “feature” org mode has that I find lacking in Joplin, is the
ability to better organize long-term sets of notes. With a logging style of
note-taking, notes are often organized by a specific range in time (day, week,
month). These systems are naturally structured hierarchically and Joplin’s notebook/note
organization only allows for systems to be 2 levels deep. However, org files are stored
as text files on a traditional file system.&lt;/p&gt;
&lt;p&gt;Lastly, org files can be linked (and exported as html files), so any note can
still be quickly navigated to, even if the archive spans hundreds of note files over
several years.&lt;/p&gt;
&lt;h2&gt;Home vs Work Notes&lt;/h2&gt;
&lt;p&gt;Now with some of each note system’s features broken down, how do they
fit with my preferences across different note taking use-cases? (mostly personal vs.
work)&lt;/p&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/tOOnfjxW85-853.webp 853w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/tOOnfjxW85-853.jpeg&quot; alt=&quot;Joplin Android GUI&quot; width=&quot;853&quot; height=&quot;1280&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My personal notes tend to be organized as more traditional notes. I make large
groups, such as ‘Home’, ‘Tech’, ‘Travel’, and stash notes into each. In a note,
I tend to organize information on a topic and store it for later. For example,
I may have a notebook for online classes, and then a note for each class.&lt;/p&gt;
&lt;p&gt;For another example, I have a notebook called ‘Travel’ which contains a note
for each trip I plan.  In the note, I store information that I will need, such
as flight numbers, or hotel addresses. Below important information, I jot down
ideas while planning and researching before leaving. This may include places to
to eat, or activities I want to do (with links to the websites).&lt;/p&gt;
&lt;p&gt;Joplin’s mobile platform support is particularly useful here, as I am able to
write up notes on my desktop, and then read them from my phone on the go.&lt;/p&gt;
&lt;h3&gt;Work&lt;/h3&gt;
&lt;p&gt;By contrast, my work notes are a mix of task-list and logbook for each
week. I plan each week by moving &lt;code&gt;TODO&lt;/code&gt; tasks from one day to
the next, and changing the status as I work on each one. As I work, I log notes
under each task about my progress, as well as any issues/solutions I experience
on the way. While the notes are mostly rambling, logging thoughts as I work
helps me to quickly identify issues, and solve problems faster.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/MNYPkipSVd-1082.webp 1082w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-org-mode-for-work/MNYPkipSVd-1082.jpeg&quot; alt=&quot;My notes exported as linked html pages&quot; width=&quot;1082&quot; height=&quot;832&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The index page of my exported weekly work notes.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Most importantly, due to organizing notes by &lt;em&gt;week&lt;/em&gt; rather than topic, it is
best if I can archive the notes in more of a tree layout. Org notes are plain
text files, so I can save them on my hard drive any way I want. For example, I
have my work notes organized as &lt;code&gt;./work/archive/{YEAR}/{MONTH}{DAY}/week-of-{date}.org&lt;/code&gt;.
Each week, I also export the finished notes to linked html files, which is very
useful for going back and looking at my previous notes.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it. I’ve switched back to emacs org-notes for my work tasks,
but remain on Joplin for my personal and home notes. So far… I’m loving
it. I think that I switched to Joplin because org-mode wasn’t ideal for my
personal notes. However, Joplin was lacking for my work
notes. That’s okay. I learned that my home vs. work notes are two completely
different use cases, and as a result should use different tools.&lt;/p&gt;
&lt;p&gt;So, moving forward, I plan to keep using Joplin for my personal notes, and
emacs org-mode for my work tasks. Perfect.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>New LG-32QK500-w Monitor</title>
    <link href="https://ryan.himmelwright.net/post/new-lg32qk500w/" />
    <updated>2019-07-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/new-lg32qk500w/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/OYTMurMHT2-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/OYTMurMHT2-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;At the beginning of summer, I was entertaining the idea of improving my wife’s
computer setup by adding a new external monitor. She agreed. As a result, we
have purchased, recieved, and even &lt;em&gt;mounted&lt;/em&gt;, the &lt;a href=&quot;https://www.lg.com/us/monitors/lg-32QK500-W-led-monitor&quot;&gt;LG
32QK500w&lt;/a&gt; monitor.
Here are my thoughts.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Reasoning&lt;/h2&gt;
&lt;p&gt;Recently, my wife has had to spend more time prepping at night for her next workday, which
she does on her laptop. This made it a good time to finally setup her
office space in the guest bedroom. She has an old 21.5&amp;quot; 1080p monitor from
college, but we decided it might not be the best fit for the
space.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/dmdQAfQE8--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/dmdQAfQE8--1200.jpeg&quot; alt=&quot;Netflix logo&quot; width=&quot;1200&quot; height=&quot;361&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;We thought that it would be nice to have a larger screen in that room, guests
could watch video content. Additionally, my wife and I have a habit of falling
asleep on the couch watching TV most weekend nights… but we want to keep our
bedroom TV free. Having a spot where we can lay down on a bed and prep for the
inevitable while watching a movie, is probably a good idea (and our backs agree).&lt;/p&gt;
&lt;p&gt;I started looking for slightly larger monitors and quickly noticed something… the
2560x1440 32&amp;quot; monitors were cheaper than their 27&amp;quot; counterparts. This
made sense, as the dpi is lower. However, after doing the math, 2560x1440 pixels @ 32&amp;quot; is
the same dpi of as a 24&amp;quot; 1920x1080 monitor (109 dots per inch). While this
isn’t the &lt;em&gt;sharpest&lt;/em&gt; configuration, it is very common and reasonable for productivity setups.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/oc9Ze7EoYi-811.webp 811w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/oc9Ze7EoYi-811.jpeg&quot; alt=&quot;2560 by 1440 pixels at 32 inches has a dpi of 91.&quot; width=&quot;811&quot; height=&quot;301&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;2560x1440 pixels at 32&quot; has a dpi of 91.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/oBWBy7xtiA-811.webp 811w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/oBWBy7xtiA-811.jpeg&quot; alt=&quot;1920 by 1080 pixels at 24 inches also has a dpi of 91.&quot; width=&quot;811&quot; height=&quot;301&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;1920x1080 pixels at 24&quot; also has a dpi of 91.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In fact, my wife doesn’t have the best vision (especially at night when she
tends to work), and tends to actually &lt;em&gt;prefer&lt;/em&gt; the larger scale of 109 dpi. We
even purposefully picked a 1600x900 resolution screen over a FHD one when
buying her Thinkpad for the same reason.&lt;/p&gt;
&lt;p&gt;Lastly, while it is scaled up a bit on a physically larger display, these
monitors still provide a 2560x1440 resolution. Recently, I have decided that
this particular resolution, may be the best for productivity , at least in the
type of workflows I use. I even figured out how to &lt;a href=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/&quot;&gt;configure a 2560x1440
“sub-monitor” on my 43&amp;quot; QHD monitor&lt;/a&gt;
when I need to &lt;em&gt;focus&lt;/em&gt;. Having an &lt;em&gt;actual&lt;/em&gt; 2560x1440 monitor in the house would
be wonderful for both my wife’s and my own productivity.&lt;/p&gt;
&lt;h2&gt;Selection&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/bu-I-ZKw3q-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/bu-I-ZKw3q-1200.jpeg&quot; alt=&quot;New monitor box&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The new monitor in it&#39;s box.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So I wanted a 32&amp;quot;, 2560x1440 IPS monitor… at a decent price. After browsing
around amazon, I came across the &lt;a href=&quot;https://www.amazon.com/gp/product/B07LD6XJ8X/ref=ppx_yo_dt_b_asin_title_o03_s01?ie=UTF8&amp;amp;psc=1&quot;&gt;LG
32QK500W&lt;/a&gt;.
It was listed at a very reasonable price (on sale), had a few bonus features
(75 Hz refresh + freesync options), and was an LG IPS panel, which I felt
comfortable purchasing considering how much I enjoyed &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/&quot;&gt;my LG
monitor’s&lt;/a&gt; panel.&lt;/p&gt;
&lt;p&gt;In summary, the LG 32QK500-W has the following specs/features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;32 inch, 2560 x 1440 pixel resolution&lt;/li&gt;
&lt;li&gt;IPS Panel&lt;/li&gt;
&lt;li&gt;mDP and 2 HDMI imports&lt;/li&gt;
&lt;li&gt;VESA mount compatible&lt;/li&gt;
&lt;li&gt;Decently thin bezels&lt;/li&gt;
&lt;li&gt;60 &lt;em&gt;and&lt;/em&gt; 75 Hertz refresh rates&lt;/li&gt;
&lt;li&gt;Freesync&lt;/li&gt;
&lt;li&gt;On sale for less than $250 (USD)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also purchased a cheap &lt;a href=&quot;https://www.amazon.com/gp/product/B01BCUM766/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;Amazon basics
mount&lt;/a&gt;
to wall mount it, but in hindsight it might of been worth paying a little
extra for a better one.&lt;/p&gt;
&lt;h2&gt;What I Like&lt;/h2&gt;
&lt;h3&gt;Resolution&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/p3X4l-afaq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/p3X4l-afaq-1200.jpeg&quot; alt=&quot;Screenshot of working on monitor&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The resolution is perfect for productivity work.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The “2k” resolution is perfect. It gives just enough space to get work done,
but not &lt;em&gt;so&lt;/em&gt; much space that it is distracting. I can perfectly fit two &lt;em&gt;full&lt;/em&gt;
windows next to each other on the screen. At the same time, it is still a small
enough resolution that a single focused full screen window doesn’t look odd.&lt;/p&gt;
&lt;h3&gt;Colors&lt;/h3&gt;
&lt;p&gt;The colors look great. They are vivid, and the blacks are &lt;em&gt;dark&lt;/em&gt;.
I think I may even enjoy them better than on my own monitor…&lt;/p&gt;
&lt;h3&gt;75 Hz refresh rate (and Freesync!)&lt;/h3&gt;
&lt;p&gt;Lastly, the 75 Hz refresh rate and Freesync was a nice little bonus. While the
only computer we have in the house that can &lt;em&gt;really&lt;/em&gt; take advantage of these
features is &lt;a href=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/&quot;&gt;my desktop&lt;/a&gt; (which is in another
room)…  it’s nice to know we have the option if I want to test it out.&lt;/p&gt;
&lt;h2&gt;What I Don’t Like&lt;/h2&gt;
&lt;h3&gt;No speakers… and audio jack is noisy&lt;/h3&gt;
&lt;p&gt;Normally, I could care less if a monitor has speakers or not. They usually
sound terrible, and I end up plugging external headphones/speakers to the
computer directly anyway. However, as we intend to also use this monitor as a
“TV” via an &lt;a href=&quot;https://www.amazon.com/Fire-TV-Stick-4K-with-Alexa-Voice-Remote/dp/B079QHML21/ref=sr_1_1?keywords=amazon+fire+stick&amp;amp;qid=1563791000&amp;amp;s=gateway&amp;amp;sr=8-1&quot;&gt;Amazon
Firestick&lt;/a&gt;,
it would be nice to have even &lt;em&gt;basic&lt;/em&gt; speakers to pump audio through by
default. It &lt;em&gt;does&lt;/em&gt; have a 3.5mm headphone jack, but there seemed to be a bit of
noise when I tried it.&lt;/p&gt;
&lt;h3&gt;While dpi is perfect for it’s use, it isn’t the best&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, the dpi isn’t the best, and the image isn’t quite as
“crisp” as you might find on a high resolution display. Also, at such a large
screen size, it is almost required to sit back from the screen a little bit
further than normal. However, as I already described in the sections above…
for our use case, this was actually preferred.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/E62YzUAHxy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lg32qk500w/E62YzUAHxy-1200.jpeg&quot; alt=&quot;Screenshot of working on monitor&quot; width=&quot;1200&quot; height=&quot;1137&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The monitor all set up mounted above my wife&#39;s new desk. (Next step... hide the wires!)&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;There isn’t much left to say. At this price point, and for what we wanted this
monitor for, I think it was a perfect match. While it might not be the absolute
&lt;em&gt;best&lt;/em&gt; monitor out there, it is a great value. If you’re looking for a large
1440p monitor to get some work done on, that could also be a TV replacement at
a good price (I’m looking at you college students), I highly recommend it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Switched to Joplin Notes</title>
    <link href="https://ryan.himmelwright.net/post/switched-to-joplin-notes/" />
    <updated>2019-06-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/switched-to-joplin-notes/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/bf46lJd0VW-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/bf46lJd0VW-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pardee Hall - Lafayette College, Easton PA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As a &lt;a href=&quot;https://ryan.himmelwright.net/post/org-babel-setup/&quot;&gt;massive fan&lt;/a&gt; of emacs’s
&lt;a href=&quot;https://orgmode.org/&quot;&gt;org-mode&lt;/a&gt;, it should be no surprise that I’ve been using
it for my personal and work planning/notes over the last few years.  However,
as my daily emacs usage has slowly dropped, and the support for &lt;code&gt;.org&lt;/code&gt; files
outside of emacs remains low (other than &lt;a href=&quot;https://github.com/himmAllRight/dotfiles/tree/master/emacs&quot;&gt;Github README
files&lt;/a&gt;), I started
to look for a more standard system. The last few months, I’ve been using
&lt;a href=&quot;https://joplinapp.org/&quot;&gt;Joplin&lt;/a&gt;. Here are my thoughts.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Previous System - Org-mode&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/Vt_KC_Bj-7-968.webp 968w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/Vt_KC_Bj-7-968.jpeg&quot; alt=&quot;Emacs with a note opened in org-mode.&quot; width=&quot;968&quot; height=&quot;955&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Emacs with a note opened in org-mode.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Previously, I had been taking all of my notes using org-mode in emacs. Each
week, I made a new &lt;code&gt;.org&lt;/code&gt; file for my work notes, and another for my personal
planning/notes. I would then list my &lt;a href=&quot;https://orgmode.org/manual/TODO-items.html&quot;&gt;todo
items&lt;/a&gt;, usually grouped by day,
category, or both. I would also
&lt;a href=&quot;https://orgmode.org/manual/Tags.html&quot;&gt;tag&lt;/a&gt; each item, for better organization.&lt;/p&gt;
&lt;p&gt;As I worked on each task, I could quickly add time-stamped notes using the
built-in &lt;a href=&quot;https://orgmode.org/manual/Drawers.html&quot;&gt;logbook drawer&lt;/a&gt; shortcut. In
these notes, I would ramble, or paste a code snippet to save for later. As work
on each item progressed, I could set the &lt;code&gt;TODO&lt;/code&gt; status to &lt;code&gt;Working On&lt;/code&gt;
&lt;code&gt;Finished&lt;/code&gt;, &lt;code&gt;Removed&lt;/code&gt;, or any other state I had pre-defined.  Eventually, I
&lt;a href=&quot;https://github.com/himmAllRight/ry-org-scrum&quot;&gt;wrote an emacs-lisp
script/package&lt;/a&gt; that took the
&lt;code&gt;todo&lt;/code&gt; items in my &lt;code&gt;org&lt;/code&gt; file, and dumped them into a SCRUM board at the top of
the file.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/gv3iS8zl2V-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/gv3iS8zl2V-1200.jpeg&quot; alt=&quot;Pictured of org notes exported to HTML&quot; width=&quot;1200&quot; height=&quot;564&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;org-notes allowed me to link and export my notes to HTML.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, org files can be &lt;a href=&quot;https://orgmode.org/manual/Exporting.html&quot;&gt;exported&lt;/a&gt;
to all sorts of file types (md, html, pdf, latex). By combining the export
feature with &lt;a href=&quot;https://orgmode.org/manual/Internal-links.html&quot;&gt;internal
linking&lt;/a&gt;, I was able to create
an organized system for my archived notes. I created an &lt;code&gt;index.org&lt;/code&gt; file which
linked to each week’s notes. Every week, I would archive the html export of a
note, and add it to the index list. This meant I could browse my old notes as a
website.&lt;/p&gt;
&lt;h3&gt;What I liked&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;org-mode&lt;/code&gt; is great.&lt;/li&gt;
&lt;li&gt;I could take all of my notes in a simple markdown language (org)&lt;/li&gt;
&lt;li&gt;Good code-block support&lt;/li&gt;
&lt;li&gt;Note linking&lt;/li&gt;
&lt;li&gt;Exported to html (or txt, md, pdf, or anything really)&lt;/li&gt;
&lt;li&gt;My previous notes were all exported to an archive website&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Issues&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I &lt;em&gt;had&lt;/em&gt; to use emacs for any edits&lt;/li&gt;
&lt;li&gt;Not supported on mobile devices&lt;/li&gt;
&lt;li&gt;As I used more &lt;code&gt;.md&lt;/code&gt; syntax for other applications, I found myself fumbling between &lt;code&gt;org&lt;/code&gt; and &lt;code&gt;md&lt;/code&gt; syntax often.&lt;/li&gt;
&lt;li&gt;Syncing issues (mostly Nextcloud issues. Seafile helped, but it still occurred)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in all, I loved taking notes in org-mode. However, I wanted something that
could be used on &lt;em&gt;all&lt;/em&gt; my devices, and was more easily translatable outside of
“emacsOS”.&lt;/p&gt;
&lt;h2&gt;The Switch - Joplin&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/mEsHTvTH-r-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/mEsHTvTH-r-1200.jpeg&quot; alt=&quot;Window of the Joplin GUI application&quot; width=&quot;1200&quot; height=&quot;703&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Joplin GUI window (in &#39;split mode&#39;).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h3&gt;About Joplin&lt;/h3&gt;
&lt;p&gt;Joplin is an open source note-taking and to-do application. It synchronizes and
organizes markdown notes into notebooks, across many platforms (Android, iOS,
macOS, Linux, and Windows). It even has both a GUI &lt;em&gt;and&lt;/em&gt; terminal client.  So,
I decided to give it a try.&lt;/p&gt;
&lt;h3&gt;My Setup&lt;/h3&gt;
&lt;p&gt;When configuring joplin, a sync system has to be defined (well, &lt;em&gt;should&lt;/em&gt;. Notes
can also be saved locally). There are a few supported options including using
Nextcloud. I had previously synced my org notes using Nextcloud, but eventually
moved away from it due to repeated file conflict issues. So I wanted to try
something different.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/vLLacheJjT-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/vLLacheJjT-1200.jpeg&quot; alt=&quot;Window of the Joplin GUI application&quot; width=&quot;1200&quot; height=&quot;384&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Joplin GUI window (in &#39;split mode&#39;).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While researching Joplin, I learned that I could sync notes to the WebDav
server on the fastmail account I have for email. I
wasn’t using any of the account’s file storage, so I figured I this would be a
good way to get use out of a service I’m already paying for. It works great!&lt;/p&gt;
&lt;h2&gt;What I like&lt;/h2&gt;
&lt;h3&gt;Syncs on &lt;em&gt;all&lt;/em&gt; my devices&lt;/h3&gt;
&lt;p&gt;What I have enjoyed most about this setup is that my notes sync to all of my
devices. Most importantly, I can &lt;em&gt;read&lt;/em&gt; the notes on every device. With
previous systems, the files would sync everywhere, but I still couldn’t always
easily read them.&lt;/p&gt;
&lt;h3&gt;CLI and GUI versions&lt;/h3&gt;
&lt;p&gt;Similarly, I like that Joplin has both a GUI and CLI client. I
mostly use the GUI, but enjoy having the CLI as an option because it means I
can always ssh into a machine and take notes. Additionally, the
cli client allows me to take/edit notes using my own terminal editors such as
emacs &lt;em&gt;or&lt;/em&gt; vim. This is quite convenient, and I should probably start using
Joplin this way more often.&lt;/p&gt;
&lt;h3&gt;Markdown Note Exports&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/SHwBOU3W8U-445.webp 445w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/SHwBOU3W8U-445.jpeg&quot; alt=&quot;Joplin cli application&quot; width=&quot;445&quot; height=&quot;143&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Export options in Joplin GUI.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I like that notes are formatted in markdown. This provides some
consistency when writing, but ensures that my notes are in a format that is
standardized. Additionally, joplin allows easy exporting of
the notes to other formats such as &lt;code&gt;json&lt;/code&gt; or as &lt;code&gt;pdf&lt;/code&gt;s.&lt;/p&gt;
&lt;h3&gt;Easy enough to use&lt;/h3&gt;
&lt;p&gt;Lastly, Joplin has been easy to use. After setup, I just have to open the app,
and start typing away (maybe with a sync or two). While my last setup required
the user to be somewhat of an emacs/org guru… this doesn’t.&lt;/p&gt;
&lt;h2&gt;Issues I’m still figuring out/Anticipate&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/56MrdxeRfP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switched-to-joplin-notes/56MrdxeRfP-1200.jpeg&quot; alt=&quot;Joplin cli application&quot; width=&quot;1200&quot; height=&quot;746&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Joplin CLI window.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Setup is a pain&lt;/h3&gt;
&lt;p&gt;While configuring sync can be a pain, it’s not my biggest gripe with the Joplin
setup process. It’s adding a new system. By default, if I configure a new
device with my sync solution (my laptop or phone for example) and then sync, it
will load up all of the crap default example tags and notes, which then pull
down to all of my systems. This is extremely frustrating because I have to keep
deleting them.  This is multiplied by the fact that it happens with &lt;em&gt;each
client&lt;/em&gt; I setup. So if I setup the gui and cli on my laptop, it will happen
&lt;em&gt;twice&lt;/em&gt; (2x each default tags/notes will show up on all my systems).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Semi-Solution&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;slight&lt;/em&gt; work-around I have been able to use when setting up a new computer
is to simply copy the config files from an existing system to the new one.
These folders can be found at &lt;code&gt;~/.config/joplin/&lt;/code&gt;, &lt;code&gt;~/.config/Joplin/&lt;/code&gt;, and/or
&lt;code&gt;~/.config/joplin-desktop/&lt;/code&gt;. This also means that the new system &lt;em&gt;should&lt;/em&gt; have less
it needs to sync down from the server on the first sync.&lt;/p&gt;
&lt;h3&gt;Long term archiving&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;big&lt;/strong&gt; problem I have… and think I will continue to have with Joplin, is
the long-term organization of notes. Part of this is my inability to figure out an
efficient and organized way to handle my “log” notes.&lt;/p&gt;
&lt;p&gt;This problem partially stems from Joplin’s shallow organizational
structure. There are notebooks, and notes. That’s it. Unlike having
a file structure where I can do something like
&lt;code&gt;archive/2019/June/Weekly_Plans/&lt;/code&gt;, I can’t really go that deep with Joplin, so
I end up just having a single notebook with &lt;em&gt;all&lt;/em&gt; my weekly plan notes listed
in it.&lt;/p&gt;
&lt;p&gt;One remaining solution might be to get more aggressive with the tagging
feature, and see if I can create a more organized system using that. I might be
able to tag each note with a month/year, and then truncate my log notes each
month. The only problem with this is that it doesn’t seem like I can select
&lt;em&gt;multiple&lt;/em&gt; tags at a time, which would mean &lt;em&gt;all&lt;/em&gt; of my notes for a month
will be shown under the tag. That would be a deal breaker for me :( .&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In summary, I think that Joplin &lt;em&gt;is&lt;/em&gt; a great open source notes solution.  If
someone needs a system to keep a bunch of normal notes synced across all their
devices, this is it. I however, might need to try out something else long-term.
On the bright side, there are a bunch of features in Joplin that love and will
definitely look for… in my next solution.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>SELF 2019</title>
    <link href="https://ryan.himmelwright.net/post/self2019/" />
    <updated>2019-06-20T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/self2019/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/self2019/YNwhlubUKm-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/self2019/YNwhlubUKm-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Sheraton Charlotte Airport, Charlotte, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The past few years I have watched the South East Linux Fest (SELF) from a
distance, often via listreams run by &lt;a href=&quot;https://www.jupiterbroadcasting.com/&quot;&gt;Jupiter
Broadcasting&lt;/a&gt; and/or the &lt;a href=&quot;https://www.asknoahshow.com/&quot;&gt;Ask Noah
Show&lt;/a&gt;. Last year, I decided to finally travel to
Charlotte for SELF 2018. While my wife and I &lt;em&gt;did&lt;/em&gt; drive down to North Carolina
that weekend, it was instead to Durham… with a moving truck. This year, being
only two hours away, I decided there was no excuse. I did a day-trip…  but I
went. I’m glad I did.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;SELF&lt;/h2&gt;
&lt;p&gt;I finalized my plans to attend SELF last minute, about two weeks ago. I
convinced a friend from work to attend with me one day over lunch, and we
registered right after. So, this past Saturday morning we left Durham around
6:30 am for the 2-ish hour drive to Charlotte.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/self2019/-nw62p-8RD-550.webp 550w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/self2019/-nw62p-8RD-550.jpeg&quot; alt=&quot;Run GCC tshirt I saw&quot; width=&quot;550&quot; height=&quot;457&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The first thing I saw when I passed through the hotel doors.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When we got to the hotel parkng lot, we weren’t entirely sure where to go.
Then, we noticed a person walking towards a door carrying a old mechanical
keyboard under their arm… a clue. Sure enough, after following that person
through the same door, we found ourselves in the middle of a hallway filled
with Linux booths and face-to-face with a &lt;a href=&quot;https://shop.fsf.org/tshirts-hoodies/run-gcc-shirt&quot;&gt;“RUN GCC”
T-shirt&lt;/a&gt; on display. We
found SELF.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/self2019/dza77bzgnv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/self2019/dza77bzgnv-1200.jpeg&quot; alt=&quot;My SELF 2019 Badge&quot; width=&quot;1200&quot; height=&quot;658&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My SELF 2019 Badge.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After walking around a bit, we worked our way to registration and received our
badges. They were little booklets on a lanyard that contained event information and the
schedule. However, it &lt;em&gt;did&lt;/em&gt; confuse me at first because the one side was
upside-down compared to the other side… but then I realized it was so it
could function as a name-tag, but still be &lt;em&gt;easily&lt;/em&gt; read while still around
one’s neck. Very practical.&lt;/p&gt;
&lt;h2&gt;Talks&lt;/h2&gt;
&lt;p&gt;Throughout the day, I went to several talks with an array of topics, including building
personal clouds, 3D printers, security compliance testing, network backups, and
even building a tiny house. The talks were generally interesting, and I left with a
bunch of notes on topics and software I might want to dig into at some point.
For example, I think it might be useful to look into &lt;a href=&quot;https://www.chef.io/products/chef-inspec/&quot;&gt;chef
inspec&lt;/a&gt; (or at least similar
alternatives to it), for verifying build/test environments at work.&lt;/p&gt;
&lt;p&gt;The biggest lesson I learned from the talks at SELF wasn’t from their
&lt;em&gt;content&lt;/em&gt;, but rather the &lt;em&gt;environment&lt;/em&gt; they were presented in. Overall, it
seemed like a very knowledgeable, but supportive audience.&lt;/p&gt;
&lt;p&gt;For example, one of the speakers was a little flustered as it was his first time
giving a talk. When the speaker stated he was a bit nervous, the room responded
with &lt;em&gt;“don’t worry, you’re doing great so far! This is how you learn”&lt;/em&gt;. Then…
the live demo experienced some issues (as they tend to do). The audience
responded by laughing &lt;em&gt;with&lt;/em&gt; the speaker and shouting out statements like “That
always happens to me! See, you &lt;em&gt;are&lt;/em&gt; one of us!”. People then started
discussing related tips and tricks they’ve learned over the years, while we
&lt;em&gt;all&lt;/em&gt; worked to troubleshoot the demo.&lt;/p&gt;
&lt;p&gt;The room was able to take what &lt;em&gt;could&lt;/em&gt; have been a disaster for the speaker,
and turn it into an engaging experience where &lt;em&gt;everyone&lt;/em&gt; in the room connected
and learned from one another.  As a result, a “&lt;em&gt;failed&lt;/em&gt;” talk ended up being a
&lt;em&gt;better&lt;/em&gt; experience than originally planned.&lt;/p&gt;
&lt;h2&gt;Thoughts&lt;/h2&gt;
&lt;p&gt;I think SELF displayed glimmers of what the Linux and Open Source
community &lt;em&gt;can be&lt;/em&gt; when at their best. Did I encounter people I disagreed with,
or thought were a bit too abrasive? You bet. Did it ultimately matter? Not
really. When we disagree, if we can muster up the maturity to &lt;em&gt;work
together&lt;/em&gt;, we will learn and grow as a community, even when everything is going wrong.
Just like in the talk I described above.
&lt;code&gt;&amp;lt;/soapbox&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I am fully aware that I “did SELF wrong” by only going for
a single day and mostly watching talks. I didn’t spend too much
time in the “&lt;a href=&quot;https://web.archive.org/web/20201129232611/https://blogs.vmware.com/opensource/2018/05/15/hallway-track-open-source-conferences/&quot;&gt;hallway
track&lt;/a&gt;”,
and missed out on the late-night antics and philosophical discussions I heard
about. Regardless, I still learned a bunch and had a great time. Now that I’ve scoped out
SELF, in the future I think I plan to stay for the whole event… and maybe
even submit a talk. See you there!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating a RAM Disk with a tmpfs Mount</title>
    <link href="https://ryan.himmelwright.net/post/tmpfs-mount-ramdisk/" />
    <updated>2019-06-05T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/tmpfs-mount-ramdisk/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/tmpfs-mount-ramdisk/xyenVQ6Udg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/tmpfs-mount-ramdisk/xyenVQ6Udg-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Riverwalk, Wilmington NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;RAM is fun. If a computer has &lt;em&gt;extra&lt;/em&gt; memory, it can be used for fun &lt;em&gt;beyond&lt;/em&gt;
opening extra chrome tabs, or firing up Slack. Want to mount a partition that
is &lt;em&gt;fast&lt;/em&gt; and can be entirely wiped out just by rebooting? Or are you just
bored (guilty)?  Regardless of the reason, lets create and mount a RAM-disk!&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;RAM disks and tmpfs&lt;/h3&gt;
&lt;p&gt;Creating a RAM disk in Linux is actually quite easy. Unlike other OS’s, it
doesn’t require special third-party software or digging deep into the registry.
This is because RAM-based file systems are already heavily used in a Linux system.
Many root sub-directories are actually mounted
&lt;a href=&quot;http://man7.org/linux/man-pages/man5/tmpfs.5.html&quot;&gt;tmpfs&lt;/a&gt; objects, most
notably &lt;code&gt;/tmp&lt;/code&gt;. To see some &lt;code&gt;tmpfs&lt;/code&gt; mounts on a system, &lt;code&gt;grep&lt;/code&gt; the &lt;code&gt;df&lt;/code&gt;
command for &lt;code&gt;tmpfs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ~ &lt;span class=&quot;token function&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-h&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; tmpfs
devtmpfs                  16G     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;   16G   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;% /dev
tmpfs                     16G  299M   16G   &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;% /dev/shm
tmpfs                     16G  &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;.0M   16G   &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;% /run
tmpfs                     16G     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;   16G   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;% /sys/fs/cgroup
tmpfs                     16G  &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;.9M   16G   &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;% /tmp
tmpfs                    &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;.2G  184K  &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;.2G   &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;% /run/user/1000
tmpfs                     12G     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;   12G   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;% /var/ramdisk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All the contents of a &lt;code&gt;tmpfs&lt;/code&gt; filesystem reside in system memory, typically
RAM. This allows file access that is &lt;em&gt;fast&lt;/em&gt;.  However, like RAM,
&lt;code&gt;tmpfs&lt;/code&gt; is also &lt;em&gt;volatile&lt;/em&gt;, meaning it will all be erased if the computer
restarts or shuts down. So don’t store anything important in &lt;code&gt;tmpfs&lt;/code&gt;!&lt;/p&gt;
&lt;h3&gt;fstab&lt;/h3&gt;
&lt;p&gt;The easiest way to add a new ‘RAM disk’ to our system is by adding it as a new
mount in the &lt;code&gt;/etc/fstab&lt;/code&gt; file. I’m currently running &lt;a href=&quot;https://silverblue.fedoraproject.org/&quot;&gt;Fedora
Silverblue&lt;/a&gt;, so I had to place it under
&lt;code&gt;/var/&lt;/code&gt; because I don’t have write access elsewhere, but feel free to place
your mount wherever you want. Just &lt;strong&gt;make sure the location exists&lt;/strong&gt;, or the
system won’t boot correctly when loading the &lt;code&gt;fstab&lt;/code&gt;. After adding the following line
to &lt;code&gt;/etc/fstab&lt;/code&gt;, a 10G &lt;code&gt;tmpfs&lt;/code&gt; ‘device’ will be mounted at &lt;code&gt;/var/ramdisk&lt;/code&gt; during
each boot:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# /etc/fstab&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
tmpfs      /var/ramdisk       tmpfs   rw,nodev,nosuid,size&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;10G	&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, Linux allocates half the RAM available on the system to a new
&lt;code&gt;tmpfs&lt;/code&gt; mount. However, I wanted to specify my disk size, so I used the &lt;code&gt;size&lt;/code&gt;
argument to allocate 10G.&lt;/p&gt;
&lt;h3&gt;Mounting&lt;/h3&gt;
&lt;p&gt;With the &lt;code&gt;fstab&lt;/code&gt; edited, it is time to mount everything. &lt;a href=&quot;https://wiki.archlinux.org/index.php/Tmpfs&quot;&gt;The arch wiki
page&lt;/a&gt; cautions &lt;em&gt;against&lt;/em&gt; running
&lt;code&gt;mount -a&lt;/code&gt; to mount the new &lt;em&gt;tmpfs&lt;/em&gt; disks, but that is because any files that
might already be in the directories will be removed during the mount. While
this may be a concern when editing any of the already “naturally occurring” &lt;code&gt;tmpfs&lt;/code&gt;
mounts on a system (such as &lt;code&gt;/tmp&lt;/code&gt; and &lt;code&gt;/run&lt;/code&gt;), our use case is adding a
&lt;em&gt;new&lt;/em&gt; one. So, we &lt;em&gt;should&lt;/em&gt; be safe!&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mount&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify that the RAM disk is mounted, check the &lt;code&gt;df&lt;/code&gt; output. (&lt;em&gt;Note: It
should be empty since it was just allocated&lt;/em&gt;)&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-h&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
tmpfs                     10G     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;   10G   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;% /var/ramdisk
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Testing&lt;/h3&gt;
&lt;p&gt;With the tmpfs mount, how can we be sure it’s actually functioning as a
RAM disk? The simplest way to test it is… move some files over to the mount
location and see if it starts to use up RAM!&lt;/p&gt;
&lt;p&gt;For my quick test, I quickly copied two large &lt;code&gt;iso&lt;/code&gt; files to the new mount:&lt;/p&gt;
&lt;p&gt;Link: &lt;a href=&quot;https://ryan.himmelwright.net/post/tmpfs-mount-ramdisk/ram-disk-usage.gif&quot;&gt;Demo video of RAM usage increasing as file copied to RAM disk&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; ./*.iso /var/ramdisk/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;During the copy, I had the gnome &lt;code&gt;System Monitor&lt;/code&gt; application opened so I could
see if the RAM usage slowly climbed as the files copied… and it did!
Additionally, when I deleted the copied files, the RAM usage went back down.
Looks like everything worked!&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;While RAM disks are less useful these days due to faster and faster
storage, they can still be fun. I’ve placed a full Linux file system in mine,
&lt;code&gt;chroot&lt;/code&gt;’ed into it, and ran a full DE with xorg and everything! It was quite
snappy. I also use it as a ‘scratch’ disk where I can work on a temporary
project and blow it away when I’m done. The options are endless!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting up Tmuxinator</title>
    <link href="https://ryan.himmelwright.net/post/setting-up-tmuxinator/" />
    <updated>2019-05-23T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/setting-up-tmuxinator/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setting-up-tmuxinator/8A3k-SxZQo-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setting-up-tmuxinator/8A3k-SxZQo-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Port Angeles, WA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Several weeks ago I wrote about &lt;a href=&quot;https://ryan.himmelwright.net/post/scripting-tmux-workspaces/&quot;&gt;scripting a tmux session’s
initialization&lt;/a&gt;. At the end of the post I
mentioned that while writing it, I had learned about
&lt;a href=&quot;https://github.com/tmuxinator/tmuxinator&quot;&gt;tmuxinator&lt;/a&gt;. I even teased that it
looked so good, I may switch to it eventually. Well… that didn’t take long. I’ve
scrapped my tmux shell scripts, and have converted to using tmuxinator. Here’s
how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What is Tmuxinator?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/tmuxinator/tmuxinator&quot;&gt;Tmuxinator&lt;/a&gt; is a command line
application that makes it easy to create and manage tmux sessions. It allows a
user to define how to setup a session, including naming windows, splitting
panes, and what commands initially run in each pane. The configuration is done
in yaml, so it is very easy to figure out and get started.&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;Most distros probably have &lt;code&gt;tmuxinator&lt;/code&gt; in their offical repos. These days, I
mostly run Fedora on all of my machines and I was able to find it there:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; termixinator&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, I found that this version was quite out of date (&lt;code&gt;0.6.11&lt;/code&gt;) even on my new
Fedora 30 install, so I recommend using the method stated on
the project’s Github page (requires &lt;code&gt;rubygems&lt;/code&gt;) to install a much more current
version (&lt;code&gt;1.1.0&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;gem &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; tmuxinator&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Create&lt;/h2&gt;
&lt;p&gt;To create a new tmuxinator profile, pass the desired project name to &lt;code&gt;tmuxinator new&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;tmuxinator new Website&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will open up a new tmuxinator template inside your default editor. The
default template contains a bunch of comments that help guide how to configure
it.&lt;/p&gt;
&lt;h3&gt;Header&lt;/h3&gt;
&lt;p&gt;The first thing to set at the top of the template is the &lt;code&gt;name&lt;/code&gt; field, followed
by the &lt;code&gt;root&lt;/code&gt; directory. The tmux session name is defined with &lt;code&gt;name&lt;/code&gt;, and
&lt;code&gt;root&lt;/code&gt; will be the directory which all of the windows/panes open up in by
default. For example, when converting my website script, I set &lt;code&gt;name&lt;/code&gt; to
&lt;code&gt;Website&lt;/code&gt; and &lt;code&gt;root&lt;/code&gt; to the location where I keep my website’s git working
directory, &lt;code&gt;~/Documents/himmAllRight-source&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# /home/ryan/.config/tmuxinator/Website.yml&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Website
&lt;span class=&quot;token key atrule&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ~/Documents/himmAllRight&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;source&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Windows&lt;/h3&gt;
&lt;p&gt;Next, I needed to configure my windows. To convert the script from my previous
post, I wanted a dedicated ‘Main’ window, one for the server, one for vim, and
one for an extra shell. Additionally, since that last post I’ve added a window
that launches my hugo-served website in a web browser. To recreate &lt;em&gt;all of
that&lt;/em&gt; with tmuxinator:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;windows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; zsh
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; clear
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hugo serve &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;D &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;F
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nvim
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Shell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; zsh; clear
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Web&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; qutebrowser localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1313&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Done. Save the file and that’s it! See the benefit of using tmuxinator??&lt;/p&gt;
&lt;h3&gt;Pane splits&lt;/h3&gt;
&lt;p&gt;While I don’t use them in this example configuration, it should be noted that
setting up pane splits is also quite easy with tmuxinator. For example, if I
wanted to split the server window into two panes, one running the hugo server
and one running htop, I could use:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; even&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;vertical
    &lt;span class=&quot;token key atrule&quot;&gt;panes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; hugo
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; htop&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;layout&lt;/code&gt; key defines which
&lt;a href=&quot;http://man7.org/linux/man-pages/man1/tmux.1.html#WINDOWS_AND_PANES&quot;&gt;tmux layout&lt;/a&gt; to
use when splitting the panes, and &lt;code&gt;panes&lt;/code&gt; is a list that defines what to run in
each pane.&lt;/p&gt;
&lt;h2&gt;Launch&lt;/h2&gt;
&lt;p&gt;To start up the newly defined tmuxinator project, use &lt;code&gt;tmuxinator start&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;tmuxinator start Website&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/setting-up-tmuxinator/start-website-tmuxinator.gif&quot;&gt;Click for a demo animation of tmuxinator&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Manage&lt;/h2&gt;
&lt;p&gt;All of the tmuxinator profile templates are stored at &lt;code&gt;~/.config/tmuxinator&lt;/code&gt;,
which means they can easily be copied to a new machine, or even
saved/maintained in source control.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I think older versions stored them at &lt;code&gt;~/.tmuxinator/&lt;/code&gt;, so check there
if they are missing.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it. While scripting my own method was a good bash exercise and helped me
learn the details of &lt;code&gt;tmux&lt;/code&gt; a little bit better, I ultimately think that
&lt;code&gt;tmuxinator&lt;/code&gt; is the way to go. This is especially true when using tmux to work
on several projects or even across multiple machines. If you &lt;em&gt;still&lt;/em&gt; haven’t
tried &lt;code&gt;tmux&lt;/code&gt;… I &lt;em&gt;highly&lt;/em&gt; recommend checking it out!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Custom Neofetch ASCII Art</title>
    <link href="https://ryan.himmelwright.net/post/custom-neofetch-ascii-art/" />
    <updated>2019-04-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/custom-neofetch-ascii-art/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/custom-neofetch-ascii-art/LhakTHDg1C-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/custom-neofetch-ascii-art/LhakTHDg1C-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;604&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Downtown Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have been using &lt;a href=&quot;https://github.com/dylanaraps/neofetch&quot;&gt;neofetch&lt;/a&gt; as my
command-line system information tool for a few years now. While browsing
&lt;a href=&quot;https://reddit.com/r/unixporn/&quot;&gt;/r/unixporn&lt;/a&gt;, I noticed that several
of the submissions did not have distro logos in their neofetch/screenfetch
ascii art. I knew that neofetch was customizable, but have never dug
into it myself. So I decided to change that.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Custom Neofetch ASCII Art&lt;/h3&gt;
&lt;p&gt;I started my neofetch investigation the same way one should start researching
&lt;em&gt;any&lt;/em&gt; Linux command-line tool: &lt;code&gt;man neofetch&lt;/code&gt;. Inside the man pages, I was
greeted with all sorts of customization options. Eventually, I scrolled down to
the section I wanted, &lt;code&gt;IMAGE BACKEND&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There I learned that a custom image could be specified using the &lt;code&gt;--source&lt;/code&gt; flag.
For example, to set the art to an ascii charmeleon file I &lt;em&gt;just happen&lt;/em&gt; to have
on my desktop, I tried:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;neofetch &lt;span class=&quot;token parameter variable&quot;&gt;--source&lt;/span&gt; /home/ryan/Documents/Pokemon-ascii/5.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AAAAAND… It didn’t work. It was still the Fedora logo. Clearly, I was still
missing something.&lt;/p&gt;
&lt;p&gt;After some browsing, I found the &lt;a href=&quot;https://github.com/dylanaraps/neofetch/wiki/Custom-Ascii-art-file-format&quot;&gt;ASCII art
page&lt;/a&gt;
on neofetch’s Github wiki. Reading it, I noticed that I was missing the &lt;code&gt;${c1}&lt;/code&gt;
in my pokemon ascii files. So, I made a copy of the file and added &lt;code&gt;S{c1}&lt;/code&gt; to
the top:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  welcome-messages git:(master) ✗ head charmeleon
${c1}
                      ,-&#39;`&#92;
                  _,&amp;quot;&#39;    j
           __....+       /               .
       ,-&#39;&amp;quot;             /               ; `-._.&#39;.
      /                (              ,&#39;       .&#39;
     |            _.    &#92;             &#92;   ---._ `-.
     ,|    ,   _.&#39;  Y    &#92;             `- ,&#39;   &#92;   `.`.
     l&#39;    &#92; ,&#39;._,&#92; `.    .              /       ,--. l
  .,-        `._  |  |    |              &#92;       _   l .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AAAAAND… It worked!&lt;/p&gt;
&lt;h3&gt;Adding a Shell Alias&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/custom-neofetch-ascii-art/IZGq8RPtHG-849.webp 849w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/custom-neofetch-ascii-art/IZGq8RPtHG-849.jpeg&quot; alt=&quot;Custom Charmeleon ASCII art for my desktop&#39;s neofetch output&quot; width=&quot;849&quot; height=&quot;717&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Custom Charmeleon ASCII art for my desktop&#39;s neofetch output.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, I wanted the charmeleon ascii art to be the &lt;em&gt;default&lt;/em&gt; neofetch output on my
desktop. While I’m sure that I could have accomplished this &lt;em&gt;properly&lt;/em&gt; by
editing the &lt;code&gt;~/.config/neofetch/config.conf&lt;/code&gt; file I learned about, for now…
creating an alias seemed easier XD. So I added the following line to my
&lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;neofetch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;neofetch --source /home/ryan/Documents/ascii/pokemon/charmeleon&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now whenever I call plain &lt;code&gt;neofetch&lt;/code&gt; on my desktop, it runs with the charmeleon
art.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;So that’s it. A nice &lt;em&gt;short&lt;/em&gt; post for once! Despite the size, I hope if was
useful. If you haven’t heard of neofetch before, go check it out. If you have,
I encourage you to go digging through the man pages. I promise you won’t regret
it.  Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating &quot;Sub&quot;-Monitor Workflows Using xrandr</title>
    <link href="https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/" />
    <updated>2019-04-29T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/LgIj1EUYnX-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/LgIj1EUYnX-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;NC State Campus, Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A couple months ago I swapped out my dual monitor setup for a single (but
massive), &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/&quot;&gt;42.5&amp;quot; UHD IPS display&lt;/a&gt;. I love it, but admit
that sometimes, it has &lt;em&gt;too much&lt;/em&gt; workspace. While I still think that the
larger display was better choice, I sometimes wish that
I had the more limited, but &lt;em&gt;focused&lt;/em&gt; workspace of an ultrawide or 1440p
monitor. &lt;em&gt;Maybe I still can…&lt;/em&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Reasoning&lt;/h2&gt;
&lt;p&gt;Okay. I understand that this post might seem ridiculous to most people.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“But Ryan, if you have a great display with such a large resolution, why
would you want to &lt;strong&gt;intentionally&lt;/strong&gt; scale it down smaller?!?!?!”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The truth is, there are a number of occasions when having a single, smaller
display is helpful.&lt;/p&gt;
&lt;h3&gt;Focused Work&lt;/h3&gt;
&lt;p&gt;First, when trying to focus deeply on a single task, I like to have the windows
that I &lt;em&gt;need&lt;/em&gt; for the activity opened, and nothing else. Furthermore, I like
to have these windows opened at a &lt;em&gt;reasonable size&lt;/em&gt; (Note: a full screen
3840x2160 single terminal looks stupid). With such a big screen, it is just too
tempting to drag chat windows, monitoring apps, and videos to the sides of the workspace.
While 1080p is a little cramped, I think a single 1440p resolution is &lt;a href=&quot;https://hackernoon.com/why-i-stopped-using-multiple-monitors-bfd87efa2e5b&quot;&gt;more
ideal for &lt;em&gt;focused
work&lt;/em&gt;&lt;/a&gt;
(especially because I tend to take advantage of &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_desktop&quot;&gt;virtual
desktops&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Tiling Window Managers&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/Lp89A9QdlK-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/Lp89A9QdlK-1200.jpeg&quot; alt=&quot;writing this post with i3&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I actually wrote most of the draft for this post in i3.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Second, when working (especially programming), I often like to use tiling
window managers ( &lt;a href=&quot;https://ryan.himmelwright.net/post/started-using-i3blocks/&quot;&gt;i3 for example&lt;/a&gt; ). This lets me
&lt;em&gt;work&lt;/em&gt; without having to manually move around the windows, or even leave my
keyboard. In tiling window managers, applications tend to open up full screen by
default, which again… is just obnoxious on such a large display.
Scaling down the display allows me to still use tiling window managers without
compromise.&lt;/p&gt;
&lt;h3&gt;Gaming&lt;/h3&gt;
&lt;p&gt;Lastly, a half-reason is gaming. I’m not a huge gamer so I didn’t opt for a
crazy graphics card when I &lt;a href=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/&quot;&gt;designed and built my
desktop&lt;/a&gt;. When I &lt;em&gt;do&lt;/em&gt;
game, I usually play in windowed-mode (which
is usually a better experience anyway… again for field of view reasons).
However, sometimes a game won’t support windowed mode, or I want to
play full screen at a lower resolution so that my GPU can handle it.&lt;/p&gt;
&lt;h2&gt;How&lt;/h2&gt;
&lt;p&gt;Now that it is (hopefully) understood &lt;em&gt;why&lt;/em&gt; I want to setup a “sub-display”
inside my monitor, lets switch to &lt;em&gt;how&lt;/em&gt; I did it.&lt;/p&gt;
&lt;h3&gt;Problem 1: Scaling&lt;/h3&gt;
&lt;p&gt;To get “sub”-resolutions working, I had to fix two problems. The first was that
even if I select a smaller resolution on my computer, most monitors will
display that resolution, but scaled to the size of the display. For example, if
I set my computer’s display settings to 1920x1080 on my 3840x2160 monitor, by
default it will double up the pixels to get the smaller resolution
to &lt;em&gt;fit&lt;/em&gt; full-size on the monitor. This makes everything appear gigantic… which
is the exact opposite of what I wanted.&lt;/p&gt;
&lt;p&gt;After playing around in my monitor’s input settings, I noticed that there is an
“Aspect Ratio” field, with a &lt;code&gt;1:1&lt;/code&gt; option. When selected, the monitor displays
an image at its pixel density. So a 1920x1080 pixel display shows as a smaller
image in the middle of the screen, but with a &lt;code&gt;1:1&lt;/code&gt; pixel density. Problem #1 solved!&lt;/p&gt;
&lt;h3&gt;Problem 2: New Resolution Sets&lt;/h3&gt;
&lt;p&gt;The second problem was that by default, many of the resolutions I wanted to use
out do not show up in the display settings. This makes sense, as &lt;em&gt;most&lt;/em&gt; people
won’t be selecting &lt;code&gt;21:9&lt;/code&gt; resolutions on a &lt;code&gt;16:9&lt;/code&gt; panel. I needed to add
new options using &lt;code&gt;xrandr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After spending sometime in a terminal with &lt;code&gt;xrandr&lt;/code&gt;, I was able to create new &lt;code&gt;xrandr&lt;/code&gt; modes, and set my monitor to use them.&lt;/p&gt;
&lt;h3&gt;Creating a new xrandr mode&lt;/h3&gt;
&lt;p&gt;To create a new xrandr mode, I first needed to calculate a new modeline. This
can be done by using the &lt;code&gt;gtf&lt;/code&gt; tool. To calculate a modeline for a
3440x1440 resolution at 59.9 hertz for example, use the following:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ~ gtf &lt;span class=&quot;token number&quot;&gt;3440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;59.9&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# 3440x1440 @ 59.90 Hz (GTF) hsync: 89.25 kHz; pclk: 418.41 MHz&lt;/span&gt;
  Modeline &lt;span class=&quot;token string&quot;&gt;&quot;3440x1440_59.90&quot;&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;418.41&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;3440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3688&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4064&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4688&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1441&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1444&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1490&lt;/span&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-HSync&lt;/span&gt; +Vsync

➜  ~&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The line that starts with &lt;em&gt;Modeline&lt;/em&gt; (but &lt;em&gt;not including&lt;/em&gt; “Modeline”) is what
we want. Copy that and give it to &lt;code&gt;xrandr&lt;/code&gt; with the &lt;code&gt;--newmode&lt;/code&gt; flag to create
a new mode:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;xrandr &lt;span class=&quot;token parameter variable&quot;&gt;--newmode&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3440x1440_59.90&quot;&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;418.41&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;3440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3688&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4064&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4688&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1441&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1444&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1490&lt;/span&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-HSync&lt;/span&gt; +Vsync&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, to add that mode to a display, use that same line but with the
&lt;code&gt;--addmode&lt;/code&gt; flag, and the monitor to switch (&lt;code&gt;DP-1&lt;/code&gt; in my case):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;xrandr &lt;span class=&quot;token parameter variable&quot;&gt;--addmode&lt;/span&gt; DP-1 &lt;span class=&quot;token string&quot;&gt;&quot;3440x1440_59.90&quot;&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;418.41&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;3440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3688&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4064&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4688&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1441&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1444&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1490&lt;/span&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-HSync&lt;/span&gt; +Vsync&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: To find the available monitor names, enter a plain &lt;code&gt;xrandr&lt;/code&gt; command, and
it will spit out all the available outputs.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Switching to the new mode&lt;/h3&gt;
&lt;p&gt;To switch to the new mode, I like to use a GUI tool named &lt;code&gt;arandr&lt;/code&gt;.  Simply right
click on the display’s rectangle, and select the new mode name from the
“Resolution” list (Or &lt;em&gt;Outputs&lt;/em&gt; -&amp;gt; &lt;em&gt;Monitor Name&lt;/em&gt; -&amp;gt; &lt;em&gt;Resolution&lt;/em&gt; -&amp;gt; &lt;em&gt;New MODE
NAME&lt;/em&gt; in the menu bar).&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/4eVCYBkjqk-1131.webp 1131w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/4eVCYBkjqk-1131.jpeg&quot; alt=&quot;Using arandr to select new mode&quot; width=&quot;1131&quot; height=&quot;1292&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using `arandr` to select new mode (SO many resolutions XD ).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;If you are too &lt;a href=&quot;https://en.wikipedia.org/wiki/Leet&quot;&gt;1337&lt;/a&gt; to use a GUI app, never fear! The display can be switched to the new mode using &lt;code&gt;xrandr&lt;/code&gt; with the
&lt;code&gt;--output&lt;/code&gt; and &lt;code&gt;--mode&lt;/code&gt; flags:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;xrandr &lt;span class=&quot;token parameter variable&quot;&gt;--output&lt;/span&gt; DP-1 &lt;span class=&quot;token parameter variable&quot;&gt;--mode&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3440x1440_59.90&quot;&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;418.41&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;3440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3688&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4064&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4688&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1441&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1444&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1490&lt;/span&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-HSync&lt;/span&gt; +Vsync&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;But Wait, There’s More! Scripting it&lt;/h3&gt;
&lt;p&gt;After running these commands twice… I realized it would be easy enough to automate. So I did with this script:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# add-xrandr.sh&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# --------------&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# A function to prompt the user if they want to switch to the new mode now.&lt;/span&gt;
&lt;span class=&quot;token function-name function&quot;&gt;switch_to_new_mode&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Switch to mode &lt;span class=&quot;token variable&quot;&gt;$modename&lt;/span&gt; now? [y/n]: &quot;&lt;/span&gt;
 &lt;span class=&quot;token builtin class-name&quot;&gt;read&lt;/span&gt; change
 &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$change&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;y&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;switching monitor...&quot;&lt;/span&gt;
  xrandr &lt;span class=&quot;token parameter variable&quot;&gt;--output&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$MONITOR&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--mode&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$modename&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Okay, no switch. Enjoy!&quot;&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Create a new mode for the display&lt;/span&gt;
&lt;span class=&quot;token function-name function&quot;&gt;create_new_mode&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Adding new mode: &lt;span class=&quot;token variable&quot;&gt;$gtf_output&lt;/span&gt;&quot;&lt;/span&gt;
 xrandr &lt;span class=&quot;token parameter variable&quot;&gt;--newmode&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$gtf_output&lt;/span&gt;
 &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Adding new mode &lt;span class=&quot;token variable&quot;&gt;$modename&lt;/span&gt; to display &lt;span class=&quot;token variable&quot;&gt;$MONITOR&lt;/span&gt;&quot;&lt;/span&gt;
 xrandr &lt;span class=&quot;token parameter variable&quot;&gt;--addmode&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$MONITOR&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$modename&lt;/span&gt;
 &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Done!&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Message if the mode appears to already exist&lt;/span&gt;
&lt;span class=&quot;token function-name function&quot;&gt;mode_already_exists&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hmmm... I think the mode &lt;span class=&quot;token variable&quot;&gt;$modename&lt;/span&gt; already exists.&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## Main Function to set vars and code&lt;/span&gt;
&lt;span class=&quot;token function-name function&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;# Input Vars&lt;/span&gt;
 &lt;span class=&quot;token assign-left variable&quot;&gt;WIDTH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;
 &lt;span class=&quot;token assign-left variable&quot;&gt;HEIGHT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt;
 &lt;span class=&quot;token assign-left variable&quot;&gt;MONITOR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$3&lt;/span&gt;
 &lt;span class=&quot;token assign-left variable&quot;&gt;ESLEEP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;# Fancier Vars :P&lt;/span&gt;
 &lt;span class=&quot;token assign-left variable&quot;&gt;gtf_output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;gtf $WIDTH $HEIGHT &lt;span class=&quot;token number&quot;&gt;59.9&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;modeline&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;s/&#92;&amp;lt;Modeline&#92;&gt;//g&#39;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
 &lt;span class=&quot;token assign-left variable&quot;&gt;modename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; $gtf_output &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;&#92;(&lt;span class=&quot;token entity&quot; title=&quot;&#92;&amp;quot;&quot;&gt;&#92;&quot;&lt;/span&gt;.*&lt;span class=&quot;token entity&quot; title=&quot;&#92;&amp;quot;&quot;&gt;&#92;&quot;&lt;/span&gt;&#92;)&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
 &lt;span class=&quot;token assign-left variable&quot;&gt;modeexists&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;xrandr &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; $modename&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

 &lt;span class=&quot;token comment&quot;&gt;# Run&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$modeexists&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  create_new_mode
     switch_to_new_mode
 &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
  mode_already_exists
     switch_to_new_mode
 &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## Execute Main&lt;/span&gt;
main &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basically, the script is run by providing it the desired width, height, and
&lt;code&gt;xrandr&lt;/code&gt; display to apply the new mode to. For example, to create a new
&lt;code&gt;3440x1440&lt;/code&gt; mode for my &lt;code&gt;DP-1&lt;/code&gt; display, I would run the following command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./add-xrandr.sh &lt;span class=&quot;token number&quot;&gt;3440&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt; DP-1.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script will generate a new mode based on the parameters fed it it, and then
check to see if the mode already exists. If it doesn’t, it will create it, add
it to the display name passed in, and will ask the user if they would like to
switch to the new mode. If the mode &lt;em&gt;was&lt;/em&gt; already detected, the script will just
ask the user if they wish to switch to it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: The script is hard-coded to always create modes at &lt;code&gt;59.9&lt;/code&gt; Hertz,
because I don’t have any fancy fast monitors. If you want that option, just add
another parameter and swap out the &lt;code&gt;59.9&lt;/code&gt; with it.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Pros/Cons&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/v2tymcGX3k-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sub-monitor-workflows-with-xrandr/v2tymcGX3k-1200.jpeg&quot; alt=&quot;Monitor in &#39;ultrawide mode&#39;&quot; width=&quot;1200&quot; height=&quot;709&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Monitor in &#39;ultrawide mode&#39;. It is set to 3440x1440px and measures ~36&quot; diagonally.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h3&gt;What this solves&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;When I have the monitor set to a sub-resolution, I don’t experience any of
the “edge shadowing” issues I mentioned in the review post.&lt;/li&gt;
&lt;li&gt;I can test out any resolution setup less than 2840x2160. (Including Ultrawide
setups)&lt;/li&gt;
&lt;li&gt;Using a smaller resolution is friendlier to my tiling wm setups&lt;/li&gt;
&lt;li&gt;I can use a better “single focus” monitor setup. For example, the equivalent of a 28&amp;quot;
1440p display.&lt;/li&gt;
&lt;li&gt;I can set my monitor to a smaller resolution to better play full screen games (so that
the resolution better fits my GPU performance &lt;em&gt;and&lt;/em&gt; the game is in my
field of view)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What it doesn’t fix&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;physical&lt;/em&gt; size of my monitor stays the same. (MASSIVE Bezels :P )&lt;/li&gt;
&lt;li&gt;I can’t really do multiple displays setups. Even though my monitor supports
multiple inputs and picture-by-picture, I still can’t do a &lt;em&gt;good&lt;/em&gt; horizontal +
vertical 1080p setup, because it will center each one.&lt;/li&gt;
&lt;li&gt;The DPI is slightly bigger than if I got the &lt;em&gt;common&lt;/em&gt; size/pixel monitors.&lt;/li&gt;
&lt;li&gt;Any &lt;em&gt;curving&lt;/em&gt; or other physical attributes another monitor form factor has.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In conclusion, I love this setup. With this work-around, I was able to take a few
use cases where my monitor didn’t fully fit my needs, and fix it. I am even
happier with my monitor selection now, as I can enjoy a massive IPS display when I want it,
but also have the ability to tone it down when I want to focus in more. So far,
it’s working great!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Switching to a Mesh Network</title>
    <link href="https://ryan.himmelwright.net/post/switching-to-mesh-network/" />
    <updated>2019-04-01T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/switching-to-mesh-network/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/zSid-fgBRC-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/zSid-fgBRC-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;American Tobacco Campus, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Not too long ago, I updated our home networking equipment to an Ubiqui
setup. However, we recently moved into a new house and I switched to a
mesh wifi network. No, the switch was not &lt;em&gt;just&lt;/em&gt; to increase wifi
coverage throughout the entire house. The real motivator
(ironically)… was that I needed a better ethernet connection…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;About two years ago, I &lt;a href=&quot;https://ryan.himmelwright.net/post/upgrading-network-to-ubiquiti/&quot;&gt;replaced my Linksys wifi
router(s)&lt;/a&gt;
with the tiny-but-mighty &lt;a href=&quot;https://store.ui.com/products/edgerouter-x&quot;&gt;Ubiquity Edge Router
X&lt;/a&gt;, paired with an &lt;a href=&quot;https://www.ui.com/unifi/unifi-ap-ac-lite/&quot;&gt;Ubiqui AP AC
lite&lt;/a&gt; wifi access point. I loved
it. It handled our web traffic flawlessly, and unlike the previous Linksys
routers, I never had to randomly restart it. It just worked for months on end.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/fcC2nO1Oc4-700.webp 700w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/fcC2nO1Oc4-700.jpeg&quot; alt=&quot;The Google Fiber network box&quot; width=&quot;700&quot; height=&quot;207&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Google Fiber &quot;network box&quot;.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I enjoyed the setup &lt;em&gt;so&lt;/em&gt; much in fact, that while I was thrilled to
have a Google Fiber internet connection when we moved to Durham NC
last year, I was a little sad that setting up the edgerouter on the
fiber modem required a little bit of tweaking. I looked into it
but ultimately decided that figuring it out wasn’t worth the hassle
for the short period of time which we’d likely live in the
apartment. So, I reluctantly used the Google provided box which worked &lt;em&gt;okay&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;We Moved&lt;/h2&gt;
&lt;p&gt;The Google fiber was indeed short lived, as my wife and I just moved
into a house this past month. When working with Spectrum to setup our
internet (&lt;em&gt;yea… I know&lt;/em&gt;), we discovered that while there were coax
plugs in both the living room and master bedroom, we couldn’t
actually find any hook-ups to connect the house to the external cable/internet
service line.&lt;/p&gt;
&lt;h3&gt;Cable-line in Bedroom&lt;/h3&gt;
&lt;p&gt;After searching with multiple techs, we decided just to have them
drill a line into the master bedroom (which is at the back of the
house and allows the connection box to be with the other utility
meters). Our house isn’t massive, and having the router in the bedroom
works well enough, except for &lt;em&gt;one&lt;/em&gt; issue.&lt;/p&gt;
&lt;p&gt;I have several servers and desktop computers that &lt;em&gt;require&lt;/em&gt; an
ethernet connection, either because they don’t have wifi cards, and/or
because I use Wake-On-Lan with them. This means that I need an
ethernet connection in my office… at the front of the house. Now
that the only ethernet router/switch was in the bedroom, and the rest
of the house relied on wifi…  I needed to figure something out.&lt;/p&gt;
&lt;h3&gt;Power-line Issues&lt;/h3&gt;
&lt;p&gt;In past apartments, I had used power-line ethernet adapters to extend
ethernet from a router in a different room to my computers. The speeds
weren’t great, but then again, neither was our connection so it wasn’t
a huge problem. This time though, I ran some tests…&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/4vFbBEzKFW-769.webp 769w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/4vFbBEzKFW-769.jpeg&quot; alt=&quot;Diagram comparing internet speeds: 1&#92;) Plugged directly into Router: 27 ms ping, 231.44 Mbps Download, 11.79 Mbps upload 2&#92;) Wifi in Office: 17 ms ping, 178.74 Mbps Download, 11.72 Mbps Upload 3&#92;) Powerline Ethernet in Office: 15 ms ping, 17.66 MBps Download, 11.86 Mbps Upload.&quot; width=&quot;769&quot; height=&quot;700&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Different internet Speed test results after hooking up
internet to the house.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So, while it &lt;em&gt;worked&lt;/em&gt;, this setup wasn’t ideal. More importantly, I
found out a few days later there was a massive problem that
&lt;em&gt;completely prevents&lt;/em&gt; me from being able to use the power-line
adapters. The house’s electrical panel is between the master bedroom
and the office, which is the worst case scenario. One day, we turned
on the washing machine and our internet went down. It wasn’t just the
power-line connections that were effected… the entire network, wifi
and router was essentially being
&lt;a href=&quot;https://en.wikipedia.org/wiki/Denial-of-service_attack&quot;&gt;dos’d&lt;/a&gt; by the
washer. When I unplugged the power-line adapters, it worked
again. Power-line was not an option anymore.&lt;/p&gt;
&lt;h2&gt;Mesh Network&lt;/h2&gt;
&lt;p&gt;I wondered if there was another way to connect ethernet in my office,
beyond having to drop cat6 cable across the house. I looked into wifi
bridges, but most of the devices were designed for large distances,
and ones for home use were not much better than a power-line adapters
when it came to performance/reliability. While looking at wifi
bridges, a thought occurred to me… would a mesh network system work?
If the satellite devices had ethernet ports… maybe.&lt;/p&gt;
&lt;h3&gt;Netgear Orbi&lt;/h3&gt;
&lt;p&gt;When I started researching wifi mesh networks, the orbi systems were consistently
rated the highest, and people generally reported that they experienced &lt;em&gt;great&lt;/em&gt;
performance. When I dug further into it, I learned that this high level of performance was
likely due to the orbi’s &lt;a href=&quot;https://www.netgear.com/landings/mesh-network/&quot;&gt;“tri-band wifi
technology”&lt;/a&gt;.
Basically, in addition to the normal 2.4 and 5 Ghz network bands, the orbi
system has &lt;em&gt;a second 5 Ghz band&lt;/em&gt; dedicated to connecting the orbi router and
satilites together. In other words… a dedicated wifi bridge :D.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/Hl8BIguaoN-1182.webp 1182w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/switching-to-mesh-network/Hl8BIguaoN-1182.jpeg&quot; alt=&quot;The RBK50 system comes with 1 router, and 1 satilite device. Both have 4 etherent ports.&quot; width=&quot;1182&quot; height=&quot;954&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The RBK50 system comes with 1 router, and 1 satilite device. Both have 4 etherent ports.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I started looking at the orbi hardware and saw that there were several
types of
&lt;a href=&quot;https://www.netgear.com/orbi/products.aspx#filter=.satellites-indoor%2C.satellites%2C.satellites-outdoor%2C.satellites&quot;&gt;satellites&lt;/a&gt;.
Some were just wall plugs, but a few had 2-4 ethernet
ports. Perfect. Realizing this, I ended up ordering the
&lt;a href=&quot;https://www.netgear.com/orbi/rbk50.aspx&quot;&gt;RBK50&lt;/a&gt; system, which
contained a 4-port orbi router, and a 4-port satellite. My hope was that
I could set up the router in the master bedroom with our modem, and
then put the satellite in my office on the other end of the house.
I could then connect my hardwired devices, or even a network switch, into the
ethernet ports on the satellite. Problem sovled.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I have ordered and setup my orbi system, and will write a review post
about it sometime in the future. This post’s intention was mostly to
just explain &lt;em&gt;why&lt;/em&gt; I ended up getting an orbi system.&lt;/p&gt;
&lt;p&gt;Many people have started to use home wifi “mesh” systems because they
provide a simple solution to cover an entire house with a strong wifi
connection. While this is a great &lt;em&gt;bonus&lt;/em&gt; I get from having the mesh
system, as this post demonstrates, my &lt;em&gt;main&lt;/em&gt; purpose for purchasing
the orbi was to be a solid “ethernet over wifi” system in the new
house. So far… it’s working great.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Scripting A Tmux Work-space Start-up</title>
    <link href="https://ryan.himmelwright.net/post/scripting-tmux-workspaces/" />
    <updated>2019-03-27T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/scripting-tmux-workspaces/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/scripting-tmux-workspaces/rOWpiWNPbB-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/scripting-tmux-workspaces/rOWpiWNPbB-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Liberty Warehouse, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I love the terminal multiplexer, &lt;a href=&quot;http://www.tmux.com&quot;&gt;tmux&lt;/a&gt;. It adds
functionality to the terminal, such as multiple tabs, pane splitting, and the
ability to detach and re-attach everything later (which is &lt;em&gt;amazing&lt;/em&gt; when
combined with &lt;code&gt;ssh&lt;/code&gt;). I have been utilizing tmux even more at work, and recently
started to script the start up/configuration a tmux session for each project.
The other day, I decided to write a script to spin up a session for working on my
website… and thought it would be a great tutorial!&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Tmux&lt;/h2&gt;
&lt;p&gt;When working on a project, I like to use tmux to maintain all of the terminal
windows related to it. This keeps everything together, and even allows me to
detach the session, switch to another computer, ssh into the previous computer,
and re-attach my working tmux session. Paired with the fact that I’ve started
using VIM again, it works seamlessly. Scripting the initialization makes
getting started even smoother.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/scripting-tmux-workspaces/tmux-demo.gif&quot;&gt;Tmux Demo Animation&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Automating tmux initialization for working on my website&lt;/h2&gt;
&lt;h3&gt;New Script&lt;/h3&gt;
&lt;p&gt;First, lets create a new script. Start by opening a new file, and adding a bash
&lt;a href=&quot;https://en.wikipedia.org/wiki/Shebang_(Unix)&quot;&gt;shebang&lt;/a&gt; line to the top:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line tells the system that the following text will be a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Bash_(Unix_shell)&quot;&gt;bash&lt;/a&gt; script.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Optional: Save and close the file, then re-open it to take advantage the text
editor’s bash syntax highlighting if it didn’t automatically switch it on.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Create a new tmux session (with var name)&lt;/h3&gt;
&lt;p&gt;Next, lets define a variable to store the tmux session name. This will make it
easier to change the session name later on. Using the session variable, a new
tmux-session with our desired name can be created.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Session Name&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Website&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Start New Session with our name&lt;/span&gt;
tmux new-session &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$session&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Name initial default Window (and switch to &lt;code&gt;zsh&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;Let’s customize the default window and give it a new name. Lets call this first tmux
window &lt;code&gt;&amp;quot;Main&amp;quot;&lt;/code&gt;, and have it simply run a &lt;a href=&quot;https://ohmyz.sh/&quot;&gt;zsh&lt;/a&gt; shell. After
creating a new session, there is only one window, so I know it will be
identified with the number &lt;code&gt;0&lt;/code&gt;. I can use this with &lt;code&gt;-t&lt;/code&gt; to rename the window.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Name first Window and start zsh&lt;/span&gt;
tmux rename-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Main&#39;&lt;/span&gt;
tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Main&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;zsh&#39;&lt;/span&gt; C-m &lt;span class=&quot;token string&quot;&gt;&#39;clear&#39;&lt;/span&gt; C-m&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can then use the &lt;code&gt;send-keys&lt;/code&gt; command with the new window name to start zsh.
This is the equivalent of typing &lt;code&gt;zsh&lt;/code&gt;, &lt;code&gt;[Enter]&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt;, &lt;code&gt;[Enter]&lt;/code&gt; into the
command line.&lt;/p&gt;
&lt;h3&gt;Add a new (named) window for hugo server&lt;/h3&gt;
&lt;p&gt;With the main tmux window setup, I want to add a few more for
different tasks. First, I want a window that can run the hugo server
as I’m writing a post. With a session already created, I can name the
window as I create it, using just the &lt;code&gt;new-window&lt;/code&gt; comamnd with the
&lt;code&gt;-n&lt;/code&gt; flag.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create and setup pane for hugo server&lt;/span&gt;
tmux new-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$session&lt;/span&gt;:1 &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hugo Server&#39;&lt;/span&gt;
tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hugo Server&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;hugo serve -D -F&#39;&lt;/span&gt; C-m&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, I used tmux &lt;code&gt;send-keys&lt;/code&gt;, this time to send the &lt;code&gt;hugo serve -D -F&lt;/code&gt; command to start up a hugo server for local draft editing.&lt;/p&gt;
&lt;h3&gt;Add a new (name) pane for vim&lt;/h3&gt;
&lt;p&gt;Now, I need a place to write website posts… so lets fire up a new tmux
window, and open up neovim inside of it.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# setup Writing window&lt;/span&gt;
tmux new-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;:2 &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Writing&#39;&lt;/span&gt;
tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Writing&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nvim&quot;&lt;/span&gt; C-m&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Another shell&lt;/h3&gt;
&lt;p&gt;Lastly, lets create one more shell window, just in case it’s needed. Why not?&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Setup an additional shell&lt;/span&gt;
tmux new-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;:3 &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Shell&#39;&lt;/span&gt;
tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Shell&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;zsh&quot;&lt;/span&gt; C-m &lt;span class=&quot;token string&quot;&gt;&#39;clear&#39;&lt;/span&gt; C-m&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Attach Session&lt;/h3&gt;
&lt;p&gt;With the tmux session all configured and customized, we can tell the script to
go ahead and attach it, using the &lt;code&gt;attach-session&lt;/code&gt; command with the session
name variable.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Attach Session, on the Main window&lt;/span&gt;
tmux attach-session &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;:0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: the &lt;code&gt;0&lt;/code&gt; tells tmux to attach the first window (&lt;code&gt;Main&lt;/code&gt;). Using
another index value will attach a different window. For example, &lt;code&gt;1&lt;/code&gt;
would open the &lt;code&gt;Hugo Server&lt;/code&gt; window when attaching&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Bonus: Minor improvement&lt;/h2&gt;
&lt;p&gt;At this point, I was done with the tmux script. It worked well for creating my
session and attaching it. However, there was &lt;em&gt;one&lt;/em&gt; issue I still had. If I ran
the script when there was a tmux session with the same name &lt;em&gt;already&lt;/em&gt;, it would
just double up the windows in that session. If I accidentally did this, I would
have to go through each window, close out whatever and was running, and then
close the window.&lt;/p&gt;
&lt;p&gt;To fix this issue, I decided to wrap the initialization commands inside of an
&lt;code&gt;if&lt;/code&gt; statement, and only run them if the tmux session &lt;em&gt;didn’t&lt;/em&gt; already exist.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/scripting-tmux-workspaces/tmux-duplicate-windows.gif&quot;&gt;Duplicate windows demo animation&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Checking if the Session Already Exists&lt;/h3&gt;
&lt;p&gt;First, I needed a way to &lt;em&gt;check&lt;/em&gt; whether the desired tmux session already existed
or not. This can be done by “grep’ing” the output of &lt;code&gt;tmux list-sessions&lt;/code&gt; for
the session name, which we’ve already conveniently stored in our &lt;code&gt;$SESSION&lt;/code&gt;
variable. For Cleanliness, I took the output of that process and saved it in a
&lt;code&gt;SESSIONEXISTS&lt;/code&gt; variable, defined directly under &lt;code&gt;SESSION&lt;/code&gt; in the script.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;SESSIONEXISTS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;tmux list-sessions &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the session exists, &lt;code&gt;SESSIONEXISTS&lt;/code&gt; will be a string of the line which &lt;code&gt;grep&lt;/code&gt;
matched. Otherwise, it will just be an empty string (&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Now, I can use the &lt;code&gt;SESSIONEXISTS&lt;/code&gt; variable with an &lt;code&gt;if&lt;/code&gt; condition to wrap the tmux
setup code so that it only runs if &lt;code&gt;SESSIONEXISTS&lt;/code&gt; is &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$SESSIONEXISTS&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;attach-session&lt;/code&gt; command should be &lt;em&gt;outside&lt;/em&gt; of the &lt;code&gt;if&lt;/code&gt; body,
because it will be run in both cases (even if the session doesn’t have
to be created, we still want to attach the one that already exists).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That is it. Here is the script with everything all put together:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/sh&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Set Session Name&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;&lt;span class=&quot;token environment constant&quot;&gt;SESSION&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Website&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;SESSIONEXISTS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;tmux list-sessions &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Only create tmux session if it doesn&#39;t already exist&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$SESSIONEXISTS&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Start New Session with our name&lt;/span&gt;
    tmux new-session &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Name first Pane and start zsh&lt;/span&gt;
    tmux rename-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Main&#39;&lt;/span&gt;
    tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Main&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;zsh&#39;&lt;/span&gt; C-m &lt;span class=&quot;token string&quot;&gt;&#39;clear&#39;&lt;/span&gt; C-m &lt;span class=&quot;token comment&quot;&gt;# Switch to bind script?&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Create and setup pane for hugo server&lt;/span&gt;
    tmux new-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;:1 &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hugo Server&#39;&lt;/span&gt;
    tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hugo Server&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;hugo serve -D -F&#39;&lt;/span&gt; C-m &lt;span class=&quot;token comment&quot;&gt;# Switch to bind script?&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# setup Writing window&lt;/span&gt;
    tmux new-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;:2 &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Writing&#39;&lt;/span&gt;
    tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Writing&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nvim&quot;&lt;/span&gt; C-m

    &lt;span class=&quot;token comment&quot;&gt;# Setup an additional shell&lt;/span&gt;
    tmux new-window &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;:3 &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Shell&#39;&lt;/span&gt;
    tmux send-keys &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Shell&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;zsh&quot;&lt;/span&gt; C-m &lt;span class=&quot;token string&quot;&gt;&#39;clear&#39;&lt;/span&gt; C-m
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Attach Session, on the Main window&lt;/span&gt;
tmux attach-session &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$SESSION&lt;/span&gt;:0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope this post has been helpful! Although, I have to admit that this post
&lt;em&gt;may&lt;/em&gt; be moot if I decide check out
&lt;a href=&quot;https://github.com/tmuxinator/tmuxinator&quot;&gt;tmuxinator&lt;/a&gt; (which a co-worker
recommended) … oh well. I guess this endeavor was still a good exercise in
some bash scripting :P.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Why I Got an iPad</title>
    <link href="https://ryan.himmelwright.net/post/getting-an-ipad/" />
    <updated>2019-02-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/getting-an-ipad/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/2gF50sg3Xf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/2gF50sg3Xf-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Desk, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the past few months, I’ve been considering getting a tablet device for the first time since I was in college. After researching all sorts of options, I had myself convinced that I would likely be picking up a 2018 9.7&amp;quot; iPad at some point. Then I ordered an &lt;a href=&quot;https://www.apple.com/shop/buy-ipad/ipad-pro-10-5&quot;&gt;iPad Pro 10.5&amp;quot; (2017 model)&lt;/a&gt;. Here’s why.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why a Tablet?&lt;/h2&gt;
&lt;p&gt;When I use technology, I try to plan out and use devices which best fit the tasks I want to accomplish. After evaluating my work habits, I started to think that a tablet might function well in many of my use cases.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/MvHg1KfNte-850.webp 850w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/MvHg1KfNte-850.jpeg&quot; alt=&quot;a picture of the keyONE&quot; width=&quot;850&quot; height=&quot;691&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My blackberry keyONE&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The main reason? I don’t like using smart phones. I think modern phones are a wonderful piece of technology, but they are often misused. While some smart phone apps can greatly enhance one’s life, many are convenient distractions.&lt;/p&gt;
&lt;p&gt;My phone is &lt;em&gt;always&lt;/em&gt; with me, so I keep the set of apps installed on it to a minimum. I try to use apps that are most needed “on the go” (maps/lyft, music/podcasts, and various communication apps). I find that this helps prevent me from easily distracting myself throughout the day.&lt;/p&gt;
&lt;p&gt;Recently, I realized that the experience of some tasks on the phone isn’t great, and that they would likely be best completed on a tablet. Additionally, setting up a tablet for these apps meant I could still enjoy them in a more portable way, but maintain a clean, distraction-free phone. Some of the activities I intended to offload from my phone are:&lt;/p&gt;
&lt;h3&gt;Portable Media Consumption&lt;/h3&gt;
&lt;p&gt;I usually consume the majority of my media either on our TV, or at my computer. That being said, I did start to notice myself watching Youtube, Hulu, and Netflix, more often from my phone. This usually occurred when the TV was in use, or I simply wanted to watch from a different location (ex: my bed). Having a small screen and tinny speakers, watching video on a phone is terrible. This is especially true on my &lt;a href=&quot;https://www.gsmarena.com/blackberry_keyone-8508.php&quot;&gt;Blackberry KEYone’s&lt;/a&gt; &lt;em&gt;even smaller&lt;/em&gt; screen (Which I choose because it is a great &lt;em&gt;communication device&lt;/em&gt;, with a long battery life. Regardless, even on a large, bezel-less phone screen, watching video on a smart phone is still sub-optimal).&lt;/p&gt;
&lt;p&gt;In college, I used my previous tablet (&lt;a href=&quot;https://en.wikipedia.org/wiki/Asus_Eee_Pad_Transformer&quot;&gt;an Asus Transformer T101&lt;/a&gt;, with no dock) to watch videos in my dorm or around campus. I loved its convenient, but yet still enticing viewing experience. I assumed that with a modern tablet, it would only be better.&lt;/p&gt;
&lt;h3&gt;Reading&lt;/h3&gt;
&lt;p&gt;Being such a visual learner, I tend to gravitate towards visual media. Reading however, is much less passive than video. When watching a movie, it will continue to progress whether the viewer is paying attention or not. Reading is much more &lt;em&gt;self-driven&lt;/em&gt;, which I find helps to build my concentration. As a
result, reading is something I am always trying to do more of.&lt;/p&gt;
&lt;p&gt;I have owned a kindle paperwhite for years, and it is perfect for reading books.  However, there are some types of reading material that my kindle falls simply does not handle well.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/0q_GU9pDp--720.webp 720w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/0q_GU9pDp--720.jpeg&quot; alt=&quot;The cover of &#39;Learn You a Haskell for Great Good!&#39;&quot; width=&quot;720&quot; height=&quot;499&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I have a bunch of dev books in my reading collection.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For example, thanks to &lt;a href=&quot;https://www.humblebundle.com/books/&quot;&gt;Humble Book Bundles&lt;/a&gt;, I have acquired a collection of programming, DevOps, system administration, and general technology ebooks. You know… the type of book that is &lt;em&gt;technically&lt;/em&gt; classified as a “textbook”… but does not sound nearly as cool when I call it that…  Anyway the point is… they are &lt;em&gt;horrendous&lt;/em&gt; to read on the kindle. I would love the ability to flip through a &lt;em&gt;textbook&lt;/em&gt; on a large, color screen, and a tablet enables that.&lt;/p&gt;
&lt;p&gt;Lastly, the internet is full of all sorts of “articles”. News stories, blog posts, product announcements, opinion pieces, tutorials, reviews… all litter the internet. Recently, I’ve been starting to corral this craziness by using tools such as &lt;a href=&quot;https://getpocket.com/&quot;&gt;pocket&lt;/a&gt; to &lt;em&gt;save&lt;/em&gt; articles I &lt;em&gt;want&lt;/em&gt; to read.
Then, I can come back and batch-read them later. This helps me break the endless rabbit-hole of digging through, and semi-reading “&lt;em&gt;related&lt;/em&gt;” articles (half of which aren’t even good).  This practice has worked &lt;em&gt;alright&lt;/em&gt; on my computer, but I do think it would be &lt;em&gt;ideal&lt;/em&gt; to sit down in an armchair with a tablet in my lap, for my batch reads.&lt;/p&gt;
&lt;h3&gt;Offload &lt;em&gt;Some&lt;/em&gt; of My Smart Phone Use&lt;/h3&gt;
&lt;p&gt;As previously stated, my biggest motivation for buying a tablet is to offload my phone usage to it. This is particularly true for apps which I am &lt;em&gt;required&lt;/em&gt; to use… but don’t need to have access to at all times.&lt;/p&gt;
&lt;p&gt;The best example of this? Banking and other financial apps. I appreciate the ease of modern banking, and love that I can simply take a picture to deposit a check. However, I don’t need this on my phone. Not only am I unlikely to be scanning checks and moving money around my bank accounts in public… it is not really safe to be doing so. I’d rather just wait until I get home on my own network, and do my banking from a tablet (which stays at home much more frequently than my phone).&lt;/p&gt;
&lt;h2&gt;Other Options&lt;/h2&gt;
&lt;h3&gt;Android Tablets&lt;/h3&gt;
&lt;p&gt;When I started looking at tablets, I quickly noticed that the market had changed &lt;em&gt;dramatically&lt;/em&gt; from a just few years ago. While there are still android tablets &lt;em&gt;available&lt;/em&gt;, the future of Google &lt;em&gt;supporting&lt;/em&gt; android on tablets is a bit unclear. They appear to be trying to shift everything to ChromeOS, as demonstrated with the &lt;a href=&quot;https://store.google.com/us/product/pixel_slate?hl=en-US&quot;&gt;Pixel Slate&lt;/a&gt;.
Additionally, Android still tends to treat tablets as just a large screened phone, and applications are often not optimized for it. Understandably, I was a little leery about Android tablets. As for ChromOS tablets, the Slate is currently the only real contender, but with it’s high price point, and &lt;a href=&quot;https://www.youtube.com/watch?v=2jsqZMCPJts&quot;&gt;lack-luster reviews&lt;/a&gt;, it was out of the running.&lt;/p&gt;
&lt;h3&gt;Amazon Fire Tablets&lt;/h3&gt;
&lt;p&gt;In contrast to the pixel slate, at the low end of the tablet market was the Amazon fire tablet family.  These devices are quite affordable and would work well for anyone that just wants to read/watch video on a tablet (especially Amazon content). However, I just felt that the Fire tablet might be a bit too limiting for me, as I tend to push technology as far as I can.  Beyond that, the cheaper iterations of each tablet are the “special offers” ones, which show amazon ads on the tablet. By the time I had selected a 10&amp;quot; tablet &lt;em&gt;without&lt;/em&gt; special offers, the price point started approaching (well… almost) another contender… the 2018 iPad.&lt;/p&gt;
&lt;h2&gt;Why an iPad?&lt;/h2&gt;
&lt;h3&gt;My Apple Device History&lt;/h3&gt;
&lt;p&gt;I originally wanted to do a full &lt;em&gt;multi-paragraph section&lt;/em&gt; detailing my limited use of apple products throughout my life, and how I only really buy them when they &lt;em&gt;obviously&lt;/em&gt; stand out above the competition. But that would be pretentious, extremely boring, and quite honestly, annoying. So in short: the only Apple devices that I have owned are 1) an iPod Video, and 2) iPod Touch. So why am I willing to get an iPad as my tablet device?&lt;/p&gt;
&lt;h3&gt;iPad&lt;/h3&gt;
&lt;p&gt;While researching, I found that the Apple &lt;em&gt;iPad&lt;/em&gt; was consistently &lt;a href=&quot;https://www.youtube.com/watch?v=GD1zTrs2EMw&quot;&gt;recommended&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=e7eNq6hTm5E&quot;&gt;as a&lt;/a&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=sGkr6Cjr0TI&quot;&gt;&lt;em&gt;great-value&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=y3PFt1Y8ZgA&quot;&gt;tablet-device&lt;/a&gt; on the market today. Most notably, the 2018 entry-level iPad, which
sold for $330 USD, but could often be found on sale for under $300.&lt;/p&gt;
&lt;p&gt;Aimed at the education market, the 2018 iPad saw a cpu upgrade, and support for the Apple pencil. The only con I ran into people reporting was that because it didn’t have a laminated screen, there was a bit of a “gap” between it and the glass. Despite the minor screen issue, this was still the best device I could find
around that price range.&lt;/p&gt;
&lt;h3&gt;Why the 10.5&amp;quot; Pro?&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/HyqIYXaOBS-900.webp 900w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/getting-an-ipad/HyqIYXaOBS-900.jpeg&quot; alt=&quot;a 10.5 iPad pro&quot; width=&quot;900&quot; height=&quot;505&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A 10.5&quot; iPad Pro&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So… why did I end up with the 10.5&amp;quot; iPad Pro? The switch happened last minute the morning I ended up ordering it. My upgrade happened because of two factors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sale&lt;/strong&gt;: The iPad Pro 10.5&amp;quot; had gone on a sale at BestBuy, and the configuration I liked dropped to $499 (from $650). While this was a bit more expensive, I thought it was still a good deal, and the 2018 iPad
wasn’t on sale anywhere so it was the full $330.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;: Essentially, the Pro had a slightly larger and &lt;em&gt;much&lt;/em&gt; better display, better speakers, and double the storage space (64GB compared to 32GB). These are features that I don’t think most people need to care about, I knew I would really appreciate those features, especially now that I could get them for only a ~$170 increase over the iPad, compared to the normal $310 difference.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;So, that’s the history and reasoning as to &lt;em&gt;why&lt;/em&gt; I decided to get a tablet device, and why that device ultimately ended up being an iPad 10.5&amp;quot; Pro. It arrived a couple weeks ago, and I have been enjoying it so far. I think I’ll give myself a bit more time with it, before I even &lt;em&gt;attempt&lt;/em&gt; to write any sort of review.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Extending a VM Virtual Disk</title>
    <link href="https://ryan.himmelwright.net/post/extending-vm-hd/" />
    <updated>2019-02-07T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/extending-vm-hd/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/vapoWpmTpe-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/vapoWpmTpe-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Foster Street, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last week, I extended the virtual disk of the VM hosting my
&lt;a href=&quot;https://jenkins.io&quot;&gt;jenkins&lt;/a&gt; server. Shortly after, I increased the maximum
disk size of one of the job’s docker containers, maxing out the disk. This
meant that I needed to extend the drive*… again*. If you ever do something
twice, it is &lt;em&gt;best&lt;/em&gt; to have it documented for the potential third time. So,
here we are.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Assumptions&lt;/h2&gt;
&lt;p&gt;Before getting started, I want to point out that this method is specific to the
environment I currently have for &lt;em&gt;my VMs&lt;/em&gt;. Specifically, I am using kvm/qemu
and virt-manager, with qcow2 images for the virtual disks. Additionally, the
specific VM I was extending was installed with LVM and it’s main partition was
formatted with a xfs file system. Just note that some steps &lt;em&gt;may&lt;/em&gt; differ
elsewhere. This is what worked for &lt;em&gt;me&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Clone VM&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/DFmM4zGN1u-452.webp 452w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/DFmM4zGN1u-452.jpeg&quot; alt=&quot;Clone VM window in Virt Manager&quot; width=&quot;452&quot; height=&quot;626&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Cloning the VM in Virt Manager.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While not &lt;em&gt;required&lt;/em&gt;, it isn’t a &lt;em&gt;bad&lt;/em&gt; idea to first clone the VM (just
in case anything becomes damaged). If using &lt;code&gt;virt-manager&lt;/code&gt;, cloning a
VM is as simple as right clicking a &lt;em&gt;powered down&lt;/em&gt; VM, and selecting
“&lt;em&gt;Clone…&lt;/em&gt;”. A window will pop up with options for cloning the
VM. Make the desired name changes and hit &lt;em&gt;Clone&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Extend qcow2 file&lt;/h2&gt;
&lt;p&gt;The first step in resizing the virtual drive is to first expand
the &lt;code&gt;qcow2&lt;/code&gt; image. By default, the images tend to be stored at
&lt;code&gt;/var/lib/libvirt/images/&lt;/code&gt; and will require &lt;code&gt;root&lt;/code&gt; privileges to
access. Virt-Manager can be used to double check which image the VM is
using for its disk. To resize the qcow2 image, use the &lt;code&gt;qemu-img resize&lt;/code&gt; command, providing it the image file path/name and then the size
to expand it. For example, I used &lt;code&gt;+40G&lt;/code&gt; in my command (&lt;code&gt;qemu-img resize Jenkins.qcow2 +40G&lt;/code&gt;) to extend the image by 40GB.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;root@ninetales:/var/lib/libvirt/images&lt;span class=&quot;token comment&quot;&gt;# qemu-img info Jenkins.qcow2&lt;/span&gt;
image: Jenkins.qcow2
&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; format: qcow2
virtual size: 20G &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;21474836480&lt;/span&gt; bytes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
disk size: 20G
cluster_size: &lt;span class=&quot;token number&quot;&gt;65536&lt;/span&gt;
Format specific information:
    compat: &lt;span class=&quot;token number&quot;&gt;1.1&lt;/span&gt;
    lazy refcounts: &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    refcount bits: &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;
    corrupt: &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;

root@ninetales:/var/lib/libvirt/images&lt;span class=&quot;token comment&quot;&gt;# qemu-img resize Jenkins.qcow2 +40G&lt;/span&gt;
Image resized.

root@ninetales:/var/lib/libvirt/images&lt;span class=&quot;token comment&quot;&gt;# qemu-img info Jenkins.qcow2&lt;/span&gt;
image: Jenkins.qcow2
&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; format: qcow2
virtual size: 60G &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64424509440&lt;/span&gt; bytes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
disk size: 20G
cluster_size: &lt;span class=&quot;token number&quot;&gt;65536&lt;/span&gt;
Format specific information:
    compat: &lt;span class=&quot;token number&quot;&gt;1.1&lt;/span&gt;
    lazy refcounts: &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    refcount bits: &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;
    corrupt: &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;

root@ninetales:/var/lib/libvirt/images&lt;span class=&quot;token comment&quot;&gt;#&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command &lt;code&gt;qemu-image info&lt;/code&gt; can be helpful in verifying that the resize
worked, by checking the size of the image.&lt;/p&gt;
&lt;h2&gt;Gparted Live ISO&lt;/h2&gt;
&lt;p&gt;For the next few steps, it is a good idea to boot the system from a live CD.
This will run the OS in RAM, allowing the disk to be fully unmounted.  With
access to the VM’s display, an ISO such as the &lt;a href=&quot;https://gparted.org/livecd.php&quot;&gt;gparted live
CD&lt;/a&gt; can be used to resize the partitions,
as it contains the amazing graphical tool, &lt;code&gt;gparted&lt;/code&gt; (duh).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you boot up the VM and don’t see the new unallocated space available in the
volume… make sure you didn’t accidentally boot the &lt;em&gt;backup VM&lt;/em&gt;… Not that
&lt;strong&gt;I made such a mistake…&lt;/strong&gt; :P&lt;/p&gt;
&lt;h3&gt;LVM Resize&lt;/h3&gt;
&lt;p&gt;My VM is installed using LVM volumes, so I had to resize them
before I could resize the file system. Gparted will do this
automatically when resizing a partition.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/WS_r3jHfUR-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/WS_r3jHfUR-1024.jpeg&quot; alt=&quot;Booting into the Gparted live ISO&quot; width=&quot;1024&quot; height=&quot;887&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Booting into the Gparted live ISO.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To resize the partition, first verify that the correct virtual disk
is selected in the top right drop-down the window. Next, select the
partition to expand, and click the “&lt;em&gt;Resize/Move&lt;/em&gt;” icon at the top.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/376OyENCK1-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/extending-vm-hd/376OyENCK1-1024.jpeg&quot; alt=&quot;Resizing the partition in Gparted&quot; width=&quot;1024&quot; height=&quot;887&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Resizing the partition in Gparted.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the resize window, I changed the &lt;code&gt;Free space following (MiB)&lt;/code&gt;
value to &lt;code&gt;0&lt;/code&gt;, to expand the partition to use &lt;em&gt;all&lt;/em&gt; of the unallocated space.
Lastly, I hit the &lt;em&gt;Resize&lt;/em&gt; button and let Gparted do it’s magic.&lt;/p&gt;
&lt;h3&gt;Grow XFS&lt;/h3&gt;
&lt;p&gt;Finally, with the lvm volume expanded, I just had to grow my file system to use
the new space. I booted up the VM and logged in. This VM uses an xfs file
system, so I was able to use the &lt;code&gt;xfs_growfs&lt;/code&gt; command to expand the partition:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ryan@mr-mime ~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; xfs_growfs /dev/centos/root
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. Ryan removed output &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; the post&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
data blocks changed from &lt;span class=&quot;token number&quot;&gt;4851712&lt;/span&gt; to &lt;span class=&quot;token number&quot;&gt;15322112&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ryan@mr-mime ~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token function&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-h&lt;/span&gt;
Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   59G   19G   40G  &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;% /
devtmpfs                 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;% /dev
tmpfs                    &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G  &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;.0K  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G   &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;% /dev/shm
tmpfs                    &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G  &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;.7M  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G   &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;% /run
tmpfs                    &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.9G   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;% /sys/fs/cgroup
/dev/vda1                497M  231M  267M  &lt;span class=&quot;token number&quot;&gt;47&lt;/span&gt;% /boot
tmpfs                    379M     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;  379M   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;% /run/user/1000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: My VM’s disk space was COMPLETELY full. This meant that I couldn’t use auto
tab complete in my shell because it spit out there’s no disk space. Typing the
command out fully by hand, still worked.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With the xfs partition resized, I rebooted the VM for good measure, and
everything was up and running again.&lt;/p&gt;
&lt;p&gt;That’s about it. Remember, the steps I took might need to be altered for other
environments, but this post should still be a good &lt;em&gt;starting&lt;/em&gt; point. I know it
will help me when I acidently overfill this VM &lt;em&gt;again&lt;/em&gt;… Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Replacing a Drive in My ZFS Mirror</title>
    <link href="https://ryan.himmelwright.net/post/replace-zfs-mirror-drive/" />
    <updated>2019-01-15T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/replace-zfs-mirror-drive/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/replace-zfs-mirror-drive/GKS5gx7Iv_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/replace-zfs-mirror-drive/GKS5gx7Iv_-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Desk, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Right before Thanksgiving, one of the hard drives in my server started get
noisy… very noisy. Fearing the worst, I &lt;a href=&quot;https://ryan.himmelwright.net/post/zfs-backups-to-luks-external&quot;&gt;did a
backup&lt;/a&gt;, and shutdown the server until I had
time to investigate further… and likely replace the drive. That
time came this past week.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Verifying the Drive Failed&lt;/h2&gt;
&lt;p&gt;Before throwing money at the problem, I wanted to see if ZFS was detecting any
issues. When I ran a &lt;code&gt;zpool status&lt;/code&gt; on my &lt;code&gt;Data&lt;/code&gt; pool, it warned me that one of
the devices had experienced an error, but that I had not (&lt;em&gt;yet&lt;/em&gt;) encountered
any data errors. Time to buy a new drive.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;λ ninetales ~ → zpool status Data
  pool: Data
 state: ONLINE
status: One or &lt;span class=&quot;token function&quot;&gt;more&lt;/span&gt; devices has experienced an unrecoverable error.  An
	attempt was made to correct the error.  Applications are unaffected.
action: Determine &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; the device needs to be replaced, and &lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt; the errors
	using &lt;span class=&quot;token string&quot;&gt;&#39;zpool clear&#39;&lt;/span&gt; or replace the device with &lt;span class=&quot;token string&quot;&gt;&#39;zpool replace&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
   see: http://zfsonlinux.org/msg/ZFS-8000-9P
  scan: resilvered &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.14M &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; 0h0m with &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; errors on Sat Jan &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;:49:31 &lt;span class=&quot;token number&quot;&gt;2019&lt;/span&gt;
config:

	NAME                                  STATE     READ WRITE CKSUM
	Data                                  ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	  mirror-0                            ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	    ata-TOSHIBA_DT01ACA300_365XDT3KS  ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
	    ata-TOSHIBA_DT01ACA300_365XDR5KS  ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

errors: No known data errors&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ordering a New Drive&lt;/h2&gt;
&lt;p&gt;When I started shopping for hard drives, I decided to replace my broken 7200 RPM
one a 5400 RPM one. I’d rather have the drives last longer and run quieter,
than whatever marginal speed difference the faster spinning disks &lt;em&gt;may&lt;/em&gt;
provide. I decided to finally go with a &lt;a href=&quot;https://www.amazon.com/dp/B008JJLW4M/ref=twister_B07GXT9HNH?_encoding=UTF8&amp;amp;psc=1&quot;&gt;3TB Western Digital RED
drive&lt;/a&gt;
this time, even tough it’s a bit more expensive… mostly to try out.&lt;/p&gt;
&lt;h2&gt;Replacing the Drive&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/replace-zfs-mirror-drive/TXswHxPORQ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/replace-zfs-mirror-drive/TXswHxPORQ-1200.jpeg&quot; alt=&quot;Swapping the two hard drives&quot; width=&quot;1200&quot; height=&quot;1075&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Swapping the hot-swap caddy from the broken hard drive (left) with my new WD Red drive (right).&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Physically&lt;/em&gt; swapping the hard drives was a breeze. I could easily tell which
drive was the defective one… as it caused the entire server to rumble (ಠ_ಠ) .
So I slid it out, unscrewed the drive from its caddy, and screwed in the new
one. Lastly, I slide the caddy back into the server and booted it up. I love
hot-swap drive bays.&lt;/p&gt;
&lt;h3&gt;Figuring Out Which Disk To Replace&lt;/h3&gt;
&lt;p&gt;Determining which disk was being replaced &lt;em&gt;in software&lt;/em&gt; was a bit more
difficult. In order to add the new drive to the &lt;code&gt;Data&lt;/code&gt; pool, I needed to tell
ZFS which one had been &lt;em&gt;replaced&lt;/em&gt;.  This was made more complicated by the fact
that previously, the two drives in the mirror were the same model and both showed up
as &lt;code&gt;/dev/disk/by-id/ata-TOSHIBA_DT01ACA300_365XDR5KS&lt;/code&gt;. So, I needed to find the
&lt;code&gt;guid&lt;/code&gt; for each drive, because it would differ between them. I used the command
&lt;code&gt;zdb&lt;/code&gt; to spit out information about of each of my pools:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;λ ninetales ~ → zdb
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;other pool output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
Data:
    version: &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;
    name: &lt;span class=&quot;token string&quot;&gt;&#39;Data&#39;&lt;/span&gt;
    state: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    txg: &lt;span class=&quot;token number&quot;&gt;15996848&lt;/span&gt;
    pool_guid: &lt;span class=&quot;token number&quot;&gt;2285339125999939520&lt;/span&gt;
    errata: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    comment: &lt;span class=&quot;token string&quot;&gt;&#39;iocage&#39;&lt;/span&gt;
    hostname: &lt;span class=&quot;token string&quot;&gt;&#39;ninetales&#39;&lt;/span&gt;
    com.delphix:has_per_vdev_zaps
    vdev_children: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    vdev_tree:
        type: &lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;
        id: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
        guid: &lt;span class=&quot;token number&quot;&gt;2285339125999939520&lt;/span&gt;
        children&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;:
            type: &lt;span class=&quot;token string&quot;&gt;&#39;mirror&#39;&lt;/span&gt;
            id: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
            guid: &lt;span class=&quot;token number&quot;&gt;15171243251753521949&lt;/span&gt;
            metaslab_array: &lt;span class=&quot;token number&quot;&gt;34&lt;/span&gt;
            metaslab_shift: &lt;span class=&quot;token number&quot;&gt;34&lt;/span&gt;
            ashift: &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;
            asize: &lt;span class=&quot;token number&quot;&gt;3000588042240&lt;/span&gt;
            is_log: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
            create_txg: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
            com.delphix:vdev_zap_top: &lt;span class=&quot;token number&quot;&gt;125&lt;/span&gt;
            children&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;:
                type: &lt;span class=&quot;token string&quot;&gt;&#39;disk&#39;&lt;/span&gt;
                id: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
                guid: &lt;span class=&quot;token number&quot;&gt;4676737554230074290&lt;/span&gt;
                path: &lt;span class=&quot;token string&quot;&gt;&#39;/dev/disk/by-id/ata-TOSHIBA_DT01ACA300_365XDT3KS&#39;&lt;/span&gt;
                phys_path: &lt;span class=&quot;token string&quot;&gt;&#39;/dev/ada1&#39;&lt;/span&gt;
                whole_disk: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
                not_present: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
                DTL: &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;
                create_txg: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
                com.delphix:vdev_zap_leaf: &lt;span class=&quot;token number&quot;&gt;126&lt;/span&gt;
            children&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;:
                type: &lt;span class=&quot;token string&quot;&gt;&#39;disk&#39;&lt;/span&gt;
                id: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
                guid: &lt;span class=&quot;token number&quot;&gt;13442522248687181242&lt;/span&gt;
                path: &lt;span class=&quot;token string&quot;&gt;&#39;/dev/disk/by-id/ata-TOSHIBA_DT01ACA300_365XDR5KS&#39;&lt;/span&gt;
                phys_path: &lt;span class=&quot;token string&quot;&gt;&#39;/dev/ada3&#39;&lt;/span&gt;
                whole_disk: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
                DTL: &lt;span class=&quot;token number&quot;&gt;122&lt;/span&gt;
                create_txg: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
                com.delphix:vdev_zap_leaf: &lt;span class=&quot;token number&quot;&gt;159&lt;/span&gt;
    features_for_read:
        com.delphix:hole_birth
        com.delphix:embedded_data
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first, I still didn’t know which drive was which. However, after looking
deeper, I noticed that &lt;em&gt;one&lt;/em&gt; of the Toshiba drives listed had the line
&lt;code&gt;not_present: 1&lt;/code&gt;, indicating that it was the broken drive I had removed!&lt;/p&gt;
&lt;h3&gt;Replacing the drive&lt;/h3&gt;
&lt;p&gt;With the &lt;code&gt;guid&lt;/code&gt; of the broken drive known, I was able to start the process of
replacing it in the pool with the new one. I issued a &lt;code&gt;zpool replace&lt;/code&gt; command
with the following arguments:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; zpool replace Data &lt;span class=&quot;token number&quot;&gt;4676737554230074290&lt;/span&gt; /dev/sdd&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Data&lt;/code&gt; - the name of the pool&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;4676737554230074290&lt;/code&gt; - the &lt;code&gt;guid&lt;/code&gt; of the previous drive&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/dev/sdd&lt;/code&gt; - the path to the new drive&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Afterwards, the resilvering process started (rebuilding the mirror by copying
data from one drive to the other). I was able to check the status of
the process using &lt;code&gt;zpool status Data&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;λ ninetales by-uuid → zpool status Data
  pool: Data
 state: DEGRADED
status: One or &lt;span class=&quot;token function&quot;&gt;more&lt;/span&gt; devices is currently being resilvered.  The pool will
	&lt;span class=&quot;token builtin class-name&quot;&gt;continue&lt;/span&gt; to function, possibly &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; a degraded state.
action: Wait &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; the resilver to complete.
  scan: resilver &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; progress since Sat Jan &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;:29:36 &lt;span class=&quot;token number&quot;&gt;2019&lt;/span&gt;
	&lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt;.6M scanned out of &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.03T at &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;.42M/s, 123h32m to go
	&lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt;.2M resilvered, &lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt;% &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;
config:

	NAME                                  STATE     READ WRITE CKSUM
	Data                                  DEGRADED     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	  mirror-0                            DEGRADED     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	    replacing-0                       DEGRADED     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	      &lt;span class=&quot;token number&quot;&gt;4676737554230074290&lt;/span&gt;             UNAVAIL      &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;  was /dev/disk/by-id/ata-TOSHIBA_DT01ACA300_365XDT3KS
	      sdd                             ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resilvering&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	    ata-TOSHIBA_DT01ACA300_365XDR5KS  ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

errors: No known data errors&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resilvering can take a &lt;em&gt;long&lt;/em&gt; time. Luckily, I only had about ~1 TB of data to
rebuild, so I hoped it wouldn’t &lt;em&gt;actually&lt;/em&gt; take the 123.5 hours that the first
&lt;code&gt;status&lt;/code&gt; predicted! Regardless, during the resilvering process, the only thing
to do is wait (&lt;em&gt;and hope that the other drive doesn’t break in the process!&lt;/em&gt;).
So I did.&lt;/p&gt;
&lt;h3&gt;Resilver Complete&lt;/h3&gt;
&lt;p&gt;In just over 4 hours, my pool had rebuilt and was back online.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;λ ninetales ~ → zpool status Data
  pool: Data
 state: ONLINE
  scan: resilvered &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;.03T &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; 4h8m with &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; errors on Sat Jan &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:38:26 &lt;span class=&quot;token number&quot;&gt;2019&lt;/span&gt;
config:

	NAME                                  STATE     READ WRITE CKSUM
	Data                                  ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	  mirror-0                            ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	    sdd                               ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	    ata-TOSHIBA_DT01ACA300_365XDR5KS  ONLINE       &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;     &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

errors: No known data errors&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at this output now, I realize I probably should have added the new
drive by &lt;code&gt;uuid&lt;/code&gt; instead of pathname…hmmm…&lt;/p&gt;
&lt;p&gt;Oh well. That is a post for another day. At least my broken drive
has &lt;em&gt;finally&lt;/em&gt; been replaced!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Shell Stuff - Easy File Cleanup</title>
    <link href="https://ryan.himmelwright.net/post/shell-stuff-find-grep-sub/" />
    <updated>2019-01-05T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/shell-stuff-find-grep-sub/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/shell-stuff-find-grep-sub/fZq5qiTGyk-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/shell-stuff-find-grep-sub/fZq5qiTGyk-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;World War I Memorial Park, North Attleboro, MA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Applications can leave their junk files all over the place. While I
appreciate that all of the &lt;code&gt;.swp&lt;/code&gt;, &lt;code&gt;.retry&lt;/code&gt;, and &lt;code&gt;&amp;quot;conflict&amp;quot;&lt;/code&gt; files are there to
help me when things go wrong… sometimes I just want to clean up my
file system. So… here is a simple string of commands I often use to declutter
my files.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;em&gt;DISCLAIMER:&lt;/em&gt; I know there are &lt;em&gt;MANY&lt;/em&gt; ways to accomplish this.  The method
described in this post is to share &lt;strong&gt;one&lt;/strong&gt; efficient solution I use, that might
help someone that currently knows &lt;strong&gt;zero&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;The commands&lt;/h2&gt;
&lt;p&gt;First, lets quickly meet the commands we will be using:&lt;/p&gt;
&lt;h3&gt;Find&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;find&lt;/code&gt; is a classic UNIX command that searches for files in a directory
hierarchy. By default, it writes out the file path for each file/directory that
it finds.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;➜  tree
.
├── dirA
│   ├── file3
│   ├── file4
│   └── file5
├── dirB
│   └── file6
├── file1
└── file2

2 directories, 6 files

➜  find .
.
./file2
./file1
./dirB
./dirB/file6
./dirA
./dirA/file5
./dirA/file4
./dirA/file3&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Grep&lt;/h3&gt;
&lt;p&gt;Another classic. Basically, &lt;code&gt;grep&lt;/code&gt; searches for a pattern in each file
provided. In addition to files, it can search text passed through a pipe (this
is important for our use, but more on that later).&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;➜  cat file1
This is a fake file
with a few lines of content.

However, I want search for something
without opening it...

Secret: 12345

I wonder if I will be able to get it...


➜  grep Secret file1
Secret: 12345&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Command Substitution&lt;/h2&gt;
&lt;p&gt;Lastly, &lt;em&gt;command substitution&lt;/em&gt; is taking one command, and using it’s output as
&lt;em&gt;part&lt;/em&gt; of &lt;em&gt;another command&lt;/em&gt;. Traditionally, this was done by calling the
substitution command `inside backticks`, but it &lt;a href=&quot;http://mywiki.wooledge.org/BashFAQ/082&quot;&gt;is now preferred to use
$(COMMAND) instead of backticks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;➜ echo I am at: `pwd`
I am at: /tmp/demo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or (preferred):&lt;/p&gt;
&lt;pre class=&quot;language-test&quot;&gt;&lt;code class=&quot;language-test&quot;&gt;➜ echo I am at: $(pwd)
I am at: /tmp/demo&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Pipes&lt;/h2&gt;
&lt;p&gt;An &lt;a href=&quot;https://en.wikipedia.org/wiki/Pipeline_(Unix)&quot;&gt;unix pipe&lt;/a&gt; (&lt;code&gt;|&lt;/code&gt;) directs the
&lt;em&gt;output&lt;/em&gt; of one command, to be used as the &lt;em&gt;input&lt;/em&gt; for another command. Pipes
can be used to chain together several commands, forming a &lt;em&gt;pipeline&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;The output of &lt;code&gt;ls&lt;/code&gt; can be fed as input to &lt;code&gt;wc&lt;/code&gt; (word count) to create a
pipeline command that returns the number of files/directories in the current directory.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt;
dirA  dirB  file1  file2

➜ &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Putting It All Together&lt;/h2&gt;
&lt;p&gt;Now that we know all the parts, how does it all fit together? One particular
shell chain I find convenient is pairing &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;grep&lt;/code&gt; to recursively get
all the paths of a particular file type, and then use it in a command
substitution to pass that result on to another command (such as &lt;code&gt;rm&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;COMMAND &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; SEARCHSTRING&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the combination I use to clean up my directories. While working on
writing ansible playbooks, I can generate a few &lt;code&gt;*.retry&lt;/code&gt; files, as well as
some &lt;code&gt;*.swp&lt;/code&gt; files from editing in vim.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜ &lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; .retry          &lt;span class=&quot;token comment&quot;&gt;## Find *.retry files&lt;/span&gt;
./file1.retry
./dirA/file5.retry
./dirA/file3.retry

➜ &lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; .retry&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;    &lt;span class=&quot;token comment&quot;&gt;## Delete *.retry files&lt;/span&gt;

➜ &lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; .retry          &lt;span class=&quot;token comment&quot;&gt;## Check that they were deleted&lt;/span&gt;

➜&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;That’s it. A small post for a &lt;em&gt;simple&lt;/em&gt; but &lt;strong&gt;powerful&lt;/strong&gt; command line set.  If
you haven’t used this team of commands before, give it a try sometime! Have
fun!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Configuring Pass, the Standard Unix Password Manager</title>
    <link href="https://ryan.himmelwright.net/post/setting-up-pass/" />
    <updated>2018-12-12T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/setting-up-pass/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setting-up-pass/kMBlJcOrKH-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setting-up-pass/kMBlJcOrKH-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Liberty Warehouse Apartments, Durham, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve been using &lt;code&gt;pass&lt;/code&gt; to manage my passwords for quite some time.
During the early days of use, I occasionally had difficulty configuring it on
new machines, but those days appear to be long gone. It is a simple, generic,
yet flexible system.  Here’s how to get started.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;em&gt;(but first, some background… feel free to skip ahead)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;My Password Manager History&lt;/h2&gt;
&lt;p&gt;For the longest time, I didn’t use a password manager (in my defence, not many
people did). Then in college, I started using
&lt;a href=&quot;https://www.lastpass.com/&quot;&gt;LastPass&lt;/a&gt;. It was simple and made it easy to switch
all of my passwords to randomly generated ones. I had a good system that
worked for a few years, and  was even able to integrate my
&lt;a href=&quot;https://www.yubico.com&quot;&gt;yubikey&lt;/a&gt; with it.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setting-up-pass/ZP1YqwFiTy-556.webp 556w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setting-up-pass/ZP1YqwFiTy-556.jpeg&quot; alt=&quot;LastPass logo&quot; width=&quot;556&quot; height=&quot;101&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Then in 2015, Lastpass was acquired by &lt;a href=&quot;https://www.logmeininc.com&quot;&gt;LogMeIn&lt;/a&gt;,
which had a questionable past of Linux support. Like many others in the open
source community… I started looking for alternatives.&lt;/p&gt;
&lt;p&gt;I had already been searching for a LastPass replacement even before the
acquisition. My search was mainly fueled by one big issue I had with
LastPass… it required a web browser to use.  Additionally, to utilize it’s full
feature set, it needed to run as a Chrome or Firefox plugin. As someone who
often uses alternative web browsers (like &lt;a href=&quot;https://qutebrowser.org&quot;&gt;qutebrowser&lt;/a&gt;),
or works on headless machines, I try not to use applications that exist solely
as a FireFox/Chome app.  I am also not a fan of pure website-apps in
general.&lt;/p&gt;
&lt;p&gt;So, as the I watched others switch password managers amongst the acquisition
hype, one switch I remember seeing was Chris Fischer of the Linux Action Show.
In episode 387 of LAS, Chris and Noah (his co-host) discussed LastPass alternatives, and
Chris highlighted his switch to
&lt;a href=&quot;https://www.youtube.com/watch?v=OfgZ5Fh-NfE&amp;amp;feature=youtu.be&amp;amp;t=4935&quot;&gt;pass&lt;/a&gt;.
While I don’t think he kept with the system long-term… I have.&lt;/p&gt;
&lt;h2&gt;What I like about &lt;a href=&quot;https://www.passwordstore.org&quot;&gt;pass&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Unix Philosophy “Simplicity”&lt;/h3&gt;
&lt;p&gt;Okay. The average computer user will not think &lt;code&gt;pass&lt;/code&gt; is “simple”. I agree.
However, being designed to follow the &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_philosophy&quot;&gt;Unix
philosophy&lt;/a&gt;, pass’s &lt;em&gt;architecture
is&lt;/em&gt;. Basically, pass is just a nice wrapper around a bunch of
&lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_Privacy_Guard&quot;&gt;gpg&lt;/a&gt; encrypted text files. It
is a minimal, but tested solution.  This model makes pass easily compatible
with many other great tools, such as bash, git,
&lt;a href=&quot;https://git.zx2c4.com/password-store/tree/contrib/dmenu&quot;&gt;dmenu&lt;/a&gt;,
&lt;a href=&quot;https://hackage.haskell.org/package/xmonad-contrib-0.13/docs/XMonad-Prompt-Pass.html&quot;&gt;xmonad&lt;/a&gt;
and &lt;a href=&quot;https://git.zx2c4.com/password-store/tree/contrib/emacs&quot;&gt;emacs&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Command Line Tool&lt;/h3&gt;
&lt;p&gt;As a command-line tool, I can use pass anywhere. It doesn’t matter how conventional or strange the
setup may be. I can have it on my desktop, on a headless server, or even inside a container. It
makes no difference.  Even if I am on a public computer, if I can &lt;code&gt;ssh&lt;/code&gt; into one of my servers, I
can access my passwords.&lt;/p&gt;
&lt;h3&gt;Flexible&lt;/h3&gt;
&lt;p&gt;By default, pass assumes the first line of a store file is the password.  However, the
multi-line contents of a pass file can be anything. For example, pass could be used to securely store
encrypted notes. This gives the system a ton of flexibility, as the password items don’t &lt;em&gt;have&lt;/em&gt; to
conform to any sort of template.&lt;/p&gt;
&lt;h2&gt;Installing Pass &amp;amp; Help Packages&lt;/h2&gt;
&lt;p&gt;On Fedora, pass can be installed using &lt;code&gt;dnf&lt;/code&gt;. For other systems, check out the
“Download” section of the &lt;a href=&quot;https://www.passwordstore.org/&quot;&gt;pass website&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; pass&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configuring Pass&lt;/h2&gt;
&lt;p&gt;After installing pass, there are few steps to configure it. First, we need to create a gpg key if
one doesn’t already exist. Then, we need to initialize a password-store using that key.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;I went a little heavy with the animation images in the remainder of
the post. Sorry. I hope they are more useful than annoying. Being a visual learner, at the very
least they are helpful for me when I reference this post in the future. …&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;New GPG Key&lt;/h3&gt;
&lt;p&gt;To create a gpg key, the &lt;code&gt;gpg2 --gen-key&lt;/code&gt; command is normally used.  However, I opted to use &lt;code&gt;gpg2 --full-gen-key&lt;/code&gt;, which allows for a bit more control during setup.  The command will prompt for
several bits of information, and the default selections are generally fine for most of the options
(Personally, I use a 4096-bit key, because… why not?). At the end it will ask for a
name, Password, and optional comment.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;It should be noted that &lt;code&gt;gpg2&lt;/code&gt; most likely needs to be used instead of &lt;code&gt;gpg&lt;/code&gt;
for pass. However, it may vary depending on distribution and the package
versions.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Pass Init&lt;/h3&gt;
&lt;p&gt;After a gpg key has been generated, it can be used with pass.  First, find the key’s ID by using
&lt;code&gt;gpg2 --list-secret-keys&lt;/code&gt;. Then, configure pass with &lt;code&gt;pass init GPG-KEY-ID&lt;/code&gt;. This will create a
password-store directory, located by default at &lt;code&gt;~/.password-store/&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Add some items&lt;/h2&gt;
&lt;p&gt;With pass initialized, lets start adding passwords to it! Here are &lt;em&gt;some&lt;/em&gt; of
the most common commands to do so:&lt;/p&gt;
&lt;h3&gt;insert&lt;/h3&gt;
&lt;p&gt;Simply put, &lt;code&gt;pass insert&lt;/code&gt; … inserts a password. Call it with the desired folder/file
structure for the password, and pass will then prompt for the password to
save. That’s it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass insert Shopping/amazon.com/ryan
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;pass generate&lt;/h3&gt;
&lt;p&gt;In addition to inserting existing passwords, pass can also &lt;em&gt;generate new&lt;/em&gt; ones
using &lt;code&gt;pass generate&lt;/code&gt;. Just provide the password path, and optionally the
length of the password. Pass will then generate a random password, spit it out
on screen, and insert the entry to the password-store.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass generate Shopping/SomeFakeStore/ryan 35
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;pass edit&lt;/h3&gt;
&lt;p&gt;Generating passwords is great, but being a forgetful person, I like to keep additional information
in my pass entries (username, email, website url). This is where &lt;code&gt;pass edit&lt;/code&gt; comes in. When called,
&lt;code&gt;pass edit&lt;/code&gt; will open up the contents of the entry in the default editor. From there, make the
changes, and save.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass edit Shopping/SomeFakeStore/ryan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in vim:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;DdU1x&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;~&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;w7w&lt;span class=&quot;token string&quot;&gt;&quot;kvsWdHAF-&#92;Vi&quot;&lt;/span&gt;I9Q&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;I
---
Username: ryan
Password: &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;DdU1x&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;~&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;w7w&lt;span class=&quot;token string&quot;&gt;&quot;kvsWdHAF-&#92;Vi&quot;&lt;/span&gt;I9Q&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;I
URL: https://www.some-bs-store.com
Notes: I love this place&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;pass&lt;/h3&gt;
&lt;p&gt;Lastly, to retrieve stored passwords, call &lt;code&gt;pass&lt;/code&gt; with the
password entry. Optionally, use the &lt;code&gt;-c&lt;/code&gt; flag to copy the password
(first line if a multi-line entry) to the clipboard instead of spewing it into
the terminal.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pass Shopping/SomeFakeStore/ryan
&lt;span class=&quot;token comment&quot;&gt;## or ##&lt;/span&gt;
pass &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Shopping/SomeFakeStore/ryan&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Making Pass Better&lt;/h2&gt;
&lt;p&gt;With pass’s flexibility, there are many additional features to help improve it for each user’s
needs.  For me, there are two extensions that make my pass experience &lt;em&gt;much&lt;/em&gt; more enjoyable.&lt;/p&gt;
&lt;h3&gt;Pass Git&lt;/h3&gt;
&lt;p&gt;Password-Store items are text files, which allows them to be easily version controlled.
Consequently, pass has built in support for git with the &lt;code&gt;pass git&lt;/code&gt; command. If a password-store
is linked up to a git repo, normal git commands (&lt;code&gt;add&lt;/code&gt;, &lt;code&gt;mv&lt;/code&gt;, &lt;code&gt;rm&lt;/code&gt;…) can be used with the store.&lt;/p&gt;
&lt;p&gt;Additionally, when modifying the store’s contents, &lt;code&gt;pass git&lt;/code&gt; will automatically create commits
that reflect the changes. After adding or modifying a password, issue the command &lt;code&gt;pass git push&lt;/code&gt; on the updated
machine, and then &lt;code&gt;pass git pull&lt;/code&gt; on others to sync the changes.&lt;/p&gt;
&lt;h3&gt;Passmeu&lt;/h3&gt;
&lt;p&gt;While having a CLI password manager is nice when working with headless systems, it can be a bit
cumbersome for normal day-to-day use. Hence,
&lt;a href=&quot;https://git.zx2c4.com/password-store/tree/contrib/dmenu/passmenu&quot;&gt;passmenu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Passmenu is a script (now built into the upstream project) that wraps
&lt;a href=&quot;https://tools.suckless.org/dmenu/&quot;&gt;dmenu&lt;/a&gt; around pass. When &lt;code&gt;passmenu&lt;/code&gt; is run, &lt;code&gt;dmenu&lt;/code&gt; opens up
with all the password-store items to search/filter from. When an item is
selected in dmenu, the user is prompted for the gpg password (if it hasn’t been unlocked recently),
after which the password is then temporarily added to the user’s clipboard.&lt;/p&gt;
&lt;p&gt;On all my computers, I bind the command &lt;code&gt;passmenu&lt;/code&gt; to the keys &lt;code&gt;SUPER&lt;/code&gt; + &lt;code&gt;SHIFT&lt;/code&gt; + &lt;code&gt;P&lt;/code&gt;.  Whenever I
need a password, I just hit those three keys, and dmenu pops up so I can search for the password I
want.  After typing in my master passphrase, I can paste the password wherever I need it.  Passmenu
makes pass much more reasonable to use.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;See Also: &lt;a href=&quot;https://github.com/carnager/rofi-pass&quot;&gt;rofi-pass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Setting up your pass setup on a new system&lt;/h2&gt;
&lt;p&gt;Now that I’ve done it over a hundred times, setting up a new system is easy. Here’s my usual steps:&lt;/p&gt;
&lt;h3&gt;Export GPG Key&lt;/h3&gt;
&lt;p&gt;First, export the password-store’s gpg key.  To do that, use &lt;code&gt;gpg2 --list-secret-keys&lt;/code&gt; to confirm the
key’s ID, then export that key to a file with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg2 --export-secret-keys KEY-ID &amp;gt;&amp;gt; key-filename.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, transfer that file to the new machine.&lt;/p&gt;
&lt;h3&gt;Import GPG Key&lt;/h3&gt;
&lt;p&gt;On the new machine, import the gpg key using the following command (note, you
will be required to enter the key’s passphrase):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg2 --import key-filename.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the key is imported, its &lt;em&gt;trust level&lt;/em&gt; will have to be set to &lt;em&gt;ultimate&lt;/em&gt;. Use the command
&lt;code&gt;gpg2 --edit-key KEY-ID&lt;/code&gt; to enter the edit prompt. From there, type &lt;code&gt;trust&lt;/code&gt; and hit &lt;code&gt;ENTER&lt;/code&gt;. The
various levels will be shown on screen. Enter and confirm &lt;code&gt;5&lt;/code&gt;, to select ‘Ultimate’. Lastly, use
&lt;code&gt;quit&lt;/code&gt; to leave the gpg key editor.&lt;/p&gt;
&lt;h3&gt;Pull Pass Repo&lt;/h3&gt;
&lt;p&gt;With the keys configured, the last step is to pull down the password-store to the new machine. If using git, this can be done with &lt;code&gt;pass git clone&lt;/code&gt;… but if I’m being honest, I usually just do a normal &lt;code&gt;git clone&lt;/code&gt;, and then move
the folder to &lt;code&gt;~/.password-store/&lt;/code&gt;. If not using git, just copy the store’s directory and files to
the new machine. The important thing is that the store can be found at &lt;code&gt;~/.password-store&lt;/code&gt; (by
default, this of course can be changed using &lt;code&gt;pass init&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s about it. As I previously stated, I’ve been loving pass for years, and I
don’t plan to be switching off of it any time soon. At this point, if there is
something I want to improve with my password setup… I’m sure the community
has already figured out how to do it with pass!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Designing my new Ryzen Workstation</title>
    <link href="https://ryan.himmelwright.net/post/charmeleon-desktop-design/" />
    <updated>2018-11-26T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/charmeleon-desktop-design/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/Uf5NVegzif-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/Uf5NVegzif-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Red Hat Tower, Raleigh NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the past few years (particularly after moving to the &lt;a href=&quot;https://ryan.himmelwright.net/post/my-t470/&quot;&gt;T470&lt;/a&gt;
as my main device), I have been anticipating what my next desktop
workstation build will look like. For a period of time, I was convinced that
I would build a &lt;a href=&quot;https://www.techspot.com/review/1155-affordable-dual-xeon-pc/&quot;&gt;muti-socket, used xeon
build&lt;/a&gt;. Then, in 2017
AMD released their &lt;a href=&quot;https://en.wikipedia.org/wiki/Ryzen&quot;&gt;Ryzen&lt;/a&gt;
series CPUs…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why a Desktop?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/15x6EDn3Fy-1170.webp 1170w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/15x6EDn3Fy-1170.jpeg&quot; alt=&quot;Full system monitor on Laptop&quot; width=&quot;1170&quot; height=&quot;781&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My T470 has been a little tight on resources.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/&quot;&gt;My&lt;/a&gt; &lt;a href=&quot;https://ryan.himmelwright.net/post/my-t470/&quot;&gt;laptops&lt;/a&gt; &lt;a href=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/&quot;&gt;have&lt;/a&gt;
served the majority of my needs these last few years, but I have been feeling
a bit restricted when trying to do more demanding workloads.
While I still side with a “&lt;em&gt;use low-powered, portable computers, and remote
into more powerful ones when needed&lt;/em&gt;” mentality, I enjoy having &lt;em&gt;one&lt;/em&gt; of those
power computers being my physical, main workstation desktop.&lt;/p&gt;
&lt;h2&gt;Workstation Goals&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/ryzen-logo.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/6ZMBp-zYEQ-700.webp 700w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/6ZMBp-zYEQ-700.jpeg&quot; alt=&quot;Ryzen Logo&quot; style=&quot;max-width: 100%;&quot; width=&quot;700&quot; height=&quot;440&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;caption&quot;&gt;My T470 has been a little tight on resources.&lt;/div&gt;
&lt;p&gt;While building a new workstation may be fun, I have particular goals I want it
to achieve. Additionally, it seems like 98% of online build guides are for
designing &lt;em&gt;gaming&lt;/em&gt; desktops… which is not exactly what I want. So, the
features I wanted in &lt;em&gt;my build&lt;/em&gt;, likely deviate from much of the
&lt;em&gt;suggested&lt;/em&gt; guidance found online, which tends to focus entirely on gaming
performance (more FPS). Here is the breakdown of what I was looking for:&lt;/p&gt;
&lt;h3&gt;More Cores&lt;/h3&gt;
&lt;p&gt;I tend to have workloads that benefit from multiple cores (and threads). For
example, I run a bunch of virtual machines, often at the same time. While my
server’s 4 core Xeon handles day to day VMs just fine, it is nice to have a
workstation with several cores to spin up a virtualized cluster across. Beyond
VMs, I compile code, compress/decompress file packages, and occasionally encode
audio/video files. These are all tasks that love having many threads at their
disposal.&lt;/p&gt;
&lt;h3&gt;Lots of (&lt;em&gt;fast&lt;/em&gt;) Ram&lt;/h3&gt;
&lt;p&gt;When running VMs, the amount of available RAM is usually more limiting than the
number of cpu cores. For example, when running 6 VM’s, my server’s 4-core Xeon
will typically be 15-35% utilized, whereas it’s 20GB of RAM is nearly full.  If
I want more cores, I should supplement it with lots of RAM. In addition… I
use electron apps (Slack, Spotify,  etc.) and modern web browsers… so yea… more RAM.&lt;/p&gt;
&lt;p&gt;Being a Ryzen build, the RAM also needs to be &lt;em&gt;fast&lt;/em&gt;.  More than other CPU
architectures, Ryzen CPUs actually &lt;a href=&quot;https://www.youtube.com/watch?v=g0SDr3EHHmY&quot;&gt;&lt;em&gt;run better&lt;/em&gt; with faster
RAM&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Fast Storage&lt;/h3&gt;
&lt;p&gt;This main goal of this build is for it to be a multi-tasking beast. If I am
running several VMs and a bunch of applications, utilizing a stack of &lt;em&gt;fast&lt;/em&gt; RAM
to move data around… I need good &lt;em&gt;fast&lt;/em&gt; storage with &lt;a href=&quot;https://www.youtube.com/watch?v=Bh_f0uof7Jw&amp;amp;feature=youtu.be&amp;amp;t=359&quot;&gt;MOAR
IOPs&lt;/a&gt; to
support of these operations, which will all be competing for disk access. While I
wouldn’t mind having space to eventually throw in a pair 3.5&amp;quot; rust drives to configure as a
ZFS data pool, the goal for the initial &lt;em&gt;primary&lt;/em&gt; drive of this
machine is the most IOPs I can &lt;em&gt;afford&lt;/em&gt;. So, I ideally &lt;em&gt;want&lt;/em&gt; a m.2 NVMe SSD,
but &lt;em&gt;need&lt;/em&gt; a normal 2.5&amp;quot; SATA SSD at the very least.&lt;/p&gt;
&lt;h3&gt;&lt;em&gt;Some&lt;/em&gt; Gaming Ability&lt;/h3&gt;
&lt;p&gt;While it doesn’t have to be a &lt;em&gt;gaming machine&lt;/em&gt;… I would like to be able to
play the &lt;em&gt;occasional&lt;/em&gt; game. I don’t mind playing games with lower graphic
settings on my laptops, but I &lt;em&gt;do mind&lt;/em&gt; that even with the lower settings, the
CPUs nearly max out, which spikes up the temperature.
Increased graphics performance would also better support &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/&quot;&gt;my large
monitor&lt;/a&gt;.  A discrete graphics card would be nice, ideally
an AMD one due to their recent work on improving their open source drivers.&lt;/p&gt;
&lt;h3&gt;Ability to Upgrade&lt;/h3&gt;
&lt;p&gt;Most importantly, I want this computer to be a solid &lt;em&gt;base&lt;/em&gt; that I can upgrade
over the next several years. Upgradability is hands-down &lt;em&gt;the main&lt;/em&gt; advantage
of switching to a desktop over a laptop for my main rig. I want this build
designed so that as my needs grow, I can easily max out the RAM, add storage,
or upgrade the CPU and/or GPU.&lt;/p&gt;
&lt;h2&gt;Final Build Selections&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/qiQT6NyGYq-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/charmeleon-desktop-design/qiQT6NyGYq-1200.jpeg&quot; alt=&quot;charmeleon&quot; width=&quot;1200&quot; height=&quot;731&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After spending weeks researching all the parts on the market and building
probably 50 theoretical builds, I eventually settled on &lt;a href=&quot;https://pcpartpicker.com/user/himmelwr/saved/MhbcYJ&quot;&gt;my final part
list&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Name: Charmeleon&lt;/h3&gt;
&lt;p&gt;Since my last desktop build was named “Charmander”, I figured “Charmeleon” was
fitting. Also, I’m then free to rename it to “Charzard” in the future if I &lt;em&gt;evolve&lt;/em&gt;
the build to a next-generation Ryzen 7…&lt;/p&gt;
&lt;h3&gt;CPU: &lt;a href=&quot;https://en.wikichip.org/wiki/amd/ryzen_5/2600&quot;&gt;Ryzen 5 2600&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While I initially wanted a Ryzen 2700, it pushed the build out of budget.
Coming from a 4 core/4 thread system… it’s likely that I don’t &lt;em&gt;need&lt;/em&gt; 8
cores/16 threads right off the bat. Honestly, 4 cores/8 threads would be a
noticeable improvement from what I’ve been using… which is why I first
considered the Ryzen 5 2400g. I planned to start with the 2400g and upgrade to
the 2700 and a GPU once I had more cash. However, &lt;a href=&quot;https://forum.level1techs.com/t/finalizing-an-upgradable-ryzen-linux-build/134670/2&quot;&gt;someone on the level1tech
forums&lt;/a&gt;
reminded me that the 2600 was the same price as the 2400g, and I could get that
plus a GPU for not much more cost (considering I don’t need an expensive GPU).
After thinking it over, I realized that was my best option, and should
&lt;em&gt;actually&lt;/em&gt; fit my needs for quite awhile.&lt;/p&gt;
&lt;h3&gt;Motherboard: &lt;a href=&quot;https://www.msi.com/Motherboard/B450-TOMAHAWK&quot;&gt;MSI B450 Tomahawk&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I had selected the MSI B450 Tomahawk after &lt;a href=&quot;https://www.youtube.com/watch?v=lxtrHDJUMt4&quot;&gt;Wendell reviewed
it&lt;/a&gt;. &lt;a href=&quot;https://www.youtube.com/watch?v=MWGzmbbimPw&amp;amp;feature=youtu.be&amp;amp;t=145&quot;&gt;Several
other&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=MMJoLyrWa7E&amp;amp;feature=youtu.be&amp;amp;t=1260&quot;&gt;reviews
confirmed&lt;/a&gt; that it was a solid board, especially for the price. When I was
considering the 2400g, I had to switch to the &lt;a href=&quot;https://www.newegg.com/Product/Product.aspx?Item=N82E16813144188&quot;&gt;MSI B450 Gaming Pro Carbon
AC&lt;/a&gt;, because
the Tomahawk didn’t have a display output that the integrated graphics could
properly use with &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/&quot;&gt;my UHD monitor&lt;/a&gt;’s resolution (I need a
display port). When I switched to the 2600, I was able to go back to the
TOMAHAWK, and funnel the cost savings towards getting my GPU.&lt;/p&gt;
&lt;h3&gt;GPU: &lt;a href=&quot;https://www.techpowerup.com/gpu-specs/sapphire-pulse-rx-560-oc-4-gb.b4627&quot;&gt;Sapphire 1024 4GB PULSE Radeon RX 560&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As discussed above, I don’t need a killer GPU as I don’t play very demanding
games. However, I did want something that could perform at least a &lt;em&gt;little
bit&lt;/em&gt;. So, &lt;a href=&quot;https://www.youtube.com/watch?v=237L9UGQtGk&amp;amp;t=422s&quot;&gt;I picked the rx560 over the
rx550&lt;/a&gt;. At first I planned
to get something like a rx580, which I still might eventually do… but
I don’t think I will &lt;em&gt;need&lt;/em&gt; to for a long time.&lt;/p&gt;
&lt;h3&gt;RAM: G.Skill - Trident Z 32GB (2 x 16GB) DDR4-3200Mhz RAM&lt;/h3&gt;
&lt;p&gt;I wanted to start out with 32GB of RAM, with the ability to easily upgrade to
64GB down the road. This meant that I needed a 2x16GB pack. Additionally, I
needed the RAM to be &lt;em&gt;fast&lt;/em&gt;, but still not &lt;em&gt;overpriced&lt;/em&gt;. Some research showed
that at least right now, &lt;a href=&quot;https://youtu.be/lxtrHDJUMt4?t=752&quot;&gt;3200Mhz seems to be the sweet spot, especially for my
particular motherboard&lt;/a&gt;. I originally
picked another kit, but the reviews for it weren’t great, so I switched to the
G.Skill kit for only a few dollars more, but with much better reviews. I also
briefly searched for kits with tighter timings, but they were &lt;em&gt;way&lt;/em&gt; more expensive and not worth it.&lt;/p&gt;
&lt;h3&gt;Case: &lt;a href=&quot;https://www.fractal-design.com/home/product/cases/meshify/meshify-c&quot;&gt;Fractal Design - Meshify C Dark TG ATX Mid Tower&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I wanted a case that was sturdy, sleek, but not too flashy or expensive. During my
search, the Fractal Design cases kept catching eye. Eventually, I found the
Meshify C TG Dark, which seemed to be universally loved by reviewers. Additionally,
it appeared to have &lt;a href=&quot;https://www.fractal-design.com/home/product/cases/meshify/meshify-c&quot;&gt;good
thermals&lt;/a&gt;,
which I liked. This was my easiest part to pick.&lt;/p&gt;
&lt;h3&gt;PSU: EVGA - &lt;a href=&quot;https://web.archive.org/web/20181102223931/https://www.evga.com/Products/Product.aspx?pn=120-G1-0650-XR&quot;&gt;SuperNOVA G4 650W 80+ Gold, fully modular ATX PSU&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For the power supply, I wanted one that was at least gold rated, and fully
modular. So I searched for the highest rated PSUs fitting those requirements…
and SuperNova popped up all over the place. At first I picked a 550w model, but
upped to the 650w at the last minute because it was about the same price and
gives me a little more room as I upgrade.&lt;/p&gt;
&lt;h3&gt;Storage: One of my Samsung 850 Evo SSDs (temp)&lt;/h3&gt;
&lt;p&gt;Lastly, storage. For now I am just going to use a spare 250GB Samsung 850 Evo SSD
I had in my test laptop. I plan to eventually upgrade to a &lt;a href=&quot;https://www.samsung.com/semiconductor/minisite/ssd/product/consumer/970evo/&quot;&gt;500GB Samsung 970 Evo
m.2 NVMe
drive&lt;/a&gt;,
but the 850 will work for now.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So after months of planing and research, my desktop parts have all been
ordered. Hopefully, it meets the goals I set out to solve, and I will be able
to upgrade it over the next few years&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Reset Plasma 5 Monitor Configuration</title>
    <link href="https://ryan.himmelwright.net/post/reset-plasma5-monitor-config/" />
    <updated>2018-11-14T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/reset-plasma5-monitor-config/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/reset-plasma5-monitor-config/1tPX-jiRIE-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/reset-plasma5-monitor-config/1tPX-jiRIE-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;North Carolina Arboretum (Pisgah National Forest), Asheville, NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Extending a laptop’s screen to an external display can often be a nightmare.
Sometimes, it might be best to just hit the reset button and configure from
scratch. With KDE Plasma 5, you can. Here’s how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Issue&lt;/h2&gt;
&lt;p&gt;While I love having a &lt;a href=&quot;https://ryan.himmelwright.net/post/my-t470/&quot;&gt;dock-able laptop&lt;/a&gt; and &lt;a href=&quot;https://ryan.himmelwright.net/post/new-lgud4379b&quot;&gt;my 43&amp;quot; 4k LG
monitor&lt;/a&gt;, getting them configured and agreeing with each other
can sometimes be a headache. Luckily, KDE Plasma 5 (the desktop environment
I’ve been using for awhile now) is fantastic and remembers previous display
configurations.&lt;/p&gt;
&lt;p&gt;However, when I am playing around with resolutions or multiple inputs,
sometimes I set something that doesn’t work… and it is remembered, causing
more problems.&lt;/p&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;Luckily, there is an easy fix: just delete the saved profile, and
re-setup the display again. However, I often forget these very simple steps…
hence, this post (and to help anyone else that needs it, of course :P).&lt;/p&gt;
&lt;h3&gt;Delete old profile&lt;/h3&gt;
&lt;p&gt;The first step is to delete the plasma profile that saves the screen settings.
Note, this may remove the profiles for &lt;em&gt;all&lt;/em&gt; previously configured displays,
but I am okay with it for &lt;em&gt;my&lt;/em&gt; use case.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; ~/.local.share
&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-rf&lt;/span&gt; kscreen&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Extend monitors when docked&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/reset-plasma5-monitor-config/dvEsewBPuW-1059.webp 1059w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/reset-plasma5-monitor-config/dvEsewBPuW-1059.jpeg&quot; alt=&quot;The KDE Plasma 5 extend display GUI&quot; width=&quot;1059&quot; height=&quot;238&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The KDE Plasma 5 extend display GUI.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After deleting the config, dock or reconnect the laptop to the display. A GUI
with extension layout options should pop up. If it does, select the preferred
layout option.&lt;/p&gt;
&lt;h3&gt;Close laptop lid (don’t disable)&lt;/h3&gt;
&lt;p&gt;After extending the display, I usually try to make the monitor the primary
display and disable the laptop screen. This doesn’t seem to work (It causes my
monitor to blank). However, I have learned that if I simply &lt;em&gt;close my laptop
lid&lt;/em&gt; once it is connected… it does both of those things for me. I always
forget this… but now it’s documented!&lt;/p&gt;
&lt;h3&gt;(Optional) Reset kscreen&lt;/h3&gt;
&lt;p&gt;After flipping around the displays so much, plasma shell may have gotten a bit
frazzled. If so, just reset it using the following command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kquitapp5 plasmashell &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; kstart plasmashell&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I like to execute this in krunner (alt-F2) so that it runs in the background,
and because I can usually still get to krunner, even if something goes terribly
wrong with plasmashell during the process.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it. While this post is rather short (for once), hopefully it will be useful. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Monitor Selection - My New LG-UD4329b</title>
    <link href="https://ryan.himmelwright.net/post/new-lgud4379b/" />
    <updated>2018-11-12T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/new-lgud4379b/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/97K4HMb3th-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/97K4HMb3th-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After a decade of using several combinations of &lt;em&gt;dual&lt;/em&gt; monitors, I
have decided to upgrade to using a single, but &lt;em&gt;better&lt;/em&gt;,
monitor. After two years of debating, I have finally made my choice.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Monitor History&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/SE4zdkuuT7-640.webp 640w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/SE4zdkuuT7-640.jpeg&quot; alt=&quot;Pre College monitors&quot; width=&quot;640&quot; height=&quot;480&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My monitor setup right before moving to college.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Half way through high school, I purchased my first laptop (it also happens to
be the first computer I installed Linux on. I had a special edition model, but it was
very similar to &lt;a href=&quot;https://www.cnet.com/products/hp-pavilion-dv6000/specs/&quot;&gt;this
one&lt;/a&gt;). The laptop was
replacing an old standard E-machines desktop, so I kept the spare monitor to
use with the laptop. From that point on, I &lt;em&gt;usually&lt;/em&gt; had two screens to look
at, even if one was just my laptop’s screen at the far end of my desk, with an
&lt;a href=&quot;https://en.wikipedia.org/wiki/AIM_(software)&quot;&gt;AIM&lt;/a&gt; window open in it. Before
moving to college, I built a desktop computer with a new monitor (picture
above), and utilized the old one as a secondary display.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/XngeNerAxP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/XngeNerAxP-1200.jpeg&quot; alt=&quot;College Desk&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My desk during my senior year of collage.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Throughout college, as I started to explore Linux and programming more,
I began to experiment with using monitors in portrait orientations.
Eventually, I purchased a second 23.6&amp;quot; 1080p monitor so they would
match when paired. This gave me much more vertical real estate, which was
useful when writing code, essays, or lab reports.&lt;/p&gt;
&lt;h2&gt;Why go from two, to one?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/sEiY6J2Ifk-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/sEiY6J2Ifk-1200.jpeg&quot; alt=&quot;MA Desk&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;When using two monitors, I always had to have a primary and secondary.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While I like having the &lt;em&gt;screen real estate&lt;/em&gt; of two monitors, I was never able
to optimally use them because of the bezels between the monitors.  While
working, I would naturally shift over and use one monitor as a &lt;em&gt;primary&lt;/em&gt;
display, and the other as a &lt;em&gt;secondary&lt;/em&gt; display. This was okay for some tasks,
but it usually just meant that I just used the primary monitor, and &lt;em&gt;half&lt;/em&gt; of
the secondary one, because it was too hard to see the far end of the screen.&lt;/p&gt;
&lt;p&gt;This issue was &lt;em&gt;slightly&lt;/em&gt; better when using my displays in portrait mode, but
that wasn’t always ideal when connecting to remote machines, or viewing wide
content (videos, for example). It also didn’t solve the main issue. When
looking at the screens in a natural position (head centered, looking strait
forward)… I still saw two pieces of black plastic. I wanted to see my
&lt;em&gt;primary&lt;/em&gt; window directly in front of me, with some auxiliary space on either
side for documentation and terminal windows. I wanted a wide screen display.&lt;/p&gt;
&lt;h2&gt;Ultra-wide or &lt;em&gt;Large&lt;/em&gt; 16:9 UHD?&lt;/h2&gt;
&lt;p&gt;Luckily, the once &lt;em&gt;extremely&lt;/em&gt; expensive ultra-wide and large UHD monitors have
started to come down in price. After following the market for a few years, I
narrowed my decision to my favorite choice in each category: The &lt;a href=&quot;https://www.dell.com/en-us/shop/dell-ultrasharp-34-curved-ultrawide-monitor-u3415w/apd/210-adtr/monitors-monitor-accessories&quot;&gt;Dell
u3415w&lt;/a&gt;
and the &lt;a href=&quot;https://www.lg.com/us/monitors/lg-43UD79-B-4k-uhd-led-monitor&quot;&gt;LG
ud4379b&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/oaCwIAwVhY-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/oaCwIAwVhY-1200.jpeg&quot; alt=&quot;Dell u3415w&quot; width=&quot;1200&quot; height=&quot;721&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Dell u3415w 34&quot; 3440x1440p Ultra Wide Monitor.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The Dell u3415w is the slightly older (compared to the u34&lt;strong&gt;17&lt;/strong&gt;w) 34&amp;quot;
ultra-wide monitor offering from Dell. It has a 3440x1440, 60 Hz,
slightly-curved, IPS panel sitting on a solid and adjustable monitor stand. I
preferred it over other ultra-wides because it could be found for a reasonable
price, and had a professional build quality.&lt;/p&gt;
&lt;p&gt;Based on my reasoning explained in the section above, an ultra-wide (1440p
ones… not 1080p) would fit my mostly programmer use-case very well. Being a
single monitor, I could position it directly in front of me, with my main
code/content centered. The 3440px wide resolution would also allow enough
horizontal space to have a full web page of documentation, and several terminal
windows open on either side of the main display. Lastly, the 1440px vertical
resolution would be an adequate improvement from my &lt;em&gt;“starting to feel tight”&lt;/em&gt;,
1080p monitors.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/dOg7M6XP-9-1001.webp 1001w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/dOg7M6XP-9-1001.jpeg&quot; alt=&quot;Dell u3415w&quot; width=&quot;1001&quot; height=&quot;728&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The LG ud4379b 42.5&quot; 3440x2160p Monitor.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The massive LG ud4379b is a 42.5&amp;quot; 3840x2160px IPS display. At this size and
resolution, it is roughly the equivalent of having four 21&amp;quot; 1920x1080px
monitors aligned in a 2x2 grid… except with 0 bezel between them. Compared to
other 40&amp;quot;+ 4K displays, I preferred this one because once again, it was
reasonably priced, had great input options, and also seemed to have a half
decent panel. Reviewers consistently said it had vibrant colors and minimal
back light bleed.&lt;/p&gt;
&lt;p&gt;Being single monitor, I could easily sit myself in front of it, and have my
main content centered, just like with the ultra-wide. Being so large, I could
still have auxiliary windows opened on either side. One advantage the ud4379
had over the ultra-wide however, was that being a massive 16:9 screen, it could
easily be divided into 4 1080p screens. In fact, this model supports spitting
the screen into four separate displays, each taking a different input (more on
that later…). While the ultra-wides often support splitting and
picture-in-picture, it seems more natural on the 43&amp;quot; monitor.&lt;/p&gt;
&lt;h2&gt;Deciding on the 43&amp;quot; UHD&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/QYlMuOIbrv-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/QYlMuOIbrv-1200.jpeg&quot; alt=&quot;Shipped Box&quot; width=&quot;1200&quot; height=&quot;765&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The LG ud4379 can support multiple inputs at a time to the single display.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I went back and forth on which monitor I wanted several times. Eventually, I
realized the long term versatility of the LG monitor was just something I could
not ignore. Being able to use it as one massive display that I could fit
several full VM windows in, or even as 1,2, or 4 discrete monitors makes it
extremely useful for me. I tend to be playing with more than one computer or VM
at a time, and it is VERY nice to have them all fit on a single display. I
&lt;em&gt;was&lt;/em&gt; worried it would be a bit &lt;em&gt;too&lt;/em&gt; large and look stupid on my desk,
especially compared to how slick the ultra-wide would look. However, in the
end, practicality won out.&lt;/p&gt;
&lt;h2&gt;Getting the UD4379b&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/NQU-ZOHuDw-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/NQU-ZOHuDw-1200.jpeg&quot; alt=&quot;Shipped Box&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Monitor arrived a few days later perfectly safe, despite the beat up box.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After deciding that we would likely order the monitor as my birthday gift, my
wife and I started watching the price a few weeks before. One day, we noticed
the price had dropped $200… so we ordered it. The price soon went back up, so
I’m glad we snatched it when we did!&lt;/p&gt;
&lt;p&gt;It came a few days latter in a massive box that I happily carted up to our
apartment. The box was a bit beat up, but after opening it, all the components
were fine.&lt;/p&gt;
&lt;h2&gt;What I like&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/n6mE87ivwi-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/n6mE87ivwi-1200.jpeg&quot; alt=&quot;Monitor Working&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I can have my main content front and center, with other windows on the sides.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Screen Resolution&lt;/strong&gt; - The most obvious pro using a monitor at this
size and resolution… is the screen real estate. As explained above, it’s
twice the space of my old, dual 1080p monitor setup. With this space, I can
easily have several source code files, documentation, a few terminals, a
chat window, and even a video opened on my single screen as I work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IPS Display&lt;/strong&gt; - Many people might assume that the driving factor
pushing me to &lt;em&gt;finally&lt;/em&gt; update my monitors was my desire for more screen
resolution. While that was &lt;em&gt;part&lt;/em&gt; of my goal… what truly motivated me was
a desire to switch to an IPS display on my main setup after almost a year
using my T470’s IPS display (which by the way, is &lt;em&gt;not a good IPS panel&lt;/em&gt;).
As someone who spends a large percentage of their computing time in dark
terminals and IDEs, an IPS panel really does help text and colors to pop,
because the darks look &lt;em&gt;so&lt;/em&gt; much better. It turns out, this panel is
actually quite good for it’s price, and I am very happy with it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/0LizXBg9ZE-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/0LizXBg9ZE-1200.jpeg&quot; alt=&quot;Monitor Shading&quot; width=&quot;1200&quot; height=&quot;745&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;When working, I can look straight at a single display and still have enough room.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Single Monitor, No (center) Bezels&lt;/strong&gt; - As explained earlier in this
post, I like that this is is a &lt;em&gt;single&lt;/em&gt; monitor. This means that I no
longer need to twist my head back and forth, or stare at bezels. I can
looks straight ahead and see my main content, with “auxiliary” windows
view-able in my periphery.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility, multiple inputs&lt;/strong&gt; - Lastly, I enjoy the multiple
inputs and excellent PIP system of this monitor. Combined with something
like &lt;a href=&quot;https://symless.com/synergy&quot;&gt;Synergy&lt;/a&gt;, I can easily use this one
display with several devices at a time. This is great when working with
more than one device, or potentially watching content on something like a
chromecast stick, while still working on the computer. It really is the
advantage of having 4 monitors, but in a single package.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speakers&lt;/strong&gt; - They aren’t &lt;em&gt;amazing&lt;/em&gt;, and I don’t use them too often
too often, but for “monitor” speakers, they really aren’t half bad. They’re
what you’d expect from a TV. However, this means that if I were to use
something like a media stick, the built-in speaker is acceptable enough for
such a device.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What I don’t like&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/1QT5XjZnKK-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/1QT5XjZnKK-1200.jpeg&quot; alt=&quot;Monitor Shading&quot; width=&quot;1200&quot; height=&quot;414&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Viewed from the center, the monitor edges have shadowing.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Edge Shadowing&lt;/strong&gt; - The one issue that myself
&lt;a href=&quot;https://youtu.be/yA6hL3inqRc?t=250&quot;&gt;and&lt;/a&gt;
&lt;a href=&quot;https://youtu.be/3BSaPRHrA_U?t=667&quot;&gt;others&lt;/a&gt; have experienced with
this monitor is a sort of “edge shadowing”. This is apparently
something that is common on monitors this massive, and it’s
really hard to engineer around. If I sit back far enough from the
display, you can’t see it anymore. (But I don’t normally sit like that)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Input Switching Lag?&lt;/strong&gt; - While this is more of a critique of
&lt;em&gt;monitors&lt;/em&gt; in general, one thing I wish was a bit better was the input
switching. Having so many inputs, it can get confusing remembering which one is
attached to what device, or trying to quickly configure a 2x2 multi-input
layout. Having to &lt;em&gt;wait&lt;/em&gt; for each device to connect when flipping through all
the inputs makes it that much more frustrating (and disappointing when it’s the
wrong input…).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Corners Out of View&lt;/strong&gt; - Being such a large display, one issue I
sometimes have is that the outside corners are out of my field of view.
I mostly notice this when I occasionally miss notification pop-ups
that I &lt;em&gt;want&lt;/em&gt; to see. On the flip-side, notifications don’t seem to
distract me as much now when I’m trying to focus :P.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/30V3jk9Kz4-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-lgud4379b/30V3jk9Kz4-1200.jpeg&quot; alt=&quot;Monitor Shading&quot; width=&quot;1200&quot; height=&quot;650&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New monitor on my desk.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In conclusion, I an &lt;em&gt;very&lt;/em&gt; happy with my new monitor. It fits all of the
criteria I had, and works for my use-case perfectly. While I wouldn’t
recommend it for a pure gaming machine, it might be one of the best deal
&lt;em&gt;productivity&lt;/em&gt; monitors out there.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating a CI/CD &#39;Draft&#39; Website with Jenkins (and Hugo)</title>
    <link href="https://ryan.himmelwright.net/post/draft-website-with-jenkins/" />
    <updated>2018-08-15T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/draft-website-with-jenkins/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/TQFMDhdhst-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/TQFMDhdhst-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;American Tobacco Campus, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The last few months I have been working more with the open source
automation server, &lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt;. While digging into
it, I have been thinking of ways to improve my home build
environment. One idea, was to utilize Jenkins to automatically build
and deploy a “draft” website, so I can stage new posts/website change
on my home network, before publishing it to the “production”
website. Here is how that idea was Instantiated…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;My Website&lt;/h2&gt;
&lt;p&gt;I have previously &lt;a href=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/&quot;&gt;described&lt;/a&gt; how my
website is &lt;a href=&quot;https://ryan.himmelwright.net/post/website-switched-to-hugo/&quot;&gt;currently generated&lt;/a&gt;, using
the &lt;a href=&quot;https://gohugo.io&quot;&gt;Hugo&lt;/a&gt; static website generator. To organize
this system, I have two git repos: One that consists of all the hugo
source files (where I write content), and one that contains the
generated static website (that gets deployed to my web host).&lt;/p&gt;
&lt;p&gt;When writing a post, I use &lt;code&gt;hugo server -D -F&lt;/code&gt; to live view the page
in my browser. However, I occasionally want to view the state of all
the &lt;em&gt;committed code&lt;/em&gt; in the &lt;em&gt;repo&lt;/em&gt;, to see what the site would look
like if I decided to publish a post. So, I created a “drafts” website,
which shows the current state of my website’s &lt;em&gt;source&lt;/em&gt; repo (including
draft and future posts). If I want to check how a post looks on my
phone, or any other device, I can just open up the draft website after
pushing my changes.&lt;/p&gt;
&lt;h2&gt;Jenkins&lt;/h2&gt;
&lt;p&gt;I had previously created a dedicated Jenkins server on my home network
(Mr. Mime), using a CentOS 7 VM hosted on my home server. However, any
Jenkins setup should work for this project (including a &lt;a href=&quot;https://hub.docker.com/_/jenkins/&quot;&gt;docker
container&lt;/a&gt;). To get started, checkout the &lt;a href=&quot;https://jenkins.io/download/&quot;&gt;Jenkins
Website&lt;/a&gt;, and be sure to take advantage
of the &lt;a href=&quot;https://jenkins.io/doc/&quot;&gt;the documentation&lt;/a&gt; for help.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: just make sure hugo is installed on the Jenkins server, as we need
it to generate the website.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;GitHub Integration&lt;/h2&gt;
&lt;h3&gt;Jenkins Service&lt;/h3&gt;
&lt;p&gt;My website repo is hosted on GitHub, so we need to configure it to
work with our Jenkins server. To do that, go to the project’s GitHub
page, and navigate through &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Integrations &amp;amp;
services&lt;/strong&gt;. Click the &lt;strong&gt;Add service&lt;/strong&gt; drop-down and select &lt;em&gt;Jenkins
(Git Plugin)&lt;/em&gt;. Next, add the Jenkins server url (assuming the server
is accessible from the internet. If not, hosting the Jenkins server on
something like &lt;a href=&quot;http://digitalocean.com&quot;&gt;Digital Ocean&lt;/a&gt; might be an
easy solution). Lastly, make sure the &lt;strong&gt;Activate&lt;/strong&gt; box is selected,
and click the &lt;strong&gt;Add Service&lt;/strong&gt; button.&lt;/p&gt;
&lt;h3&gt;SSH Keys&lt;/h3&gt;
&lt;p&gt;While on the project’s GitHub page, make sure that the Jenkins
server’s ssh keys are added to the project. To add them, navigate to
the &lt;strong&gt;Deploy Keys&lt;/strong&gt; page (under the project’s &lt;strong&gt;Settings&lt;/strong&gt; tab). Then
select &lt;strong&gt;Add deploy key&lt;/strong&gt;, and add the public key.&lt;/p&gt;
&lt;h2&gt;A Nginx Server&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/9yH2LO3DMH-804.webp 804w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/9yH2LO3DMH-804.jpeg&quot; alt=&quot;Default nginx page&quot; width=&quot;804&quot; height=&quot;444&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The default nginx page.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;With Jenkins ready, let’s quickly setup the web server before
configuring the Jenkins project. Any web server will do (it just needs
to serve the generated &lt;em&gt;static&lt;/em&gt; website content). I used
&lt;a href=&quot;https://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; in for setup. After installing, make
sure it is running. To install and check the status of nginx on an
Ubuntu System:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;## Install&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; nginx

&lt;span class=&quot;token comment&quot;&gt;## Check it is running&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl status nginx

&lt;span class=&quot;token comment&quot;&gt;## Optional: Ensure it is enabled to start up after reboots&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the web server running, we need to know &lt;em&gt;where&lt;/em&gt; the website files
need to go. Nginx will by default serve content at
&lt;code&gt;/user/share/nginx/html/&lt;/code&gt;, so remember that location for later…&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Don’t forget to add the &lt;code&gt;jenkins&lt;/code&gt; user’s ssh key from the
jenkins server to the &lt;code&gt;authorized_keys&lt;/code&gt; file of the nginx server. This
will make file transfers easier when setting up the jenkins project.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Configuring a Project&lt;/h2&gt;
&lt;p&gt;Let’s configure our Jenkins project! Log in to the Jenkins
server and click the &lt;strong&gt;New Item&lt;/strong&gt; option on the left side bar. Enter a
name for the project, select the &lt;strong&gt;Freestyle Project&lt;/strong&gt; option, and hit
&lt;strong&gt;OK&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/BbsY9HGr0y-960.webp 960w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/BbsY9HGr0y-960.jpeg&quot; alt=&quot;The Project&#39;s General Configuration Section&quot; width=&quot;960&quot; height=&quot;377&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Project&#39;s General Configuration Section.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;General&lt;/strong&gt; section of the configuration screen, optionally
write a description about the project. Next, select the “&lt;em&gt;GitHub
Project&lt;/em&gt;” check-box, and add the GitHub repo’s url into the &lt;em&gt;Project
url&lt;/em&gt; text box.&lt;/p&gt;
&lt;h3&gt;Source Control&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/At-Gw3BmuW-1010.webp 1010w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/draft-website-with-jenkins/At-Gw3BmuW-1010.jpeg&quot; alt=&quot;Setting Credentials&quot; width=&quot;1010&quot; height=&quot;554&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Setting Credentials.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;Source Code Management&lt;/strong&gt; section of the configuration, select
the &lt;em&gt;Git&lt;/em&gt; option. Then, enter the repo’s url for the &lt;em&gt;Repository URL&lt;/em&gt;
box (I did the ssh url). For &lt;em&gt;Credentials&lt;/em&gt;, select &lt;em&gt;Add&lt;/em&gt; to configure a
new credential. Select &lt;em&gt;SSH Username with private key&lt;/em&gt; for &lt;em&gt;Kind&lt;/em&gt;,
use &lt;code&gt;jenkins&lt;/code&gt; for the &lt;em&gt;Username&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;More source control options can be configured, but this should be the
minimum setup required. &lt;em&gt;Again, for this to work public keys for the
&lt;code&gt;jenkins&lt;/code&gt; user on the jenkins server must be generated, and added as a
deployment key on GitHub.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Build Trigger&lt;/h3&gt;
&lt;p&gt;Under the &lt;strong&gt;Build Triggers&lt;/strong&gt; section, select &lt;em&gt;Poll SCM&lt;/em&gt;. Without
adding any schedule parameters, it will trigger each time a new commit
is detected. This is what we want.&lt;/p&gt;
&lt;h3&gt;Build Step&lt;/h3&gt;
&lt;p&gt;In the &lt;strong&gt;Build&lt;/strong&gt; section, click &lt;strong&gt;Add build step&lt;/strong&gt;, and select
&lt;strong&gt;Execute shell&lt;/strong&gt;. This is where we can add the shell commands to
build the website with hugo. Add the following command to the box
(don’t forget to change the url):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;hugo &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-F&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://10.1.1.77&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; public&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-D&lt;/code&gt; flag tells hugo to include all draft posts, while the &lt;code&gt;-F&lt;/code&gt; flag
has it include all posts with a future date. The &lt;code&gt;-b&lt;/code&gt; flag sets the
url for the generated website. This should the be url or IP address of
the nginx server setup previously. Lastly, the &lt;code&gt;-d&lt;/code&gt; flag tells hugo to
output the generated static website to the &lt;code&gt;public&lt;/code&gt; directory. This
will be useful to know when deploying the build.&lt;/p&gt;
&lt;h3&gt;Deploy to Webserver&lt;/h3&gt;
&lt;p&gt;For deployment, I used rsync to copy the build files to the nginx
web server. This step will be another shell command, so I’ve actually
added it as another “build” step. Add another &lt;strong&gt;Execute shell&lt;/strong&gt; and
paste the following command inside the text box (again, changing the url):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;rsync&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$WORKSPACE&lt;/span&gt;/public/&quot;&lt;/span&gt; ryan@10.1.1.77:/usr/share/nginx/html/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used the Jenkins &lt;code&gt;$WORKSPACE&lt;/code&gt; variable to get the location of the
build, and was able to append the &lt;code&gt;public&lt;/code&gt; directory to that, since we
defined it with the &lt;code&gt;-d&lt;/code&gt; flag in the hugo build step above. This will
copy the generated website, to the web server.&lt;/p&gt;
&lt;p&gt;Hit &lt;strong&gt;Save&lt;/strong&gt;, and test it out by clicking the &lt;strong&gt;Build Now&lt;/strong&gt; link on the
left. If the build is successful, check the nginx website to see if
the website was deployed!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: If it doesn’t work, double check all permissions and
credentials between accounts and servers.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Better Yet… Pipelines&lt;/h2&gt;
&lt;p&gt;What’s better than using Jenkins for automated “draft website”
deployments?  Using a &lt;a href=&quot;https://jenkins.io/doc/book/pipeline/&quot;&gt;Jenkins
Pipeline&lt;/a&gt;. A Pipeline allows
the jenkins project steps to be defined in a &lt;em&gt;Jenkinsfile&lt;/em&gt; that, among
other benefits, can be source controlled. In fact, by default a
Jenkins pipeline searches for the &lt;code&gt;Jenkinsfile&lt;/code&gt; right in the root
directory of a project’s git repo.&lt;/p&gt;
&lt;p&gt;While a pipeline and
&lt;a href=&quot;https://jenkins.io/doc/book/pipeline/jenkinsfile/&quot;&gt;Jenkinsfile&lt;/a&gt; might
be a bit more confusing to &lt;em&gt;learn&lt;/em&gt; how to setup, it is well worth it. For
example, the following Jenkinsfile can be used to do essentially what
we setup in the previous steps:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;pipeline &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    agent &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	label &lt;span class=&quot;token string&quot;&gt;&#39;mr-mime&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    stages &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	stage &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	    steps&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		sh &lt;span class=&quot;token string&quot;&gt;&#39;hugo -D -F -b &quot;http://10.1.1.77&quot;&#39;&lt;/span&gt;
	    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	stage &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;deploy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	    steps&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		sh &lt;span class=&quot;token string&quot;&gt;&#39;rsync -r &quot;$WORKSPACE/public/&quot; ryan@ponyta:/usr/share/nginx/html/&#39;&lt;/span&gt;
	    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’m not going to cover pipelines in &lt;em&gt;this&lt;/em&gt; post. However, I do
encourage readers to check them out.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it. While I currently host my website using &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub
pages&lt;/a&gt;, if I ever self-host it again, I
will definitely automate publishing it using Jenkins as well. This has
been a &lt;em&gt;very&lt;/em&gt; basic example of what Jenkins can be used for, but I
have found it rather useful when working on the content of this
website. There is so much more it can do. Have fun!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Nginx as a Reverse Proxy to Forward Sub-Domains</title>
    <link href="https://ryan.himmelwright.net/post/nginx-subdomain-reverse-proxy/" />
    <updated>2018-07-03T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/nginx-subdomain-reverse-proxy/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/nginx-subdomain-reverse-proxy/n51e1Khlqn-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/nginx-subdomain-reverse-proxy/n51e1Khlqn-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;573&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Farmers Market, Foster Street, Durham NC&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last month, Rebecca and I moved to Durham, North Carolina. As a
result, I’ve had to re-setup our home network. In years past, I setup
a virtual machine running &lt;a href=&quot;https://www.nginx.com/&quot;&gt;Nginx&lt;/a&gt; as a
reverse-proxy (Tangela), and I decided to do that again on the new
network. While it is a simple process, it is one that other people often
ask me about. So, this time… I’m taking notes!&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;The purpose of this reverse proxy is to direct outside traffic to the
appropriate host internally, by looking at the sub-domain of the URL
request. For example, I may have servers for both
&lt;code&gt;website.himmelwright.net&lt;/code&gt; and &lt;code&gt;dashboard.himmelwright.net&lt;/code&gt; running
internally on my network, but they will have the same public IP. Using
nginx, I can point all of my web traffic to &lt;em&gt;tangela&lt;/em&gt;, my
reverse-proxy. If tangela sees that the incoming request is for
&lt;code&gt;website.himmelwright.net&lt;/code&gt;, it will forward that traffic to the
website server. On the other hand, if the request is for
&lt;code&gt;dashboard.himmelwright.net&lt;/code&gt;, it will direct it to the dashboard
server. A reverse-proxy expands what can be accomplished on a single network,
and is a cleaner (and possibly safer) method than doing everything through
port-forwarding.&lt;/p&gt;
&lt;h2&gt;Setup Server&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/nginx-subdomain-reverse-proxy/sQUUaGYwW_-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/nginx-subdomain-reverse-proxy/sQUUaGYwW_-1200.jpeg&quot; alt=&quot;the pokemon tangela&quot; width=&quot;1200&quot; height=&quot;600&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Fitting my home network naming convention,
my reverse-proxy VM is named &#39;Tangela&#39;.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To get started, configure a server/container/droplet that will host nginx. I’m
using a CentOS 7 minimal install VM on Nintales (my home server). I don’t have
a bunch of traffic (well, I &lt;em&gt;shouldn’t&lt;/em&gt;), so I’m just giving it 1 core and
512MB RAM.&lt;/p&gt;
&lt;h2&gt;Setup Nginx&lt;/h2&gt;
&lt;p&gt;Next, it’s time to setup and install Nginx.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: the rest of the post will be focused on using a CentOS 7 base,
since that is what I am using. Adjust for your distro accordingly.&lt;/p&gt;
&lt;p&gt;Add the nginx repo, and install it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rpm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Uvh&lt;/span&gt; http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; yum &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tell the firewall to allow http traffic:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd &lt;span class=&quot;token parameter variable&quot;&gt;--permanent&lt;/span&gt; --add-service&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;http
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl reload firewalld&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start and enable nginx:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl start nginx
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configure Nginx&lt;/h2&gt;
&lt;p&gt;Examine the config file, just to check that everything looks alright.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;vim&lt;/span&gt; /etc/nginx/nginx.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Specifically, we want to ensure that the following line exists before
proceeding:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;include /etc/nginx/conf.d/*.conf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That line basically states that any *.conf file inside the &lt;code&gt;/etc/nginx/conf.d/&lt;/code&gt;
directory will also be loaded and used by nginx. This allows us to add our own configuration file in the next step.&lt;/p&gt;
&lt;h2&gt;Configure Proxy&lt;/h2&gt;
&lt;p&gt;I created a &lt;code&gt;reverse-proxies.config&lt;/code&gt; (it can be named anything with a
&lt;code&gt;.config&lt;/code&gt; extension) file in &lt;code&gt;/etc/nginx/conf.d/&lt;/code&gt; to contain all of
the reverse proxy definitions. These are just server block
entries. For example:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;server &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        listen &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        server_name website.himmelwright.net&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        location / &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                proxy_pass      http://192.168.1.198:80&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

server &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        listen &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        server_name dashboard.himmelwright.net&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        location / &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                proxy_pass      http://192.168.1.200:8080&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart nginx for the changes to take effect:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl restart nginx&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Side Note:&lt;/h3&gt;
&lt;p&gt;For some applications, you may need to add the URL to the &lt;code&gt;/etc/hosts&lt;/code&gt;
file, and use that for nginx. I have experienced this in the past with
&lt;a href=&quot;https://about.gitlab.com/&quot;&gt;Gitlab&lt;/a&gt;. For example:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/etc/hosts
---
&lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.1.201  git.himmelwright.net&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then in the config file:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/etc/nginx/config.d/reverse-proxies.config
---
server &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        listen &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        server_name git.himmelwright.net&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        location / &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                proxy_pass      http://git.himmelwright.net:80&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SELinux Fixes&lt;/h2&gt;
&lt;p&gt;At this point, you may be done. However, I was having issues getting
nginx to forward some of my ports… until I remembered that I was on
CentOS and it may be an issue with SELinux. It was.&lt;/p&gt;
&lt;p&gt;One “&lt;em&gt;fix&lt;/em&gt;” is to just disable SELinux. A &lt;em&gt;better&lt;/em&gt; solution is to use
setools to allow the http connections:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; yum &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; setools
setsebool &lt;span class=&quot;token parameter variable&quot;&gt;-P&lt;/span&gt; httpd_can_network_connect &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Besides having to configure your router to forward http traffic to the
server, that is really it. This is a real basic configuration, but it
has worked well for me over the years. If I start doing something more
complex, I may provide an update. Until then, enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Organizing my Emacs config with Org-Babel</title>
    <link href="https://ryan.himmelwright.net/post/org-babel-setup/" />
    <updated>2018-06-27T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/org-babel-setup/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/org-babel-setup/N0IuHHU359-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/org-babel-setup/N0IuHHU359-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;567&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Golden, CO&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://ryan.himmelwright.net/post/emacs-update-evil-usepackage/&quot;&gt;previous post&lt;/a&gt;, I completely
redid my emacs configuration from scratch, building it around the use
of evil mode and use-package. As I was wrapping up, I learned of yet
&lt;em&gt;another&lt;/em&gt; emacs package that wil forever change how I maintain my
emacs
configuration… &lt;a href=&quot;https://orgmode.org/worg/org-contrib/babel/intro.html&quot;&gt;org-babel&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Org Babel&lt;/h2&gt;
&lt;p&gt;Org-babel is an emacs package, that lets take an
&lt;a href=&quot;https://orgmode.org/&quot;&gt;org-mode&lt;/a&gt; file, and evaluates all of the &lt;a href=&quot;https://orgmode.org/org.html#Literal-examples&quot;&gt;code
blocks&lt;/a&gt; contained
within it. This means I can write an annotated org file, filled with
&lt;code&gt;emacs-lisp&lt;/code&gt; code blocks, and export just the emacs-lisp code. After
testing it out, I realized that the main emacs lisp file I use is… my
&lt;code&gt;.emacs&lt;/code&gt; file…&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Installing babel couldn’t be simpler. If you are running Emacs version
24 or higher, and a current version of Org-mode, Babel is already
available by default. In order to generate an &lt;code&gt;.emacs&lt;/code&gt; config from an
&lt;code&gt;.org&lt;/code&gt; file, we need to first setup a &lt;code&gt;config.org&lt;/code&gt; file, and then tell
&lt;code&gt;.emacs&lt;/code&gt; to load it with org-babel.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;http://config.org&quot;&gt;config.org&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First, start by creating an &lt;code&gt;.org&lt;/code&gt; file to use for all of the
configuration content. This can be named anything, and exist anywhere,
but I like to keep mine in the emacs section of my &lt;a href=&quot;https://github.com/himmAllRight/dotfiles&quot;&gt;dotfiles
repo&lt;/a&gt;, named
&lt;code&gt;config.org&lt;/code&gt;. This file functions like any other org file, so I added
a small header section at the top… because why not?&lt;/p&gt;
&lt;pre class=&quot;language-org&quot;&gt;&lt;code class=&quot;language-org&quot;&gt;#+TITLE: My Emacs Configuration
#+AUTHOR: Ryan Himmelwright
#+EMAIL: ryan@himmelwright.net
#+OPTIONS: num:nil&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, I broke down my file by creating org headings for the
different groups of the configuration. For example, I started with the
headings &lt;code&gt;Repos &amp;amp; Core Packages&lt;/code&gt;, &lt;code&gt;Core Setup&lt;/code&gt;, &lt;code&gt;Evil Mode&lt;/code&gt;, &lt;code&gt;Ivy&lt;/code&gt;,
&lt;code&gt;Writing&lt;/code&gt;, &lt;code&gt;Development&lt;/code&gt;… etc.&lt;/p&gt;
&lt;p&gt;Note: In org-mode, use “*” to create headings. Each contiguous “*”
corresponds to the equivalent HTML heading level. Ex: &lt;code&gt;*&lt;/code&gt; == &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;,
&lt;code&gt;**&lt;/code&gt; == &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;p&gt;I started transferring the emacs-lisp code from my previous &lt;code&gt;.emacs&lt;/code&gt;
file, to under each heading in &lt;code&gt;config.org&lt;/code&gt;. As I transferred text, I
transformed the comments that described each code snippet into normal
org text, and wrapped the &lt;code&gt;emacs-lisp&lt;/code&gt; inside an org code block.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/org-babel-setup/lgzJiwdZqE-572.webp 572w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/org-babel-setup/lgzJiwdZqE-572.jpeg&quot; alt=&quot;Example snippet of my org-babel config.org file&quot; width=&quot;572&quot; height=&quot;395&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Example snippet of my org-babel config.org file.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To create a code block, use the &lt;code&gt;#+BEGIN_SRC emacs-lisp&lt;/code&gt; and
&lt;code&gt;#+END_SRC&lt;/code&gt; org tags to encapsulate the &lt;code&gt;emacs-lisp&lt;/code&gt; code. Continue to
do this until all of the desired emacs-lisp code is contained inside
org code blocks.&lt;/p&gt;
&lt;h3&gt;Side Note: Easy Org Code Blocks&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/org-babel-setup/Z5TC77T4Fa-431.webp 431w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/org-babel-setup/Z5TC77T4Fa-431.jpeg&quot; alt=&quot;Example snippet of my org-babel config.org file.&quot; width=&quot;431&quot; height=&quot;220&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Code block can be easily created with `&lt;s` and=&quot;&quot; `TAB`.&lt;=&quot;&quot; figcaption=&quot;&quot;&gt;
      &lt;/s`&gt;&lt;/figcaption&gt;&lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In org-mode, you can write &lt;code&gt;&amp;lt;s&lt;/code&gt;, then hit &lt;code&gt;TAB&lt;/code&gt; and it will expand
into the &lt;code&gt;BEGIN_SRC&lt;/code&gt; and &lt;code&gt;END_SRC&lt;/code&gt; tags. Just don’t forget to add
&lt;code&gt;emacs-lisp&lt;/code&gt; to the &lt;code&gt;BEGIN_SRC&lt;/code&gt; tag!&lt;/p&gt;
&lt;h3&gt;.emacs&lt;/h3&gt;
&lt;p&gt;For the contents of &lt;code&gt;.emacs&lt;/code&gt;, call &lt;code&gt;package-initialize&lt;/code&gt; and then have
&lt;code&gt;org-babel&lt;/code&gt; load the &lt;code&gt;config.org&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;language-emacs-lisp&quot;&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;package-initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;org-babel-load-file&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~/dotfiles/emacs/config.org&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. Assuming the &lt;code&gt;config.org&lt;/code&gt; is complete, emacs should now
initialize using the code snippets from &lt;em&gt;it&lt;/em&gt;. While these are the only
two &lt;em&gt;required&lt;/em&gt; lines, do note that emacs will still write the
&lt;code&gt;#&#39;custom-set-variables&lt;/code&gt; to the bottom of the &lt;code&gt;.emacs&lt;/code&gt; file. That’s
fine. If anything, it makes it easier to source control the &lt;em&gt;actual&lt;/em&gt;
configuration file, since emacs isn’t constantly adding to it,
changing the diff.&lt;/p&gt;
&lt;h2&gt;What I like&lt;/h2&gt;
&lt;h3&gt;Organized&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/org-babel-setup/LuuPS_sgD9-736.webp 736w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/org-babel-setup/LuuPS_sgD9-736.jpeg&quot; alt=&quot;Example snippet of expanding org headers to easily find code.&quot; width=&quot;736&quot; height=&quot;535&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Expanding org headers to easily find code.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Org mode’s hierarchical structure inherently organizes the content of
files. Using it for my emacs configuration is no different. I can use
org headers to easily break down my file into sections and
sub-sections, which I can then expand and collapse as needed. For
example, my &lt;code&gt;config.org&lt;/code&gt; is currently over 500 lines long, but with
all the headers collapsed, it displays in less than 20. From there I
can just expand to the section I need.&lt;/p&gt;
&lt;h3&gt;Maintainable&lt;/h3&gt;
&lt;p&gt;Being so organized, the &lt;code&gt;config.org&lt;/code&gt; file is very easy to maintain. If
I want to edit a setting, I can just search through the headers for
the section, and then edit the code block and/or text. If I want to
add a new item, I can just insert a new header, add the code block I
want, and maybe some explanatory text. Done. Most importantly, the
structure helps prevent it from turning into an &lt;em&gt;in-production
“scratch code”&lt;/em&gt; file…&lt;/p&gt;
&lt;h3&gt;Easy to Read&lt;/h3&gt;
&lt;p&gt;It should go without saying that the organization and maintainability
of org-mode configurations make them extremely &lt;em&gt;readable&lt;/em&gt;. Not only is
the source code easier to read, but org files can be
&lt;a href=&quot;https://orgmode.org/manual/Exporting.html&quot;&gt;exported&lt;/a&gt; to all sorts of
outputs (HTML, LaTeX, OpenDocument, etc). Combined with a style-sheet,
these outputs can look quite professional.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/org-babel-setup/4xzdMdoPvj-941.webp 941w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/org-babel-setup/4xzdMdoPvj-941.jpeg&quot; alt=&quot;Example of how Github renders org files as a markdown&quot; width=&quot;941&quot; height=&quot;591&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Github renders org files as a markdown.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Beyond normal &lt;code&gt;org&lt;/code&gt; exporting, &lt;a href=&quot;http://www.github.com&quot;&gt;Github&lt;/a&gt; does
something awesome… they &lt;em&gt;render&lt;/em&gt; &lt;code&gt;org&lt;/code&gt; files as a known markup
language on their website! This means that if you click an &lt;code&gt;*.org&lt;/code&gt;
file on Gihub’s web interface, it will display a rendered version of
the content, instead of defaulting to the raw org text.&lt;/p&gt;
&lt;h2&gt;Speaking of Github…&lt;/h2&gt;
&lt;p&gt;While I have always maintained my emacs configuration in &lt;a href=&quot;https://github.com/himmAllRight/dotfiles&quot;&gt;my dotfiles
repo&lt;/a&gt;, org-babel has helped
me step up my game. My current &lt;a href=&quot;https://ryan.himmelwright.net/post/new-dotfiles/&quot;&gt;dotfile
system&lt;/a&gt; has all of my
emacs files in a separate &lt;code&gt;emacs&lt;/code&gt; folder. On Github, each directory in
a repo can contain a &lt;code&gt;README&lt;/code&gt; file (or… a symlink to &lt;em&gt;another&lt;/em&gt;
file…)  to be displayed below the list of files.&lt;/p&gt;
&lt;h2&gt;My Maintained Config&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/org-babel-setup/VJhFefXVTJ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/org-babel-setup/VJhFefXVTJ-1200.jpeg&quot; alt=&quot;Example of how Github renders org files as a markdown&quot; width=&quot;1200&quot; height=&quot;695&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Github renders org files as a
markdown.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the emacs section of my dotfiles, I have created a symlink,
&lt;code&gt;README.org&lt;/code&gt;, to my &lt;code&gt;config.org&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; config.org README.org&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Github recognizes this, so my &lt;code&gt;README&lt;/code&gt; file “contains” whatever the
contents of &lt;code&gt;config.org&lt;/code&gt; are. Now, when one visits the &lt;a href=&quot;https://github.com/himmAllRight/dotfiles/tree/master/emacs&quot;&gt;emacs
section&lt;/a&gt;
of my dotfiles repo, an organized, annotated, and always up-to-date
version of my “&lt;code&gt;.emacs&lt;/code&gt;” file is displayed as the &lt;code&gt;README&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Github support was the cherry on top of a new emacs configuration
system I am already ecstatic about. I’ve been a fan of org-mode for
years, and org-babel is one more feature to add to the growing list of
reasons &lt;em&gt;why&lt;/em&gt; I love it. I highly recommend checking it out.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Emacs Config Redo - Evil &amp; Use-Package</title>
    <link href="https://ryan.himmelwright.net/post/emacs-update-evil-usepackage/" />
    <updated>2018-05-18T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/emacs-update-evil-usepackage/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/emacs-update-evil-usepackage/elNPTd2WEX-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/emacs-update-evil-usepackage/elNPTd2WEX-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Roger Williams Park, Providence RI&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After switching to &lt;a href=&quot;http://spacemacs.org&quot;&gt;Spacemacs&lt;/a&gt; for the last year
or two, it’s about time to back and pull together &lt;em&gt;my own&lt;/em&gt; emacs
configuration again. However, spacemacs has shown me several packages
that I want to incorporate into my new emacs setup. Rather than
resurrect and Frankenstein the changes into my old &lt;code&gt;.emacs&lt;/code&gt;
file… I’m starting from scratch.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/emacs-update-evil-usepackage/L76lvsptER-778.webp 778w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/emacs-update-evil-usepackage/L76lvsptER-778.jpeg&quot; alt=&quot;New Emacs Window&quot; width=&quot;778&quot; height=&quot;790&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A window of my New Emacs configuration.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Evil Mode&lt;/h2&gt;
&lt;h3&gt;History&lt;/h3&gt;
&lt;p&gt;The one emacs package that spacemacs &lt;em&gt;really&lt;/em&gt; got me addicted to was
&lt;a href=&quot;https://github.com/emacs-evil/evil&quot;&gt;Evil&lt;/a&gt;, which emulates the main
features of the &lt;a href=&quot;https://www.vim.org&quot;&gt;vim&lt;/a&gt; text editor. I &lt;em&gt;started&lt;/em&gt;
with emacs in college, but eventually switched to Vim which, became
the first power editor that I &lt;em&gt;really&lt;/em&gt; got into (I even bought a
&lt;a href=&quot;https://www.amazon.com/dp/059652983X/?tag=mh0b-20&amp;amp;hvadid=78271540595342&amp;amp;hvqmt=b&amp;amp;hvbmt=bb&amp;amp;hvdev=c&amp;amp;ref=pd_sl_y7m3vu93e_b&quot;&gt;book&lt;/a&gt;
to learn it better). I stuck with Vim until I became a professional
LISP developer, and the switch back to Emacs was impossible to ignore,
and obvious.&lt;/p&gt;
&lt;p&gt;Even after switching to back Emacs, I have still enjoyed using command
line applications like &lt;a href=&quot;https://cmus.github.io&quot;&gt;cmus&lt;/a&gt;,
&lt;a href=&quot;https://github.com/ranger/ranger&quot;&gt;ranger&lt;/a&gt;, and
&lt;a href=&quot;http://w3m.sourceforge.net&quot;&gt;w3m&lt;/a&gt;, many of which are influenced by Vim
and often incorporate similar keyboard commands (at least the &lt;code&gt;j&lt;/code&gt;,
&lt;code&gt;k&lt;/code&gt;, &lt;code&gt;l&lt;/code&gt;, &lt;code&gt;;&lt;/code&gt; navigation). While it isn’t ideal for everything (I
prefer to &lt;em&gt;write&lt;/em&gt;, but not necessarily &lt;em&gt;edit&lt;/em&gt; code with emacs-syle
navigation), I never lost my love for the homerow-centric, and
efficient vim-style movement commands.&lt;/p&gt;
&lt;h3&gt;Best of Both Worlds&lt;/h3&gt;
&lt;p&gt;For the last two years, &lt;a href=&quot;http://spacemacs.org/&quot;&gt;Spacemacs&lt;/a&gt;’s default
Vim configuration has provided the best of both worlds. It had all of
the Evil packages pre-configured and optimized, but I could simply hit
&lt;code&gt;Ctrl-z&lt;/code&gt;, and jump back to Emacs-mode.&lt;/p&gt;
&lt;p&gt;Spacemacs was easy to use and I enjoyed it, but it really started to
have stability issues on my Windows 10 work computer. I thought that
going back to a stock emacs configuration may tone down the
complexity, and increase stability. I started to build a &lt;em&gt;new&lt;/em&gt;
configuration based around &lt;code&gt;Evil&lt;/code&gt;. During setup, I was happily
surprised to learn that the &lt;code&gt;Ctrl-z&lt;/code&gt; option to switch back to
Emacs-mode was a default &lt;code&gt;Evil&lt;/code&gt; behavior… so I still have the best of
both worlds!&lt;/p&gt;
&lt;h3&gt;Starting Config&lt;/h3&gt;
&lt;p&gt;Here is my starting &lt;code&gt;Evil&lt;/code&gt; setup. I’ve nested other &lt;code&gt;use-packages&lt;/code&gt;
inside of it, so that if &lt;code&gt;evil&lt;/code&gt; is configured, the packages that
depend on it go ahead and configure themselves too. I’m sure this will
grow over time as I add missing packages, but so far, it seems to
provide all the core functionality I need.&lt;/p&gt;
&lt;pre class=&quot;language-lisp&quot;&gt;&lt;code class=&quot;language-lisp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;;; Evil Mode&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;use-package&lt;/span&gt; evil
  &lt;span class=&quot;token lisp-property property&quot;&gt;:ensure&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;
  &lt;span class=&quot;token lisp-property property&quot;&gt;:config&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;evil-mode&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;use-package&lt;/span&gt; evil-leader
    &lt;span class=&quot;token lisp-property property&quot;&gt;:ensure&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;
    &lt;span class=&quot;token lisp-property property&quot;&gt;:config&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;global-evil-leader-mode&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;evil-leader/set-leader&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;SPC&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;evil-leader/set-key&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;s s&quot;&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;swiper&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;d x w&quot;&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;delete-trailing-whitespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;use-package&lt;/span&gt; evil-surround
    &lt;span class=&quot;token lisp-property property&quot;&gt;:ensure&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;
    &lt;span class=&quot;token lisp-property property&quot;&gt;:config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;global-evil-surround-mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;use-package&lt;/span&gt; evil-indent-textobject
    &lt;span class=&quot;token lisp-property property&quot;&gt;:ensure&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;use-package&lt;/span&gt; evil-org
    &lt;span class=&quot;token lisp-property property&quot;&gt;:ensure&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;
    &lt;span class=&quot;token lisp-property property&quot;&gt;:config&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;evil-org-set-key-theme&lt;/span&gt;
	  &lt;span class=&quot;token punctuation&quot;&gt;&#39;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;textobjects&lt;/span&gt; insert navigation additional shift todo heading&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;add-hook&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;org-mode-hook&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token lambda&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token arguments&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;evil-org-mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;use-package&lt;/span&gt; powerline-evil
    &lt;span class=&quot;token lisp-property property&quot;&gt;:ensure&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;
    &lt;span class=&quot;token lisp-property property&quot;&gt;:config&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;powerline-evil-vim-color-theme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think it needs more key bindings over time…&lt;/p&gt;
&lt;h2&gt;Use Package&lt;/h2&gt;
&lt;p&gt;After my setting up an initial install of the Evil mode parts, I
discovered the amazing
&lt;a href=&quot;https://github.com/jwiegley/use-package&quot;&gt;use-package&lt;/a&gt;. I have come
across it before reading blog posts, but never actually tried
it. After realizing how well it worked, I immediately combed through
my &lt;code&gt;.emacs&lt;/code&gt; file, converting all of my &lt;code&gt;(require &#39;package-name)&lt;/code&gt; calls
to &lt;code&gt;use-package&lt;/code&gt; forms instead.&lt;/p&gt;
&lt;h3&gt;Setup&lt;/h3&gt;
&lt;p&gt;After ensuring the &lt;a href=&quot;http://melpa.org&quot;&gt;MELPA&lt;/a&gt; repos are added and
initialized:&lt;/p&gt;
&lt;pre class=&quot;language-lisp&quot;&gt;&lt;code class=&quot;language-lisp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;;; Add Melpa packages to Repos&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;add-to-list&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;package-archives&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;melpa&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://melpa.org/packages/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;package-initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;use-package&lt;/code&gt; package can be installed next. I wrapped the
install commands in an &lt;code&gt;unless&lt;/code&gt;, so that when my emacs loads, it
only installs &lt;code&gt;use-package&lt;/code&gt; if it is not already installed.&lt;/p&gt;
&lt;p&gt;Afterwards, make sure to &lt;code&gt;(require &#39;use-package)&lt;/code&gt;. It only needs to
happen during compile, so I have mine in an &lt;code&gt;(evail-when compile ...)&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-lisp&quot;&gt;&lt;code class=&quot;language-lisp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;package-installed-p&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;use-package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;package-refresh-contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;package-install&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;use-package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;eval-when-compile&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;use-package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I keep all of this at the top of my configuration, which works well
even when initializing a new computer.&lt;/p&gt;
&lt;h3&gt;Bye Bye emacs_init.el&lt;/h3&gt;
&lt;p&gt;When I last had my own custom emacs config, I had to also maintain an
&lt;code&gt;emacs_init.el&lt;/code&gt; file. This was an emacs-lisp script that used a
&lt;code&gt;mapcar&lt;/code&gt; to apply &lt;code&gt;#&#39;package-install&lt;/code&gt; across an ever-growing list of
emacs packages my configuration required. My goal was to have a
script, so that when configuring Emacs on a new system, I could just load
and evaluate the contents of &lt;code&gt;emacs_init.el&lt;/code&gt;, and everything required
for &lt;code&gt;.emacs&lt;/code&gt; would automatically install.&lt;/p&gt;
&lt;p&gt;The reality was that it never fully worked. There were always a few
packages that would error, or that I had forgotten to add the last time
I updated my &lt;code&gt;.emacs&lt;/code&gt; file. With &lt;code&gt;use-package&lt;/code&gt;, this might be an issue
of the past, as it allows me to combine my emacs init script &lt;em&gt;with&lt;/em&gt;
my configuration files.&lt;/p&gt;
&lt;h3&gt;Key Bindings&lt;/h3&gt;
&lt;p&gt;After converting all of my &lt;code&gt;(require &#39;PACKAGE-NAME)&lt;/code&gt; calls and related
expressions to filled &lt;code&gt;use-package&lt;/code&gt; wrappers, I learned about the &lt;code&gt;:bind&lt;/code&gt;
parameter. Instead of manually setting key binds with a &lt;code&gt;setq&lt;/code&gt;,
&lt;code&gt;:bind&lt;/code&gt; takes a list of dotted pairs and binds the function (defined
in the second spot of the pair), to the key sequence stated in the
first spot of the pair.&lt;/p&gt;
&lt;p&gt;For example, to setup my preferred &lt;code&gt;ispell&lt;/code&gt; key-bindings, I used the
following &lt;code&gt;:bind&lt;/code&gt; parameter in my &lt;code&gt;ispell&lt;/code&gt; &lt;code&gt;use-package&lt;/code&gt; call:&lt;/p&gt;
&lt;pre class=&quot;language-emacs-lisp&quot;&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;use-package&lt;/span&gt; ispell
  &lt;span class=&quot;token lisp-property property&quot;&gt;:ensure&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;t&lt;/span&gt;
  &lt;span class=&quot;token lisp-property property&quot;&gt;:bind&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;C-c w&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;ispell-word&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;C-c r&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token quoted-symbol variable symbol&quot;&gt;&#39;ispell-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps to keep the configuration a bit cleaner and organized. The
syntax is also straight-forward and easy to remember.&lt;/p&gt;
&lt;h2&gt;System Specific Load Files&lt;/h2&gt;
&lt;p&gt;Finally, I moved all my work-specific emacs configuration (setting up
Allegro Common Lisp, defining some helper functions… and anything
Windows specific) into it’s own &lt;code&gt;emacs-work.el&lt;/code&gt; file. With that in it’s
own separate file, I just needed to &lt;code&gt;load&lt;/code&gt; it from my main &lt;code&gt;.emacs&lt;/code&gt;
configuration. The only issue with that, is I only want it to load on
my &lt;em&gt;work computer&lt;/em&gt;. So, I wrapped it with a nice little handler:&lt;/p&gt;
&lt;pre class=&quot;language-emacs-lisp&quot;&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;string-equal&lt;/span&gt; system-name &lt;span class=&quot;token string&quot;&gt;&quot;LAFAYETTE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~/.emacs-work.el&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works because I don’t name my home computers with the same name
as my work machine. This little tweak worked so well, that I decided
to make another file, &lt;code&gt;emacs-linux.el&lt;/code&gt;, that I could dump my Linux
and/or home specific configuration into. I only load it when on a
GNU/Linux machine:&lt;/p&gt;
&lt;pre class=&quot;language-emacs-lisp&quot;&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;string-equal&lt;/span&gt; system-type &lt;span class=&quot;token string&quot;&gt;&quot;gnu/linux&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token car&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~/.emacs-linux.el&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve really enjoyed the ability to only load parts of my configuration
when a certain condition is met. Breaking down my configuration into
use-specific components seems like a good idea (like abstracting messy
code to smaller functions). Right now, I try to keep my configuration
file partitioned into sections, based on use-case (Writing,
Development, Appearance, etc). However, as I continue to fine-tune my
emacs setup, I think I might break those sections into actual &lt;em&gt;files&lt;/em&gt;,
and then &lt;code&gt;load&lt;/code&gt; them from the main config.&lt;/p&gt;
&lt;p&gt;The only issue I can see with that is that it can be confusing with
overlapping use-cases. However, I already have to deal with that in a
single configuration (which &lt;em&gt;section&lt;/em&gt; to put it in), and my process
of converting everything to &lt;code&gt;use-package&lt;/code&gt; has actually started to
minimize that issue. So it might work…&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Now that I have an “&lt;code&gt;Evil&lt;/code&gt;” Emacs setup configured, things should be
returning to business as usually. As I work, I sure there’ll be a
forgotten package here or there that needs to added. However, with
&lt;code&gt;use-package&lt;/code&gt;, that should be a piece of cake, and easy to maintain
from here on out.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>SQL Intro</title>
    <link href="https://ryan.himmelwright.net/post/sql-intro/" />
    <updated>2018-04-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/sql-intro/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sql-intro/mY3QmVyJX4-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sql-intro/mY3QmVyJX4-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;614&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Ross School of Business - University of Michigan, Ann Arbor, MI&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;SQL is one of those technologies that which to be everywhere, but yet… I
somehow haven’t &lt;em&gt;had&lt;/em&gt; to use it for anything in school or at work. I know, I
know… it’s quite a feat. Still, the pervasiveness of SQL-like databases argues
that I really &lt;em&gt;should&lt;/em&gt; learn it. So I am.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;The main purpose of this website is to document some of the tech stuff I do in
my free time. This serves three purposes: 1) Organizing my thoughts into a post
enhances my learning, 2) I can easily refer back to the post to refresh my
memory in the future, 3) It’s a good medium to share what I’ve learned with
others.&lt;/p&gt;
&lt;p&gt;This post is a prime example, as it didn’t start out as a &lt;em&gt;post&lt;/em&gt;, but a file of
the &lt;em&gt;notes&lt;/em&gt; I took as I learned SQL basics. As I progressed, I realized all of
the accumulating information should be cleaned up and posted. So here we are.
I’ve redone the examples, and turned my shorthand blurbs into sentences, but if
this post still seems a bit different than previous ones (no images, more code
snippets than words)… that’s because it’s my learning notes with makeup
heavily applied. Enjoy.&lt;/p&gt;
&lt;h2&gt;Install Setup&lt;/h2&gt;
&lt;p&gt;I first installed mysql, by going to the &lt;a href=&quot;https://dev.mysql.com/downloads&quot;&gt;mySQL download
page&lt;/a&gt; and getting the appropriate the version
for my VM (centos 7). This may differ based on Distro/DB.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I’m not going to cover any of the installation steps, as this post is more about
the SQL language, not setting up the DB. Besides, I did this in mysql, but in
the future I’ll likely look at using MariaDB anyway…&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I do however want to note this issue I encountered, so I don’t stumble over it
in the future. At first, I couldn’t get the &lt;code&gt;/usr/bin/mysql_secure_installation&lt;/code&gt;
command to run due to a permission denied error…&lt;/p&gt;
&lt;p&gt;Eventually, I learned that the first time mysql runs, it creates a temp password
in the log, located at &lt;code&gt;/var/log/mysqld.log&lt;/code&gt;. Using that password, I was able to
login.&lt;/p&gt;
&lt;h2&gt;DBs and Commands:&lt;/h2&gt;
&lt;p&gt;Conventionally, SQL commands are typed in all CAPS, and statements end with a
terminating &lt;code&gt;;&lt;/code&gt;. The rest of this post contains some SQL functions, with
examples of how they are used.&lt;/p&gt;
&lt;h3&gt;Show databases&lt;/h3&gt;
&lt;p&gt;To display all of the available databases, use the &lt;code&gt;SHOW DATABASES;&lt;/code&gt; command.
Note, some of the DBs displayed are ones generated for the database system. For
example, in the output below, &lt;em&gt;I&lt;/em&gt; only created &lt;code&gt;dbCustomerInfo&lt;/code&gt; and &lt;code&gt;dbTest&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| dbCustomerInfo     |
| dbTest             |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
6 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Create database&lt;/h3&gt;
&lt;p&gt;To create a new database, use the &lt;code&gt;CREATE DATABASE&lt;/code&gt; command. Simple.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| dbCustomerInfo     |
| dbTest             |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
6 rows in set (0.00 sec)

mysql&gt; CREATE DATABASE shotLivedDB;
Query OK, 1 row affected (0.00 sec)

mysql&gt; SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| dbCustomerInfo     |
| dbTest             |
| mysql              |
| performance_schema |
| shotLivedDB        |
| sys                |
+--------------------+
7 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Drop a database (delete):&lt;/h3&gt;
&lt;p&gt;As easy as it was to &lt;em&gt;create&lt;/em&gt; that database, deleting, or &lt;em&gt;dropping&lt;/em&gt; it is just
as effortless with the `DROP DATABASE command, so be careful!&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; DROP DATABASE shotLivedDB;
Query OK, 0 rows affected (0.00 sec)

mysql&gt; SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| dbCustomerInfo     |
| dbTest             |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
6 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Select DB&lt;/h3&gt;
&lt;p&gt;To select which db to use inside the mysql shell use… &lt;code&gt;USE&lt;/code&gt;. For this
tutorial, I created a database named &lt;code&gt;dbTest&lt;/code&gt; and selected it with &lt;code&gt;USE dbTest&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Tables&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Note: make sure to first select a DB to work with: &lt;code&gt;USE dbTest;&lt;/code&gt;…&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Show Tables&lt;/h3&gt;
&lt;p&gt;To show all of the tables in a database, use &lt;code&gt;SHOW TABLES;&lt;/code&gt;. For example:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SHOW TABLES;
+------------------+
| Tables_in_dbTest |
+------------------+
| tblUsers         |
+------------------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Create Table&lt;/h3&gt;
&lt;p&gt;To create a &lt;em&gt;new&lt;/em&gt; table, use &lt;code&gt;CREATE TABLE&lt;/code&gt;. The &lt;code&gt;CREATE TABLE&lt;/code&gt; function takes&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the name of the new table, and 2) a list of the table fields (with their
data types). For example, to create a user information table that contains a user’s
first name, last name, age, and state, as well as an identification number, the
following SQL command can be used:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; CREATE TABLE tblUsers (id int PRIMARY KEY AUTO_INCREMENT, firstname varchar(50),lastname varchar(50), age INT,state varchar(2));
Query OK, 0 rows affected (0.01 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;first name&lt;/code&gt;, &lt;code&gt;last name&lt;/code&gt;, and &lt;code&gt;state&lt;/code&gt; columns have a &lt;code&gt;varchar&lt;/code&gt; data type,
which are strings of various sizes (50 and 2 characters in this case). The &lt;code&gt;id&lt;/code&gt;
and &lt;code&gt;age&lt;/code&gt; columns have an &lt;code&gt;int&lt;/code&gt; data type. Notice that the &lt;code&gt;id&lt;/code&gt; column
has some other junk defined after the &lt;code&gt;int&lt;/code&gt; identifier…&lt;/p&gt;
&lt;h3&gt;Constraints and Fields&lt;/h3&gt;
&lt;p&gt;In addition to specifying data &lt;em&gt;type&lt;/em&gt;, other &lt;em&gt;constraints&lt;/em&gt; can be imposed on
columns when defining a new table. Constraints are used to limit the type of
data that goes into the table, and can be implemented at the column or table
level. To see the fields of a table, use the &lt;code&gt;SHOW FIELDS FROM tablename&lt;/code&gt;
command:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SHOW FIELDS FROM tblUsers;
+-----------|-------------|------|-----|---------|----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------|-------------|------|-----|---------|----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| firstname | varchar(50) | YES  |     | NULL    |                |
| lastname  | varchar(50) | YES  |     | NULL    |                |
| age       | int(11)     | YES  |     | NULL    |                |
| state     | varchar(2)  | YES  |     | NULL    |                |
+-----------|-------------|------|-----|---------|----------------+
5 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; column uses the &lt;code&gt;PRIMARY KEY&lt;/code&gt; and &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; constraints.
&lt;code&gt;PRIMARY_KEY&lt;/code&gt; is a combination of the &lt;code&gt;NOT NULL&lt;/code&gt; and &lt;code&gt;UNIQUE&lt;/code&gt; constraints,
meaning it ensures that all values in the column are unique and not &lt;code&gt;NULL&lt;/code&gt;. The
&lt;code&gt;AUTO_INCREMENT&lt;/code&gt; field generates an unique number that is automatically
incremented during each insert to the table.&lt;/p&gt;
&lt;h3&gt;INSERT INTO&lt;/h3&gt;
&lt;p&gt;To actually &lt;em&gt;add&lt;/em&gt; data to the table, the &lt;code&gt;INSERT INTO&lt;/code&gt; command is used. As an
example, to add some users to the table created in the previous step:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; INSERT INTO tblUsers (firstname,lastname,age,state) 
VALUES (&#39;Joe&#39;,&#39;Fry&#39;,32,&#39;RI&#39;);
Query OK, 1 row affected (0.01 sec)

mysql&gt; INSERT INTO tblUsers (firstname,lastname,age,state) 
VALUES (&#39;Emily&#39;,&#39;Flanders&#39;,22,&#39;CA&#39;);
Query OK, 1 row affected (0.01 sec)

mysql&gt; INSERT INTO tblUsers (firstname,lastname,age,state) 
VALUES (&#39;Tina&#39;,&#39;Oak&#39;,42,&#39;NC&#39;);
Query OK, 1 row affected (0.00 sec)

mysql&gt; INSERT INTO tblUsers (firstname,lastname,age,state) 
VALUES (&#39;Bob&#39;,&#39;Builder&#39;,51,&#39;MO&#39;);
Query OK, 1 row affected (0.01 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;tblUsers&lt;/code&gt; table should now contain the information of the 4 users added. To check
this, use &lt;code&gt;SELECT * FROM tblUsers;&lt;/code&gt; to select &lt;em&gt;everything&lt;/em&gt; from the &lt;code&gt;tblUsers&lt;/code&gt;
table:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  8 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;INSERT INTO Another Table&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;INSERT INTO&lt;/code&gt; command combined with &lt;code&gt;SELECT FROM&lt;/code&gt;, can insert contents of &lt;em&gt;one
table&lt;/em&gt; into &lt;em&gt;another&lt;/em&gt;. This technique can be quite useful, providing a simple way to
create quick backups.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; CREATE TABLE tblUsersBackup (id int PRIMARY KEY AUTO_INCREMENT, firstname varchar(50),lastname varchar(50), age INT,state varchar(2));
Query OK, 0 rows affected (0.02 sec)

mysql&gt; SELECT * FROM tblUsersBackup;
Empty set (0.00 sec)

mysql&gt; INSERT INTO tblUsersBackup SELECT * FROM tblUsers;
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql&gt; SELECT * FROM tblUsersBackup;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Drop (delete) Table&lt;/h3&gt;
&lt;p&gt;To delete a table, the &lt;code&gt;DROP&lt;/code&gt; command is used. For example, to delete the backup
table from above, issue &lt;code&gt;DROP TABLE tblUsersBackup;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; DROP TABLE tblUsersBackup;
Query OK, 0 rows affected (0.01 sec)

mysql&gt; SELECT * FROM tblUsersBackup;
ERROR 1146 (42S02): Table &#39;dbTest.tblUsersBackup&#39; doesn&#39;t exist&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE: Using &lt;code&gt;DROP&lt;/code&gt; commands will delete the entire structure (including the
schema), even if there is data in it. Be Careful!&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;SELECT&lt;/h3&gt;
&lt;p&gt;In addition to selecting &lt;em&gt;all&lt;/em&gt; of the data in a table using &lt;code&gt;SELECT *&lt;/code&gt;, The
&lt;code&gt;SELECT&lt;/code&gt; command can be utilized to select all sorts of combinations of the
data. Here are a few examples:&lt;/p&gt;
&lt;p&gt;– Select specific columns from the table:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT firstname,lastname FROM tblUsers;
+-----------|----------+
| firstname | lastname |
+-----------|----------+
| Joe       | Fry      |
| Emily     | Flanders |
| Tina      | Oak      |
| Bob       | Builder  |
+-----------|----------+
4 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;– Grab column(s), after matching in another:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT firstname,lastname FROM tblUsers WHERE state=&quot;RI&quot;;
+-----------|----------+
| firstname | lastname |
+-----------|----------+
| Joe       | Fry      |
+-----------|----------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition to searching using &lt;code&gt;=&lt;/code&gt; (which only grabs exact matches), other
operators, such as &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; (not equal), &lt;code&gt;BETWEEN&lt;/code&gt; (between an
inclusive range), and &lt;code&gt;LIKE&lt;/code&gt; (search for pattern) can be used with the &lt;code&gt;WHERE&lt;/code&gt;
clause. For example:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT firstname,lastname FROM tblUsers WHERE age&gt;=25;
+-----------|----------+
| firstname | lastname |
+-----------|----------+
| Joe       | Fry      |
| Tina      | Oak      |
| Bob       | Builder  |
+-----------|----------+
3 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT firstname,lastname FROM tblUsers WHERE age BETWEEN 30 AND 50;
+-----------|----------+
| firstname | lastname |
+-----------|----------+
| Joe       | Fry      |
| Tina      | Oak      |
+-----------|----------+
2 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ALTER&lt;/h3&gt;
&lt;p&gt;To change the table schema (add, drop or modify columns), the &lt;code&gt;ALTER&lt;/code&gt; command
is used along with &lt;code&gt;ADD&lt;/code&gt;, &lt;code&gt;DROP COLUMN&lt;/code&gt;, or &lt;code&gt;MODIFY COLUMN&lt;/code&gt; (respectively)
after the tablename. For example:&lt;/p&gt;
&lt;p&gt;– Add a &lt;code&gt;born&lt;/code&gt; column to the table&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)

mysql&gt; ALTER TABLE tblUsers ADD born year;
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------|------+
| id | firstname | lastname | age  | state | born |
+----|-----------|----------|------|-------|------+
|  5 | Joe       | Fry      |   32 | RI    | NULL |
|  6 | Emily     | Flanders |   22 | CA    | NULL |
|  7 | Tina      | Oak      |   42 | NC    | NULL |
|  9 | Bob       | Builder  |   51 | MO    | NULL |
+----|-----------|----------|------|-------|------+
4 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;– Change &lt;code&gt;born&lt;/code&gt; column from a &lt;code&gt;year&lt;/code&gt; data type to a &lt;code&gt;date&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; ALTER TABLE tblUsers MODIFY COLUMN born date;
Query OK, 4 rows affected (0.03 sec)
Records: 4  Duplicates: 0  Warnings: 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;– Drop the &lt;code&gt;born&lt;/code&gt; column from the table&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------|------+
| id | firstname | lastname | age  | state | born |
+----|-----------|----------|------|-------|------+
|  5 | Joe       | Fry      |   32 | RI    | NULL |
|  6 | Emily     | Flanders |   22 | CA    | NULL |
|  7 | Tina      | Oak      |   42 | NC    | NULL |
|  9 | Bob       | Builder  |   51 | MO    | NULL |
+----|-----------|----------|------|-------|------+
4 rows in set (0.00 sec)

mysql&gt; ALTER TABLE tblUsers DROP COLUMN born;
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NOTE: Depending on the type of SQL database being used, the &lt;code&gt;MODIFY&lt;/code&gt; command may
be known as &lt;code&gt;ALTER&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Delete&lt;/h3&gt;
&lt;p&gt;All the &lt;em&gt;data&lt;/em&gt; in a table can be removed using &lt;code&gt;DELETE * FROM tblName&lt;/code&gt;,
&lt;em&gt;without&lt;/em&gt; deleting the table schema. However, this is almost never used because
there are better commands to do that.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Specific&lt;/em&gt; items from the table can be removed using &lt;code&gt;DELETE FROM&lt;/code&gt;
commands:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsersBackup;                                                                    
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)

mysql&gt; DELETE FROM tblUsersBackup WHERE age&gt;35;
Query OK, 2 rows affected (0.01 sec)

mysql&gt; SELECT * FROM tblUsersBackup;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
+----|-----------|----------|------|-------+
2 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Multiple criteria can also be used to specify what to delete:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsersBackup;                                                                    
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+

mysql&gt; DELETE FROM tblUsersBackup WHERE age&gt;50 OR state=&quot;RI&quot;;
Query OK, 2 rows affected (0.00 sec)

mysql&gt; SELECT * FROM tblUsersBackup;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
+----|-----------|----------|------|-------+
2 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Indexes&lt;/h3&gt;
&lt;p&gt;An index speeds up the data retrieval time of a table. However, this comes at a
speed cost for &lt;em&gt;updating&lt;/em&gt; the table. So, it is usually a good idea to only index
columns or tables that are frequently searched on.&lt;/p&gt;
&lt;p&gt;To add an index, use &lt;code&gt;CREATE INDEX&lt;/code&gt;. &lt;em&gt;(Again, may differ depending on database
technology being used)&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SHOW FIELDS FROM tblUsers;
+-----------|-------------|------|-----|---------|----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------|-------------|------|-----|---------|----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| firstname | varchar(50) | YES  |     | NULL    |                |
| lastname  | varchar(50) | YES  |     | NULL    |                |
| age       | int(11)     | YES  |     | NULL    |                |
| state     | varchar(2)  | YES  |     | NULL    |                |
+-----------|-------------|------|-----|---------|----------------+
5 rows in set (0.00 sec)

mysql&gt; CREATE INDEX indexTblUsers ON tblUsers (id);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Drop Table&lt;/h3&gt;
&lt;p&gt;As we already know, you can drop a whole table with &lt;code&gt;DROP&lt;/code&gt;. However, like many
of the other commands, &lt;code&gt;DROP&lt;/code&gt; can be used with other items, like an index:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; ALTER TABLE tblUsers DROP INDEX indexTblUsers;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Truncate&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;truncate&lt;/code&gt; command will delete the data, but leave table schema the same…
again, still be careful because it &lt;em&gt;will&lt;/em&gt; delete &lt;em&gt;all the data&lt;/em&gt; in the table.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsersBackup;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
+----|-----------|----------|------|-------+
2 rows in set (0.00 sec)

mysql&gt; TRUNCATE TABLE tblUsersBackup;
Query OK, 0 rows affected (0.03 sec)

mysql&gt; SELECT * FROM tblUsersBackup;
Empty set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Auto Increment&lt;/h3&gt;
&lt;p&gt;An Integer value, that once assigned, auto increments by 1 everytime the table
is updated.&lt;/p&gt;
&lt;p&gt;You can auto increment a key, which is very useful for IDs in a table (as we saw
earlier).all the data&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SHOW FIELDS FROM tblPeople;
+-----------|-------------|------|-----|---------|----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------|-------------|------|-----|---------|----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| firstname | varchar(50) | YES  |     | NULL    |                |
| lastname  | varchar(50) | YES  |     | NULL    |                |
| age       | int(11)     | YES  |     | NULL    |                |
| state     | varchar(2)  | YES  |     | NULL    |                |
+-----------|-------------|------|-----|---------|----------------+
5 rows in set (0.00 sec)

mysql&gt; INSERT INTO tblPeople (firstname, lastname, age, state) 
VALUES (&#39;Josh&#39;, &#39;Rivers&#39;, 19, &quot;PA&quot;);
Query OK, 1 row affected (0.00 sec)

mysql&gt; INSERT INTO tblPeople (firstname, lastname, age, state) 
VALUES (&#39;Kim&#39;, &#39;Medows&#39;, 32, &quot;CO&quot;);
Query OK, 1 row affected (0.00 sec)

mysql&gt; SELECT * FROM  tblPeople;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  1 | Josh      | Rivers   |   19 | PA    |
|  2 | Kim       | Medows   |   32 | CO    |
+----|-----------|----------|------|-------+
2 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: The auto increment value can be manually set.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; INSERT INTO tblPeople (firstname, lastname, age, state) 
VALUES (&#39;Dan&#39;, &#39;Valley&#39;, 40, &quot;NH&quot;);
Query OK, 1 row affected (0.01 sec)

mysql&gt; SELECT * FROM  tblPeople;
+------|-----------|----------|------|-------+
| id   | firstname | lastname | age  | state |
+------|-----------|----------|------|-------+
|    1 | Josh      | Rivers   |   19 | PA    |
|    2 | Kim       | Medows   |   32 | CO    |
| 1000 | Dan       | Valley   |   40 | NH    |
+------|-----------|----------|------|-------+
3 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When setting the auto increment value manually, just be careful that it isn’t
set to one that will eventually overwrite another value in the table that &lt;em&gt;must&lt;/em&gt;
be unique, or it will error.&lt;/p&gt;
&lt;h2&gt;SQL Functions&lt;/h2&gt;
&lt;p&gt;SQL &lt;em&gt;functions&lt;/em&gt;, are SQL statements that don’t directly manipulate data, but can
be use to extract other useful information from the database and tables. They
are often used in conjunction with &lt;code&gt;SELECT&lt;/code&gt;. There are a bunch of base SQL
functions to use. Here is a sampling of a few:&lt;/p&gt;
&lt;h3&gt;COUNT&lt;/h3&gt;
&lt;p&gt;Shows the number of matched results returned. In this example, the number of
rows in &lt;code&gt;tblUsers&lt;/code&gt;, and then the number of users over the age of 35:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)

mysql&gt; SELECT COUNT(*) FROM tblUsers;
+----------+
| COUNT(*) |
+----------+
|        4 |
+----------+
1 row in set (0.00 sec)

mysql&gt; SELECT COUNT(*) FROM tblUsers WHERE age&gt;35;
+----------+
| COUNT(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Average and Sum Functions&lt;/h3&gt;
&lt;p&gt;To get an average of the number values in a column, use the &lt;code&gt;AVG&lt;/code&gt; function:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT AVG(age) from tblUsers;
+----------+
| AVG(age) |
+----------+
|  36.7500 |
+----------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get a total of a column’s number values, use the &lt;code&gt;SUM&lt;/code&gt; function:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT SUM(age) from tblUsers;
+----------+
| SUM(age) |
+----------+
|      147 |
+----------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note, that select statements using functions can still be combined:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT COUNT(*),AVG(age) from tblUsers;
+----------|----------+
| COUNT(*) | AVG(age) |
+----------|----------+
|        4 |  36.7500 |
+----------|----------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Like Operator&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;LIKE&lt;/code&gt; operator can be used for matching, with wildcards (&lt;code&gt;%&lt;/code&gt;). For example
in the following searches, &lt;code&gt;%S&lt;/code&gt; and &lt;code&gt;S%&lt;/code&gt; yield different results because the
first looks for last names which &lt;em&gt;end&lt;/em&gt; in an “s”, and the second grabs last
names which &lt;em&gt;start&lt;/em&gt; with “s”. The last example returns names which have “er”
anywhere in the last name.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note, &lt;code&gt;LIKE&lt;/code&gt; uses higher CPU usage. With larger data sets, try to use it on
columns which are indexed if possible.&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
| 10 | Zach      | Scout    |   27 | NV    |
+----|-----------|----------|------|-------+
5 rows in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsers WHERE lastname LIKE &#39;%S&#39;;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  6 | Emily     | Flanders |   22 | CA    |
+----|-----------|----------|------|-------+
1 row in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsers WHERE lastname LIKE &#39;S%&#39;;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
| 10 | Zach      | Scout    |   27 | NV    |
+----|-----------|----------|------|-------+
1 row in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsers WHERE lastname LIKE &#39;%er%&#39;;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  6 | Emily     | Flanders |   22 | CA    |
|  9 | Bob       | Builder  |   51 | MO    |
+----|-----------|----------|------|-------+
2 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Views&lt;/h2&gt;
&lt;p&gt;Views create a custom filter to display a set of the data. This can be useful
when defining several use cases of an application. For example, a view can
saved, and then just &lt;em&gt;updated&lt;/em&gt;, instead of &lt;em&gt;recalculated&lt;/em&gt;, when querying for
dash boards, and/or reports.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
|  9 | Bob       | Builder  |   51 | MO    |
| 10 | Zach      | Scout    |   27 | NV    |
+----|-----------|----------|------|-------+
5 rows in set (0.00 sec)

mysql&gt; CREATE VIEW myView AS SELECT COUNT(*), AVG(age), SUM(age) FROM tblUsers;
Query OK, 0 rows affected (0.01 sec)

mysql&gt; SELECT * FROM myView;
+----------|----------|----------+
| COUNT(*) | AVG(age) | SUM(age) |
+----------|----------|----------+
|        5 |  34.8000 |      174 |
+----------|----------|----------+
1 row in set (0.00 sec)

mysql&gt; DELETE FROM tblUsers WHERE firstname=&#39;Bob&#39;;
Query OK, 1 row affected (0.01 sec)

mysql&gt; SELECT * FROM myView;
+----------|----------|----------+
| COUNT(*) | AVG(age) | SUM(age) |
+----------|----------|----------+
|        4 |  30.7500 |      123 |
+----------|----------|----------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Joins&lt;/h2&gt;
&lt;h3&gt;Inner Join&lt;/h3&gt;
&lt;p&gt;An Inner join will return the selected rows from multiple tables, when there is
at least one match in each table. For example:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
| 10 | Zach      | Scout    |   27 | NV    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsersPts;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  1 | Joe       | Fry      | Red   |  20000 |
|  2 | Emily     | Flanders | Blue  |  17000 |
|  3 | Tina      | Oak      | Red   |  32800 |
|  4 | Bob       | Builder  | Green |  40100 |
+----|-----------|----------|-------|--------+
4 rows in set (0.00 sec)

mysql&gt; SELECT tblUsersPts.firstname, tblUsersPts.lastname, tblUsersPts.points
    -&gt; FROM tblUsersPts INNER JOIN tblUsers
    -&gt; ON tblUsers.lastname=tblUsersPts.lastname 
    -&gt; AND tblUsers.firstname=tblUsersPts.firstname;
+-----------|----------|--------+
| firstname | lastname | points |
+-----------|----------|--------+
| Joe       | Fry      |  20000 |
| Emily     | Flanders |  17000 |
| Tina      | Oak      |  32800 |
+-----------|----------|--------+
3 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Right Join&lt;/h3&gt;
&lt;p&gt;A &lt;em&gt;right&lt;/em&gt; join will return &lt;em&gt;everything&lt;/em&gt; in the right table, and any existing/matched
items on the left. Any non-matching data will display as null.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsers;
+----|-----------|----------|------|-------+
| id | firstname | lastname | age  | state |
+----|-----------|----------|------|-------+
|  5 | Joe       | Fry      |   32 | RI    |
|  6 | Emily     | Flanders |   22 | CA    |
|  7 | Tina      | Oak      |   42 | NC    |
| 10 | Zach      | Scout    |   27 | NV    |
+----|-----------|----------|------|-------+
4 rows in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsersPts;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  1 | Joe       | Fry      | Red   |  20000 |
|  2 | Emily     | Flanders | Blue  |  17000 |
|  3 | Tina      | Oak      | Red   |  32800 |
|  4 | Bob       | Builder  | Green |  40100 |
+----|-----------|----------|-------|--------+
4 rows in set (0.00 sec)

mysql&gt; SELECT firstname, lastname, points FROM tblUsersPts 
-&gt; RIGHT JOIN tblUsers ON tblUsers.lastname=tblUsersPts.lastname 
-&gt; AND tblUsers.firstname=tblUsersPts.firstname;
+-----------|----------|--------+
| firstname | lastname | points |
+-----------|----------|--------+
| Joe       | Fry      |  20000 |
| Emily     | Flanders |  17000 |
| Tina      | Oak      |  32800 |
| NULL      | NULL     |   NULL |
+-----------|----------|--------+
4 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Left Join&lt;/h3&gt;
&lt;p&gt;Basically the same as a &lt;code&gt;RIGHT&lt;/code&gt; join, but with the left table items all joining.&lt;/p&gt;
&lt;h3&gt;Full Join&lt;/h3&gt;
&lt;p&gt;A full join shows &lt;em&gt;all&lt;/em&gt; records from both the right and left table, regardless
of matching the relation records of either.&lt;/p&gt;
&lt;p&gt;NOTE: Full outer joins do not work on mysql, but would on postresql. It is
recommended to use Unions to emulate them if needed.&lt;/p&gt;
&lt;h2&gt;Unions&lt;/h2&gt;
&lt;p&gt;Unions are used to combine and concatenate &lt;code&gt;SELECT&lt;/code&gt; statements from multiple
tables.&lt;/p&gt;
&lt;p&gt;This example doesn’t work well because it doesn’t make much sense (&lt;code&gt;age&lt;/code&gt; isn’t
the same as a &lt;code&gt;point&lt;/code&gt;), but at least it displays what is happening during the &lt;code&gt;UNION&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT firstname, lastname, points FROM tblUsersPts 
UNION SELECT firstname, lastname, age FROM tblUsers;
+-----------|----------|--------+
| firstname | lastname | points |
+-----------|----------|--------+
| Joe       | Fry      |  20000 |
| Emily     | Flanders |  17000 |
| Tina      | Oak      |  32800 |
| Bob       | Builder  |  40100 |
| Joe       | Fry      |     32 |
| Emily     | Flanders |     22 |
| Tina      | Oak      |     42 |
| Zach      | Scout    |     27 |
+-----------|----------|--------+
8 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Sorting Records&lt;/h2&gt;
&lt;p&gt;Record rows can be sorted in the ascending or descending order of a column by using
the &lt;code&gt;ODER&lt;/code&gt; command and either &lt;code&gt;ASC&lt;/code&gt; for “ascending” or &lt;code&gt;DESC&lt;/code&gt; for “descending”.
The results can be limited or trimmed using another statement, like &lt;code&gt;LIMIT 1&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsersPts;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  1 | Joe       | Fry      | Red   |  20000 |
|  2 | Emily     | Flanders | Blue  |  17000 |
|  3 | Tina      | Oak      | Red   |  32800 |
|  4 | Bob       | Builder  | Green |  40100 |
+----|-----------|----------|-------|--------+
4 rows in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsersPts ORDER BY points DESC LIMIT 3;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  4 | Bob       | Builder  | Green |  40100 |
|  3 | Tina      | Oak      | Red   |  32800 |
|  1 | Joe       | Fry      | Red   |  20000 |
+----|-----------|----------|-------|--------+
3 rows in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsersPts ORDER BY team,lastname ASC;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  2 | Emily     | Flanders | Blue  |  17000 |
|  4 | Bob       | Builder  | Green |  40100 |
|  1 | Joe       | Fry      | Red   |  20000 |
|  3 | Tina      | Oak      | Red   |  32800 |
+----|-----------|----------|-------|--------+
4 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Minimum and Maximum Values&lt;/h2&gt;
&lt;p&gt;Similar to &lt;code&gt;ORDER&lt;/code&gt;, the minimum and maximum values in a particular column of the
table can be returned using the &lt;code&gt;MIN&lt;/code&gt; and &lt;code&gt;MAX&lt;/code&gt; commands:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsersPts ORDER BY team,lastname ASC;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  2 | Emily     | Flanders | Blue  |  17000 |
|  4 | Bob       | Builder  | Green |  40100 |
|  1 | Joe       | Fry      | Red   |  20000 |
|  3 | Tina      | Oak      | Red   |  32800 |
+----|-----------|----------|-------|--------+
4 rows in set (0.00 sec)

mysql&gt; SELECT MIN(points),MAX(points)  FROM tblUsersPts;
+-------------|-------------+
| MIN(points) | MAX(points) |
+-------------|-------------+
|       17000 |       40100 |
+-------------|-------------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To return other fields with the min/max item, a sub-query (another SQL query
inside parenthesis) may have to be used:&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT firstname,lastname,points FROM tblUsersPts 
-&gt; WHERE points=(SELECT MAX(points) FROM tblUsersPts);
+-----------|----------|--------+
| firstname | lastname | points |
+-----------|----------|--------+
| Bob       | Builder  |  40100 |
+-----------|----------|--------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Upper and Lower Case Conversions&lt;/h2&gt;
&lt;p&gt;Strings (such as names), and be easily altered using functions like &lt;code&gt;UCASE&lt;/code&gt; and
&lt;code&gt;LCASE&lt;/code&gt;. These two functions change the &lt;em&gt;displayed&lt;/em&gt; text (the data is not
altered) to be upper or lower case.&lt;/p&gt;
&lt;pre class=&quot;language-SQL&quot;&gt;&lt;code class=&quot;language-SQL&quot;&gt;mysql&gt; SELECT * FROM tblUsersPts;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  1 | Joe       | Fry      | Red   |  20000 |
|  2 | Emily     | Flanders | Blue  |  17000 |
|  3 | Tina      | Oak      | Red   |  32800 |
|  4 | Bob       | Builder  | Green |  40100 |
+----|-----------|----------|-------|--------+
4 rows in set (0.01 sec)

mysql&gt; SELECT UCASE(lastname),LCASE(firstname) FROM tblUsersPts;
+-----------------|------------------+
| UCASE(lastname) | LCASE(firstname) |
+-----------------|------------------+
| FRY             | joe              |
| FLANDERS        | emily            |
| OAK             | tina             |
| BUILDER         | bob              |
+-----------------|------------------+
4 rows in set (0.00 sec)

mysql&gt; SELECT * FROM tblUsersPts;
+----|-----------|----------|-------|--------+
| id | firstname | lastname | team  | points |
+----|-----------|----------|-------|--------+
|  1 | Joe       | Fry      | Red   |  20000 |
|  2 | Emily     | Flanders | Blue  |  17000 |
|  3 | Tina      | Oak      | Red   |  32800 |
|  4 | Bob       | Builder  | Green |  40100 |
+----|-----------|----------|-------|--------+
4 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Now()&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Now*()&lt;/code&gt; function creates a new value, using the current date and time. This
can be appended when creating a view, to mark when changes happen over time.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;firstname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;lastname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;points&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; updated
    &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; tblUsersPts&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----|-----------|----------|-------|--------|---------------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; firstname &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; lastname &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; team  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; points &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; updated             &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----|-----------|----------|-------|--------|---------------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Joe       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Fry      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Red   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;20000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2018&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Emily     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Flanders &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Blue  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;17000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2018&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Tina      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Oak      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Red   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;32800&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2018&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Bob       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Builder  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Green &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;40100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2018&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----|-----------|----------|-------|--------|---------------------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I think that is enough to at least get started with SQL :) (it was for me
anyway). There’s not much else to say other than hopefully this post server as
a good SQL quick-reference down the road :). Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trying Out Seafile</title>
    <link href="https://ryan.himmelwright.net/post/trying-out-seafile/" />
    <updated>2018-04-01T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/trying-out-seafile/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/ayveyZJK1Y-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/ayveyZJK1Y-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;647&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Outer Banks, NC USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In college, I mostly used Dropbox to handle all of my file syncing needs. As I
approached graduation, I setup an Owncloud droplet (which is now a Nextcloud
instance) to supplant my Dropbox usage. While it has worked fairly well, I’ve
been watching &lt;a href=&quot;https://www.seafile.com/en/home/&quot;&gt;seafile&lt;/a&gt; from a distance the
last few years, but haven’t taken the time to try it out. I have now.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Seafile&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/hTriJDNL2I-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/hTriJDNL2I-1024.jpeg&quot; alt=&quot;Seafile logo&quot; width=&quot;1024&quot; height=&quot;256&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Seafile Logo.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As stated on their website:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Seafile is an enterprise file hosting platform with high reliability and
performance. Put files on your own server. Sync and share files across
different devices, or access all the files as a virtual disk.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While self-defined as an “&lt;em&gt;enterprise&lt;/em&gt; file hosting platform”, there is a
Community Edition that is &lt;em&gt;Free &amp;amp; Open Source&lt;/em&gt;… which is exactly how I like my
software. So lets get started.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Similar to a Nextcloud configuration, to get seafile up and running, a server
component is first installed. Afterwards, clients connect to it.&lt;/p&gt;
&lt;h3&gt;Server&lt;/h3&gt;
&lt;p&gt;The countless number of configurations seafile supports may make setting up the
server component for the first time a bit intimidating. There are several
database back-ends (SQLite, MySQL…), web servers (Nginx, Apache), and
advanced options (Memcached, LDAP, etc) which can be selected. However, the
seafile developers &lt;em&gt;do&lt;/em&gt; provide an &lt;a href=&quot;https://github.com/haiwen/seafile-server-installer&quot;&gt;installation
script&lt;/a&gt; to easily install
both the Pro and Community editions under Linux. Which is what I used.&lt;/p&gt;
&lt;h3&gt;Download&lt;/h3&gt;
&lt;p&gt;The first step is to download the server application. A link for the
install package can found &lt;a href=&quot;https://www.seafile.com/en/download/#server&quot;&gt;partway down the download
page&lt;/a&gt;. After Downloading, extract
the contents of the package.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;wget&lt;/span&gt; https://download.seadrive.org/seafile-server_6.2.5_x86-64.tar.gz
&lt;span class=&quot;token function&quot;&gt;tar&lt;/span&gt; xf seafile-server*.tar.gz&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the extracted directory, there are several &lt;code&gt;setup-seafile-*.sh*&lt;/code&gt; scripts.
I just used the basic &lt;code&gt;setup-seafile.sh&lt;/code&gt; one.&lt;/p&gt;
&lt;h3&gt;Dependencies&lt;/h3&gt;
&lt;p&gt;Depending on which setup script it used, and the state of the host machine,
there may be some missing dependencies. While I am sure there is a nice list of
the required dependencies posted &lt;em&gt;somewhere&lt;/em&gt;… I didn’t look for it. If I am
being completely honest… I just kept repeatedly running the script, and
installed whichever package it yelled at me for each time. Eventually, the install
worked. On my fresh install of Ubuntu 16.04 server, this meant
installing the following dependency packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;python-setuptools&lt;/li&gt;
&lt;li&gt;python-imaging&lt;/li&gt;
&lt;li&gt;sqlite3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means that on an Ubuntu 16.04 server, a simple &lt;code&gt;sudo apt install python-setuptools python-imaging sqlite3&lt;/code&gt; should do the trick…&lt;/p&gt;
&lt;h3&gt;Install Script&lt;/h3&gt;
&lt;p&gt;With the dependencies installed, the script should run through without screaming
for missing packages. However, it &lt;em&gt;will&lt;/em&gt; ask for a few pieces of information
including &lt;em&gt;server-name&lt;/em&gt;, &lt;em&gt;server-ip/domain&lt;/em&gt;, &lt;em&gt;data-dir&lt;/em&gt;, and &lt;em&gt;fileserver port&lt;/em&gt;
to install the fileserver component. Answer accordingly.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/hKW2abwBEZ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/hKW2abwBEZ-1200.jpeg&quot; alt=&quot;sefile web client&quot; width=&quot;1200&quot; height=&quot;778&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Seafile Web Client, Seahub.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After configuring the fileserver, the script will flow right into configuring
&lt;a href=&quot;https://github.com/haiwen/seahub&quot;&gt;Seahub&lt;/a&gt;, the web interface that sits on top
of the fileserver. This part is less needy, and will only require an &lt;em&gt;[Enter]&lt;/em&gt;
press to continue.&lt;/p&gt;
&lt;p&gt;At the end, the script should display useful information about how to
start/stop/reset the servers, as well as what ports each part is running on.&lt;/p&gt;
&lt;h3&gt;Run &amp;amp; Start&lt;/h3&gt;
&lt;p&gt;Starting the servers is done by doing what the script says… run the
following commands:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./seafile.sh start
./seahub.sh start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the servers running, it may be necessary to configure the firewall to allow
their ports. On Ubuntu 16.04:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ufw allow &lt;span class=&quot;token number&quot;&gt;8000&lt;/span&gt;/tcp
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ufw allow &lt;span class=&quot;token number&quot;&gt;8082&lt;/span&gt;/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. With any luck, clients should be able to connect to the server
(assuming the server is reachable from the client computer… but that’s a
lesson for another day).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: The first time seahub is run, it will need to setup an admin account. So
while it was nice and considerate in the last step… it will insist on being
supplied a username (email) and password for the new admin account. Again, just
comply.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Client&lt;/h3&gt;
&lt;p&gt;The seafile desktop client was trivial to install. It was in the Solus repos, so
I just needed to run &lt;code&gt;sudo eopkg it seafile-client&lt;/code&gt;, and I was done. I also
installed it on my work computers (which unfortunately runs Windows 10), and
even that was simple. I just downloaded and installed the Windows “Desktop
Syncing Client” at the top of the &lt;a href=&quot;https://www.seafile.com/en/download/&quot;&gt;download
page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;My Thoughts&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/T-CdyqxPNr-360.webp 360w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/T-CdyqxPNr-360.jpeg&quot; alt=&quot;Seafile Desktop Client&quot; &quot;=&quot;&quot; width=&quot;360&quot; height=&quot;863&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Seafile Desktop Client&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have been using seafile as a syncing solution for the past few weeks. After
the initial setup, it has been rather uneventful… which is a good thing. It
just runs in the background, and I don’t really think about it. Exactly what you
want with a syncing solution.&lt;/p&gt;
&lt;p&gt;That being said, I haven’t fully dug into all of the details of the application
yet. So, if I incorrectly critique something due to my own ignorance that
simply isn’t true, I apologize in advance.&lt;/p&gt;
&lt;h3&gt;What I Like&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Desktop Client&lt;/strong&gt; - The desktop client is simple. It responds quickly, and is
a nice little command center where I can view how seafile is configured on each
individual system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Select &lt;em&gt;which&lt;/em&gt; Libraries to sync&lt;/strong&gt; - Seafile libraries allow me to sync only
what I need. Nextcloud had this feature, but it was implemented a little differently. I
had to go to the &lt;code&gt;Nextcloud&lt;/code&gt; folder in the client UI, and then check/uncheck all
of the sub-folders I wanted to sync on my machine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pick &lt;em&gt;where each&lt;/em&gt; libraries sync&lt;/strong&gt; - Even better than being able to pick
&lt;em&gt;which&lt;/em&gt; libraries to sync, is having the power to choose &lt;em&gt;where&lt;/em&gt; each one will
reside. In seafile, the location of each library is completely independent.
For example, I can sync my &lt;code&gt;work&lt;/code&gt; library to &lt;code&gt;~/Documents/Work/&lt;/code&gt;, my &lt;code&gt;Music&lt;/code&gt;
to &lt;code&gt;~/Music&lt;/code&gt;, and &lt;code&gt;emacs&lt;/code&gt; to
&lt;code&gt;~/Documents/Programming/Editors/why/so/many/diectories/emacs/&lt;/code&gt;. On another
computer, I may choose to only sync my &lt;code&gt;work&lt;/code&gt; library to &lt;code&gt;~/Desktop/work&lt;/code&gt; (It
doesn’t have to be the same across computers). I love this. It lets seafile &lt;em&gt;integrate&lt;/em&gt; with my
workflow, rather than forcing me to make &lt;em&gt;it be&lt;/em&gt; my workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encrypted Libraries&lt;/strong&gt; - When creating a new library, it is possible to
make an encrypted one. These libraries use client-side, end-to-end encryption
and require a password. The file &lt;em&gt;contents&lt;/em&gt; (Note: not directory or file
&lt;em&gt;names&lt;/em&gt;) are encrypted on the client side, and not on the server. This means
that even the server admin cannot access the file contents on the server.
This is a feature I’d like to see in more applications.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/f3YbH2_neP-673.webp 673w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-seafile/f3YbH2_neP-673.jpeg&quot; alt=&quot;Docker PS&quot; width=&quot;673&quot; height=&quot;427&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;When syncing an encrypted library, a password must be entered.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast Sync&lt;/strong&gt; - I only have anecdotal evidence, but the syncing in seafile
&lt;em&gt;feels fast&lt;/em&gt;. Files seem to pull down very quickly, and setting up my
libraries on a new device doesn’t take a very long time. I might have to
actually &lt;em&gt;measure&lt;/em&gt; if it’s any better than something like nextcloud, but if I’m
happy… does it really &lt;em&gt;matter&lt;/em&gt;?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What I Don’t Like&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Folders/Nested libraries&lt;/strong&gt; - As far as I can tell, I don’t think it is
possible to organize seafile libraries with folders, or a nested library
structure. Admittedly, this is one of those areas that I haven’t had to look
too much into yet. However, as I start to increase my seafile usage and the
number of my libraries increases, I could see this being a feature I’d really
enjoy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Phone Sync Tricky&lt;/strong&gt; - The phone sync (specifically auto photo upload) was
tricky to get working at first. After setting it up, it didn’t seem to work,
or at least not as promptly as nextcloud does. I took a few photos to test it
out today, it seemed to work &lt;em&gt;okay&lt;/em&gt;, but I don’t think it started syncing
right away. Right now, I don’t 100% trust it to work without me thinking
about it. Hopefully this improves with time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Future Plans&lt;/h2&gt;
&lt;p&gt;To summarize, I have been loving seafile, and I think I have only scratched the
surface. I haven’t even tried playing with features like History and Snapshots,
Full Text File Search, or yet. My plan is to continue experimenting a bit more,
and then switch to it as my main syncing system when I redo my main server
setup in a couple of weeks. If you haven’t given seafile a try recently, I
recommend it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Docker Quick Start</title>
    <link href="https://ryan.himmelwright.net/post/docker-quickstart/" />
    <updated>2018-02-25T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/docker-quickstart/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/heYKcg19dV-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/heYKcg19dV-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Inner Harbor, Baltimore, MD USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over the past few months (particularly over the holiday season), I started to
explore and learn several technologies I’ve had my eye on the last few years.
First on the list: Docker. These days, Docker has a massive
ecosystem surrounding it, and can take years to truly master. This post on the
other hand, will &lt;em&gt;hopefully&lt;/em&gt; help you get up and playing with docker containers in
just a few minutes. Lets get started.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Installing Docker&lt;/h2&gt;
&lt;p&gt;Installing docker on Solus was easy enough. I just
had to install the package, and then enable the service:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; eopkg it &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl start &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On other distributions, it may not be in the package manager, or it might be
under a different name. To be sure, check out the community edition
&lt;a href=&quot;https://docs.docker.com/install/linux/docker-ce/fedora/#set-up-the-repository&quot;&gt;installation
documentation&lt;/a&gt;
for your specific distro.&lt;/p&gt;
&lt;h2&gt;Adding User to Docker Group&lt;/h2&gt;
&lt;p&gt;By default, user accounts will not have permission to run docker commands
without root access. So, after first installing docker, it might be tempting to
just run everything using &lt;code&gt;sudo&lt;/code&gt;, but that isn’t the best idea. To get around
this, simply add the user account to the &lt;code&gt;docker&lt;/code&gt; group:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usermod&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-G&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; ryan&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the docker group is not created for some reason, it can be added:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;groupadd&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, that user should be able to run docker commands without requiring
&lt;code&gt;sudo&lt;/code&gt;. They &lt;em&gt;might&lt;/em&gt; have to log out and back in first.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: These commands may differ based on distro.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Some Useful Commands&lt;/h2&gt;
&lt;p&gt;Before getting too caught up in creating and using containers, lets first go
over a few useful commands that make navigating docker a bit easier.&lt;/p&gt;
&lt;h3&gt;docker ps&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/C98W0u0GGg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/C98W0u0GGg-1200.jpeg&quot; alt=&quot;Example of docker ps and docker ps -a commands&quot; width=&quot;1200&quot; height=&quot;254&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Example of the docker ps and docker ps -a commands.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Similar to how Unix-based systems have the &lt;code&gt;ps&lt;/code&gt; command to see running
processes, docker has &lt;code&gt;docker ps&lt;/code&gt; to see created and running containers. To
view the currently running containers, use the basic &lt;code&gt;docker ps&lt;/code&gt; command:&lt;/p&gt;
&lt;p&gt;The base command however, doesn’t always tell the whole story. A system might
have containers that &lt;em&gt;exist&lt;/em&gt; but are not running. A container may have been
stopped, &lt;em&gt;or&lt;/em&gt; exited if it encountered an error. Containers that are stopped or
exited &lt;em&gt;will not&lt;/em&gt; show up in the default &lt;code&gt;docker ps&lt;/code&gt; command. To see &lt;em&gt;all&lt;/em&gt; of the
current containers on the system, run &lt;code&gt;ps&lt;/code&gt; with the &lt;code&gt;-a&lt;/code&gt; flag.&lt;/p&gt;
&lt;h3&gt;docker inspect&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/y1l9_K7mUv-734.webp 734w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/y1l9_K7mUv-734.jpeg&quot; alt=&quot;Docker inspect examples: docker inspect web-test | grep Status and docker inspect web-test | grep IPAddress&quot; width=&quot;734&quot; height=&quot;233&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using the docker inspect command to get the container&#39;s status and IP address.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Another useful command when working with docker containers is &lt;code&gt;docker inspect&lt;/code&gt;.
The &lt;code&gt;inspect&lt;/code&gt; command will dump the xml for all the low level information of the
container/docker object. The output contains basically &lt;em&gt;everything&lt;/em&gt; about the
container: Full ID, time created, state, volumes, network information…
everything. It can be useful to pipe the output of &lt;code&gt;inspect&lt;/code&gt; to grep, in order
to get specific information about the container quickly.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; inspect container_name &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; IPAddress&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, the above command will grab and return only the lines which contain
“IPAddress”.&lt;/p&gt;
&lt;h3&gt;help&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/9rXNb2nEvc-799.webp 799w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/9rXNb2nEvc-799.jpeg&quot; alt=&quot;Using the docker help command flags&quot; width=&quot;799&quot; height=&quot;338&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Using the docker help command flags.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last but not least, don’t forget about the &lt;code&gt;help&lt;/code&gt; command. To see all the
available docker commands, run &lt;code&gt;docker help&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, when using each of those specific commands, (&lt;code&gt;inspect&lt;/code&gt; for
example), a description and possible options can be shown using the &lt;code&gt;--help&lt;/code&gt;
flag (as in &lt;code&gt;docker inspect --help&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Just as man pages can be extremely valuable when working on a Linux system,
&lt;code&gt;help&lt;/code&gt; is just as essential when using docker.&lt;/p&gt;
&lt;h2&gt;Images&lt;/h2&gt;
&lt;p&gt;A docker container is created from a base &lt;em&gt;image&lt;/em&gt;. Images can be pulled down
from &lt;a href=&quot;https://hub.docker.com/&quot;&gt;Dockerhub&lt;/a&gt;. To search docker hub from command
line, use the &lt;code&gt;docker search&lt;/code&gt; command. For example, &lt;code&gt;docker search nginx&lt;/code&gt; will
search for images related to nginx.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/QhsHofP-Xw-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/QhsHofP-Xw-1200.jpeg&quot; alt=&quot;Docker Search output. Ex: docker search nginx&quot; width=&quot;1200&quot; height=&quot;517&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The search output for nginx images.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The search will list all of the public images in order, from most to least
stars. When a desired image is found, pull it down using the &lt;code&gt;docker pull&lt;/code&gt;
command. For example, &lt;code&gt;docker pull nginx&lt;/code&gt; will pull down the &lt;a href=&quot;https://hub.docker.com/_/nginx/&quot;&gt;nginx
image&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/4SAy6B0jeu-803.webp 803w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/4SAy6B0jeu-803.jpeg&quot; alt=&quot;Docker Pull output. Ex: docker pull nginx&quot; width=&quot;803&quot; height=&quot;305&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pulling the latest nginx image.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Docker will then download all the layers for the image. When the download
completes, the image will be locally available to create docker containers
from. To see all of the local images, use the &lt;code&gt;docker images&lt;/code&gt; command:&lt;/p&gt;
&lt;p&gt;To delete an image, use the &lt;code&gt;docker images rm&lt;/code&gt; command, or my preferred, lazier
command, &lt;code&gt;docker rmi&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/3VwzVsi5wA-944.webp 944w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/3VwzVsi5wA-944.jpeg&quot; alt=&quot;Docker images and docker rmi&quot; width=&quot;944&quot; height=&quot;484&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Viewing downloaded images with the docker images command, then deleting the &#39;test-ubuntu&#39; image using the docker rmi command.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, it should be noted that custom images can be created/tailored using a
&lt;a href=&quot;https://docs.docker.com/engine/reference/builder/#usage&quot;&gt;Dockerfile&lt;/a&gt;. With a
Dockerfile is defined, an image can be created from it using the &lt;code&gt;docker build&lt;/code&gt;
command. For example, the following Dockerfile would use an
&lt;a href=&quot;https://hub.docker.com/_/ubuntu/&quot;&gt;ubuntu&lt;/a&gt; image for the base, but also update &amp;amp;
install several packages in the image:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# This is a custom ubuntu image with SSH already installed&lt;/span&gt;
FROM ubuntu&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest
MAINTAINER himmallright &amp;lt;ryan.himmelwright@gmail.com&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;

RUN apt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;get update

RUN apt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;get install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y vim stow git tmux fish htop emacs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To build the image, run the following command in the same directory as the
Dockerfile (if defining a specific file, the &lt;code&gt;-f&lt;/code&gt; flag can be used):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; build &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; ubuntu-base:v1 &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I like to use the &lt;code&gt;-t&lt;/code&gt; flag, so that I can specify a &lt;code&gt;name:tag&lt;/code&gt; for the image.
This helps to make it easier to find it in the &lt;code&gt;docker images&lt;/code&gt; list.&lt;/p&gt;
&lt;h2&gt;Creating Containers&lt;/h2&gt;
&lt;p&gt;Docker containers can be &lt;em&gt;created&lt;/em&gt; (but not run) with the &lt;code&gt;docker create&lt;/code&gt;
command. When creating containers, it is useful to use flags to tailor the
details of the container. For example, the &lt;code&gt;-m&lt;/code&gt; flag can be used to create a
memory limit, &lt;code&gt;--name&lt;/code&gt; to name the container, and so on. To create a simple nginx
container from the image we previously pulled:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; create &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; web-test nginx:latest&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Starting &amp;amp; Running Containers&lt;/h2&gt;
&lt;p&gt;To &lt;em&gt;start&lt;/em&gt; a container created with &lt;code&gt;docker create&lt;/code&gt;, or one that has been previously
stopped, use the &lt;code&gt;docker start&lt;/code&gt; command. For example, to start
the container created in the previous step:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker start web-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, a container can be stopped with &lt;code&gt;docker stop&lt;/code&gt;, or restarted with
&lt;code&gt;docker restart&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using a &lt;code&gt;docker create&lt;/code&gt; and &lt;code&gt;docker start&lt;/code&gt; combination, &lt;code&gt;docker run&lt;/code&gt;
can be used to both instantiate &lt;em&gt;and&lt;/em&gt; start a container. To create and start the
“web-test” container from the previous examples:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --name web-test -d nginx:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Cleaning containers&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/KODG2bMMcr-646.webp 646w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/KODG2bMMcr-646.jpeg&quot; alt=&quot;Docker rm all images&quot; width=&quot;646&quot; height=&quot;257&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Removing all the older container images.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Over time, old containers may build up on the system. To remove an old (but not
running) container, use &lt;code&gt;docker rm&lt;/code&gt; with either the container name, &lt;em&gt;or&lt;/em&gt; the id.
Note, to easily delete &lt;em&gt;all&lt;/em&gt; of the containers on the system, &lt;code&gt;docker rm&lt;/code&gt; can be
fed the output of &lt;code&gt;docker ps -aq&lt;/code&gt;, where the &lt;code&gt;-aq&lt;/code&gt; flag returns a list of all
the container ids.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-aq&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(Note: this doesn’t work in my Fish shell, but does in bash)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Attaching to Containers&lt;/h2&gt;
&lt;p&gt;After a container is running, it might occasionally be necessary to attach to
it and poke around with a shell. There is the obvious way to do this,
&lt;code&gt;docker attach&lt;/code&gt;, and a somewhat work-around way, which I prefer to use: &lt;code&gt;docker exec&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/PgTggwcutm-1149.webp 1149w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/PgTggwcutm-1149.jpeg&quot; alt=&quot;Docker attach example&quot; width=&quot;1149&quot; height=&quot;373&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;An example using the docker attach command.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The attach command works as one would expect. It allows a user to “Attach local
standard input, output, and error streams to a running container”. This is all
well an good, except for one issue: when exiting the attached container, the
&lt;em&gt;container also&lt;/em&gt; exits.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/dIbbtrmNMd-1149.webp 1149w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/dIbbtrmNMd-1149.jpeg&quot; alt=&quot;Docker exec bash example&quot; width=&quot;1149&quot; height=&quot;373&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;An example using docker exec with a bash shell, as an alternative to docker attach.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A way around this annoyance is to utilize the &lt;code&gt;docker exec&lt;/code&gt; command, which
allows a command to be executed inside a container. Executing a shell program
inside the container, such as &lt;code&gt;bash&lt;/code&gt;, mimics the &lt;code&gt;attach&lt;/code&gt; command, but with the
added benefit that when exiting, only the shell exits, and &lt;em&gt;not&lt;/em&gt; the entire
container.&lt;/p&gt;
&lt;h2&gt;Ports &amp;amp; Volumes&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, docker containers can be built and run using many specific
commands to tailor the container and how it interfaces with the host system.
There are two concepts in particular that I want to briefly touch on in this
&lt;em&gt;quick start&lt;/em&gt; post. Those two items, are &lt;code&gt;ports&lt;/code&gt; and &lt;code&gt;volumes&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Ports&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/It_I-Ziaid-1018.webp 1018w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/It_I-Ziaid-1018.jpeg&quot; alt=&quot;Containerized nginx server connected to through localhost&quot; width=&quot;1018&quot; height=&quot;491&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Container ports can be forwarded to the host&#39;s ports.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While it is nice to spin up a web server inside a docker container, it isn’t
always very useful to only have it available to the host machine. By using the
&lt;code&gt;-p&lt;/code&gt; flag when running a container, the container’s ports can be forwarded to
ports on the host system.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/Ur2yKV7_fL-1194.webp 1194w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/Ur2yKV7_fL-1194.jpeg&quot; alt=&quot;Creating an nginx container, forwarding port 80 to he host&#39;s 8081&quot; width=&quot;1194&quot; height=&quot;208&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Creating an nginx container, forwarding it&#39;s port 80 to the host&#39;s port 8081.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;-p&lt;/code&gt; with just a single number, as in &lt;code&gt;-p 8080&lt;/code&gt;, will declare that port of
the container to be exposed. To forward exposed ports to the host, use &lt;em&gt;two&lt;/em&gt;
port numbers, separated with a &lt;code&gt;:&lt;/code&gt;. The first number is the host port to bind
to, and the second is the container port to expose and forward.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -itd --name webtest -p 8081:80 nginx:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example, a nginx container is started with port 80 forwarded to port
8081 on the host. As a result, any computer connecting to port 8081 of the host
machine will be directed to the nginx web server inside the container.&lt;/p&gt;
&lt;h3&gt;Volumes&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/KohsZ2GsxI-759.webp 759w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/KohsZ2GsxI-759.jpeg&quot; alt=&quot;Nginx container with website pages in a volume from the host.&quot; width=&quot;759&quot; height=&quot;378&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Containers can attach volumes from the host system for persistent data.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, by design docker containers are mean to be expendable. They are run, and then
disposed. It should not be assumed that &lt;em&gt;any&lt;/em&gt; data inside the container will
be preserved by default. That is, unless
&lt;a href=&quot;https://docs.docker.com/storage/volumes/&quot;&gt;volumes&lt;/a&gt; are used.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/docker-quickstart/tEEESmf5dY-1149.webp 1149w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/docker-quickstart/tEEESmf5dY-1149.jpeg&quot; alt=&quot;Creating an nginx container, forwarding port 80 to he host&#39;s 8081&quot; width=&quot;1149&quot; height=&quot;444&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Creating an nginx container, forwarding it&#39;s port 80 to the host&#39;s port 8081.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Docker volumes are the preferred mechanism for preserving data across container
runs, and are specified using the &lt;code&gt;-v&lt;/code&gt; flag. Similar to setting ports, volumes
can be created by providing either a single path, or two separated by a &lt;code&gt;:&lt;/code&gt;.
When a single path is provided, as in &lt;code&gt;-v /Data&lt;/code&gt;, docker will create a volume
and bind it to that location within the container. Two locations can be provided
to bind a directory on the host system, to the volume inside to container.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --name testsite -v /home/ryan/testsite/:/usr/share/nginx/html nginx:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above command, for example, &lt;code&gt;-v /home/ryan/testsite/:/usr/share/nginx/html&lt;/code&gt; will use the &lt;code&gt;/home/ryan/testsite/&lt;/code&gt;
directory of the host system, as a volume located at &lt;code&gt;/usr/share/nginx/html&lt;/code&gt;
inside the container. This means that the container will server the website
files, which are located (and can be easily edited), on the host system.&lt;/p&gt;
&lt;h2&gt;In Conclusion&lt;/h2&gt;
&lt;p&gt;So, this has been… a rather long post. However, when it comes to Docker, this
&lt;em&gt;really is just the tip of the iceberg&lt;/em&gt;. With any luck though, you should now
know the basic to at &lt;em&gt;least&lt;/em&gt; get &lt;em&gt;started&lt;/em&gt;. So… go ahead and have some fun!.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Living the Dock Life - My Thinkpad T470</title>
    <link href="https://ryan.himmelwright.net/post/my-t470/" />
    <updated>2018-02-01T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/my-t470/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-t470/EvZ_hCRc72-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-t470/EvZ_hCRc72-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;881&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my previous post, I discussed how I sold off my System76 Bonobo laptop, and
planned to replace it with a new computer. Well, I did… awhile ago. For the
last few months, I have been using a Lenovo T470 Thinkpad (with dock) as my new
daily driver. Here are my thoughts.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;The Choice&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-t470/5R15JG40rP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-t470/5R15JG40rP-1200.jpeg&quot; alt=&quot;T470 Coffee&quot; width=&quot;1200&quot; height=&quot;821&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My T470 on a Sunday morning.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The
&lt;a href=&quot;https://www.notebookcheck.net/Lenovo-ThinkPad-T470-Core-i5-Full-HD-Notebook-Review.198130.0.html&quot;&gt;T470&lt;/a&gt;
is the 14&amp;quot; model of Lenovo’s “business” laptop lineup. The T470 series actually
has several variants itself, including the “high end”
&lt;a href=&quot;https://www.notebookcheck.net/Lenovo-ThinkPad-T470s-Core-i7-WQHD-Laptop-Review.200880.0.html&quot;&gt;T470s&lt;/a&gt;,
that has a slimmer chassis, made from “higher quality materials”, and a better
screen compared to the T470. There is also the &amp;quot;high
performance&amp;quot;&lt;a href=&quot;https://www.notebookcheck.net/Lenovo-ThinkPad-T470p-Core-i7-GeForce-940MX-Laptop-Review.226802.0.html&quot;&gt;T470p&lt;/a&gt;,
which features a faster, quad-core CPU, but a slightly larger chassis. While
these variants caught my attention, I ultimately decided on the normal T470.
This was partially because I could max the T470 out at 32GBs of RAM (The T470s
can only hit 24GB due to one of the sticks being soldered in), but mostly
because I assumed that the combination of having a lower powered CPU (compared
to the T70p), but a slightly larger case (compared to the T480s) would make it
the optimal choice concerning my heat/noise concerns outlined in the previous
post.&lt;/p&gt;
&lt;h2&gt;The Specs&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-t470/gqDbeWidEt-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-t470/gqDbeWidEt-1200.jpeg&quot; alt=&quot;Inside T470&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Guts of the T470.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;So, the T470 then…&lt;/p&gt;
&lt;p&gt;I got my T470 from a vendor on Ebay. It came configured with a 2.5GHz (3.1 GHz
Turbo) i5-7200u cpu, 8 GBs of DDR4 RAM, a 14&amp;quot; 1920x1080 screen, and a 500GB Hard
Drive with Windows 10 on it. After checking that it booted up fine, I
immediately swapped the slow HD with the 250GB SSD that was in my x230, and
installed Solus on it. In Solus, everything worked
“out of the box”, and I would guess other Linux distributions would as well. For
Christmas, I got a new 500GB Samsung EVO SSD to upgrade the 250GB one. In total,
the T470 has less total storage than the Bonobo had, but &lt;em&gt;all&lt;/em&gt; of it is fast SSD
storage. I am getting better at using my server for network storage anyway, so
it all works out :) .&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-t470/D-_2vTJIIT-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-t470/D-_2vTJIIT-1200.jpeg&quot; alt=&quot;Solus Install&quot; width=&quot;1200&quot; height=&quot;905&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Installing Solus the first night.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While I still plan to upgrade the T470 in the future, here are it’s specs at the
time of writing this post:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T470 Thinkpad
Intel i5-7200u [2.5 Ghz (3.1 Ghz Turbo), 2 Cores, 4 Threads]
8 GB RAM
500 GB Samsung EVO SSD
14&amp;quot; 1920x1080 IPS Display
Solus
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The hardware on the T470 is nice, and the technical specs are &lt;em&gt;okay&lt;/em&gt;, but the
real killer feature of switching to a Thinkpad for my main computer is that I
also purchased the ThinkPad Ultra Dock (40A2). This means that I am able to
&lt;em&gt;easily&lt;/em&gt; take advantage of my periphery devices whenever I am working at my
desk.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Thinkpad Ultra Dock 40A2
2 x 23.6&amp;quot; ASUS 1920x1080 Monitors
Happy Hacking Keyboard Pro2
Audioengine A2+ Speakers &amp;amp; Stands
Bose AE2 Headphones
Logitech HD Pro Webcam c920 Widescreen
Blue Yeti Microphone - Backout Edition
Inateck USB 3.0 to SATA Dual-Bay Hard Drive Docking Station
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What I like&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-t470/YS9oieKecS-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-t470/YS9oieKecS-1200.jpeg&quot; alt=&quot;T470 in Hotel&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I&#39;ve enjoyed the portability and great keyboard of the T470.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now that I’ve provided some background information and a summary, I’ll quickly
list what I &lt;em&gt;like&lt;/em&gt; and &lt;em&gt;do not like&lt;/em&gt; about my new T470.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cool &amp;amp; Quiet&lt;/strong&gt; - After realizing that I am picky about heat and noise in
computers, one of my biggest goals when replacing my old computer was to find
one that ran cooler and quieter. I think I did well here. &lt;em&gt;This thing is
silent.&lt;/em&gt; Using one of my new favorite command line applications,
&lt;a href=&quot;https://amanusk.github.io/s-tui/&quot;&gt;s-tui&lt;/a&gt;, I have observed that most of the
time when I’m writing code, browsing the web, or listening to music, the fans
in the T470 don’t even kick on. When I do push the machine a little by
running VMs, or being on a video conference, the do kick up, but even then I
have to put my ear right against the computer to hear anything. At it’s
loudest, the T470 is still a very quiet device.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Size &amp;amp; Portability&lt;/strong&gt; - I love the size and portability of this laptop. The
14&amp;quot; form factor is large enough that I don’t feel cramped when using it as a
stand-alone device, but still small enough that I can easily throw it in my
bag and bring it with me. While I wouldn’t mind the T470 shedding a &lt;em&gt;little&lt;/em&gt;
weight (which is mostly from the battery), I find it’s thickness perfectly
acceptable for my uses (hear that Apple?). As far as build quality, it is a
solid machine. When I rest my hands on it to type, they feel fully supported
and there is &lt;em&gt;zero&lt;/em&gt; flex. When holding the closed laptop, it feels as though
it could take quite a beating and still be fine. I love that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keyboard &amp;amp; Track-point/pad&lt;/strong&gt; - It’s a Thinkpad keyboard, what else do I have
to say? The keys are adequately spaced, have travel (though not as much as my
x230), and feel &lt;em&gt;crisp&lt;/em&gt; when pressed, not mushy. It has a track&lt;em&gt;point&lt;/em&gt; with
physical buttons, which at this point I don’t think I could do without (Crap.
Unintentional pun. Oh well, I’m leaving it.). While I don’t use it often, the
track&lt;em&gt;pad&lt;/em&gt; is also quite nice. I find it to be an ideal size, and has
rubberized texture which prevents my fingers from feeling raw after long use.
I find it hard these days for others laptops to compete with the Thinkpad’s
keyboard/track-point setup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dock-able&lt;/strong&gt; - I’ve already mentioned this above, but I love that the T470 is
dock-able. This is becoming more common with the pervasiveness of USB-C, but
Thinkpads have been in the dock game for years. It works well, and fits my
current computing use-case perfectly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Thunderbolt/USB C&lt;/strong&gt; - The big difference between the T470 and the T460
essentially comes down to the Thunderbolt 3/USB-C port on the T470. I don’t
use it much &lt;em&gt;so far&lt;/em&gt;, but I hope the industry continues the move down the this
route, and I will be glad to already have the port as they do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Battery(s)&lt;/strong&gt; - The battery configuration and life on this laptop is great.
The T470 can get a 3-cell battery that doesn’t stick out, or an extended
battery that comes in 6 and 9 cell variations (Note: the 6 and 9 cell are the
same size). The T470 also supports an internal 3 cell battery, that can be
used to keep the laptop powered on while the external battery is swapped. I
currently have the least optimal configuration: A bulky 6-cell external, with
no internal battery. Even so, it easily lasts me throughout the day, and I can
always upgrade it down the road. I love the flexibility.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What I don’t like&lt;/h2&gt;
&lt;p&gt;While I overall love the T470, there are a few things I’m not thrilled about.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Screen&lt;/strong&gt; - When I first got my laptop… I thought the screen was
defective. I had read in reviews that it wasn’t very &lt;em&gt;bright&lt;/em&gt;, but the colors
were terrible. My screen always looked as if I was using something like
&lt;a href=&quot;https://justgetflux.com/&quot;&gt;f.lux&lt;/a&gt;. After doing some reading, it turns out that
IPS panels (which I had never owned before) can appear warmer in color. That
combined with the low brightness and &lt;em&gt;not great&lt;/em&gt; color accuracy of the T470
panel, made it appear particularly awful to me. I found and configured a &lt;a href=&quot;https://www.notebookcheck.net/uploads/tx_nbc2/N140HCA_EAB_01.icm&quot;&gt;ICC
file&lt;/a&gt; which
helped, but the biggest fix was time. As terrible as it was at first, I really
don’t mind the screen. Yea, it could be brighter with better colors, but now I
think it looks nice, and colors on top of black really pop.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Track-point&lt;/strong&gt; - I stated in the section above that the track-point was one of
the features I loved about this laptop. That is true… but, if I &lt;em&gt;had&lt;/em&gt; to get
picky, the track-point itself is not that great. It’s hard to move around with
and it’s sensitivity differs depending on which direction it is traveling. At
times it can be a real pain. By contrast, my x230 is very stable and precise.
I wish the T470’s track-point was like the one on my x230’s.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Graphics&lt;/strong&gt; - I can’t complain too much here because I knew what I was
getting myself into, but… the graphics performance isn’t the best. I know
&lt;em&gt;not&lt;/em&gt; having dedicated graphics is what allows the T470 to run cool and sip
power, but I wouldn’t mind a &lt;em&gt;little&lt;/em&gt; more umph. I don’t expect to do heavy
gaming on the T470. If I want to game, I am going to build a gaming computer.
However, I do wish I could play basic games a &lt;em&gt;little&lt;/em&gt; better in the meantime.
(&lt;em&gt;On the bright side…with the thunderbolt 3 port… an eGPU is a
possibility…hmmm…&lt;/em&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Future Plans&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-t470/bnm3tfcoKS-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-t470/bnm3tfcoKS-1200.jpeg&quot; alt=&quot;T470 Ram Slot&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The empty RAM slot I hope to add a 16GB stick to... if prices ever drop again...&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Overall, I have been very happy with the T470 as my main computer. I’d still
like to upgrade the RAM at some point, and I &lt;em&gt;have&lt;/em&gt; entertained the idea of
trying an eGPU setup… I might &lt;em&gt;eventually&lt;/em&gt; build a new desktop, but even then,
I would likely have it headless, running VMs and connect to it remotely from the
T470 (except for occasional gaming). That being said, right now, the T470 is
working great as is!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Sold My Bonobo</title>
    <link href="https://ryan.himmelwright.net/post/sold-my-bonobo/" />
    <updated>2018-01-14T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/sold-my-bonobo/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/yKq7jcBP_i-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/yKq7jcBP_i-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Last fall, I did something I didn’t think I would do anytime soon… I sold my
main workstation computer. Specifically, my System76 Bonobo Extreme laptop.
Several events over the last year have led me to the realization that my
&lt;em&gt;actual&lt;/em&gt; computing &lt;em&gt;needs&lt;/em&gt; are drastically different than what they were when I
purchased the Bonobo in early 2015. This post will highlight some of those
realizations, and why I ultimately decided to sell my Bonobo laptop.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why I got the Bonobo&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/M5RqGHiCtc-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/M5RqGHiCtc-1200.jpeg&quot; alt=&quot;Bonobo Setup&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Bonobo Workstation Setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I bought my Bonobo laptop from &lt;a href=&quot;https://www.system76.com&quot;&gt;System76&lt;/a&gt; during my
first post-college year. I needed to replace my old workstation: an aging
desktop that I built before my High School graduation. While its i7-930 CPU,
6GB of RAM, NVIDIA GTX 260, and hodgepodge of hard drives (SSD and HHD) serverd
my needs throughout college, I was starting to feel restricted. After some
research, I came to the conclusion that it would be better to get a new system,
instead of trying to upgrade my old desktop. I wanted a new, powerful computer
that could easily run multiple VMs, play some video games, and compile
software/run models &lt;em&gt;fast&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;After a few months at my new job, I noticed I didn’t use a portable computer
as much. I went to work, used my work computer, and then came home to my
desktop. So portability wasn’t the &lt;em&gt;biggest&lt;/em&gt; concern when searching. That being
said, I still lived out of state from my family, and would leave for a week or
more at a time during hollidays or vacation. I wanted to still &lt;em&gt;be able&lt;/em&gt; to lug
my computer with me during family vists. I also liked to game &lt;em&gt;a little bit&lt;/em&gt;
when visiting with my brothers, or after work, so I added a dedicated GPU to the search criteria.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/VN5rtKJXFj-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/VN5rtKJXFj-1200.jpeg&quot; alt=&quot;Portable Bonobo Setup&quot; width=&quot;1200&quot; height=&quot;631&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Portable Bonobo Setup.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;All of this pointed me towards desktop-replacement laptops. I wanted to support
a Linux-only vendoor, so I looked at System76. The Bonobo seemed comparable to
other laptops on the market, but being a System76 device, I felt more confident
all the hardware would play nicely with Linux. It was a big laptop, but my plan
was to use it mostly as a desktop computer, with the option to still travel with
it when I needed to go somewhere for more than just a day or two. Besides, I
liked that the case was larger, as I hoped it would help increase airflow
to help with heat management.&lt;/p&gt;
&lt;p&gt;I purchased the Bonobo, and it worked out very well for several years. It was a
very powerful computer (more than my old &lt;em&gt;desktop&lt;/em&gt;), but still portable &lt;em&gt;enough&lt;/em&gt;
for when I wanted to take it somewhere.&lt;/p&gt;
&lt;h2&gt;The Modern Reality&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/CnRsbVN2Xb-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/CnRsbVN2Xb-1200.jpeg&quot; alt=&quot;Bonobo Thickness&quot; width=&quot;1200&quot; height=&quot;255&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Bonobo Side View.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As the years passed, my computing needs changed. On top of that, some of the
initial assumptions about my computer use turned out to not be entirely true.&lt;/p&gt;
&lt;h3&gt;I’m Not a Big &lt;s&gt;PC&lt;/s&gt; Gamer.&lt;/h3&gt;
&lt;p&gt;For example, when my brothers and I would pull out our laptops to play games
together, the games we played were never highly demanding. Our top played games
were Minecraft, Serious Sam 3, Dungeon Defenders, and &lt;em&gt;maybe&lt;/em&gt; CS GO. Needless to
say, the Bonobo was a little overkill. Unfortunately, with my brothers and I all
living in different states now, we really don’t see each other in person enough
to play computer games in the same room anymore. These days, when we want to
remotely play video games together… we play on the Xbox ONE (which I bought
mostly to keep in touch with my friends and family. I didn’t own one when I
purchased the Bonobo).&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/BJm8cbNc__-1056.webp 1056w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/BJm8cbNc__-1056.jpeg&quot; alt=&quot;xbox one&quot; width=&quot;1056&quot; height=&quot;594&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Xbox One&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have enjoyed owning a computer that can handle most of games I throw at
it, but if I’m being honest… I don’t play games too often. Even on the Xbox,
I go in bursts of playing once or twice a week, and then don’t touch a game for
months. While I wouldn’t mind having the ability to play more demanding games on
my computer, it isn’t a &lt;em&gt;requirement&lt;/em&gt; for me… expecially on a laptop.&lt;/p&gt;
&lt;h3&gt;I Travel More. Sometimes On Airplanes.&lt;/h3&gt;
&lt;p&gt;One change compared to when I originally bought my bonobo is that I travel more.
When I first moved to Massachusetts, I figured most of my traveling would be
from here back to Pennsylvania, or to vacation once a year. However, my
wife and I have managed to keep in touch with some of our old friends… who
live all over the country. Plus, now that my brothers have moved out of
the house, they have started to spread out too (as already mentioned in the
section above). So, I travel more often. In particular, I fly more
frequently.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/dc_Xyoe3Om-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/dc_Xyoe3Om-1200.jpeg&quot; alt=&quot;TSA logo&quot; width=&quot;1200&quot; height=&quot;398&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;To say it bluntly, the Bonobo sucks to travel with. It takes up the majority of
my bag, and most bags don’t even fit it. Besides the laptop size, TSA is often
freaked out by the MASSIVE power supply, and has had to re-scan my bag several
times, thinking I had a “bluetooth speaker” or something else I neglected to
tell them about (despite my polite warnings that “my laptop’s power charger is
massive”). Beyond security, It isn’t a computer I can pull out of my bag
and easily use in an airport terminal. In fact, the last time I flew with it, I
tried to work on it at table, but couldn’ because the power
outlet didn’t supply enough juice to charge the laptop. So, I was
forced to switch to my phone for entertainment while waiting several hours
for my flight, despite having a massive gaming laptop in my bag.&lt;/p&gt;
&lt;h3&gt;It turns out I’m noise picky.&lt;/h3&gt;
&lt;p&gt;When building or purchasing a computer, something to consider is noise. With my
previous machines, I didn’t worry about it. I’d joke to myself, “Noise? Why
should I be concerned about that? I’m not building a recording studio”! After
several years of focusing on how I &lt;em&gt;actually&lt;/em&gt; use my computing devices, I’ve
noticed that I am very picky with noise, and on a related note, heat. Every time
I hear the fans kick up, I start to worry that I am over-taxing the system and
start to monitor the biggest culprits. When running taxing processes, I watch
the cpu temps and hate seeing them high.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/cYD0wkA7BA-479.webp 479w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/cYD0wkA7BA-479.jpeg&quot; alt=&quot;Temp Sensors&quot; width=&quot;479&quot; height=&quot;194&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Parallel Compression on the Bonobo Spiked the Temps.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I hoped the extra &lt;em&gt;thickness&lt;/em&gt; of the Bonobo would help with any heating issues.
While it might have a little bit, when you shove a decent GPU into a laptop…
it’s going to give off heat. Additionally, when using a powerful CPU to it’s
full potential, it is very difficult for a laptop to compete with a
desktop-level cooling system. The Bonobo is a very powerful laptop, but the loud
fans prevented me from actually &lt;em&gt;using&lt;/em&gt; it to its full potential, because I
would immediately start shutting down processes when the noise kicked up. I know
the fans spin up to &lt;em&gt;help&lt;/em&gt; with thermals, the Bonobo was just a tad to hot and
loud for my taste.&lt;/p&gt;
&lt;h3&gt;A Desktop Replacement?&lt;/h3&gt;
&lt;p&gt;My issues with the Bonobo boiled down to the fact that based on &lt;em&gt;my current
computing use-case&lt;/em&gt;, the Bonobo functioned less as a
&lt;em&gt;desktop-replacement-laptop&lt;/em&gt;, and more of just a &lt;em&gt;shitty, somewhat portable,
desktop&lt;/em&gt;. Most of the time, I am just browsing or writing code on the machine in
front of me. If I need more power, I connect remotely to a bigger, better,
computer. I also never game “on the go”, so if I want to have a computer for
gaming, it makes much more sense to eventually have a half-decent desktop (or
invest in an eGPU), which will fit my needs much better that even high-end
gaming laptops.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/gOahsH3Hlg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/sold-my-bonobo/gOahsH3Hlg-1200.jpeg&quot; alt=&quot;Bonobo Power Cord&quot; width=&quot;1200&quot; height=&quot;720&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The power cord alone made it difficult to pick up the Bonobo and move it elsewhere.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The Bonobo is great if you sit it on a desk and use it as your full computer. I
did not. I have a ton of periphery devices, and started to even use it in
clam-shell mode, making the nice 17&amp;quot; screen a waste. Additionally, I hated
having to unplug and re-plug all the devices when I wanted to move the computer.
&lt;em&gt;In theory&lt;/em&gt;, I could move the Bonobo from my desk into the other room and work
with my wife at the kitchen table whenever we wanted, but I &lt;em&gt;rarely&lt;/em&gt; did.
Unplugging all my devices and fishing the massive power cable out from behind my
desk (because the battery life is … yea, I don’t think I need to explain
this), was usually enough to prompt me to treat the Bonobo as a desktop, rather
than a laptop.&lt;/p&gt;
&lt;h2&gt;Recent Discoveries &amp;amp; My New Plan&lt;/h2&gt;
&lt;p&gt;A while back, I bought a &lt;a href=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/&quot;&gt;used x230 Thinkpad&lt;/a&gt;. Last
summer, I finally purchased a &lt;a href=&quot;https://www.ebay.com/itm/Lenovo-Thinkpad-Mini-Docking-Station-Series-3-USB-3-0-T410-L420-L520-X230-T530/272238176342?epid=562055390&amp;amp;hash=item3f62a8e856:g:PbIAAOSw2x1XMPHU:sc:USPSPriority!02703!US!-1&quot;&gt;used docking
station&lt;/a&gt;
for it, so I could try out a docked setup. I loved it. It matched my needs
exceptionally. I was able to have &lt;em&gt;all&lt;/em&gt; of my periphery devices attached to the
dock and thus, the x230, but with the push of a button I could detach the laptop be
on my way. The setup worked so well in fact, that I started to use my x230 as my
desk computer, and would remote to my Bonobo on the other side of the room when
I needed it. At that point, I realized I might want to switch things up.&lt;/p&gt;
&lt;p&gt;I want to conclude by again stating that my System76 Bonobo Extreme was &lt;em&gt;not&lt;/em&gt; a
bad machine. It is great when used as a standalone, semi-portable computer. I
didn’t use it that way, and as a result, it just wasn’t for me anymore. I
learned that a quite, cool, portable, but dockable, laptop is ideal for my main
device &lt;em&gt;right now&lt;/em&gt;, even if it isn’t as powerful. Unfortunately, System76
doesn’t offer such a machine at this time. So, my plan is to replace the Bonobo
with newer thinkpad + dock combination. Hopefully that combination will give me
a nice dock setup that I have grown to appreciate and love.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>New Happy Hacking Keyboard Pro 2</title>
    <link href="https://ryan.himmelwright.net/post/new-hhkb-pro2/" />
    <updated>2017-12-28T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/new-hhkb-pro2/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/CabqcS_40S-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/CabqcS_40S-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After years of using a Happy Hacking Keyboard Lite, I have &lt;em&gt;finally&lt;/em&gt; decided to splurge on a Happy Hacking Professional 2 keyboard. And I love it.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;em&gt;Note: I have been slowly chipping away at this post for the last few months. I have been very busy, but I should be able to get back into the swing of writing again.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;My history with the HHKB Lite&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/XngeNerAxP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/XngeNerAxP-1200.jpeg&quot; alt=&quot;Getting the HHKB Keyboard&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Computer (HHKB lite included) during my senior year of college.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I started using Linux during my senior year of high school. In college, I started diving deeper down the rabbit hole, learning more about shells, window managers, and programming. By senior year, I found myself sitting in front of an &lt;a href=&quot;http://www.archlinux.org&quot;&gt;Arch Linux&lt;/a&gt; install, configured with a tiling window manger to organize the scattering of VIM and VIM-like terminal applications I used every day. To be kind to my wrists, I wanted something that was optimized at the hardware level for my heavy &lt;em&gt;CTRL&lt;/em&gt; key use. This is when I first learned about the HHKB. However, as a broke college student, I opted for the much cheaper &lt;em&gt;lite&lt;/em&gt; version to test out. I enjoyed the layout so much, that years later when I needed to replace a keyboard at work, I had them order me a HHKB lite (again, the Pro was very expensive).&lt;/p&gt;
&lt;h2&gt;Time for a Mechanical Keyboard&lt;/h2&gt;
&lt;p&gt;After years of using the standard rubber dome HHKB lite, I wanted to finally switch to a true mechanical keyboard. I searched long and hard, looking at different layouts designs, and learning about the various switch types (mostly the commony &lt;a href=&quot;https://en.wikipedia.org/wiki/Cherry_(keyboards)&quot;&gt;cherry MX switches&lt;/a&gt;). After purchasing and playing with a switch tester, I confirmed that I would likely enjoy MX clear switches best. They are quiet enough I could still reasonably use them in an office, but still had the nice tactile bump that I love (more than what I experienced on the Browns). Additionally, I knew that I wanted to stick with a small, minimalist layout, so I exclusively looked at &lt;a href=&quot;https://deskthority.net/wiki/60%25&quot;&gt;60% keyboards&lt;/a&gt;, with the occasional &lt;a href=&quot;https://deskthority.net/wiki/40%25&quot;&gt;40%&lt;/a&gt; board catching my eye.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/sSzHXxMx73-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/sSzHXxMx73-1200.jpeg&quot; alt=&quot;The Pok3r Keyboard&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Pok3r Keyboard.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Like many, I eventually landed at the &lt;a href=&quot;https://deskthority.net/wiki/Vortex_Pok3r&quot;&gt;Vortex Pok3r&lt;/a&gt;. It is beautiful, with a minimal, but sturdy appearance, and has a MX clear variant. I had made my decision, but at the time, new boards weren’t available. I decided to sit back and wait to see if it would drop on &lt;a href=&quot;https://www.massdrop.com/&quot;&gt;Massdrop&lt;/a&gt; any time soon. I had waited years to purchase a mechanical keyboard, and I could wait a little longer to get the one I wanted.&lt;/p&gt;
&lt;h2&gt;The Planck Drop&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/FlQFmkMhx0-1023.webp 1023w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/FlQFmkMhx0-1023.jpeg&quot; alt=&quot;The Planck Keyboard&quot; width=&quot;1023&quot; height=&quot;682&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Planck Keyboard.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While I was waiting for a Pok3r drop… something interesting caught my eye on Massdrop… the &lt;a href=&quot;https://www.massdrop.com/buy/planck-mechanical-keyboard&quot;&gt;Planck keyboard&lt;/a&gt;… The planck is a keyboard kit that you solder together to create a programmable, &lt;em&gt;ortholinear&lt;/em&gt;, 40% keyboard. When I say &lt;em&gt;programmable&lt;/em&gt;, I don’t mean that you can just bind some keys. The Planck is fully open and the firmware C code is available on Github for modification. So, you can actually customize and hack together the firmware, which was very appealing to me. So I joined the drop.&lt;/p&gt;
&lt;p&gt;After joining, I purchased a soldering kit so that I could construct the keyboard once it finally shipped. Unfortunately, the planck drop had &lt;em&gt;several&lt;/em&gt; delays, but as I previously stated, I didn’t waiting. The planck got delayed &lt;em&gt;so&lt;/em&gt; much, that they eventually allowed people to cancel their order and receive a refund. I thought the order cancellation was something you &lt;em&gt;opted in&lt;/em&gt; to do, but it was actually an &lt;em&gt;opt out&lt;/em&gt; situation… so my lack of response had my order canceled, pre-maturely ending my planck experience.&lt;/p&gt;
&lt;h2&gt;Deciding on the HHKB Pro&lt;/h2&gt;
&lt;p&gt;With my planck order canceled, and a refund imminent, I started to look at other options again. The main contender was still the Pok3r. I started researching it again and learning about how I could program the layers to have a layout I would like… and eventually realized I just kept re-creating the infamous HHKB layout.&lt;/p&gt;
&lt;p&gt;So I started to consider the HHKB again. My main reasons for not getting it originally was that it is a good bit more expensive than the majority of mechanical keyboards on the market. However, after waiting months for the planck, and noticing I kept comparing everything to the HHKB, I realized I would eventually end up with an HHKB anyway, and decided to just go for it now. Decision made.&lt;/p&gt;
&lt;h2&gt;Getting the HHKB&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/i8x_CSq7CN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-hhkb-pro2/i8x_CSq7CN-1200.jpeg&quot; alt=&quot;Getting the HHKB Keyboard&quot; width=&quot;1200&quot; height=&quot;1200&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My HHKB&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Once I decided on the HHKB, (and it was approved by my wife :P ), I purchased it on Amazon. The one thing I did contemplate about was which color to get. I didn’t know if I wanted to get the black or white model. I knew I wanted printed key-caps, and that made the white board very appealing. The printed keys on it look &lt;em&gt;so crisp&lt;/em&gt;. Eventually, I decided on the dark gray because it matches everything else I own, whereas the white board would drastically stick out.&lt;/p&gt;
&lt;p&gt;I placed the order and within a few days, it was on my doorstep (well, the neighbor’s, but that’s an issue with our post…).&lt;/p&gt;
&lt;h2&gt;What I like&lt;/h2&gt;
&lt;h3&gt;Layout&lt;/h3&gt;
&lt;p&gt;As already mentioned, I love they HHKB layout. It was a bit tricky to get accustomed to at first, but that was &lt;em&gt;several years&lt;/em&gt; ago at this point. The HKKB Pro does have a few difference in layout when compared to the lite version (the pro does not have a bottom left &lt;code&gt;fn&lt;/code&gt; key, or any dedicated arrow keys). However, I had prepared myself for this change over the last few years by only using the right &lt;code&gt;fn&lt;/code&gt; key and the &lt;code&gt;fn&lt;/code&gt; arrow keys. The hardest thing to adjust to was the slightly different sized &lt;code&gt;alt&lt;/code&gt; and &lt;code&gt;super&lt;/code&gt; keys on the left (due to the missing &lt;code&gt;fn&lt;/code&gt; key).&lt;/p&gt;
&lt;p&gt;I have learned to appreciate all the little HHKB layout differences. This includes the “delete” by default backspace key, with the &lt;code&gt;~&lt;/code&gt; key above it, and the &lt;code&gt;ESC&lt;/code&gt; key next to &lt;code&gt;1&lt;/code&gt;. Last but not least, &lt;strong&gt;the CTRL in the CAPS location&lt;/strong&gt; &lt;em&gt;(with CAPs still available as &lt;code&gt;fn&lt;/code&gt; + &lt;code&gt;Tab&lt;/code&gt;)&lt;/em&gt;. I honestly think this should be the standard keyboard layout.&lt;/p&gt;
&lt;h3&gt;PBT key-caps&lt;/h3&gt;
&lt;p&gt;I didn’t expect the PBT key-caps to be such a big deal, but I absolutely love them. I could tell from images that they would &lt;em&gt;look&lt;/em&gt; better (especially the white ones), but I didn’t anticipate how great they would feel. They are solid, but with a soft touch, and are a &lt;em&gt;much&lt;/em&gt; better improvement over every keyboard I have previously owned/used.&lt;/p&gt;
&lt;h3&gt;The Switches&lt;/h3&gt;
&lt;p&gt;I wasn’t sure how I would like the topre switches after getting excited for Cherry MX clears. However, I seem to really like them… at least on the HHKB. I have heard that they feel different on the HHKB compared to other Topre keyboards due to the HHKBs plastic back. I’m not sure if this is true or not, but I like the result. Key presses are smooth but at the same time very crisp. Being a heavy typist, I often bottom out, but when I do it feels very soft, and not harsh at all. I figure that if I am going to bottom out on my keys regardless of what keyboard I have, it is probably best to handle it how the HHKB does.&lt;/p&gt;
&lt;h3&gt;… Can I say sound?&lt;/h3&gt;
&lt;p&gt;I love the sound of typing on this keyboard. While it is definitely an audible keyboard, it isn’t nearly as obnoxious as louder clicky switches, like MX Blues or Greens. It has a soft, muted, lower pitched sound as I type (compared to normal “clicky” switches). I really do enjoy hearing it, and find myself sometimes taking off my headphones to listen to it if I need motivation to get more characters on the screen.&lt;/p&gt;
&lt;h2&gt;What I don’t like&lt;/h2&gt;
&lt;h3&gt;Cost&lt;/h3&gt;
&lt;p&gt;As I’ve mentioned earlier in the post, probably the &lt;em&gt;biggest&lt;/em&gt; downside to the HHKB is the substantial cost. At &amp;gt;$230 USD, it is expensive, even for mechanical keyboards. The cost is the single problem that kept me from buying it all these years. Eventually, I realized it is what I wanted, and it would likely last me &lt;em&gt;years&lt;/em&gt;, so it all works out. Additionally, if I didn’t like the PRO, or didn’t want it anymore, they &lt;em&gt;do&lt;/em&gt; have an incredible resale value, so I could likely get a large chunk of my money back fairly easily.&lt;/p&gt;
&lt;h3&gt;Transporting it (related to cost)&lt;/h3&gt;
&lt;p&gt;While the cost issue is mitigated after just biting the bullet and hitting the “check out” button, there is another minor annoyance that is &lt;em&gt;related&lt;/em&gt; to the high price. Ideally, I’d like to use my HKKB whenever I am at the computer. Being a software developer… I use a computer for 8+ hours a day at work. So, I like to use my keyboard at work… but I also like to bring it home. Ideally, I’d get two: one for home and one for at the office. At the high price point though… I might have to wait to make that purchase. As far as keyboards go, I’d say the HHKB is probably one of the best travel boards out there. It’s light, but sturdy, and there is even a [travel case designed specifically for it](&lt;a href=&quot;https://www.amazon.com/Carrying-Happy-Hacking-Keyboard-PD-KB01SD/dp/B000FHSNFW/ref=sr_1_1?ie=UTF8&amp;amp;qid=1514499138&amp;amp;sr=8-1&amp;amp;keywords=HHKB+case&quot;&gt;https://www.amazon.com/Carrying-Happy-Hacking-Keyboard-PD-KB01SD/dp/B000FHSNFW/ref=sr_1_1?ie=UTF8&amp;amp;qid=1514499138&amp;amp;sr=8-1&amp;amp;keywords=HHKB+case&lt;/a&gt; go). So far I’ve been using the box to transport it, but am going to get the traveling case soon. That should hold me over until it is justifiable to get a second board. One day…&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I actually was just gifted the carrying case for Christmas. I will check out how it works in the new year. It should at the very least be better than the cardboard box it came in…&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;“Mac” mode / Delete “fix”&lt;/h3&gt;
&lt;p&gt;One benefit of getting the HHKB &lt;em&gt;Pro&lt;/em&gt; over the &lt;em&gt;lite&lt;/em&gt; version, is that it has some media key functionality (volume, start/stop). However, these keys only work when the dip switches are configured to put the board in “Mac mode”. In addition to enabling the media keys, it changes a few other things, like changing the iconic HHKB “Delete” key to a more traditional “Backspace” default. Now, I understand that 99% of people probably welcome this change with open arms. However… after using the HHKB lite for so many years, I have &lt;em&gt;really&lt;/em&gt; become accustomed to the “delete” key there, and actually prefer it now. With how I jump around my editor/terminals with emacs bindings, I have actually developed a workflow where I use &lt;em&gt;delete&lt;/em&gt; more often than &lt;em&gt;backspace&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I cannot figure out a way to have &lt;em&gt;both&lt;/em&gt; the &lt;em&gt;mac mode&lt;/em&gt; and “delete key default” functionality enabled at the same time. So… I have opted to just forgo the new media keys, which is unfortunate. It’s a nice upgrade from the &lt;em&gt;lite&lt;/em&gt; version, and a shame I can’t take advantage of it just because I &lt;em&gt;prefer&lt;/em&gt; the default layout of the keyboard.&lt;/p&gt;
&lt;h3&gt;Missing more “Modern” Features/Perks&lt;/h3&gt;
&lt;p&gt;The HHKB being a “&lt;em&gt;classic&lt;/em&gt;”, seems to be lacking in some of the more “modern” features of high-end keyboards. There is no backlighting, no custom programming/macro setups, or wireless connectivity. I even believe that the two USB ports on the back are USB 2, not 3 (let alone C). While it would be &lt;em&gt;nice&lt;/em&gt; to have some of these features in the HHKB, I’m not hurting &lt;em&gt;too badly&lt;/em&gt; for them… &lt;em&gt;yet&lt;/em&gt;. A modern refresh would be amazing though.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;All in all, it is a great keyboard and I plan to use it for a long time. Typing on it feels and sounds so natural, and it has the same layout I have grown to love over the last few years. I am thrilled to finally upgrade to the pro, and wish I did so years ago.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Website Switched to Hugo</title>
    <link href="https://ryan.himmelwright.net/post/website-switched-to-hugo/" />
    <updated>2017-09-02T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/website-switched-to-hugo/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-switched-to-hugo/h8_Qr70fcf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-switched-to-hugo/h8_Qr70fcf-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Fort Rodney, St Lucia&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/&quot;&gt;last post&lt;/a&gt;, I stated that &lt;em&gt;within a few days&lt;/em&gt;, I would officially generate and publish the website using &lt;a href=&quot;https://gohugo.io&quot;&gt;Hugo&lt;/a&gt;. I then proceeded to publish &lt;em&gt;that&lt;/em&gt; post using Hugo. Close enough. If I remember correctly, I ended that post listing off a few tasks that I wanted to complete &lt;em&gt;before&lt;/em&gt; switching the site (oops). Well… they’re complete &lt;em&gt;now&lt;/em&gt;. Here’s how.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Create Single Pages (About/Homelab)&lt;/h3&gt;
&lt;p&gt;This wasn’t so hard to do once setup, but it took a little while for me to get to that point. I was unaware that I needed to configure a layout for the pages, which was the source of most of my confusion.&lt;/p&gt;
&lt;p&gt;After that realization, I created a new layout directory (in my theme directory) for each of the pages to add: &lt;code&gt;/layouts/about/&lt;/code&gt; and &lt;code&gt;/layouts/homelab/&lt;/code&gt;. I then copied the &lt;code&gt;/layouts/post/single.html&lt;/code&gt; file into the two directories  to use as a template for the two new layouts (&lt;code&gt;/layouts/about/about-page.html&lt;/code&gt; and &lt;code&gt;/layouts/homelab/homelab-page.html&lt;/code&gt;). The pages only required a basic layout that would inject the &lt;code&gt;.Content&lt;/code&gt; from the markdown files. Additionally, I tweaked the header slightly to display an “&lt;em&gt;updated on&lt;/em&gt;” date, rather than a “&lt;em&gt;posted on&lt;/em&gt;” date.&lt;/p&gt;
&lt;p&gt;With the templates made, I  constructed a new &lt;code&gt;/content/pages/&lt;/code&gt; category, and added  &lt;code&gt;about.md&lt;/code&gt; and &lt;code&gt;homelab.md&lt;/code&gt; files to it. In both files, I defined the &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;layout&lt;/code&gt; parameters, so that the new layouts would be used. Lastly, I used the &lt;code&gt;menu&lt;/code&gt; parameter to declare that each page would be part of the main menu.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; About
&lt;span class=&quot;token key atrule&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2017-08-28T09:51:18-04:00&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; about
&lt;span class=&quot;token key atrule&quot;&gt;menu&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-150&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; about&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;page
&lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; img/header&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;images/park&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;books.jpg
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Setting up an RSS Feed&lt;/h3&gt;
&lt;p&gt;It turns out that &lt;a href=&quot;https://gohugo.io/templates/rss/&quot;&gt;Hugo ships with its own RSS 2.0 template&lt;/a&gt; by default. When I first saw this, I thought that I may still have to dash off a layout or markdown page for the feed, but even that was unnecessary. Each “content” section (&lt;em&gt;ex: post or pages&lt;/em&gt;) has an RSS automatically generated at &lt;code&gt;/section-name/index.rss&lt;/code&gt;. I don’t need a feed for my static pages, so I just found the &lt;a href=&quot;https://ryan.himmelwright.net/post/index.xml&quot;&gt;feed for my posts&lt;/a&gt;. To make it easily accessible, I added a menu link.  Without a defined markdown file for the rss feed page, I needed another way to add to the navigation menu. I accomplished this by adding the following code to the bottom of my &lt;code&gt;config.toml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;menu.main&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  name = &quot;RSS&quot;
  url = &quot;/post/index.xml&quot;
  weight = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is how both the &lt;code&gt;Home&lt;/code&gt; and &lt;code&gt;Archives&lt;/code&gt; links were already been added to the navigation bar by default in the theme. I just added another item for the RSS link, and adjusted the weight to have it to show up at the end of the menu. That was it.&lt;/p&gt;
&lt;h3&gt;Check how the posts display&lt;/h3&gt;
&lt;p&gt;One task I needed to complete was going through and editing each post. The main issue that requiring a fix, which I &lt;a href=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/#image-size&quot;&gt;explained in the previous post&lt;/a&gt;, was that the image tags needed to be switched from markdown to html syntax. While I originally planned to set a &lt;code&gt;width=100%&lt;/code&gt; parameter for the image tags, I learned that using a &lt;code&gt;max-width: 100%;&lt;/code&gt; worked much better for my use-case. I know this can be handled in the overall css file, but I like explicitly defining how to handle each image when I write a post.&lt;/p&gt;
&lt;p&gt;While editing the posts, I noticed that the &lt;a href=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/#summary-setup&quot;&gt;post summaries&lt;/a&gt; weren’t displaying the content that I intended them to. I have hugo configured so that I manually cut off the summary location using a &lt;code&gt;more&lt;/code&gt; tag in the markdown.But it didn’t appear to be doing that.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-switched-to-hugo/IKvID4iDqZ-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-switched-to-hugo/IKvID4iDqZ-1200.jpeg&quot; alt=&quot;Hugo Logo&quot; width=&quot;1200&quot; height=&quot;985&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;A: An over-extended post summary&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;B: The extra content of the post that was included in the summary&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;C: The corrected post summary.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of the summaries seemed to extend beyond the cutoff point, still including the next section header, and some of the section’s content. After further inspection, I noticed that I had a space on either side of the “more” in the tag. So, I had to go through and delete the extra spaces in each post.&lt;/p&gt;
&lt;h3&gt;Next/Prev Posts&lt;/h3&gt;
&lt;p&gt;The main “&lt;em&gt;Small Tweak&lt;/em&gt;” that I wanted to figure out was setting up navigation links at the bottom of each post page. I added some code between the  &lt;code&gt;and&lt;/code&gt; tags of my theme’s &lt;code&gt;/layout/post/single.html&lt;/code&gt; file. I first made a section using &lt;code&gt;if&lt;/code&gt; statements to establish the “Next Post” and “Prev Post” header line. Then in a second block, I placed the actual links using on the next line using  &lt;code&gt;with&lt;/code&gt; statements. The &lt;code&gt;if&lt;/code&gt; and &lt;code&gt;when&lt;/code&gt; statements are required so that previous and next posts are  only linked &lt;em&gt;if they exist&lt;/em&gt;. So, the first and last post will only display one of the two links.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Next Post/ Previous Post Links --&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Next Post:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;


     &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Prev Post:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;


&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;br&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;


    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 40%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;


    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 40%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I have continued to enjoy using Hugo. The few issues I had with it, keep being quickly dismissed as I learn more about the system. It is simple to use, but at the same time provides an immense level power and control. I will continue tweak the site here and there, but I think for the most part, I can consider the transition complete. Which I am very happy about :).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Transitioning Website to Hugo</title>
    <link href="https://ryan.himmelwright.net/post/website-transition-to-hugo/" />
    <updated>2017-08-27T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/website-transition-to-hugo/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/D66Xi9w-_1-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-transition-to-hugo/D66Xi9w-_1-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Fort Rodney, St Lucia&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While I have loved using &lt;a href=&quot;http://cryogenweb.org&quot;&gt;Cryogen&lt;/a&gt; to create this website for over a year and a half now, I have started the transition to using another static website generator. Specifically, I have been experimenting with &lt;a href=&quot;https://gohugo.io&quot;&gt;Hugo&lt;/a&gt;. This post will detail why I am switching, what I have ported over thus far, and what still needs to be completed before generating the official site with hugo.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;From Cryogen to Hugo&lt;/h2&gt;
&lt;p&gt;My departure from Cryogen really has nothing to do with the project itself. It is a prime example of what &lt;a href=&quot;https://clojure.org/&quot;&gt;clojure&lt;/a&gt; is capable of, and I feel that more people should give it a shot. Recently though, I’ve been itching to switch up my website’s theme a bit. While I fancy my &lt;a href=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/&quot;&gt;Immutable Theme&lt;/a&gt; I created a couple months ago, it isn’t quite doing what I had hoped for. I love dark themes, but the type of posts I’ve been creating really don’t look good with them. I like to add diagrams, code snippets, and images to all of my posts. Diagram posts look wonderful with a white background, but are garbage in a dark theme. This difference was glaring last week as I switched between my markdown editor’s preview window (default white theme), and the website’s live preview view, while writing my &lt;a href=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/&quot;&gt;reverse tunnels&lt;/a&gt; post.&lt;/p&gt;
&lt;p&gt;Similar to the logic I employed earlier this month when &lt;a href=&quot;https://ryan.himmelwright.net/post/back-to-solus/&quot;&gt;switching back to Solus&lt;/a&gt;, I thought that if I was going to scrap my theme and start from scratch, I might as well check out different website generator. I had been keeping an eye on the various site generators, but the one I considered the most (and even dabbled with a bit last month), was &lt;a href=&quot;https://gohugo.io&quot;&gt;Hugo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What I’ve Done So Far&lt;/h2&gt;
&lt;p&gt;I started the process of transporting my website from cryogen to hugo. So far, my experience with Hugo has been great. Here is what I’ve done:&lt;/p&gt;
&lt;h3&gt;Installed &amp;amp; Setup a test Hugo site&lt;/h3&gt;
&lt;p&gt;Obtaining and installing Hugo on my computers was very simple, as it was in the reops (Solus). I just had to run the command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; eopkg it hugo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After I had hugo installed, I experimented with creating new website projects for a few minutes before finally creating one to start my transition. To create a new website, I used the command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;hugo new site ryan-hugo-test&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This created a new directory with all the project’s default core files, and adhering to the required hugo &lt;a href=&quot;https://gohugo.io/content-management/organization/&quot;&gt;directory structure&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Like cryogen, hugo can spin up a website in a test server during development. To do this, use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hugo serve -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: I used the &lt;code&gt;-D&lt;/code&gt; flag to additionally include any files marked as “drafts.”&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Setup a Theme and started tweaking it&lt;/h3&gt;
&lt;p&gt;With the hugo site generated, I wanted to setup a proper theme. After sampling a handful of demo sites from hugo’s &lt;a href=&quot;https://gohugo.io/themes/&quot;&gt;theme page&lt;/a&gt;, I decided on the startbootstrap-clean-theme. I’ve seen it used on other sites, and I think it is a commonly used theme with other site generators. However, it is clean and simple, with a white-background base like I wanted. Additionally, I love having header images for posts and pages. It lets me better personalize the website by exclusively using images I’ve photographed myself.&lt;/p&gt;
&lt;p&gt;To get the theme, I cloned it from git:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone git@github.com:humboldtux/startbootstrap-clean-blog.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: this particular theme has an abundant amount of features, so it is a good idea to copy the provided example config.toml and build off of it. This is one reason why I started with configuring the theme right off the bat.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Before I started ripping into the theme too much, I copied the theme directory to make my own version, and set the &lt;code&gt;theme&lt;/code&gt; line in my &lt;code&gt;config.toml&lt;/code&gt; to reflect the change. I also went through all the lines of the example &lt;code&gt;config.toml&lt;/code&gt; and  changed them accordingly.&lt;/p&gt;
&lt;p&gt;With the theme setup, and the configuration edited, I started making some minor tweak to the site. The main tweak I made was to add the &lt;code&gt;Summary&lt;/code&gt; contents to the post list on the home page. I currently use this feature in Cryogen, so all of my posts are written to support it. Summaries are baked into hugo, but I needed to edit the theme to include it on the posts page. To achieve this, I edited the &lt;code&gt;/layouts/post/summary.html&lt;/code&gt; file of the theme slightly:&lt;/p&gt;
&lt;p&gt;original:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-preview&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h3&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-subtitle&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-meta&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      
 &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;with post summaries:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-preview&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h3&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-subtitle&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-meta&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;summary&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post-meta&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Click to Read More --&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also setup and fixed the code syntax highlighting. I first installed pygments, which again was &lt;em&gt;very&lt;/em&gt; easy to do on my Solus computers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo eopkg it pygments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, I just made sure that the language was specified at the start of each code snippet. I ran into an issue where the colors for the code were poorly selected, resulting in some invisable text. Apparently, this is a common issue, and I found some &lt;em&gt;.css&lt;/em&gt; code to add to the &lt;code&gt;pre&lt;/code&gt; section of my &lt;code&gt;/static/css/clean-blog-min.css&lt;/code&gt; file that resolved the issue.&lt;/p&gt;
&lt;p&gt;If all went well, the code above should have proper syntax highlighting.&lt;/p&gt;
&lt;h3&gt;Dumped my Backlog of Posts and altered the header content&lt;/h3&gt;
&lt;p&gt;To test out if the website worked properly, I wanted to import all of my posts. First… I needed to figure out where to put them. Hugo has a slightly different file structure, but I eventually figured out that I could create a &lt;code&gt;/content/post/&lt;/code&gt; directory, and dump them there.&lt;/p&gt;
&lt;p&gt;Cryogen, written in &lt;a href=&quot;https://clojure.org/&quot;&gt;clojure&lt;/a&gt;, uses a clojure map for the post’s meta data. Hugo on the other hand, uses a several &lt;a href=&quot;https://gohugo.io/content-management/front-matter/&quot;&gt;font matter formats&lt;/a&gt; (&lt;code&gt;toml&lt;/code&gt;, &lt;code&gt;yaml&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;) for meta data. So, I had to convert the post headers. I’m sure that there was a simple, programmatic way, or even a tool, created to accomplish this… but I just did it by hand. It wasn’t so bad. I used emacs.&lt;/p&gt;
&lt;p&gt;After importing the markdown files for the posts, I needed to add all the images the posts contain. Again, this took a tiny bit of research to figure out the file structure, but I quickly learned that anything in the &lt;code&gt;/static/&lt;/code&gt; directory gets copied to the site’s root directory when the site is compiled. So, I was able to copy my cryogen &lt;code&gt;/img/&lt;/code&gt; folder directly to &lt;code&gt;/static/img/&lt;/code&gt; in hugo, and all my image paths worked out-of-the-box!&lt;/p&gt;
&lt;p&gt;Unfortunately, I immediately noticed that some images in the posts were massive, and not constrained to the content width. I looked for a solution, and tried editing the &lt;code&gt;css&lt;/code&gt;, but I eventually just started to convert the markdown syntax images, to use normal html &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags, with a &lt;code&gt;width=100%&lt;/code&gt; parameter. Ultimately, this gives me a bit more power with how I set images anyway.&lt;/p&gt;
&lt;h3&gt;Made a bunch of header images&lt;/h3&gt;
&lt;p&gt;After fixing my in-post images, I started to play with post/page header images. I figured out where they were located in the theme, and added two of my own to replace the defaults. I read that a header image can be set with the &lt;code&gt;image == &amp;quot;...&amp;quot;&lt;/code&gt; option in a post/page’s font matter… so I went a little crazy. I stayed up late browsing through some of my photos, and converting them to header images (I did this by shading them a just a tad, so the overlaying text is legible).&lt;/p&gt;
&lt;h2&gt;What Needs to be Done&lt;/h2&gt;
&lt;p&gt;This post has turned out to be a giant monster, so I’ll be brief here and turn the sub-sections into a quick list instead.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create Single Pages (About/Homelab)&lt;/strong&gt; - I still need to figure out and create my website’s single pages. These include the &lt;em&gt;About Me&lt;/em&gt; and &lt;em&gt;Homelab&lt;/em&gt; pages. The content of those pages has also been slipping out of date… but I won’t let that hold me up from switching the site over first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Setting up an RSS Feed&lt;/strong&gt; - Similar to the task above, I want to make sure I have an RSS feed for the blog configured, and accessible in the main menu.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check how the posts display&lt;/strong&gt; - I need to go through the posts and make sure they are displaying content correctly. I’ll check that links work, images aren’t massive, code syntax languages are set, etc. This actually shouldn’t be as bad as it sounds because I’ve done much of it already.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Small tweaks&lt;/strong&gt; - There are bound to be a few tweaks here or there that I’ll notice and want to changed (&lt;em&gt;example from above: I’ve already added the post summaries to the home page post list&lt;/em&gt;). One item that comes to mind is adding &lt;em&gt;next&lt;/em&gt; and &lt;em&gt;previous&lt;/em&gt; post markers at the bottom of each post. Again, I won’t let that hold me up though.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, that’s about it. I’ll work now on editing and publishing this post, and with any luck, the website should switch over to the Hugo generated one within a few days. I hope you enjoy it!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Simple Reverse SSH Tunnels</title>
    <link href="https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/" />
    <updated>2017-08-26T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/KQobyZZbjV-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/KQobyZZbjV-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;676&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Cliff Walk, Newport, RI USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Reverse SSH tunnels are &lt;em&gt;very&lt;/em&gt; useful and simple to setup, but can be a bit
tricky to figure out at first. So, here’s a brief and simple guide on how to
easily create reverse tunnels.&lt;/p&gt;
&lt;!--more --&gt;
&lt;h3&gt;SSH Tunnels&lt;/h3&gt;
&lt;p&gt;A secured shell (SSH) tunnel is an encrypted tunnel, created with a connection using the ssh protocol. It can be thought of as a pipe between two computers that data travels through. Being encrypted, people outside the pipe can only see that data packages are traveling through it, but cannot read the actual contents of the package. SSH tunnels are used to securely connect between devices, as well as forward ports between devices. For example, if I am working on a website hosted on port 3000 of my internally networked laptop, I can use a ssh tunnel to forward that port to one on a public server so that friends can view the website.&lt;/p&gt;
&lt;h3&gt;Reverse Tunnels&lt;/h3&gt;
&lt;p&gt;Reverse tunnels are just like normal ssh tunnels except… well… in reverse. This means that I can connect to a remote computer, and have &lt;em&gt;its&lt;/em&gt; port tunneled to &lt;em&gt;me&lt;/em&gt;, which can be very handy. This is largely used for one particular usecase: providing easy, temporarily, ssh access to computers behind a network and/or firewall.&lt;/p&gt;
&lt;p&gt;To make all of this (hopefully) easier to understand, I have drafted up a few diagrams. For the example, lets say I am away and my wife wants me to fix something on her laptop. Unless I have her &lt;em&gt;properly&lt;/em&gt; configure the router to forward ssh traffic to her laptop, I normally cannot do this. Additionally, she might be at a friends house, office, or other public place where there is no access to the router controls (well, she &lt;em&gt;shouldn’t&lt;/em&gt;). So, her laptop doesn’t have a direct public IP address, but our server &lt;em&gt;does&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/dKzKBSuS_3-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/dKzKBSuS_3-1200.jpeg&quot; alt=&quot;Computer behind firewall&quot; width=&quot;1200&quot; height=&quot;334&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Two computers (at least one without a direct public IP), both with access to a cloud server with a public IP.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Because my wife can connect to the public cloud server, she can initiate a reverse tunnel from her laptop, with the server. The tunnel directly connects the server to her laptop.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/WJJgCN9LZB-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/WJJgCN9LZB-1200.jpeg&quot; alt=&quot;Computer behind firewall&quot; width=&quot;1200&quot; height=&quot;364&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The laptop computer creates a reverse ssh tunnel to the cloud server.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;On my end, I first ssh to the server from my computer. Once the tunnel is started, I can then ssh again to a specified local port on the server, and it will tunnel me directly to her laptop. This will give me a command prompt as if I was sitting with an open terminal at her computer.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/d_qwlZRXhg-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/simple-reverse-ssh-tunnel/d_qwlZRXhg-1200.jpeg&quot; alt=&quot;Computer behind firewall&quot; width=&quot;1200&quot; height=&quot;425&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;With the reverse tunnel setup, the first computer can ssh to the second via the cloud server and tunnel connection.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I am done working, my wife can close the tunnel, and I will no longer be able to access her computer.&lt;/p&gt;
&lt;h3&gt;Creating The Reverse Tunnel&lt;/h3&gt;
&lt;p&gt;SSH tunnels can be initiated on linux easily from the command line (assuming ssh is setup and properly configured). To create a reverse tunnel, use the &lt;code&gt;-R&lt;/code&gt; flag. After the flag, provide what I call the “path” of the tunnel. So, the server’s port where the tunnel will be found, the host of that port (I almost always use &lt;code&gt;localhost&lt;/code&gt;), and the port to be tunneled. Lastly, make sure to specify the &lt;em&gt;IP&lt;/em&gt; or &lt;em&gt;hostname&lt;/em&gt; of the remote computer just like in a typical plain &lt;code&gt;ssh hostname&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -R remote-port:localhost:local-port host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -R 19999:localhost:22 meowth
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Connecting&lt;/h3&gt;
&lt;p&gt;After setting up the tunnel, the initializing computer can be accessed &lt;em&gt;from the server&lt;/em&gt; by ssh’ing to the &lt;em&gt;remote-port&lt;/em&gt; of &lt;em&gt;localhost&lt;/em&gt; (defined above):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -p 19999 localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should start an ssh session to the initializing computer. In my example, this is the laptop.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;SSH tunnels are a fascinating and useful piece of technology. The ssh protocol makes modern computing much more secure, and easier to manage multiple computers. That’s &lt;em&gt;reverse&lt;/em&gt; tunnels in a nutshell. Have fun tunneling!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Back to Solus</title>
    <link href="https://ryan.himmelwright.net/post/back-to-solus/" />
    <updated>2017-07-31T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/back-to-solus/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-to-solus/0MMOH3WeVA-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-to-solus/0MMOH3WeVA-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Well, that was faster than I expected. This past weekend, I wiped the Fedora installation on my main computer, and replaced it with Solus. My Fedora install broke, and I needed to use a Fedora Live CD to fix it. I thought that if I had to use a live CD to fix the issue, I might as well just do a clean install. With the idea of a clean install in my head… I thought (possibly influenced by my recent &lt;a href=&quot;https://ryan.himmelwright.net/post/dabbling-with-go/#motivation&quot;&gt;motivation&lt;/a&gt; to play with Go), that I might as well do the clean install with Solus…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Nvidia Issues&lt;/h2&gt;
&lt;p&gt;Basically, what I think happened was that my Nvidia drivers got messed up during an update. When I rebooted my computer, my screens were black. This happened no matter which kernel I booted into from Grub. I assumed it was a graphics problem and preceded to switch to another tty to login and fix the problem. I was able to get to a CLI login screen (with the normal &lt;code&gt;user:&lt;/code&gt; prompt) and tried login in. However, after typing in my user name and hitting enter, instead of being promoted for a password, I received an “Error Logging In” message. So, I tried the same thing with the root user account. Same thing. After typing in a user name, it yelled at me. There was no way to log in.&lt;/p&gt;
&lt;p&gt;I jumped into the IRC chat and started asking around for advice. As always, everyone was very helpful and tried to help me troubleshoot. They shared my bewilderment. We came to the conclusion that it must of been something with the Nvidia drivers. I was told that it is common to have issues when installing the drivers from Nvidia’s web site instead of the &lt;a href=&quot;https://rpmfusion.org/&quot;&gt;RPM Fusion Repos&lt;/a&gt; (I intended to install from the RPM fusion repos, but I went to the nvidia site to find out what version to should use… and then just downloaded it from there. My bad :P).&lt;/p&gt;
&lt;p&gt;Anyway, I quickly realized that at the very least, I would have to boot up and log into a Live CD to fix the problem. As stated in the intro paragraph, while waiting for the ISO image to download I thought that if I have to load up the CD, I might as well just do a clean Fedora 26 install. A couple minutes later as I was booting up the Live CD, I through that as long as I was reinstalling my OS… l should consider installing… Solus.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=IVpOyKCNZYw&amp;amp;t=101&quot;&gt;Video Link&lt;/a&gt; – &lt;em&gt;Linus Torvalds famously flipped off Nvidia during a Q&amp;amp;A&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Installed Solus&lt;/h2&gt;
&lt;p&gt;With the Fedora live environment all booted and ready, I started downloading the latest Solus ISO (Budgie) on my other laptop. Once the ISO image was mounted to a drive, I booted it up and started installing Solus. After the install, I immediately updated because I knew there have been a TON of updates since the last ISO snapshot (mid-April). When I rebooted, I absolutely loved what I saw. It is amazing how much Solus and Budgie have improved, even during my short distro-hopping vacation.&lt;/p&gt;
&lt;p&gt;Looking at my post history, it may appear as though I used Fedora for a few days, became fed up with it, and came running back to Solus. That is not entirely true. The reality is that I sat on writing my &lt;a href=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/&quot;&gt;Fedora post&lt;/a&gt; for a long time. I used Fedora for about a month, and it was generally a great experience. If it didn’t break, I probably would have stayed for a bit longer. When considering a new install though, I realized that I had missed Solus.&lt;/p&gt;
&lt;p&gt;As of now, my plan is to keep using Solus, at least on my main computer. I am also hoping to get more involved with the project, and start packing up some software again. Maybe I can even help with some debugging and development. We shall see…&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Dabbling with GO</title>
    <link href="https://ryan.himmelwright.net/post/dabbling-with-go/" />
    <updated>2017-07-30T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/dabbling-with-go/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/dabbling-with-go/JHeCgbLCqf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/dabbling-with-go/JHeCgbLCqf-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Portland, ME USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After spending most of this month’s dedicated learning time working on &lt;a href=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/&quot;&gt;system&lt;/a&gt;, &lt;a href=&quot;https://ryan.himmelwright.net/post/creating-a-git-remote/&quot;&gt;server&lt;/a&gt;, and &lt;a href=&quot;https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/&quot;&gt;network&lt;/a&gt; activities,  I have been itching to start some home programming again. To motivate myself, I even considered dabbling with a new programming language… and with very little internal debate, I decided to just &lt;a href=&quot;https://golang.org&quot;&gt;Gopher&lt;/a&gt; it (I’m so sorry).&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What is GO?&lt;/h2&gt;
&lt;p&gt;Go (sometimes referred to as “golang”) is an open source programming language, developed in 2007 by a team at Google (Robert Griesemer, Rob Pike, and Ken Thompson). Distributed under a &lt;a href=&quot;https://golang.org/LICENSE&quot;&gt;BSD-like license&lt;/a&gt;, Go is also maintained and developed by open source volunteers all over the world.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/dabbling-with-go/3s7eoPVm8X-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/dabbling-with-go/3s7eoPVm8X-1200.jpeg&quot; alt=&quot;A blue gopher&quot; width=&quot;1200&quot; height=&quot;587&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The GO mascot.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Like C, it is a compiled and statically typed language. Unlike C, Go includes garbage collection and memory safety features, as well as other design aspects that are reminiscent of modern dynamic languages like python (ex: type inference and package &lt;code&gt;import&lt;/code&gt; statements). Lastly, Go has a concurrent programming implementation that utilizes what are known as ‘&lt;a href=&quot;https://tour.golang.org/concurrency/1&quot;&gt;goroutines&lt;/a&gt;’. Goroutines are special light-weight “threads” that can process many concurrent tasks. All of these features work together to form an extremely relevant language for modern computing.&lt;/p&gt;
&lt;p&gt;Like the programming language itself, the Go project summarizes all of this in a nice and concise statement on their &lt;a href=&quot;https://golang.org/doc/&quot;&gt;documentation page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Go programming language is an open source project to make programmers more productive.
Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It’s a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;I’ve wanted to try &lt;a href=&quot;https://golang.org/&quot;&gt;Go&lt;/a&gt; for a long time now. A couple of months ago, I was trying to decide if I should pursue learning Rust or Go. At the time, I ultimately ended up experimenting with &lt;a href=&quot;https://www.rust-lang.org/en-US/&quot;&gt;Rust&lt;/a&gt;. I was toying with the idea of creating an experimental lisp, and Rust’s feature set makes it great language for writing compilers and interpreters. However, while Rust is a great language, it can be quite complicated to learn. Go on the other hand, is apparently simpler and quick to grasp, which is &lt;a href=&quot;https://golang.org/doc/faq#creating_a_new_language&quot;&gt;why it was created&lt;/a&gt; in the first place.&lt;/p&gt;
&lt;p&gt;When the Solus team announced that they were declaring Go a first class citizen language of the project, my interest peaked. &lt;a href=&quot;https://github.com/solus-project/solbuild&quot;&gt;Solbuild&lt;/a&gt;, the Solus package build system was written in Go. In the announcement, the project stated that they intended to use Go for building tools. True to that statement, this past week, Ikey (the creator of Solus) published a patreon post detailing the new repo manager (&lt;a href=&quot;https://github.com/solus-project/ferryd&quot;&gt;ferryd&lt;/a&gt;) he’s been working on… again in Go. After reading that post, I decided it was time for me to give it a Go (again, very sorry).&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;I first went to the Go &lt;a href=&quot;https://golang.org/doc/install&quot;&gt;install page&lt;/a&gt; to figure out if there were any odd components to install. It didn’t appear so.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; Before progressing any further, I should point out that if you just want to &lt;em&gt;try&lt;/em&gt; go, the project has a little embedded editor/compiler on the &lt;a href=&quot;https://golang.org&quot;&gt;home page&lt;/a&gt; of the website. Beyond that, they have an amazing &lt;a href=&quot;https://tour.golang.org/welcome/1&quot;&gt;Go tour&lt;/a&gt; that also has an embedded programming environment, and can be completed entirely in a web browser.&lt;/p&gt;
&lt;p&gt;After playing with the online editor online for a bit, I decided that I wanted to install Go on my system. I was on my NixOS laptop at the time, so I installed go with the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nix-env -i go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Alakazam, it was&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install golang
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(… and then when I jumped back to Solus on Alakazam, it was &lt;code&gt;sudo eopkg it golang&lt;/code&gt;… but more on that later…)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, I walked through the &lt;a href=&quot;https://golang.org/doc/install#testing&quot;&gt;test your install&lt;/a&gt; steps, building a simple “hello world” app to make sure everything was working properly. This was especially important, given that I was on nixOS, which can sometimes be picky with system paths and environment variables. Luckily, everything worked fine. If I continue down the Go path, I might write a “Getting started” post to detail how to setup a proper Go environment on Linux.&lt;/p&gt;
&lt;h2&gt;First Steps&lt;/h2&gt;
&lt;p&gt;After confirming my install, I went back and continued  &lt;a href=&quot;https://tour.golang.org/welcome/1&quot;&gt;A Tour of Go&lt;/a&gt; to better learn the language. I am interested to eventually read the &lt;a href=&quot;https://golang.org/doc/effective_go.html&quot;&gt;Effective Go&lt;/a&gt; documentation. It will be interesting to simply &lt;em&gt;read&lt;/em&gt; the correct style and conventions for the language, instead of trudging through a holly war to find answers.&lt;/p&gt;
&lt;p&gt;After working on the tutorial for awhile, I started playing around with the language on my own.  Below is a snippet of code I wrote while fooling around. It’s nothing fancy. I was impressed though with how easy it really was to pick up the basics of the language and get going. I am excited to learn more.&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; main

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;runtime&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello, world&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;getOS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// I know this is dumb&lt;/span&gt;

	x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;74&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt;
	sum &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sumInts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	fsum &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sum: %v&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Factorial of sum(%v): %v&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sum&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fsum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sumInts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	sum &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; y
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sum
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	os &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; runtime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GOOS
	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OS: %v&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// impure&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Migrating from Solus to Fedora for now</title>
    <link href="https://ryan.himmelwright.net/post/solus-to-fedora/" />
    <updated>2017-07-25T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/solus-to-fedora/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/ZG3b_qWFQX-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/ZG3b_qWFQX-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For awhile, I have been debating the idea of switching back to Fedora (from Solus). At least on my main computer. First, let me state this right up front: I am still &lt;em&gt;very&lt;/em&gt; satisfied with Solus. I think it is one of the best current Linux distros, and I want to still contribute to the project. However, there are a few reasons why Solus isn’t the best fit for my needs &lt;em&gt;right now&lt;/em&gt;, and I will highlight them below.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why Switch&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/1ny3QWnOkG-1038.webp 1038w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/1ny3QWnOkG-1038.jpeg&quot; alt=&quot;Solus and Fedora Logos&quot; width=&quot;1038&quot; height=&quot;501&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Solus (Left) and Fedora (right) Project Logos.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first phrase stated on the Solus Project homepage is “Solus is an operating system that is designed for &lt;strong&gt;home computing&lt;/strong&gt;.” I find this to be true, and Solus does a great job at it. The Linux community needs a few good, focused, distros. While I have been using Solus for my “&lt;em&gt;home&lt;/em&gt;” computing, the computing tasks I’ve focused on recently do not fall into the category of standard &lt;em&gt;home computing&lt;/em&gt; use. Recently, my main top computing activities and goals are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Writing (okay… but still)&lt;/li&gt;
&lt;li&gt;Running all sorts of VMs&lt;/li&gt;
&lt;li&gt;Trying various Server Technologies
&lt;ul&gt;
&lt;li&gt;ZFS&lt;/li&gt;
&lt;li&gt;KVM&lt;/li&gt;
&lt;li&gt;Containerization Technologies (LXC, Docker, …)&lt;/li&gt;
&lt;li&gt;Ansible and other automation tools&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Learning about Clustering
&lt;ul&gt;
&lt;li&gt;OpenMP&lt;/li&gt;
&lt;li&gt;High Availability&lt;/li&gt;
&lt;li&gt;Distributed File systems&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Trying to get involved with some other Open Source Projects
&lt;ul&gt;
&lt;li&gt;Fedora (Infrastructure, Dev)&lt;/li&gt;
&lt;li&gt;NixOS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you can see, many of the above items are not desktop based, but really
&lt;em&gt;server&lt;/em&gt; based operations. Solus is &lt;em&gt;not&lt;/em&gt; a server distribution, as it doesn’t
&lt;em&gt;try&lt;/em&gt; to be one. Which is a good thing. It is focused on its audience. I
just happen to not be in that audience at the moment.&lt;/p&gt;
&lt;p&gt;Additionally, one of my goals for the near future is to transform my long-time Proxmox server into a &lt;a href=&quot;https://www.centos.org/&quot;&gt;CentOS&lt;/a&gt; box. Using Fedora on my main workstation does help me get accustom to that environment. It also allows me to more accurately test out ideas before I plan the big move.&lt;/p&gt;
&lt;p&gt;Lastly, I had been eyeing up the Plasma desktop, and wanted to try that out again. At the time of writing this post, Solus doesn’t fully support the Plasma desktop (yet). However, Fedora &lt;em&gt;does&lt;/em&gt; have a &lt;a href=&quot;https://spins.fedoraproject.org/kde/&quot;&gt;KDE Plasma Desktop Spin&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The Switch&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/fcorfIJxBc-812.webp 812w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/fcorfIJxBc-812.jpeg&quot; alt=&quot;Solus and Fedora Logos&quot; width=&quot;812&quot; height=&quot;497&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Screenfetch on my new Fedora Install.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I eventually (and somewhat sporadically), made the switch to Fedora. I switched
the week before the 26 release, so I decided to start with 25, and then upgrade
later (although I did test out 26 on my other laptop). This let me ensure that
the 26 issues were ironed out before upgrading. I also got to test out the &lt;code&gt;dnf&lt;/code&gt;
system upgrade process. I recorded &lt;em&gt;post-switch&lt;/em&gt; notes during the last few weeks
to document how everything went.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Well… I did it. I switched to Fedora on Alakazam yesterday. I went with the
Fedora 25 KDE spin and did all of the hoops to get that up and going. Not only
is it taking some time to get used to Fedora again, but I am needing to
reacquaint myself with the KDE environment… it does seem different that other
Plasma setups I’ve used in the past… But I like it.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I got used to it in no time:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Alakazam is doing well on Fedora. I’ve been enjoying it and think I will stay on
it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Updates&lt;/h2&gt;
&lt;p&gt;I ran some updates that I thought &lt;em&gt;might&lt;/em&gt; be problematic, based
on &lt;a href=&quot;https://ryan.himmelwright.net/post/back-on-arch/#fedora&quot;&gt;previous issues&lt;/a&gt; I’ve encountered with Fedora. I
made sure to note the results as well. The first notable update was from the
first time I updated the kernel, and the second was  upgrading from
Fedora 25 to 26. Both updates went very smoothly without any issues:&lt;/p&gt;
&lt;h3&gt;Kernel Upgrade&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;So I am about to do my first Kernel update since being on Fedora again (on
Alakazam). We’ll see how the video drivers respond… To note, I am still on 25
so it hopefully won’t be too bad…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;…reboot…&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No issues whatsoever :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Upgrade to Fedora 26&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/_r3utP_BDX-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/solus-to-fedora/_r3utP_BDX-1200.jpeg&quot; alt=&quot;Solus and Fedora Logo&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I upgraded my Fedora 25 Plasma Install to Fedora 26.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Other than some odd issues with the GUI tool, the upgrade from 25 to 26 was
smooth and uneventful.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I just upgraded Alakazam from Fedora 25 KDE, to Fedora 26 this morning. I couldn’t really get the
graphical installer to start, but that could be because I have several desktop environments setup
(Plasma &amp;amp; Gnome), so it may have been confused (I was using the Gnome Software App in Plasma…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I just did the upgrade using the (command line) dnf upgrade tool, like I normally use, and it worked
wonderfully. It even looks like my nvidia drivers stayed and my monitors were configured correctly
after rebooting. The only difference is I don’t seem to have the same Plasma animations I had
before, but that is fine, and likely part of the update.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So that it. Those are my reasons for switching (for now), and the results of my
switch. I am still happy with Fedora, at least on Alakazam, and will likely
remain on it until I have a convincing reason to leave.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating a git Repo Remote</title>
    <link href="https://ryan.himmelwright.net/post/creating-a-git-remote/" />
    <updated>2017-07-19T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/creating-a-git-remote/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/creating-a-git-remote/cnYjZVzNJM-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/creating-a-git-remote/cnYjZVzNJM-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Boston, MA USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For over a year or so, I have been using a
self-hosted &lt;a href=&quot;https://about.gitlab.com/&quot;&gt;Gitlab&lt;/a&gt; to host all of my private repos.
For a few months now, I have been meaning to migrate my Gitlab repos to bare, minimal
ones, hosted directly on a server. The majority of my code/configs are hosted
publicly on &lt;a href=&quot;https://github.com/himmAllRight&quot;&gt;my Github&lt;/a&gt; page, and it really
doesn’t make sense to maintain a full Gitlab instance for the few (like…2)
private repositories that I keep. Moving the git repos to new ones right on the server
is actually fairly simple. For such a simple process, all the guides I saw
online went way above and beyond what I needed. So, here are the &lt;em&gt;two&lt;/em&gt; steps I
did to migrate my repos.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;SSH Keys&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;pre&lt;/em&gt; (and somewhat optional) step to is to setup ssh key authentication. If ssh keys are not configured, git will prompt for the password of the repo’s host user . When using a git service (ex: Github or Gitlab), this is usually unknown, so ssh keys are required. When rolling your own remote git repo, the password will likely be known. Still, setting up ssh authentication makes the process easier and more secure. If you
do not know how to configure ssh keys, I included a small ssh key how-to &lt;a href=&quot;https://ryan.himmelwright.net/post/Ansible-On-Pi-Cluster/#ssh&quot;&gt;here&lt;/a&gt; in &lt;a href=&quot;https://ryan.himmelwright.net/post/Ansible-On-Pi-Cluster&quot;&gt;a previous post&lt;/a&gt;. Many of the git guides out there call for creating a &lt;code&gt;git&lt;/code&gt; user and setting up ssh keys with that user. This is a great idea if multiple people need access to the git repo. However, for my purposes I will use my username, as I will be the only one accessing it (which in my case is a good thing).&lt;/p&gt;
&lt;h3&gt;Creating Server Repo&lt;/h3&gt;
&lt;p&gt;Once ssh authentication is configured, ssh into the remote server that will host the git repository. Creating the remote repo is a simple process. First, make a directory for the repo (the normal convention is to use a &lt;code&gt;.git&lt;/code&gt; ending: &lt;code&gt;REPO-NAME.git&lt;/code&gt;). Next, jump into the created directory (&lt;code&gt;cd&lt;/code&gt;) and run the command &lt;code&gt;git init --bare&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir REPO-NAME.git
cd REPO-NAME.git
git init --bare
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will initialize the repository inside that directory. The &lt;code&gt;git init&lt;/code&gt; command is used to create a git repository. The &lt;code&gt;--bare&lt;/code&gt; option flag tells git to treat it as a bare repository. Bare repositories do not contain a working or checked out copy of the source files. Thus, the plain &lt;code&gt;git init&lt;/code&gt; command creates a &lt;em&gt;working&lt;/em&gt; repo, while &lt;code&gt;git init --bare&lt;/code&gt; is used to create a &lt;em&gt;sharing(server) repo&lt;/em&gt;. This allows the working repositories of many developers to be synced with the server repo.&lt;/p&gt;
&lt;h3&gt;Cloning Repo&lt;/h3&gt;
&lt;p&gt;If the remote git repository is a totally new repository, it can be cloned down to a working directory on a developer machine fairly easily:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone user@hostname:REPONAME.git
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pointing Local Repo to Server&lt;/h3&gt;
&lt;p&gt;However, I already had an existing working repository that I wanted to sync with the new remote shared repo. With the remote repo initialized, I wanted to point my existing git repository on the local machine to it. To do this, enter the directory of the git repository, and edit the config the (&lt;code&gt;.git/config&lt;/code&gt;). To redirect the repo to point to the new remote, edit the &lt;code&gt;url&lt;/code&gt; line to the location of the repo:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;username@hostname:reponame&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd Server-Node-Files
vim ./git/config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After pointing to the new remote, feel free to push the content to it: (Only push everything (*) if it is desired)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add *
git commit -m &amp;quot;First push to new Remote&amp;quot;
git push origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that’s it. Enjoy spinning up and using your own personal git repositories!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Encountered Issues Setting Up Ubiquiti Network</title>
    <link href="https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/" />
    <updated>2017-06-29T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/s-FCS4iR8o-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/s-FCS4iR8o-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;WWI Memorial Park, North Attleboro, MA USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;This past weekend, I setup my new ubiquiti network. It actually took up a good
portion of Sunday, because I ran into a few minor issues.
Fortunately/Unfortunately, these issues were mostly because it my first time
configuring this type of setup, and there was a lot of trial and error. The basic
network is now all configured and has been running great. It was a good day and I
learned a lot :). In fact, I am confident that if I had to start over from
scratch, the process would take me about 10-15 minutes. Just to be sure, I’m
going to quickly jot down the major pain points I experienced my first time around.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Trouble Connecting to the EdgeRouter-x for Initial Setup&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/Td-MIOkpSQ-949.webp 949w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/Td-MIOkpSQ-949.jpeg&quot; alt=&quot;During initial setup, I was connecting the router wrong&quot; width=&quot;949&quot; height=&quot;606&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;During initial setup, I was connecting the router wrong&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The edgerouter needed to be directly connected to a computer during its initial
setup, to make the EdgeOS configuration wizard accessible. The
instructions clearly stated to connect an ethernet cable from my laptop to the
&lt;code&gt;eth0/POE&lt;/code&gt; port on the edgerouter, but I guess I didn’t believe them.&lt;/p&gt;
&lt;p&gt;Instead, I plugged the ethernet cable from my modem into &lt;code&gt;eth0&lt;/code&gt;, and my computer
to &lt;code&gt;eth1&lt;/code&gt;. That didn’t work. However, once I &lt;em&gt;properly&lt;/em&gt; connected the devices (and
manually set a static IP on my laptop, &lt;code&gt;192.168.1.2&lt;/code&gt; for example), I was able to access
the configuration page in my browser via &lt;code&gt;https://192.168.1.1&lt;/code&gt; (don’t forget
the &lt;em&gt;s&lt;/em&gt; in &lt;em&gt;https&lt;/em&gt;). Lesson Learned: manuals are (&lt;em&gt;usually&lt;/em&gt;) not out to get you.&lt;/p&gt;
&lt;h2&gt;Setting up POE and Connecting the AP&lt;/h2&gt;
&lt;p&gt;This was not actually an issue I encountered, but rather a confusion. I was unsure what the best setup for
the &lt;a href=&quot;https://en.wikipedia.org/wiki/Power_over_Ethernet&quot;&gt;POE&lt;/a&gt; hardware was. At
first, I had the POE adaptor connected between the edgerouter and the AP,
because I wasn’t sure if it could optimally power both devices. I found
an
&lt;a href=&quot;https://www.youtube.com/watch?v=f7FeYsJqotc&amp;amp;list=PLDBkup9c8YMgZaE50hAjP7rbbVriTlyQf&amp;amp;index=1&quot;&gt;informative guide&lt;/a&gt; that
indicated that the POE adaptor could indeed power both.&lt;/p&gt;
&lt;pre class=&quot;language-scheme&quot;&gt;&lt;code class=&quot;language-scheme&quot;&gt;Modem  --
        &lt;span class=&quot;token identifier&quot;&gt;|
         --&gt; POE Adaptor --&gt; (*eth0*)  edgerouter-x  (*eth4*) --&gt; AP Lite
        |&lt;/span&gt;
Power  --&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Digram describing the correct link up&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once I swapped the cables all around, I had to go into the router
configuration and enable the POE for &lt;code&gt;eth4&lt;/code&gt;. Afterwards, the AP lit up,
indicating that it was connected and powered.&lt;/p&gt;
&lt;h2&gt;Issues linking/configuring the AP&lt;/h2&gt;
&lt;p&gt;This was the problem I spent the most time on. I had to install the
configuration software for the Ubiquiti access point, but the “Linux binary” was
a .deb, and I didn’t feel like extracting the contents of the package so that I
could install it on Solus (yet). So instead, I spun up a few Ubuntu VMs to try
it out, but I over-looked the fact that VMs on my laptop use a
different subnet (192.168.&lt;strong&gt;122&lt;/strong&gt;. *) for the virtual network. I had hoped that
because the VM’s network was routed through my laptop, which was connected
directly to the edgerouter, it would still be able to see the access point.
Regardless… the AP couldn’t see the VM and vice versa. Finally, I admitted
that the issues were most likely caused by the 192.168.122.* IP address that the VM was
assigned.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/iHVStFjZt4-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/issues-setting-up-ubiquiti-network/iHVStFjZt4-1200.jpeg&quot; alt=&quot;Image of the pokemon, Venomoth&quot; width=&quot;1200&quot; height=&quot;635&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;I spun up a new Ubuntu VM (Venomoth) to host the Ubifi controller&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;By this point, I had also learned that the Linux software is more of a server
service, and not a GUI desktop application. So, I concluded that spinning up a
dedicated VM on my server to host the wifi controller was worth it. Virtual
machines hosted on my server automatically get configured on the main
subnet, so it also resolved my issue. I was able to detect and configure the
access point immediately. This setup made more sense anyway, as I can always
connect to the AP controller by going to the VM’s IP on my browser,  just like I
can with my router.&lt;/p&gt;
&lt;p&gt;Well, that was all of my setup “&lt;em&gt;issues&lt;/em&gt;”. There was nothing I would consider to
be an actual &lt;em&gt;issue&lt;/em&gt;, just some confusions of an Ubiquiti/POE first-timer. Like
I stated earlier, I am sure I could redo the setup in about 15 minutes without any
issues… 10 now that I recorded everything in the post!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Home Network Ubiquiti Upgrade</title>
    <link href="https://ryan.himmelwright.net/post/upgrading-network-to-ubiquiti/" />
    <updated>2017-06-26T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/upgrading-network-to-ubiquiti/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/upgrading-network-to-ubiquiti/GcZtSNqdop-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/upgrading-network-to-ubiquiti/GcZtSNqdop-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A few weeks ago, I went back to Pennsylvania for a week to attend my college swim
team reunion, and my brother’s high school graduation. While I was away, the
wifi-router Rebecca and I were using decided to die (of course). When I returned, I
setup our old router as a &lt;em&gt;temporary&lt;/em&gt; fix. It was terrible. So, I began
researching how I should upgrade our network. This time around, I am doing this
&lt;em&gt;correctly&lt;/em&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;The router that died was a Linksys WRT 1900. When I got home, I setup our old
router as a temporary fix. It’s a very basic Linksys E1200. After using it for
just a few hours, I remembered why I hated it so much. It is slower than a
snail, and seems to stop working each day or so, requiring me to unplug and
re-plug it daily (I think it is something with DHCP. It keeps trying to reassign
IPs to devices, and then doesn’t seem to understand how to accept their requests
afterwards). Thus, the more &lt;em&gt;temporary&lt;/em&gt; this solution was, the better.&lt;/p&gt;
&lt;p&gt;If I am redoing our network setup, I want to do it &lt;em&gt;properly&lt;/em&gt; this time,
splitting out the router from the wireless access point using &lt;em&gt;good&lt;/em&gt; hardware and
software. My plan is to get an ubiquity edge-router-x, and pair it with an Ubiquiti
wireless access point.&lt;/p&gt;
&lt;p&gt;Originally I wanted to build a small pfsense box to use as a router, but after
digging a little deeper and doing some research, I saw that the EdgeRouter would
more than meet my needs and is a great start to a network upgrade. Additionally,
at $50 (USD), it truly is a great deal. As for the wireless access point, I was
always considering an UniFi device, and I thought the EdgeRouter should
pair rather seamlessly, considering both products are made by Ubiquiti.&lt;/p&gt;
&lt;p&gt;From what I read, the EdgeRouter is a great router with an okay firewall, while
pfsense is an amazing firewall that can do routing. So, if I want to dig more
into pfsense in the future, I can still set up a firewall box, and connect it in front of
the router.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Modem --&amp;gt; pfsense --&amp;gt; EdgeRouter-x -&amp;gt; devices
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So… I think I’ve finalized my decision and will purchase the items soon. I
will update when I get them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update (06/24/2017): I have followed through with this
purchase, and the items arrived from Amazon. Expect a post or two about my
experience setting them up (and yes, I added this update before actually
publishing this post)&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Refreshing my i3 setup with i3blocks</title>
    <link href="https://ryan.himmelwright.net/post/started-Using-i3blocks/" />
    <updated>2017-06-22T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/started-Using-i3blocks/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/IBtxDiDQC9-999.webp 999w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/IBtxDiDQC9-999.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;999&quot; height=&quot;563&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;a rofi window&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The last couple of weeks I have shifted back to using the i3 window mangers. When I fired it up, my fingers danced across the keyboard, remembering all of the personalized keybindings I have cultivated in my i3 configuration over the years. It is a simple, beautiful setup… well, beautiful minus one of the components. My i3status bar was looking rather bland and dated, especially compared some of the i3 setups posted by all the cool kids over at &lt;a href=&quot;https://www.reddit.com/r/unixporn/&quot;&gt;/r/unixporn&lt;/a&gt;. I decided it was time for a refresh.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;i3status&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/rmu_7VCj1Z-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/rmu_7VCj1Z-1200.jpeg&quot; alt=&quot;&quot; One=&quot;&quot; of=&quot;&quot; my=&quot;&quot; simple=&quot;&quot; i3status=&quot;&quot; setups&quot;&quot;=&quot;&quot; width=&quot;1200&quot; height=&quot;29&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;One of my simple i3status setups.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I first configured i3 several years ago, I used i3status because it was easy to use with i3 and did everything by default. Over time, I learned how to create and modify &lt;a href=&quot;https://github.com/himmAllRight/dotfiles/blob/master/i3/.config/i3/i3status.conf&quot;&gt;my own .i3status.conf&lt;/a&gt; so that I could get it to play nice with un-standerd configurations (ex: &lt;code&gt;/Data&lt;/code&gt; partitions and such). While i3status served me well for many years, using the same-old setup has become boring. I started noticing several other nice looking status bar tools being used in i3 setups, and wanted to try them out.&lt;/p&gt;
&lt;h2&gt;Polybar&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/1M_g08i8e--1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/1M_g08i8e--1200.jpeg&quot; alt=&quot;The example polybar&quot; width=&quot;1200&quot; height=&quot;29&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The example polybar.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first bar I saw and tried was &lt;a href=&quot;https://github.com/jaagr/polybar&quot;&gt;polybar&lt;/a&gt;. I started with it because some of the examples look awesome. It looks very modern and has an infinite number of features. I set it up and was able to use the example bar just fine. However, when I started to customize my own, I started to run into a few issues. The biggest issue was polybar not detecting my work-space names, along with other elements. Additionally, due to the support for several window managers, the example configuration file seemed cluttered, and I was never sure what I could edit, and what I should delete. After some frustration, I decided put it aside for now. I might come back to it one day when I’m bored.&lt;/p&gt;
&lt;h2&gt;i3blocks&lt;/h2&gt;
&lt;p&gt;Next, I learned of &lt;a href=&quot;https://github.com/vivien/i3blocks&quot;&gt;i3blocks&lt;/a&gt;. It appeared to have everything I wanted in a status bar, yet remained simple, respecting the &lt;a href=&quot;https://i3wm.org/docs/i3bar-protocol.html&quot;&gt;i3bar protocol&lt;/a&gt;. So I gave it a whirl.&lt;/p&gt;
&lt;h3&gt;Downloading from the Repos&lt;/h3&gt;
&lt;p&gt;Just like installing any other package on Linux, I decided to first check to see it it was in the Solus Repos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo eopkg sr i3block
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It was. So, I installed it (&lt;code&gt;sudo eopkg it i3blocks&lt;/code&gt;) and started learning how to setup my configuration.&lt;/p&gt;
&lt;h3&gt;Fonts&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/vPx_P5_Ln3-654.webp 654w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/vPx_P5_Ln3-654.jpeg&quot; alt=&quot;A Very Small Sampling of the Awesome Fonts&quot; width=&quot;654&quot; height=&quot;357&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A *Very Small* Sampling of the Awesome Fonts&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first hurdle I came across when first launching i3blocks was that I did not have all the fonts used in the default configuration installed. I temporarily removed the special fonts from the config, just so I could check that everything was working. But what’s the fun in that? One of the biggest reasons I wanted to redo my bar was to have cool modern icon fonts! So, I found the &lt;a href=&quot;http://fontawesome.io/&quot;&gt;font awesome&lt;/a&gt; package in the Solus repos and installed it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo eopkg it font-awesome-ttf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the awesome fonts at my disposal, I had a plethora of icons to use. So I went through and picked out icons for each of the work-space tabs and status markers.&lt;/p&gt;
&lt;h3&gt;Finding Git Repos&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/ZA1MY_otcD-764.webp 764w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/ZA1MY_otcD-764.jpeg&quot; alt=&quot;Example of Modules in new i3-block repo&quot; width=&quot;764&quot; height=&quot;102&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Example of Modules in new i3-block repo&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While researching how to configure i3blocks, I started encountering several different GitHub repos and forks of the project. Some forks seemed to have additional modules that my repo version didn’t. I downloaded and built &lt;a href=&quot;https://github.com/Anachron/i3blocks&quot;&gt;this one&lt;/a&gt; to try out. I then configured i3 to point to the new build instead, and got started setting up my own blocks.&lt;/p&gt;
&lt;h3&gt;Forking my own for Solus Tweaks&lt;/h3&gt;
&lt;p&gt;As I was configuring the individual blocks, I noticed that many of them didn’t work by default on my computers. I dug deeper by opening up and peeking at the actual bash scripts blocks refer to. I noticed that many of them were trying to query data from applications that are &lt;em&gt;not&lt;/em&gt; used in Solus. For example, the &lt;code&gt;battery&lt;/code&gt; block didn’t work for me because it relied on &lt;code&gt;acpi&lt;/code&gt;, which, while often used in Arch Linux for battery information (I use it myself when on arch) is not packaged in Solus. Instead, Solus relies on &lt;code&gt;upower&lt;/code&gt;. To fix my issues, I cobbled together my own &lt;code&gt;battery&lt;/code&gt; bash script, that queried battery information using &lt;code&gt;upower&lt;/code&gt; instead. Note, the &lt;code&gt;BATTERY_ICON&lt;/code&gt; uses the battery fonts (which likely won’t show in the browser). Unless it is charging… then it uses a lightning bolt :) .&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

BATTERY=0
BATTERY_STATE=$(echo &amp;quot;${BATTERY_INFO}&amp;quot; | upower -i $(upower -e | grep &#39;BAT&#39;) | grep -E &amp;quot;state|to&#92; full&amp;quot; | awk &#39;{print $2}&#39;)
BATTERY_POWER=$(echo &amp;quot;${BATTERY_INFO}&amp;quot; | upower -i $(upower -e | grep &#39;BAT&#39;) | grep -E &amp;quot;percentage&amp;quot; | awk &#39;{print $2}&#39; | tr -d &#39;%&#39;)
URGENT_VALUE=10

if [[ &amp;quot;${BATTERY_POWER}&amp;quot; -gt 87 ]]; then
    BATTERY_ICON=&amp;quot;&amp;quot;
elif [[ &amp;quot;${BATTERY_POWER}&amp;quot; -gt 63 ]]; then
     BATTERY_ICON=&amp;quot;&amp;quot;
elif [[ &amp;quot;${BATTERY_POWER}&amp;quot; -gt 38 ]]; then
     BATTERY_ICON=&amp;quot;&amp;quot;
elif [[ &amp;quot;${BATTERY_POWER}&amp;quot; -gt 13 ]]; then
     BATTERY_ICON=&amp;quot;&amp;quot;
elif [[ &amp;quot;${BATTERY_POWER}&amp;quot; -le 13 ]]; then
     BATTERY_ICON=&amp;quot;&amp;quot;
else
    BATTERY_ICON=&amp;quot;&amp;quot;
fi


if [[ &amp;quot;${BATTERY_STATE}&amp;quot; = &amp;quot;discharging&amp;quot; ]]; then
    echo &amp;quot;${BATTERY_ICON} ${BATTERY_POWER}%&amp;quot;
    echo &amp;quot;${BATTERY_ICON} ${BATTERY_POWER}%&amp;quot;
    echo &amp;quot;&amp;quot;
else
    echo &amp;quot; ${BATTERY_POWER}%&amp;quot;
    echo &amp;quot; ${BATTERY_POWER}%&amp;quot;
    echo &amp;quot;&amp;quot;
fi

if [[ &amp;quot;${BATTERY_POWER}&amp;quot; -le &amp;quot;${URGENT_VALUE}&amp;quot; ]]; then
  exit 33
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Over the past few weeks, it seems to work well enough. As I continued to &lt;em&gt;tweak&lt;/em&gt;, or downright &lt;em&gt;create&lt;/em&gt; blocks to work well in Solus, I eventually decided that it might be a good idea to just &lt;a href=&quot;https://github.com/himmAllRight/i3blocks&quot;&gt;create my own fork&lt;/a&gt; of the repo. This way, I can have my own i3blocks repo that works well with Solus. I noticed that the implementation of &lt;code&gt;i3blocks&lt;/code&gt; found in the Solus repos is also using non-Solus items for it’s scripts (ex: acpi for the battery). Maybe one of these days I’ll jump into irc and see how people feel about swapping in mine instead (or at least one that works better in Solus)…&lt;/p&gt;
&lt;h3&gt;Color Update for i3 &amp;amp; rofi&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/p2SFsP8E2P-1000.webp 1000w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/p2SFsP8E2P-1000.jpeg&quot; alt=&quot;New Color Scheme for rofi launcher&quot; width=&quot;1000&quot; height=&quot;562&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New color scheme for rofi launcher&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I started using i3 several years ago, I used &lt;a href=&quot;http://tools.suckless.org/dmenu/&quot;&gt;dmenu&lt;/a&gt; as my launcher (it’s the launcher used in &lt;a href=&quot;http://dwm.suckless.org/&quot;&gt;dwm&lt;/a&gt;, another tiling window manager I used to use). I eventually switched to rofi after seeing it used in some very nice window manager setups (sound familiar?). However, I always used the plain default theme and never bothered to improve it. With all the work I did making i3bars look nice, I thought I should at least make rofi match. Making the color theme wasn’t nearly as hard as I anticipated it to be. That’s mostly because I found and used the rofi theme generator.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/0od7H6g-ow-999.webp 999w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/0od7H6g-ow-999.jpeg&quot; alt=&quot;New rofi window switcher&quot; width=&quot;999&quot; height=&quot;563&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Newly configured rofi window switcher&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While touching up rofi, I learned it can also switch through already opened windows (Previously, I  only used it to launch new programs). I really liked that in my new i3 setup, it also displayed the work-spaces, icon font and all. I immediately bound it to my Super+Tab key for easy use.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/started-Using-i3blocks/i3-gaps-demo.mp4&quot;&gt;Click to watch a demo of my i3gaps setup with i3blocks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thus far, I have been very satisfied with my current i3 setup. Replacing i3status with i3blocks, combined with adding small improvements, for example, the awesome fonts, really helped to enhance the look and feel of my i3 configuration. Additionally, ROFI much better now that it matches everything.&lt;/p&gt;
&lt;p&gt;I am glad to be using i3 again. It is so efficient to use and I love flying around the work-spaces and windows. It is particularly useful on my x230’s 12&amp;quot; screen, as it utilizes all of the limited resolution. If you have never used a tiling window manager in Linux, I strongly suggest you give it a shot. It can take some time to get up and going the first time, but once a custom configuration is built, it always feels like home.&lt;/p&gt;
&lt;p&gt;To show off my new i3 setup with i3bars, I’ve posted a small video at the top of this section. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Exporting Proxmox VMs</title>
    <link href="https://ryan.himmelwright.net/post/exporting-proxmox-vms/" />
    <updated>2017-06-16T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/exporting-proxmox-vms/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/dJHVK0ebZr-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/dJHVK0ebZr-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Pigeon Island National Park, St Lucia&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;For a long time I have been thinking about replacing my server’s proxmox install with vanilla Debian or CentOS, mostly for learning purposes. I would first setup zfs on the new system and import my existing data pools. Then, I would use either a system like ovrit or just plain kvm/lxc to run my VMs and containers. In order to do this though… I have to first figure out how export my containers and VMs running in Proxmox. As it turns out… exporting the VMs wasn’t very hard…&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Exporting Proxmox VM Disk&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/mncuYIeZ5u-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/mncuYIeZ5u-1200.jpeg&quot; alt=&quot;Proxmox logo&quot; width=&quot;1200&quot; height=&quot;193&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My Proxmox VMs are setup on an LVM virtual group, with each virtual drive being a lvm volume passed to the VM. So, I wanted to be able to extract these disks to something I could more easily transfer. I ended up converting the lvm volumes to qcow2 images because it was easy and I’ve actually experienced okay performance with qcow2 on my workstations. Additionally, qcow2 being a single file, is easy to move around and I can always convert them to something else on the final system. To export one of the VMs, I ran the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;qemu-img convert -O qcow2 /dev/pve/vm-108-disk-1 /Data/freebsd-vm.qcow2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was it. It took a few minutes to finish, but honestly that is all I had to do (I’m sure I should of taken a snapshot or something then copy that, but this seemed to work fine). When it completed, I copied the image down to my desktop and decided to test it out with virt-manager.&lt;/p&gt;
&lt;h2&gt;Importing the image to Virt-Manager&lt;/h2&gt;
&lt;p&gt;I opened up virt-manager and selected the button to create a new VM. At the first prompt, instead of selecting my usual “Local install media (ISO image or CDROM)” option, I choose to “Import an existing disk image”.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/g0YiSqhYeR-400.webp 400w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/g0YiSqhYeR-400.jpeg&quot; alt=&quot;import image&quot; width=&quot;400&quot; height=&quot;434&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Import the image.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;At the second menu, I opened up the browse menu to see my already configured locations. From there, I found where I had saved the converted qcow2 image, and selected it.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/6Tqd5Cni8A-750.webp 750w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/exporting-proxmox-vms/6Tqd5Cni8A-750.jpeg&quot; alt=&quot;select qcow2 image&quot; width=&quot;750&quot; height=&quot;534&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Next, I continued setting up the virtual specs (CPU, RAM, etc.) for the machine just as I would with any other VM setup. When I was done, I started the VM and FreeBSD booted right up. I logged in and compared the installed applications and files with the still running proxmox VM. They were identical.&lt;/p&gt;
&lt;p&gt;That’s all I really have for this post. It was extremely simple to export the VMs. I know I’m not fully done yet and still have to import the VMs to the final system, but I’ll save that for a later post. Right now… I have quite a few VM images to convert, so I might as well get started.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Updating the Pi Cluster with Ansible</title>
    <link href="https://ryan.himmelwright.net/post/updating-pi-cluster-with-ansible/" />
    <updated>2017-05-25T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/updating-pi-cluster-with-ansible/</id>
    <content type="html">&lt;p&gt;With Ansible configured on the Pi cluster, it is time to have it do something useful. When working with a clustered system, even the simplest tasks become tedious and time consuming. For example, updating the system. While I could manually update each of the 3 pi nodes, it is not scalable to 10 or 30 nodes, let alone hundreds or thousands. Tools like Ansible, make doing tasks such a supdating clustered systems, trivial again. In this post, I will walk through setting up an Ansible playbook to update my Pi cluster.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h3&gt;Hosts File&lt;/h3&gt;
&lt;p&gt;The first task when using Ansible is to setup the &lt;code&gt;hosts&lt;/code&gt; file. No, not the normal &lt;code&gt;/etc/hosts&lt;/code&gt; file, but the &lt;em&gt;other&lt;/em&gt; one &lt;em&gt;just&lt;/em&gt; for Ansible, which can be found at &lt;code&gt;/etc/ansible/hosts&lt;/code&gt;. Configuring the Ansible hosts file is fairly straightforward. Groups of computers are defined using &lt;code&gt;[brackets]&lt;/code&gt;, with computer ip/hostnames of the group are listed below. For example:&lt;/p&gt;
&lt;p&gt;A nice feature of group definitions is that hierical structures can be constructed using the &lt;code&gt;:child&lt;/code&gt; suffix in order to create groups of groups. For example, for my homelab, I like to make an ansible hosts file that splits out my servers based on their distribution, and then group those by their packaging type. This makes it easier for me to do generic updates, which is what I mostly use ansible for (at this point). So, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ubuntu]
mrmime
geodude

[debian]
ninetales

[fedora]
fedora-test

[centos]
tangels

[arch]
meowth
staryu
diglet

[deb:children]
ubuntu
debian

[rpm:children]
fedora
centos

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For use with the cluster, I kept it simple, although I did opt to create rpi/bpi subgroups:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[cluster:children]
rpis
bpis

[rpis]
pi0
pi1

[bpis]
bpi
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Ping Hosts&lt;/h3&gt;
&lt;p&gt;Once the hosts file is setup, it can be tested using the &lt;code&gt;ping&lt;/code&gt; module. I tested my &lt;code&gt;cluser&lt;/code&gt; group, as well as the &lt;code&gt;rpis&lt;/code&gt; and &lt;code&gt;bpis&lt;/code&gt; subgroups.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible rpis -m ping
ansible bpis -m ping
ansible cluster -m ping
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Assuming the steps of &lt;a href=&quot;https://ryan.himmelwright.net/post/Ansible-On-Pi-Cluster&quot;&gt;the last post&lt;/a&gt; were done correctly, this should work. If not, double check that post and make sure everything looks correct.&lt;/p&gt;
&lt;h3&gt;Playbooks&lt;/h3&gt;
&lt;p&gt;After confirming that the hosts file is properly configured, I started to dig into playbooks. Playbooks are Ansible’s scripting system used to configure, deploy, and orcistrate systems. They can describe ways in which systems should be configured (ex: enable ssh), or outline a set of steps for an IT task (ex: running updates, restarting a server). As stated in the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/user_guide/playbooks.html&quot;&gt;playbook documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“If Ansible modules are the tools in your workshop, playbooks are your instruction manuals, and your inventory of hosts are your raw material.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Playbook files are expressed using &lt;a href=&quot;https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html&quot;&gt;YAML syntax&lt;/a&gt;, which is easy to read, but still powerful. The first step when creating a new playbook, being a YAML file, is to set the header and footer. The header consists of three &lt;code&gt;-&lt;/code&gt;&#39;s at the top of the file, and the footer ends the file with three periods (&lt;code&gt;.&lt;/code&gt;). This indicates the start and end of the document.&lt;/p&gt;
&lt;p&gt;When writing a playbook to update the pi cluster, I first needed to declare what systems the playbook is used with. To do that, I used the &lt;code&gt;hosts&lt;/code&gt; key, and provided it with the &lt;code&gt;cluster&lt;/code&gt; group name, which is defined in my &lt;code&gt;/etc/ansible/hosts&lt;/code&gt; file, as the value.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- hosts: cluster

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the hosts are defined, modules can be added to update the nodes. To list the tasks, I used the &lt;code&gt;taks:&lt;/code&gt; key, with the same indentation as the &lt;code&gt;hosts:&lt;/code&gt; keyword. Instead of using a single value, I provided the &lt;code&gt;tasks:&lt;/code&gt; keyword with a list of things to do. The first task I want to do when updating the nodes is to check that they running and connected. This can be accomplised with the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/modules/ping_module.html&quot;&gt;ping module&lt;/a&gt; that I used earlier in the post. The ping module will try to connect to each node, verify that a usable python is installed, and return &lt;code&gt;pong&lt;/code&gt; upon success. To add the module, I added &lt;code&gt;- ping: ~&lt;/code&gt;, indented, to the line below &lt;code&gt;tasks:&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- hosts: cluster

  tasks:
    - ping: ~
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Apt Module&lt;/h3&gt;
&lt;p&gt;After defining the ping module, I started to get a bit fancier. Well… a little bit fancier. Each node in my pi cluster is running some verison of Ubuntu, which uses apt as it’s package manager. If I wanted to ssh into each node and update them manually, the steps I would follow would be to 1) run the command &lt;code&gt;sudo apt-get update&lt;/code&gt; to update the repository cache, and 2) run &lt;code&gt;sudo apt-get upgrade&lt;/code&gt; to actually install the updates. To recreate these commands in the playbook, I used the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/modules/apt_module.html&quot;&gt;apt module&lt;/a&gt;. To start with updating the repository cache, I added the following lines to my playbook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Update APT package manager repositories cache
  become: true
  apt:
    update_cache: yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;name:&lt;/code&gt; defines the name of the task, and is the text printed out to the console when executing this step of the playbook. Setting the &lt;code&gt;become&lt;/code&gt; key to &lt;code&gt;true&lt;/code&gt; tells Ansible to run the command with privilege escalation (sudo). Lastly, the remaining two lines run the &lt;code&gt;update_cache:&lt;/code&gt; functionality of the apt module.&lt;/p&gt;
&lt;p&gt;With the repositories updated on each node, I can have ansible run the updates by adding the following lines to the playbook (after the cache update ones):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Upgrade installed packages
  become: true
  apt:
    upgrade: dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This set of commands is very similar to the last group. The &lt;code&gt;name:&lt;/code&gt; again provides a description of what the task is doing, and privilege escalation is used again via &lt;code&gt;become: true&lt;/code&gt;. The only difference is that the apt module is using the &lt;code&gt;upgrade: dist&lt;/code&gt; command instead. This will run the updates for any installed packages on the system.&lt;/p&gt;
&lt;h3&gt;Update Cluster Playbook&lt;/h3&gt;
&lt;p&gt;I then had a completed playbook to update the pi cluster:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- hosts: cluster

  tasks:
    - ping: ~

    - name: Update APT package manager repositories cache
      become: true
      apt:
        update_cache: yes

    - name: Upgrade installed packages
      become: true
      apt:
        upgrade: dist
...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last step is to test it out! Playbooks can be executed using the &lt;code&gt;ansible-playbook&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-playbook update-cluster.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When running the playbook, ansible will first attempt to gather facts about each node, and then begin to run each of the tasks defined in the playbook. At each step, it will print out the &lt;code&gt;name&lt;/code&gt; of each task, followed by the status/result for each node. When it completes, all the nodes in the cluster should be updated. Now you can update three+ computers with a single command! Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Configuring Ansible on the Pi Cluster</title>
    <link href="https://ryan.himmelwright.net/post/ansible-on-pi-cluster/" />
    <updated>2017-05-21T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/ansible-on-pi-cluster/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/ansible-on-pi-cluster/6f8UN3bz71-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/ansible-on-pi-cluster/6f8UN3bz71-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://ryan.himmelwright.net/post/Setting-up-the-pi-cluster/&quot;&gt;previous post&lt;/a&gt;, I pieced together my pi cluster, and installed variations of Ubuntu 16.04 Server on each of its nodes. With the cluster built, I quickly needed an easy way to maintain and interact with the system as a whole. This, is where &lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt; comes in. In this post, I will walk through the steps I took to setup Ansible on my Cluster.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Ansible&lt;/h2&gt;
&lt;p&gt;Ansible is an open source, configuration management and automation system. It is written in Python, and financially backed by &lt;a href=&quot;http://www.redhat.com&quot;&gt;Red Hat&lt;/a&gt;. It simplifies the management of groups of computers, through the use of modules (standalone units of work. for example, apt, ping, rpm, etc). Ansible is script-able using simple YAML files, known as playbooks, that define a set of orchestration tasks for one or many computers. These scripts can be edited and version controlled, creating a simple &lt;a href=&quot;https://en.wikipedia.org/wiki/Infrastructure_as_Code&quot;&gt;infrastructure as code&lt;/a&gt; setup.&lt;/p&gt;
&lt;h2&gt;Setting up the User Account&lt;/h2&gt;
&lt;p&gt;When Ansible executes commands on the PIs, it will do so from the user-account (ryan) that I setup in the last post. However, many of these commands will require Root privledges. While I previously setup sudo and added the &lt;code&gt;ryan&lt;/code&gt; account to the &lt;code&gt;sudo&lt;/code&gt; group … it required that I manually enter my password. Ansible did not like this, so I had to update the sudo configuration to allow the &lt;code&gt;ryan&lt;/code&gt; account to run &lt;code&gt;sudo&lt;/code&gt; commands with out a password. To do this, I opened the &lt;code&gt;sudoers&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo visudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and added the following line to the end of the file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ryan  ALL=(ALL:ALL) NOPASSWD: ALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I repeated this on each of the nodes, and afterwards was no longer promted for a password when running &lt;code&gt;sudo&lt;/code&gt; commands. This made Ansible happy.&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;ssh&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Setup SSH Keys&lt;/h2&gt;
&lt;p&gt;Well… &lt;em&gt;almost&lt;/em&gt; happy.&lt;/p&gt;
&lt;p&gt;Ansible’s main method of communication is via ssh, which by default, prompts me for a password when connecting. Ansible &lt;em&gt;really&lt;/em&gt; hates passwords. So, I had to configure ssh to use keys instead. Honestly, this is proabaly a good step to do regardless, now that the &lt;code&gt;ryan&lt;/code&gt; account no longer uses a password when running &lt;code&gt;sudo&lt;/code&gt;. To setup key-based logins, I appended the contents of my main computer’s ssh public key*, to each pi’s &lt;code&gt;authorized_keys&lt;/code&gt; file. This can all be done using a magic one-line pipe command (x3, one for each pi):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat ~/.ssh/id_rsa.pub | ssh pi0 &amp;quot;cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys&amp;quot;
cat ~/.ssh/id_rsa.pub | ssh pi1 &amp;quot;cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys&amp;quot;
cat ~/.ssh/id_rsa.pub | ssh bpi &amp;quot;cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;*Note: If keys are not already generated, they can be created using the command:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Key Only Login&lt;/h3&gt;
&lt;p&gt;To help secure access to the PIs (and to get on Ansible’s good side), I configured sshd to disable password logins, and only allow connections from clients with approved keys. To disable password authentication, I opened the &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; file, found the line containing &lt;code&gt;# PasswordAuthentication yes&lt;/code&gt;, changed the &lt;code&gt;yes&lt;/code&gt; to a &lt;code&gt;no&lt;/code&gt;, and unncommented it by removing the &lt;code&gt;#&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While I was in the &lt;code&gt;sshd_config&lt;/code&gt; file, I also set &lt;code&gt;PermitRootLogin&lt;/code&gt; to &lt;code&gt;no&lt;/code&gt;, for good measure.&lt;/p&gt;
&lt;p&gt;Lastly, I reset the &lt;code&gt;sshd&lt;/code&gt; service and repeated the steps for each pi:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart sshd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, I was unable to login to the PIs from a computer with unauthorized
ssh keys, but, still could from the authorized computer.&lt;/p&gt;
&lt;h2&gt;Install Python&lt;/h2&gt;
&lt;p&gt;The last issue Ansible complained about was that it needed python installed on the Pis. Like everything else, the Bananna Pi already had this setup, but I had to install it on the two Raspberry Pis. It was simple enough:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install python
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Install Ansible&lt;/h2&gt;
&lt;p&gt;I have a confession. So, you know how I have been fun and cheery by anthropomorphisizing Ansible, saying that it was &lt;em&gt;“happy”&lt;/em&gt; or &lt;em&gt;“frusterated”&lt;/em&gt; during the previous steps? That wasn’t true. I made it up. Ansible wasn’t &lt;em&gt;actually&lt;/em&gt; installed yet. &lt;em&gt;So… to install Ansible…&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo eopkg it ansible
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used &lt;code&gt;eopkg&lt;/code&gt; because I am currently running Solus. You might use &lt;code&gt;sudo apt-get install ansible&lt;/code&gt;, &lt;code&gt;sudo dnf install ansible&lt;/code&gt;, or &lt;code&gt;pacaur -S ansible&lt;/code&gt; depending on whatever distro you are using.&lt;/p&gt;
&lt;p&gt;That’s all for &lt;em&gt;setting up&lt;/em&gt; Ansible. I’ll cut this post off here, but in the next post, I’ll walk through the steps on how to get Ansible to be useful.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting Up My Pi Cluster</title>
    <link href="https://ryan.himmelwright.net/post/setting-up-the-pi-cluster/" />
    <updated>2017-05-05T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/setting-up-the-pi-cluster/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setting-up-the-pi-cluster/jbStwdYAJB-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setting-up-the-pi-cluster/jbStwdYAJB-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have a Raspberry Pi 2, a Raspberry Pi 3, and a Banana Pi. A while ago, I constructed a small tower to house my pi devices. Since then, I have additionally acquired a power source, and some CAT6 cable to connect them all up to a switch. I hope to use the Pis as a mini clustered environment, where I can learn (and play) with some of the “Devops” technologies/techniques out there. This post will briefly explain the initial setup of my cluster.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h1&gt;Setting up the OS Images&lt;/h1&gt;
&lt;p&gt;Before doing anything with the hardware, I had to setup the pi “hard drives” (micro SD cards), so they could boot.&lt;/p&gt;
&lt;h3&gt;Ubuntu for Raspberry PI&lt;/h3&gt;
&lt;p&gt;After mucking around with Rapsbian and Hypriot one Sunday, I decided to just go with a plain Ubuntu image for the Raspberry Pis. I don’t have anything against these specific OSes, but I am mostly setting up this cluster to simulate what I would do on a “real” system. For me, that often means using a straight OS like Ubuntu.&lt;/p&gt;
&lt;p&gt;Luckily, Canonical makes special &lt;a href=&quot;https://wiki.ubuntu.com/ARM/RaspberryPi&quot;&gt;Ubuntu ARM images&lt;/a&gt;, specifically for the Raspberry Pi. I download the 16.04 server version for both the raspberry pi 2 and raspberry pi 3. The process to write these images to the microSD card differs slightly from ones I’ve used in the past. It still uses &lt;code&gt;dd&lt;/code&gt;, but the image is first piped through &lt;code&gt;xzcat&lt;/code&gt;, as such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xzcat ubuntu-16.04.2-preinstalled-server-armhf+raspi2.img.xz | sudo dd bs=4M of=/dev/mmcblk0
xzcat ubuntu-16.04-preinstalled-server-armhf+raspi3.img.xz   | sudo dd bs=4M of=/dev/mmcblk0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Armbian&lt;/h3&gt;
&lt;p&gt;The one issue that I have with the Banana Pi compared to the Raspberry Pi, is that it is commonly not supported. It can be hard to find a bananna pi specific image, and the raspberry pi ones usually do not work. For example, while Canonical linked to Raspberry Pi images, it did not mention the banana pie.  This is where Armbiangcomes in.&lt;/p&gt;
&lt;p&gt;Armbian is a lightweight Debian and Ubuntu based distribution, that provides builds for various ARM devices. Thus the name, &lt;em&gt;ARM-bian&lt;/em&gt;. One of these many supported devices… is the banana pi. I downloaded the Ubuntu 16.04 Server flavor of Armbian for the Banana PI, and copied it to my micro SD card with the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dd if=Armbian_5.25_Bananapi_Ubuntu_xenial_next_4.9.7.img  of=/dev/mmcblk0 bs=1M
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Hardware Setup&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/setting-up-the-pi-cluster/KSz8NavVFY-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/setting-up-the-pi-cluster/KSz8NavVFY-1200.jpeg&quot; alt=&quot;I picture of a wooden rack with pis on it&quot; width=&quot;1200&quot; height=&quot;804&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The Pi Cluster&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After the operating system images have been copied the SD cards, the hardware can be setup. I started by inserting the microSD cards into the PIs, being careful to use the correct card with each device. Unlike the Raspberry PIs, the Bananna PI uses a normal SD card instead of a microSD, so I just left it in the converter card I used to connect it to my computer when imaging.&lt;/p&gt;
&lt;p&gt;After adding the “hard drives”, I connected each PI to my network switch, via Ethernet. In the future, I would like to put the cluster on it’s own mini (managed?) switch so that I can have the nodes on their own private network, but connected to my main network. For now, this works.&lt;/p&gt;
&lt;p&gt;Lastly, plug in the power connectors. Pi devices can be very finicky when not properly powered, so it is a good idea to use an capable USB charging device. I have had trouble in the past with my devices not working correctly due to insufficient power (especially the bpi). I knew this problem would be an even more pronounced with the cluster because I planned to connect a HD to the bpi with a SATA connector. So, I picked up an &lt;a href=&quot;https://www.amazon.com/Anker-Charger-PowerPort-Multi-Port-Samsung/dp/B00VH8ZW02/ref=sr_1_1?ie=UTF8&amp;amp;qid=1493860165&amp;amp;sr=8-1&amp;amp;keywords=Anker+power+port+5&quot;&gt;Anker Power Port 5&lt;/a&gt; and it has been working great.&lt;/p&gt;
&lt;p&gt;Lastly, two nice features of the banana pi is that it has a 1 GB ethernet port, &lt;em&gt;and&lt;/em&gt; a SATA connector with power. So, to utilize this functionality, and get the most out of the bpi, I ordered &lt;a href=&quot;https://www.amazon.com/JBtek-Connectors-Banana-Supply-Terminals/dp/B00ZP0L0VS/ref=sr_1_1?ie=UTF8&amp;amp;qid=1493860481&amp;amp;sr=8-1&amp;amp;keywords=banana+pi+sata&quot;&gt;the appropriate SATA connector&lt;/a&gt; from amazon for a few bucks. When it arrived, I connected it to the SATA and SATA power ports on the pi, and then to an old 300GB laptop HD I had laying around (it was the drive that came with &lt;a href=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/&quot;&gt;my x230&lt;/a&gt;). After the drive was connected, running &lt;code&gt;lsblk&lt;/code&gt; on the bpi automatically showed a &lt;code&gt;/dev/sda&lt;/code&gt; device, in addition to the typical &lt;code&gt;mmcblk0&lt;/code&gt; microSD device. I  mounted the drive to a folder using the following command to test it out:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mount /dev/sda1 Data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the hard drive mounted, I was able to browse its contents and see of all the crap still on it. The combination of the GB network and large HD makes the bpi a great little storage node, which is how I intend to use it.&lt;/p&gt;
&lt;h3&gt;Bootup and Connecting via SSH&lt;/h3&gt;
&lt;p&gt;When the Pis are plugged in, they should automatically boot up. In order to connect to them, I found their IPs from my main computer using &lt;code&gt;nmap&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap -sP 192.168.1.0/24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command may differ depending on your network setup&lt;/p&gt;
&lt;p&gt;The Raspberry PIs have &lt;code&gt;(Raspberry Pi Foundation)&lt;/code&gt; in the MAC address lines, and my Banana Pi had &lt;code&gt;bananapi&lt;/code&gt; in the host name. Once I had the IP addresses, I could ssh in using the default usernames and passwords for the images (the user/pass should be listed on the sites). After logging in for the first time, each PI prompted me to change the password (as it should).&lt;/p&gt;
&lt;h3&gt;Adding A Sudo User&lt;/h3&gt;
&lt;p&gt;When connecting to remote devices, I don’t like to be logged in as root, so the first thing I did was setup my user account with sudo privileges, on the two Raspberry Pis (Armbian actually prompted me through these steps the first time I logged into the Banana pi. Kudos to them).&lt;/p&gt;
&lt;p&gt;To add the user, set it’s password (important), and then add it to the sudo group, I used the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;useradd -m -s /bin/bash ryan
passwd ryan
usermod -a -G sudo ryan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, I know I can add the user to the sudo group in the &lt;code&gt;useradd&lt;/code&gt; command, but I prefer to do it with &lt;code&gt;usermod&lt;/code&gt;. Personal Preference.&lt;/p&gt;
&lt;p&gt;That’s it. At this point, all of my the PIs are minimally set up accessible. The next steps include updating packages, setting up ssh keys, and configuring … &lt;a href=&quot;https://www.ansible.com&quot;&gt;Ansible&lt;/a&gt;. But that will all be in the &lt;em&gt;next post&lt;/em&gt;. See you then!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>ZFS Snapshot Backups to an External Drive with LUKS</title>
    <link href="https://ryan.himmelwright.net/post/zfS-Backups-To-LUKS-External/" />
    <updated>2017-04-20T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/zfS-Backups-To-LUKS-External/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/zfS-Backups-To-LUKS-External/xqghaoWgTL-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/zfS-Backups-To-LUKS-External/xqghaoWgTL-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;708&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have been using &lt;a href=&quot;https://en.wikipedia.org/wiki/ZFS&quot;&gt;ZFS&lt;/a&gt; data pools to store data on my server for some time now. As great as that is, I am ashamed to admit that I have not had a &lt;em&gt;true&lt;/em&gt; backup system in place (raid/mirrors are not backup). I have a backup solution that I have attempted in the past, but ran into an issue and let it drift to the side. That changes now. It’s time to revisit my solution, and complete it to the end.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Currently, my server is configured with two main zfs mirrored pools. The first one, &lt;code&gt;Data&lt;/code&gt;, is built on 2 x 3TB hard drives, providing 2.72 TB of usable disk space. It contains all of my wife’s and my data, organized into sub-category pools (ex: &lt;code&gt;Data/Music&lt;/code&gt;, &lt;code&gt;Data/Pictures&lt;/code&gt;, &lt;code&gt;Data/ryan&lt;/code&gt;, etc). The second, &lt;code&gt;Backups&lt;/code&gt;, is built on the 2 x 1TB hard drives from my old desktop, creating a 928 GB usable pool . It stores the automatic backups of some of the VMs and LXC containers hosted on the server.&lt;/p&gt;
&lt;p&gt;Before I had my 3TB drives, I bought a 2TB external hard drive to backup the 1TB drives to. While it isn’t as large as the total usable space on my server, it is enough to store my data backups to, for the time being.&lt;/p&gt;
&lt;p&gt;My goal is to setup a zfs pool on the external drive, so I can use zfs’s send &amp;amp; receive functionality to send bi-weekly-ish incremental snapshots to it. When I am not running backups, I want to store the drive at an off-site location. With the drive being stored elsewhere, I want to ensure that the data is protected, so I will be encrypting the it using &lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup&quot;&gt;LUKS&lt;/a&gt;, the Linux disk encryption software.&lt;/p&gt;
&lt;h2&gt;Setting up LUKS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gitlab.com/cryptsetup/cryptsetup/blob/master/README.md&quot;&gt;LUKS&lt;/a&gt; (Linux Unified Key Setup) is the standard for Linux disk encryption. I will use it to encrypt the external drive, and then present the LUKS mapper devices to ZFS as a block device. To do this, we need to first install &lt;code&gt;cryptsetup&lt;/code&gt; with &lt;code&gt;sudo apt-get install cryptsetup&lt;/code&gt; (Assuming you are on a Debian-based operating system). Once that is installed, we can use the &lt;code&gt;cryptsetup&lt;/code&gt; command to configure LUKS on the drive.&lt;/p&gt;
&lt;p&gt;The cryptsetup tool has a plethora of settings and options. After researching around, I decided to use options that the author of &lt;a href=&quot;http://www.makethenmakeinstall.com/2014/10/zfs-on-linux-with-luks-encrypted-disks/&quot;&gt;this post&lt;/a&gt; used, because they were doing something very similar to what I am trying. I configured LUKS on my external drive using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cryptsetup luksFormat --cipher aes-xts-plain64 --key-size 512 --iter-time 10000 --use-random -y /dev/sdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--cipher aex-xts-plain64&lt;/code&gt;and &lt;code&gt;--key-size 512&lt;/code&gt; refer to the algorithm and key size used to encrypt the data. In general, the larger the key, the harder the encryption is to crack.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--iter-time 10000&lt;/code&gt; and &lt;code&gt;--use-random -y&lt;/code&gt; are additional precautions to make it more difficult to crack the encryption. The &lt;code&gt;--iter-time 10000&lt;/code&gt; means it will spend at least 10 seconds processing the passphrase each time the disk is unlocked. This makes it much harder to brute-force the passphrase.&lt;/p&gt;
&lt;p&gt;Once the device is encrypted, we need to unlock it and map it as a device. This is done using the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo cryptsetup luksOpen /dev/sdf sdf-enc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/dev/sdf&lt;/code&gt; is the external disk, and &lt;code&gt;sdf-enc&lt;/code&gt; is whatever you want to name the unlocked device. This is the name that what will be used when referring to the unlocked device. With the drive is encrypted and unlocked, it’s time for some ZFS magic.&lt;/p&gt;
&lt;h2&gt;Creating a ZFS Pool&lt;/h2&gt;
&lt;p&gt;I am creating a zpool using just my single external drive, so the setup is very basic. No mirrors, no zvols. A simple zpool is created with the simple command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zpool create externalBackup sdf-enc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;h2&gt;Taking Base Snapshots&lt;/h2&gt;
&lt;p&gt;With a zpool initialized on the externalDrive, I can now send snapshots to it. To start, I created a base snapshot to send. Starting with the smaller pool, &lt;code&gt;/Backups&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zfs snapshot -r Backups@VM-LXC-BackupBase
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command creates a recursive snapshot of my &lt;code&gt;Backups&lt;/code&gt; zpool, named &lt;code&gt;VM-LXC-BackupBase&lt;/code&gt;. Making a base snapshot for my &lt;code&gt;/Data&lt;/code&gt; zpool is nearly the same:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zfs snapshot -r Data/DataBackupBase
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Sending the Base Snapshots&lt;/h2&gt;
&lt;p&gt;After taking base snapshots of the zpools, I can transfer them to the external drive using the zfs &lt;code&gt;send&lt;/code&gt; and &lt;code&gt;recv&lt;/code&gt; commands. Again, starting with the &lt;code&gt;VM-LXC-BackupBase&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zfs send Backups@VM-LXC-BackupBase | sudo zfs recv externalBackup/VM-LXC-BackupBase
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking back, I realized I named this poorly… The external zpool should be &lt;code&gt;/externalBackup/VM-LXC-Backup&lt;/code&gt;, not &lt;code&gt;BackupBase&lt;/code&gt;, that name is just for the first &lt;em&gt;snapshot&lt;/em&gt;. Oh well.&lt;/p&gt;
&lt;p&gt;Now for the slightly harder pool, &lt;code&gt;/Data&lt;/code&gt;, with all of the sub zpools. The first time I attempted this, only the parent &lt;code&gt;Data&lt;/code&gt; snapshot was copied, but none of the children were (&lt;code&gt;Data/Music&lt;/code&gt;, &lt;code&gt;Data/Pictures&lt;/code&gt;, etc). After some digging around the docs and online I realized I was missing the &lt;code&gt;-R&lt;/code&gt; to my &lt;code&gt;zfs send&lt;/code&gt; command.  Also note, that when using the &lt;code&gt;-R&lt;/code&gt; flag, the snapshot name for the destination pool are not specified (because it is copying multiple). It will use the same snapshot names from the source pool.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zfs send -R Data@DataBackupBase | sudo zfs recv externalBackup/DataBackup
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Incremental Backups&lt;/h2&gt;
&lt;p&gt;Taking a snapshot of my data and sending it to an external drive is nice, but I don’t want to send all of the data each time I backup. Transferring can take a very long time, especially as my data pools continue to grow. I don’t want to sit around all day, listening to hard drives hum as my data transfers.&lt;/p&gt;
&lt;p&gt;A useful feature of the zfs &lt;code&gt;send&lt;/code&gt; and &lt;code&gt;recv&lt;/code&gt; commands is the ability to send &lt;em&gt;incremental&lt;/em&gt; snapshots. This means when I want to update my backups, I can just send the &lt;em&gt;changes&lt;/em&gt; between the two snapshots. This is similar to &lt;a href=&quot;https://en.wikipedia.org/wiki/Diff_utility&quot;&gt;source code diffs&lt;/a&gt;, but for file systems.&lt;/p&gt;
&lt;p&gt;To send incremental snapshots, the &lt;code&gt;-i&lt;/code&gt; or &lt;code&gt;-I&lt;/code&gt; flag is used. The difference between the two is that the &lt;code&gt;-i&lt;/code&gt; flag will send the difference between the two snapshots listed, whereas &lt;code&gt;-I&lt;/code&gt; will send a series of snapshots between the two listed. For example, if I’ve taken several snapshots of my data (&lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;, and &lt;code&gt;D&lt;/code&gt;), but have neglected to copy them to the external drive since snapshot &lt;code&gt;A&lt;/code&gt;, I can use &lt;code&gt;-I A D&lt;/code&gt; in my &lt;code&gt;zfs send&lt;/code&gt; command, and all four of the snapshots will be sent to the external.&lt;/p&gt;
&lt;p&gt;To send an incremental update to my backup, I first created new snapshot for my pools (this time with a date):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zfs snapshot -r Backups@VM-LXC-Backup20170418
sudo zfs snapshot -r Data@DataBackup20170418
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I sent the incremental changes between the base snapshots, and new ones I just made:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zfs send -R -i Backups@VM-LXC-BackupBase Backups@VM-LXC-Backup20170418 | sudo zfs recv externalBackup/VM-LXC-BackupBase
sudo zfs send -R -i Data@DataBackupBase Data@DataBackup20170418 | sudo zfs recv externalBackup/DataBackup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that I specify &lt;em&gt;two&lt;/em&gt; snapshots in the send command, to define the range of differences to send.&lt;/p&gt;
&lt;h3&gt;A Minor Issue&lt;/h3&gt;
&lt;p&gt;The first time I tried sending an incremental backup, I encountered a minor issue. ZFS gave me an error stating that my destination had been changed since last snapshot (meaning the base snapshot on the externalBackup pool). I looked this up online and it seems that sometimes, just looking around the pool can change files. Some people recommended setting the destination pool to read-only, so I did that to my backup pool with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zfs set readonly=on externalBackup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I am not sure if this will eliminate this problem in the future, but I guess I will find out.&lt;/p&gt;
&lt;p&gt;I still had the error when sending, so I added the &lt;code&gt;-F&lt;/code&gt; flag to the &lt;code&gt;zfs recv&lt;/code&gt; command. I am not sure if this was the &lt;em&gt;best&lt;/em&gt; solution, but it seemed to be okay. I also thought about rolling back to the snapshot, and then copying which is likely a safer method (if you don’t mind losing the “changes” on the destination pool).&lt;/p&gt;
&lt;h2&gt;Safely Closing and Removing the External Drive&lt;/h2&gt;
&lt;p&gt;When the incremental backups finishes transferring, the external drive can be removed. The sequence of steps to do this safely are 1) export the zpool 2) close the LUKS device, and 3) unplug the drive. To export the zpool and close the LUKS device I used the commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zpool export externalBackup
sudo cryptSetup luksClose sdf-enc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, I was able to unplug the external drive, and store it in a safe location, until I need to backup data to it again.&lt;/p&gt;
&lt;h2&gt;Opening and Importing zpool for Recurring Backups&lt;/h2&gt;
&lt;p&gt;Lastly, there are a few steps to take when reconnecting the drive to run a daily/monthly/weekly (whatever) backup. First, the drive must be decrypted and mounted, using the same command as above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo cryptSetup luksOpen /dev/sdf sdf-enc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, the zpool on the drive must be imported so that it can be used from the main system. Like exporting the pool, the command is simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo zpool import externalBackup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. Now the steps detailing taking snapshots and sending incremental backups can be repeated.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;I am happy with this solution for now. It allows me to leverage ZFS a bit more, and become more familiar with it. The biggest issue I will likely face is space on the external drive. Luckily, ZFS makes it easy to delete old snapshots. In the future, I might also consider using an online backup solution like &lt;a href=&quot;https://www.tarsnap.com/&quot;&gt;Tarsnap&lt;/a&gt;, but I need to find a cost-effective one first. I’ll be sure to update as I continue to expand my backup solution.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>LIA 1.0 Beta Released</title>
    <link href="https://ryan.himmelwright.net/post/lia-1-0-beta-released/" />
    <updated>2017-04-17T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/lia-1-0-beta-released/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/XSTw39eBx8-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/XSTw39eBx8-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;World&#39;s End, Hingham, MA USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have released the 1.0 Beta version for a personal project of mine: the &lt;strong&gt;L&lt;/strong&gt;edger &lt;strong&gt;I&lt;/strong&gt;mport &lt;strong&gt;A&lt;/strong&gt;ssistant, or &lt;a href=&quot;https://github.com/himmALlRight/LIA/&quot;&gt;LIA&lt;/a&gt;. This post will talk briefly about the background of LIA, what it does, and explain the beta release.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What is LIA?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/Vv750TYn94-793.webp 793w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/Vv750TYn94-793.jpeg&quot; alt=&quot;Credit Card Statement CSV&quot; width=&quot;793&quot; height=&quot;62&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Example Cred Card Statement CSV File&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;A while ago, I discovered &lt;a href=&quot;http://www.ledger-cli.org&quot;&gt;Ledger&lt;/a&gt;, the command line double-entry accounting application. Its powerful, yet simple design attracted me, and I wanted to try it out. To use it effectively however, I needed a method to import our bank and credit card statements into ledger journals. Then, I could use ledger to analyze the finances. However, there was an issue. While there are several great ledger convert/import options out there, many were more complicated than what I was looking for. So…I wrote my own.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/N61zL3cRbT-613.webp 613w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/N61zL3cRbT-613.jpeg&quot; alt=&quot;Example Ledger Journal File&quot; width=&quot;613&quot; height=&quot;306&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Example Ledger Journal File&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I started writing LIA, I just wanted a python script that could help me convert the contents of a .csv file into a ledger-journal formatted file (without any of the fancy features. Just the basics). I thought it was a simple task and should only take me a few hours. It was, and it did. I wrote the the first basic implementation of LIA on a Sunday afternoon. While coding that bare-bones version, I realized that even though it &lt;em&gt;technically worked&lt;/em&gt;, it would not be enjoyable to use, and therefore I would never use it. So I decided to expand it into a full project, something more than a simple script.&lt;/p&gt;
&lt;h2&gt;What does LIA do?&lt;/h2&gt;
&lt;p&gt;LIA executes the core functionality that originally prompted me to write it: converting bank/credit card statement csv files into ledger journal files. Beyond that basic functionality,  LIA has a few nice features that help the user manually convert these files in an enjoyable way. By going through each transaction manually, the user has full control to make sure data is being input correctly. However, LIA helps make this otherwise dull process fast and efficient. Some of LIA’s features that help accomplish this are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data order is recognized by a header mechanism&lt;/li&gt;
&lt;li&gt;Prompts the user to potentially edit the transaction information&lt;/li&gt;
&lt;li&gt;Sets default transaction information from the values of the csv file&lt;/li&gt;
&lt;li&gt;Manual transaction entries when needed (No input file)&lt;/li&gt;
&lt;li&gt;Supports multiple destination accounts&lt;/li&gt;
&lt;li&gt;Automatic placement system. The user can specify a file containing rules to automatically place transactions. (ex: anything with “Dunkin” in the description will default to Expenses:Food:Coffee)&lt;/li&gt;
&lt;li&gt;Colored prompts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/LIA-demo.gif&quot;&gt;Click for demo animation&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;What does LIA 1.0 Beta mean?&lt;/h2&gt;
&lt;h3&gt;LIA 1.0&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/5U0CXw77Kq-900.webp 900w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/lia-1-0-beta-released/5U0CXw77Kq-900.jpeg&quot; alt=&quot;My 1.0 Todo List&quot; width=&quot;900&quot; height=&quot;380&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;My Todo list to release the 1.0 Version&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I expanded LIA to a personal project, I recorded several features that would make converting CSV statements easier, and got started. I worked on the project here and there, adding each feature over time. These features are what I determined were required in order for the application to be acceptably &lt;em&gt;usable&lt;/em&gt;. When all of those requirements were met, I would release an official 1.0 Branch.&lt;/p&gt;
&lt;p&gt;The main functionality of LIA has been implemented for a while now. Being a python application, it has been possible to run LIA by calling the files with python. However, I didn’t want to release the 1.0 version without first making an installer. I wanted LIA to be run like a normal linux application. I have now finished configuring the project and a &lt;code&gt;setup.py&lt;/code&gt; file, so users can use python’s setuptools to install LIA as an application on their computer. Additionally, I have even packaged LIA as a Solus eopkg. It looks like I am ready for release.&lt;/p&gt;
&lt;h3&gt;Beta&lt;/h3&gt;
&lt;p&gt;Sort of. Until now, I have been developing, but not using LIA day to day. I want to spend some time actually &lt;em&gt;using&lt;/em&gt; the application to see if there are any remaining issues. Also, I have not confirmed that it fully does what is needed for ledger. I want to get a few ledger users to quickly look at it and let me know if they see any issues. After testing it for a bit, I will release it as the official 1.0 release. This will mean it should be stable enough for people to use, if they so choose to.&lt;/p&gt;
&lt;h2&gt;More Information&lt;/h2&gt;
&lt;p&gt;To test out LIA, read some documentation, or find out more  &lt;a href=&quot;https://github.com/himmAllRight/lia/&quot;&gt;check it out&lt;/a&gt; on &lt;a href=&quot;https://github.com/himmAllRight&quot;&gt;my github page&lt;/a&gt;. I plan to continue to develop it further in the future. If you have a suggestion, or even some code you’d like to contribute, feel free to let me know, either on github or my &lt;a href=&quot;https://ryan.himmelwright.net/pages/about/&quot;&gt;other contact methods&lt;/a&gt;. Enjoy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>New Website Theme - Immutable</title>
    <link href="https://ryan.himmelwright.net/post/new-theme-immutable/" />
    <updated>2017-04-11T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/new-theme-immutable/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/jgHSj47_Cn-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/jgHSj47_Cn-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Blue Hills Reservation, Milton, MA USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I first started using &lt;a href=&quot;http://cryogenweb.org&quot;&gt;Cryogen&lt;/a&gt; to generate this website, I wanted to create a personalized theme. This desire led me to hack a the default theme into a mutant, which I boringly named “&lt;em&gt;ryan1&lt;/em&gt;” (I anticipated it to be temporary). It looked like a relic, designed from when I first learned how to make a web page… the early 2000’s. Like &lt;s&gt;many&lt;/s&gt; all websites from that time, it was not mobile friendly. I hope to change all of that, by introducing my new website theme: &lt;em&gt;&lt;strong&gt;Immutable&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/P93etOQ0nN-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/P93etOQ0nN-1200.jpeg&quot; alt=&quot;Homepage with old ryan1 Theme&quot; width=&quot;1200&quot; height=&quot;603&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Homepage with the old Theme&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/g1lVs9PaFl-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/g1lVs9PaFl-1200.jpeg&quot; alt=&quot;Homepage with Immutable Theme&quot; width=&quot;1200&quot; height=&quot;603&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Homepage with the new Immutable Theme&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While it may possibly still look dated (I wouldn’t know, I’m a backend dev), it addresses several of the issues I had with the &lt;em&gt;ryan1&lt;/em&gt; theme. Here are a few examples of these improvements:&lt;/p&gt;
&lt;h3&gt;Removed the Sidebar &amp;amp; Dispersed Its Contents&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/tOonV-PVnS-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/tOonV-PVnS-1200.jpeg&quot; alt=&quot;sidebar components moved to other parts of site&quot; width=&quot;1200&quot; height=&quot;725&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;The sidebar components have been distributed to other parts of the site&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have to be honest here… I originally planned on keeping the side bar when I started to build the theme. I liked how it held all of my links (Github, LinkedIn, etc), recent posts, and tags in one, easy to find, location. I also liked having my avatar picture in the side bar. I felt it made the site more personable (or maybe I’m just egotistical). After working on the base of the theme however, I realized the site might be better off without it. I’ve relocated the items to other locations of the site. The majority of the links have been relocated to the drop-down menu (more on that  below), and I at least added my picture to the &lt;a href=&quot;https://ryan.himmelwright.net/pages/about/&quot;&gt;About Page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I might reinstate a sidebar in the future, but only if it looks &lt;em&gt;good&lt;/em&gt; and I can add it &lt;em&gt;correctly&lt;/em&gt;. The way I implemented the old sidebar caused many of the issues prompting this new theme, and I do not want to bring back those problems.&lt;/p&gt;
&lt;h3&gt;Removed the Bad Footer&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/g9r5VWH0dj-1199.webp 1199w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/g9r5VWH0dj-1199.jpeg&quot; alt=&quot;bad footer placements&quot; width=&quot;1199&quot; height=&quot;344&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Bad Footer Placements&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;One of the issues created by the sidebar and my poor css floats, was the footer. On pages with a large enough the main content, it looked fine, consisting of a dark gray bar &lt;em&gt;along the bottom of the page&lt;/em&gt; . However, when viewing a page with a small content section, such as the &lt;em&gt;About&lt;/em&gt; page, the footer would rise up behind the sidebar. It looked terrible.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/CHpu22_MWG-1197.webp 1197w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/CHpu22_MWG-1197.jpeg&quot; alt=&quot;new footer&quot; width=&quot;1197&quot; height=&quot;262&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;New Footer&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;Immutable&lt;/em&gt; theme, I have fixed the footer and removed the gray bar. The background looks great, and the text is clearly visible over it, making the bar no longer required. I like minimal footers. I only need a small copyright statement, and I enjoy having a &lt;a href=&quot;http://cryogenweb.org&quot;&gt;Cryogen&lt;/a&gt; shout-out here. So that’s all I have.&lt;/p&gt;
&lt;h3&gt;Mobile Support&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/zv3m7tFgps-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/zv3m7tFgps-1200.jpeg&quot; alt=&quot;mobile support improvements&quot; width=&quot;1200&quot; height=&quot;510&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Mobile viewing the homepage and a post, on both the old theme and Immutable&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The biggest reason for making the new theme was that viewing the website on a mobile device was a poor experience… unusable even. Again, because I implemented the sidebar poorly, it always remained on the side. Even on a narrow mobile screen. There was not enough room for the actual main content section, and pages/posts consisted of a skinny line of text down the side of the phone. Images… well, don’t get me started on how well images were displayed. &lt;em&gt;Immutable&lt;/em&gt; solves these issues. The main content window takes up the majority of the screen, allowing for easy content reading.&lt;/p&gt;
&lt;h3&gt;Mobile Navigation&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/ZZi7CSeBPh-400.webp 400w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-theme-immutable/ZZi7CSeBPh-400.jpeg&quot; alt=&quot;main navigation on mobile&quot; width=&quot;400&quot; height=&quot;384&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Main navigation drop-down and the extended drop-down menu on mobile&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In addition to better viewing, site navigation has also been improved on mobile. When I say &lt;em&gt;improved&lt;/em&gt;, I of course mean &lt;em&gt;added&lt;/em&gt;, as is was sort-of non-existent before. In &lt;em&gt;ryan1&lt;/em&gt; there was an drop-down menu icon, but when it was tapped… nothing happened. At all. Now, there is an icon that drops down a site navigation menu when tapped. At the bottom of the navigation items, there is a &lt;em&gt;more&lt;/em&gt; tab. When clicked, it extends the menu to also include my contact links (Github, LinkedIn, etc.), recent posts, and the list of post tag links.&lt;/p&gt;
&lt;h3&gt;Continued Work&lt;/h3&gt;
&lt;p&gt;While its name is &lt;em&gt;immutable&lt;/em&gt;, the actual theme itself is not (bad joke). There are a few things I plan on tweaking over time. For example, I don’t love the fonts and spacing and I will continue to improve them. I also want to edit the &lt;code&gt;code&lt;/code&gt; colors. I did a quick edit of the colors so that they worked with the theme, but they not what I ultimately want. I use the code segments a lot on this site, so it is important that they look nice.&lt;/p&gt;
&lt;p&gt;Well, that is it. I finally got around to making a new theme, and plan to make it even better over time. I hope you enjoy, even on a phone ;).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Linux from Scratch - SBUs and Binutils</title>
    <link href="https://ryan.himmelwright.net/post/lfs-sbus-and-binutils/" />
    <updated>2017-04-04T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/lfs-sbus-and-binutils/</id>
    <content type="html">&lt;p&gt;Well, after all of the preparation, we are ready to start compiling packages. This post cover compiling all the packages, but it will detail the first build of &lt;a href=&quot;https://www.gnu.org/software/binutils/&quot;&gt;Binutils&lt;/a&gt;, which is arguably the most important package to compile. Why is Binutils so crucial? It determines the SBU time for your build system. What’s an SBU? Read on to find out!&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;SBUs&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/lfs-sbus-and-binutils/nnYNoFdGAk-402.webp 402w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/lfs-sbus-and-binutils/nnYNoFdGAk-402.jpeg&quot; alt=&quot;A table of package SBUs and their predicted build time&quot; width=&quot;402&quot; height=&quot;286&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;A table of package SBUs and their predicted build time]&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When completing LFS, people commonly want to know how long it will take to compile each package. Unfortunately, build times are very much dependent on the power and configuration of the system the packages are being compiled on. Some packages may take a few minutes on a powerful workstation, but hours on an aged laptop. While it cannot be said how long a specific build will take on any device, we can normalize how long each package build takes comparatively to each other. This normalization is done using Stand Build Units, or SBUs.&lt;/p&gt;
&lt;p&gt;A SBU is the unit of time measurement it takes to make and install a standard package. Each package in the LFS book has a SBU value, so that build times can be gauged. So, if The first package to be constructed in the book (and in this post), is Binutils, so that is the package which SBUs are normalized to. For example, if it took 10 minutes to build Binutils on your machine, then 1 SBU = 10 minutes for that machine. This means a 4.5 SBU package can be expected to take ~45 minutes to build.&lt;/p&gt;
&lt;h3&gt;SBU Accuracy&lt;/h3&gt;
&lt;p&gt;SBUs are not completely accurate, and should be used as an estimate at best. Due to the many factors that may differ between setups, SBUs can be off by dozens of minutes in worst-case scenarios. Certain make options might also throw the system off.&lt;/p&gt;
&lt;p&gt;For example, systems with multiple cores can run “parallel make” using the &lt;code&gt;-j&lt;/code&gt; make-flag, as in &lt;code&gt;make -j4&lt;/code&gt;. This tell &lt;em&gt;make&lt;/em&gt; to compiled the package using multiple cores. Parallel compilation has the potential to speed up the build process significantly. However, due to how compilation jobs are divided for parallel builds, SBUs are even harder to predict and may be even more sporadic. Just remember that and don’t expect too much SBU accuracy when using &lt;code&gt;make -j&lt;/code&gt;. Also, if you ever run into a problem during a build step, it is a good idea to first retry with a single processor build. If this does not fix the issue itself, the error message can at least be more easily analyzed.&lt;/p&gt;
&lt;h2&gt;My Encountered Issues with tar…&lt;/h2&gt;
&lt;p&gt;The first time I attempted to make binutils, I encountered a few errors. The gist of it was that I was not able untar the package correctly, at least from the &lt;em&gt;lfs&lt;/em&gt; user. Everything worked fine from the &lt;em&gt;root&lt;/em&gt; or even &lt;em&gt;ryan&lt;/em&gt; user accounts, but running tar on &lt;em&gt;lfs&lt;/em&gt; returned the following error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar (child): bzip2: Cannot exec: Too many levels of symbolic links
tar (child): Error is not recoverable: exiting now
tar: Child returned status 2
tar: Error is not recoverable: exiting now
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I searched around but much of the initial advice didn’t help my problem. It often indicated that my /usr/bin/bzip2 might be a symlink and should be altered. That wasn’t the case. Then tried something that illuminated the issue: I removed the &lt;code&gt;/tools/bin&lt;/code&gt; from the begging of the &lt;code&gt;path&lt;/code&gt; variable (defined in the &lt;em&gt;lfs&lt;/em&gt; &lt;code&gt;.bashrc&lt;/code&gt; file). That temporarily fixed the issues. So I knew the problem was related to the symlink I setup in &lt;a href=&quot;https://ryan.himmelwright.net/post/LFS-Final-Preparation-Steps&quot;&gt;the previous LFS post&lt;/a&gt;, specifically the &lt;code&gt;ln -sv $LFS/tools /&lt;/code&gt; command. It must have failed and I wasn’t paying attention.&lt;/p&gt;
&lt;p&gt;Now that I was knew what the problem was, I was able to fix it by running the following commands (some of them might need to be run from a &lt;em&gt;root/sudo&lt;/em&gt; account):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -rf $LFS/tools
rm -rf /tools
mkdir -pv $LFS/tools
ln -sv $LFS/tools /
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These commands remove and reset the &lt;code&gt;tools&lt;/code&gt; symlinks. I then made sure to re-add &lt;code&gt;/tools/bin&lt;/code&gt; to the begging of the &lt;code&gt;path&lt;/code&gt; var in the &lt;em&gt;lfs&lt;/em&gt; &lt;code&gt;.bashrc&lt;/code&gt; and test it. Problem fixed!&lt;/p&gt;
&lt;h2&gt;Extracting BinUtils&lt;/h2&gt;
&lt;p&gt;It is important that Binutils is built first in the process. This is mostly because when Glibc and GCC are built, they perform various tests on the linker and assembler to figure out which of their own features to enable.&lt;/p&gt;
&lt;p&gt;To start building BinUtils, move to the sources directory (&lt;code&gt;$LFS/sources&lt;/code&gt;) and extract the package with (If you encounter issues, see section above):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar xfjv binutils-2.27.tar.bz2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: You’re version might be different&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Binutils documentation recommends building it in a dedicated &lt;code&gt;build&lt;/code&gt; directory, so lets go ahead and make, then enter, that directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir build
cd build
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Making &amp;amp; Executing a Build Script&lt;/h2&gt;
&lt;p&gt;Now it is time to build. Normally, this would best be done by sequentially performing a series of &lt;em&gt;configure&lt;/em&gt;, &lt;em&gt;make&lt;/em&gt;, and &lt;em&gt;make install&lt;/em&gt; commands, but for the first binutils compilation, we want to get an accurate reading on how long it takes (to determine our SBU time). To accomplish this easily, I put all of the commands into a bash script. This way, I could execute the script, and easily time the whole process using the &lt;code&gt;time&lt;/code&gt; utility. To create the script, I wrote the following commands into a file (&lt;code&gt;build-binutils.sh&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

../configure --prefix=/tools            &#92;
             --with-sysroot=$LFS        &#92;
             --with-lib-path=/tools/lib &#92;
             --target=$LFS_TGT          &#92;
             --disable-nls              &#92;
             --disable-werror
make

case $(uname -m) in
  x86_64) mkdir -v /tools/lib &amp;amp;&amp;amp; ln -sv lib /tools/lib64 ;;
esac

make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The various options of the &lt;code&gt;configure&lt;/code&gt; command mean the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--prefix=/tools&lt;/code&gt;: Configures the build to install the Binutils programs to the /tools directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--with-sysroot=$LFS&lt;/code&gt;: For cross compilation, tells the build system to look in our $LFS directory for the target system libraries, as needed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--with-lib-path=/tools/lib&lt;/code&gt;: configures the library path that the linker should be configured to use.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--target=$LFS_TGT&lt;/code&gt;: the machine description in the &lt;code&gt;LFS_TGT&lt;/code&gt; variable is slightly different than the value returned by the &lt;em&gt;config.guess&lt;/em&gt; script, so this option will tell the &lt;em&gt;configure&lt;/em&gt; script to adjust Binutil’s build system for building the cross linker.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disable-nls&lt;/code&gt;: disables the internationalization, as it is not needed for the temporary tools.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disable-werror&lt;/code&gt;: Prevents the build from stopping in the event that there are warnings from the host’s compilier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, the &lt;code&gt;case&lt;/code&gt; statement creates a symlink to ensure the sanity of the tool chain, if building on a &lt;em&gt;x86_64&lt;/em&gt; architecture.&lt;/p&gt;
&lt;p&gt;To runs the script, first make it executable,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x build-binutils.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, time and execute the script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;time ./build-binutils.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the script completes, the time it took to run it will be printed out. Congratulations, this is the SBU for your system!&lt;/p&gt;
&lt;p&gt;Once I had my standard single-thread SBU value, I wanted to do a run with the &lt;code&gt;-j4&lt;/code&gt; make flag for comparison. To “reset”, I deleted the contents of the &lt;code&gt;binutils/build/&lt;/code&gt; directory, as well as &lt;code&gt;$LFS/tools/&lt;/code&gt;. I then edited my &lt;code&gt;build-binutils.sh&lt;/code&gt; script to do a parallel compile by changing &lt;code&gt;make&lt;/code&gt; line to &lt;code&gt;make -j4&lt;/code&gt;. I then re-ran the script with &lt;code&gt;time&lt;/code&gt;. &lt;em&gt;Note: This is not described anywhere in the official documentation, but is just my best guess at what to do for a re-run of binutils. I very well may be missing steps. However, remember LFS is a learning experience, so we will find out&lt;/em&gt;!&lt;/p&gt;
&lt;h2&gt;Checking the Build&lt;/h2&gt;
&lt;p&gt;After the build is complete, it is a good idea to run the tests, &lt;em&gt;especially&lt;/em&gt; for binutils. In this case, use the make command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;make -k check
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Building the Chapter 5 Tools From Here on Out…&lt;/h2&gt;
&lt;p&gt;For the sake of time, I will not be writing posts detailing the build process for each of the remaining packages in Chapter 5. I might keep a little log about how each of the builds went and post it, but I am unsure. If I encounter any major snags along the way, I will be sure to write a post detailing them. Otherwise, with any luck, my plan is to keep compiling and see you in Chapter 6!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Linux from Scratch - Final Preparation Steps</title>
    <link href="https://ryan.himmelwright.net/post/lfs-final-preparation-steps/" />
    <updated>2017-03-21T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/lfs-final-preparation-steps/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/MyqMHvAEjR-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/MyqMHvAEjR-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now that the &lt;em&gt;repeated&lt;/em&gt; setup steps have been defined in &lt;a href=&quot;https://ryan.himmelwright.net/post/LFS-Repeated-Setup-Steps/&quot;&gt;my previous LFS post&lt;/a&gt;, there are a &lt;em&gt;few&lt;/em&gt; more preparation steps to complete in order to start building the LFS system. I promise… we will start compiling soon. If all goes well, this should be the last preparation post.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Downloading Sources&lt;/h2&gt;
&lt;p&gt;When it comes down to it, Linux from scratch is just a bunch of packages, and the Linux kernel, all compiled from source and linked together. To build all of this, we need to download the source code… for &lt;em&gt;all&lt;/em&gt; of those packages. Luckily, LFS keeps a list of what is needed, and downloading it is trivial. &lt;em&gt;(Note: These commands should be run as &lt;code&gt;root&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/KJqXliGT8X-564.webp 564w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/KJqXliGT8X-564.jpeg&quot; alt=&quot;code making sources&quot; width=&quot;564&quot; height=&quot;194&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;First, lets make a new directory to put all of the source code. To make the directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -v $LFS/sources
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The LFS book suggests for this directory to be writable and sticky. A “Sticky” directory allows only the file owner to delete a file in it. To make the directory both writable and sticky, use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod -v a+wt $LFS/sources
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/wget-sources.gif&quot;&gt;(Click to open example animation)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To download all of the source packages at once, download &lt;a href=&quot;http://www.linuxfromscratch.org/lfs/view/stable-systemd/wget-list&quot;&gt;the LFS wget list&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget http://www.linuxfromscratch.org/lfs/view/stable-systemd/wget-list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After downloading the wget list, it can be used as the input-file for wget. This will download all the sources with one command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget --input-file=wget-list --continue --directory-prefix=$LFS/sources
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should take a few minutes to download everything (or longer if on a poor connection).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/sources-md5.git&quot;&gt;(Click to open example animation)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ever since LFS-7.0, a md5sums file is provided, which can be downloaded and used to verify the integrity of downloaded packages. Download this file, again with &lt;em&gt;wget&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget http://www.linuxfromscratch.org/lfs/view/stable-systemd/md5sums
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, compare the hashes in the list to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Md5sum&quot;&gt;md5sum&lt;/a&gt; for each of the source packages. This is done with the commands:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;pushd&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$LFS&lt;/span&gt;/sources
md5sum &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; md5sums
&lt;span class=&quot;token function&quot;&gt;popd&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The results should all say &lt;em&gt;OK&lt;/em&gt;. If not, try re-downloading the sources and verifying again.&lt;/p&gt;
&lt;h2&gt;Creating the $LFS/tools Directory&lt;/h2&gt;
&lt;p&gt;LFS is built in two main steps. The first step builds a set of temporary tools to build the system, but not be included as part of the Final LFS system itself. To help prevent these tools from accidentally being included in the final system, they are kept in a separate directory that can be deleted after they have served their purpose. Make this directory as root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -v $LFS/tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we will create a &lt;code&gt;/tools&lt;/code&gt; symlink to the host system.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ln -sv $LFS/tools /
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This enables the tool-chain to be compiled so that it always refers to &lt;code&gt;/tools&lt;/code&gt;, which ensures that the compiler, assembler, and linker will work in both the first, and second steps of the LFS build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; &lt;em&gt;I did this step wrong the first time (I think it failed), and encountered errors later when trying to run tar. If you encounter issues as well, jump to &lt;a href=&quot;https://ryan.himmelwright.net/post/LFS-SBUs-and-Binutils/&quot;&gt;my next post&lt;/a&gt; to see how I resolved these issues&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Adding the LFS User&lt;/h2&gt;
&lt;p&gt;Running a system as root is a dangerous. Running the wrong command can completely obliterate a system, and having a typo bork the LFS build, or even the host system, would be horrific. To prevent this, the book recommends creating an unprivileged user to build the packages from. To do so, first create an &lt;em&gt;lfs&lt;/em&gt; group and then create + add a &lt;em&gt;lfs&lt;/em&gt; user to it using the commands (as root, ironic for this section…):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are not familiar with the &lt;em&gt;useradd&lt;/em&gt; command, then &lt;a href=&quot;https://en.wikipedia.org/wiki/RTFM&quot;&gt;&lt;em&gt;RTFM&lt;/em&gt;&lt;/a&gt; by typing &lt;code&gt;man useradd&lt;/code&gt;. I’m just kidding (although reading the man pages is never a bad idea). Here is a quick summary of what all of the flags mean.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-s /bin/bash&lt;/code&gt; sets our &lt;em&gt;lfs&lt;/em&gt; user’s default shell to &lt;em&gt;bash&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-g lfs&lt;/code&gt; adds the user to the &lt;em&gt;lfs&lt;/em&gt; group that was created in the previous command&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-m&lt;/code&gt; creates the user’s home directory (&lt;em&gt;/home/lfs&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-k /dev/null&lt;/code&gt; changes the input direction to the special null device to prevent the copying of files from a skeleton directory&lt;/li&gt;
&lt;li&gt;lastly, &lt;code&gt;lfs&lt;/code&gt; is the new user’s name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before logging into the user, the password must be set.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;passwd lfs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also want to grant the &lt;em&gt;lfs&lt;/em&gt; user full access to the tools directory we made (&lt;em&gt;$LFS/tools&lt;/em&gt;), so lets make &lt;em&gt;lfs&lt;/em&gt; the owner of that directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chown -v lfs $LFS/tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do the same for the sources directory we made:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chown -v lfs $LFS/sources
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, login as &lt;em&gt;lfs&lt;/em&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;su - lfs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: the “-” tells su to start a login shell, rather than a non-login shell. This mostly ensures that various files are read at login to setup environment variable and other profiles.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Setting up the Build Environment&lt;/h2&gt;
&lt;p&gt;Now with the &lt;em&gt;lfs&lt;/em&gt; user created, we need to setup a proper working environment for that user. To do this, we will create the &lt;code&gt;.bash_profile&lt;/code&gt; and &lt;code&gt;.bashrc&lt;/code&gt; files.&lt;/p&gt;
&lt;h3&gt;Creating .bash_profile&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/set-bash-profile.gif&quot;&gt;(Click to open example animation)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When logging in as the &lt;em&gt;lfs&lt;/em&gt; user, the shell first reads the &lt;code&gt;/etc/profile&lt;/code&gt; of the host, followed by the &lt;code&gt;.bash_profile&lt;/code&gt;. So, lets start with the &lt;code&gt;.bash_profile&lt;/code&gt;. Create/open &lt;code&gt;.bash_profile&lt;/code&gt; and add the following line to it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exec env -i HOME=$HOME TERM=$TERM PS1=&#39;&#92;u:&#92;w&#92;$ &#39; /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line replaces the running shell with a new one that contains a completely empty environment, except the &lt;em&gt;HOME&lt;/em&gt;, &lt;em&gt;TERM&lt;/em&gt;, &lt;em&gt;PS1&lt;/em&gt; variables. This ensures that there are no stray environment variables, that may interfere with the build environment.&lt;/p&gt;
&lt;h3&gt;Creating .bashrc&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://ryan.himmelwright.net/post/lfs-final-preparation-steps/set-bashrc.gif&quot;&gt;(Click to open example animation)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The new instance of this the shell is a non-login shell, so it does not read the &lt;code&gt;/etc/profile&lt;/code&gt; or &lt;code&gt;.bash_profile&lt;/code&gt; files. However, it does read the &lt;code&gt;.bashrc, so lets go ahead and create that. Open &lt;/code&gt;~/.bashrc` and add the following lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set +h
umask 022
LFS=/mnt/lfs
LC_ALL=POSIX
LFS_TGT=$(uname -m)-lfs-linux-gnu
PATH=/tools/bin:/bin:/usr/bin
export LFS LC_ALL LFS_TGT PATH

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;set +h&lt;/code&gt; line turns off bash’s hash function. This is normally a usefully feature, as it essentially caches the path-names of executables. Removing this will ensure that the newly compiled tools will always be found by the shell once they are available, because the shell will have to re-search the &lt;em&gt;PATH&lt;/em&gt; each time. Similarly, placing &lt;code&gt;/tools/bin&lt;/code&gt; ahead of the standard &lt;code&gt;/bin:/usr/bin&lt;/code&gt; &lt;em&gt;PATH&lt;/em&gt; &lt;em&gt;(line 6)&lt;/em&gt;, also helps force the shell to immediately locate up all the programs in chapter 5 after installation. These two techniques will hopefully prevent the risk of using old programs from the host instead of the newly compiled ones.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;umask 022&lt;/code&gt; line defines the &lt;a href=&quot;https://en.wikipedia.org/wiki/Umask&quot;&gt;umask&lt;/a&gt; to 022, which sets up the system so that created files and directories are only writable by their owner, but are readable and executable by anyone.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;LFS=/mnt/lfs&lt;/code&gt; line should look familiar, as it sets the &lt;code&gt;LFS&lt;/code&gt; variable to our LFS mount point.&lt;/p&gt;
&lt;p&gt;Lastly, setting the &lt;code&gt;LC_ALL&lt;/code&gt; variable to &lt;code&gt;POSIX&lt;/code&gt; or &lt;code&gt;C&lt;/code&gt; ensures that everything will work as expected in the &lt;em&gt;chroot&lt;/em&gt; environments (regarding localization settings).&lt;/p&gt;
&lt;p&gt;To enable this new environment we’ve setup, source the user-profile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congratulations! We are now ready to start compiling some code in the next post!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Linux from Scratch - Repeated Setup Steps</title>
    <link href="https://ryan.himmelwright.net/post/lfs-repeated-setup-steps/" />
    <updated>2017-03-13T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/lfs-repeated-setup-steps/</id>
    <content type="html">&lt;p&gt;During the Linux From Scratch process, there may be times when the build environment (computer, VM, chroot, whatever) must be restarted. If so, there are a few steps from the setup phase that have to be re-initialized. This post maps out those steps.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Setting The $LFS Variable&lt;/h3&gt;
&lt;p&gt;After setting up the virtual disk for my LFS build, I needed to define where I wanted to eventually mount it. This location is important, because it is the path that the $LFS variable is set to. The $LFS variable is used throughout the book, to easily point to where the LFS system is being built.&lt;/p&gt;
&lt;p&gt;To set the #LFS variable, I ran the following command: *&lt;/p&gt;
&lt;p&gt;&lt;code&gt;export LFS=/mnt/lfs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To check that the variable set correctly, just print it out using echo (if successful, the path that was specified should print out).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;echo $LFS&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Ensuring the $LFS Variable is &lt;em&gt;Always&lt;/em&gt; Set&lt;/h3&gt;
&lt;p&gt;There are several ways to ensure that the &lt;em&gt;$LFS&lt;/em&gt; variable is always loaded during login. One method the book recommends is to edit the &lt;em&gt;.bash-profile&lt;/em&gt; found in both &lt;em&gt;~&lt;/em&gt; and */root, by appending the export command defined above to them. This way every time the build machine resets, simply logging into the system (which loads &lt;em&gt;bash&lt;/em&gt;, assuming it is the default), will export the &lt;em&gt;$LFS&lt;/em&gt; variable.&lt;/p&gt;
&lt;h3&gt;Mounting the LFS Partition(s)&lt;/h3&gt;
&lt;p&gt;After setting the &lt;em&gt;$LFS&lt;/em&gt; variable, I could finally mount my LFS drive/partition to that location. First, I ensured that the directory existed by running:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mkdir -pv $LFS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: In this command, the -v again means verbose, so a message will be printed for each directory created. The -p flag is for --parents, and will instruct “mkdir” to also make parent directories, as needed. So, if &lt;code&gt;/mnt/&lt;/code&gt; does not already exist, will be created along with &lt;code&gt;/mnt/lfs&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After creating the directories, I mounted them with the command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo mount -v -t ext4 /dev/sdb $LFS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If multiple partitions are being used for the LFS build (&lt;em&gt;such as a separate &lt;code&gt;/home&lt;/code&gt; partition&lt;/em&gt;), they should also be mounted at this time.&lt;/p&gt;
&lt;p&gt;After mounting my partition, the LFS book recommended that I check that the partition was not mounted with restrictive permissions. To do this, I ran the &lt;code&gt;mount&lt;/code&gt; command again, but this time without any parameters. From the output, I was able to see and confirm that the partition was not mounted with restrictive permissions, such as &lt;code&gt;nosuid&lt;/code&gt; or &lt;code&gt;nodev&lt;/code&gt;. If either of these options are set, the partition should be remounted.&lt;/p&gt;
&lt;p&gt;Lastly, if a &lt;em&gt;swap&lt;/em&gt; partition is being used, do not forget to enable it using &lt;code&gt;swapon&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;swapon -v /dev/xxx&lt;/code&gt;  (with &lt;em&gt;xxx&lt;/em&gt; the name of the swap partition)&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Remember, if the LFS host system is restarted for any reason, these steps must be completed upon logging into the rebooted system. Even if measures were taking to &lt;em&gt;always&lt;/em&gt; complete these steps (such as adding the &lt;em&gt;$LFS&lt;/em&gt; variable to the bash profile, or mounting the partitions via the &lt;em&gt;fstab&lt;/em&gt; file), it is still a good idea to check and make sure that they &lt;em&gt;actually&lt;/em&gt; initialized as  intended. This can prevent several headaches down the road.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My New Dotfiles Management - Using GNU Stow</title>
    <link href="https://ryan.himmelwright.net/post/new-dotfiles/" />
    <updated>2017-03-07T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/new-dotfiles/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-dotfiles/LQ8fEWrqcd-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-dotfiles/LQ8fEWrqcd-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Rodney Bay, St Lucia&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have maintained a “dotfiles” repository since I made my github account in 2013. However, overtime it became more of a post-apocalyptic wasteland, cluttered with remnants of obsolete configurations and scraps of scripts. It was no longer the pristine, culled, collection that I desired. I also did not have an efficient method of easily linking the files on a new system. I had to manually make symlinks for each dotfile. I knew there were &lt;em&gt;much&lt;/em&gt; better dotfiles setups out there, but I never got around to it. Until now.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;One day, after reading &lt;a href=&quot;http://brandon.invergo.net/news/2012-05-26-using-gnu-stow-to-manage-your-dotfiles.html&quot;&gt;this post&lt;/a&gt;, I finally decided to sit down and clean up my dotfiles directory. I wanted to re-organize it so that I could use &lt;a href=&quot;http://freecode.com/projects/gnustow&quot;&gt;GNU Stow&lt;/a&gt; to initialize my dotfiles.&lt;/p&gt;
&lt;p&gt;After setting it all up, I decided to just start from scratch with a &lt;a href=&quot;https://github.com/himmAllRight/dotfiles&quot;&gt;new repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Using Stow and dotfiles&lt;/h2&gt;
&lt;p&gt;If you haven’t seen it before, I highly suggest reading the post I have linked above. But in the meantime, I can provide a quick summary of how my dotfiles are setup.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-dotfiles/Yyy3D0tH9i-616.webp 616w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-dotfiles/Yyy3D0tH9i-616.jpeg&quot; alt=&quot;My Dotfiles Dir&quot; width=&quot;616&quot; height=&quot;198&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Each application has an associated sub-directory (ex: &lt;code&gt;dotfiles/emacs&lt;/code&gt;), which contains all of the dotfiles/folders associated with that application. Structurally, I treat the items in each application directory as if they were in my &lt;code&gt;~&lt;/code&gt;. For example, the &lt;code&gt;vim&lt;/code&gt; sub-directory has my &lt;code&gt;.vimrc&lt;/code&gt;, as well as the &lt;code&gt;.vim/colors/&lt;/code&gt; directory. This is so that when I use stow, it will properly link them in &lt;code&gt;~&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-dotfiles/HbQuMtOzsY-616.webp 616w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-dotfiles/HbQuMtOzsY-616.jpeg&quot; alt=&quot;Vim dotfiles directory&quot; width=&quot;616&quot; height=&quot;198&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I setup my dotfiles on a new system, or install an application for which I already have dotfiles saved for, setting them up is as easy as typing:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;stow application-dir&lt;/code&gt; (ex: &lt;code&gt;stow vim&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;GNU Stow then links the files under my home directory. In my vim example, this means symlinks are created for &lt;code&gt;~/.vimrc&lt;/code&gt; and &lt;code&gt;~/.vim/colors/*&lt;/code&gt;, pointing to their respective locations in &lt;code&gt;~/dotfiles/vim/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/new-dotfiles/AUwrxoAsbc-616.webp 616w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/new-dotfiles/AUwrxoAsbc-616.jpeg&quot; alt=&quot;Vim dotfiles in Home&quot; width=&quot;616&quot; height=&quot;198&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I think this setup is brilliant. Initializing an application’s directory is so simple, and I can choose to only initialize specific sub-directories.&lt;/p&gt;
&lt;p&gt;In the future, I might make multiple branches of the repository, one for each of my computers, so I can maintain specific configurations. In theory, I could also just make different folders (ex &lt;code&gt;vim-laptop&lt;/code&gt; and &lt;code&gt;vim-server&lt;/code&gt;), but I like the branch idea better because it’s a little easier for me to merge changes. We shall see.&lt;/p&gt;
&lt;p&gt;Anyway, that’s the new setup. Enjoy :-D&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My New, Used x230</title>
    <link href="https://ryan.himmelwright.net/post/my-new-used-x230/" />
    <updated>2016-11-29T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/my-new-used-x230/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/XYZof-1oJL-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/XYZof-1oJL-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;As I start to do more with a computer on the go, the netbooks I
previously used just are not cutting it anymore. They were great for minimal
use, but couldn’t run VMs, over-heated, and didn’t have great
battery life. Now that I am going to libraries and other locations to
work on personal and open source projects, I really needed a better
mobile pc setup.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Other Considerations&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/-rJlpztA88-650.webp 650w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/-rJlpztA88-650.jpeg&quot; alt=&quot;Asus x201e&quot; width=&quot;650&quot; height=&quot;502&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I had been keeping an eye open for a new mobile computer for well over a
year to find out what is out there. I mostly looked sub $300 ubuntu laptops like the Asus 11&amp;quot;
celeron (x201e) I got in college. I don’t need much HD
space on my portable computer, because I usually use a minimal
OS, and only have my projects (which are git controlled), and sometimes a
&lt;em&gt;little bit&lt;/em&gt; of music. Even with dropping my requirements to computers with
&lt;em&gt;very tiny&lt;/em&gt; HDs (often &amp;lt; 60GB), none of the options seemed all that impressive. They
usually had Celeron processors and little ram. Even worse, they
were not easily expandable. I also briefly entertained the idea of
putting Linux on a chrome book, but they had the same issues that all
the other sub $300 options had.&lt;/p&gt;
&lt;h3&gt;Used Thinkpads&lt;/h3&gt;
&lt;p&gt;When I intensified my search, I started to consider other
options. There is one alternative that is very popular among Linux users,
but I always seem to forget about it when looking for a new
device. That option of course, is buying a used Thinkpad and installing
Linux on it. This appeared to be an ideal time to try it out. I
don’t mind if there is a scratch or two on a cheap device. This is my
portable computer after all, so I’m sure a shinny new device wouldn’t stay
pristine for long anyway. So, I started researching used Thinkpads and
loved how much you could get for the money. There were i5 devices with an
acceptable bit of ram and “okay” hard drives for under my $300 price
point. Better yet, the Thinkpads seemed easy to upgrade .
So, even if the device had a bad battery, or needed a
RAM/HD upgrade, &lt;em&gt;I could could do it&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I eventually narrowed my search down to the X230 or T430s. That year’s
model seemed like the perfect mix of still being relatively new, while
also being old enough that many business had started to replace
them, flooding the market and driving the cost down. They also
didn’t have the terrible track pad that the x240 and T440s had (without buttons). I
eventually decided on the x230 because the main use of this laptop would be portability.&lt;/p&gt;
&lt;h3&gt;This Particular Thinkpad x230 …&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/1JIKAz1Ng9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/1JIKAz1Ng9-1200.jpeg&quot; alt=&quot;x230&quot; width=&quot;1200&quot; height=&quot;867&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    
I found my used x230 on Amazon, for about $185. The vendor an acceptable
rating, and the specs were good for the price:&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intel i5-3320M&lt;/li&gt;
&lt;li&gt;4 GB RAM&lt;/li&gt;
&lt;li&gt;12.5&amp;quot; 1366x768 HD LED Display&lt;/li&gt;
&lt;li&gt;320 GB HHD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The listing also said that it came with a “used 6-cell battery”. So I planned
from the beginning to have it replaced. Sure enough, when the laptop arrived,
the battery was completely dead. If I unplugged the laptop, it would shutdown.
So, I ordered a 9-cell battery online to replace it. I added
a 4GB stick of RAM that I took from an older laptop, and replaced the slow HHD
(with Windows pre-installed), with a cheap 120GB SSD I had lying
around. After installing Korora 24 on the new SSD, I had a
nice, portable computer that only cost me about $200 (plus some spare
parts). As advertised, swapping the battery, adding RAM, and replacing
the HD were &lt;em&gt;super simple&lt;/em&gt; and &lt;em&gt;accessible&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;What I like about it&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Track point&lt;/strong&gt; - I love track points. Using other laptops with
just track-pads is really frustrating now. I completely understand why
people are so crazy about them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keyboard&lt;/strong&gt; - The keyboard really is great. I feels solid and not like
other laptop keyboards. It gives good feedback and is very nice to type on. I know many
people hate this model because keyboard layout changed from the
older models like the x220, but I still think the x230 has a better
keyboard than most laptops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Physical buttons&lt;/strong&gt; - The x230 has a physical mute, volume control, and
mic buttons. This is a feature I enjoy much more than I thought I
would.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Upgradability&lt;/strong&gt; - As I have already mentioned, it is &lt;em&gt;very&lt;/em&gt; easy
to upgrade the x230. Right when I got it, I replaced the HHD with an
SSD and added another stick of RAM in under five
minutes. In fact, I have swapped the SSD out temporarily with
a second drive several times now to try out different distros on the
hardware. It is just so easy to do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feel&lt;/strong&gt; - I really like how this laptop feels. I’ve already
mentioned that I enjoy the solidness of the keyboard, but I also
love the exterior texture of the case. It has a slight rubbery
feel to it that makes it easy to hold.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bang for your buck&lt;/strong&gt; - Getting a used thinkpad really is a
great deal. Even counting the minor upgrades I installed, I was able
to get &lt;em&gt;an actual computer&lt;/em&gt; for around the same price as the
&lt;em&gt;netbook&lt;/em&gt; alternatives I was looking at.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Size&lt;/strong&gt; - This is my mobile computer, so I need it to be
small. While it isn’t as thin as my wife’s macbook air, it is still
very portable. I actually don’t mind it being a little bit thicker,
as I find it to be the perfect size to hold and carry around. It is
a similar size to a few of my programming books and slides into my
backpack very easily.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What I don’t like&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Has a little bit of flex&lt;/strong&gt; - While the laptop overall feels pretty
solid, the screen does have a bit of flex when pressed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resolution&lt;/strong&gt; - I really hate 1366x768 on laptops. I don’t need 1920x1080, which would be very tiny on a 12.5&amp;quot; screen. 1600x900 however…&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Track pad (but honestly who cares)&lt;/strong&gt; - The track-pad isn’t great. In fact, it can be downright annoying. The bottom of the track-pad curves around the edge of computer, which looks nice, but rubs against my stomach when I am working with the laptop on my lap. This causes the cursor to fly around and do everything but what I want it to do. This would be a much bigger issue on other laptops, but with how much I use the track point, I really don’t care. I usually just disable the track-pad all together.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speakers aren’t great (but again, not a bit deal)&lt;/strong&gt; - The speakers are very quiet and don’t
sound very good. This isn’t a huge deal for me because being my mobile computer, I am using
headphones 99% of the time. Still, it would be nice if when I occasionally share a video with someone they could actually hear it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusions&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/93yDbW7gqP-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/my-new-used-x230/93yDbW7gqP-1200.jpeg&quot; alt=&quot;x230&quot; width=&quot;1200&quot; height=&quot;867&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have had my x230 for several months now and still love it. I use it at work daily (to listen to
music, podcasts, and to have a &lt;em&gt;linux&lt;/em&gt; computer available to me at all times in a Windows office). It is a perfect mix of being portable while still having enough power to accomplish whatever I want to work on. I still prefer to vote with my wallet by purchasing from a vendors like &lt;a href=&quot;http://www.system76.com&quot;&gt;System76&lt;/a&gt; when possible, but buying used business hardware, such as thinkpads, is a very affordable alternative. I have no regrets buying my used x230. It is infinitely better than my other options at that price point.&lt;/p&gt;
&lt;h3&gt;Update&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;I have taken so long to publish this post, I am actually now using Arch Linux on the x230. It also runs great :)&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trying out snapd on Arch Linux</title>
    <link href="https://ryan.himmelwright.net/post/snapd-on-arch/" />
    <updated>2016-07-17T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/snapd-on-arch/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/TNA4vcoDoL-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/TNA4vcoDoL-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;678&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Boston, MA USA&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The past few weeks I have been listening to all of the buzz about
Ubuntu “snaps”, but I just recently decided to try this new technology
out.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Snapd Install and Setup&lt;/h3&gt;
&lt;p&gt;I decided to install snapd on my small netbook (Abra), which is running Arch
Linux. So, installing snapd was as easy as running:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pacaur -S snapd&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now, I use pacaur so that I can install packages from Arch Linux’s
main repos, as well as the AUR. However, because snapd has actually
been moved to the community repos, you can install it with vanilla
pacman, without aur support.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo pacman -S snapd&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After snapd is installed, it needs to be started. To start the snapd
service, just use systemd:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo systemctl start snapd&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Additionally, If you want to start the snapd service automatically
after a reboot run the command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo systemctl enable snapd&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Using snap&lt;/h3&gt;
&lt;p&gt;After snapd was installed and running, I started
playing with the snap application. The first thing I should note is
that to the extent of my knowledge, snap has to be run as root (at
least for now). So my commands all start with &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, I wanted to find a snap package. This can be done using the
&lt;code&gt;snap find&lt;/code&gt; command as follows:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo snap find package-name&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(where &lt;strong&gt;package-name&lt;/strong&gt; is the name of the package to search)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/Reio5Ko0Xd-867.webp 867w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/Reio5Ko0Xd-867.jpeg&quot; alt=&quot;Finding Snaps&quot; width=&quot;867&quot; height=&quot;170&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Finding Snaps.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Just like in any standard package manager search, the potential
matches were returned, along with a version number, the developer
name, and a summary.&lt;/p&gt;
&lt;p&gt;Snap has several other commands. To see a full list of available snap
commands and a description for each one, just type:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo snap help&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/mH5bIsTzq4-614.webp 614w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/mH5bIsTzq4-614.jpeg&quot; alt=&quot;Snap Help&quot; width=&quot;614&quot; height=&quot;577&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Snap Help.&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Testing a snap&lt;/h3&gt;
&lt;p&gt;After learning about the basic snap commands, I wanted to install a
snap to see how well it works. I decided to see if there was a
Telegram snap (I heard there was, so it was a good bet). I ran
&lt;code&gt;sudo snap find telegram&lt;/code&gt;, and a list of my options appeared.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/0RNvSf3Pnk-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/0RNvSf3Pnk-1200.jpeg&quot; alt=&quot;Snap telegram&quot; width=&quot;1200&quot; height=&quot;102&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I choose the last one, and installed it using the command
&lt;code&gt;sudo snap install telegram-serguisens&lt;/code&gt;. A progress bar tracked the download of
the snap, and then the installation. That was it. To my delight,
within a few seconds the Ubuntu &lt;code&gt;snap&lt;/code&gt; application had downloaded the
telegram snap, and installed it on my &lt;em&gt;arch linux&lt;/em&gt; box, without any issue.&lt;/p&gt;
&lt;h3&gt;My Only Issue&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/_PzzmTial9-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/_PzzmTial9-1200.jpeg&quot; alt=&quot;Snap Telegram Menu&quot; width=&quot;1200&quot; height=&quot;674&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Well… &lt;em&gt;almost&lt;/em&gt; without any issues. The one problem I did have was
after installing the telegram snap, the executable wasn’t in my path so
I couldn’t start it from my launcher or even a terminal. I think this
was actually because snapd wasn’t setup fully. I rebooted my computer
and telegram now launches just fine. The snap is even identified with
its full name (&lt;em&gt;telegram-serguisens&lt;/em&gt;), so I can be sure it is the
actual snap, and not another Telegram install.&lt;/p&gt;
&lt;h3&gt;Thoughts&lt;/h3&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/Wm7VVdKCgf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/snapd-on-arch/Wm7VVdKCgf-1200.jpeg&quot; alt=&quot;snap telegram&quot; width=&quot;1200&quot; height=&quot;674&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Telegram Snap&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;While the fact that snaps are easy to package and install for
developers and users alike, I think my favorite feature was something
that I didn’t even think about prior to trying it out, and I might not
of realized had I installed snapd on another computer…&lt;/p&gt;
&lt;p&gt;This little computer is low power, and tends to heat up and go crazy
when I install certain packages (particularly ones from the
AUR). Sometimes it is so bad that I have to kill the installation and
forego that application for awhile. Telegram is one of them. I think
this is because the aur build pulls down the telegram .deb package and
then has to extract and compile it in order to install it on
Arch. This little computer doesn’t like that. The snap however, seems
to of just download and installed itself. It hardly pegged my CPU at
all. If this is true, snaps might have an additional use-case on weaker
hardware, like my netbook. (yes, I know snaps might have a bigger disk
footprint, but honestly that trade-off is fine with me)&lt;/p&gt;
&lt;p&gt;I think the universal application frameworks are a great idea that is
very much needed on Linux. Snapd is very promising and I look forward
to seeing what it and other solutions have in store for the future of
Linux.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trying Out the Plasma 5 Desktop Environment</title>
    <link href="https://ryan.himmelwright.net/post/trying-out-plasma5/" />
    <updated>2016-06-26T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/trying-out-plasma5/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/VdBlJYg06f-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/VdBlJYg06f-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;After watching the continued development of the Plasma 5 desktop
environment from a distance, I determined I needed to give it another
try. It was just before version 5.6 was released, and the project
appeared to be finding it’s stride. Right up front, I want to come
clean and admit that I have never really liked the KDE/Plasma desktop
environment (more on that below). However, with that said… I am still
using the plasma desktop on my main computer (months later, and
haven’t aborted yet**…). Here are my thoughts.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;My History with KDE/Plasma&lt;/h3&gt;
&lt;p&gt;Now, as I said above, &lt;em&gt;I have never really liked KDE (now Plasma)&lt;/em&gt;. In
fact, sometimes I down right &lt;em&gt;hated&lt;/em&gt; it. It is a great project, and I
&lt;em&gt;wanted&lt;/em&gt; to like it. It has so many features and is amazing
for power users. I have tried it again and again, desperately wanting
to be swept off my feet, but it’s design always kept me bolted to the
ground. The theming made me feel like I was using a Fisher-Price
operating system (which is ironic, because like I said… the plasma
desktop is probably one of the best power user environments out
there). The bulky features always made it feel childish. So, with that
said… I have been using using the Plasma desktop on all of my
personal computers for several months. Here are my thoughts.&lt;/p&gt;
&lt;h3&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The theming has come a long way&lt;/strong&gt;. It no longer looks like a
child’s toy. Even the default theme is &lt;em&gt;very&lt;/em&gt; appealing. Also, it
has &lt;em&gt;dark themes&lt;/em&gt;, which I am always a fan of.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/EiM_zWGi1S-1100.webp 1100w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/EiM_zWGi1S-1100.jpeg&quot; alt=&quot;Default KDE Plasma 5.6 Desktop&quot; width=&quot;1100&quot; height=&quot;618&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I Love the customization.&lt;/strong&gt; I love the level of customization
the plasma desktop allows. I can set transparency, add/remove
panels or widgets, set custom keybindings (very important to me),
and a bunch of other operations very easily.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/dNvdk9bDm8-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/dNvdk9bDm8-1200.jpeg&quot; alt=&quot;Plasma Settings&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lets me hide window bars&lt;/strong&gt; One customization I love is the ability
to hide the window bars. I am not a fan of window bars, because I
think they look clunky and just take up space. One feature I like
about many tiling window managers is it is usually very easy to hide
the window bars. However, that isn’t always the case in Desktop
Environments, but I was pleased with how easy it was to do it in
Plasma.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It Lets me create an awesome-wm environment, but as a full DE&lt;/strong&gt;
Similar to above, I am able to implement some of the features I love
about tiling window managers like awesome and i3, but with the full
desktop environment. In addition to removing window bars, I like to
easily move and resize windows by hitting the meta or alt key and
left/right clicking the windows. Again, in Plasma this was simple.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I love krunner. A lot.&lt;/strong&gt; I like using application launchers, and
krunner looks nice, is lightning fast, and gets the job done. I
didn’t really know much about krunner during previous attempts at
using KDE. This was definitely contributor to why I liked it much
more this time around.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/OBkFsxYDJf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/OBkFsxYDJf-1200.jpeg&quot; alt=&quot;Plasma Settings&quot; width=&quot;1200&quot; height=&quot;1065&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The ability to use old-school animations is fun&lt;/strong&gt; There is not much to say
here. While not very practical, sometimes spinning a desktop cube is
just straight up &lt;em&gt;fun&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/iIJwa5p3sj-1024.webp 1024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/iIJwa5p3sj-1024.jpeg&quot; alt=&quot;Desktop Cube&quot; width=&quot;1024&quot; height=&quot;768&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast. Even for a full DE&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multi-monitor support. WTF.&lt;/strong&gt; This was by far the biggest issue I
had. My main computer is a 17&amp;quot; laptop that I connect to two 24&amp;quot;
displays. However, because it is a indeed a laptop, I sometimes move
it around and be semi-mobile with it. This requires disconnecting the
monitors and going from 1 screen to 3 or vice-versa. Well it turns
out that this is usually a pain in the ass for me in Plasma. First,
I have to configure the monitors one-by-one, hitting “apply” between
each configuration (it doesn’t like when I try to move and place all
3 displays in one swoop). Second, once I setup the screen placement,
the panels often go crazy and can’t be found, or I have to play
with them to set them up correctly. Lastly, sometimes the displays
overlap each other oddly, even though they appear normal in the
display settings. The wallpapers almost never set correctly and my
right-click menu is disabled on some screens but not
others. Basically, it’s just a hot mess and the most infuriating
problem I faced when using Plasma. If I used a laptop without
monitors, or even a permanent desktop setup, I would be
fine. However, that is not my use case.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/nVtPsKbovf-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/trying-out-plasma5/nVtPsKbovf-1200.jpeg&quot; alt=&quot;Plasma Settings&quot; width=&quot;1200&quot; height=&quot;478&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Randomly just stops working&lt;/strong&gt; Sometimes, KDE applications just
crash and I can’t seem to get them back. Widgets seemed to beak
things more frequently, so I just stopped using them. (Again, not a
good solution)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A full DE&lt;/strong&gt; While I said above that it was nice having an
environment that resembled a window manager, but had the benefit of
the full suite of applications that come with a desktop environment,
sometimes it was also a pain. There’s just a lot of crap that I
don’t use. The obvious reminder of this was when I would run
&lt;code&gt;pacman -Syu&lt;/code&gt;, and see that the massive KDE stack would need
updates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sometimes all the settings can be very confusing&lt;/strong&gt; Having all
these options is great, but sometimes it was
overwhelming. Additionally, the settings felt disjointed and I
didn’t know where the settings for some things where.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Final Thoughts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I can finally use Plasma&lt;/li&gt;
&lt;li&gt;Very fast and lightweight for all that it is&lt;/li&gt;
&lt;li&gt;Still has a bit of stability/polish work to go&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Update: I took forever to get this post out and I have actually moved off of Plasma. I just couldn’t handle the multi-monitor issues I had. However, I will definitely be trying out the Desktop Environment again in the future (I moved to i3-gaps for now. I love my tiling window managers :) )&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using Cryogen for Website</title>
    <link href="https://ryan.himmelwright.net/post/website-switched-to-cryogen/" />
    <updated>2016-04-13T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/website-switched-to-cryogen/</id>
    <content type="html">&lt;p&gt;Ever since resurrecting my personal website, I have experimented with several
static website generators. Thus far, I have tried
&lt;a href=&quot;https://github.com/kelvinh/org-page&quot;&gt;org-page&lt;/a&gt;,
&lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; (&lt;em&gt;several&lt;/em&gt; times), and even (almost) made
&lt;a href=&quot;https://github.com/himmAllRight/ryBlog/blob/master/org-blog.el&quot;&gt;my own emacs org-page solution&lt;/a&gt;. Now that I have
started using the &lt;a href=&quot;http://clojure.org/&quot;&gt;clojure&lt;/a&gt; programming language, I have come across
&lt;a href=&quot;http://cryogenweb.org/&quot;&gt;cryogen&lt;/a&gt;. As you may have already guessed, this site is now
being generated using cryogen.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Emacs/org-mode Solutions&lt;/h2&gt;
&lt;p&gt;About a year ago, obsession with
&lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;emacs&lt;/a&gt; (and more importantly,
org-mode) eventually led me to try out
&lt;a href=&quot;https://github.com/kelvinh/org-page&quot;&gt;org-page&lt;/a&gt; while revitalizing my
personal website. The idea of writing webpage content in .org files
was &lt;em&gt;very&lt;/em&gt; appealing and I wanted to gain a more experience
writing emacs lisp, so it seemed like a good choice. Overall,
org-page was a good learning experience, but I found the documentation
and support to be lacking. It seemed to be more of a
personal project, rather than a fully supported
framework. while I know other people were able to
get it working, org-page simply wasn’t working for me.&lt;/p&gt;
&lt;p&gt;For over a year now, I have used org-mode at work each week to track
my hours and to take notes (including code snippets). I then export
the org files to html, creating a full index of my work
notes/logs. After digging deeper into org-page, realized it was just a
fancy wrapper around the org-project functions I used at work. So, I
decided to implment &lt;a href=&quot;https://github.com/himmAllRight/ryBlog/blob/master/org-blog.el&quot;&gt;my own emacs org-page solution&lt;/a&gt;
(well, &lt;em&gt;half&lt;/em&gt; implement… I guess I never fully finished
it). Creating my own solution helped me learn even more about org-mode
(which helped with my work notes), but I ultimately abandoned using this method
for my personal website. Managing my own solution quickly became a pain
because I had to setup emacs, install the dependencies, and get a &lt;em&gt;.emacs&lt;/em&gt; file working &lt;em&gt;just right&lt;/em&gt; on any compuer I wanted to generate the site from.&lt;/p&gt;
&lt;h2&gt;Jekyll&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-switched-to-cryogen/W49S7pxG-2-375.webp 375w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-switched-to-cryogen/W49S7pxG-2-375.jpeg&quot; alt=&quot;jekyll logo&quot; width=&quot;375&quot; height=&quot;166&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;My first experience with &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; was about two
years ago, right after I graduated college. It was the first true
static website generator that I tried and I absolutely loved the
concept. You see, I taught myself how to build websites in the early
to mid 2000’s when I was in middle school, and apparently never
developed beyond that point. During college, I maintained a personal
site by hand. It was super simplistic html that used tables for the
layout, and css for coloring. That’s about it. Using a static website
generator like Jekyll for the first time was amazing, since it
automatically produced static webpages that looked &lt;em&gt;much&lt;/em&gt; better than
anything I could do by hand. All I had to worry about was the
content. It was what 12-year-old-me longed for (and tried to do
using iframes and other messiness). I eventually stopped using Jekyll
because I was unable to get the theming quite right, and … well
… I started &lt;em&gt;really&lt;/em&gt; getting into emacs. But you’ve already heard
that story.&lt;/p&gt;
&lt;p&gt;After taking a breif hiatus from Jekyll to adventure deeper into emacs
land, I returned. This time, I was able to find and configure a
&lt;a href=&quot;https://github.com/joshgerdes/jekyll-uno&quot;&gt;enticing theme&lt;/a&gt; that fit my
needs. My personal website probably looked the best is ever has, and I
really enjoyed it. However, I have recently become frusterated using
Jekyll (again). It is a great static website generator, but because I
don’t often develop in Ruby (right now), setting up the proper ruby
gems environment and dependencies on
my computers makes me want to bang my head against
the wall. Additionally, I have been learning
&lt;a href=&quot;http://clojure.org/&quot;&gt;clojure&lt;/a&gt; and while digging through different
clojure projects, I found &lt;a href=&quot;http://cryogenweb.org/&quot;&gt;cryogen&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Cryogen&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/website-switched-to-cryogen/xLpVj0tXEC-1000.webp 1000w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/website-switched-to-cryogen/xLpVj0tXEC-1000.jpeg&quot; alt=&quot;Cryogen logo&quot; width=&quot;1000&quot; height=&quot;350&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://cryogenweb.org/&quot;&gt;Cryogen&lt;/a&gt; is a static website generator
written in &lt;a href=&quot;https://clojure.org/&quot;&gt;Clojure&lt;/a&gt;.  If you already have
&lt;a href=&quot;http://leiningen.org/&quot;&gt;Leiningen&lt;/a&gt; installed (which if using Clojure,
you should), starting a new Cryogen application is as easy as entering
&lt;code&gt;lein new cryogen project-name&lt;/code&gt; into a terminal. Once the project is
created, you can &lt;code&gt;cd&lt;/code&gt; into the directory and run &lt;code&gt;lein ring server&lt;/code&gt;.
Clojure will then fire up a local webserver of the compiled project
(by default on port 3000). Whenever a change to a project file is
saved, the cryogen server re-compiles the project and updates the
webserver. As a result, it easy to edit and see the changes
live. Cryogen has a rather large, but simple
&lt;a href=&quot;http://cryogenweb.org/docs/structure.html&quot;&gt;directory structure&lt;/a&gt; that is used to
organize the project. This structure is slightly different from
Jekyll, and takes a bit of getting to, but I think it does a better
job at keeping everything organized once you learn it.&lt;/p&gt;
&lt;p&gt;The one thing I &lt;em&gt;really&lt;/em&gt; like about Cryogen is the fact
that… well… it’s clojure. This means that things can often feel more
“lisp-y”. For example, in Jekyll, the preferences
and configuration of the website are kept inside a yaml configuration
file. Similarly, the meta-data and information for a page or blog post
are defined in a very specific yaml header at the top of the markdown
files. Cryogen follows a similar concept, but instead of yaml
headers, it simply uses a p-list for the
&lt;a href=&quot;http://cryogenweb.org/docs/configuration.html&quot;&gt;configuration setup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To change a post’s information (ex: title, author, date), one just has
to change the keywords in the list at the top of the post’s markdown
file. This flexability means that figuring out how to setup use my own
configuration was a breeze. For programmers that have previously used
Clojure or another LISP, the configuration in Cryogen is very
intuitive and natural.&lt;/p&gt;
&lt;p&gt;The only &lt;em&gt;downside&lt;/em&gt; (sort of… it’s my fault) I have experienced
while using Cryogen is that being a smaller community, there isn’t
much out there in terms of templetes and themes (at least that I was
able to find during a lazy search). So, while I was able to easily
setup an amazing looking website using Jekyll (by using someone else’s
hard work), I am forced to be a bit more hands-on using
cryogen. Initially, I thought this was a negative, but after spending
some time hacking away at the default theme and cleaning some rust off
css/html skills, I think I have the site looking &lt;em&gt;good enough&lt;/em&gt; for
now. As a bonus, I am starting to re-learn web design. However, I am
&lt;em&gt;slowly&lt;/em&gt; catching up, so “modern” design features like mobil support
might not happen right away. It’s not the best looking site, but I
has personal touch, which I guess is good in a personal website.&lt;/p&gt;
&lt;p&gt;That’s about it. That’s why my site suddenly looked different one
weekend. If I look forward to using both Clojure and Cryogen more for not
only this website, but other personal projects as well.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;* PS: Re-reading this post I realize trying Cryogen after getting excited about Clojure is VERY much like when I started using org-page after getting excited about emacs. Hopefully this setup is a keeper that I stick with. ;)&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Back On Arch, After Frantic Distro-hopping</title>
    <link href="https://ryan.himmelwright.net/post/back-on-arch/" />
    <updated>2016-03-13T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/back-on-arch/</id>
    <content type="html">&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-arch/3AO-aqcajn-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-arch/3AO-aqcajn-1200.jpeg&quot; alt=&quot;A post header image&quot; width=&quot;1200&quot; height=&quot;674&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;In the last few weeks, I have started my CS masters classes, and needed to setup a productive work environment on my portable laptop (abra). Also, while I love Fedora, the Nvidia stuff was being a pain on my main computer (alakazam), and I was looking for a replacement there as well. So, after a long, drawn out battle trying nearly all of my favorite Linux distros, I have found myself once again using &lt;a href=&quot;https://www.archlinux.org/&quot;&gt;Arch Linux&lt;/a&gt; on both of my personal computers.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-arch/AlvPURWDAH-500.webp 500w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-arch/AlvPURWDAH-500.jpeg&quot; alt=&quot;The Fedora logo&quot; width=&quot;500&quot; height=&quot;158&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When I started my journey, I had Fedora on both of my computers. I really like the &lt;a href=&quot;https://getfedora.org&quot;&gt;Fedora Project&lt;/a&gt; and I think Fedora is a great distribution with a lot of innovative new features. I seems that the Fedora Project is aiming to be the Linux Distro for developers, and I feel that they are really starting to hit that target.&lt;/p&gt;
&lt;p&gt;There is one remaining big issue I had while running Fedora on my main computer. Nvidia drivers. They are just a pain to maintain, and kept breaking my system during updates. I know the &lt;a href=&quot;http://rpmfusion.org/&quot;&gt;RPM Fusion Repos&lt;/a&gt; are supposed to help, but they just aren’t there yet. With my classes starting, I needed something that was a bit more stable (and would hopefully support installing VMWare Workstation 9).&lt;/p&gt;
&lt;h2&gt;The ‘Easy Stability’ of Ubuntu-based?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-arch/5B5hdABCZ6-320.webp 320w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-arch/5B5hdABCZ6-320.jpeg&quot; alt=&quot;Ubuntu Mate Logo&quot; width=&quot;320&quot; height=&quot;41&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Guided by desires to have a “stable” distro, I decided that the &lt;em&gt;obvious&lt;/em&gt; answer would be to use an &lt;a href=&quot;http://www.ubuntu.com/&quot;&gt;Ubuntu&lt;/a&gt;-based distro. So, I went with &lt;a href=&quot;https://ubuntu-mate.org/&quot;&gt;Ubuntu Mate&lt;/a&gt;, based on the &lt;em&gt;awesome&lt;/em&gt; work the team has done recently. I’ve used Ubuntu Mate in the past on several different computers, and in fact, still have it running on (charmander). I had some issues installing Ubuntu Mate on alakazam for some reason, but using a simple &lt;code&gt;nomodeset&lt;/code&gt; at boot seemed work.&lt;/p&gt;
&lt;p&gt;After I got it installed, I was able to install the nvidia drivers, but I had other random issues here and there. I also tried installing VMWare, but had issues at every step. To be completely honest, I really wasn’t feeling using an Ubuntu distro on alakazam. If I was going to be replacing Fedora, I wanted to make up for it by using something that gave me more power (I’ve started to get the itch to try out Gentoo again. Spoiler, I am not using Gentoo. At least until my class is over…). While Ubuntu could easily support my needs being the well supported and advanced distro that it is … I craved something else.&lt;/p&gt;
&lt;h2&gt;Leaping to Suse?&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-arch/j8zcPvBqvE-320.webp 320w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-arch/j8zcPvBqvE-320.jpeg&quot; alt=&quot;OpenSuse Logo&quot; width=&quot;320&quot; height=&quot;203&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;During my last few months on Fedora, I took notice of another rpm distro that was  making noise in the Linux community: &lt;a href=&quot;https://www.opensuse.org/&quot;&gt;OpenSuse&lt;/a&gt;. Specifically, the new &lt;a href=&quot;https://software.opensuse.org/421/en&quot;&gt;OpenSuse Leap&lt;/a&gt; piqued my interest. I had played with it in a VM a few days earlier and liked what I saw. The OpenSuse installer along with the YaST configuration tool makes it &lt;em&gt;stupid easy&lt;/em&gt; to setup powerful and advanced features in Linux. Want to setup a KVM virtualization environment or a samba share? You basically just have to click a button. Testing out &lt;a href=&quot;https://en.opensuse.org/Portal:Snapper&quot;&gt;Snapper&lt;/a&gt; was also slick. I wish more distributions would start to include tools like snapper, built in.&lt;/p&gt;
&lt;p&gt;Leap was a solid and extensive distribution. Ultimately though, that was the biggest qualm I had with it: there was a lot going on. I did not know what many of the pre-installed configuration tools were. Normally, I am fine with learning new tools, but I wanted a distribution that I could set up and get to work. OpenSuse had &lt;em&gt;too many&lt;/em&gt; “toys” already in it. I would not be able to resist spending hours learning everything. Ironically, my other complaint was that some of the applications I &lt;em&gt;did&lt;/em&gt; want, were not easily available. Often, they were not included in the default repositories, and while many application websites will provide a *.deb download, *.rpms are a bit more scarce. I know there is the wonderful &lt;a href=&quot;https://build.opensuse.org/&quot;&gt;openSuse Build Service&lt;/a&gt;, but like &lt;a href=&quot;https://copr.fedorainfracloud.org/&quot;&gt;Fedora’s copr build system&lt;/a&gt;, I often found it to be hit or miss. Both do a good job at addressing a major issue in their respective distributions, but neither isn’t quite to the level of … well, the &lt;a href=&quot;https://aur.archlinux.org/&quot;&gt;AUR&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Antegeros&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-arch/QDn61H5t1M-453.webp 453w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-arch/QDn61H5t1M-453.jpeg&quot; alt=&quot;Antegeros Logo&quot; width=&quot;453&quot; height=&quot;130&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Which brings me to &lt;a href=&quot;https://en.wikipedia.org/wiki/Antergos&quot;&gt;Antergos&lt;/a&gt;. By this point, I was no longer able to hide the fact that I was starting to severely miss many features a distribution like &lt;a href=&quot;https://www.archlinux.org/&quot;&gt;Arch Linux&lt;/a&gt; provides. I love that you can easily customize just about &lt;em&gt;anything&lt;/em&gt;. I also appreciate that if I want to try something new, 99% of the time the latest version is already in the official repos or the &lt;a href=&quot;https://aur.archlinux.org/&quot;&gt;AUR&lt;/a&gt;. Additionally, when I am trying a new application, or customizing my setup, the &lt;a href=&quot;https://wiki.archlinux.org/&quot;&gt;Arch Wiki&lt;/a&gt; has some of &lt;em&gt;the best documentation&lt;/em&gt; in the Linux ecosystem. However, I wanted something that I could just install and have working. I didn’t feel like going through a full vanilla Arch install. So I installed Antergos. Antergos has a simple but thorough installer that pulls down all the latest packages during the installation. This leaves the user with an up-to-date and &lt;em&gt;gorgeous&lt;/em&gt; setup after installation.&lt;/p&gt;
&lt;p&gt;Unfortunately, the “simplicity” of Antergos (which is sort of the antithesis to the definition of &lt;em&gt;simplicity&lt;/em&gt; in &lt;a href=&quot;https://wiki.archlinux.org/index.php/Arch_Linux#Simplicity&quot;&gt;The Arch Way&lt;/a&gt;) always seems to bite me in the end. Eventually, &lt;em&gt;something&lt;/em&gt; breaks, and I have a hard time figuring out &lt;em&gt;what&lt;/em&gt;. While it might be a pain to install &lt;em&gt;everything&lt;/em&gt; in vanilla Arch, the user &lt;em&gt;&amp;quot;has a much fuller understanding of his or her system&lt;/em&gt;&amp;quot; as a result. A few years ago, when I first started using Arch, I thought this argument just stupid gray beard nonsense. Now, I appreciate the benefits of this sentiment, and even agree with it (I’ve also learned to script my post install process, so it is less of a pain :P).&lt;/p&gt;
&lt;p&gt;After a few days of fighting with Antegeros, I decided to just bite the bullet and install Vanilla Arch. I still think Antegros is a great distribution that works for a bunch of people. It is great for users that want to experience the power of arch Linux, without having to go through all the overhead. Antegeros is actually how I started to learn Arch Linux. Nowadays though, I seem to prefer &lt;em&gt;simplicity&lt;/em&gt; of vanilla Arch.&lt;/p&gt;
&lt;h2&gt;All Roads Lead to Arch&lt;/h2&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-arch/wHYPEPbUBy-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-arch/wHYPEPbUBy-1200.jpeg&quot; alt=&quot;Arch Linux Logo&quot; width=&quot;1200&quot; height=&quot;398&quot;&gt;&lt;/picture&gt;
        
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;I have been running Arch on both of my personal laptops for several weeks now. Arch doesn’t magically solve all of my issues, but it makes &lt;em&gt;fixing&lt;/em&gt; them much easier. If something isn’t working, the combination of the wiki and the community make the problem &lt;em&gt;much&lt;/em&gt; more approachable. Additionally, the rolling release model appears to mitigate bug-related issues, instead of cause them. For example, I installed Arch on alakazam first, but left Ubuntu Mate on abra (my portable laptop). I did this because my school has a weird network setup that is often difficult to connect to. After searching for hours, I determined that my issues under Ubuntu were caused by a known, but unfixed, bug in my version of network manager. I did not feel like dealing with this, so I installed arch. My internet at school works works fine now. I was able to use the wiki to properly configure my settings, and I was good to go. (Side-note: I never fully got VMWare 9 to work properly on any of my distros, but I came closest with Arch.)&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/back-on-arch/dIE_9d4oHr-1200.webp 1200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/back-on-arch/dIE_9d4oHr-1200.jpeg&quot; alt=&quot;A desktop screenshot running the awesome wm&quot; width=&quot;1200&quot; height=&quot;674&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Abra running Arch Linux with the [Awesome](http://awesome.naquadah.org/) window manager&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Being back on arch has been great. I love using tiling window managers like i3, dwm, xmonad, stumpwm, and awesome. While this this is possible on all distributions, it is much easier on arch. Starting with a blank slate, and (again) having the wiki makes setting up alternative desktop environments and window managers a breeze. I now have a &lt;em&gt;very&lt;/em&gt; productive work environment that is tailored to my needs. While Arch Linux might not be a traditional “stable” distribution, I have found it to be much more durable for my use-case.&lt;/p&gt;
&lt;p&gt;I am planning to stay on Arch for awhile. That being said, I really did enjoy all the other distributions I tried out. Each one seems to be carving out a niche in the Linux environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fedora is becoming a great distribution for Linux developers. In fact, I still have Fedora 23 (Server Edition) running on my home server. Assuming that the upgrade to 24 goes smoothly (my upgrade from 22 -&amp;gt; 23 went great, thanks to &lt;a href=&quot;https://fedoraproject.org/wiki/Features/DNF&quot;&gt;dnf&lt;/a&gt;), I see no reason to switch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenSuse Leap is a solid distribution that is great for power users that want a turn-key distribution with all the bells and whistles. I think it is especially geared toward people with system administration backgrounds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ubuntu Mate is really the best distribution out there for people that want a classic feeling Linux environment, with a modern flair. It is what Ubuntu would be if it never left gnome 2. It is also great for low resource hardware, particularly boards like the &lt;a href=&quot;https://www.raspberrypi.org/&quot;&gt;raspberry pi&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Antegros is fantastic for Arch users that hate the Arch install. It is also great for people that want to move to Arch, but aren’t yet comfortable with tools like pacman.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is great to see all the distributions finally finding their groove. This isn’t something I could say about Linux 2 years ago. As I have already stated, I plan to stay on Arch Linux for the time being, but I am very excited to continue trying out the new releases of the all distributions listed above. With so many great options, it really is a great time to be a Linux user :) .&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How I asked my Groomsmen</title>
    <link href="https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/" />
    <updated>2016-01-06T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/</id>
    <content type="html">&lt;p&gt;Over the last few months Rebecca (my Fiancee) and I have continued making progress on all the little tasks that we have to complete before our wedding. One of these tasks is asking our intended bridesmaids/groomsmen to be in the wedding.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Rebecca approached this by asking each of the girls in a way that was somewhat unique to each person, over time. Even though each person was asked differently, all the methods were executed in a very “Rebecca-like” manner.&lt;/p&gt;
&lt;p&gt;So, I was told that I couldn’t simply ask the guys. I had to figure out a “fun” way to ask them, and if possible, to do it in a way that was very “Ryan-like”.&lt;/p&gt;
&lt;h2&gt;Asking the Groomsmen&lt;/h2&gt;
&lt;p&gt;While Rebecca was slowly chipping away at asking her bridesmaids, we kept failing at finding a way to ask the guys. Finally, one night while driving back north to Massachusetts, I said out of frustration:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I wish I could just write a stupid piece of code that asks them,
compile it, and give it to them… wait a minute…”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At that moment, I started to figure out how I could make it work. The results of that brainstorming session are described below:&lt;/p&gt;
&lt;h2&gt;Snail Mail Part&lt;/h2&gt;
&lt;p&gt;I first had to find a way to give the intended groomsmen a link to get any code that I wrote. So, Rebecca and I designed the tri-fold letter pictured below (Sorry for the photo quality).&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/hbosTJ6ZW4-400.webp 400w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/hbosTJ6ZW4-400.jpeg&quot; alt=&quot;The front of the letter&quot; width=&quot;400&quot; height=&quot;226&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Figure 1: The Front of Letter&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;The front of the envelope had a simple black boarder and said “She said that I had to make it official.”&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/TsVGefH4YO-400.webp 400w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/TsVGefH4YO-400.jpeg&quot; alt=&quot;The first flap of the letter&quot; width=&quot;400&quot; height=&quot;226&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Figure 2: The First Flap of the Letter&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;When the reader flipped up that flap, another message read “So we’re doing this my way”.&lt;/p&gt;
&lt;p&gt;
      &lt;/p&gt;&lt;figure&gt;
        &lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/Gs-Hg-31-d-200.webp 200w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryan.himmelwright.net/post/how-i-asked-my-groomsmen/Gs-Hg-31-d-200.jpeg&quot; alt=&quot;The QR code posted inside the letter&quot; width=&quot;200&quot; height=&quot;200&quot;&gt;&lt;/picture&gt;
        &lt;figcaption&gt;Figure 3: The QR Code posted on the Inside of the Letter&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;p&gt;Lastly, when the reader folded that flap down, they got a taste of what “my way” might entail when the only thing that was printed on the page was a single QR code.&lt;/p&gt;
&lt;p&gt;When the reader scanned the QR code, it opened up a list of instructions on their phone that said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Find your computer.&lt;/li&gt;
&lt;li&gt;Go to /message/ and follow the instructions.&lt;/li&gt;
&lt;li&gt;Text Me&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;*Note: Link no longer active.&lt;/p&gt;
&lt;h2&gt;Website&lt;/h2&gt;
&lt;p&gt;When the intended groomsmen went to the link provided, they saw a webpage that I made, containing our wedding logo on the bottom right, and a welcome message. The welcome message basically stated that the webpage was not the end of the scavenger hunt, and that they would have to download some code that I compiled, that contained the message.&lt;/p&gt;
&lt;p&gt;Below the welcome message, I had separate instructions that guided Window, Mac, and Linux users, as well as users that wished to compile from source (sort of).&lt;/p&gt;
&lt;p&gt;Below that I had links to the executable downloads for each operating system mentioned. Lastly, I posted the source code for the code they were downloading, because … you know… open source.
Executable(s)&lt;/p&gt;
&lt;p&gt;When the intended groomsmen ran the compiled code (source code posted below), it showed them that it was “decrypting” their message, and printed the percent complete. After about 20% or so, I confessed that there was no “decrypting” and that each message is decoded when printed. I then proceeded to rant in these “decrypting” messages about how I originally wrote a nice GUI python app, but it was a pain to cross compile, and how I eventually just scrapped it and wrote it in LISP. I also mentioned that if they all ran Linux, it wouldn’t of been an issue. When it finally reached 100%, I decided to finally give them the message.&lt;/p&gt;
&lt;p&gt;Source Code for the executable I wrote containing the message. Nothing fancy, but everything hidden.&lt;/p&gt;
&lt;pre class=&quot;language-scheme&quot;&gt;&lt;code class=&quot;language-scheme&quot;&gt;
&lt;span class=&quot;token comment&quot;&gt;;; Used to build the executable&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; build-exe &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exe-src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sb-ext:save-lisp-and-die&lt;/span&gt; exe-src :toplevel #&lt;span class=&quot;token symbol&quot;&gt;&#39;main&lt;/span&gt; :executable t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; main &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;intro-message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrypt-loading&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;secret-message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; intro-message &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt; t &lt;span class=&quot;token string&quot;&gt;&quot;~a~%~%&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;33&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; 
                                       &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; 
                                       &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; 
                                       &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; 
                                       &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; 
                                       &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt; t &lt;span class=&quot;token string&quot;&gt;&quot;~a~%&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; 
                                     &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; decrypt-loading &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;messages&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Enq&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;89&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Newline&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Si&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;69&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;D&lt;/span&gt;c4 &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Nak&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Syn&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Etb&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Can&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Em&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;69&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Sub&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Rs&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;!&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;71&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;85&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;81&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;$&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;66&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;85&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;79&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;&#39;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;45&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;87&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;77&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;76&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;120&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;81&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;0&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;69&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;87&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;77&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;41&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;2&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;41&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;71&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;85&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;106&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;87&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;@&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;C&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;45&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;67&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;76&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;F&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;76&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;76&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;I&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;66&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;87&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;70&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;M&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;66&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;P&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;106&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;76&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;120&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;S&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;71&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;85&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;V&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;66&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;41&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;Y&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;&#92;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;66&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;_&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;49&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;37&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;b&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt;
                      &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;#&#92;d&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;70&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;41&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrypted-messages&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-pair-list&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt; t &lt;span class=&quot;token string&quot;&gt;&quot;~a~%&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;68&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;77&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dotimes&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;cond&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt; i &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;caar&lt;/span&gt; decrypted-messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt; t &lt;span class=&quot;token string&quot;&gt;&quot;~d% ~a   ~a ~%&quot;&lt;/span&gt; i &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;67&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;cdar&lt;/span&gt; decrypted-messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setf&lt;/span&gt; decrypted-messages &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;cdr&lt;/span&gt; decrypted-messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;t&lt;/span&gt;
             &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt; t &lt;span class=&quot;token string&quot;&gt;&quot;~d% ~a~%&quot;&lt;/span&gt; i  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;67&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; secret-message &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt; t &lt;span class=&quot;token string&quot;&gt;&quot;~%~a~%~%~%- ~a~%~%&quot;&lt;/span&gt; 
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;106&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;45&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;113&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;82&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;46&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;104&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;68&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;65&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;89&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;118&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;112&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;69&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;82&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;83&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;79&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;78&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;73&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;119&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;116&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;87&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;105&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;117&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt;
                            &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;103&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;114&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;115&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;63&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;82&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;97&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; encode-message &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapcar&lt;/span&gt; #&lt;span class=&quot;token symbol&quot;&gt;&#39;char-code&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concatenate&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;&#39;list&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; decode-message &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;num-list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;&#39;string&lt;/span&gt; #&lt;span class=&quot;token symbol&quot;&gt;&#39;code-char&lt;/span&gt; num-list&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; encode-pair-list &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pair-list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapcar&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token lambda-parameter&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;cons&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;code-char&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;car&lt;/span&gt; pair&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;cdr&lt;/span&gt; pair&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; pair-list&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;;;(load &quot;decryptMessage.cl&quot;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defun&lt;/span&gt; decode-pair-list &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pair-list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapcar&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token lambda-parameter&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;cons&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;char-code&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;car&lt;/span&gt; pair&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode-message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;cdr&lt;/span&gt; pair&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; pair-list&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Message&lt;/h2&gt;
&lt;p&gt;After putting up with my ranting, I finally posted the message asking the guys to be my groomsmen. It read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So, putting all jokes and nerdy-ness aside, I do have an important
question to ask. As you are probably aware, Rebecca and I are finally
getting married in September. This is will be a very special &lt;em&gt;DAY&lt;/em&gt; in
my life, and as a very special &lt;em&gt;PERSON&lt;/em&gt; in my life, I would like to
ask:  Will you be one of my groomsmen?&lt;/p&gt;
&lt;p&gt;Ryan&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;About 2 days after we mailed out the letters, all of then intended groomsmen had messaged me back, confirming that they would be my groomsmen. I also think they enjoyed my methods of asking.&lt;/p&gt;
&lt;p&gt;I guess I was successful in asking everyone in a very “Ryan-like” fashion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt;: The &lt;a href=&quot;https://github.com/himmAllRight/groomsmen-ask&quot;&gt;source code repo&lt;/a&gt; for this little project is now publically available on my &lt;a href=&quot;https://github.com/himmAllRight&quot;&gt;Github Page&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>25 Days of C</title>
    <link href="https://ryan.himmelwright.net/post/25-days-of-c/" />
    <updated>2015-12-24T00:00:00Z</updated>
    <id>https://ryan.himmelwright.net/post/25-days-of-c/</id>
    <content type="html">&lt;p&gt;As I continue to learn more and more programming languages, I am somewhat ashamed to admit that I have yet to learn C. However, it is not because I am trying to avoid it.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;The Reason&lt;/h2&gt;
&lt;p&gt;C is one of the top languages I wish to know, and know well. C is the backbone for so much code, especially in the Linux ecosphere that it is almost imperative for one to at least understand it. I keep trying to poke my head into open source projects, and so many of them are built or based on C (especially when looking at projects more at the distro and distro applications level).&lt;/p&gt;
&lt;p&gt;Also, this spring I will start working on my CS MS degree, and while I think the first course I take will be theory based, I know several other courses that will require it (ex: computer architecture and compilers). Being comfortable with C will allow me to focus on the topics, and not on the syntax.&lt;/p&gt;
&lt;p&gt;I have some experience with C++ from my software engineering course I took during undergrad, but that is about it. My goal however is to understand just plain C, which will hopefully make implementing other C-based languages such as C++ a bit easier. My ultimate goal is to eventually have C be one of my top 3 languages (I’m thinking LISP, C, and maybe python or ruby. I know. I’m old school.).&lt;/p&gt;
&lt;h2&gt;The Plan&lt;/h2&gt;
&lt;p&gt;In the last few weeks, I have set up and started looking at Learn C the Hard Way. I read the first section or two, but haven’t been consistent with working through it.&lt;/p&gt;
&lt;p&gt;Now that it is December, I thought I should maybe start a new monthly challenge, or even a “12/25 days till something”. I was thinking about things I’ve been trying to constantly work on the last few months, and C popped into my head. Learn C the Hard Way fits particularly well because it is broken down nicely into small segments that I can maybe commit to being able to finish 1 per day.&lt;/p&gt;
&lt;p&gt;I have to look ahead a bit to see if this challenge is doable, but i think that as of now, the plan is to try to complete at least 1 (I can work ahead if I want to) segment of the book a day for the month of December. I am also going to allow myself the ability to “pre-cache” segments ahead of time so that I don’t have to pull away and work on it during the holidays if I don’t want to.&lt;/p&gt;
&lt;p&gt;Overall, I think this is a good plan that should allow me to start getting some C experience under my belt. I’ll be sure to update in the future with how it goes.&lt;/p&gt;
&lt;h2&gt;Update&lt;/h2&gt;
&lt;p&gt;So, now that I have made it a good bit through the month, I must admit that I have not adhered to this challenge very strictly. I have done many of the exercises, but I have fallen somewhat behind. Basically, I accomplished a bunch of other things and let this challenge slide. I will continue to work on it, but I haven’t been able to keep with with the one exercise per day rate. Oh well.&lt;/p&gt;
</content>
  </entry>
</feed>