<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>posts on Loren&#39;s blog</title>
    <link>https://lorendb.dev/posts/</link>
    <description>Recent content in posts on Loren&#39;s blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>loren@lorendb.dev (Loren Burkholder)</managingEditor>
    <webMaster>loren@lorendb.dev (Loren Burkholder)</webMaster>
    <copyright>© Loren Burkholder</copyright>
    <lastBuildDate>Thu, 05 Mar 2026 23:13:52 -0500</lastBuildDate>
    <atom:link href="https://lorendb.dev/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Shut Up and Take My Money</title>
      <link>https://lorendb.dev/posts/shut-up-and-take-my-money/</link>
      <pubDate>Thu, 05 Mar 2026 23:13:52 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/shut-up-and-take-my-money/</guid>
      <description>&lt;p&gt;I recently had the opportunity to purchase some software in an IT role. Everything went well until I needed a modification to my billing. Easy enough, according to the software&amp;rsquo;s website. &lt;em&gt;Just email us and we&amp;rsquo;ll apply the modification.&lt;/em&gt; So I shot off an email asking to apply a modification to my billing.&lt;/p&gt;&#xA;&lt;p&gt;I received a friendly response from a salesman: &amp;ldquo;How are you planning to use our product?&amp;rdquo; OK, fine, he can get his metrics. I&amp;rsquo;m not opposed to that. So I shot off another email, briefly describing my use case. Additionally, I mentioned that we have subscribed to this software; we&amp;rsquo;re running a trial period for it and plan to fully roll it out before long, barring any major hiccups.&lt;/p&gt;&#xA;&lt;p&gt;The salesman&amp;rsquo;s next reply took me off guard. &amp;ldquo;I think our product will work well for you. Let&amp;rsquo;s set up a sales call.&amp;rdquo; I&amp;rsquo;m sorry, what?! I already told you that I have bought your product; you don&amp;rsquo;t have to sell it to me again.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s go back in time to an entirely different yet hauntingly familiar incident.&lt;/p&gt;&#xA;&lt;p&gt;Some time ago, I was asked to look into purchasing email security software. This seemed like a straightforward task. However, I was dumbfounded to see that it was impossible to purchase email security software&amp;hellip; unless you were willing to go through a sales call. You couldn&amp;rsquo;t even find pricing numbers on product websites. For at least one company, I filled out the contact form with an invalid phone number so I could ask for pricing without worrying that my phone would start ringing off the hook.&lt;/p&gt;&#xA;&lt;p&gt;In both of these cases, the software in question is aimed squarely at IT professionals. Lest we forget, the stereotypical IT worker is an introverted nerd who hates talking to people. If you are trying to sell something to an introvert who hates talking to people, your selling point should be a self-service checkout, so the introvert can avoid sales calls.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not an idiot; neither am I incapable of research. When I need to purchase software, I research my options, compare their features (and price), and decide which best meets my needs. Then I go searching for an &amp;ldquo;add to cart&amp;rdquo; button. If I am met instead with &amp;ldquo;Schedule a call,&amp;rdquo; I am not pleased. I already decided I was going to give you money; you don&amp;rsquo;t have to sell your product to me again.&lt;/p&gt;&#xA;&lt;p&gt;If I have a question about your product during the research phase, I&amp;rsquo;ll first refer to the internet. If I can&amp;rsquo;t surface anything useful myself, I may see if an LLM knows the answer. Only if neither the internet nor an LLM can help me will I send your sales team an email, because I know they will hound me for money and try to set up sales calls that I don&amp;rsquo;t want. A self-service sale should be much more meaningful than a sale that is the result of a sales team, because it means the customer liked your product without you breathing down their neck about it. In fact, your &amp;ldquo;Features&amp;rdquo; page on your website should be the entirety of your sales team.&lt;/p&gt;&#xA;&lt;p&gt;Shut up and take my money.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>rant</category>
    </item>
    <item>
      <title>Getting Bitten by Poor Naming Schemes</title>
      <link>https://lorendb.dev/posts/getting-bitten-by-poor-naming-schemes/</link>
      <pubDate>Thu, 18 Dec 2025 23:50:43 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/getting-bitten-by-poor-naming-schemes/</guid>
      <description>&lt;p&gt;I recently came into possession of an old Dell Precision T3610 workstation and promptly installed Proxmox to&#xA;add it to my Proxmox cluster. After performing some ludicrously silly RAM and storage upgrades (how about 96&#xA;GB of DDR3, plus a 13-disk array of 500 GB SSDs?), I decided I wanted to max out the CPU as well.&lt;/p&gt;&#xA;&lt;p&gt;The Precision T3610 shipped with an &lt;a href=&#34;https://www.intel.com/content/www/us/en/products/sku/75780/intel-xeon-processor-e51650-v2-12m-cache-3-50-ghz/specifications.html&#34;&gt;Intel Xeon E5-1650 v2&lt;/a&gt;.&#xA;According to the linked Intel product page, this CPU uses the FCLGA2011 socket. &lt;em&gt;Easy enough,&lt;/em&gt; I thought to&#xA;myself. &lt;em&gt;Just find the best CPU that supports FCLGA2011, make sure you have the latest BIOS installed, and&#xA;everything should be all hunky dory.&lt;/em&gt; So I did some research and landed on the&#xA;&lt;a href=&#34;https://www.intel.com/content/www/us/en/products/sku/93790/intel-xeon-processor-e78890-v4-60m-cache-2-20-ghz/specifications.html&#34;&gt;Xeon E7-8890 v4&lt;/a&gt;.&#xA;It&amp;rsquo;s several years newer than the E5-1650 v2, has a whopping 24 cores (and hyperthreading bumps it to 48 logical&#xA;cores!), and can support having not one, not two, but &lt;em&gt;eight&lt;/em&gt; of itself installed in a single motherboard! Most&#xA;crucially, the Intel product page says it uses the FCLGA2011 socket. When I stumbled across one of these monsters&#xA;on eBay for just $15, I snapped it up.&lt;/p&gt;&#xA;&lt;p&gt;Cue my massive shock and disappointment when, a few days later, I found myself unable to install the E7-8890 v4&#xA;in my T3610. The new CPU, despite being the same physical size as the old CPU, had extra contacts on the bottom&#xA;and had a different physical keying. What? I thought Intel said this was the same socket!&lt;/p&gt;&#xA;&lt;p&gt;Some amount of research later, I discovered that Intel&amp;rsquo;s LGA2011 socket has many variations. One of these&#xA;variations is also called Socket R (or LGA2011-0). The T3610, and by extension the old E5-1650 v2 CPU, uses&#xA;Socket R. The newer E7-8890 v4, meanwhile, uses a different variation called Socket R2 (or LGA2011-1). As if&#xA;this wasn&amp;rsquo;t confusing enough, there&amp;rsquo;s even a third variation of the LGA2011 socket! I&amp;rsquo;ll refer you to the&#xA;&lt;a href=&#34;https://en.wikipedia.org/wiki/LGA_2011#Physical_design_and_socket_generations&#34;&gt;Wikipedia&lt;/a&gt; page for more info&#xA;on that.&lt;/p&gt;&#xA;&lt;p&gt;This is obviously not a great naming scheme. Why not use unique numbers for each version of the socket instead of&#xA;tacking on a suffix? But the real kicker here is that Intel itself doesn&amp;rsquo;t seem to be able to keep up with its own&#xA;naming scheme! It appears that its CPU specifications pages refer to all variants of the LGA2011 socket as&#xA;FCLGA2011. This leaves folks like myself wondering what went wrong when their new-to-them CPUs don&amp;rsquo;t fit in their&#xA;motherboards.&lt;/p&gt;&#xA;&lt;p&gt;So where does that leave me? Well, I now have a fancy paperweight. I could have returned the CPU, but return&#xA;shipping costs would have been half of what I paid for the CPU itself, so I&amp;rsquo;m hanging onto it for now in case I&#xA;ever come into possession of a server with a Socket R2 motherboard that could use a nicer CPU. At least it wasn&amp;rsquo;t&#xA;a super expensive CPU, so all in all, this isn&amp;rsquo;t the worst learning experience ever.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>hardware</category><category>rant</category><category>selfhosting</category>
    </item>
    <item>
      <title>The Advantage of a Fishtank PC Case</title>
      <link>https://lorendb.dev/posts/the-advantage-of-a-fishtank-pc-case/</link>
      <pubDate>Thu, 06 Feb 2025 17:16:41 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/the-advantage-of-a-fishtank-pc-case/</guid>
      <description>&lt;p&gt;My PC is built in a fishtank-style case, namely the &lt;a href=&#34;https://lian-li.com/product/o11-vision/&#34;&gt;Lian Li O11 Vision&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/fishtank-case-advantage/lian-li-o11-vision.jpg&#34;&#xA;    alt=&#34;A picture of a black PC case with three sides having glass panels&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;The Lian Li O11 Vision. What a nice case!&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Despite being built as a collab with the PC Masterrace subreddit (aka PCMR), this case and others with the same fishtank style&#xA;are widely derided by many Redditors. While glass panels give you a great view of your internal components and RGB lighting,&#xA;not everybody &lt;em&gt;likes&lt;/em&gt; RGB, and having the top and front made of glass limits the effective airflow of the case, leaving your&#xA;PC susceptible to running hotter. However, I think I have identified the biggest advantage that fishtank cases have over more traditional cases.&lt;/p&gt;&#xA;&lt;p&gt;It is easy to forget to occasionally clean out your PC; when you do finally open the case,&#xA;&lt;a href=&#34;https://www.reddit.com/r/pcmasterrace/comments/1iipo0o/cleaned_my_pc/&#34;&gt;the&lt;/a&gt;&#xA;&lt;a href=&#34;https://www.reddit.com/r/pcmasterrace/comments/1hbe6qy/this_is_a_friendly_reminder_to_clean_out_your_pc/&#34;&gt;results&lt;/a&gt;&#xA;&lt;a href=&#34;https://www.reddit.com/r/pcmasterrace/comments/19chqvf/cleaned_finally/&#34;&gt;can&lt;/a&gt;&#xA;&lt;a href=&#34;https://www.reddit.com/r/pcmasterrace/comments/1iakvag/what_smoking_10_joints_a_day_inside_of_your_house/&#34;&gt;be&lt;/a&gt;&#xA;&lt;a href=&#34;https://www.reddit.com/r/pcmasterrace/comments/wsd8ch/dirty_cpu_fan/&#34;&gt;horrifying&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;With a fishtank case, this will not happen. As you use the PC, you will always have a front-row seat to the internals. In order&#xA;to keep your PC presentable, you will of necessity need to clean the thing out every now and again. Despite having my PC for just&#xA;three months, I&amp;rsquo;ve already popped the side panels off to dust the glass once.&lt;/p&gt;&#xA;&lt;p&gt;If you still hate fishtank cases but want to make sure you see any dust or just prevent dust outright, might I suggest trying an&#xA;&lt;a href=&#34;https://streacom.com/products/da6-mini-itx-case/&#34;&gt;open frame case&lt;/a&gt; or an &lt;a href=&#34;https://immersion-cooling.thermaltake.com/&#34;&gt;immersion cooling case&lt;/a&gt;?&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>pc</category>
    </item>
    <item>
      <title>The Case for Custom Email Domains</title>
      <link>https://lorendb.dev/posts/the-case-for-custom-email-domains/</link>
      <pubDate>Sun, 26 Jan 2025 22:02:01 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/the-case-for-custom-email-domains/</guid>
      <description>&lt;p&gt;I recently switched my primary email to be hosted under my own domain, instead of using Outlook.com mail. It turns out there both advantages and disadvantages to doing so. I&amp;rsquo;ll try to fairly address both sides, but (spoiler alert) I think that the pros heavily outweigh the cons.&lt;/p&gt;&#xA;&lt;p&gt;Before we begin, I&amp;rsquo;d like to note that I personally am using &lt;a href=&#34;https://purelymail.com&#34;&gt;Purelymail&lt;/a&gt;&amp;rsquo;s hosting, as it is cheap ($10/yr) and has glowing reviews from its customers. However, I am not affiliated with Purelymail, nor will I discourage you from using some other service.&lt;/p&gt;&#xA;&lt;p&gt;Also, I will refer to all the well-known free email providers as Big Email for the remainder of this article.&lt;/p&gt;&#xA;&lt;h2 id=&#34;reasons-to-use-a-custom-domain&#34;&gt;Reasons to use a custom domain&lt;/h2&gt;&#xA;&lt;p&gt;My biggest reason for moving to my own domain was to assert ownership over my online accounts. It&amp;rsquo;s not uncommon to read stories of people whose Big Email accounts were inexplicably permabanned with no chance of recovering them. With a custom domain, even if my mail provider goes dark or bans my account, I can just point my DNS records at another mail service and maintain access to all my accounts that rely on sending authentication emails.&lt;/p&gt;&#xA;&lt;p&gt;Switching to a custom domain is obviously cool because you can control the entire email address, not just the username.&lt;/p&gt;&#xA;&lt;p&gt;If you want to get some real hands-on experience, you can run your own mail server! All you need is a cheap VPS and a willingness to get your hands dirty.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A VPS (virtual private server) is required here because residential ISPs typically block email servers being run from your home.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;On the other hand, services like Purelymail will run the mail server for you for very cheap, taking most of the burden off your shoulders without&lt;/p&gt;&#xA;&lt;h2 id=&#34;reasons-to-avoid-a-custom-domain&#34;&gt;Reasons to avoid a custom domain&lt;/h2&gt;&#xA;&lt;p&gt;Setting up a custom domain can be a bit tricky if you aren&amp;rsquo;t a technically inclined person (although if you&amp;rsquo;re reading my blog, you likely are savvy enough). On the other hand, Big Email provides a pain-free solution that anybody can use. A counterpoint to this argument: when you buy a domain from &lt;a href=&#34;https://porkbun.com&#34;&gt;Porkbun&lt;/a&gt;, you are offered turnkey email hosting for $2/inbox/mo. Porkbun handles all the DNS records for you.&lt;/p&gt;&#xA;&lt;p&gt;While custom domains do save you from Big Email lockouts, the onus is still on you to make sure that you don&amp;rsquo;t fall victim to &lt;a href=&#34;https://en.wikipedia.org/wiki/Domain_hijacking&#34;&gt;domain hijacking&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Big Email typically provides much more robust spam detection and reporting features than smaller providers will.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;There are certainly valid reasons to avoid custom email domains, but I think that if you already own a domain, you should seriously consider using it for your email as well.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>email</category><category>selfhosting</category>
    </item>
    <item>
      <title>Fixing VR&#39;s screen door effect with the new iPad</title>
      <link>https://lorendb.dev/posts/fixing-the-screen-door-effect/</link>
      <pubDate>Sat, 15 Jun 2024 03:45:00 -0400</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/fixing-the-screen-door-effect/</guid>
      <description>&lt;p&gt;While I do not own a VR headset, as a technophile, I am very intrigued by the technology and like to follow the developments that happen in the VR space. As a result, I&amp;rsquo;ve become aware of the screen door effect: when you look closely at content rendered on a VR headset, you can see the gaps between pixels. Given that pixels are laid out in a grid, this ends up giving the feeling of looking through a screen door. Newer headsets have mitigated this problem somewhat by using higher resolution displays, but the problem is still there. You can see an example of this on the &lt;a href=&#34;https://www.bigscreenvr.com/&#34;&gt;Bigscreen Beyond&lt;/a&gt;, &lt;a href=&#34;https://www.apple.com/apple-vision-pro/&#34;&gt;Apple Vision Pro&lt;/a&gt;, and &lt;a href=&#34;https://www.meta.com/quest/quest-3/&#34;&gt;Meta Quest 3&lt;/a&gt; in this image from &lt;a href=&#34;https://kguttag.com/2024/06/14/hypervision-micro-oled-vs-lcd-and-why-the-apple-vision-pro-is-blurry/&#34;&gt;Karl Guttag&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://i0.wp.com/kguttag.com/wp-content/uploads/2024/06/BSB-vs-AVP-vs-MQ3-Close-Up-crop-copy-2.jpg&#34;&#xA;    alt=&#34;A comparison of three VR headsets&amp;#39; displays&amp;#39; sharpness&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;The effect is especially noticeable here on the Quest 3.&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;The Vision Pro has been hailed by some as eliminating the screen door effect, but Karl Guttag &lt;a href=&#34;https://kguttag.com/2024/03/01/apple-vision-pros-optics-blurrier-lower-contrast-than-meta-quest-3/&#34;&gt;debunked that theory&lt;/a&gt;: the Vision Pro has the same screen door effect as any other headset. It&amp;rsquo;s not noticeable because Apple intentionally left the optics slightly out of focus; this makes the effect difficult to see at the cost of some sharpness.&lt;/p&gt;&#xA;&lt;p&gt;You may wonder, then, if we&amp;rsquo;ll ever eliminate this effect. After all, if the incredibly pixel-dense Vision Pro displays can&amp;rsquo;t fix it, will we ever build dense enough displays to fix it? To answer this question, let&amp;rsquo;s take a look at the new iPad Pro.&lt;/p&gt;&#xA;&lt;h2 id=&#34;tandem-oled&#34;&gt;Tandem OLED&lt;/h2&gt;&#xA;&lt;p&gt;One of the features of the latest iPad Pro is the new Tandem OLED display. Apple has leveraged the transparency of OLED panels by stacking two of them in one display. While this is expensive technology, it does have the benefit of greatly boosting the potential screen brightness: when the two OLED panels work together, you can double your potential brightness. However, I&amp;rsquo;m not interested in making VR headsets excessively bright (although that could possibly be useful for some VR games). Instead, I think tandem OLED displays can fix the screen door effect by filling in the gaps between pixels.&lt;/p&gt;&#xA;&lt;p&gt;For reference, here is Apple&amp;rsquo;s magnification of the Vision Pro display (interestingly, I couldn&amp;rsquo;t find a similar image for any other OLED VR headset):&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/vr-tandem-oled/avp-oled.webp&#34;&#xA;    alt=&#34;A dense pixel layout&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;Not bad, considering that those pixels are just 7.5 micrometers across.&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;As far as I can tell, the iPad display has simply stacked two panels directly on top of each other. This provides no benefits to VR. Instead, I propose offsetting the top panel a bit to fill the empty space of the lower panel with the pixels of the other. For starters, here&amp;rsquo;s what it would look like if you simply offset an identical panel vertically and horizontally by half a pixel&amp;rsquo;s width:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/vr-tandem-oled/avp-tandem-oled-basic-offset.webp&#34;&#xA;    alt=&#34;A pixel layout with an identical layout offset above it&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;There&amp;#39;s far less dead space already.&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;But we can go further with this. What if you created a custom pixel layout for the second layer? I&amp;rsquo;m no expert on OLED screens, so I can&amp;rsquo;t say whether this is actually a practical layout, but let&amp;rsquo;s suppose that we can create a display like this:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/vr-tandem-oled/avp-tandem-oled-space-filling-offset-second-layer.webp&#34;&#xA;    alt=&#34;A pixel layout that uses a row of alternating red and green subpixels, interspersed with rows of columnar blue subpixels&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;This might work, although I&amp;#39;m a little concerned that the blue subpixels would complicate the wiring traces.&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;When layered on top of the Vision Pro&amp;rsquo;s pixel layout, we get a display with nearly zero black areas:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/vr-tandem-oled/avp-tandem-oled-space-filling-offset.webp&#34;&#xA;    alt=&#34;An extremely dense pixel layout&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;This is looking better and better...&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;And this is all assuming we&amp;rsquo;re sticking with the Vision Pro&amp;rsquo;s pixel layout. Other headsets will have their own subpixel layouts that may be more or less conducive to creating a secondary offset layout.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;If you want to play around with your own pixel layouts, you can download the XCF file that I used to create these images &lt;a href=&#34;https://lorendb.dev/files/avp-tandem-oled.xcf&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;Will we see this technology adopted any time soon? I hestitate to say that we will; after all, the tandem OLED configuration is expensive enough that it could easily become cost-prohibitive for many people. However, I think it&amp;rsquo;s only a matter of time until somebody either comes up with this idea independently or steals it from this post, and the higher quality display might be worth the money if it&amp;rsquo;s installed on a premium edition headset.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>vr</category>
    </item>
    <item>
      <title>Maybe it is the year of the Linux desktop</title>
      <link>https://lorendb.dev/posts/maybe-it-is-year-of-linux-desktop/</link>
      <pubDate>Thu, 23 May 2024 19:30:00 -0400</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/maybe-it-is-year-of-linux-desktop/</guid>
      <description>&lt;p&gt;A while back I &lt;a href=&#34;https://lorendb.dev/posts/the-year-of-linux-on-the-desktop&#34;&gt;wrote&lt;/a&gt; about the challenges that are preventing mass Linux adoption. I&amp;rsquo;ve been mulling on this and related topics for a while, and yet I haven&amp;rsquo;t really progressed far beyond my closing thoughts from then:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;However, I think there&amp;rsquo;s another way we could push Linux faster. If some enterprising person launches a Linux computer startup that focuses heavily on marketing, strikes deals with retailers to promote their computers, and ships a polished distro that will make the transition from Windows as easy as possible, I think there is a real possibility that Linux on the desktop could happen sooner than we expect. Unfortunately, I don&amp;rsquo;t know of any tech billionares who are looking for a cool side project, but if that describes you, you&amp;rsquo;re welcome to talk to me about it!&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;Well, it just so happens that a tech billionare is publicly contemplating the potential of Linux:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Maybe it is time to move consumer desktops to Linux&lt;/p&gt;&amp;mdash; Elon Musk (@elonmusk) &lt;a href=&#34;https://twitter.com/elonmusk/status/1792693133651918989?ref_src=twsrc%5Etfw&#34;&gt;May 20, 2024&lt;/a&gt;&lt;/blockquote&gt;&#xA;&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s right, the man that some adore as &amp;ldquo;Technoking&amp;rdquo; and others deride as &amp;ldquo;Space Karen&amp;rdquo; now is talking about switching the general public over to Linux on the desktop. Granted, he didn&amp;rsquo;t actually say &amp;ldquo;We&amp;rsquo;re building a Linux PC&amp;rdquo;, but when Elon Musk publicly talks about crazy ideas, sometimes they actually happen (&lt;a href=&#34;https://en.wikipedia.org/wiki/Acquisition_of_Twitter_by_Elon_Musk&#34;&gt;example&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-little-background&#34;&gt;A little background&lt;/h2&gt;&#xA;&lt;p&gt;Before you dismiss this tweet as &amp;ldquo;another meme from Elon&amp;rdquo;, let&amp;rsquo;s take a look at the historical context. In February, Elon bought a new Windows laptop and was frustrated when he discovered that Microsoft requires you to sign in with a Microsoft account:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Just bought a new PC laptop and it won’t let me use it unless I create a Microsoft account, which also means giving their AI access to my computer! This is messed up.&lt;br&gt;&lt;br&gt;There used to be an option to skip signing into or creating a Microsoft account. &lt;br&gt;&lt;br&gt;Are you seeing this too?&lt;/p&gt;&amp;mdash; Elon Musk (@elonmusk) &lt;a href=&#34;https://twitter.com/elonmusk/status/1761835728000954654?ref_src=twsrc%5Etfw&#34;&gt;February 25, 2024&lt;/a&gt;&lt;/blockquote&gt;&#xA;&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;p&gt;He went so far as to &lt;a href=&#34;https://twitter.com/elonmusk/status/1762173998056804747&#34;&gt;complain at Satya Nadella&lt;/a&gt; in a (probably futile) attempt to get Microsoft to ditch the sign-in requirement.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;By the way, if you want to bypass Microsoft account sign-in on Windows, you can easily bypass it during setup. When asked to sign in, type &amp;ldquo;&lt;a href=&#34;mailto:no@thankyou.com&#34;&gt;no@thankyou.com&lt;/a&gt;&amp;rdquo; for the email address and anything you want in the password field. Since this email address has been blacklisted, the sign-in will fail and you will be dropped back to a local account creation screen.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;Elon&amp;rsquo;s recent tweet suggesting that Linux should take over was prompted by Microsoft&amp;rsquo;s announcement of &lt;a href=&#34;https://arstechnica.com/gadgets/2024/05/microsofts-new-recall-feature-will-record-everything-you-do-on-your-pc/&#34;&gt;Recall&lt;/a&gt;, an AI assistant that will log everything you did on your PC for the last three months and then feed that into Copilot to try to help you find more things. Besides the &lt;a href=&#34;https://doublepulsar.com/how-the-new-microsoft-recall-feature-fundamentally-undermines-windows-security-aa072829f218&#34;&gt;massive security implications&lt;/a&gt; of Recall, the entire concept seems dystopian, making the &lt;a href=&#34;https://www.theregister.com/2024/05/10/apple_ipad_ad_bombs/&#34;&gt;Apple &amp;ldquo;Crush&amp;rdquo; ad&lt;/a&gt; seem downright appealing by comparison. You may not have any features of humanity left, but at least Apple (theoretically) isn&amp;rsquo;t feeding everything you do into the hungry maw of data-collecting servers (although that could start changing if the rumors of a new OpenAI-powered Siri are true).&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-part-where-i-make-wild-speculations&#34;&gt;The part where I make wild speculations&lt;/h2&gt;&#xA;&lt;p&gt;At this point, there are two possible outcomes: either Elon makes sure to keep all of Windows&amp;rsquo; antifeatures turned off and just lives with it, or he starts a computer company out of frustration. The second scenario is the interesting one here.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to assume such a computer company would focus on laptops initially, but pretty much all of the below commentary applies equally to desktop PCs.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;If Elon decides to start a computer company, one thing is certain: it will be divisive solely because it is owned by Elon Musk. On the one hand, ardent fans will all but smash their existing computers as they rush to daily-drive Elon&amp;rsquo;s laptop instead. On the other hand, some will go out of their way to avoid Elon&amp;rsquo;s laptops, possibly even switching their operating system:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/elon-linux/convince-ditch-linux.png&#34;&#xA;    alt=&#34;The one thing that might convince me to ditch Linux .. Elon convincing people to use it...&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;This is a coworker&amp;#39;s reaction to Elon&amp;#39;s Linux tweet.&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Of course, I&amp;rsquo;m not planning to ditch Linux if Elon starts a Linux computer company, and I certainly hope nobody else does either.&lt;/p&gt;&#xA;&lt;p&gt;Beyond the initial online arguments that are grounded only in personal opinions of Elon, what would a potential product look like and what would it do for Linux? Most likely, any laptop produced by Elon would be done as a subsidiary of X, which means it would likely sprout a (paywalled) xAI integration. This could be good or bad, depending on how it&amp;rsquo;s executed. Done well, it ends up being a useful AI assistant that doesn&amp;rsquo;t push itself in your face. Done poorly, you end up with a final product that is no better than Windows.&lt;/p&gt;&#xA;&lt;p&gt;Would the laptop sell well? I hesitate to make a call on that. While the laptop would face an uphill battle to gain widespread acceptance while being an Elon Musk product, it would have the advantage of a high-profile launch, with accompanying media coverage. Imagine all the news outlets that covered the Apple Vision Pro now covering a Linux laptop, and you&amp;rsquo;ll get an idea of how many potential users could be reached. As long as the laptop has a solid enough user experience to get positive reviews, that translates into possibly more media coverage Linux laptops have ever received before, helping sell the laptop to people who aren&amp;rsquo;t using their opinion of Elon to decide whether or not to buy.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://gs.statcounter.com/os-market-share/desktop/united-states-of-america&#34;&gt;According to Statcounter&lt;/a&gt;, Linux desktop share sits at 3.74%, with ChromeOS at 6.18% (thanks to its widespread adoption in public schools) and macOS at 22.59%. For Linux to reach a point where software makers begin to treat it as an equal player, it needs to at least get close to equal macOS&amp;rsquo;s market share. My not-at-all-scientific mental estimations put the required number of conversions well into the millions, making this no easy task.&lt;/p&gt;&#xA;&lt;p&gt;Fortunately, it&amp;rsquo;s not a task that Elon would have to achieve himself. With the all the publicity surrounding this laptop, existing companies like &lt;a href=&#34;https://system76.com&#34;&gt;System76&lt;/a&gt; that already have a solid product would have a golden opportunity to push themselves into the public eye, and if public demand for any Linux-powered laptop rose enough, the big OEMs could potentially start joining the trend. After all, it&amp;rsquo;s not like most people unconditionally love Windows or anything, and between an all-seeing AI Big Brother, an ad-riddled interface, and a history of being just unstable enough to bluescreen at the worst possible times, the Windows of the future just might be a Windows that nobody uses.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-are-the-chances-that-this-happens&#34;&gt;What are the chances that this happens?&lt;/h2&gt;&#xA;&lt;p&gt;Frankly, I&amp;rsquo;m not holding my breath. While Elon probably has the money to start a computer company several times over without batting an eye, it&amp;rsquo;s not clear whether he actually wishes to do so, as his attention is already spread quite thinly between all his other endeavors. With that being said, Elon&amp;rsquo;s distrust of others to do a good job might push him to build a computer that he feels he can trust with his personal information.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;What do I mean by &amp;ldquo;Elon&amp;rsquo;s distrust of others to do a good job&amp;rdquo;? As far as I can tell, he views himself as the only person he can trust to hold his companies to idealistic standards. It&amp;rsquo;s why he bought Twitter; he ultimately didn&amp;rsquo;t trust the existing Twitter leadership to provide a politically fair platform, so he put himself in charge instead. It also could partly explain why he still retains a significant amount of control and involvement at SpaceX; his goal for that company is to enable Martian colonies, and if somebody else was in charge of SpaceX, they might veer from that goalpost and focus on the existing launch market.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;Regardless of whether Elon starts a computer company, his tweet has likely already had a positive impact. As of 5/23/2024, Twitter reports 43,000 views on that tweet; many of those views are probably from non-Linux users. This exposure alone has probably convinced at least a few people to try Linux, and at the end of the day, that&amp;rsquo;s a good thing.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>linux</category><category>opensource</category><category>rant</category>
    </item>
    <item>
      <title>Does Skylon have a place in the world of Starship?</title>
      <link>https://lorendb.dev/posts/skylon-in-the-world-of-starship/</link>
      <pubDate>Fri, 12 Apr 2024 22:09:06 -0400</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/skylon-in-the-world-of-starship/</guid>
      <description>&lt;p&gt;As a space nerd, I love learning about the craziest rocket concepts that have come up over the years (and there certainly &lt;a href=&#34;https://en.wikipedia.org/wiki/Rotary_Rocket&#34;&gt;have&lt;/a&gt; &lt;a href=&#34;https://newatlas.com/space/hydrofoil-space-shuttle-launch/&#34;&gt;been&lt;/a&gt; &lt;a href=&#34;https://en.wikipedia.org/wiki/OTRAG_(rocket)&#34;&gt;quite&lt;/a&gt; &lt;a href=&#34;https://spacecraft.fandom.com/wiki/Star-Raker&#34;&gt;a&lt;/a&gt; &lt;a href=&#34;https://en.wikipedia.org/wiki/Project_Orion_(nuclear_propulsion)&#34;&gt;lot&lt;/a&gt;). One category of concept intrigues me in particular: &lt;a href=&#34;https://en.wikipedia.org/wiki/Single-stage-to-orbit&#34;&gt;single-stage-to-orbit&lt;/a&gt;, or SSTO. This type of spacecraft can reach orbit without the need for detachable boosters or other staged hardware. This is quite easy to achieve on a body like the Moon, given its low gravity and lack of atmosphere. SSTOs are also feasible on Mars, as Mars has lower gravity and a very thin atmosphere. However, Earth&amp;rsquo;s higher gravity and thicker atmosphere make SSTOs fairly tricky to pull off; nevertheless, it&amp;rsquo;s doable, and so there have been numerous designs proposed.&lt;/p&gt;&#xA;&lt;p&gt;Many SSTO designs have a traditional rocket form factor, with the exception that the fuel tank is conical rather than cylindrical (the &lt;a href=&#34;https://en.wikipedia.org/wiki/McDonnell_Douglas_DC-X&#34;&gt;DC-X&lt;/a&gt; is a good example of this). However, the spaceplane form factor is also common and arguably sports some benefits: at takeoff, you can use the atmosphere to your advantage as it generates lift, and you can just land on a runway instead of having to fool around with parachutes or retropropulsive landings (a la Falcon 9). In fact, as far as I can tell, the spaceplane-style SSTO is the more popular SSTO style among Kerbal Space Program players; &lt;a href=&#34;https://www.reddit.com/r/KerbalSpaceProgram/search/?q=ssto&amp;amp;type=media&#34;&gt;browsing the KSP subreddit&lt;/a&gt; shows both spaceplanes and rockets, but spaceplanes are far more common.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Of course, what goes in KSP doesn&amp;rsquo;t necessarily go in real life. While Kerbin&amp;rsquo;s gravity and atmospheric pressure are similar to Earth&amp;rsquo;s, it is only about a tenth of the size of Earth, making it far easier to get to orbit. This makes it very easy to take a large fuel tank, slap a Vector on the bottom, and get 4 km/s of delta-V, which is enough to get to orbit and back with a single stage.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;skylon&#34;&gt;Skylon&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Skylon_(spacecraft)&#34;&gt;Skylon&lt;/a&gt; stands out among spaceplane SSTO designs for various reasons. For starters, its SABRE engines can run in an airbreathing mode, which reduces the amount of liquid oxygen that Skylon has to carry (which in turn allows it to carry more hydrogen fuel), but also function just fine when burning &lt;a href=&#34;https://en.wiktionary.org/wiki/hydrolox&#34;&gt;hydrolox&lt;/a&gt;. Furthermore, it&amp;rsquo;s the only SSTO spaceplane that is in active development; while not much news is forthcoming from its developer &lt;a href=&#34;https://reactionengines.co.uk/&#34;&gt;Reaction Engines&lt;/a&gt;, it appears that they are still working on SABRE and Skylon.&lt;/p&gt;&#xA;&lt;p&gt;However, Skylon has been in development since the 1980s. It has its roots in the &lt;a href=&#34;https://en.wikipedia.org/wiki/HOTOL&#34;&gt;HOTOL&lt;/a&gt; spaceplane; while the HOTOL project was ultimately cancelled by the British government, the people behind it didn&amp;rsquo;t give up the idea of a spaceplane with a dual-mode engine, eventually starting Reaction Engines to develop Skylon as a sort of second iteration on the concept. Since then, we&amp;rsquo;ve seen the end of the space shuttle and the rise of Falcon 9, with Starship looming on the horizon. We&amp;rsquo;re no longer in an era where rocket launches are prohibitively expensive; in fact, if you just want to send a CubeSat into orbit, you can do so quite cheaply. SpaceX currently estimates a launch cost of $300,000 for a 1U CubeSat weighing up to fifty kilograms on one of their rideshare missons. In other words, if you have three hundred grand burning a hole in your pocket, you can get sweet bragging rights by launching your own satellite! And it will only get cheaper: when SpaceX starts offering rideshare launches on Starship, costs will likely drop significantly due to Starship&amp;rsquo;s improved efficiency (not to mention its reusable second stage, which represents an enormous savings on its own).&lt;/p&gt;&#xA;&lt;p&gt;At this point, then, should we be throwing in the towel on Skylon? Does it even have a place in today&amp;rsquo;s landscape, or does Starship completely obsolete Skylon?&lt;/p&gt;&#xA;&lt;h2 id=&#34;by-the-numbers&#34;&gt;By the numbers&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to use mass-to-orbit as a deciding factor, because Starship would beat every other rocket out there, and besides, just because we need super-heavy-lift rockets for some payloads doesn&amp;rsquo;t mean we should use them for every single satellite that is ever launched. Instead, I&amp;rsquo;ll try to make a more fair comparison based on the &lt;a href=&#34;https://en.wikipedia.org/wiki/Payload_fraction&#34;&gt;payload fraction&lt;/a&gt; and launch costs of each vehicle. Please keep in mind that while I&amp;rsquo;ve tried to get accurate numbers here, you should treat everything said here as a rough calculation, as Skylon is still operation in the theoretical realm and the numbers given for Starship are likely not extremely precise. Additionally, SpaceX is continually iterating Starship for more performance, so we could easily see the numbers change in another year or so. I&amp;rsquo;ve also converted every number to freedom units, so there could be slight precision losses from that as well.&lt;/p&gt;&#xA;&lt;p&gt;According to &lt;a href=&#34;https://web.archive.org/web/20160418035934/http://www.reactionengines.co.uk/tech_docs/SKYLON_User_Manual_rev1-1.pdf&#34;&gt;its manual&lt;/a&gt;, Skylon has a payload capacity of 16.5 tons, while SpaceX &lt;a href=&#34;https://www.spacex.com/vehicles/starship/&#34;&gt;says&lt;/a&gt; that Starship can launch up to 150 tons. Given Skylon&amp;rsquo;s 345 ton mass and Starship&amp;rsquo;s 5000 ton mass (&lt;a href=&#34;https://en.wikipedia.org/wiki/SpaceX_Starship#Description&#34;&gt;Wikipedia&lt;/a&gt;), we can calculate a payload fraction of 4.8% for Skylon and 3% for Starship. While this seems to point to Skylon, the cost per launch tells a different story: Skylon&amp;rsquo;s estimated cost per launch is &lt;a href=&#34;https://en.wikipedia.org/wiki/Skylon_(spacecraft)#Project_brief&#34;&gt;$10 millon&lt;/a&gt;. While we don&amp;rsquo;t know the exact numbers for Starship, Elon Musk has claimed that costs could fall as low as a few million dollars per launch. Even if Starship cost the same to launch as Skylon, it still has a clear win in cost per pound to orbit: in this scenario, Skylon would cost $302.39 per pound to orbit, while Starship would cost just $33.33 per pound to orbit.&lt;/p&gt;&#xA;&lt;p&gt;Since numbers don&amp;rsquo;t lie, you&amp;rsquo;d be forgiven if you immediately wrote off Skylon at this point. However, I think there&amp;rsquo;s one more thing we need to consider.&lt;/p&gt;&#xA;&lt;h2 id=&#34;apples-to-oranges&#34;&gt;Apples to oranges&lt;/h2&gt;&#xA;&lt;p&gt;Skylon and Starship are both orbital launch vehicles that aim to decrease the cost to launch while promoting reusability, but that&amp;rsquo;s about where the similarities end, as Skylon leans into the SSTO spaceplane design while Starship embraces traditional rocket designs alongside retropropulsive landing. Clearly, we can&amp;rsquo;t properly compare these vehicles without weighing these differences, and I think this is where Skylon gets an opportunity to shine.&lt;/p&gt;&#xA;&lt;p&gt;Traditional reentry capsules (think Apollo) tend to pull many Gs on reentry, which causes a certain level of discomfort to the crew. I couldn&amp;rsquo;t find numbers on the projected G-forces on either Starship or Skylon, but Skylon has a big advantage in this department: its wings. As a craft that is capable of gliding, Skylon is capable of taking a much shallower reentry path that reduces the overall stress on the vehicle. In an age where spacecraft are being proposed for use as long-range passenger transport vehicles and various companies are developing space stations for commercial or tourist use, Skylon could become the perfect crew transport vehicle to reduce stress on passengers, both at launch and reentry. Given that Reaction Engines &lt;a href=&#34;https://web.archive.org/web/20120615150211/http://www.reactionengines.co.uk/skylon_pax.html&#34;&gt;claimed&lt;/a&gt; an intended crew capacity of 30 or 40, Skylon could become the luxury space travel option, while a trip on Starship would be a bit more like flying economy: less expensive and less comfortable. And it&amp;rsquo;s not necessarily just people; Skylon could potientially enable us to launch equipment that is much more delicate to further scientific research.&lt;/p&gt;&#xA;&lt;h2 id=&#34;its-up-to-reaction-engines-now&#34;&gt;It&amp;rsquo;s up to Reaction Engines now&lt;/h2&gt;&#xA;&lt;p&gt;With all this in mind, I&amp;rsquo;m optimistic that there is still a place for Skylon in Starship&amp;rsquo;s world. At this point, we just need Reaction Engines to pull through and actually build the thing. Until then, we&amp;rsquo;ll just have to keep flying the traditional rockets.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>rockets</category><category>skylon</category><category>space</category><category>ssto</category><category>starship</category>
    </item>
    <item>
      <title>Introducing the Matrix Codebrowser</title>
      <link>https://lorendb.dev/posts/introducing-matrix-codebrowser/</link>
      <pubDate>Wed, 27 Mar 2024 18:54:14 -0400</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/introducing-matrix-codebrowser/</guid>
      <description>&lt;p&gt;Today marks the public introduction of the &lt;a href=&#34;https://matrix.codebrowser.lorendb.dev&#34;&gt;Matrix Codebrowser&lt;/a&gt;. It&amp;rsquo;s an instance of &lt;a href=&#34;https://github.com/KDAB/codebrowser&#34;&gt;codebrowser&lt;/a&gt; that is hosting the sources for &lt;a href=&#34;https://github.com/Nheko-Reborn/mtxclient&#34;&gt;mtxclient&lt;/a&gt;, &lt;a href=&#34;https://github.com/quotient-im/libQuotient&#34;&gt;libQuotient&lt;/a&gt;, and &lt;a href=&#34;https://gitlab.matrix.org/matrix-org/olm&#34;&gt;olm&lt;/a&gt; (with more projects to come in the future).&lt;/p&gt;&#xA;&lt;h2 id=&#34;testimonials&#34;&gt;Testimonials&lt;/h2&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;But it&amp;rsquo;s almost surreal to see my own code in a page that I usually see when I need to understand Qt&amp;rsquo;s inner workings.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://matrix.to/#/@kitsune:matrix.org&#34;&gt;kitsune&lt;/a&gt;, &lt;a href=&#34;https://matrix.to/#/%23thisweekinmatrix%3Amatrix.org/%24yzI2tGmJWaS4Cxp_IPDx4XUup2Uo-qBWVx2W5LRl9s0?via=matrix.org&amp;amp;via=envs.net&amp;amp;via=element.io&#34;&gt;March 27, 2024&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;what-is-it-good-for&#34;&gt;What is it good for?&lt;/h2&gt;&#xA;&lt;p&gt;If you&amp;rsquo;ve never used a codebrowser instance before, you probably are wondering &amp;ldquo;why would you make such a big deal about a public source mirror?&amp;rdquo; Well, codebrowser doesn&amp;rsquo;t just mirror a repository to a web interface. It also uses compilation information to detect and display all the symbols in a source file. For example, try mousing over some symbols in the &lt;a href=&#34;https://codebrowser.dev/qt6/qtbase/src/corelib/tools/qscopedvaluerollback.h.html&#34;&gt;QScopedValueRollback&amp;lt;&amp;gt; source&lt;/a&gt; from the &lt;a href=&#34;https://codebrowser.dev&#34;&gt;official codebrowser instance&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This ability to easily navigate the content of a library&amp;rsquo;s codebase is the raison d&amp;rsquo;être of codebrowser. I occasionally have a need to see exactly what a certain Qt function is doing under the hood; while I could grep through the GitHub mirror, it&amp;rsquo;s far faster to look up the function in codebrowser, and I can also quickly dive into function calls from there.&lt;/p&gt;&#xA;&lt;h2 id=&#34;more-to-come&#34;&gt;More to come&lt;/h2&gt;&#xA;&lt;p&gt;The current public version of the code only supports C++ codebases; however, the official instance has seen massive work by &lt;a href=&#34;https://kdab.com&#34;&gt;KDAB&lt;/a&gt; to add support for Rust and Dart code. Once KDAB&amp;rsquo;s work is made public, I plan to add more repositories like &lt;a href=&#34;https://github.com/matrix-org/matrix-rust-sdk&#34;&gt;matrix-rust-sdk&lt;/a&gt; and &lt;a href=&#34;https://github.com/matrix-org/vodozemac&#34;&gt;vodozemac&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;You can also submit requests! I&amp;rsquo;m currently mainly focused on Matrix-related libraries, since that is likely the most useful code I can support, but if you want to see your favorite client or server added, I&amp;rsquo;ll be happy to add it (as long as it&amp;rsquo;s within reason). Just leave a comment below or come say hi &lt;a href=&#34;https://matrix.to/#/%23matrix-codebrowser%3Anheko.im?via=nheko.im&#34;&gt;on Matrix&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>code</category><category>matrix</category><category>selfhosting</category>
    </item>
    <item>
      <title>The year of Linux on the desktop, and why we can&#39;t have nice things</title>
      <link>https://lorendb.dev/posts/the-year-of-linux-on-the-desktop/</link>
      <pubDate>Tue, 19 Mar 2024 15:32:00 -0400</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/the-year-of-linux-on-the-desktop/</guid>
      <description>&lt;p&gt;Ever since the dawn of time (er, Linux), enthusiastic users have predicted the advent of the year of Linux on the desktop. Hardly a year has gone by without somebody proclaiming it to be such a year. And yet Linux only &lt;a href=&#34;https://arstechnica.com/gadgets/2024/03/linux-continues-growing-market-share-reaches-4-of-desktops/&#34;&gt;recently crossed 4%&lt;/a&gt; of desktop market share worldwide. What gives?&lt;/p&gt;&#xA;&lt;p&gt;On the surface, it&amp;rsquo;s easy to point to different reasons. Maybe it&amp;rsquo;s the &lt;a href=&#34;https://en.wikipedia.org/wiki/List_of_Linux_distributions&#34;&gt;overwhelming sea of distros&lt;/a&gt;, or the lack of official support for Adobe (or whatever proprietary Windows-only software you use), or the gaps in the user experience that leave you poking around in the terminal, wondering where it all went wrong. Maybe it&amp;rsquo;s even the lack of prebuilt Linux PCs from major manufacturers. These are all valid arguments, but I think there&amp;rsquo;s a deeper problem here.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Heads up - this post is a bit of a rant. Proceed at your own risk; I provide absolutely no guarantee that you will agree with the stated sentiments.&lt;/p&gt;&#xA;&lt;p&gt;Also, pedants likely will notice that I&amp;rsquo;m not treating ChromeOS as Linux. This is intentional; the Linux experience that everybody wants to see become popular doesn&amp;rsquo;t include Google accounts and spyware throughout the OS. Instead, I&amp;rsquo;m using the term Linux to refer to the more traditional distros.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;opensource-is-not-capitalism&#34;&gt;Opensource is not capitalism&lt;/h2&gt;&#xA;&lt;p&gt;It&amp;rsquo;s no secret that opensource software is a very socialist way to do things. For example, companies whose product is opensource tend to make their money by selling hosted versions of their software or providing paid support for their products. Unfortunately for Linux, this directly conflicts with the capitalist economy that most of the world runs on. Why is this unfortunate for Linux? Because it drives down contributions.&lt;/p&gt;&#xA;&lt;p&gt;Now, I can hear you screaming at me already: &amp;ldquo;But everybody hires kernel developers nowadays! Even Microsoft funds kernel developers!&amp;rdquo; And you&amp;rsquo;re right! However, you need to remember a few things. First, developers from, say, Microsoft are only concerned about kernel development that directly impacts Microsoft. This practically means that Microsoft&amp;rsquo;s kernel involvement is limited to making &lt;a href=&#34;https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux&#34;&gt;WSL&lt;/a&gt; possible and supporting their custom Linux distro that they use for Azure. Beyond that, they have little incentive to get involved. This means that while the kernel does get plenty of attention, there&amp;rsquo;s also potential for resources to be focused in areas that don&amp;rsquo;t directly benefit many users.&lt;/p&gt;&#xA;&lt;p&gt;Second, and perhaps more importantly, Linux is not just the kernel. While I don&amp;rsquo;t want to fan the flames of the &lt;a href=&#34;https://stallman-copypasta.github.io&#34;&gt;GNU/Linux&lt;/a&gt; warriors, their arguments do point to the underlying issue here: while the kernel provides a rock-solid base for your operating system, there are dozens of other components that make up a Linux OS (init system, desktop environment, network connection managers, etc.). Every one of those components must itself receive maintenance in order for your system to run smoothly.&lt;/p&gt;&#xA;&lt;p&gt;The question, then, is &amp;ldquo;who maintains all of this software?&amp;rdquo; Sure, there are plenty of volunteers out there who are looking to fix bugs on their systems or just have a fun time writing code, but you can&amp;rsquo;t count on a volunteer to provide forty hours of programming a week. To build a truly polished and reliable product, paid development is nearly essential. You may disagree with me, but I&amp;rsquo;d like to point out that much of the polished Linux software we love has had developers behind it paid to work on it: besides the kernel itself, there&amp;rsquo;s systemd, many KDE components, much of Pop_OS!, and many other projects, not to mention applications like Firefox, Element, and Visual Studio Code, all of which are direct products of companies instead of being volunteer projects.&lt;/p&gt;&#xA;&lt;p&gt;At this point, you&amp;rsquo;re probably wondering where I&amp;rsquo;m going with this. Well, if you&amp;rsquo;ll &lt;a href=&#34;https://xkcd.com/365/&#34;&gt;bear with me&lt;/a&gt;, I promise it will all make sense in a bit.&lt;/p&gt;&#xA;&lt;h2 id=&#34;this-is-why-we-cant-have-nice-things&#34;&gt;This is why we can&amp;rsquo;t have nice things&lt;/h2&gt;&#xA;&lt;p&gt;If so many companies are already investing in Linux and the wider opensource ecosystem, why hasn&amp;rsquo;t the world switched to Linux? To understand that, let&amp;rsquo;s go back to what I said earlier about Linux kernel development:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Developers from, say, Microsoft are only concerned about kernel development that directly impacts Microsoft.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;And therein lies the rub. While we see many companies embracing opensource, nearly all of them end up being like, say, Signal: they provide value to the ecosystem and create an incredible product, but at the end of the day, they are only investing into the apps part of the Linux ecosystem. I&amp;rsquo;m certainly not trying to downplay what companies like Signal are doing, but at the end of the day, the core system components are left without support.&lt;/p&gt;&#xA;&lt;p&gt;This is not to say that core software is without support; again, volunteer effort goes a long way to keep the lights on, and companies like &lt;a href=&#34;https://blue-systems.com&#34;&gt;Blue Systems&lt;/a&gt; (who work on KDE) and &lt;a href=&#34;https://system76.com&#34;&gt;System76&lt;/a&gt; (who have their own distro and have contributed massively to the entire opensource stack, including Coreboot) are doing amazing things to supplement volunteer work with commercially-funded development. However, the number of companies paying developers to work on projects like KDE is vastly lower than the number of companies paying developers to work on the kernel.&lt;/p&gt;&#xA;&lt;p&gt;In my opinion, this lack of commercial support is what leads to the rough edges in the Linux experience. If you want to see what I mean, the best way is to try a volunteer-built distro like Debian or Manjaro against a commercially-built distro like Zorin OS or Pop_OS!. The distros backed by for-profit companies have had much more work put into making the best experience possible, whereas the volunteer distros largely depend on upstream development to identify and fix rough edges.&lt;/p&gt;&#xA;&lt;p&gt;However, with most opensource companies focused on building only one or two apps rather than funding an ecosystem, we can&amp;rsquo;t fix the problem overnight.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-bright-light-is-valve&#34;&gt;The bright light is&amp;hellip; Valve?&lt;/h2&gt;&#xA;&lt;p&gt;Interestingly enough, the trail to fixing this ecosystem problem may have been blazed by a gaming company. That&amp;rsquo;s right, Valve is back to save the day again, but to understand why, we must first look away from Valve.&lt;/p&gt;&#xA;&lt;p&gt;If commercially-backed development only happens when a company has an incentive to provide that development, we need to figure out what companies would directly benefit from a better Linux desktop experience. We don&amp;rsquo;t have to look far, though. As I mentioned above, System76 has poured lots of time and effort into developing the best Linux experience for their hardware, going so far as to create their own distro (and they&amp;rsquo;re building their own desktop environment, too!). But it doesn&amp;rsquo;t have to be just System76. It could be any company selling a Linux PC.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, there&amp;rsquo;s not a lot of those out there today. But that could change in the future! Specifically, Lenovo is a prime candidate to become a major provider of Linux systems. A few months ago, the user mindcrime &lt;a href=&#34;https://news.ycombinator.com/item?id=39137536&#34;&gt;revealed&lt;/a&gt; in a Hacker News thread that Lenovo prefers Linux:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Lenovo secretly want Linux to catch on big-time, because they HATE the amount of leverage Microsoft have over them due to the ubiquity of Windows on so many of their devices. But they have to be careful to not embrace Linux too openly for fear of Microsoft retaliation. So they&amp;rsquo;re in a bit of a catch-22 situation. [&amp;hellip;]&lt;/p&gt;&#xA;&lt;p&gt;- &lt;a href=&#34;https://news.ycombinator.com/user?id=mindcrime&#34;&gt;mindcrime&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;If mindcrime is right, Lenovo would be eager to produce Linux hardware the moment Linux seemed to be gaining significant market share (and indeed, &lt;a href=&#34;https://www.lenovo.com/us/en/d/linux-laptops-desktops/&#34;&gt;they already do make some Linux machines&lt;/a&gt;). However, Lenovo is going to need something more than a report of 4% Linux usage worldwide from Statcounter before they pull the trigger on Linux devices. And for that something, we need to look to Valve.&lt;/p&gt;&#xA;&lt;p&gt;Valve has a long history with Linux. For ten years, they&amp;rsquo;ve used it as the base of SteamOS, and their games have supported Linux for many years as well. However, Valve wasn&amp;rsquo;t content to stop there. They currently are supporting development of KDE to run on the Steam Deck. Futhermore, they&amp;rsquo;ve invested heavily into development of Proton to allow Windows-only games to run on Linux, and according to &lt;a href=&#34;https://www.protondb.com&#34;&gt;ProtonDB&lt;/a&gt;, fully 40% of games released on Steam are known to be partially or entirely playable on Linux.&lt;/p&gt;&#xA;&lt;p&gt;The thing is, Proton is just a soft fork of Wine, which is aimed at running any Windows software on Linux, and most (if not all) Proton code is ultimately upstreamed into Wine proper. In other words, Valve&amp;rsquo;s work on Proton is just another ecosystem investment. Like their work on KDE, their Wine contributions have the capability to unlock Linux for everyone; if and when Wine gets good enough to run nearly all Windows software, including major products like Adobe or Office, with few to no glitches, we&amp;rsquo;ll have one less blocker for potential new Linux users, and it&amp;rsquo;ll be due in no small part to Valve.&lt;/p&gt;&#xA;&lt;p&gt;How does this relate to Lenovo? In my opinion, there are two major blockers for Lenovo (or other OEMs) to adopt Linux: app support and ease of use. Valve is helping on the app support front by developing Proton and on the ease of use front by contributing to KDE. Without major OEM contributions, I foresee Valve being the primary driver of these efforts until OEMs start pushing Linux big-time. In other words, Valve may provide the impetus that drives Linux popularity up to the point that Lenovo feels ready to publicly move toward Linux.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s also worth pointing out that we shouldn&amp;rsquo;t expect OEMs to freeload on Linux. Granted, there will probably be smaller system integrators that will simply OEM-install Ubuntu with their own wallpaper and call it a day, but larger system integrators will almost certainly invest money into the ecosystem. How can I predict that so confidently? Because OEMs already spend enormous amounts of money on Windows.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s take a closer look at the numbers. According to &lt;a href=&#34;https://www.gartner.com/en/newsroom/press-releases/01-10-2024-gartner-says-worldwide-pc-shipments-increased-zero-point-three-percent-in-fourth-quarter-of-2023-but-declined-fourteen-point-eight-percent-for-the-year&#34;&gt;Gartner&lt;/a&gt; (table 3), in 2023, Lenovo alone shipped 59.7 million PCs. I think it&amp;rsquo;s safe to assume that most of them were running Windows, but to be conservative, let&amp;rsquo;s say just 40 million of those PCs shipped with an active copy of Windows (maybe the other 20 million were running ChromeOS). I wasn&amp;rsquo;t able to find what large OEMs pay per Windows license (drop a comment below if you know!), but let&amp;rsquo;s choose an absurdly low number for sake of argument. Assuming Lenovo pays just $5 per Windows OEM key, that works out to $200 million going to Microsoft per year. That&amp;rsquo;s enough to hire 1,000 developers and pay them $200,000 per year to work on the Linux ecosystem. If we adjust the OEM license key price to even $20 (which still seems like a very low estimate), the developer count jumps to 4,000. And $200,000 is a pretty good salary; I&amp;rsquo;m sure there are many developers who would be happy to work full-time on opensource for much less than that.&lt;/p&gt;&#xA;&lt;p&gt;Granted, Lenovo will probably take the opportunity to recoup some of the licensing costs as profit instead of reinvesting that money, and they also won&amp;rsquo;t entirely abandon Windows, but imagine the effect that even 100 full-time developers could have on your favorite desktop environment. And now remember that there are other manufacturers that will likely jump on board; looking at Gartner&amp;rsquo;s data, the top five Windows PC manufacturers shipped a combined 185.8 million PCs in 2023. Given a $20 OEM license cost and assuming just half of that money gets redirected to Linux development at $150,000 per developer, OEMs could hire over twelve thousand developers! A developer army of that size would undoubtedly make short work of bug trackers everywhere.&lt;/p&gt;&#xA;&lt;h2 id=&#34;so-which-year-will-linux-take-over&#34;&gt;So which year will Linux take over?&lt;/h2&gt;&#xA;&lt;p&gt;It&amp;rsquo;s tough to predict when Linux will truly take off. I do think that it has a strong chance to become dominant in the future once Microsoft &lt;a href=&#34;https://www.theverge.com/2023/6/27/23775117/microsoft-windows-11-cloud-consumer-strategy&#34;&gt;switches Windows to a cloud service&lt;/a&gt;, as many people won&amp;rsquo;t want to hand over their PC to Microsoft or pay a subscription to use Windows (although Google will probably attempt to push ChromeOS into the gap left by Microsoft). However, could it rise sooner than that?&lt;/p&gt;&#xA;&lt;p&gt;Microsoft&amp;rsquo;s cloud PC ambitions are likely many years away from being fully realized, so we must look to alternate ways that Linux could rise. As I mentioned above, Valve&amp;rsquo;s investments are pushing the Linux ecosystem into a place where it can serve most users&amp;rsquo; needs. For example, many gamers are learning that Linux actually works just fine on their Steam Decks and can even run their games on their PCs. This could definitely help push Linux upward; however, we&amp;rsquo;re still missing a major piece of the puzzle.&lt;/p&gt;&#xA;&lt;p&gt;If you walk into your local Best Buy, Walmart, or Costco and have a look at the various computers displayed, you&amp;rsquo;ll notice an overwhelming selection of Windows machines with a few Macs and Chromebooks thrown in. There are no machines with Linux installed. Ditto for web retailers. While you can find Linux machines on some websites, they are almost never shown as front-page products, and many retailers just don&amp;rsquo;t carry Linux machines at all. This means that most users don&amp;rsquo;t even get a chance to evaluate Linux as an option.&lt;/p&gt;&#xA;&lt;p&gt;For those who are interested in Linux, then, there are two options: learn how to create a virtual machine or figure out how to boot from a Linux USB and install it. To be fair, the Linux installation experience has had an incredible amount of work put into it, and it&amp;rsquo;s extremely polished and easy to use (in my opinion, better than the Windows experience), but it&amp;rsquo;s still beyond the range of many people&amp;rsquo;s technical ability.&lt;/p&gt;&#xA;&lt;p&gt;Linux will suffer from this deemphasizing effect until some company steps up to provide Linux computers that are promoted at common retailers. To be honest, Valve could save us again here: if they built and sold a line of gaming PCs preloaded with a desktop-oriented version of SteamOS, desktop Linux would likely get into the hands of hundreds of thousands of gamers. Valve wouldn&amp;rsquo;t even have to build the PCs; even if some OEM had Valve slap a Steam logo on a gaming PC with SteamOS installed, we&amp;rsquo;d likely see a massive uptick in Linux users.&lt;/p&gt;&#xA;&lt;p&gt;Of course, stores like Walmart tend to stock low- to mid-range PCs intended for casual use rather than dedicated gaming, so the Valve approach might not work well for getting Linux PCs into stores. Unfortunately, this is going to be a little harder to overcome, but I think there&amp;rsquo;s another problem we&amp;rsquo;ve been overlooking this whole time: today&amp;rsquo;s prebuilt Linux systems are almost all high-end workstations. For example, with the exception of the Meerkat mini PC, System76 doesn&amp;rsquo;t sell desktops or laptops for less than $1,000. With many consumers being constrained by a tight budget, then, System76 is out of the question. However, if we could get something like the &lt;a href=&#34;https://pine64.org/devices/pinebook_pro/&#34;&gt;Pinebook Pro&lt;/a&gt; (but perhaps built with an i3 or similar processor) into the hands of consumers, it would be very appealing. I think that a low-end Linux laptop could easily be built for $300 or less without majorly sacrificing build quality; after all, the Pinebook Pro has a magnesium alloy chassis and is still only a little over $200.&lt;/p&gt;&#xA;&lt;p&gt;Finally, I think it&amp;rsquo;s worth looking at what I&amp;rsquo;m going to call the Hail Mary approach. In this approach, a major OEM like Lenovo would invest millions of dollars into a fast-track Linux development campaign, with the goal of creating a distro with a frictionless experience in just a year&amp;rsquo;s time. From the development side, I think this would be possible; if they used an already solid distro for the base (say, Fedora or openSUSE) and picked Plasma for the UI, they are already at least 85% of the way to a beginner-friendly product. Their year of development could mainly focus on removing rough edges, implementing a few missing features, and generally making sure the experience was absolutely competitive with Windows.&lt;/p&gt;&#xA;&lt;p&gt;However, from a business perspective, the Hail Mary approach is extremely risky. Unless it was extremely well cloaked, Microsoft would quickly learn that Lenovo was about to ditch Windows. If Microsoft decided to immediately cut off business relations, Lenovo would be left unable to ship either Windows machines or polished Linux machines for quite a while. This kind of risk means that a Hail Mary will almost certainly not happen.&lt;/p&gt;&#xA;&lt;p&gt;With all things considered, then, I predict that while the year of the Linux desktop has a high chance of happening, it will almost certainly not happen in the next three to five years. For that to happen, we&amp;rsquo;d need Microsoft to go all-in on Windows 365 within a few years from now, as well as large efforts from at least one major player to develop the Linux ecosystem.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-few-ways-we-can-game-the-system&#34;&gt;A few ways we can game the system&lt;/h2&gt;&#xA;&lt;p&gt;First off, if you can, please consider contributing to your favorite opensource product to help fill the gap. For example, I have made a few contributions to KDE in the form of code; you may prefer to pick a different desktop environment, work on an application, or even contribute by helping with non-programming tasks (documentation, bug triage, etc.) or donating money.&lt;/p&gt;&#xA;&lt;p&gt;However, I think there&amp;rsquo;s another way we could push Linux faster. If some enterprising person launches a Linux computer startup that focuses heavily on marketing, strikes deals with retailers to promote their computers, and ships a polished distro that will make the transition from Windows as easy as possible, I think there is a real possibility that Linux on the desktop could happen sooner than we expect. Unfortunately, I don&amp;rsquo;t know of any tech billionares who are looking for a cool side project, but if that describes you, you&amp;rsquo;re welcome to talk to me about it!&lt;/p&gt;&#xA;&lt;h2 id=&#34;hang-in-there&#34;&gt;Hang in there!&lt;/h2&gt;&#xA;&lt;p&gt;Regardless of the difficulties that we face, Linux will almost certainly get its time in the limelight within the next ten years. It&amp;rsquo;s already showing steady growth, especially in India, where it commands 15% market share, and it ultimately beats its opponents in nearly every way. Unlike Windows, it&amp;rsquo;s lean and mean (and developer-friendly); unlike macOS, it&amp;rsquo;s completely under your control and allows freedom of hardware choice; unlike ChromeOS (and Windows and macOS), it&amp;rsquo;s privacy-respecting, so your daily computer usage won&amp;rsquo;t be sent off to Big Tech&amp;rsquo;s servers. Finally, despite its reliance on volunteer contributions, it provides a remarkably stable and polished experience already. With this combination of features, it&amp;rsquo;s just a matter of time before people start to switch, and once global Linux adoption reaches a certain critical mass, OEM adoption and contribution will do the rest.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>opensource</category><category>rant</category>
    </item>
    <item>
      <title>C&#43;&#43; Spotlight: FINAL CUT terminal GUI</title>
      <link>https://lorendb.dev/posts/cpp-spotlight-final-cut-gui/</link>
      <pubDate>Sat, 02 Mar 2024 15:20:00 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/cpp-spotlight-final-cut-gui/</guid>
      <description>&lt;p&gt;This is the first post in what will hopefully be a long series where I take a look at different C++ libraries and utilities that are uniquely awesome. Today, we&amp;rsquo;re looking at &lt;a href=&#34;https://github.com/gansm/finalcut&#34;&gt;FINAL CUT&lt;/a&gt;, a library for implementing a GUI in the terminal. It promises an API that is very similar to Qt&amp;rsquo;s API and plenty of functionality, and it doesn&amp;rsquo;t disappoint.&lt;/p&gt;&#xA;&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;&#xA;&lt;p&gt;I was happy to see that openSUSE packages FINAL CUT. A quick &lt;code&gt;sudo zypper in libfinal-devel&lt;/code&gt; later, I had the library installed on my system. Getting it into my C++ test project, however, was not quite as fun. It appears that FINAL CUT doesn&amp;rsquo;t provide a CMake interface out of the box, so I had to manually add the library to my CMake file:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cmake_minimum_required(&lt;span style=&#34;color:#e6db74&#34;&gt;VERSION&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;3.5&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;project(&lt;span style=&#34;color:#e6db74&#34;&gt;cpp-playground&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;LANGUAGES&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;CXX&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;set(&lt;span style=&#34;color:#e6db74&#34;&gt;CMAKE_CXX_STANDARD&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;20&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;set(&lt;span style=&#34;color:#e6db74&#34;&gt;CMAKE_CXX_STANDARD_REQUIRED&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;ON&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;find_library(&lt;span style=&#34;color:#e6db74&#34;&gt;final&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;final&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;add_executable(&lt;span style=&#34;color:#e6db74&#34;&gt;cpp-playground&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;main.cpp&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;target_link_libraries(&lt;span style=&#34;color:#e6db74&#34;&gt;cpp-playground&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;PRIVATE&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;final&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s not a huge dealbreaker or anything, but it&amp;rsquo;s always nice to be able to use &lt;code&gt;find_package&lt;/code&gt; instead of &lt;code&gt;find_library&lt;/code&gt;, given that &lt;code&gt;find_package&lt;/code&gt; does so much more.&lt;/p&gt;&#xA;&lt;p&gt;With CMake set up, let&amp;rsquo;s try the basic dialog example linked in the FINAL CUT wiki:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;final/final.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;argv[])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FApplication app(argc, argv);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FDialog dialog(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;app);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dialog.setText(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;A dialog&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FPoint position{&lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FSize size{&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dialog.setGeometry(position, size);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FWidget&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;setMainWidget(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;dialog);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dialog.show();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; app.exec();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running that code will show a window in your terminal; you can drag it around with your mouse and click on the button in the titlebar to open the menu!&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/final-cut/dialog-example.png&#34;&#xA;    alt=&#34;A basic dialog made with FINAL CUT&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;That background would make my eyes hurt if it stayed there too long...&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;h2 id=&#34;lets-build-something-useful&#34;&gt;Let&amp;rsquo;s build something useful!&lt;/h2&gt;&#xA;&lt;p&gt;Demos are great, but what if we could build something that has an actual use? Let&amp;rsquo;s build a proof-of-concept system monitor application that will show individual resources in separate windows. We&amp;rsquo;ll start with a main dialog that shows the app name and version:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MainDialog&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FDialog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    MainDialog(finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FApplication &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;app)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FDialog(app)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        setText(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;finaltop&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FPoint position{&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FSize size{&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_about &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FLabel(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;finaltop v0.0.1&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_about&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setGeometry({&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;}, {&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        setGeometry(position, size);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FLabel &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;m_about;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For reference, our &lt;code&gt;main()&lt;/code&gt; function looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;argv[])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FApplication app(argc, argv);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    MainDialog mainDialog(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;app);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mainDialog.show();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FWidget&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;setMainWidget(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;mainDialog);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; app.exec();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note the call to &lt;code&gt;finalcut::FWidget::setMainWidget()&lt;/code&gt;; this marks our MainDialog as the primary dialog for the application. Closing this dialog will shut down the entire app.&lt;/p&gt;&#xA;&lt;p&gt;Now let&amp;rsquo;s get down to business and implement individual resource dialogs, beginning with adding an uptime counter to the main dialog. Since Linux adheres to the &lt;a href=&#34;https://en.wikipedia.org/wiki/Everything_is_a_file&#34;&gt;&amp;ldquo;everything is a file&amp;rdquo;&lt;/a&gt; design, we can easily parse this from &lt;code&gt;/proc/uptime&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;string getUptime()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;ifstream proc_uptime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/proc/uptime&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; uptime;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    proc_uptime &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; uptime;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; hours, minutes, seconds;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hours &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static_cast&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(uptime &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    minutes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static_cast&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;((uptime &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; hours &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    seconds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static_cast&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(uptime &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; hours &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; minutes &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;to_string(hours) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;h &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;to_string(minutes) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;m &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;to_string(seconds) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;s&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We could dump this string directly to the dialog in the constructor, but that&amp;rsquo;s no fun. Let&amp;rsquo;s utilize timers to update a label every second. First, we need to create a label and register a timer for it in the constructor:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// m_uptime is a finalcut::FLabel * and m_uptimeTimer is an int&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;m_uptime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FLabel(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Uptime: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; getUptime(), &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;m_uptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setGeometry({&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;}, {&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;m_uptimeTimer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; addTimer(&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we&amp;rsquo;ll add a function to &lt;code&gt;MainDialog&lt;/code&gt; that will handle the timer:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onTimer&lt;/span&gt;(finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FTimerEvent &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;e) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (e&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;getTimerId() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; m_uptimeTimer)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_uptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setText(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Uptime: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; getUptime());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Note that FINAL CUT will not automatically redraw controls, likely&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// because rendering to the terminal is a pretty expensive operation.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        m_uptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;redraw();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that done, we now have a nifty dynamic uptime counter! Here&amp;rsquo;s what it looks like on my machine:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/final-cut/finaltop-main-dialog.png&#34;&#xA;    alt=&#34;A dialog displaying &amp;#39;finaltop v0.0.1&amp;#39; along with system uptime information&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Next, we&amp;rsquo;ll build a CPU meter. For sake of brevity and simplicity, I won&amp;rsquo;t implement a per-core monitor; instead, I&amp;rsquo;ll just use the first line of &lt;code&gt;/proc/stat&lt;/code&gt; to get overall CPU usage. With that being said, the logic to extend CPU monitoring to cover all cores would be simple; while the first line of &lt;code&gt;/proc/stat&lt;/code&gt; is the overall CPU usage, the next number of lines provide per-core monitoring, so we could reuse the parsing logic to extract the per-core usage values and create a dynamically-sized list of progress bars to monitor each of them.&lt;/p&gt;&#xA;&lt;p&gt;First, we&amp;rsquo;ll add a CPU widget class that displays a &lt;code&gt;finalcut::FProgressBar&lt;/code&gt;, as well as the CPU name from &lt;code&gt;/proc/cpuinfo&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CpuDialog&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FDialog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CpuDialog(finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FWidget &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;parent)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FDialog(parent)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        setText(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CPU&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        setGeometry({&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;}, {&lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_cpuMeter &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FProgressbar{&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_cpuMeter&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setGeometry({&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;}, {&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parseCpuUsage();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_cpuInfo &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FLabel{&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_cpuInfo&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setGeometry({&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;}, {&lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;ifstream cpuinfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/proc/cpuinfo&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;string line;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;getline(cpuinfo, line))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (line.starts_with(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;model name&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_cpuInfo&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setText(line.substr(line.find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;:&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        m_cpuUsageTimer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; addTimer(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onTimer&lt;/span&gt;(finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FTimerEvent &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;e) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (e&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;getTimerId() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; m_cpuUsageTimer)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parseCpuUsage();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            m_cpuMeter&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;redraw();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FProgressbar &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;m_cpuMeter;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    finalcut&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FLabel &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;m_cpuInfo;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; m_cpuUsageTimer;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; m_totalCpuLastTick &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; m_activeCpuLastTick &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we just have to plug in live data from &lt;code&gt;/proc/stat&lt;/code&gt;. Here&amp;rsquo;s the parser function I ended up building (this was not directly AI-written but it&amp;rsquo;s inspired by an AI-generated prototype function):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parseCpuUsage&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;ifstream proc_stat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/proc/stat&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;string cpuLine;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;getline(proc_stat, cpuLine);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;string cpu;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;stringstream cpuStream(cpuLine);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cpuStream &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; cpu &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; user &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; nice &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; system &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; idle &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; iowait &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; irq &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; softirq &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; steal &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; guest &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; guestnice;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; totalTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; user &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; nice &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; system &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; idle &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; iowait &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; irq &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; softirq &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; steal &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; guest &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; guestnice;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; activeTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; user &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; nice &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; system &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; irq &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; softirq &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; steal &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; guest &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; guestnice;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    m_cpuMeter&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;reset();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    m_cpuMeter&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setPercentage(((activeTime &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; m_activeCpuLastTick) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (totalTime &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; m_totalCpuLastTick)) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    m_totalCpuLastTick &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; totalTime;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    m_activeCpuLastTick &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; activeTime;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I won&amp;rsquo;t explain much about this function; you can read &lt;a href=&#34;https://www.man7.org/linux/man-pages/man5/proc.5.html&#34;&gt;&lt;code&gt;man 5 proc&lt;/code&gt;&lt;/a&gt; if you want to learn more about how &lt;code&gt;/proc/stat&lt;/code&gt; formats data. I will point out, however, that I am storing the time values in doubles. Using ints in this case would result in the displayed value clamping to either 0% or 100%. Also of note is the call to &lt;code&gt;FProgressBar::reset()&lt;/code&gt;; by default, calling &lt;code&gt;setPercentage()&lt;/code&gt; will add to the value of the progress bar rather than overwrite it.&lt;/p&gt;&#xA;&lt;p&gt;The final result looks pretty good! Here&amp;rsquo;s a screenshot with &lt;a href=&#34;https://browserbench.org/Speedometer2.1&#34;&gt;Speedometer&lt;/a&gt; running in the background to provide a load:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/final-cut/finaltop-cpu-dialog.png&#34;&#xA;    alt=&#34;A dialog with a progressbar monitoring CPU usage, along with the processor name&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Since memory information in Linux is exposed through &lt;code&gt;/proc/meminfo&lt;/code&gt; in a similar manner to &lt;code&gt;/proc/cpuinfo&lt;/code&gt;, implementing a memory use widget is not a lot more work than a bit of copy-and-paste. For brevity, I won&amp;rsquo;t show the implementation on this page, but you can find it &lt;a href=&#34;https://github.com/LorenDB/finaltop/blob/master/main.cpp#L111&#34;&gt;here&lt;/a&gt;. Here&amp;rsquo;s how my final implementation looks:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/final-cut/finaltop-ram-dialog.png&#34;&#xA;    alt=&#34;A dialog with a progressbar monitoring memory usage&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;If you have swap enabled, this dialog will also show a swap meter.&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;h2 id=&#34;final-thoughts-ha-ha&#34;&gt;Final thoughts (ha ha)&lt;/h2&gt;&#xA;&lt;p&gt;Apart from the annoying all-caps name, FINAL CUT is very fun and intuitive to work with. I&amp;rsquo;ve had a lot of fun working with it so far, and I might keep developing this system monitor out into a fully-fledged system monitor just as an excuse to keep working with FINAL CUT. If you want to play around with the code, I&amp;rsquo;ve uploaded it to GitHub as &lt;a href=&#34;https://github.com/LorenDB/finaltop&#34;&gt;finaltop&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Looking ahead, I have a small list of libraries that I&amp;rsquo;d like to try out in the future, but if you have any recommendations, please let me know!&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>c&#43;&#43;</category><category>c&#43;&#43; spotlight</category><category>finalcut</category><category>programming</category><category>terminal</category>
    </item>
    <item>
      <title>Ollama is now packaged for openSUSE</title>
      <link>https://lorendb.dev/posts/ollama-is-now-packaged-for-opensuse/</link>
      <pubDate>Tue, 27 Feb 2024 20:20:00 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/ollama-is-now-packaged-for-opensuse/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;TL;DR: I&amp;rsquo;ve packaged &lt;a href=&#34;https://ollama.com&#34;&gt;Ollama&lt;/a&gt; for openSUSE! As of February 27, 2024, it&amp;rsquo;s available for Tumbleweed (Leap will hopefully pick it up at some point). Just run &lt;code&gt;sudo zypper ref &amp;amp;&amp;amp; sudo zypper in ollama&lt;/code&gt; to get it! Please note that it will not use your GPU for now; if you need GPU support, keep installing Ollama from &lt;a href=&#34;https://ollama.com&#34;&gt;https://ollama.com&lt;/a&gt; instead. Now for the main post&amp;hellip;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;As somebody who has been a local LLM enthusiast ever since the advent of the Llama models &lt;a href=&#34;https://xkcd.com/285/&#34;&gt;[citation needed]&lt;/a&gt;, I have been using &lt;a href=&#34;https://ollama.com&#34;&gt;Ollama&lt;/a&gt; for some time now. However, Ollama has one small problem:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/ollama/ollama-website-install-script.png&#34;&#xA;    alt=&#34;Oh noes, it&amp;#39;s an install script!&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;Oh noes, it&amp;#39;s an install script!&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s right, it&amp;rsquo;s 2024 and we still haven&amp;rsquo;t learned that &lt;a href=&#34;https://www.flatpak.org&#34;&gt;Flatpaks&lt;/a&gt; and &lt;a href=&#34;https://appimage.org&#34;&gt;AppImages&lt;/a&gt; are the best way to distribute a Linux application that will work on any distro (yes, I am part of the &lt;a href=&#34;https://www.reddit.com/r/linux/comments/j3ajnf/whats_wrong_with_snaps_why_so_many_people_hate_it/&#34;&gt;Snap&lt;/a&gt; &lt;a href=&#34;https://www.reddit.com/r/linux/comments/wlg2g7/why_do_linux_users_tend_to_hate_snaps/&#34;&gt;hater&lt;/a&gt; &lt;a href=&#34;https://itsfoss.community/t/avoiding-snaps-in-ubuntu-22-04-and-derivatives/9363&#34;&gt;community&lt;/a&gt;). Instead, the Ollama authors have created a &lt;a href=&#34;https://github.com/ollama/ollama/blob/main/scripts/install.sh&#34;&gt;script&lt;/a&gt; that will download Ollama, create a systemd service, and even install CUDA drivers if necessary.&lt;/p&gt;&#xA;&lt;p&gt;Now, to be fair, Flatpaks and AppImages aren&amp;rsquo;t intended to be used for systemd services. Also, while Ollama&amp;rsquo;s website doesn&amp;rsquo;t inform you of the security risks of running a random script from the internet, it at least gives you a link to the script source, so you can read over it and make sure it&amp;rsquo;s not going to do anything nefarious.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;By the way, in case you don&amp;rsquo;t know, you can validate any install script even if the source isn&amp;rsquo;t linked. Instead of directly piping the script into &lt;code&gt;sh&lt;/code&gt;, instead pipe it into a temporary file. You can then read that file to make sure it looks safe before actually running the script.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;The obvious best practice for distributing Linux software, however, is to provide system packages. Some projects (like &lt;a href=&#34;https://docs.docker.com/desktop/install/linux-install/#supported-platforms&#34;&gt;Docker&lt;/a&gt;) provide their package repositories; however, in most cases, software should be distributed through the main repository. Since the Ollama devs are not doing this themselves (which is completely understandable; most developers don&amp;rsquo;t maintain their own packages), I decided to take it upon myself to refresh my knowledge of RPM and the &lt;a href=&#34;https://build.opensuse.org&#34;&gt;Open Build Service&lt;/a&gt; and package Ollama for openSUSE myself.&lt;/p&gt;&#xA;&lt;h2 id=&#34;it-cant-be-that-hard-right&#34;&gt;It can&amp;rsquo;t be that hard, right?&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve packaged a few things for openSUSE before, but it&amp;rsquo;s been a little while, so I decided to start from scratch. I cloned my home repository to my system and created a new package:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ osc checkout home:LorenDB&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;home:LorenDB&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ osc mkpac ollama&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This just creates a blank folder. You&amp;rsquo;d think it would at least create a .spec file (or &amp;ldquo;specfile&amp;rdquo;), but no, you have to do the whole thing yourself. So I used this &lt;a href=&#34;https://docs.fedoraproject.org/en-US/package-maintainers/Packaging_Tutorial_GNU_Hello/&#34;&gt;RPM packaging tutorial&lt;/a&gt; from Fedora (which openSUSE links to from their wiki) and copied the sample specfile over to my local repository, adapting the project name, license, and a few other things as needed. I also created a &lt;a href=&#34;https://openbuildservice.org/help/manuals/obs-user-guide/cha.obs.source_service.html&#34;&gt;_service file&lt;/a&gt; to automatically fetch the Ollama source from GitHub. Then I ran &lt;code&gt;osc build&lt;/code&gt; to build the package. It failed, which I was completely expecting. After all, Ollama is written in Go, so I needed to adapt the specfile to actually build Ollama.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;code&gt;osc&lt;/code&gt; is the command-line tool for interacting with Open Build Service repositories and packages. It&amp;rsquo;s really powerful; for example, when I call &lt;code&gt;osc build&lt;/code&gt; as mentioned above, it will spin up a VM just like the VMs used on the OBS runners to build packages.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;unexpected-difficulties&#34;&gt;Unexpected difficulties&lt;/h2&gt;&#xA;&lt;p&gt;Given that I am unfamiliar with the Go workflow, I copied a bunch of stuff from the &lt;a href=&#34;https://build.opensuse.org/package/view_file/openSUSE:Factory/hugo/hugo.spec?expand=1&#34;&gt;Hugo specfile&lt;/a&gt; and rebuilt the package.&lt;/p&gt;&#xA;&lt;p&gt;It didn&amp;rsquo;t build.&lt;/p&gt;&#xA;&lt;p&gt;The build output told me that Go was unable to access &lt;code&gt;https://proxy.golang.org&lt;/code&gt;, which is used for the Go package registry. This happens because Go wants to download all of Ollama&amp;rsquo;s dependencies, but OBS&amp;rsquo;s runners aren&amp;rsquo;t internet-connected. After talking with the extremely helpful folks in &lt;a href=&#34;https://matrix.to/#/#packaging:opensuse.org&#34;&gt;#packaging:opensuse.org&lt;/a&gt;, I learned that OBS actually has a mechanism in place to handle Go packages. With the power of _service files, you can build an archive of Go packages that will be passed into the runner. This way, openSUSE doesn&amp;rsquo;t have to package every Go package in existence just to satisfy arbitrary Go-based packages. All you have to do to enable this is add the following service to your _service file:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;service&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;go_modules&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mode=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;manual&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;param&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;compression&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;xz&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/service&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I found that a few more tweaks were required to make this work; I had to add the resulting &lt;code&gt;vendor.tar.xz&lt;/code&gt; to my specfile as a source and untar it during setup. I also had to add &lt;code&gt;-mod=vendor&lt;/code&gt; to the &lt;code&gt;GOFLAGS&lt;/code&gt; environment variable so Go would know to use my bundled modules.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A quick acknowledgement here: I referenced the &lt;a href=&#34;https://gitlab.archlinux.org/archlinux/packaging/packages/ollama/-/blob/main/PKGBUILD?ref_type=heads&#34;&gt;Arch Linux Ollama PKGBUILD&lt;/a&gt; for some stuff related to the Go build process. Thanks, Arch folks!&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;Having done all this, I rebuilt the package.&lt;/p&gt;&#xA;&lt;p&gt;It didn&amp;rsquo;t build.&lt;/p&gt;&#xA;&lt;h2 id=&#34;git-shenanigans&#34;&gt;Git shenanigans&lt;/h2&gt;&#xA;&lt;p&gt;Ollama does not provide LLM execution capability by itself. Rather, it acts as a powerful API wrapper for the wonderful &lt;a href=&#34;https://github.com/ggerganov/llama.cpp&#34;&gt;llama.cpp&lt;/a&gt;. This means that building Ollama needs to include a build of llama.cpp. Ollama achieves this by packaging llama.cpp as a git submodule. Unfortunately, the build script attempts to fetch the llama.cpp submodule from GitHub on every build. I was able to disable this by adding &lt;code&gt;export OLLAMA_SKIP_PATCHING=1&lt;/code&gt; to the build process. However, the build still failed, as the &lt;code&gt;tar_scm&lt;/code&gt; service that I used to download the source does not include the submodule in the source tarball by default.&lt;/p&gt;&#xA;&lt;p&gt;&amp;hellip;or so I thought when Ollama failed to build llama.cpp, complaining that &lt;code&gt;llm/llama.cpp&lt;/code&gt; was not a git repository. I added the following to my &lt;code&gt;tar_scm&lt;/code&gt; service:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;param&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submodules&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;enable&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;but llama.cpp still wouldn&amp;rsquo;t build! After some sleuthing, I realized that the problem was a little bit more insiduous than I had thought. While &lt;code&gt;tar_scm&lt;/code&gt; had been downloading the llama.cpp submodule all along, it was also silently discarding the &lt;code&gt;.git&lt;/code&gt; directory from both the top-level Ollama repo and the llama.cpp submodule. This meant that when Ollama tried to run some git commands on llama.cpp to get the code in the proper state, git would fail, thereby crashing the build. To fix this, I had to add this parameter to &lt;code&gt;tar_scm&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;param&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;package-meta&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;yes&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With all this done, I rebuilt the package&amp;hellip; and it successfully built!&lt;/p&gt;&#xA;&lt;h2 id=&#34;systemd-and-user-accounts&#34;&gt;systemd and user accounts&lt;/h2&gt;&#xA;&lt;p&gt;However, I wasn&amp;rsquo;t out of the woods yet. I still had to properly package the systemd service so users don&amp;rsquo;t have to open a new terminal and run &lt;code&gt;ollama serve&lt;/code&gt; every time they want to talk to an AI. Bundling the service was pretty easy; I extracted the contents of the service file from the Ollama script, put them in a dedicated &lt;code&gt;ollama.service&lt;/code&gt; file, added that file to the specfile, and then added the following instructions to my specfile:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-rpm&#34; data-lang=&#34;rpm&#34;&gt;%pre -f %{name}.pre&#xA;%service_add_pre %{name}.service&#xA;&#xA;%post&#xA;%service_add_post %{name}.service&#xA;&#xA;%preun&#xA;%service_del_preun %{name}.service&#xA;&#xA;%postun&#xA;%service_del_postun %{name}.service&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This installs and uninstalls the Ollama service when you install and uninstall the package. Unfortunately, it doesn&amp;rsquo;t autostart the service, but that&amp;rsquo;s a feature for another day.&lt;/p&gt;&#xA;&lt;p&gt;I also had to create the &lt;code&gt;ollama&lt;/code&gt; user account, since Ollama wants to run the server as a dedicated user. I achieved this by adding a file called &lt;code&gt;ollama-user.conf&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#Type Name   ID GECOS      Home directory Shell&#xA;u     ollama -  &amp;#34;Ollama&amp;#34; /usr/share/ollama    -&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and adding that file to my specfile:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-rpm&#34; data-lang=&#34;rpm&#34;&gt;%{sysusers_requires}&#xA;&#xA;# ...&#xA;&#xA;%build&#xA;%sysusers_generate_pre %{SOURCE3} %{name} %{name}-user.conf&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this done, I pushed the package to OBS (I had been working on the package locally until now) and spun up a fresh openSUSE Tumbleweed VM  to test the package on a clean system. Unfortunately, I had left an error in &lt;code&gt;ollama-user.conf&lt;/code&gt;, so installation failed. A quick fix later, I successfully installed Ollama from an RPM package for the first time!&lt;/p&gt;&#xA;&lt;p&gt;My joy was short-lived, however, as I quickly realized that the systemd service was failing.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/ollama/failing-systemd-service.png&#34;&#xA;    alt=&#34;Repeating log entries showing that the Ollama service is failing to start&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Some investigation revealed that while I&amp;rsquo;d successfully specified that the &lt;code&gt;ollama&lt;/code&gt; user&amp;rsquo;s home folder was &lt;code&gt;/usr/share/ollama&lt;/code&gt;, that folder is not automatically created. The fix ended up being as simple as registering &lt;code&gt;/usr/share/ollama&lt;/code&gt; in the specfile:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-rpm&#34; data-lang=&#34;rpm&#34;&gt;%files&#xA;# %{_datadir} expands to &amp;#34;/usr/share&amp;#34; and %{name} expands to &amp;#34;ollama&amp;#34;&#xA;%attr(-, ollama, ollama) %{_datadir}/%{name}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note the &lt;code&gt;%attr(-, ollama, ollama)&lt;/code&gt;; I initially failed to set &lt;code&gt;ollama&lt;/code&gt; as the owner of &lt;code&gt;/usr/share/ollama&lt;/code&gt;, so Ollama was unable to access its own home folder! Also of note is that after receiving feedback on my package, I changed the home folder to &lt;code&gt;/var/lib/ollama&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;With this fix in place, I again installed the package on my test VM&amp;hellip; and after running &lt;code&gt;sudo systemctl start ollama&lt;/code&gt;, I was able to run the Ollama systemd service!&lt;/p&gt;&#xA;&lt;h2 id=&#34;getting-it-into-the-opensuse-repositories&#34;&gt;Getting it into the openSUSE repositories&lt;/h2&gt;&#xA;&lt;p&gt;Now I just needed to upstream the package to the &lt;a href=&#34;https://build.opensuse.org/project/show/openSUSE:Factory&#34;&gt;openSUSE:Factory&lt;/a&gt; repository, which is the default repository for all packages that ship with openSUSE Tumbleweed. However, you can&amp;rsquo;t directly submit a package to Factory.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/ollama/one-does-not-simply-upload-to-opensuse-factory.png&#34;&#xA;    alt=&#34;One does not simply upload to openSUSE:Factory&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Instead, openSUSE packaging uses development repositories. These are repositories that are officially run by openSUSE but not directly packaged in openSUSE. Instead, they serve as a staging area to verify that packages build and look sane. Once a package passes in the devel repository, it can be submitted to Factory. In my case, Ollama fit best in the &lt;a href=&#34;https://build.opensuse.org/project/show/science:machinelearning&#34;&gt;science:machinelearning&lt;/a&gt; project, so I submitted my Ollama package there. Around 12 hours later, my request was accepted!&lt;/p&gt;&#xA;&lt;p&gt;The final step to adding a package to Factory is to write to the &lt;a href=&#34;https://lists.opensuse.org/archives/list/factory@lists.opensuse.org/&#34;&gt;openSUSE Factory mailing list&lt;/a&gt; to announce your new package, and then submit a request with a link back to that email. Unfortunately, my first two attempts at submitting my package were rejected due to various minor problems in the specfile. However, after submitting my third request, I recieved this exciting email:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/ollama/package-finally-accepted-email.png&#34;&#xA;    alt=&#34;An email stating that Ollama was accepted into openSUSE:Factory&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;This was the most exciting email I&amp;#39;ve recieved in a little while.&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s right, Ollama was finally accepted into Factory! My packaging saga was finally over!&lt;/p&gt;&#xA;&lt;p&gt;&amp;hellip;at least, it&amp;rsquo;s over for now. Ollama releases happen more or less weekly, so I&amp;rsquo;ll probably have to spend a few minutes every weekend pulling down the latest sources and sending them off to Factory, but the hard work is done.&lt;/p&gt;&#xA;&lt;h2 id=&#34;some-limitations-of-the-package&#34;&gt;Some limitations of the package&lt;/h2&gt;&#xA;&lt;p&gt;If every cloud has a silver lining, then every silver lining must have a cloud&amp;hellip; right? At any rate, this package is not a perfect solution for everyone. If you wish to use CUDA or ROCm to accellerate your LLMs by running them on the GPU instead of the CPU, you will need to install Ollama from source. openSUSE can&amp;rsquo;t package a CUDA or ROCm version of Ollama, since neither of those frameworks are available in the main openSUSE repositories. ROCm is open source, so it could eventually land in the main repository, but CUDA is nonfree, so it&amp;rsquo;s out of the question for now. I&amp;rsquo;m not ruling out the possibility of eventually releasing an RPM that links against CUDA or ROCm, but something will have to change in order for that to happen.&lt;/p&gt;&#xA;&lt;p&gt;However, if you&amp;rsquo;re just looking for a quick way to run an LLM on your machine and don&amp;rsquo;t need extremely fast results, then enjoy running Ollama from the openSUSE repositories!&lt;/p&gt;&#xA;&lt;h2 id=&#34;some-sort-of-a-conclusion&#34;&gt;Some sort of a conclusion&lt;/h2&gt;&#xA;&lt;p&gt;Despite the various frustrations of building the Ollama RPM, I really enjoyed this little adventure into packaging and plan to keep getting involved. I&amp;rsquo;ll continue to maintain the Ollama package, and I&amp;rsquo;m also planning to take maintenance of a few other packages. Furthermore, I&amp;rsquo;ve created a few more packages! Specifically, I&amp;rsquo;ve packaged &lt;a href=&#34;https://supernotes.app/open-source/sn-pro/&#34;&gt;Supernote&amp;rsquo;s SN Pro font&lt;/a&gt; (&lt;a href=&#34;https://build.opensuse.org/package/show/M17N:fonts/supernotes-snpro-fonts&#34;&gt;package&lt;/a&gt;), and I&amp;rsquo;m working on submitting a package for &lt;a href=&#34;https://github.com/nickbabcock/dness&#34;&gt;dness&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;At this point, I also want to thank &lt;a href=&#34;https://matrix.to/#/%40rrahl0%3Aopensuse.org&#34;&gt;Richard Rahl&lt;/a&gt; and &lt;a href=&#34;https://matrix.to/#/%40smolsheep%3Aopensuse.org&#34;&gt;smolsheep&lt;/a&gt; for their help. Whenever I asked questions in &lt;a href=&#34;https://matrix.to/#/#packaging%3Aopensuse.org&#34;&gt;#packaging:opensuse.org&lt;/a&gt;, they were always quick to patiently answer my questions and point out problems with my package. People like them are what makes contributing to open source projects awesome. Thanks, y&amp;rsquo;all!&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>ai</category><category>opensource</category><category>opensuse</category><category>packaging</category>
    </item>
    <item>
      <title>How to reject a feature request</title>
      <link>https://lorendb.dev/posts/how-to-reject-a-feature-request/</link>
      <pubDate>Wed, 21 Feb 2024 21:40:00 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/how-to-reject-a-feature-request/</guid>
      <description>&lt;p&gt;Open-source maintainers go through a lot. They have to deal with users who feel entitled to having their bugs fixed before the other bugs, contributers who submit pull requests of&#xA;subpar quality, and loads and loads of duplicate issues. Given all this, it&amp;rsquo;s understandable that maintainers sometimes feel overwhelmed or lash out at those they feel are being&#xA;unreasonable (for example, Linus Torvalds famously &lt;a href=&#34;https://lkml.org/lkml/2018/9/16/167&#34;&gt;stepped down&lt;/a&gt; to address his abusive behavior, and still occasionally sends a heated email).&lt;/p&gt;&#xA;&lt;p&gt;However, it is in everybody&amp;rsquo;s best interest that maintainters stay calm and respectful. Abusive maintainers will quickly lead to projects being abandoned by both contributers and&#xA;users, which reduces the quality of open-source software in general.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A quick note here: I don&amp;rsquo;t currently maintain any large open-source projects. However, I&amp;rsquo;ve watched and experienced feature request rejections in various projects, and I feel like&#xA;this is something that everybody should be reminded of periodically.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;It is inevitable that a user will someday request a feature that you don&amp;rsquo;t like. For example, I have observed users repeatedly requesting that &lt;a href=&#34;https://github.com/nheko-reborn/nheko&#34;&gt;nheko&lt;/a&gt;&#xA;gain the capability to show link previews. The maintainer has repeatedly rejected those requests on grounds that it reduces privacy (by giving your IP address to any URL that&#xA;somebody sends you, even if you don&amp;rsquo;t open the URL yourself).&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-wrong-way-to-reject-a-feature-request&#34;&gt;The wrong way to reject a feature request&lt;/h2&gt;&#xA;&lt;p&gt;You may be tempted to tell the user off for suggesting such a clearly flawed and stupid feature. Why are you wasting my time? I&amp;rsquo;m not going to spend my evening working on a feature&#xA;that nobody but you will ever use! If you want it so bad, why don&amp;rsquo;t you just fork the project and implement it yourself?&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the problem: you&amp;rsquo;re establishing yourself as an opinionated, self-centered person who will only implement features that you will use yourself. Now, to be clear, it&amp;rsquo;s not wrong&#xA;to implement features just for yourself. This is referred to as &lt;a href=&#34;https://nheko.io/notes/9n4dz70ktmu0ozzu&#34;&gt;annoyance-driven development&lt;/a&gt;. However, a good maintainer will put some&#xA;effort into implementing features to make life easier for others.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-right-way-to-reject-a-feature-request&#34;&gt;The right way to reject a feature request&lt;/h2&gt;&#xA;&lt;p&gt;Instead, you should politely reject the request with a clear explanation of why you think it&amp;rsquo;s a bad idea. It&amp;rsquo;s fine to suggest forking the project, but you should refrain from doing&#xA;that in most cases. Instead, invite them to contribute the feature themselves.&lt;/p&gt;&#xA;&lt;p&gt;You should never reject a feature across the board unless it has serious ethical, privacy, or security concerns or if it would require a ridiculous amount of refactoring for a feature&#xA;that would affect a very small percentage of your users. Here&amp;rsquo;s why: even if you refuse to implement the feature yourself, leaving that feature as a possibility could lead to a new&#xA;contributor. Once they have added the feature, you don&amp;rsquo;t need to touch the code again (unless it breaks) or use that feature at all. However, some of your other users have gained a&#xA;new feature, and your software has become more compelling for new users.&lt;/p&gt;&#xA;&lt;h2 id=&#34;when-its-ok-to-be-a-bit-grouchy&#34;&gt;When it&amp;rsquo;s OK to be a bit grouchy&lt;/h2&gt;&#xA;&lt;p&gt;Sometimes users persist in being unreasonable. Let&amp;rsquo;s look at a real-life situation where a feature request was declined, but the user kept pestering the maintainer about it.&lt;/p&gt;&#xA;&lt;p&gt;It turns out that there is a tiny group of Linux users who believe that &lt;a href=&#34;https://www.freedesktop.org/wiki/Software/dbus/&#34;&gt;D-Bus&lt;/a&gt; is bloated, a security risk, and otherwise a bad idea.&#xA;One of them, @kyronsaif, &lt;a href=&#34;https://github.com/Nheko-Reborn/nheko/issues/1390&#34;&gt;opened an issue&lt;/a&gt; asking if D-Bus could be dropped as a dependency of nheko, where it is currently used to&#xA;show notifications on Linux and expose a useful API for other applications. The maintainer, @deepbluev7, &lt;a href=&#34;https://github.com/Nheko-Reborn/nheko/issues/1390#issuecomment-1445040189&#34;&gt;replied&lt;/a&gt;&#xA;with a calm, rational explanation of why nheko depends on D-Bus and why the dependency won&amp;rsquo;t be dropped. Case closed&amp;hellip; until it wasn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;p&gt;The next day, user @Tcll &lt;a href=&#34;https://github.com/Nheko-Reborn/nheko/issues/1391&#34;&gt;opened another issue&lt;/a&gt; asking to remove D-Bus. This issue is less of a request and more of a tirade&#xA;against D-Bus. This can be observed in multiple places. First, @Tcll says:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;I haven&amp;rsquo;t found any alternative implementations of dbus&#xA;(I personally don&amp;rsquo;t care about integrations enough to look)&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;This indicates a lack of effort put into the issue. While @Tcll clearly cares enough to create an issue, there is little to no effort put into giving reasonable alternatives to&#xA;D-Bus. Later, @Tcll says:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;it breaks nemo [editor&amp;rsquo;s note: nemo is a Linux file manager]&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;and&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;other apps automatically try to integrate with it to do more than they should have to&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;Neither of these are issues with D-Bus, and neither of these have anything to do with nheko using D-Bus.&lt;/p&gt;&#xA;&lt;p&gt;@deepbluev7 &lt;a href=&#34;https://github.com/Nheko-Reborn/nheko/issues/1391#issuecomment-1445154982&#34;&gt;responded&lt;/a&gt; with an emphatic message reiterating the reasoning from the previous issue and then&#xA;closed the issue. @Tcll fired back with a message filled with incorrect claims (&amp;ldquo;sad that the user has to be forced to build this because Linux just can&amp;rsquo;t be easy to use&amp;rdquo;), lack of&#xA;willingness to put forth any personal effort (&amp;ldquo;don&amp;rsquo;t ask me to spend time learning your code structure though, you already know your structure&amp;rdquo;), and personal attacks on @deepbluev7&#xA;(&amp;ldquo;way to be a bad dev letting your personal preferences get in the way of others&amp;hellip;&amp;rdquo;).&lt;/p&gt;&#xA;&lt;p&gt;For the sake of brevity, I won&amp;rsquo;t continue to provide a play-by-play of the entire issue thread, since most of it continued in this vein. However, I will point out that every time&#xA;@deepbluev7 replied, his replies continued to contain rational reasons for rejecting the feature request. His answers do start sounding more and more frustrated:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Any user can do anything they want with Nheko (apart from what the GPL disallows), but no user can force me to do anything or restrict me from spending my time on more useful things.&#xA;FOSS is about collaboration, not about making others do stuff for you. And it is common and even beneficial in FOSS to just fork and implement what you like there and then have upstream&#xA;pull it, when they think it is useful. I think you are seriously misunderstanding something here.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;but he continues to retain a respectful tone throughout, even mentioning an alternative solution for @Tcll:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;I agree. Maintaining multiple implementations on how to do everything in Nheko as well as ways to switch between those at runtime is probably much more overhead to maintain, than just&#xA;having multiple clients. There are actually several other clients, some of them might fit your requirements better!&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;As the discussion begins to taper off, @Tcll acknowledges @deepbluev7&amp;rsquo;s handling of the situation:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;at least that&amp;rsquo;s a more reasonable answer than &amp;ldquo;build it yourself&amp;rdquo; or other flack like I commonly get&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;at least this was a much more respectful issue, and I love it when things go this way&#xA;even if neither of us can reach a conclusion, I&amp;rsquo;d rather it end with respect than enemies being made out of it&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;Clearly, some of @deepbluev7&amp;rsquo;s responses would not be appropriate if this were a corporate support desk, but this isn&amp;rsquo;t a corporate support desk. This is an individual volunteer whose personal&#xA;time is being taken up by this discussion, and the resulting frustration is completely understandable. However, at no point did @deepbluev7 veer into the realm of blatant name-calling or&#xA;violent attacks.&lt;/p&gt;&#xA;&lt;h2 id=&#34;in-conclusion&#34;&gt;In conclusion&lt;/h2&gt;&#xA;&lt;p&gt;Maintainers must retain a certain level of professionalism in dealing with users. However, as long as you aren&amp;rsquo;t running an open-source company, it&amp;rsquo;s fine if you aren&amp;rsquo;t entirely&#xA;professional. Nobody expects &lt;a href=&#34;https://xkcd.com/2347/&#34;&gt;a random guy fron Nebraska&lt;/a&gt; to behave as if he were the support staff for a Big Tech company, and neither do they expect you to&#xA;behave that way. Just stay respectful to your users, even if they are a bit unreasonable, and you&amp;rsquo;ll find that others will respect you for it.&lt;/p&gt;&#xA;&lt;p&gt;Also, if @deepbluev7, @kyronsaif, or @Tcll read this post, please don&amp;rsquo;t take offense at being used as an example!&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>opensource</category>
    </item>
    <item>
      <title>Fixing Hugo&#39;s RSS Feed</title>
      <link>https://lorendb.dev/posts/fixing-hugos-rss-feed/</link>
      <pubDate>Mon, 19 Feb 2024 23:15:00 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/fixing-hugos-rss-feed/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve built this blog using &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt;, which I love for its sheer simplicity. However, I noticed that the autogenerated &lt;a href=&#34;https://lorendb.dev/index.xml&#34;&gt;RSS feed&lt;/a&gt;&#xA;wasn&amp;rsquo;t showing any author information. It turns out that I copy-pasted an outdated &lt;code&gt;hugo.toml&lt;/code&gt; template that used an older method of specifying author information. This led to&#xA;the following changes in my &lt;code&gt;hugo.toml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# before&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;author&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Loren Burkholder&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# after&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;author&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Loren Burkholder&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;author&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;link&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://lorendb.dev&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;author&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;loren@lorendb.dev&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, the feed still had some problems. The author for each post was formatted as &lt;code&gt;loren@lorendb.dev (Loren Burkholder)&lt;/code&gt;, which is pointlessly verbose. Furthermore,&#xA;while I try to add tags to each post I make, Hugo was blissfully ignoring them in the RSS feed.&lt;/p&gt;&#xA;&lt;p&gt;The author name formatting was fairly easy to fix; I just needed to copy the &lt;a href=&#34;https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml&#34;&gt;default RSS template&lt;/a&gt;&#xA;to &lt;code&gt;layouts/_default/rss.xml&lt;/code&gt; and change the author field from this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with $authorEmail }}&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;author&amp;gt;&lt;/span&gt;{{ . }}{{ with $authorName }} ({{ . }}){{ end }}&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with $authorName }}&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dc:creator&amp;gt;&lt;/span&gt;{{ . }}&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dc:creator&amp;gt;&lt;/span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will render each post with the author set to &lt;code&gt;Loren Burkholder&lt;/code&gt;. Much better! You will note that I changed the &lt;code&gt;&amp;lt;author&amp;gt;&lt;/code&gt; tag to &lt;code&gt;&amp;lt;dc:creator&amp;gt;&lt;/code&gt;. This is because the&#xA;RSS spec &lt;a href=&#34;https://www.rssboard.org/rss-profile#element-channel-item-author&#34;&gt;requires&lt;/a&gt; the &lt;code&gt;&amp;lt;author&amp;gt;&lt;/code&gt; tag to use the &lt;code&gt;me@example.com (My Name)&lt;/code&gt; format. &lt;code&gt;&amp;lt;dc:creator&amp;gt;&lt;/code&gt;, however,&#xA;allows you to use just a name without violating the spec. Unfortunately, it looks like &lt;a href=&#34;https://apps.kde.org/akregator/&#34;&gt;Akregator&lt;/a&gt; can&amp;rsquo;t handle a feed without &lt;code&gt;&amp;lt;author&amp;gt;&lt;/code&gt;,&#xA;and &lt;a href=&#34;https://tt-rss.org&#34;&gt;Tiny Tiny RSS&lt;/a&gt; doesn&amp;rsquo;t observe the &lt;code&gt;&amp;lt;dc:creator&amp;gt;&lt;/code&gt; field, so I had to put back the original author field as well.&lt;/p&gt;&#xA;&lt;p&gt;The tags were slightly trickier. Since I&amp;rsquo;m a total Hugo scripting noob, I ended up copying a &lt;a href=&#34;https://discourse.gohugo.io/t/loop-through-categories-tags/20317/2&#34;&gt;general tag rendering snippet&lt;/a&gt;&#xA;from the Hugo forums and adapting it to my RSS template:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ if isset .Params &amp;#34;tags&amp;#34; -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ range $index, $name := sort .Params.tags -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;category&amp;gt;&lt;/span&gt;{{ $name }}&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/category&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{- end}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- end }} &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can find the entire RSS template &lt;a href=&#34;https://gist.github.com/LorenDB/2faa2bb78885806c8d4c914d01130e1e&#34;&gt;here&lt;/a&gt; if you want to use it in your own Hugo site.&lt;/p&gt;&#xA;&lt;h2 id=&#34;an-update-from-the-next-day&#34;&gt;An update from the next day&lt;/h2&gt;&#xA;&lt;p&gt;The morning after I published this post, I noticed that Hugo is also truncating the contents of RSS feeds, so anybody using a feed reader on this blog would only see the first&#xA;paragraph or so of each post. This is an absolute crime in the realm of feed readers; it forces people to leave their feed reader for an external site to read the post. Being&#xA;lazy, I copied &lt;a href=&#34;https://www.godo.dev/tutorials/hugo-full-text-rss/&#34;&gt;somebody else&amp;rsquo;s approach&lt;/a&gt; of telling Hugo to use the post &lt;code&gt;.Content&lt;/code&gt; instead of the &lt;code&gt;.Summary&lt;/code&gt;. However, I&#xA;also found out (the hard way) that you must keep the call to &lt;code&gt;transform.XMLEscape&lt;/code&gt;; otherwise, your RSS feed will probably break when others try to add it to their feed readers.&lt;/p&gt;&#xA;&lt;p&gt;I also realized that my original &lt;code&gt;&amp;lt;author&amp;gt;&lt;/code&gt; implementation didn&amp;rsquo;t meet RSS spec, so I modified it to come back in line with the spec.&lt;/p&gt;&#xA;&lt;h2 id=&#34;update-2242024&#34;&gt;Update (2/24/2024)&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve realized from &lt;a href=&#34;https://news.ycombinator.com/item?id=39495285&#34;&gt;this HN thread&lt;/a&gt; that using &lt;code&gt;&amp;lt;dc:creator&amp;gt;&lt;/code&gt; requires a modification to the &lt;code&gt;&amp;lt;rss&amp;gt;&lt;/code&gt; element:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;rss&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;version=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns:atom=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://www.w3.org/2005/Atom&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns:dc=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://purl.org/dc/elements/1.1/&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will actually import the appropriate RSS namespace to provide &lt;code&gt;&amp;lt;dc:creator&amp;gt;&lt;/code&gt;. Without this, some readers like Thunderbird would fail.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>blog</category><category>hugo</category><category>rss</category>
    </item>
    <item>
      <title>Lapce for C&#43;&#43; Development</title>
      <link>https://lorendb.dev/posts/lapce-for-cpp-development/</link>
      <pubDate>Sun, 18 Feb 2024 23:08:00 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/lapce-for-cpp-development/</guid>
      <description>&lt;p&gt;On February 18, &lt;a href=&#34;https://lapce.dev&#34;&gt;Lapce&lt;/a&gt; hit &lt;a href=&#34;https://news.ycombinator.com/item?id=39421090&#34;&gt;number one on Hacker News&lt;/a&gt;. I was immediately intrigued to see&#xA;a new cross-platform IDE show up that was &lt;em&gt;not&lt;/em&gt; built on Electron. In fact, I was intrigued enough to download Lapce and take it for a spin (in fact, I&amp;rsquo;m writing&#xA;this post in Lapce).&lt;/p&gt;&#xA;&lt;p&gt;My review will be influenced heavily by the fact that I nearly exclusively work with C++. I sometimes use QML in conjunction with C++, and I also really enjoy using&#xA;&lt;a href=&#34;https://dlang.org&#34;&gt;D&lt;/a&gt;, but the fact remains that in both personal and work programming, I generally use C++. Therefore, I will mainly evaluate Lapce based on its&#xA;performance as a C++ IDE. Currently, I daily-drive Qt Creator, which is a fairly polished IDE that eschews fancy visuals in favor of building a highly functional IDE&#xA;for folks like myself who work with Qt. This means that Lapce will have a high bar to clear for me to actually consider adopting it as my default IDE.&lt;/p&gt;&#xA;&lt;p&gt;Without further ado, let&amp;rsquo;s get into the review!&lt;/p&gt;&#xA;&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;&#xA;&lt;p&gt;Lapce has a download button on their website, so I figured I&amp;rsquo;d try their Linux download. I was expecting to maybe get links to .deb or .rpm files or maybe an AppImage;&#xA;instead, they provide a .tar.gz that contains a single binary file. Rather unorthodox, but it actually worked fine on openSUSE Tumbleweed, so obviously they&amp;rsquo;ve built&#xA;it right. However, I&amp;rsquo;d prefer to see them distribute either an AppImage (which can be properly installed using &lt;a href=&#34;https://news.ycombinator.com/item?id=39421090&#34;&gt;AppImageLauncher&lt;/a&gt;)&#xA;or a .flatpakref file that points to a Flatpak repository. After all, Lapce is already &lt;a href=&#34;https://flathub.org/apps/dev.lapce.lapce&#34;&gt;available on Flathub&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;After running the binary from the .tar.gz, I decided to see if they had a native openSUSE package available; I was pleasantly surprised to find that there is a &lt;code&gt;lapce&lt;/code&gt;&#xA;package available in the default Tumbleweed repo (for the uninitiated, openSUSE puts most software in the &lt;code&gt;openSUSE-Tumbleweed-Oss&lt;/code&gt; repo, but some software is in other&#xA;official repositories; for example, many Matrix clients are in &lt;code&gt;network:messaging:matrix&lt;/code&gt;). I installed the package and found that it did not remember my settings from&#xA;the .tar.gz version. This was highly disappointing; software on Linux should always store settings in the same paths. However, upon further inspection, I found that this&#xA;is actually a good thing: while openSUSE bundles the nightly version, the .tar.gz download is the latest stable build, so they store their configuration in separate&#xA;folders to avoid a nightly build breaking your development workflow.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-ui&#34;&gt;The UI&lt;/h2&gt;&#xA;&lt;p&gt;My first impressions when actually running the IDE can be summed up thusly: Lapce is trying to create a more-or-less drop-in replacement for Visual Studio Code, but&#xA;written in Rust. And as long as it&amp;rsquo;s not using Electron, I count that as a win.&lt;/p&gt;&#xA;&lt;p&gt;However, I&amp;rsquo;d like to get a bit more specific about the UI. I never have really appreciated the Visual Studio Code layout; I much prefer how Visual Studio or Qt Creator&#xA;organize the UI. Now, Lapce does a pretty good job at looking OK, but I do have a fair number of nitpicks. I&amp;rsquo;ve categorized them to make them seem slightly more coherent.&lt;/p&gt;&#xA;&lt;h3 id=&#34;actually-editing-text&#34;&gt;Actually editing text&lt;/h3&gt;&#xA;&lt;p&gt;It&amp;rsquo;s shockingly easy to lose focus on the editor, and when you do, keyboard inputs won&amp;rsquo;t work like you would expect. For example, if you use the resize handle on the&#xA;file view, the editor will stop recieving input until you click somewhere else in the UI. Even global shortcuts (like Save or the command palette) don&amp;rsquo;t work.&lt;/p&gt;&#xA;&lt;p&gt;Lapce doesn&amp;rsquo;t support the normal middle-click paste functionality that is generally supported on Linux. This is a huge deal-breaker for me, as I use this all the time.&#xA;Another essential text editing feature that is not implemented is dragging selected text. I suppose this may be because Lapce is using a &lt;a href=&#34;https://github.com/lapce/floem&#34;&gt;custom UI toolkit&lt;/a&gt;&#xA;that may not have implemented text dragging yet.&lt;/p&gt;&#xA;&lt;h3 id=&#34;theme-problems&#34;&gt;Theme problems&lt;/h3&gt;&#xA;&lt;p&gt;The default theme has very similar colors for the general background and the editor background. This, when combined with the minimalist design, makes it rather hard to&#xA;differentiate between your file content and the file sidebar, especially when you are working with a Markdown file that is mainly white text.&lt;/p&gt;&#xA;&lt;h3 id=&#34;file-sidebar-issues&#34;&gt;File sidebar issues&lt;/h3&gt;&#xA;&lt;p&gt;The file sidebar has a number of issues that I want to point out. First, the sidebar is laid out in two sections. At the top is open editors; at the bottom is your&#xA;filesystem view. I am used to this being the other way around; unfortunately, I can&amp;rsquo;t find any way to change this. To make matters worse, you can&amp;rsquo;t even resize the&#xA;two sections. The default sizing leaves me able to see only three open files at a time. Moving on, there are only two icons used in the sidebar by default: one for&#xA;folders and one for files. Thankfully, there is one extension that adds new icons (Material-themed) to Lapce, which add unique and colorful icons for many file types,&#xA;but the default experience makes it extremely difficult to find files. To make matters worse, if a file has a name long enough to start overflowing the file explorer,&#xA;Lapce will shrink and then remove the icon for the file before it will &lt;a href=&#34;https://doc.qt.io/qt-5/qtwidgets-widgets-elidedlabel-example.html&#34;&gt;elide&lt;/a&gt; the file name. This&#xA;makes it harder to distinguish files while providing no real benefit in terms of file name readability.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-command-palette&#34;&gt;The command palette&lt;/h3&gt;&#xA;&lt;p&gt;The command palette is unusual, to say the least. To begin with, it uses &lt;code&gt;Ctrl+Shift+P&lt;/code&gt;, which is a bit unwieldy, and simultaneously uses&amp;hellip; &lt;code&gt;F1&lt;/code&gt;?! &lt;code&gt;F1&lt;/code&gt; is recognized&#xA;everywhere as the de facto Help key, not a command palette key! I tried rebinding to the more familiar and easier &lt;code&gt;Ctrl+K&lt;/code&gt;, but it didn&amp;rsquo;t work. It turns out that&#xA;&lt;code&gt;Ctrl+K&lt;/code&gt; is used for shortcut layering. Fine, I thought, I&amp;rsquo;ll use &lt;code&gt;Ctrl+K Ctrl+K&lt;/code&gt;. No dice. I eventually settled on &lt;code&gt;Ctrl+Shift+K&lt;/code&gt;, but it feels like a suboptimal&#xA;solution. Once I had my shortcuts in place, I found that your shortcut(s) will only open the command palette; to close it, you have to use Esc. This is bad design;&#xA;menus should be closable with the same key that opens them (see: the Windows Start menu). Once you get the command palette open, it types a colon at the beginning,&#xA;which limits you to commands. To search files, you have to remove the colon. Given that my main use for this sort of global search bar feature is quickly jumping to&#xA;files or symbols, I&amp;rsquo;m not impressed. Finally, if you click on the command palette bar, you can&amp;rsquo;t type anything into it, not even Esc. You have to click outside the&#xA;palette to close it and then open it with the shortcut.&lt;/p&gt;&#xA;&lt;h3 id=&#34;miscellaneous-ui-issues&#34;&gt;Miscellaneous UI issues&lt;/h3&gt;&#xA;&lt;p&gt;While the file sidebar shows your open editors, there is also a tab bar at the top of the window with your open files. To me, this is a waste of precious vertical&#xA;space that could be used to show more code. After all, in IDEs, vertical space is precious, and I don&amp;rsquo;t want to sacrifice visible lines of code to add a redundant&#xA;open files view. On that note, Lapce scores poorly on visible lines of code. It shows just 52 lines at a time, while Visual Studio Code manages 64 and Qt Creator&#xA;gets 66 (although I may have changed font sizes in the other two IDEs; I don&amp;rsquo;t remember). After changing the font size from the default 13 points down to a&#xA;still-readable 12, Lapce still only has 58 visible lines of code.&lt;/p&gt;&#xA;&lt;p&gt;There is a general lack of tooltips throughout the UI that makes it difficult to know what every button will do. Again, this may be due to the custom UI toolkit.&lt;/p&gt;&#xA;&lt;h2 id=&#34;markdown-editing&#34;&gt;Markdown editing&lt;/h2&gt;&#xA;&lt;p&gt;This post was written in Lapce as I worked with the IDE; this gave me a great opportunity to try out Lapce&amp;rsquo;s Markdown support. Overall, it looks pretty nice. There&amp;rsquo;s&#xA;good syntax highlighting, code blocks that have a content language specified have appropriate syntax highlighting applied to the contents, and there is even support&#xA;for the TOML front matter that Hugo generates. However, I do have a few complaints:&lt;/p&gt;&#xA;&lt;p&gt;Generally, IDEs don&amp;rsquo;t use word-wrapping by default, since it would make code extremely difficult to read. However, when you are working with Markdown, you&amp;rsquo;re not&#xA;working with carefully formatted code. You&amp;rsquo;re working with paragraphs of text. And when you are working with paragraphs of text, word-wrapping makes it easier to read.&#xA;Visual Studio Code word-wraps Markdown files for this reason. However, Lapce does not word-wrap Markdown. I&amp;rsquo;d love to see this fixed.&lt;/p&gt;&#xA;&lt;p&gt;Inline code is very difficult to distinguish from regular text; the backticks are highlighted but the contents are not. I&amp;rsquo;d love to see the contents have either a&#xA;different text color or a unique background color.&lt;/p&gt;&#xA;&lt;p&gt;List handling is so-so; while most editors will auto-generate the next list item when you press Enter, Lapce does not.&lt;/p&gt;&#xA;&lt;p&gt;Finally, Lapce has no Markdown preview support. To me, this isn&amp;rsquo;t a huge deal, but for some people it would probably be a deal-breaker.&lt;/p&gt;&#xA;&lt;h2 id=&#34;c-development&#34;&gt;C++ development&lt;/h2&gt;&#xA;&lt;p&gt;Upon opening a C++ project, I immediately noticed that Lapce supports C++ syntax out of the box. Nearly every character in every file has coloring applied to it. Some&#xA;symbols such as braces, brackets, parentheses, and operators remain the default text color, but every identifier and value in the source has a color applied. As somebody&#xA;who appreciates the value of colors to better communicate what&amp;rsquo;s going on in code, I am a huge fan of this. (In fact, I have changed the Qt Creator dark syntax theme to&#xA;add more color; you can get it &lt;a href=&#34;https://lorendb.dev/files/qt-creator-dark-colorful.xml&#34;&gt;here&lt;/a&gt;.)&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, there was not much more than syntax highlighting available. After searching fruitlessly for a menu entry to allow me to manage extensions, however, I&#xA;found the extensions button at the top of the file view. Sure enough, there was a clangd extension available, so I installed it and&amp;hellip; nothing changed, except for an&#xA;error popup declaring that the clangd extension couldn&amp;rsquo;t retrieve an artifact. What? I eventually learned that it was trying (and apparently failing) to download clangd.&#xA;Fortunately, you can configure it with the path to your already-installed clangd. After doing so, I was rewarded with a fairly usable C++ experience. Here&amp;rsquo;s how Lapce&#xA;looks with the code for &lt;a href=&#34;https://invent.kde.org/utilities/alpaka&#34;&gt;Alpaka&lt;/a&gt; open:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/lapce/lapce-alpaka.png&#34;&#xA;    alt=&#34;The Alpaka source code open in Lapce&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;However, it wasn&amp;rsquo;t all smooth sailing. I immediately realized that Lapce displays &lt;a href=&#34;https://www.jetbrains.com/help/idea/inlay-hints.html&#34;&gt;inlay hints&lt;/a&gt; when it has enough&#xA;information available to do so. I find inlay hints incredibly annoying, so I had to uncover the setting to turn them off. Another unfortunate realization was that the C++&#xA;syntax highlighting became less colorful with clangd enabled; however, there is still plenty of color in the IDE, so I don&amp;rsquo;t feel like that is a huge problem. Code&#xA;completion was another rough patch; while Lapce gave me plenty of completions from clangd, I found that it wasn&amp;rsquo;t completely intelligent about doing so. For example,&#xA;typing &lt;code&gt;case Foo:&lt;/code&gt; in a switch statement would trigger a completion popup suggesting a variable. Normally, I would expect to be able to type a newline before any&#xA;completion popups display; with Lapce, however, I have to press Esc after typing the colon.&lt;/p&gt;&#xA;&lt;p&gt;It quickly became obvious to me that Lapce is not designed to create an extremely powerful C++ experience. As a simple example, while Qt Creator allows you to switch&#xA;between headers and source files, there is no such functionality in Lapce. However, for a more impactful example, let&amp;rsquo;s look at the C++ build environment support. In&#xA;IDEs like Lapce and Visual Studio Code, you are not given project support. You are instead given support for run configurations, which (in the case of Lapce) are TOML&#xA;files that can contain commands for running apps. In my opinion, when you already have build systems like CMake, Meson, and Cargo that are designed to handle this for&#xA;you, I shouldn&amp;rsquo;t have to specify this stuff myself. I should be able to simply have Lapce run &lt;code&gt;cmake --build&lt;/code&gt; on my code when I ask it to build my app.&lt;/p&gt;&#xA;&lt;p&gt;With all this being said, I realize that I&amp;rsquo;m giving Lapce a bit of a hard time here. It&amp;rsquo;s not intended to be a supercharged C++ IDE; it aims to be a replacement for Visual&#xA;Studio Code, and it does well at that. It&amp;rsquo;s just that Visual Studio Code and similar editors have introduced a paradigm that I don&amp;rsquo;t appreciate.&lt;/p&gt;&#xA;&lt;h2 id=&#34;final-assessment&#34;&gt;Final assessment&lt;/h2&gt;&#xA;&lt;p&gt;Lapce is a promising IDE, and for a &lt;a href=&#34;https://news.ycombinator.com/item?id=39423493&#34;&gt;hobby project&lt;/a&gt; it&amp;rsquo;s at an impressive level of completion. Since it is clearly marked&#xA;as being in beta, I am willing to forgive a lot of problems as bugs that are planned to be fixed before a 1.0 release.&lt;/p&gt;&#xA;&lt;p&gt;However, I will not be switching to Lapce due to its overall lackluster C++ experience. Qt Creator provides a much more powerful experience for me; in fact, the only real&#xA;gripe I have with Qt Creator is that it doesn&amp;rsquo;t support languages other than C++ and QML very well. This could technically be fixed by creating plugins for other languages,&#xA;but Qt Creator&amp;rsquo;s plugin interface is not very well documented.&lt;/p&gt;&#xA;&lt;p&gt;I do plan to keep an eye on Lapce. I dislike Visual Studio Code for its use of Electron and its invasive telemetry. If Lapce improves enough and gets a D plugin, I&amp;rsquo;ll&#xA;probably switch my D developement to Lapce so I can ditch Visual Studio Code once and for all.&lt;/p&gt;&#xA;&lt;p&gt;Finally, I want to give a shoutout to the Lapce developers for creating such a cool (and gorgeous) app. If any of the developers reads this, please forgive the ranty&#xA;tone of voice and take this as constructive criticism :)&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>c&#43;&#43;</category><category>ide</category><category>review</category>
    </item>
    <item>
      <title>Let&#39;s Ban SMS 2FA</title>
      <link>https://lorendb.dev/posts/lets-ban-sms-2fa/</link>
      <pubDate>Sun, 11 Feb 2024 16:08:00 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/lets-ban-sms-2fa/</guid>
      <description>&lt;p&gt;There, I said it.&lt;/p&gt;&#xA;&lt;p&gt;Now that I&amp;rsquo;ve got that out of the way, let&amp;rsquo;s talk a bit about why we should actually ban SMS 2FA.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A quick note: I&amp;rsquo;ve rewritten this post to present a more balanced view of the topic. You can find the old version on the Internet Archive.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;the-problems&#34;&gt;The problems&lt;/h2&gt;&#xA;&lt;p&gt;SMS 2FA is something of a paradox in and of itself. Two-factor authentication is generally positioned as a way to increase your security; however, &lt;a href=&#34;https://en.wikipedia.org/wiki/SMS&#34;&gt;SMS messaging&lt;/a&gt; is inherently insecure, since it is unencrypted and SMS messages are publicly broadcast wirelessly. This means that bad actors who are physically near to you can trivially snoop on 2FA codes that are texted to you.&lt;/p&gt;&#xA;&lt;p&gt;However, unencrypted message broadcasting is not the only concern here. &lt;a href=&#34;https://en.wikipedia.org/wiki/SIM_swap_scam&#34;&gt;SIM-swapping&lt;/a&gt; is another method that can allow an attacker to gain access to your SMS messages; in this sort of attack, the attacker calls your phone company, claims that they are you, and asks to swap your phone number over to their SIM card. While the FCC is &lt;a href=&#34;https://www.theverge.com/2023/7/11/23791183/fcc-sim-swapping-port-out-phone-hijacking-security-protection&#34;&gt;taking action to fight SIM-swapping&lt;/a&gt;, it is still very much a relevant attack; in fact, last month (January 2024) the U.S. Securities and Exchange Commission had its Twitter (yes, I&amp;rsquo;m still calling it Twitter) account &lt;a href=&#34;https://www.techradar.com/pro/sec-reveals-how-its-twitter-account-was-hacked-and-its-rather-embarrassing&#34;&gt;hacked&lt;/a&gt; in a SIM swap attack.&lt;/p&gt;&#xA;&lt;p&gt;To be fair, SIM swapping is not a trivial attack. It generally requires social engineering to complete, which makes it less useful for drive-by attackers. However, &lt;a href=&#34;https://www.bleepingcomputer.com/news/security/sim-swappers-hijacking-phone-numbers-in-esim-attacks/&#34;&gt;eSIM swapping&lt;/a&gt; is starting to gain popularity. This attack is less complex to complete, as it doesn&amp;rsquo;t require social engineering the phone company. You just need to get the victim&amp;rsquo;s login credentials, log into their phone provider account, and swap the eSIM.&lt;/p&gt;&#xA;&lt;h2 id=&#34;companies-dont-care&#34;&gt;Companies don&amp;rsquo;t care&lt;/h2&gt;&#xA;&lt;p&gt;You might be wondering why this is such a big problem. Sure, SMS is offered as a 2FA method on many sites, but you don&amp;rsquo;t need to enable it, right? Well, it turns out that platforms like PayPal and Amazon don&amp;rsquo;t care that SMS 2FA is insecure. If you want to set up any form of 2FA for your account, you are forced to set up SMS as a fallback option. This makes their 2FA support little more than a farce, given that anybody attacking your Amazon or PayPal account can theoretically override your 2FA by intercepting your SMS. The reasoning for this? &amp;ldquo;If you lose your 2FA device, we&amp;rsquo;ll text you a code to recover access to your account.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;(A bit more on the account recovery: given a valid email address, PayPal will happily send you a text to verify a password reset, and Amazon lets you reset your password &lt;em&gt;with just a phone number&lt;/em&gt;. This means that as long as you know that somebody has an Amazon account that may be linked to their phone number, intercepting their SMS could give you full access to their Amazon account.)&lt;/p&gt;&#xA;&lt;p&gt;Even for the services that aren&amp;rsquo;t forcing SMS 2FA down your throat, SMS is still presented as an option on many sites. Its ease of use undoubtedly has led many people to say &amp;ldquo;Oh, I&amp;rsquo;ll just use SMS since it&amp;rsquo;s easier&amp;rdquo;, which leaves those people more vulnerable to attack.&lt;/p&gt;&#xA;&lt;p&gt;Having said this, I must offer a commendation to Twitter. In 2023, they &lt;a href=&#34;https://blog.twitter.com/en_us/topics/product/2023/an-update-on-two-factor-authentication-using-sms-on-twitter&#34;&gt;disabled&lt;/a&gt; SMS 2FA for anyone not subscribed to Twitter Blue, citing its insecurity as the reason. I&amp;rsquo;d prefer if Twitter had disabled SMS 2FA across the board, but even if they left it as an incentive for people to pay for Twitter, it&amp;rsquo;s still a welcome change.&lt;/p&gt;&#xA;&lt;h2 id=&#34;it-isnt-that-hard-to-fix&#34;&gt;It isn&amp;rsquo;t that hard to fix&lt;/h2&gt;&#xA;&lt;p&gt;Now, some people might start complaining that changing their SMS 2FA deployment will be a huge technical problem. No, it won&amp;rsquo;t. Companies have several options here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use &lt;a href=&#34;https://en.wikipedia.org/wiki/Time-based_one-time_password&#34;&gt;TOTP&lt;/a&gt;; adding TOTP support is going to be fairly trivial, since it only requires an algorithm to calculate a code based on the original secret value (you&amp;rsquo;ll probably also want to throw in a QR code generator, but that&amp;rsquo;s technically optional). And it&amp;rsquo;s not like you&amp;rsquo;ll have to roll your own implemention; GitHub is full of &lt;a href=&#34;https://github.com/topics/totp-generator&#34;&gt;TOTP libraries (and apps)&lt;/a&gt;. This does require the user to install a TOTP app, but there are many trusted TOTP apps available, so that shouldn&amp;rsquo;t be a big problem.&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;a href=&#34;https://www.passkeys.com&#34;&gt;passkeys&lt;/a&gt;, the latest and greatest in 2FA security. Passkeys are (as far as I can tell) sort of a software-defined WebAuthn key that operates like a &lt;a href=&#34;https://www.yubico.com/product/security-key-series/security-key-c-nfc-by-yubico-black/&#34;&gt;hardware security key&lt;/a&gt; (as an aside, apparently Yubico stopped making these in blue). They are supported by modern browsers and phone operating systems, not to mention password managers like Bitwarden, and are generally designed to be extremely simple to use. Users generally shouldn&amp;rsquo;t need to install any extra apps to use passkeys.&lt;/li&gt;&#xA;&lt;li&gt;If your 2FA flow is hardcoded in such a way that you &lt;em&gt;have&lt;/em&gt; to use an SMS-like flow where you send the user a message containing a one-time code which the user must repeat back to you, you can at least swap SMS for a different service. Some platforms support email, but you could also use something like end-to-end encrypted &lt;a href=&#34;https://matrix.org&#34;&gt;Matrix&lt;/a&gt; chats, or you could even bake a 2FA sending service into your app the way &lt;a href=&#34;https://github.blog/2022-01-25-secure-your-github-account-github-mobile-2fa/&#34;&gt;GitHub does&lt;/a&gt;. If you &lt;em&gt;really&lt;/em&gt; can&amp;rsquo;t drop SMS, at least try to upgrade messages to &lt;a href=&#34;https://en.wikipedia.org/wiki/Rich_Communication_Services&#34;&gt;RCS&lt;/a&gt; whenever possible. (If you are using Twilio Verify, you are already being automatically &lt;a href=&#34;https://www.twilio.com/docs/verify/rcs&#34;&gt;upgraded to RCS&lt;/a&gt;.)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;what-we-can-do-about-it&#34;&gt;What we can do about it&lt;/h2&gt;&#xA;&lt;p&gt;How can we combat SMS 2FA? It turns out that there&amp;rsquo;s a surprising amount of things you can do.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Disable SMS 2FA for any account that will let you turn it off. Obviously, you&amp;rsquo;ll have to keep SMS 2FA on for Amazon and PayPal, but at least reduce your usage of SMS 2FA as much as possible.&lt;/li&gt;&#xA;&lt;li&gt;Write to companies that force SMS 2FA and ask them to remove it.&lt;/li&gt;&#xA;&lt;li&gt;Tell others about the risks of SMS 2FA. The more people know about this risk, the less the chance that one of them will fall victim to a SIM swap.&lt;/li&gt;&#xA;&lt;li&gt;If you work at a company that has SMS 2FA deployed, ask them to remove it from your product. This is especially true if you work at PayPal, Amazon, or anywhere else that could be a likely target for attackers. This includes banks (yes, my bank forces SMS 2FA, and no, I&amp;rsquo;m not happy about it).&lt;/li&gt;&#xA;&lt;li&gt;If you live in the US, write to your congressman to urge him to introduce legislation banning the use of unencrypted message-based 2FA (and mention the SEC Twitter hack to show that the government is susceptible to this attack). If you don&amp;rsquo;t live in the US, write to whoever represents you in your government (e.g. EU residents can write their MEP). While this may seem like a bit of a long shot, it&amp;rsquo;s possible that your message could result in an actual law. EU residents are especially likely to succeed, given the EU&amp;rsquo;s historical stance on legislating sane tech laws (i.e. the &lt;a href=&#34;https://en.wikipedia.org/wiki/General_Data_Protection_Regulation&#34;&gt;GDPR&lt;/a&gt; and the &lt;a href=&#34;https://en.wikipedia.org/wiki/Digital_Markets_Act&#34;&gt;DMA&lt;/a&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Share this post, or if you have your own blog, write your own post on this subject! The more publicity this issue gets, the better.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;the-part-where-i-backtrack-a-bit&#34;&gt;The part where I backtrack a bit&lt;/h2&gt;&#xA;&lt;p&gt;Having said all of this, is SMS 2FA completely evil? The answer is actually complex. First, while SMS 2FA is insecure, it is better than no 2FA at all, since it can function as a deterrent to a casual hacker. Second, it is user-friendly. TOTP, passkeys, and physical security keys all tend to be more effort to learn to use than a simple SMS verification scheme. To successfully remove SMS 2FA, we&amp;rsquo;ll have to educate the general public on how to use the alternatives or create a truly simple-to-use alternative. I do think that passkeys could become a good solution, however, as device and browser makers have put a lot of effort into making them a seamless option.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;I hope you enjoyed reading this little rant here. Hopefully, you were inspired to take action in some way to help remedy the SMS 2FA problem. If you sent a letter to your government representative or started a discussion at your company to try to remove SMS 2FA, please leave a comment below! If this post gathers enough attention and people start logging their actions below, I&amp;rsquo;ll create a page tracking all our SMS 2FA shenanigans to help keep tabs on the situation.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>rant</category><category>security</category>
    </item>
    <item>
      <title>Archiving Steam games for fun and profit</title>
      <link>https://lorendb.dev/posts/archiving-steam-games-for-fun-and-profit/</link>
      <pubDate>Wed, 03 Jan 2024 23:27:24 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/archiving-steam-games-for-fun-and-profit/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;tldr: if you want to create an archive of all your Steam games, check out &lt;a href=&#34;https://github.com/LorenDB/download-steam-games&#34;&gt;download-steam-games&lt;/a&gt; on my GitHub. It has minimal documentation, but you should be able to get it running without too much trouble.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;I recently decided it would be cool to download some beta versions of &lt;a href=&#34;https://www.kerbalspaceprogram.com/games-kerbal-space-program-2&#34;&gt;KSP2&lt;/a&gt; so I can revisit them in a year or so and laugh about how many terrible bugs there were in the original version of the game. I decided that it would make sense to download the game backup directly to my server, so I &lt;code&gt;ssh&lt;/code&gt;ed into the server, installed Steam from the system repositories, and started a new &lt;code&gt;ssh -X&lt;/code&gt; session. However, when I launched Steam, I realized that my server&amp;rsquo;s connection to the internet is much too slow to run &lt;code&gt;ssh -X&lt;/code&gt; in any sort of usable state. In fact, it was so slow that it apparently timed something out on Steam&amp;rsquo;s end and made it practically impossible to log in to Steam.&lt;/p&gt;&#xA;&lt;p&gt;Undaunted, I started looking into &lt;a href=&#34;https://developer.valvesoftware.com/wiki/SteamCMD&#34;&gt;SteamCMD&lt;/a&gt;. It turns out that while SteamCMD is intended for game server administrators, it will happily download any Steam game. It didn&amp;rsquo;t take me long to install it and use it to download a game. However, it&amp;rsquo;s a bit of a pain running through a set of instructions in SteamCMD again and again for downloading multiple games, so I set out to script it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;but-why-would-you-do-this&#34;&gt;But why would you do this?&lt;/h2&gt;&#xA;&lt;p&gt;Why wouldn&amp;rsquo;t I do this? If you think I&amp;rsquo;m crazy, just go read &lt;a href=&#34;https://www.reddit.com/r/DataHoarder/&#34;&gt;r/DataHoarder&lt;/a&gt; for a while.&lt;/p&gt;&#xA;&lt;p&gt;Seriously, while I don&amp;rsquo;t expect Steam to disappear tomorrow, there is precedent for games to be removed from the platform; many of the original games on the platform have been removed. The oldest game still available (counting by which date it was made available on Steam) is the original &lt;a href=&#34;https://store.steampowered.com/app/10&#34;&gt;Counterstrike&lt;/a&gt; with a game ID of 10. Games 1 through 9 aren&amp;rsquo;t on the platform anymore, and I&amp;rsquo;m sure they are far from the only games deleted from the platform. Archiving games allows me to avoid losing games that are removed from Steam. Also, sometimes enthusiasts like to be able to access old versions of games; one of the top posts of all time on r/DataHoarder is about &lt;a href=&#34;https://www.reddit.com/r/DataHoarder/comments/o9cnj3/one_womans_quest_to_never_delete_anything_allowed/&#34;&gt;somebody who hoarded an old Minecraft alpha&lt;/a&gt;, giving Minecraft enthusiasts a chance at taking a more complete look at the evolution of the game.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Update (2/23/2024): it turns out that I am wrong on this point. Games may be delisted from Steam, but they are not deleted. If you own the game, you can still download it. Also, since Steam numbers games by tens, the original Counterstrike &lt;em&gt;is&lt;/em&gt; the first listed game on Steam. Apologies for this!&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;a-naïve-bash-script&#34;&gt;A naïve bash script&lt;/h2&gt;&#xA;&lt;p&gt;My first approach was to create a bash script to wrap SteamCMD with a nice syntax. Of course, I also wanted to bypass the interactive prompt. Let&amp;rsquo;s go through what I ended up building:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;STEAM_ACCOUNT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GAME_NAME&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GAME_STEAM_ID&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GAME_FORCE_WINDOWS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GAME_BETA&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These variables control some basic stuff. &lt;code&gt;STEAM_ACCOUNT&lt;/code&gt; is the account you logged into SteamCMD with; while SteamCMD caches credentials, you still have to issue the &lt;code&gt;login &amp;lt;username&amp;gt;&lt;/code&gt; command every time you start it, so the script has to know what username to use. &lt;code&gt;GAME_NAME&lt;/code&gt; doesn&amp;rsquo;t have to be the actual game name; it&amp;rsquo;s just used to generate the filename for the final game archive. &lt;code&gt;GAME_STEAM_ID&lt;/code&gt; is the numerical ID of the game you want to download; for example, KSP2 is &lt;a href=&#34;https://store.steampowered.com/app/954850/Kerbal_Space_Program_2/&#34;&gt;&lt;code&gt;954850&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;GAME_FORCE_WINDOWS&lt;/code&gt; deserves a more detailed explanation. While I run Linux, some games do not have Linux builds available. For those games, I would like to save a backup of the Windows build instead. This will let me run the game via Proton later. Conveniently, SteamCMD allows you to override what platform you download the game for by setting &lt;code&gt;@sSteamCmdForcePlatformType &amp;lt;platform&amp;gt;&lt;/code&gt;. &lt;code&gt;GAME_FORCE_WINDOWS&lt;/code&gt; is used later as part of the SteamCMD script contents; therefore, if I want to add a Windows override, I can set &lt;code&gt;GAME_FORCE_WINDOWS&lt;/code&gt; to &lt;code&gt;@sSteamCmdForcePlatformType windows&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Finally, we have &lt;code&gt;GAME_BETA&lt;/code&gt;. &lt;a href=&#34;https://steamdb.info&#34;&gt;SteamDB&lt;/a&gt; shows beta version identifiers for games; you can use those to download earlier versions of some games. This is something I want to use, given that the original purpose of this project was to archive early KSP2 builds.&lt;/p&gt;&#xA;&lt;p&gt;The next bit of the script is simply logic to set each of these variables based on command line parameters. I&amp;rsquo;ve decided to skip it for brevity, as it is trivial bash logic. Moving on, we get this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat &amp;gt; ~/.download-$GAME_NAME.txt &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;&amp;lt; EOF&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$GAME_FORCE_WINDOWS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;force_install_dir $HOME/Steam/downloads/$GAME_NAME&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;login $STEAM_ACCOUNT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;app_update $GAME_STEAM_ID $GAME_BETA validate&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;quit&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;EOF&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SteamCMD can be controlled by scripts. This is how we&amp;rsquo;re going to get around its interactive interface.&lt;/p&gt;&#xA;&lt;p&gt;The first line of the script is the aforementioned platform override. After that, we use &lt;code&gt;force_install_dir&lt;/code&gt; to force SteamCMD to download the game to a known location. In this version of the script, I&amp;rsquo;ve hardcoded it to &lt;code&gt;~/Steam/downloads&lt;/code&gt;, since I&amp;rsquo;ve installed SteamCMD to &lt;code&gt;~/Steam&lt;/code&gt;. Then we log in and issue the actual command to grab the app; once the app has downloaded, we quit SteamCMD.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PREVIOUS_DIR&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;pwd&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd ~/Steam&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;~/Steam/steamcmd.sh +runscript $HOME/.download-$GAME_NAME.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd $PREVIOUS_DIR&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rm ~/.download-$GAME_NAME.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we actually execute the script and then delete it to prevent clutter.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PREVIOUS_DIR&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;pwd&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd ~/Steam/downloads&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tar --use-compress-program&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pigz -cf $PREVIOUS_DIR/$GAME_NAME.tar.gz $GAME_NAME&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd $PREVIOUS_DIR&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rm -r $HOME/Steam/downloads/$GAME_NAME&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The final step is to compress the game folder into a single &lt;code&gt;.tar.gz&lt;/code&gt; file. Again, we&amp;rsquo;re dumping the final archive into &lt;code&gt;~/Steam/downloads&lt;/code&gt; for now. The only really unusual part of this is that I&amp;rsquo;m telling &lt;code&gt;tar&lt;/code&gt; to use &lt;code&gt;pigz&lt;/code&gt; to do the gzip compression. This is because &lt;code&gt;pigz&lt;/code&gt; will use all CPU cores, which is important when you&amp;rsquo;re archiving many gigabytes of game data.&lt;/p&gt;&#xA;&lt;p&gt;This is all fine and good, but you still have to manually run the command for each game. To mitigate this, I created a &lt;a href=&#34;https://paste.segfault.foo/loren/ac1cfff462804e70bdf85fb764328b19&#34;&gt;simple script&lt;/a&gt; that read a vaguely CSV-like file which listed games to download. That was definitely better than nothing, but it still felt pretty hacky.&lt;/p&gt;&#xA;&lt;h2 id=&#34;building-it-the-right-way&#34;&gt;Building it the right way&lt;/h2&gt;&#xA;&lt;p&gt;I decided to port my two scripts into one app. I chose to write the app in &lt;a href=&#34;https://dlang.org&#34;&gt;D&lt;/a&gt;; in retrospect, Python would probably have been a good choice simply because it comes preinstalled on pretty much every distro, but I much prefer D for pretty much anything. My ported script is pretty similar to the functionality shown above; however, I added various improvements. Paths are no longer hardcoded, it&amp;rsquo;s possible to download games for any and all platforms (Windows, macOS, and Linux), and game data is stored as a JSON file instead of in a weird custom-ish format.&lt;/p&gt;&#xA;&lt;p&gt;The app, which I&amp;rsquo;m calling &lt;code&gt;download-steam-games&lt;/code&gt; (how original), is available &lt;a href=&#34;https://github.com/LorenDB/download-steam-games&#34;&gt;on GitHub&lt;/a&gt;. Building it is a simple matter of running &lt;code&gt;dub build&lt;/code&gt;. After that, you need to run &lt;code&gt;dub run -- --add-game&lt;/code&gt; to add a game to the download list. After you&amp;rsquo;ve added some games, you can use &lt;code&gt;dub run&lt;/code&gt; to run the application in download mode. It will download all your games for every platform you&amp;rsquo;ve specified, &lt;code&gt;.tar.gz&lt;/code&gt; them, and put them in the output folder of your choosing.&lt;/p&gt;&#xA;&lt;h2 id=&#34;limitations&#34;&gt;Limitations&lt;/h2&gt;&#xA;&lt;p&gt;The app currently is somewhat limited: there&amp;rsquo;s no functionality to reconfigure the settings, you can&amp;rsquo;t specify beta versions with &lt;code&gt;--add-game&lt;/code&gt;, and there&amp;rsquo;s no progress reporting during download. However, I think it&amp;rsquo;s robust enough to actually use in production. I hope to address all of these issues soon, and while I&amp;rsquo;m at it, I&amp;rsquo;d like to add a feature to add a timestamp to the archive name so you can download many versions of the same game over time.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;This is a fun project that is helping get me hooked on data hoarding. If you start using my app to help hoard your own games, please leave a comment below! I&amp;rsquo;d love seeing how many gigabytes (or terabytes) of games you&amp;rsquo;re hoarding.&lt;/p&gt;&#xA;&lt;h2 id=&#34;updates&#34;&gt;Updates&lt;/h2&gt;&#xA;&lt;p&gt;Shortly after writing this post, I&amp;rsquo;ve added some logging to the app. It ain&amp;rsquo;t pefect, but it ain&amp;rsquo;t terrible either.&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>data-hoarding</category><category>gaming</category><category>selfhosting</category>
    </item>
    <item>
      <title>Thoughts on KSP2 For Science!</title>
      <link>https://lorendb.dev/posts/thoughts-on-ksp2-for-science/</link>
      <pubDate>Tue, 26 Dec 2023 09:20:21 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/thoughts-on-ksp2-for-science/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.kerbalspaceprogram.com/games-kerbal-space-program-2&#34;&gt;Kerbal Space Program 2&lt;/a&gt; had its first&#xA;major update, called For Science!, released a week ago (as of my writing this). As a KSP nerd, I&#xA;downloaded it and ran it that day. Since then, I&amp;rsquo;ve done some messing around with it, and I thought&#xA;I&amp;rsquo;d write down my impressions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-good&#34;&gt;The good&lt;/h2&gt;&#xA;&lt;p&gt;There&amp;rsquo;s so much to love about the For Science! update. As the first Early Access milestone, it brings a new&#xA;gameplay mode called Exploration mode, which is similar to the Science mode from the original Kerbal Space&#xA;Program. And boy, has science been improved! In KSP1, science was often cumbersome; to collect all your&#xA;science points, you needed to track every time you entered a new biome and manually run all of your&#xA;experiments. In KSP2, you just add one science part to your craft (the Science Jr. is a great example) and&#xA;start flying. Science collection runs automatically for you, meaning that you won&amp;rsquo;t have to wonder if your&#xA;orbit just crossed another biome. Granted, some contracts require you to take a Kerbal on EVA and manually&#xA;make an observation, but in general, science is very passive.&lt;/p&gt;&#xA;&lt;p&gt;And speaking of contracts, that system is also improved. I haven&amp;rsquo;t personally played KSP1 in career mode&#xA;(or at least not any farther than one or two launches), but I&amp;rsquo;ve heard that KSP1 contracts were often bland:&#xA;you might be asked to launch a vehicle above 1000 meters. In KSP2, you immediately get believable contracts&#xA;like proving that you can get to space, then orbiting, then going on EVA, and so forth and so on.&lt;/p&gt;&#xA;&lt;p&gt;Another huge part of For Science! is the addition of a thermals system. The KSP2 team has created a&#xA;beautiful reentry effect. Here&amp;rsquo;s the thermals on reentry from Minmus (this was after a number of aerobraking&#xA;passes, so my actual velocity wasn&amp;rsquo;t too high):&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://lorendb.dev/images/ksp2-screenshots/for-science-reentry-heating.jpg&#34;&#xA;    alt=&#34;A capsule reentering Kerbin&amp;#39;s atmosphere at sunset&#34;&gt;&lt;figcaption&gt;&#xA;      &lt;h4&gt;Isn&amp;#39;t it just gorgeous?&lt;/h4&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;While some people have reported bugs with thermals, I&amp;rsquo;m happy to report I haven&amp;rsquo;t encountered any yet.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a lot more to cover, but I&amp;rsquo;ll cut this section short in interest of keeping this post readable.&#xA;However, I&amp;rsquo;ll at least mention a few: wobbly rockets are fixed (!!!), there are many more easter eggs (or&#xA;discoverables, as they are now called), the boat dock has had a facelift, and buoyancy works, just to name&#xA;the most obvious ones.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-bad&#34;&gt;The bad&lt;/h2&gt;&#xA;&lt;p&gt;KSP2 For Science! hasn&amp;rsquo;t been all sunshine and roses, however. There have been some bugs pointed out, but&#xA;I&amp;rsquo;ve personally experienced two of them.&lt;/p&gt;&#xA;&lt;p&gt;The first is orbital decay. In real life, given enough time, any orbit will decay to the point that the&#xA;orbiting object crashes. In KSP and KSP2, orbital decay is not implemented; players don&amp;rsquo;t want to have to&#xA;manually boost their satellites and space stations again and again. However, at release, KSP2 players found&#xA;that if you orbited the right planets or moons at a low enough altitude, your orbit would decay. The KSP2&#xA;team diagnosed and fixed the issue, or so I thought. However, my first Minmus mission in For Science!&#xA;proved otherwise. Orbital decay is still an issue. Thankfully, you can prevent orbital decay by dropping&#xA;into timewarp when possible, but in my opinion, this should be a top-priority bug for the KSP2 team.&lt;/p&gt;&#xA;&lt;p&gt;The other major bug that I&amp;rsquo;ve experienced is disappearing orbital lines. Normally, when you open the map&#xA;view, you see a line that marks your orbit. However, KSP2 has a bug that will sometimes set a craft&amp;rsquo;s&#xA;state to &amp;ldquo;Landed&amp;rdquo; when it is clearly not landed. When a craft is in the Landed state, it does not have&#xA;orbital lines drawn. I experienced this during an extremely simple mission to low Kerbin orbit and back;&#xA;there were no quicksaves or quickloads in the mission. At some point during ascent, I realized that my&#xA;orbital lines were gone. Due to the simplicity of the mission, I was able to complete it without orbital&#xA;lines, but for more complex missions, you would have to quicksave and quickload to continue. However,&#xA;sometimes reloading the game doesn&amp;rsquo;t fix the bug; in that case, you have to shut down the game and&#xA;manually edit the craft file to override the vessel state.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-ugly&#34;&gt;The ugly&lt;/h2&gt;&#xA;&lt;p&gt;I have a few things to point out here that fall into the &amp;ldquo;ugly&amp;rdquo; category. First, camera behavior is buggy.&#xA;When you switch to the map view and back, the camera always resets its view. This isn&amp;rsquo;t too bad by itself,&#xA;but the default camera view for large craft will often be clipped into the middle of the vehicle. This&#xA;means that every time you switch to the map and back, you have to zoom out a bunch to see where you even are.&lt;/p&gt;&#xA;&lt;p&gt;Second, sometimes clouds render as black during the night. I will add a massive disclaimer that I only&#xA;saw this once, and I didn&amp;rsquo;t try to reproduce it, but the fact remains that as I landed, the clouds were&#xA;completely black, which didn&amp;rsquo;t happen before For Science!&lt;/p&gt;&#xA;&lt;p&gt;The final ugly thing? Jebediah Kerman is no longer selected by default as the first kerbonaut on any&#xA;mission! What is this world coming to?!&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-takeaway&#34;&gt;The takeaway&lt;/h2&gt;&#xA;&lt;p&gt;Is KSP2 ready for mass adoption by anyone and everyone? Until orbital decay and disappearing map lines are&#xA;fixed, no. I can&amp;rsquo;t recommend a game that has such serious gameplay-affecting bugs. However, I am optimistic&#xA;that the KSP2 team is working on fixes for these and other problems. Who knows? I have a friend who&amp;rsquo;s waiting&#xA;for KSP2 to have the bugs fixed before he dives into the Kerbal universe for the first time; maybe in a few&#xA;months I can tell him that KSP2 is ready!&lt;/p&gt;&#xA;&lt;h2 id=&#34;if-you-work-on-ksp2&#34;&gt;If you work on KSP2&lt;/h2&gt;&#xA;&lt;p&gt;If any KSP2 devs read this, first, thank you for making a great game! I personally have entirely switched&#xA;from KSP1 to KSP2. However, it&amp;rsquo;d be awesome if you&amp;rsquo;d pull the right strings to get those bugs fixed. :)&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>gaming</category><category>ksp2</category>
    </item>
    <item>
      <title>Grouphosting</title>
      <link>https://lorendb.dev/posts/grouphosting/</link>
      <pubDate>Fri, 22 Dec 2023 15:32:27 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/grouphosting/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been diving into the world of selfhosting recently, but there&amp;rsquo;s a catch: it&amp;rsquo;s not just me. I&amp;rsquo;m doing it with some friends. This, of course, is fun, but I think the term &amp;ldquo;selfhosting&amp;rdquo; is a bit of a misnomer here; furthermore, I think that there is a powerful new type of selfhosting here that could change the way people selfhost.&lt;/p&gt;&#xA;&lt;h2 id=&#34;grouphosting&#34;&gt;Grouphosting&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been calling this concept &amp;ldquo;grouphosting&amp;rdquo;. What is grouphosting? It&amp;rsquo;s fairly similar to selfhosting, except there are multiple administrators. What advantages does this have? The immediately obvious answer is that server problems can (hopefully) be resolved more quickly; the more administrators you have in your group, the more likely it is that one of them will be available when the inevitable server problem occurs.&lt;/p&gt;&#xA;&lt;p&gt;However, there&amp;rsquo;s another powerful feature that is available only to grouphosters: resilient backups on a budget. In commercial environments where money is not a big problem and preserving your data is vital, sysadmins set up &lt;a href=&#34;https://www.veritas.com/information-center/3-2-1-backup-rule&#34;&gt;3-2-1 backups&lt;/a&gt; for maximum resiliency, which includes geographically separate data copies. Some companies already have multiple locations available to locate backup servers; others simply purchase storage from providers like Backblaze or AWS to give themselves a separate datacenter. However, for individual selfhosters, geographically redundant might mean &amp;ldquo;My server rack in the basement syncs to an old NAS in my attic.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;When you grouphost, you are involving multiple people who live at separate geographical locations. Therefore, even if not everybody in the group is capable of hosting a server at their place, you hopefully will end up with at least two people in your group who are capable and willing to put a server in their house. At this point, all you need to do is provide the servers and you&amp;rsquo;ve achieved resilient backups without paying a cloud hosting company!&lt;/p&gt;&#xA;&lt;p&gt;Closely related to this is the concept of load balancing. For the purposes of this blog post, let&amp;rsquo;s say you&amp;rsquo;re deploying Matrix. Matrix doesn&amp;rsquo;t need a lot of resources for an instance that will only serve one user; a few gigs of RAM and a fairly low-power CPU would suffice. However, assuming you&amp;rsquo;re selfhosting on a physical machine at your house, you probably have more than just a few gigs of RAM - even the Raspberry Pi 5 comes with a minimum of 4 GB. If you&amp;rsquo;re using an old desktop or a dedicated server, you almost certainly will have 8 GB or more. This is plenty of resources to run a Matrix instance with more than just one user. By grouphosting, you can keep your machine just to a Matrix server, but now you&amp;rsquo;ll be utilizing its extra capacity with accounts for your entire group, and in return you&amp;rsquo;ll get access to the other services the group is hosting.&lt;/p&gt;&#xA;&lt;h2 id=&#34;i-dont-trust-my-friends-on-my-server&#34;&gt;I don&amp;rsquo;t trust my friends on my server!&lt;/h2&gt;&#xA;&lt;p&gt;I understand exactly where you&amp;rsquo;re coming from. It&amp;rsquo;s good to keep integrity of your system at the forefront; if you aren&amp;rsquo;t comfortable with grouphosting, it&amp;rsquo;s your right to refuse others access to your server.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-if-i-cant-find-anybody-at-all&#34;&gt;What if I can&amp;rsquo;t find anybody at all?&lt;/h2&gt;&#xA;&lt;p&gt;That&amp;rsquo;s OK! Some people may need to just selfhost. I just think that it makes sense to grouphost when possible.&lt;/p&gt;&#xA;&lt;h2 id=&#34;i-bought-a-domain-that-is-based-around-my-name-does-the-whole-group-have-to-use-it&#34;&gt;I bought a domain that is based around my name. Does the whole group have to use it?&lt;/h2&gt;&#xA;&lt;p&gt;If they want to, sure; if they don&amp;rsquo;t want to, tell them to buy their own domain. After all, you can get domains for a lot of great TLDs (including .com, .xyz, .dev, and .foo, to name a few) for around $10 per year or less, so it won&amp;rsquo;t break the bank to share the cost of a domain.&lt;/p&gt;&#xA;&lt;p&gt;Of course, you can always host your own stuff (like a blog) under any domain you want.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ok-youve-got-me-convinced-how-should-i-start&#34;&gt;OK, you&amp;rsquo;ve got me convinced. How should I start?&lt;/h2&gt;&#xA;&lt;p&gt;If you know anybody who selfhosts, ask them, but be prepared for rejection: they may not wish to migrate to a new domain. Otherwise, talk to all your friends that you would trust with your server. Once you&amp;rsquo;ve got a few people on board, buy yourself a server or two. I personally scored a well-equipped Dell PowerEdge R520, a server rack, and a few other things for $650 on Craigslist. Sites like eBay and &lt;a href=&#34;https://www.theserverstore.com&#34;&gt;The Server Store&lt;/a&gt; are also great resources for finding cheap used servers. Of course, you could also find cheap desktop PCs instead of rackmounted servers. &lt;a href=&#34;https://servethehome.com&#34;&gt;ServeTheHome&lt;/a&gt; is an awesome resource for finding good home server hardware, and they even have a &lt;a href=&#34;https://forums.servethehome.com/index.php?forums/great-deals.8/&#34;&gt;dedicated forum section&lt;/a&gt; for sharing great deals on server hardware.&lt;/p&gt;&#xA;&lt;p&gt;Once you&amp;rsquo;ve got a server or two and have put them at people&amp;rsquo;s homes, it&amp;rsquo;s pretty similar to selfhosting. Just make sure to give the right people administrative access to your installed services. You should also probably write down some details of everything you install; that way, if anything crashes, any administrator can read your documentation to help troubleshoot the problem.&lt;/p&gt;&#xA;&lt;p&gt;Once you&amp;rsquo;re done setting up your server(s), don&amp;rsquo;t just rest on your laurels. Be on the lookout for other potential grouphosters. If you have done your grouphosting right, you should be able to loop in a new grouphoster with minimal effort. Ideally, you should even be able to put the new guy&amp;rsquo;s server to use, if he has one.&lt;/p&gt;&#xA;&lt;h2 id=&#34;fin&#34;&gt;Fin&lt;/h2&gt;&#xA;&lt;p&gt;I hope that at least some part of this made you go &amp;ldquo;yeah, that actually makes sense&amp;rdquo;. While I doubt this post will have a huge impact, it might start some other person out into their own grouphosting, and if that happens, I&amp;rsquo;m happy. :)&lt;/p&gt;&#xA;&#xA;&#xA;</description>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Hello World!</title>
      <link>https://lorendb.dev/posts/hello-world/</link>
      <pubDate>Fri, 22 Dec 2023 10:47:01 -0500</pubDate>
      <author>loren@lorendb.dev (Loren Burkholder)</author>
      <dc:creator>Loren Burkholder</dc:creator>
      <guid>https://lorendb.dev/posts/hello-world/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m setting up this blog to try to start writing down the occasional thing that might&#xA;be useful to others.&lt;/p&gt;&#xA;</description>
      
    </item>
  </channel>
</rss>
