{"generator":"Bridgetown","link":[{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/feed.xml","rel":"self","type":"application\/atom+xml"}},{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/","rel":"alternate","type":"text\/html"}}],"updated":"2026-08-09T16:49:15+00:00","id":"https:\/\/lucas.dohmen.io\/feed.xml","title":"lucas.dohmen.io","subtitle":"Homepage of Lucas Dohmen featuring a blog","author":{"name":"Lucas Dohmen"},"entry":[{"title":"Rails is done","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2026\/08\/09\/rails-is-done\/","rel":"alternate","type":"text\/html","title":"Rails is done"}},"published":"2026-08-09T00:00:00+00:00","updated":"2026-08-09T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2026-08-09-rails-is-done.md","content":"<p>If you need more context on why Rails needs a new leadership, here is <a href=\"https:\/\/davidcel.is\/articles\/rails-needs-new-governance\">David Celis summing it up a year ago<\/a>, <a href=\"https:\/\/po-ru.com\/2026\/07\/29\/it-doesnt-matter-whether-matz-is-nice\">Paul Battley a few weeks ago<\/a>, and\u2014even though it is hard to believe\u2014it even got worse since then.<\/p>\n\n<h2 id=\"how-could-a-fork-even-work\">How could a fork even work?<\/h2>\n<p>Rails is a huge code base and has multiple engineers who can invest significant work time into improving it. How could a few people fork and maintain it in their free time? Because Rails is done. Since I started to update The Rails 5 Way to Rails 6 (and then later 7 and 8), I\u2019ve been monitoring the changes to Rails closely:<\/p>\n\n<p>If you generate a Rails application with <code class=\"highlighter-rouge\">rails new --minimal<\/code>, you only get the core parts of the framework (railties, actionpack, activesupport, activemodel, activerecord and actionview). There is a lot of other stuff in Rails nowadays, but if you focus in on those parts, they are done. We haven\u2019t seen any major changes to these core gems since Rails 6.0 in 2019 (outside of ActiveRecord<sup id=\"fnref:active-record\"><a href=\"#fn:active-record\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup>) with the introduction of the Zeitwerk autoloader. What has happened since are just additional components that are just gems or npm packages you could add if you fancy them:<\/p>\n\n<ul>\n  <li>A series of asset pipelines.<\/li>\n  <li>A set of JavaScript libraries you could use with Rails, or any other Ruby\/PHP\/Java\/\u2026 framework.<\/li>\n  <li>A deployment tool that can deploy any containerized web application.<\/li>\n  <li>A reverse proxy written in Go.<\/li>\n  <li>New default gems as backends for ActiveJob, ActionCable and caching.<\/li>\n<\/ul>\n\n<p>Why is that? Because Rails is done, and that\u2019s great! It covers the needs for web apps from small to large, and there is nothing you need to add to it. So if all we fork is the core and not all the optional gems you could add on top of it, we have a stable target. The job is to monitor the Rails repository for security patches and port them to the fork as well as look for performance and smaller improvements and evaluate if we should add them. This will be challenging, but doable.<\/p>\n\n<h2 id=\"codename-amiko\">Codename: Amiko<\/h2>\n<p>Right now, we have a really nice community building up in Matrix and spikes of how the fork will work. The current name is Amiko, but it will probably change before we do the first release. If this sounds interesting to you, join the community. <a href=\"https:\/\/codeberg.org\/amiko\/governance\">You can find an entry point here.<\/a><\/p>\n\n<p>Amiko\u2019s primary goal is to provide an LTS version of Rails 8.x built and maintained by a community of people that do not tolerate nazis, transphobes, racists, or any kind of bigotry.  The idea is that if you have an existing Rails 8.x application, you switch out the gems<sup id=\"fnref:aliases\"><a href=\"#fn:aliases\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2<\/a><\/sup>, do a few search and replace operations, and your app is ready to go.  We want Amiko to be boring and friendly.<\/p>\n\n<h2 id=\"changes\">Changes<\/h2>\n<p>We might also work on some flaws in Rails if we can do that without breaking compatibility. The asset pipeline comes to mind that has been in a state of absolute chaos of a dozen half-working approaches since the core team lost their frontend knowledge <a href=\"https:\/\/www.theverge.com\/2021\/4\/30\/22412714\/basecamp-employees-memo-policy-hansson-fried-controversy\">in 2021 when Basecamp imploded<\/a>. We also discussed how <code class=\"highlighter-rouge\">amiko new<\/code> could work if it wasn\u2019t designed by someone who has not understood that if a chef serves the same meal to everyone, that\u2019s called \u201cfast food\u201d and not \u201comakase\u201d. But expect little changes to what you already know from Rails 6\/7\/8.<\/p>\n\n<h2 id=\"why-not-hanakai\">Why not Hanakai?<\/h2>\n<p><a href=\"https:\/\/ryanbigg.com\/2026\/08\/amiko-a-desperate-virtue-signalling-attempt\">Ryan Bigg wrote a blog post that disagrees with Amiko\u2019s goal<\/a>. He suggested that instead we should all join <a href=\"https:\/\/hanakai.org\">Hanakai<\/a>. And in a way we do. We join them in their mission and core values:<\/p>\n\n<blockquote>\n  <p>\u201cThe Hanakai community is a place where people of all backgrounds and experience levels can feel respected, and can share and grow. A place for people to be proud of, and feel safe within. We do not tolerate nazis, transphobes, racists, or any kind of bigotry.\u201d<\/p>\n<\/blockquote>\n\n<p>Like <a href=\"https:\/\/timriley.info\">Tim Riley<\/a>, we want Ruby to be a thriving ecosystem, not a monoculture. Please watch <a href=\"https:\/\/www.youtube.com\/watch?v=dlBiBbo3P8s\">his excellent keynote that really inspired me<\/a>. In our view, the perfect job queue works with Amiko, Hanami, and Roda. We see ourselves as another alternative among many awesome choices. And we want Amiko to be an especially great choice for everyone who has a big Rails app that they can\u2019t \u201cjust rewrite in Hanakai\u201d or that like the design of Rails, but not their leadership. If you want a completely different design than Rails, I encourage you to check out the awesome work of the Hanakai community. If you want an awesome static site generator with the option of adding server side logic, check out Bridgetown (which I use for this website, for example). And if you want to mix and match with Amiko as the web framework, Rom as your data layer and organize your business logic with Dry::Operation, you have embraced okonomi.<\/p>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>I hope we can all thrive together. We want to add a choice to the Ruby ecosystem, and join the other groups that want to build for a community of people of all genders, skin colors, sexual orientations, experience levels and backgrounds. We want nazis, transphobes and racists to run away screaming and will kick them out if they don\u2019t. And we want to continue building our web applications without feeling icky.<\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to <a href=\"https:\/\/bitboxer.de\">Bodo<\/a> and <a href=\"https:\/\/codelater.de\">Dirk<\/a> for their feedback \u2764\ufe0f<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:active-record\">\n      <p>And most of the improvements were added in 6.1 in 2020, except for at-work encryption, asynchronous query loading, some authentication methods, and composite primary keys.\u00a0<a href=\"#fnref:active-record\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:aliases\">\n      <p>With a clever <a href=\"https:\/\/codeberg.org\/amiko\/experimental-amiko-metapackage\">alias hack<\/a> so your dependencies will also use the forked gems.\u00a0<a href=\"#fnref:aliases\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"Big Tech Exit","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2026\/01\/04\/big-tech-exit\/","rel":"alternate","type":"text\/html","title":"Big Tech Exit"}},"published":"2026-01-04T00:00:00+00:00","updated":"2026-01-04T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2026-01-03-big-tech-exit.md","content":"<p>At <a href=\"https:\/\/events.ccc.de\/congress\/2025\/infos\/index.html\">39c3<\/a>, <a href=\"https:\/\/marcuwekling.de\">Marc-Uwe Kling<\/a>, together with the <a href=\"https:\/\/www.ccc.de\">CCC<\/a>, called for the <a href=\"https:\/\/di.day\">Digital Independence Day<\/a>: The idea is to use the first Sunday every month to <strong>reduce your personal dependence on big tech and billionaires<\/strong>.<\/p>\n\n<p>What I like about this approach is that it acknowledges that this is a process of many tiny steps and that a lot of people will need support. Hackerspaces in Germany will offer help to those who require it. I also really liked the idea of the \u201cplus one rule\u201d. Let\u2019s take the example of WhatsApp, the quasi-standard messenger in Germany. Instead of telling people to uninstall WhatsApp, which they will likely be unable to do, ask them to add Signal. This starts a network effect for Signal, allowing you to, one by one, move conversations from WhatsApp to Signal.<\/p>\n\n<h2 id=\"taking-inventory\">Taking Inventory<\/h2>\n\n<p>For the first Digital Independence Day, I want to check where I am at with my dependence on Big Tech and what I want to achieve this year. I\u2019m writing this on the coolest notebook I\u2019ve owned so far: the <a href=\"https:\/\/shop.mntre.com\/products\/mnt-pocket-reform\">MNT Pocket Reform<\/a>. It is open hardware and runs Debian. I also use Linux exclusively for work, most of my applications are open source and I don\u2019t use X, WhatsApp, Instagram\u2026<\/p>\n\n<figure>\n<img alt=\"The MNT Pocket Reform showing a terminal with a keyboard lightened up in the colors of the trans flag\" src=\"\/_bridgetown\/static\/pocket-reform-I3FRNKAS.avif\" width=\"1000\" height=\"1333\" loading=\"lazy\" \/>\n<\/figure>\n\n<p>But still, I have some dependencies on Big Tech. These companies are also known as the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Big_Tech#Magnificent_Seven\">\u201cMagnificent\u201d Seven<\/a>, based on the fact that they dominate the stock-market valuations. Their power over our lives is significant, and centralization of power leads to oligarchy. This is no longer theoretical, as each of the leaders of those companies kissed the ring of <a href=\"https:\/\/en.wikipedia.org\/wiki\/June_2025_No_Kings_protests\">King Trump<\/a>, so their politics are entirely clear. In an ideal world, I would not use anything made by these companies, but unfortunately that\u2019s not the case. So in the following table, I list the \u201cMagnificent\u201d Seven<sup id=\"fnref:musk\"><a href=\"#fn:musk\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup> and which of their products I\u2019m still using:<\/p>\n\n<div class=\"table-responsive\">\n  <table>\n    <thead>\n      <tr>\n        <th>Company<\/th>\n        <th>What I still use from them<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <th>Alphabet<br \/>(Google, YouTube, Nest, Waymo...)<\/th>\n        <td>YouTube<\/td>\n      <\/tr>\n      <tr>\n        <th>Amazon<br \/>(AWS, Audible, Eero, Ring...)<\/th>\n        <td>Nothing \ud83c\udf89<\/td>\n      <\/tr>\n      <tr>\n        <th>Apple<\/th>\n        <td>Apple TV (the device), iPhone, iPad, iCloud, Apple Messages &amp; FaceTime<\/td>\n      <\/tr>\n      <tr>\n        <th>Meta<br \/>(Facebook, WhatsApp, Instagram...)<\/th>\n        <td>Nothing \ud83c\udf89<\/td>\n      <\/tr>\n      <tr>\n        <th>Microsoft<\/th>\n        <td>Xbox, LinkedIn, GitHub, npm (plus 365 at Work)<\/td>\n      <\/tr>\n      <tr>\n        <th>Musk Companies<br \/>(Tesla, X, SpaceX and xAI)<\/th>\n        <td>Nothing \ud83c\udf89<\/td>\n      <\/tr>\n      <tr>\n        <th>Nvidia<\/th>\n        <td>Nothing \ud83c\udf89<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<p>It makes me quite happy to look at this table, but I still want to reduce my dependency on Apple, Microsoft, and YouTube. Let\u2019s go over it by category.<\/p>\n\n<h2 id=\"iphone-and-ipad\">iPhone and iPad<\/h2>\n<p>As a recovering Apple user (I used Macs exclusively for over 20 years), I still use my iPhone and iPad. To me, switching from iOS to Android is no improvement. <a href=\"https:\/\/media.ccc.de\/v\/froscon2025-3317-postmarketos_-_digital_independence_in_your_pocket\">Luca Weiss explained really well why Android Forks are not a sustainable solution either in his talk at FrOSCon 2025<\/a>, so I will not repeat it here. I hope to switch to mainline Linux on my mobile devices at some point \u2013 <a href=\"https:\/\/linmob.net\/2025-year-in-review\/\">the progress they made in 2025 is impressive<\/a>. GNOME is already my favorite desktop UI ecosystem, and I\u2019m sure it will become my favorite mobile one as well.<\/p>\n\n<p>I might get a <a href=\"https:\/\/shop.fairphone.com\/de\/shop\/fairphone-5-1959?color=223&amp;select=228&amp;refurbished=0\">refurbished Fairphone 5<\/a> this year to try it out, as <a href=\"https:\/\/wiki.postmarketos.org\/wiki\/Fairphone_5_(fairphone-fp5)\">PostmarketOS already supports it quite well<\/a>. I do not plan to switch this year, though.<\/p>\n\n<h2 id=\"icloud\">iCloud<\/h2>\n<p>Like most iOS users, I had a lot of data synced to iCloud. In 2025, I already made a lot of progress\nmoving things out of iCloud. I went through the storage section in the iCloud settings and deleted\nthe data I did not want to store in iCloud anymore and moved them elsewhere. It is a tedious\nprocess, as Apple Settings is slow and crashy. Some deletions I had to repeat several times. This is what\u2019s\nremaining right now that I want to move out this year:<\/p>\n\n<ul>\n  <li>Photos: The original plan was to move them to Nextcloud, but unfortunately the iOS integration is unreliable. I guess <a href=\"https:\/\/immich.app\">Immich<\/a> and <a href=\"https:\/\/ente.io\">Ente<\/a> are the other alternatives to try.<\/li>\n  <li>Notes and Reminders: Shared with my partner, we want to try Obsidian as a replacement. If it works well, I will also move my personal notes from Nextcloud Notes to Obsidian.<\/li>\n  <li>Messages: I\u2019m surprised that there are 14 GB listed here. I will research if there is a way to archive and then delete them.<\/li>\n  <li>Calendars: I moved mine as well as the ones shared with my partner to Mailbox\u2013only the calendar shared with my family is still there.<\/li>\n  <li>Contacts: I started to move them over to Mailbox, but I\u2019m cleaning up along the way. I will continue with that.<\/li>\n<\/ul>\n\n<p>What I will keep in iCloud as long as I have an iPhone\/iPad (less than 4 GB in total):<\/p>\n\n<ul>\n  <li>Passwords: Only contains wifi credentials.<\/li>\n  <li>Apps: Ice Cubes, Overcast &amp; Signal store some basic info.<\/li>\n  <li>Backup<\/li>\n<\/ul>\n\n<h2 id=\"navigation-and-planning\">Navigation and Planning<\/h2>\n<p>I recently uninstalled Google Maps and am using <a href=\"https:\/\/www.comaps.app\">CoMaps<\/a> for navigation by foot. In the car, I use its navigation system. For cycling, I will try CoMaps or a bike computer. To no longer use komoot for planning cycling tours, I want to learn <a href=\"https:\/\/bikerouter.de\">Bikerouter<\/a> instead.<\/p>\n\n<h2 id=\"communication\">Communication<\/h2>\n<p>My main messenger is Signal, by a huge margin. I might be able to convince my remaining contacts and groups on iMessage (the only Big Tech messenger I\u2019m using) to switch. Let\u2018s see.<\/p>\n\n<p>My main social network is the fediverse, and I\u2019m very happy with it. I really like having a social network not controlled by a corporation, and I like the interactions there. I\u2019m also using LinkedIn, which I\u2019m not happy about. But I consider it a necessary evil for now.<\/p>\n\n<h2 id=\"githubnpm\">GitHub\/npm<\/h2>\n<p>Except for one project, I moved the open source projects I maintain from GitHub to Codeberg, and I prefer it a lot. That last remaining project on GitHub is <a href=\"https:\/\/www.faucet-pipeline.org\">faucet-pipeline<\/a> which I maintain together with my friend FND. We will transfer that to Codeberg as well this year. I\u2019m still using GitHub for commercial projects, and it remains the main place for open-source projects in general. I remain hopeful that, step by step, the latter will change.<\/p>\n\n<p>For frontend and Node development, npm is a sad and necessary garbage fire. It needs to be replaced, but I don\u2019t see any alternative emerging.<\/p>\n\n<h2 id=\"browser-and-search-engine\">Browser and Search Engine<\/h2>\n<p>I\u2019m a longtime Firefox user (now LibreWolf, but I consider this a different default configuration and not a different browser). There are only three general-purpose browser engines (Chromium, WebKit, and Gecko), and every single one is financed by Google, directly or indirectly. That\u2019s the sad state of the web. I think Firefox (or one of its \u201cforks\u201d) is the best choice.<\/p>\n\n<p>I switched away from Google years ago and am currently using Ecosia (previously DuckDuckGo). But the truth is, there are only four search indices (Google, Bing, Baidu, and Yandex) and proxies like DuckDuckGo, Ecosia, and Startpage to access them. I\u2019m slightly hopeful for <a href=\"https:\/\/www.eu-searchperspective.com\">European Search Perspective<\/a> and <a href=\"https:\/\/openwebsearch.eu\">OpenWebSearch<\/a>\u2013until then, I will stay on Ecosia and keep an eye on alternatives.<\/p>\n\n<h2 id=\"entertainment\">Entertainment<\/h2>\n<p>At the beginning of 2025, I cancelled my Spotify subscription and switched back to buying music. I consider Spotify to be similarly evil as the Big Tech companies<sup id=\"fnref:spotify\"><a href=\"#fn:spotify\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2<\/a><\/sup>. I now mainly buy music via Bandcamp, as they pay out the biggest share to artists as far as I know.<\/p>\n\n<p>For video, it is not that simple. If a video is exclusively available on YouTube or Disney+, then you can either go there or not see it at all. In our household, our current strategy is to rotate through the subscriptions: Subscribe to Disney+, when we watched the things we wanted to see, cancel it, and subscribe to Netflix. I\u2019m not happy with it, but I guess this will still be the case one year from now. We are using an Apple TV for it, as it seems to be the least bad streaming box. However, we are watching less and prefer German public broadcasting, media.ccc.de and PeerTube over YouTube, Apple TV, Netflix, and Disney+.<\/p>\n\n<p>A long time ago, I used to be a Mac gamer. But it never completely clicked for me. A few years ago, I learned that I actually prefer playing with a controller on a comfy armchair and switched to consoles. I play solo on the Switch (Zelda franchise) and online with a friend on the Xbox (Baldur\u2019s Gate 3). I\u2019m especially annoyed that I need to pay a monthly fee to Microsoft to be allowed to play on Larian\u2019s servers.<\/p>\n\n<p>Therefore I\u2019m intrigued by <a href=\"https:\/\/store.steampowered.com\/sale\/hardware\">Valve\u2019s upcoming hardware releases<\/a>, as it might be the replacement I am searching for. Additionally, I want to set up a home server with Jellyfin for both audio and video.<\/p>\n\n<h2 id=\"additionally\">Additionally<\/h2>\n<p>Outside of reducing my personal usage of Big Tech, at work, I want to continue helping companies to move their applications out of the hyperscalers (AWS, GCP, Azure, and Cloudflare). Together with my friend <a href=\"https:\/\/codelater.de\">Dirk<\/a>, we successfully moved <a href=\"https:\/\/www.fejo.dk\">fejo.dk<\/a> completely away from AWS. Let\u2019s see if other companies want my help with that this year as well.<\/p>\n\n<p>I will continue not using LLMs. I haven\u2019t seen any productivity gains outside the error margin except for some very narrow use cases, but what I see is a big increase in the dependency on Big Tech, <a href=\"https:\/\/embracethered.com\/blog\/posts\/2025\/39c3-agentic-probllms-exploiting-computer-use-and-coding-agents\/\">horrible security problems<\/a>, suicides, stealing from artists and workers, floods of useless traffic on websites from crawlers, the web flooded by slop, misinformation and propaganda, exploitation of workers in Africa, reactivation of nuclear facilities, <a href=\"https:\/\/www.404media.co\/researchers-are-hunting-america-for-hidden-datacenters\/\">new, enormous data centers slurping up the energy<\/a> and of course the potential to pull the world economy into the abyss. And also, thanks for increasing RAM and SSD prices.<\/p>\n\n<p>Outside of Big Tech, I also see a huge problem with online payments. It is basically impossible to pay online without using MasterCard, Visa, or PayPal, providing an accurate picture of all online trading to the USA. Let\u2019s see if <a href=\"https:\/\/wero-wallet.eu\">Wero<\/a> can change that this year.<\/p>\n\n<p><a href=\"https:\/\/events.ccc.de\/congress\/2025\/hub\/event\/detail\/cuii-wie-konzerne-heimlich-webseiten-in-deutschland-sperren\">At 39c3 I learned that DNS provided by German Internet providers is under tight control of CUII<\/a>, making it an easy way of censorship. To address that, as well as invasive online advertising, I want to set up a Pi-hole for our home network backed by <a href=\"https:\/\/www.ccc.de\/censorship\/dns-howto\/\">a more trustworthy DNS resolver<\/a>.<\/p>\n\n<p>Lastly, I\u2019m using the closed-source password manager 1Password. I would prefer an open-source solution, but it seems like all of them have significant downsides. I\u2019ll keep an eye on that.<\/p>\n\n<h2 id=\"my-goals-for-2026\">My Goals for 2026<\/h2>\n<p>This year, my goals for decreasing my dependence on Big Tech are:<\/p>\n<ul>\n  <li>Leave iCloud (except for backups).<\/li>\n  <li>Set up a home server with Jellyfin and PiHole.<\/li>\n  <li>Move faucet-pipeline from GitHub to Codeberg.<\/li>\n  <li>Learn Bikerouter.<\/li>\n  <li>Try out PostmarketOS on a Fairphone 5.<\/li>\n  <li>See if I can convince a few more people to switch from iMessage to Signal.<\/li>\n  <li>Keep an eye on the new Steam Hardware, European search indices, and password managers.<\/li>\n<\/ul>\n\n<p>Let\u2019s check back on January 3. 2027 to see how far I\u2019ve gotten.<\/p>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>This post focuses on my personal goals for the year. If you also want to leave Big Tech but feel overwhelmed, my advice would be to start small. You might not feel ready to leave behind your eBooks and audiobooks on Amazon, but you can start by not ordering physical items from them. You might not feel ready to leave WhatsApp behind, but you can start by installing Signal and seeing which of your contacts are already there. If you use Gmail, you can take a first look at <a href=\"https:\/\/www.kuketz-blog.de\/empfehlungsecke\/#email-anbieter\">alternative email providers<\/a> and if you use Google, you can try out <a href=\"https:\/\/www.kuketz-blog.de\/empfehlungsecke\/#suchmaschine\">alternative search engines<\/a>. <strong>Choose one thing, and make a switch.<\/strong><\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to <a href=\"https:\/\/rohrspatz.studio\">nanoparsec<\/a>, <a href=\"https:\/\/mkhl.codeberg.page\">mkhl<\/a>, <a href=\"https:\/\/mariusscheel.de\">Marius<\/a> and FND for their feedback \u2764\ufe0f<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:musk\">\n      <p>I took the liberty of replacing Tesla with \u201cMusk Companies\u201d as they are strongly connected and I view them as a single entity.\u00a0<a href=\"#fnref:musk\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:spotify\">\n      <p>They pay so little to artists that it should be criminal. But they do instead pay right-wing podcasters. And now they want to replace artists with generated music\u2026\u00a0<a href=\"#fnref:spotify\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"Optimizing Webfonts","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2025\/10\/20\/webfonts\/","rel":"alternate","type":"text\/html","title":"Optimizing Webfonts"}},"published":"2025-10-20T00:00:00+00:00","updated":"2025-10-20T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2025-10-20-webfonts.md","content":"<p>Fonts are a critical part of web performance: Custom fonts can cause significant layout shifts, and their file size can be quite enormous. The fastest option is to bypass them completely by using <a href=\"https:\/\/modernfontstacks.com\">web-safe fonts<\/a>. However, a custom font can provide a lot of personality to a website, so many web designers consider them indispensable. So how can we make a font as small as possible to reduce its impact on web performance?<\/p>\n\n<h2 id=\"typefaces\">Typefaces<\/h2>\n\n<p>Let\u2019s use <a href=\"https:\/\/fixel.macpaw.com\">Fixel<\/a>, the font I\u2019m using on this website, as an example. Fixel comes in three variants:<\/p>\n\n<ul>\n  <li>A display variant for headings<\/li>\n  <li>A text variant for continuous text<\/li>\n  <li>A variable font<\/li>\n<\/ul>\n\n<p>Both the text and display typeface come in 9 weights in both italic and regular. To get the nomenclature right, the Fixel Display typeface consists of 18 fonts (like <code class=\"highlighter-rouge\">FixelDisplay-Bold.otf<\/code>). The variable font, on the other hand, covers the full range in a single font. This sounds like a great improvement, but it comes at a cost: The variable font has a much larger file size. Each of the fonts of the display and text typeface is around 120-140 kB. The variable font is 470 kB.<\/p>\n\n<h2 id=\"compression\">Compression<\/h2>\n\n<p>The first thing we can do is compress the font. We do this with the Web Open Font Format: WOFF2<sup id=\"fnref:woff2\"><a href=\"#fn:woff2\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup> is basically an OTF or TTF font file compressed with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Brotli\">Brotli<\/a>. Let\u2019s compare the sizes:<\/p>\n\n<div class=\"table-responsive\">\n  <table>\n    <thead>\n      <tr>\n        <th>Font<\/th>\n        <th>Uncompressed Size<\/th>\n        <th>Compressed Size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>Fixel Text Medium<\/td>\n        <td>138 kB<\/td>\n        <td>74 kB<\/td>\n      <\/tr>\n      <tr>\n        <td>Fixel Variable<\/td>\n        <td>472 kB<\/td>\n        <td>199 kB<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<p>Compression makes a much bigger difference for the variable font. So now we can say that if you use three or more fonts of this typeface, you should use the variable font instead. This is only the beginning of our optimization, though.<\/p>\n\n<h2 id=\"subsetting\">Subsetting<\/h2>\n<p>On a conceptual level, a font file maps every character to a glyph. A font might contain a lot of characters that don\u2019t actually appear on your website. Fixel, for example, contains both Latin and Cyrillic characters. This is an amazing feature if your page is available in both English and Ukrainian. But it is also an opportunity to reduce it to the characters you need on your specific website from the 600 symbols that Fixel offers in every font. That process is called subsetting.<\/p>\n\n<p>We will use the Python-based <a href=\"https:\/\/fonttools.readthedocs.io\/en\/latest\/subset\/index.html\">fonttools<\/a> for that. If you have Python and pip installed, you can install it like this:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>pip <span class=\"nb\">install <\/span>fonttools brotli uharfbuzz\n<\/code><\/pre><\/div><\/div>\n\n<p><em>Alternatively, I also prepared a <a href=\"https:\/\/codeberg.org\/moonglum\/compress\/src\/branch\/main\/fonts\">Dockerfile<\/a>.<\/em><\/p>\n\n<p>We identify the characters we need by specifying code points in Unicode or ranges thereof. This website, for example, features texts in English and German. Let\u2019s start with the 94 (printable) ASCII characters within the range <code class=\"highlighter-rouge\">0000<\/code> to <code class=\"highlighter-rouge\">007F<\/code>. This covers all letters used in the English language, as well as some common punctuation like <code class=\"highlighter-rouge\">.<\/code> and mathematical symbols like <code class=\"highlighter-rouge\">+<\/code>.  For the German language, we also require the three umlauts in upper and lower case, as well as the lower case <a href=\"https:\/\/en.wikipedia.org\/wiki\/%C3%9F\">Eszett<\/a><sup id=\"fnref:eszett\"><a href=\"#fn:eszett\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2<\/a><\/sup>:<\/p>\n\n<div class=\"highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>00C4,00D6,00DC,00DF,00E4,00F6,00FC\n<\/code><\/pre><\/div><\/div>\n\n<p>Without going all typography nerd, we need to talk about a few other characters:<\/p>\n\n<ul>\n  <li>Quotation marks: If we don\u2019t want to use ASCII quotation marks, we also need to add <a href=\"https:\/\/en.wikipedia.org\/wiki\/Quotation_mark#Summary_table\">the respective language\u2019s typographic quotation marks<\/a>. We will thus add double quotation marks for \u201cEnglish\u201d (<code class=\"highlighter-rouge\">201C<\/code> and <code class=\"highlighter-rouge\">201D<\/code>) and \u201eGerman\u201c (<code class=\"highlighter-rouge\">201E<\/code> and <code class=\"highlighter-rouge\">201F<\/code>).<\/li>\n  <li>Multiplication and division symbols: \u00d7 and \u00f7 (<code class=\"highlighter-rouge\">00D7<\/code> and <code class=\"highlighter-rouge\">00F7<\/code>)<\/li>\n  <li>The en dash and em dash<sup id=\"fnref:em-dash\"><a href=\"#fn:em-dash\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">3<\/a><\/sup>: \u2013 and \u2014 (<code class=\"highlighter-rouge\">2013<\/code> and <code class=\"highlighter-rouge\">2014<\/code>)<\/li>\n  <li>Finally, apostrophe and ellipsis will make a few people really happy: \u2019 (<code class=\"highlighter-rouge\">2019<\/code>) and \u2026 (<code class=\"highlighter-rouge\">2026<\/code>)<\/li>\n<\/ul>\n\n<p>At this point, you might ask yourself what happens if you forget a character: Will it not render? With <code class=\"highlighter-rouge\">font-family: Fixel, system-ui<\/code>, for each character not found in the first font the browser will fall back to the ones later in the list. This can lead to some quite quirky typography though:<\/p>\n\n<figure>\n<img alt=\"A sample from this website where the Fixel font is missing the German letters\" src=\"\/_bridgetown\/static\/broken-typography-NMMFLWPC.avif\" width=\"208\" height=\"44\" loading=\"lazy\" \/>\n<\/figure>\n\n<p>Let\u2019s use fonttools to subset our two fonts above to the characters we\u2019ve identified. We pass it the characters we want to keep, tell it to keep all \u201clayout features\u201d (we\u2019ll get back to that later), and to compress it to WOFF2:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>compress-fonts fonttools subset<span class=\"se\">\\<\/span>\n  <span class=\"nt\">--unicodes<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"0000-007F,00C4,00D6,00DC,00DF,00E4,00F6,00FC,201C,201D,201E,201C,00D7,00F7,2013,2014,2019,2026\"<\/span><span class=\"se\">\\<\/span>\n  <span class=\"nt\">--layout-features<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"*\"<\/span><span class=\"se\">\\<\/span>\n  <span class=\"nt\">--flavor<\/span><span class=\"o\">=<\/span>woff2<span class=\"se\">\\<\/span>\n  FixelText-Medium.otf\n<\/code><\/pre><\/div><\/div>\n\n<p>This results in significant savings:<\/p>\n\n<div class=\"table-responsive\">\n  <table>\n    <thead>\n      <tr>\n        <th>Font<\/th>\n        <th>Uncompressed Size<\/th>\n        <th>Compressed Size<\/th>\n        <th>Subsetted<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>Fixel Text Medium<\/td>\n        <td>138 kB<\/td>\n        <td>74 kB<\/td>\n        <td>29 kB<\/td>\n      <\/tr>\n      <tr>\n        <td>Fixel Variable<\/td>\n        <td>472 kB<\/td>\n        <td>199 kB<\/td>\n        <td>74 kB<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<details>\n  <summary>What about user generated content?<\/summary>\n  The subset above will work well if your page features German and English text plus the most important typography-nerd glyphs. You will run into problems when your page accepts user-generated content. This is even true if your site is completely in English, and people can leave comments in English: if a user with the last name Kr\u00fcger leaves a comment, the umlaut will not be rendered in your font of choice. An alternative is to use the full Latin-1 range: `0000-00FF`. As <a href=\"https:\/\/en.wikipedia.org\/wiki\/ISO\/IEC_8859-1#Coverage\">explained in the Wikipedia article<\/a>, this will cover a wide range of languages.\n<\/details>\n\n<h2 id=\"typograhic-features\">Typograhic Features<\/h2>\n\n<p>Modern fonts support a <a href=\"https:\/\/en.wikipedia.org\/wiki\/List_of_typographic_features\">long list of typographic features<\/a>. This goes way beyond the scope of this article. fonttools can strip down the features and comes with a <a href=\"https:\/\/fonttools.readthedocs.io\/en\/latest\/subset\/index.html\">default list of features<\/a> it will keep that works quite well. It will, for example, keep ligatures, which browsers use by default if the font provides them (as is the case for Fixel). Previously, we told fonttools to keep all features with the <code class=\"highlighter-rouge\">--layout-features=\"*\"<\/code> option. When we drop that option, we get another drastic reduction in the file sizes:<\/p>\n\n<div class=\"table-responsive\">\n  <table>\n    <thead>\n      <tr>\n        <th>Font<\/th>\n        <th>Uncompressed Size<\/th>\n        <th>Compressed Size<\/th>\n        <th>Subsetted<\/th>\n        <th>Reduced Featureset<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>Fixel Text Medium<\/td>\n        <td>138 kB<\/td>\n        <td>74 kB<\/td>\n        <td>29 kB<\/td>\n        <td>11 kB<\/td>\n      <\/tr>\n      <tr>\n        <td>Fixel Variable<\/td>\n        <td>472 kB<\/td>\n        <td>199 kB<\/td>\n        <td>74 kB<\/td>\n        <td>31 kB<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>In this article we saw how we can drastically reduce the file size of custom web fonts. We also saw the difference between a variable and non-variable font. In our case, the variable font is about three times the size of the non-variable font. We should therefore only use the variable font if we use more than two variants (combinations of font weight and italic\/non-italic).<\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to <a href=\"https:\/\/mariusscheel.de\">Marius<\/a> and <a href=\"https:\/\/prepitaph.org\">FND<\/a> for their feedback \u2764\ufe0f<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:woff2\">\n      <p>WOFF and WOFF2 basically have the same level of support, so at this point you can deliver <a href=\"https:\/\/caniuse.com\/woff2\">WOFF2 without any fallbacks<\/a>.\u00a0<a href=\"#fnref:woff2\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:eszett\">\n      <p>There is also an uppercase Eszett, but we don\u2019t need that unless we set German text in all-caps, as there is no German word that starts with an Eszett.\u00a0<a href=\"#fnref:eszett\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:em-dash\">\n      <p>You are allowed to use it even when you are not a large language model.\u00a0<a href=\"#fnref:em-dash\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"My Podcast Subscriptions in 2025","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2025\/05\/28\/podcast-subscriptions\/","rel":"alternate","type":"text\/html","title":"My Podcast Subscriptions in 2025"}},"published":"2025-05-28T00:00:00+00:00","updated":"2025-05-28T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2025-05-28-podcast-subscriptions.md","content":"<p><a href=\"https:\/\/mastodon.social\/@bitboxer\/114585300958802418\">A question by my friend Bodo<\/a> made me take another look at my podcast subscriptions. Maybe there is something interesting on my list for either Bodo or someone else.<\/p>\n\n<p>I\u2019m listening to podcasts in both English and German (which are, coincidentally, the two languages I speak). I marked the German ones with the German flag, everything else is in English.<\/p>\n\n<h2 id=\"news--politics\">News &amp; Politics<\/h2>\n\n<p>My weekly German news podcast has been <a href=\"https:\/\/lagedernation.org\">Lage der Nation<\/a>\ud83c\udde9\ud83c\uddea for quite a while, and I\u2019m a plus subscriber. Unfortunately, I think it has been declining in quality for a bit now. The number of times I\u2019ve been rolling my eyes has been steadily increasing. So I might re-evaluate that choice sometime this year.<\/p>\n\n<p><a href=\"https:\/\/pod.link\/1548604447\">The Ezra Klein Show<\/a> is focused on the USA. It is another podcast I\u2019m considering replacing, as the number of episodes I enjoy is slowly declining. But right now, there are still enough episodes I\u2019m learning something from.<\/p>\n\n<p><a href=\"https:\/\/grundgesetzlich.org\/podcast\/\">Grundgesetzlich<\/a>\ud83c\udde9\ud83c\uddea explains German constitutional rights in an interview format. It is published by the \u201cGesellschaft fuer Freiheitsrechte\u201d, an NGO I\u2019ve been donating to for some years now. I strongly recommend this podcast to Germans to learn about their rights.<\/p>\n\n<p><a href=\"https:\/\/uebermedien.de\/category\/podcast\/\">Holger Ruft An<\/a>\ud83c\udde9\ud83c\uddea covers my media critic needs, and <a href=\"https:\/\/www.klimareporter.de\/klima-update-podcast\">klima update\u00b0<\/a>\ud83c\udde9\ud83c\uddea provides me with the three most important news about the collapse of our environment every week.<\/p>\n\n<h2 id=\"luddism\">Luddism<\/h2>\n\n<p>For my dose of critical takes on the IT industry, I\u2019m listening to <a href=\"https:\/\/www.techwontsave.us\">Tech Won\u2019t Save Us<\/a>, <a href=\"https:\/\/systemcrash.info\">System Crash<\/a> and <a href=\"https:\/\/pod.link\/1730587238\">Better Offline<\/a>. I enjoy all three of them quite a lot, but would wish for a paid ad-free version of Better Offline, as the ads in that show are\u2026 bad. Awful. Distressing.<\/p>\n\n<h2 id=\"other-it\">Other IT<\/h2>\n\n<p>I just subscribed to <a href=\"https:\/\/user.space\">Urlaub im Userspace<\/a>\ud83c\udde9\ud83c\uddea which seems to be the spiritual successor to the discontinued \u201cFocus on Linux\u201d podcast. I certainly hope it is because that was my Linux and open-source podcast of choice that I enjoyed a lot. <a href=\"https:\/\/linuxafterdark.net\">Linux After Dark<\/a> has a random Linux-related topic every episode and is quite entertaining. If you\u2019re into someone going extremely deep on ThinkPads and their history, then you are probably already subscribed to <a href=\"https:\/\/thinkpad-museum.de\">ThinkPad-Museum<\/a>\ud83c\udde9\ud83c\uddea.<\/p>\n\n<p>For my updates on security, I\u2019m listening to the <a href=\"https:\/\/www.heise.de\/thema\/Passwort_Podcast\">Passwort<\/a>\ud83c\udde9\ud83c\uddea podcast as the INNOQ Security Podcast seems to be discontinued.<\/p>\n\n<p>Even though it is live-streamed, I\u2019m listening to <a href=\"https:\/\/software-architektur.tv\">Software Architektur im Stream<\/a>\ud83c\udde9\ud83c\uddea as a podcast. It covers a lot of topics broadly related to software architecture, either as a monologue by Eberhard or as an interview (I\u2019ve been a guest on the show multiple times).<\/p>\n\n<p>For web stuff, I\u2019m subscribed to <a href=\"https:\/\/offthemainthread.tech\">Off the Main Thread<\/a>, but it feels like this has been discontinued. So I\u2019m currently trying <a href=\"https:\/\/shoptalkshow.com\">ShopTalk<\/a> instead. I\u2019m also listening to <a href=\"https:\/\/www.igalia.com\/chats\">Igalia Chats<\/a>, and personally enjoy it more than ShopTalk.<\/p>\n\n<p>I\u2019m also picking single episodes from <a href=\"https:\/\/pod.link\/developer-voices\">Developer Voices<\/a> if the topic sounds interesting to me.<\/p>\n\n<h2 id=\"miscellaneous\">Miscellaneous<\/h2>\n\n<p>For my dose of cycling and outdoor, I\u2019m listening to Dima\u2019s <a href=\"https:\/\/podcastaddict.com\/podcast\/like-a-pro\/4332930\">Like a Pro<\/a> and Simon\u2019s <a href=\"https:\/\/pod.link\/1735148929\">Tour de Everywhere<\/a>. Both release very infrequently, but I enjoy every single episode.<\/p>\n\n<p><a href=\"https:\/\/www.maintenancephase.com\/\">Maintenance Phase<\/a> debunks (mainly) wellness and weight loss topics. I already learned tons from that show \u2013 highly recommended.<\/p>\n\n<p>For boardgame nerdery, I\u2019m listening to my friend Dirk\u2019s <a href=\"https:\/\/theboardgametheory.de\">BGT<\/a>\ud83c\udde9\ud83c\uddea podcast. Apart from being a really great podcast, it is also a nice community both on- and offline.<\/p>\n\n<h2 id=\"recommendations\">Recommendations<\/h2>\n\n<p>If you have any recommendations, please let me know. Given that I\u2019m listening to every episode of the podcasts listed above (except The Ezra Klein Show and Developer Voices), I\u2019m already at the maximum of subscriptions for my listening time<sup id=\"fnref:listening-time\"><a href=\"#fn:listening-time\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup>, so it would need to replace one of the above.<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:listening-time\">\n      <p>Which is, according to my Overcast statistics for 2024, a bit over an hour per day.\u00a0<a href=\"#fnref:listening-time\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"Rhythm of the Linux Desktop","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2025\/04\/15\/rhythm-of-the-linux-desktop\/","rel":"alternate","type":"text\/html","title":"Rhythm of the Linux Desktop"}},"published":"2025-04-15T00:00:00+00:00","updated":"2025-04-15T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2025-04-15-rhythm-of-the-linux-desktop.md","content":"<p>With the release of Gnome 48, I asked myself the question: When will it be available in which distro? And this led me into a tiny rabbit hole that revealed a certain rhythm in the way desktop distros are released that I wanted to share here.<\/p>\n\n<p>GNOME has a pretty fixed release schedule, with a new version every March and September<sup id=\"fnref:kde-plasma\"><a href=\"#fn:kde-plasma\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup>. The two most used desktop distros (citation needed, but if you include their downstreams, I would say this is true) are Ubuntu and Fedora. Ubuntu has GNOME as its default desktop environment. In Fedora, GNOME has the flagship status (called \u201cworkstation\u201d). With the release of Fedora 42, KDE will have the same status. Both distros decided to match their release cycle to GNOME, releasing one month after each GNOME release: every April and every October. How does that influence the other distros? For that, we first need to take a brief look at Debian.<\/p>\n\n<h2 id=\"debian\">Debian<\/h2>\n\n<p><a href=\"https:\/\/www.debian.org\">Debian<\/a> is well known for its slower release schedule. As far as I know, there is no set release schedule. But since 2007, there has been a major release every two years. Debian 13 \u201cTrixie\u201d will be released later this year with Gnome 48. In the installer, you can also choose a few other desktop environments.<\/p>\n\n<p>All packages and updates start in the experimental repository. From there, they make their way into <a href=\"https:\/\/wiki.debian.org\/DebianUnstable\">Debian Unstable<\/a> (also known as \u201cSid\u201d, a character from Toy Story). Debian Sid is a distro you can install on your devices. It is not a rolling release like Arch, as there is no release testing. After a package has been in unstable for at least 2\u201310 days and fulfills a list of other criteria, it makes its way into <a href=\"https:\/\/wiki.debian.org\/DebianTesting\">Debian Testing<\/a>. Testing is the next release of Debian (also called \u201cnext-stable\u201d) and gets a release number and name (the name is always a character from Toy Story). So the current Debian Testing<sup id=\"fnref:debian-testing\"><a href=\"#fn:debian-testing\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2<\/a><\/sup> is the Debian 13 \u201cTrixie\u201d mentioned above.<\/p>\n\n<h2 id=\"ubuntu\">Ubuntu<\/h2>\n\n<p><a href=\"https:\/\/ubuntu.com\">Ubuntu<\/a> releases follow the schema of <code class=\"highlighter-rouge\">year.month<\/code> \u2014 so the one released in April will be 25.04. In addition, each release has a code name that is an alliteration of an adjective and an animal (like \u201cPlucky Puffin\u201d). Ubuntu packages are based on Debian\u2019s unstable branch, which are synchronized every six months. Every fourth release (i.e. the April release of every even-numbered year) is an LTS release that receives updates for a longer time. The last LTS release was 24.04, so the next one will be 26.04.<\/p>\n\n<p>Ubuntu itself ships with GNOME. Ubuntu offers so-called flavours to offer different desktop environments. Each of them has its own name, like Kubuntu for KDE Plasma or Xubuntu for Xfce.\nApart from the flavours, there are also quite a few distros that use Ubuntu as their upstream. The most notable are probably:<\/p>\n\n<ul>\n  <li><a href=\"https:\/\/linuxmint.com\">Mint<\/a>, which is always based on the latest LTS and ships Cinnamon as its default. It is also quite popular among Xfce users as a more stable alternative to Xubuntu.<\/li>\n  <li><a href=\"https:\/\/system76.com\/pop\/\">Pop! OS<\/a> releases for both LTS and non-LTS. It ships its own desktop environment COSMIC, which is currently a heavily-modified GNOME. They are working on a complete rewrite in Rust that does not use GNOME.<\/li>\n  <li><a href=\"https:\/\/elementary.io\">elementary OS<\/a> is based on LTS and ships its own desktop environment Pantheon.<\/li>\n  <li><a href=\"https:\/\/zorin.com\">Zorin<\/a> is based on LTS and ships with either GNOME or XFCE, but heavily customized to make it more familiar for Windows and macOS users.<\/li>\n  <li><a href=\"https:\/\/neon.kde.org\">KDE Neon<\/a> by the KDE project is based on LTS and always ships the latest KDE Plasma versions.<\/li>\n<\/ul>\n\n<h2 id=\"fedora\">Fedora<\/h2>\n\n<p><a href=\"https:\/\/fedoraproject.org\">Fedora<\/a> releases just count up a number. The release in April will be 42. The equivalent to Debian Sid is Fedora Rawhide. Fedora does not have LTS releases, but I would argue that CentOS Stream is basically the LTS for Fedora. So far, there has been a new CentOS Stream every three years in December. CentOS Stream 10 was released in December 2024 and is supported for four years.<\/p>\n\n<p>There are two editions of Workstation starting in 42, one with GNOME and the other with KDE Plasma. The other choices, like Cinnamon, are offered as so-called \u201cspins\u201d. Additionally, there are four Atomic Desktops. They are similar to spins (each features a different Desktop environment). <a href=\"2024-12-01-silverblue.md\">I wrote a post about Silverblue, the atomic GNOME spin, if you want to learn more<\/a>. Fedora is also the base for other distros:<\/p>\n\n<ul>\n  <li><a href=\"https:\/\/nobaraproject.org\">Nobara<\/a> is Fedora with adjustments to improve it for gaming.<\/li>\n  <li><a href=\"https:\/\/universal-blue.org\">Universal Blue<\/a> is based on Atomic Desktops. Its \u201cspins\u201d are the KDE-based Aurora, GNOME-based Bluefin and gaming-focused bazzite.<\/li>\n  <li><a href=\"https:\/\/centos.org\">CentOS Stream<\/a> is upstream of\n  <a href=\"https:\/\/www.redhat.com\/en\/technologies\/linux-platforms\/enterprise-linux\">RHEL<\/a>, <a href=\"https:\/\/www.oracle.com\/linux\">Oracle Linux<\/a>, <a href=\"https:\/\/almalinux.org\">AlmaLinux<\/a>, and <a href=\"https:\/\/rockylinux.org\">RockyLinux<\/a>.<\/li>\n<\/ul>\n\n<h2 id=\"the-others\">The Others<\/h2>\n\n<ul>\n  <li><a href=\"https:\/\/archlinux.org\">Arch<\/a> is a rolling release, by the way. It serves as the basis for <a href=\"https:\/\/endeavouros.com\">EndeavourOS<\/a>, <a href=\"https:\/\/cachyos.org\">CachyOS<\/a>, <a href=\"https:\/\/manjaro.org\">Manjaro<\/a>, and <a href=\"https:\/\/store.steampowered.com\/steamos\">SteamOS<\/a>.<\/li>\n  <li><a href=\"https:\/\/alpinelinux.org\">Alpine<\/a> releases every May and December. It is also the basis for <a href=\"https:\/\/postmarketos.org\">postmarketOS<\/a>.<\/li>\n  <li><a href=\"https:\/\/www.gentoo.org\">Gentoo<\/a> is also a rolling release.<\/li>\n  <li><a href=\"https:\/\/www.opensuse.org\">openSUSE<\/a> comes in a rolling release (Tumbleweed) and stable release (Leap) flavor.<\/li>\n  <li><a href=\"https:\/\/nixos.org\">NixOS<\/a> releases every May and November.<\/li>\n<\/ul>\n\n<h2 id=\"desktop-environments\">Desktop Environments<\/h2>\n\n<p>To complete the picture, let\u2019s look at which desktop environments the most common desktop distros offer. In general, you can install any desktop environment on any distro. But, as explained above, Ubuntu offers some as \u201cflavours\u201d and Fedora offers some as \u201cspins\u201d (as well as some as Atomic Desktops, which I marked with \u269b\ufe0f). In Debian and Mint, you can choose from a list of desktop environments in the installation process:<\/p>\n\n<div class=\"table-responsive\">\n  <table>\n    <thead>\n      <tr>\n        <th>Desktop<\/th>\n        <th>Debian<\/th>\n        <th>Ubuntu<\/th>\n        <th>Mint<\/th>\n        <th>Fedora<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td><a href=\"https:\/\/www.gnome.org\">GNOME<\/a><\/td>\n        <td>Default<\/td>\n        <td><a href=\"https:\/\/ubuntu.com\/desktop\">Default<\/a><\/td>\n        <td>No<\/td>\n        <td>\n          <a href=\"https:\/\/fedoraproject.org\/workstation\">Default<\/a>\n          <a href=\"https:\/\/fedoraproject.org\/atomic-desktops\/silverblue\">\u269b\ufe0f<\/a>\n        <\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/kde.org\/plasma-desktop\">KDE Plasma<\/a><\/td>\n        <td>Yes<\/td>\n        <td><a href=\"https:\/\/kubuntu.org\">Yes<\/a><\/td>\n        <td>No<\/td>\n        <td>\n          <a href=\"https:\/\/fedoraproject.org\/kde\">Default<\/a>\n          <a href=\"https:\/\/fedoraproject.org\/atomic-desktops\/kinoite\">\u269b\ufe0f<\/a>\n        <\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/projects.linuxmint.com\/cinnamon\/\">Cinnamon<\/a><\/td>\n        <td>Yes<\/td>\n        <td><a href=\"https:\/\/ubuntucinnamon.org\">Yes<\/a><\/td>\n        <td>Default<\/td>\n        <td><a href=\"https:\/\/fedoraproject.org\/spins\/cinnamon\">Yes<\/a><\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/xfce.org\">Xfce<\/a><\/td>\n        <td>Yes<\/td>\n        <td><a href=\"https:\/\/xubuntu.org\">Yes<\/a><\/td>\n        <td>Yes<\/td>\n        <td><a href=\"https:\/\/fedoraproject.org\/spins\/xfce\">Yes<\/a><\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/mate-desktop.org\">MATE<\/a><\/td>\n        <td>Yes<\/td>\n        <td><a href=\"https:\/\/ubuntu-mate.org\">Yes<\/a><\/td>\n        <td>Yes<\/td>\n        <td><a href=\"https:\/\/fedoraproject.org\/spins\/mate\">Yes<\/a><\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/buddiesofbudgie.org\">Budgie<\/a><\/td>\n        <td>No<\/td>\n        <td><a href=\"https:\/\/ubuntubudgie.org\">Yes<\/a><\/td>\n        <td>No<\/td>\n        <td>\n          <a href=\"https:\/\/fedoraproject.org\/spins\/budgie\">Yes<\/a>\n          <a href=\"https:\/\/fedoraproject.org\/atomic-desktops\/budgie\">\u269b\ufe0f<\/a>\n        <\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/lxqt-project.org\">LXQt<\/a><\/td>\n        <td>Yes<\/td>\n        <td><a href=\"https:\/\/lubuntu.me\">Yes<\/a><\/td>\n        <td>No<\/td>\n        <td><a href=\"https:\/\/fedoraproject.org\/spins\/lxqt\">Yes<\/a><\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/www.lxde.org\/\">LXDE<\/a><\/td>\n        <td>Yes<\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td><a href=\"https:\/\/fedoraproject.org\/spins\/lxde\">Yes<\/a><\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/system76.com\/cosmic\">Cosmic<\/a><\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td>\n          <a href=\"https:\/\/fedoraproject.org\/spins\/cosmic\">Yes<\/a>\n          <a href=\"https:\/\/fedoraproject.org\/atomic-desktops\/cosmic\">\u269b\ufe0f<\/a>\n        <\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/swaywm.org\">Sway<\/a><\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td>\n          <a href=\"https:\/\/fedoraproject.org\/spins\/sway\">Yes<\/a>\n          <a href=\"https:\/\/fedoraproject.org\/atomic-desktops\/sway\">\u269b\ufe0f<\/a>\n        <\/td>\n      <\/tr>\n      <tr>\n        <td><a href=\"https:\/\/i3wm.org\">i3<\/a><\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td><a href=\"https:\/\/fedoraproject.org\/spins\/i3\">Yes<\/a><\/td>\n      <\/tr>\n      <tr>\n        <td>Miracle<\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td>No<\/td>\n        <td><a href=\"https:\/\/fedoraproject.org\/spins\/miraclewm\">Yes<\/a><\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h2 id=\"the-rhythm\">The Rhythm<\/h2>\n\n<p>Let\u2019s look at the GNOME releases, and when they were available in the distros that release twice a year:<\/p>\n\n<div class=\"table-responsive\">\n  <table>\n    <thead>\n      <th>GNOME<\/th>\n      <th>Ubuntu<\/th>\n      <th>Fedora<\/th>\n      <th>Alpine<\/th>\n      <th>NixOS<\/th>\n    <\/thead>\n    <tbody>\n      <tr>\n        <th><a href=\"https:\/\/release.gnome.org\/44\">GNOME 44<\/a><br \/>(March 2023)<\/th>\n        <td>Ubuntu 23.04<br \/>(April 2023)<\/td>\n        <td><a href=\"https:\/\/fedoramagazine.org\/whats-new-fedora-workstation-38\/\">Fedora 38<\/a><br \/>(April 2023)<\/td>\n        <td><a href=\"https:\/\/alpinelinux.org\/posts\/Alpine-3.18.0-released.html\">Alpine 3.18<\/a><br \/>(May 2023)<\/td>\n        <td>?<\/td>\n      <\/tr>\n      <tr>\n        <th><a href=\"https:\/\/release.gnome.org\/45\">GNOME 45<\/a><br \/>(September 2023)<\/th>\n        <td><a href=\"https:\/\/ubuntu.com\/blog\/ubuntu-desktop-23-10-mantic-minotaur-deep-dive\">Ubuntu 23.10<\/a><br \/>(October 2023)<\/td>\n        <td><a href=\"https:\/\/fedoramagazine.org\/whats-new-fedora-workstation-39\">Fedora 39<\/a><br \/>(November 2023)<\/td>\n        <td><a href=\"https:\/\/alpinelinux.org\/posts\/Alpine-3.19.0-released.html\">Alpine 3.19<\/a><br \/>(December 2023)<\/td>\n        <td><a href=\"https:\/\/nixos.org\/blog\/announcements\/2023\/nixos-2311\">NixOS 23.11<\/a><br \/>(November 2023)<\/td>\n      <\/tr>\n      <tr>\n        <th><a href=\"https:\/\/release.gnome.org\/46\">GNOME 46<\/a><br \/>(March 2024)<\/th>\n        <td><a href=\"https:\/\/ubuntu.com\/blog\/ubuntu-desktop-24-04-noble-numbat-deep-dive\">Ubuntu 24.04 LTS<\/a><br \/>(April 2024)<\/td>\n        <td><a href=\"https:\/\/fedoramagazine.org\/whats-new-fedora-workstation-40\/\">Fedora 40<\/a><br \/>(April 2024)<\/td>\n        <td><a href=\"https:\/\/alpinelinux.org\/posts\/Alpine-3.20.0-released.html\">Alpine 3.20<\/a><br \/>(May 2024)<\/td>\n        <td><a href=\"https:\/\/nixos.org\/blog\/announcements\/2024\/nixos-2405\">NixOS 24.05<\/a><br \/>(May 2024)<\/td>\n      <\/tr>\n      <tr>\n        <th><a href=\"https:\/\/release.gnome.org\/47\">GNOME 47<\/a><br \/>(September 2024)<\/th>\n        <td><a href=\"https:\/\/ubuntu.com\/blog\/canonical-releases-ubuntu-24-10-oracular-oriole\">Ubuntu 24.10<\/a><br \/>(October 2024)<\/td>\n        <td><a href=\"https:\/\/fedoramagazine.org\/whats-new-fedora-workstation-41\">Fedora 41<\/a><br \/>(October 2024)<\/td>\n        <td><a href=\"https:\/\/alpinelinux.org\/posts\/Alpine-3.21.0-released.html\">Alpine 3.21<\/a><br \/>(December 2024)<\/td>\n        <td><a href=\"https:\/\/nixos.org\/blog\/announcements\/2024\/nixos-2411\">NixOS 24.11<\/a><br \/>(November 2024)<\/td>\n      <\/tr>\n      <tr>\n        <th><a href=\"https:\/\/release.gnome.org\/48\">GNOME 48<\/a><br \/>(March 2025)<\/th>\n        <td><a href=\"https:\/\/canonical.com\/blog\/canonical-releases-ubuntu-25-04-plucky-puffin\">Ubuntu 25.04<\/a><br \/>(April 2025)<\/td>\n        <td><a href=\"https:\/\/fedoramagazine.org\/whats-new-fedora-workstation-42\">Fedora 42<\/a><br \/>(April 2025)<\/td>\n        <td><a href=\"https:\/\/alpinelinux.org\/posts\/Alpine-3.22.0-released.html\">Alpine 3.22<\/a><br \/>(May 2025)<\/td>\n        <td><a href=\"https:\/\/nixos.org\/blog\/announcements\/2025\/nixos-2505\">NixOS 25.05<\/a><br \/>(May 2025)<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<p>As we can see, Ubuntu and Fedora users get the GNOME release approximately one month after release (with one exception for Fedora). Alpine users get it 2-3 months after release and NixOS two months after release.<\/p>\n\n<p>Let\u2019s compare that to the arch-etype of a rolling release: <a href=\"https:\/\/linuxiac.com\/when-do-archs-users-get-the-latest-gnome-version\">According to this blog post<\/a>, Arch always waits for the .1 release of every GNOME release. It is relased roughly one month after the .0 release. If that\u2019s true, that means that <strong>Ubuntu, Fedora, and Arch users get the latest GNOME release roughly one month after the relase.<\/strong> NixOS users get it two months after the release, and Alpine users get it two to three months after the release.<\/p>\n\n<p>Seeing that most of the Ubuntu-based distros are based on LTS, and that there is an LTS release every two years, made me realize that they all have a similar rhythm to Debian<sup id=\"fnref:debian-based\"><a href=\"#fn:debian-based\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">3<\/a><\/sup>:<\/p>\n\n<ul>\n  <li>Debian 12 was released in June 2023, featuring GNOME 43. Debian 13 will be released later this year, featuring GNOME 48.<\/li>\n  <li>Ubuntu 22.04 LTS was released in April 2022, featuring GNOME 42. Ubuntu 24.04 LTS was released in April 2024, featuring GNOME 46.<\/li>\n  <li>CentOS Stream 9 was released in December 2021, featuring GNOME 40. CentOS Stream 10 was released in December 2024, featuring GNOME 47.<\/li>\n<\/ul>\n\n<p>So both <strong>Debian and Ubuntu LTS have released every two years, and CentOS Stream every three years<\/strong>. Both Debian 12 and CentOS Stream 9 did not release with the latest GNOME release, but with the one before that.<\/p>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>If you are using GNOME, you will get the newest version of GNOME roughly one month after the release in Ubuntu, Fedora, and Arch. You get it slightly later in NixOS and Alpine. With Debian and Ubuntu LTS, you get a new version every two years, which means roughly every fourth GNOME release. With CentOS Stream, it is every three years, so roughly every sixth release.<\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to <a href=\"https:\/\/mkhl.codeberg.page\">mkhl<\/a> for their feedback \u2764\ufe0f<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:kde-plasma\">\n      <p>KDE Plasma releases every February, June, and October since 6.0 (but plans to switch <a href=\"https:\/\/community.kde.org\/Schedules\/Plasma_6\">to two a year at some point<\/a>).\u00a0<a href=\"#fnref:kde-plasma\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:debian-testing\">\n      <p><a href=\"https:\/\/tinkerbetter.tube\/w\/vfvH2FhaXuVYwiUv4R1pYv\">Veronica Explains made a great video about Debian Testing<\/a>, and her recommendations about it.\u00a0<a href=\"#fnref:debian-testing\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:debian-based\">\n      <p>Which also makes me think that for a lot of Ubuntu LTS based Distros, could choose\nDebian as their base instead with no real disadvantage. But without needing to roll back things\nlike Snaps for it. Mint already started that effort with\n<a href=\"https:\/\/linuxmint.com\/download_lmde.php\">LMDE<\/a>.\u00a0<a href=\"#fnref:debian-based\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"Toolbx (with Arch, btw)","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2024\/12\/22\/toolbx\/","rel":"alternate","type":"text\/html","title":"Toolbx (with Arch, btw)"}},"published":"2024-12-22T00:00:00+00:00","updated":"2024-12-22T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2024-12-22-toolbx.md","content":"<p><a href=\"\/posts\/2024\/12\/01\/silverblue\/\">As I outlined in my previous post, I am currently using Fedora Silverblue on my personal notebook to try out an immutable Linux distribution.<\/a> The most significant difference to my previous setup is how the command line experience changes. A huge part of my work is happening in terminals, so this is important to me.<\/p>\n\n<h2 id=\"installing-toolbx-and-ptyxis\">Installing Toolbx and Ptyxis<\/h2>\n\n<p>Fedora Silverblue ships with Podman and Toolbx (both part of the <a href=\"https:\/\/github.com\/containers\">containers project<\/a>) pre-installed: After I tried out the combination of Toolbx and Ptyxis on my personal laptop, I\u2019m now also using it on my work machine, which is running Debian 12.<\/p>\n\n<p>I installed them like this:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">sudo <\/span>apt <span class=\"nb\">install <\/span>podman-toolbox\nflatpak <span class=\"nb\">install <\/span>flathub app.devsuite.Ptyxis\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"toolbx\">Toolbx<\/h2>\n\n<p>I\u2019ve chosen Arch as the distro inside of Toolbx for a simple reason: It has a vast number of up-to-date packages<sup id=\"fnref:xe\"><a href=\"#fn:xe\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup>. I did not expect it, but I\u2019m really impressed by the speed of pacman compared to apt. It is a bit unusual for me, as some packages are either not installed by default or have been split into multiple packages. But I really like it so far. So how does this work?<\/p>\n\n<p>If you ran a container via Podman both package management and interaction with the host would be a bit awkward. Toolbx is a wrapper around Podman that helps you with that. The team provides container images for a few distros, you can get them from Red Hat\u2019s container registry <a href=\"https:\/\/quay.io\">Quay<\/a>. So we will choose the Arch container:<sup id=\"fnref:arch-containerfile\"><a href=\"#fn:arch-containerfile\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2<\/a><\/sup><\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>toolbox create <span class=\"nt\">--image<\/span> quay.io\/toolbx\/arch-toolbox\n<\/code><\/pre><\/div><\/div>\n\n<p>You can now enter the container with:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>toolbox enter arch-toolbox\n<\/code><\/pre><\/div><\/div>\n\n<p>Now, we are running on Arch Linux and can install packages via <code class=\"highlighter-rouge\">pacman<\/code>. I prefer the fish shell and the vim editor, so here we go:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>pacman <span class=\"nt\">-Syu<\/span> fish vim\n<\/code><\/pre><\/div><\/div>\n\n<p>Don\u2019t ask me who thought this would be a reasonable user interface for a package manager. But we have fish and vim installed! Type <code class=\"highlighter-rouge\">fish<\/code> and enter the wonderful world of the friendly interactive shell. Now, install all the other tools you want to use on the command line (<a href=\"https:\/\/codeberg.org\/moonglum\/dotfiles#other-commandline-tools\">this is my list of CLI tools<\/a>). You can also run only a single command in the container from your host:<\/p>\n\n<div class=\"highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>toolbox run pacman -Syu bat\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"ptyxis\">Ptyxis<\/h2>\n\n<p>Now, it would be a bit awkward to first run <code class=\"highlighter-rouge\">toolbox enter<\/code> and then <code class=\"highlighter-rouge\">fish<\/code> in every terminal window we open. This is why Ptyxis, the pre-installed terminal emulator on Silverblue, offers an integration with Toolbx. Open the settings, and click on profiles. Create a new profile (I called mine Arch, btw), select your preferred color scheme, and then scroll down to \u201cDefault Container\u201d. Here, you can select the <code class=\"highlighter-rouge\">arch-toolbox<\/code> container we created. If you, like me, want to use fish, go to the Shell section and check \u201cUse Custom Command\u201d and enter <code class=\"highlighter-rouge\">\/usr\/bin\/fish<\/code>. Done! Now open a new tab with the Arch profile, and you will be in your Arch toolbox in fish. Beautiful!<\/p>\n\n<p><img alt=\"Screenshot of the Ptyxis profile settings showing the option to set the default container and default command\" src=\"\/_bridgetown\/static\/ptyxis-settings-CMOD7FV2.avif\" width=\"280\" height=\"190\" \/><\/p>\n\n<p>I created a second profile called System. It uses \u201cMy computer\u201d as the default container. This way, I can open a new tab for when I need to do something outside the toolbox. This has been very rare so far, so my default profile is the Arch one. I also chose a different color scheme for this other profile so I am able to tell them apart.<\/p>\n\n<h2 id=\"podman--breaking-out-of-the-box\">Podman &amp; Breaking out of the box<\/h2>\n\n<p>It is important to note that this container is not as isolated from the base system as containers typically are. This is not a security feature. It makes it super easy to wipe it, and start from scratch, though. And we can benefit from a tight integration with the host: It is slightly mind-blowing to me that tools like <code class=\"highlighter-rouge\">wl-copy<\/code> and <code class=\"highlighter-rouge\">wl-paste<\/code> work between the host and the container.<\/p>\n\n<p>You can also run commands on the host from within your toolbox with <code class=\"highlighter-rouge\">flatpak-spawn<\/code>. I\u2019ve created several aliases. I only create them when running inside of a Toolbx by testing if the file <code class=\"highlighter-rouge\">\/run\/.toolboxenv<\/code> exists. Here are a few examples:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if <\/span><span class=\"nb\">test<\/span> <span class=\"nt\">-f<\/span> <span class=\"s2\">\"\/run\/.toolboxenv\"<\/span>\n  <span class=\"nb\">alias <\/span><span class=\"nv\">podman<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"flatpak-spawn --host podman\"<\/span>                                                                                                                          \n  <span class=\"nb\">alias <\/span><span class=\"nv\">flatpak<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"flatpak-spawn --host flatpak\"<\/span>\n  <span class=\"nb\">alias <\/span><span class=\"nv\">firefox<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"flatpak-spawn --host firefox\"<\/span>\n  <span class=\"nb\">alias <\/span><span class=\"nv\">gsettings<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"flatpak-spawn --host gsettings\"<\/span>\n  <span class=\"c\"># ...<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This also allows me to start containers from my toolbox. As the toolbox is a Podman container itself, they are running alongside it on the host. I always run Postgres in a container, so I don\u2019t need to change anything about my workflow<sup id=\"fnref:pg-function\"><a href=\"#fn:pg-function\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">3<\/a><\/sup>:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>podman run <span class=\"nt\">-p<\/span> 5432:5432 <span class=\"nt\">--rm<\/span> docker.io\/library\/postgres\n<\/code><\/pre><\/div><\/div>\n\n<p>Now, it can be accessed by the Ruby and Node apps I run in the toolbox. It is kinda cool to me that when I run <code class=\"highlighter-rouge\">podman ps<\/code> I see both containers running side by side now.<\/p>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>I thoroughly enjoy this setup. With Toolbx, I can use the up-to-date packages from Arch on a stable base system. If I mess something up, I can throw away the container and start from scratch.  Ptyxis (even though difficult to type) makes Toolbx seamless.<\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to <a href=\"https:\/\/bascht.com\">Bascht<\/a> and <a href=\"https:\/\/mkhl.codeberg.page\">mkhl<\/a> for their feedback \u2764\ufe0f<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:xe\">\n      <p>The only tool I\u2019m missing compared to nix is <a href=\"https:\/\/git.vuxu.org\/xe\/about\">xe<\/a>. <a href=\"https:\/\/aur.archlinux.org\/packages\/xe\">It is only\navailable via AUR<\/a>, and I didn\u2019t go down that road yet.\u00a0<a href=\"#fnref:xe\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:arch-containerfile\">\n      <p>You can see the <a href=\"https:\/\/github.com\/containers\/toolbox\/tree\/main\/images\/arch\">source for that container here<\/a>\u00a0<a href=\"#fnref:arch-containerfile\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:pg-function\">\n      <p><a href=\"https:\/\/codeberg.org\/moonglum\/dotfiles\/src\/branch\/main\/config\/fish\/functions\/pg.fish\">I use a small script for running Postgres<\/a>\u00a0<a href=\"#fnref:pg-function\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"Going immutable","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2024\/12\/01\/silverblue\/","rel":"alternate","type":"text\/html","title":"Going immutable"}},"published":"2024-12-01T00:00:00+00:00","updated":"2024-12-01T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2024-12-01-silverblue.md","content":"<p>My friend <a href=\"https:\/\/mkhl.codeberg.page\">mkhl<\/a> told me about the <a href=\"https:\/\/blogs.gnome.org\/adrianvovk\/2024\/10\/25\/a-desktop-for-all\">initiative to turn Gnome OS into a general-purpose distro<\/a>. This pushed all my buttons as it combined the <a href=\"https:\/\/0pointer.net\/blog\/fitting-everything-together.html\">excellent proposal by Lennart Poettering for an immutable distro<\/a> with my favorite desktop environment, Gnome. At this point, Gnome OS is a bit too early stage for me, so I wanted to try out a similar approach for now and landed on <a href=\"https:\/\/fedoraproject.org\/atomic-desktops\/silverblue\">Fedora Silverblue<\/a>.<\/p>\n\n<p>Silverblue is also an immutable distro, but in a slightly different style. Like Gnome OS, it is built on Gnome and Flatpak for its desktop environment and its command-line experience is very close to what Gnome OS works like: Podman and Toolbx are pre-installed. But it also offers to layer on packages with the package manager dnf. So my current experiment is to do my personal computering on Silverblue, but without using its capabilities to use dnf to install packages. I\u2019m doing this on my private notebook to try it out and keep my setup on my work machine for now.<\/p>\n\n<h2 id=\"where-im-coming-from\">Where I\u2019m coming from<\/h2>\n\n<p>I was using Debian Stable with Gnome. For the GUI, I used Flatpak to get the latest versions of the GUI apps I use. On the command line, I used <a href=\"https:\/\/www.jetify.com\/devbox\">devbox<\/a> to get the latest version of command-line tools and programming languages (it is a nice wrapper around nix-shell). This is the setup I\u2019m still running for my work machine.<\/p>\n\n<h2 id=\"gui\">GUI<\/h2>\n<p>The most significant change for me in the GUI is that I now have the latest version of Gnome. I really love Gnome. It is beautiful, and the ecosystem of apps is absolutely impressive. With the latest version, it has become even more refined, and I really like the new default font Input. I can also finally use mkhl\u2019s <a href=\"https:\/\/extensions.gnome.org\/extension\/7423\/window-desaturation\">Window Desaturation<\/a>.<\/p>\n\n<p>Silverblue sticks with the recommendations from Gnome: The pre-installed apps are the <a href=\"https:\/\/apps.gnome.org\">core apps<\/a>. There are only a few places where Silverblue diverges:<\/p>\n\n<ul>\n  <li>Firefox is installed instead of Gnome Web (as most distros do)<\/li>\n  <li>Ptyxis instead of Console (which I will describe in detail in a second post)<\/li>\n  <li>Fedora Media Writer is additionally installed<\/li>\n  <li>Document Scanner, Music, and Videos are missing<\/li>\n<\/ul>\n\n<p>If you are interested in what else I\u2019m using, see my <a href=\"https:\/\/codeberg.org\/moonglum\/dotfiles#gui\">dotfiles for details<\/a>.<\/p>\n\n<p>With a few exceptions<sup id=\"fnref:not-flatpak\"><a href=\"#fn:not-flatpak\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup>, all pre-installed applications are installed with Flatpak. This is also how I installed the additional applications. This is why there is no difference for me here, as this is how I did it on Debian as well.<\/p>\n\n<h2 id=\"problems\">Problems<\/h2>\n<p>At first, I couldn\u2019t get Silverblue to install firmware updates. On Debian, I install them via Gnome Software, which works flawlessly for me. Silverblue also shows me the firmware updates there, but clicking on \u201cUpdate\u201d simply does nothing. I then tried it with <code class=\"highlighter-rouge\">fwupdmgr<\/code> and could install the updates without any issues. This is weird to me, as both are based on <a href=\"https:\/\/fwupd.org\/lvfs\/docs\/users\">fwupd<\/a>.<\/p>\n\n<p>I had an odd glitch in Ptyxis with some lines drawn on the screen. <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/gtk\/-\/issues\/7161\">I reported it, and it turns out\nto be a problem with fractional scaling<\/a>.\nSilberblue defaulted to a scale of 125% on my notebook. I now set it to 100% as I prefer to have\nmore space anyway.<\/p>\n\n<p>The disadvantage of the immutable approach is that I can\u2019t use GUI apps that are not available as flatpaks. For me, that\u2019s only the <a href=\"https:\/\/codeberg.org\/mkhl\/gnome-summoner\">Gnome Summoner<\/a>. I hope we can find a solution for that.<\/p>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>At this point, I think that Gnome is a really solid choice for many people and is definitely not restricted to programmers and ops people. I think a Gnome-based immutable distro is a really promising idea for people who don\u2019t want to fiddle around with this part of their OS. Having Gnome Software gives people a similar \u201cApp Store\u201d experience they know from their smartphone or desktop OSes like macOS or Chrome OS, but without being at the mercy of big tech.<\/p>\n\n<p>Moreover, it offers a really convincing command-line experience with podman and toolbx for those that need it. <a href=\"\/posts\/2024\/12\/22\/toolbx\">I wrote a separate blog post about that.<\/a><\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to <a href=\"https:\/\/bascht.com\">Bascht<\/a> and <a href=\"https:\/\/mkhl.codeberg.page\">mkhl<\/a> for their feedback \u2764\ufe0f<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:not-flatpak\">\n      <p>The exceptions are Disks, Files, Help, Settings, Software, System Monitor, Tour, Firefox, and Ptyxis.\u00a0<a href=\"#fnref:not-flatpak\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"Engine Failure: Array is gone - can we recover?","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2024\/02\/11\/engine-failure\/","rel":"alternate","type":"text\/html","title":"Engine Failure: Array is gone - can we recover?"}},"published":"2024-02-11T00:00:00+00:00","updated":"2024-02-11T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2024-02-11-engine-failure.md","content":"<p>Due to a severe engine failure, our JavaScript engine has lost arrays. They are gone. With the help\nof historical usage of arrays, we will try to recover from this. Is it possible? Or will future\ngenerations need to live without arrays?<\/p>\n\n<p>In our historical notes, we find the following snippet:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[];<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">];<\/span> <span class=\"c1\">\/\/ returns \"hello\"<\/span>\n<span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">2<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">]);<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Unfortunately, we can\u2019t restore syntax, so creating arrays via bracket notation (i.e. <code class=\"highlighter-rouge\">list = []<\/code>)\nwon\u2019t work. Let\u2019s try this instead:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{};<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">];<\/span> <span class=\"c1\">\/\/ returns \"hello\"<\/span>\n<span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">2<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">]);<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Phew, that works. But why? A JavaScript object is a collection of key-value pairs (AKA map or\ndictionary) with arbitrary values; keys are always strings, though<sup id=\"fnref:symbols\"><a href=\"#fn:symbols\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup>.\nEverything that you put into the square brackets will be converted to a string automatically:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{};<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">0<\/span><span class=\"dl\">\"<\/span><span class=\"p\">];<\/span> <span class=\"c1\">\/\/ returns \"hello\"<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">1<\/span><span class=\"dl\">\"<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">];<\/span> <span class=\"c1\">\/\/ returns \"world\"<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>There\u2019s also a shorthand for accessing properties. If the property name is a valid\nidentifier<sup id=\"fnref:valid-identifier\"><a href=\"#fn:valid-identifier\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2<\/a><\/sup>, you can use dot notation for reading or writing values:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{};<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">score<\/span><span class=\"dl\">\"<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">100<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">.<\/span><span class=\"nx\">score<\/span><span class=\"p\">;<\/span> <span class=\"c1\">\/\/ returns 100<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Okay, further research reveals that there was a <code class=\"highlighter-rouge\">length<\/code> property regularly used for looping over\nthe array:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[]<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span>\n<span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"nx\">list<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">])<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Where did this come from? And who updated it? Weird. Let\u2019s set it manually:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{};<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">2<\/span><span class=\"p\">;<\/span>\n\n<span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"nx\">list<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">]);<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>That works. But can we calculate it instead? Whenever we add something to our list, we need to\nupdate it. And who has time for that? What if we, instead, calculate the length? We can iterate over\nthe keys of an Object:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">key<\/span> <span class=\"k\">in<\/span> <span class=\"nx\">list<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">key<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will output <code class=\"highlighter-rouge\">\"0\"<\/code>, <code class=\"highlighter-rouge\">\"1\"<\/code>, <code class=\"highlighter-rouge\">\"length\"<\/code> and <code class=\"highlighter-rouge\">\"forEach\"<\/code>.<sup id=\"fnref:order\"><a href=\"#fn:order\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">3<\/a><\/sup>\nIn our iteration, we will first parse the key. It will either be an integer<sup id=\"fnref:integer\"><a href=\"#fn:integer\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">4<\/a><\/sup>\nor <code class=\"highlighter-rouge\">NaN<\/code>. We will compare it to the highest index we found so far (comparing with <code class=\"highlighter-rouge\">NaN<\/code> will always\nreturn <code class=\"highlighter-rouge\">false<\/code>):<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">key<\/span> <span class=\"k\">in<\/span> <span class=\"nx\">list<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">let<\/span> <span class=\"nx\">index<\/span> <span class=\"o\">=<\/span> <span class=\"nf\">parseInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">key<\/span><span class=\"p\">);<\/span>\n  <span class=\"k\">if <\/span><span class=\"p\">(<\/span><span class=\"nx\">index<\/span> <span class=\"o\">&gt;<\/span> <span class=\"nx\">highestIndex<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">highestIndex<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">index<\/span><span class=\"p\">;<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Our length will be the highest index plus 1. So let\u2019s define it like this:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n  <span class=\"na\">length<\/span><span class=\"p\">:<\/span> <span class=\"kd\">function<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kd\">let<\/span> <span class=\"nx\">highestIndex<\/span> <span class=\"o\">=<\/span> <span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n\n    <span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">key<\/span> <span class=\"k\">in<\/span> <span class=\"k\">this<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kd\">let<\/span> <span class=\"nx\">index<\/span> <span class=\"o\">=<\/span> <span class=\"nf\">parseInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">key<\/span><span class=\"p\">);<\/span>\n      <span class=\"k\">if <\/span><span class=\"p\">(<\/span><span class=\"nx\">index<\/span> <span class=\"o\">&gt;<\/span> <span class=\"nx\">highestIndex<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"nx\">highestIndex<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">index<\/span><span class=\"p\">;<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"k\">return<\/span> <span class=\"nx\">highestIndex<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">};<\/span>\n\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n\n<span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"nx\">list<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">]);<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Okay, <code class=\"highlighter-rouge\">length<\/code> works, though we can be a bit more concise with <code class=\"highlighter-rouge\">length() { \u2026 }<\/code> instead of <code class=\"highlighter-rouge\">length:\nfunction () { \u2026 }<\/code>. This works, but you\u2019d have to invoke <code class=\"highlighter-rouge\">list.length()<\/code> instead of just using\n<code class=\"highlighter-rouge\">list.length<\/code>. But JavaScript got us covered. We can define a getter:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">get<\/span> <span class=\"nf\">length<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n    <span class=\"c1\">\/\/...<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now we can use <code class=\"highlighter-rouge\">list.length<\/code> in our for loop.<\/p>\n\n<p>It appears people had different ways of doing things with arrays. So there was also another way of\niterating over arrays:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[];<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n\n<span class=\"nx\">list<\/span><span class=\"p\">.<\/span><span class=\"nf\">forEach<\/span><span class=\"p\">(<\/span><span class=\"kd\">function<\/span><span class=\"p\">(<\/span><span class=\"nx\">item<\/span><span class=\"p\">,<\/span> <span class=\"nx\">index<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">item<\/span><span class=\"p\">,<\/span> <span class=\"nx\">index<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">})<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We can solve this! Let\u2019s add a <code class=\"highlighter-rouge\">forEach<\/code> function to our makeshift array:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nf\">forEach<\/span><span class=\"p\">(<\/span><span class=\"nx\">fn<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"nf\">fn<\/span><span class=\"p\">(<\/span><span class=\"k\">this<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">],<\/span> <span class=\"nx\">i<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">},<\/span>\n\n  <span class=\"kd\">get<\/span> <span class=\"nf\">length<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span> <span class=\"cm\">\/* ... *\/<\/span> <span class=\"p\">}<\/span>\n<span class=\"p\">};<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now, our historians are a bit worried that we would need to define <code class=\"highlighter-rouge\">length<\/code> and <code class=\"highlighter-rouge\">forEach<\/code> on each of\nour lists compared to the olden days. Let\u2019s get another tool out of our JavaScript tool belt:\nPrototypes! We define an Object <code class=\"highlighter-rouge\">list<\/code> that we will use as a prototype for all our lists. We will\nadd our two methods, <code class=\"highlighter-rouge\">length<\/code> and <code class=\"highlighter-rouge\">forEach<\/code> to it:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nf\">forEach<\/span><span class=\"p\">(<\/span><span class=\"nx\">fn<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"c1\">\/\/...<\/span>\n  <span class=\"p\">},<\/span>\n\n  <span class=\"kd\">get<\/span> <span class=\"nf\">length<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n    <span class=\"c1\">\/\/...<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">};<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now, we can create our list like this:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">Object<\/span><span class=\"p\">.<\/span><span class=\"nf\">create<\/span><span class=\"p\">(<\/span><span class=\"nx\">list<\/span><span class=\"p\">);<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<span class=\"nx\">list<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n\n<span class=\"nx\">list<\/span><span class=\"p\">.<\/span><span class=\"nf\">forEach<\/span><span class=\"p\">(<\/span><span class=\"nf\">function <\/span><span class=\"p\">(<\/span><span class=\"nx\">item<\/span><span class=\"p\">,<\/span> <span class=\"nx\">i<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nf\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">item<\/span><span class=\"p\">,<\/span> <span class=\"nx\">i<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This works because JavaScript looks up properties via the prototype chain: <code class=\"highlighter-rouge\">Object.create<\/code>\nestablished <code class=\"highlighter-rouge\">list<\/code> as the prototype of the newly generated <code class=\"highlighter-rouge\">list<\/code> here.<\/p>\n\n<p>We can also provide a constructor function to hide the weird <code class=\"highlighter-rouge\">Object.create<\/code> invocation from\ndevelopers working with our list. To avoid clashes in the unexpected case of our engineers\nrecovering arrays, let\u2019s call our replacement <code class=\"highlighter-rouge\">List<\/code> for now:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">function<\/span> <span class=\"nf\">List<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">return<\/span> <span class=\"nb\">Object<\/span><span class=\"p\">.<\/span><span class=\"nf\">create<\/span><span class=\"p\">(<\/span><span class=\"nx\">list<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>But something is wrong. We build our list with <code class=\"highlighter-rouge\">let list = List()<\/code>. Our historians report that arrays\nwere created with <code class=\"highlighter-rouge\">let list = new Array()<\/code>. But worry not! <code class=\"highlighter-rouge\">new<\/code> requires us to structure the code a\nbit differently:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">function<\/span> <span class=\"nf\">List<\/span><span class=\"p\">()<\/span> <span class=\"p\">{}<\/span>\n\n<span class=\"nx\">List<\/span><span class=\"p\">.<\/span><span class=\"nx\">prototype<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nf\">forEach<\/span><span class=\"p\">(<\/span><span class=\"nx\">fn<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"c1\">\/\/...<\/span>\n  <span class=\"p\">},<\/span>\n\n  <span class=\"kd\">get<\/span> <span class=\"nf\">length<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n    <span class=\"c1\">\/\/...<\/span>\n  <span class=\"p\">},<\/span>\n<span class=\"p\">};<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now it works as expected. We can be even more concise:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">class<\/span> <span class=\"nc\">List<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nf\">forEach<\/span><span class=\"p\">(<\/span><span class=\"nx\">fn<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">;<\/span> <span class=\"nx\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"nf\">fn<\/span><span class=\"p\">(<\/span><span class=\"k\">this<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">],<\/span> <span class=\"nx\">i<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"kd\">get<\/span> <span class=\"nf\">length<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kd\">let<\/span> <span class=\"nx\">highestIndex<\/span> <span class=\"o\">=<\/span> <span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n\n    <span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">key<\/span> <span class=\"k\">in<\/span> <span class=\"k\">this<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kd\">let<\/span> <span class=\"nx\">index<\/span> <span class=\"o\">=<\/span> <span class=\"nf\">parseInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">key<\/span><span class=\"p\">);<\/span>\n      <span class=\"k\">if <\/span><span class=\"p\">(<\/span><span class=\"nx\">index<\/span> <span class=\"o\">&gt;<\/span> <span class=\"nx\">highestIndex<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"nx\">highestIndex<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">index<\/span><span class=\"p\">;<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"k\">return<\/span> <span class=\"nx\">highestIndex<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>At last, we\u2019ve restored most of the core behavior of arrays from the days of yore.\nCreating the list is still a bit annoying, though. Our historians found that, apart from the literal\nsyntax, there was also the option to build an array with <code class=\"highlighter-rouge\">Array.of<\/code> like this:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">let<\/span> <span class=\"nx\">list<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">Array<\/span><span class=\"p\">.<\/span><span class=\"k\">of<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">hello<\/span><span class=\"dl\">\"<\/span><span class=\"p\">,<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">world<\/span><span class=\"dl\">\"<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>There is a mysterious object called arguments that contains all the arguments provided to a\nfunction. It is not an array, but it feels a bit like an array (like our list). We can use our old\nfriend, the <code class=\"highlighter-rouge\">for in<\/code> loop again:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">class<\/span> <span class=\"nc\">List<\/span> <span class=\"p\">{<\/span>\n\t<span class=\"kd\">static<\/span> <span class=\"k\">of<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n\t\t<span class=\"kd\">let<\/span> <span class=\"nx\">target<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">List<\/span><span class=\"p\">();<\/span>\n\n\t\t<span class=\"k\">for <\/span><span class=\"p\">(<\/span><span class=\"kd\">let<\/span> <span class=\"nx\">i<\/span> <span class=\"k\">in<\/span> <span class=\"nx\">arguments<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n\t\t\t<span class=\"nx\">target<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">arguments<\/span><span class=\"p\">[<\/span><span class=\"nx\">i<\/span><span class=\"p\">];<\/span>\n\t\t<span class=\"p\">}<\/span>\n\n\t\t<span class=\"k\">return<\/span> <span class=\"nx\">target<\/span><span class=\"p\">;<\/span>\n\t<span class=\"p\">}<\/span>\n\n    <span class=\"c1\">\/\/ ...<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Our historians found more methods like <code class=\"highlighter-rouge\">push<\/code>, <code class=\"highlighter-rouge\">pop<\/code>, <code class=\"highlighter-rouge\">join<\/code>, and <code class=\"highlighter-rouge\">toString<\/code> that we will be able\nto recreate with the knowledge we have now. Maybe you want to give it a try.\nYou can find the code at <a href=\"https:\/\/codeberg.org\/moonglum\/engine-failure\">https:\/\/codeberg.org\/moonglum\/engine-failure<\/a>.\nFind the test suite, and unskip the skipped tests.<\/p>\n\n<p>It is possible. At least we thought so. But one of our historians made a startling discovery: You\nwere able to change the <code class=\"highlighter-rouge\">length<\/code> property, and it would change the content of the array. Let\u2019s\ninvestigate that another time.<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:symbols\">\n      <p>\u2026or symbols. But let\u2019s ignore symbols here.\u00a0<a href=\"#fnref:symbols\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:valid-identifier\">\n      <p>An identifier must start with <code class=\"highlighter-rouge\">$<\/code>, <code class=\"highlighter-rouge\">_<\/code>, or a letter, followed by <code class=\"highlighter-rouge\">$<\/code>, <code class=\"highlighter-rouge\">_<\/code>,\nletters or numbers.\u00a0<a href=\"#fnref:valid-identifier\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:order\">\n      <p>If you want to learn more about the order,\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/for...in#description\">read the description on MDN<\/a>\u00a0<a href=\"#fnref:order\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:integer\">\n      <p>There are no integers in JavaScript. It will just be a number. But a number with no\ndecimal places.\u00a0<a href=\"#fnref:integer\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}},{"title":"Sneak Peek on Rails 8","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2024\/01\/01\/rails-8-sneak-peek\/","rel":"alternate","type":"text\/html","title":"Sneak Peek on Rails 8"}},"published":"2024-01-01T00:00:00+00:00","updated":"2024-01-01T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2024-01-01-rails-8-sneak-peek.md","content":"<p><a href=\"https:\/\/github.com\/rails\/rails\/milestone\/87\">Planning for Rails 8 has started<\/a>. I assume it will be\nreleased at Rails World Toronto on September 26 and 27, 2024 (as a certain bruised ego would\nprobably not release at RailsConf). This is a quick summary of what we can expect:<\/p>\n\n<ul>\n  <li><a href=\"#database-backed-jobs-caching-and-web-sockets\">Database-backed Jobs, Caching and Web Sockets<\/a><\/li>\n  <li><a href=\"#frontend\">Frontend<\/a><\/li>\n  <li><a href=\"#language-server-linting-and-formatting\">Language Server and Formatting<\/a><\/li>\n  <li><a href=\"#smaller-changes\">Smaller Changes<\/a><\/li>\n<\/ul>\n\n<h2 id=\"database-backed-jobs-caching-and-web-sockets\">Database-backed Jobs, Caching and Web Sockets<\/h2>\n\n<p>Rails 8 will feature new default adapters for Active Job, Caching and Action Job. They will all use\nthe database as their backend. This is due to Basecamp\u2019s conviction that SSDs are so fast these days\nthat in-memory stores like Redis are not providing enough benefit over databases like MariaDB,\nPostgreSQL or SQLite. These are the new gems:<\/p>\n\n<ul>\n  <li><a href=\"https:\/\/github.com\/basecamp\/solid_queue\">Solid Queue<\/a>: Ben Sheldon, author of GoodJob, <a href=\"https:\/\/island94.org\/2023\/12\/solid-queue-first-impressions\">had a\nfirst look at Solid Queue and is overall\nimpressed<\/a>.<\/li>\n  <li><a href=\"https:\/\/github.com\/rails\/solid_cache\">Solid Cache<\/a><\/li>\n  <li>Action Cable Adapter (not released yet)<\/li>\n<\/ul>\n\n<p>Discussions on GitHub point towards Mission Control (a web UI for Action Jobs) probably not being\nready for the release of Rails 8.<\/p>\n\n<h2 id=\"frontend\">Frontend<\/h2>\n\n<p>One goal of Rails 8 is to help people build progressive web apps, replacing the need for native\nwrappers for a lot of applications. Action Notifier will be a new part of Rails to send\nnotifications to users. This will leverage the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Push_API\">Push\nAPI<\/a> (<a href=\"https:\/\/caniuse.com\/push-api\">now supported in all major\nbrowsers<\/a>) as well as alternative approaches. In addition, Rails will\nhelp with other aspects like the web manifest or service worker file.<\/p>\n\n<p>Apart from that, we will see Turbo 8 with morph, view transitions, and local storage for offline\nmode. CableReady and StimulusReflex are soft-deprecated, and successors will be included in Hotwire.<\/p>\n\n<p>Propshaft will become the default asset pipeline. You can use it both with the \u201cno build\u201d approach\nand with JavaScript and CSS bundlers.<\/p>\n\n<p>In a sad turn of events, Rails will now add browser version blocking.<\/p>\n\n<h2 id=\"language-server-linting-and-formatting\">Language Server, Linting and Formatting<\/h2>\n\n<p>Aaron Patterson spoke about the advantages of offering a language server as part of Rails in\nmultiple talks in 2023. This effort will probably be part of Rails 8. You can find <a href=\"https:\/\/www.youtube.com\/watch?v=LcDNedD-8mU\">his\ntalk<\/a> and the <a href=\"https:\/\/github.com\/Shopify\/ruby-lsp-rails\">work on\nit<\/a> for more details.<\/p>\n\n<p>In addition, Rails 8 will include a Rubocop configuration in new applications for linting and code\nformatting. From the discussions, it will likely be a bike shed of creating a <a href=\"https:\/\/github.com\/rails\/rubocop-rails-omakase\">new configuration from\nscratch<\/a> instead of using an existing one like\n<a href=\"https:\/\/github.com\/standardrb\/standard\">standard<\/a>.<\/p>\n\n<p><strong>Update:<\/strong> Apart from the changes above, a PR to include <a href=\"https:\/\/brakemanscanner.org\">Brakeman<\/a> by\ndefault on new applications has been merged. A PR with files for GitHub Actions running the tests,\nBrakeman and Rubocop as well as the configuration for Dependabot\nfor automatic dependency updates has also been merged.<\/p>\n\n<h2 id=\"smaller-changes\">Smaller Changes<\/h2>\n\n<p>As with every major Rails version, the Ruby version requirement will be bumped. This time it will be\nRuby 3.1. The Rack requirement is bumped to version 3.<\/p>\n\n<p>In Rails 7.1, the core team added another building block for authentication. <a href=\"https:\/\/gorails.com\/episodes\/rails-7-1-authentication-from-scratch\">Chris Oliver created a\ngreat overview video to see how you can put it\ntogether<\/a>. Rails 8 will feature\na generator to add a basic authentication setup to applications. Most people will probably still\nmake a better choice with devise.<\/p>\n\n<p>It makes a lot of sense to log information in a way that can be consumed easily by tools like\n<a href=\"https:\/\/www.elastic.co\/logstash\">Logstash<\/a>. My recommendation to do this has been <a href=\"https:\/\/github.com\/reidmorrison\/rails_semantic_logger\">Rails Semantic\nLogger<\/a> for a while. This will no longer be\nnecessary in Rails 8, as it will be built in.<\/p>\n\n<p><a href=\"https:\/\/kamal-deploy.org\">Kamal<\/a> will be added to the Gemfile of new applications by default (and\ncan be skipped, of course).<\/p>\n\n<p>There will also be a small Go-based proxy to support HTTP\/2, public caching, and X-Sendfile designed\nto work with Puma. I\u2019m not sure why this requires changes to Rails; let\u2019s see.<\/p>\n\n<p>Finally, Rails 8 will feature built-in rate limiting based on Redis via Kredis and a benchmarking\ntool.<\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to <a href=\"https:\/\/rohrspatz.studio\">nanoparsec<\/a> for their feedback \u2764\ufe0f<\/p>","author":{"name":"Lucas Dohmen"}},{"title":"Network Programming Basics in Ruby","link":{"@attributes":{"href":"https:\/\/lucas.dohmen.io\/posts\/2023\/06\/04\/network-programming-basics-in-ruby\/","rel":"alternate","type":"text\/html","title":"Network Programming Basics in Ruby"}},"published":"2023-06-04T00:00:00+00:00","updated":"2023-06-04T00:00:00+00:00","id":"repo:\/\/posts.collection\/_posts\/2023-06-04-network-programming-basics-in-ruby.md","content":"<p>In this series of blog posts, we want to pull back the curtain on building web applications. In my\nexperience, most developers use a framework and application server and do not look behind the\ncurtain to see what happens inside of them. Frameworks are great for making development quicker and\nmore comfortable, providing abstractions for low-level details and recurring patterns. But there\u2019s\nalways the risk of losing touch with what\u2019s actually happening under the hood, which can complicate\ndebugging and lead to unnecessary complexity. It helps to have a sense of what\u2019s going on beneath\nthose abstractions. Let\u2019s start with the basics of networking.<\/p>\n\n<ul>\n  <li><a href=\"#the-internet-protocol-suite\">The Internet protocol suite<\/a><\/li>\n  <li><a href=\"#netcat\">netcat<\/a><\/li>\n  <li><a href=\"#sockets\">Sockets<\/a><\/li>\n  <li><a href=\"#our-first-ruby-server\">Our first Ruby server<\/a><\/li>\n  <li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n  <li><a href=\"#thanks\">Thanks<\/a><\/li>\n<\/ul>\n\n<h2 id=\"the-internet-protocol-suite\">The Internet protocol suite<\/h2>\n\n<p>Before we start diving into network programming, let us briefly recap the four layers of the\nInternet protocol suite as defined in <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc1122.html\">RFC 1122<\/a>. Each\nlayer builds upon the layer beneath to offer services to the layers above it.<\/p>\n\n<h3 id=\"link-layer\">Link Layer<\/h3>\n\n<p>On the lowest level, we find the <strong>link layer<\/strong>. The link layer describes the communication between\ntwo physically connected nodes. One example of a link layer protocol is Ethernet: It specifies how\nthe bits of the layers above are turned into electrical signals within a cable. It also describes\nthe direct connection of one node to another. Routing data on a network is left to the layer above.<\/p>\n\n<h3 id=\"internet-layer\">Internet Layer<\/h3>\n\n<p>On top of this, we find the <strong>Internet layer<\/strong> with the Internet protocols (IPv4 and\nIPv6<sup id=\"fnref:ip-protocol-adoption\"><a href=\"#fn:ip-protocol-adoption\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1<\/a><\/sup>). Everything that is considered part of the Internet uses the IP\nprotocols. They offer the abstraction of sending packages from one node to another that are not\ndirectly connected on the link layer. Each package is independent though, making a connectionless\nprotocol. This layer provides a node with an IP address. The so-called loopback address<sup id=\"fnref:loopback\"><a href=\"#fn:loopback\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2<\/a><\/sup>\n<code class=\"highlighter-rouge\">::1<\/code> (or <code class=\"highlighter-rouge\">127.0.0.1<\/code> in IPv4) and their alias <code class=\"highlighter-rouge\">localhost<\/code> allow us to address our own machine.<\/p>\n\n<h3 id=\"transport-layer\">Transport Layer<\/h3>\n\n<p>The third layer is called the <strong>transport layer<\/strong>. This is where protocols like TCP and UDP reside. TCP\nprovides us with connection-oriented, reliable, ordered and error-checked communication. The\nmuch more minimal but sometimes more efficient UDP protocol does not provide any of TCP\u2019s features or\nguarantees. Both TCP and UDP provide us with \u201cports\u201d, which are 16-bit unsigned integers (a number\nbetween 0 and 65.535<sup id=\"fnref:port-ranges\"><a href=\"#fn:port-ranges\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">3<\/a><\/sup>).<\/p>\n\n<p>The transport layer is also responsible for three essential qualities: confidentiality, integrity, and\nauthenticity, as offered by <strong>Transport Layer Security<\/strong> (TLS). TLS is not a transport\nprotocol itself, but an extension to an existing transport protocol like TCP<sup id=\"fnref:transport-layer-security\"><a href=\"#fn:transport-layer-security\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">4<\/a><\/sup>.\nWhen using TLS, the communication between two parties is confidential, protected from tampering, and\nwe know that the two parties are who they claim to be. (In most cases, only one of the two parties\nproves their identity \u2013 the case where both do it is called mTLS, m for mutual). TLS is essential\nfor any secure Internet communication. Only if we talk to our own machine via loopback, we don\u2019t\nneed TLS.<\/p>\n\n<p>According to Daniel Stenberg (of <a href=\"https:\/\/curl.se\">curl<\/a> fame), it is basically impossible to\nintroduce a new transport protocol today. Even Google, arguably one of the most powerful players\ntoday, decided to build their new <strong>QUIC<\/strong> protocol on top of the existing UDP protocol. We still\nconsider it a transport protocol because the layer above uses it like one, hiding UDP from it\nentirely. QUIC offers the same guarantees as TCP, with the goal of better performance and mandatory\nTLS. QUIC with its advantages and disadvantages is out of scope for this blog post.<\/p>\n\n<h3 id=\"application-layer\">Application Layer<\/h3>\n\n<p>At the top layer, we have a plethora of <strong>application protocols<\/strong>. From mail protocols like IMAP and\nSMTP to file transfer protocols like SFTP. It\u2019s important to note that the three bottom layers are\ntypically provided by your operating system kernel (QUIC being an exception, at least for now). The\nkernel provides abstractions to communicate either via TCP or UDP. The protocols at the application\nlayer, on the other hand, are mostly implemented in userland.<\/p>\n\n<p>In this series of blog posts, we will focus on the Web and its Hypertext Transfer Protocol (HTTP).\nHTTP requires a reliable connection between client and server: HTTP\/1.1 and HTTP\/2 both use TCP\nover IP (often referred to as TCP\/IP) while HTTP\/3 uses QUIC over IP (or QUIC\/IP).<\/p>\n\n<h2 id=\"netcat\">netcat<\/h2>\n\n<p><code class=\"highlighter-rouge\">netcat<\/code> is a command-line application that allows two computers to talk to each other. We will\ncheat a bit in this chapter and let our computer talk to itself \u2013 but it works in the same way. So\nfeel free to try it out later if you have more than one computer at hand (you will have to replace\n<code class=\"highlighter-rouge\">::1<\/code> with the IP address of the other computer). In case you run into problems connecting via\nnetcat in the following sections, check if your firewall might be blocking the connection.<\/p>\n\n<p>netcat is one of those tools that has been implemented many times. On macOS and Linux, there is some\nversion of netcat installed. Depending on your system, it might be called <code class=\"highlighter-rouge\">socat<\/code>, <code class=\"highlighter-rouge\">nc<\/code> or <code class=\"highlighter-rouge\">ncat<\/code>.\nWe will be using <code class=\"highlighter-rouge\">nc<\/code> in our code samples.<\/p>\n\n<p>netcat can be started in two different modes:<\/p>\n\n<ul>\n  <li>In server mode, netcat will listen for clients that want to connect to it.<\/li>\n  <li>In client mode, netcat connects to a server that is listening for connections.<\/li>\n<\/ul>\n\n<p><em>Note that we are using IPv6 in our examples. If you have trouble with that, you can remove the <code class=\"highlighter-rouge\">-6<\/code>\noption and replace <code class=\"highlighter-rouge\">::1<\/code> with <code class=\"highlighter-rouge\">127.0.0.1<\/code>.<\/em><\/p>\n\n<p>So let us open two terminal windows. In the first window we start our netcat server:<\/p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>nc <span class=\"nt\">-6<\/span> <span class=\"nt\">-l<\/span> 8080\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"highlighter-rouge\">-6<\/code> option ensure we are using IPv6. The <code class=\"highlighter-rouge\">l<\/code> option means that our netcat is listening. The\nnumber is the port it should listen on. Our netcat is now eager for input. In our second terminal\nwindow, we will start a netcat client:<\/p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>nc ::1 8080\n<\/code><\/pre><\/div><\/div>\n\n<p>Here we told netcat to connect to our own computer, using <code class=\"highlighter-rouge\">::1<\/code> and the same port that we\u2019d chosen\nabove. As soon as we press enter, we establish a network connection between the two netcats.<\/p>\n\n<p>Now netcat is waiting for us. Type <code class=\"highlighter-rouge\">Hello!<\/code> and press enter to send it. This message will be shown\nin the other terminal as well! If we write something in the window of our listening netcat and hit\nenter, this will also appear in the sending window and vice versa. In other words: We are using\nnetcat as a chat application. To exit it, we use <code class=\"highlighter-rouge\">ctrl+c<\/code>. If you close either one of the two\nnetcats, the other one will notice that its chat partner is gone and exit as well.<\/p>\n\n<h2 id=\"sockets\">Sockets<\/h2>\n\n<p>Let\u2019s write some Ruby code, starting with writing text to a file:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">file<\/span> <span class=\"o\">=<\/span> <span class=\"no\">File<\/span><span class=\"p\">.<\/span><span class=\"nf\">open<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"output.txt\"<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"w\"<\/span><span class=\"p\">)<\/span> <span class=\"c1\"># `w` stands for write-only mode<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">write<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"Hello<\/span><span class=\"se\">\\n<\/span><span class=\"s2\">\"<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">write<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"World\"<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"highlighter-rouge\">output.txt<\/code> file will now contain two separate lines: <code class=\"highlighter-rouge\">Hello<\/code> and <code class=\"highlighter-rouge\">World<\/code>. This is due to\n<code class=\"highlighter-rouge\">\\n<\/code>, which represents a line break.<\/p>\n\n<p>We can also read a certain number of characters from a file when we put it into read mode:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">file<\/span> <span class=\"o\">=<\/span> <span class=\"no\">File<\/span><span class=\"p\">.<\/span><span class=\"nf\">open<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"output.txt\"<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"r\"<\/span><span class=\"p\">)<\/span>\n<span class=\"nb\">p<\/span> <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">read<\/span><span class=\"p\">(<\/span><span class=\"mi\">3<\/span><span class=\"p\">)<\/span>\n<span class=\"nb\">p<\/span> <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">read<\/span><span class=\"p\">(<\/span><span class=\"mi\">2<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will print <code class=\"highlighter-rouge\">\"Hel\"<\/code> and then <code class=\"highlighter-rouge\">\"lo\"<\/code>. The <code class=\"highlighter-rouge\">read<\/code> method always reads the next characters. Think\nof it as a tape deck. Remember those? When we open a file, we start at the beginning. But every time\nwe call <code class=\"highlighter-rouge\">read<\/code> we move the read head along in the file. We can \u201crewind\u201d that tape to the beginning\nwith the \u2013 you guessed it \u2013 <code class=\"highlighter-rouge\">rewind<\/code> method.<\/p>\n\n<p>There is also a way to read text files line by line using <code class=\"highlighter-rouge\">gets<\/code>:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">file<\/span> <span class=\"o\">=<\/span> <span class=\"no\">File<\/span><span class=\"p\">.<\/span><span class=\"nf\">open<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"output.txt\"<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"r\"<\/span><span class=\"p\">)<\/span>\n<span class=\"nb\">p<\/span> <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">gets<\/span>\n<span class=\"nb\">p<\/span> <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">gets<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will first output <code class=\"highlighter-rouge\">\"Hello\\n\"<\/code> and then <code class=\"highlighter-rouge\">\"World\"<\/code>.<\/p>\n\n<p>The counterpart for writing is <code class=\"highlighter-rouge\">puts<\/code>:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">file<\/span> <span class=\"o\">=<\/span> <span class=\"no\">File<\/span><span class=\"p\">.<\/span><span class=\"nf\">open<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"output.txt\"<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"w\"<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">puts<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"Hello\"<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">puts<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"Bye\"<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will write two lines into the text file.<\/p>\n\n<p>Both <code class=\"highlighter-rouge\">puts<\/code> and <code class=\"highlighter-rouge\">gets<\/code> can also be used to read and write from the terminal. You probably have used\nat least <code class=\"highlighter-rouge\">puts<\/code> in that way. What we interact with when we call these two methods is called stdin\nand stdout. They behave like files.<\/p>\n\n<p>Alright. Let\u2019s talk about sockets. A socket (also known as Berkely socket, POSIX socket or BSD\nsocket) is a file that represents a connection. You have probably heard the saying \u201cIn UNIX,\neverything is a file\u201d.  In modern UNIX systems, we can represent all kinds of things as a file (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Plan_9_from_Bell_Labs\">but\nnot everything<\/a>): a printer, for example, or a\nnetwork connection. So when we represent a network connection as a file, we can interact with it as\nif it were a file: We can read from it or write to it. The inventors of UNIX came up with this\nprinciple in 1983 and it remains the de-facto standard for network communication on UNIX-like\nsystems (for example GNU\/Linux or macOS). Most programming languages have some way to interact with\na socket. So does Ruby. Everything we learned above about files in Ruby also works with a network\nconnection.<\/p>\n\n<p>A socket can operate in either server or client mode. In the same way that we need to provide a mode\nwhen we open a file, we need to choose a mode when we open a socket. If we want Ruby to open a file,\nwe need to tell Ruby the path to the file. If we want Ruby to open a socket, we instead provide a\nnetwork address consisting of a network interface and a port. For a server socket, this is the\nnetwork address it listens on \u2013 in the case of a client socket, it is the network address it wants\nto connect to.<\/p>\n\n<p>For each socket we need to make the following choices:<\/p>\n\n<ul>\n  <li>Should it be an IPv4, IPv6 or UNIX domain socket?<\/li>\n  <li>Should it communicate via stream (TCP) or datagram (UDP)?<\/li>\n  <li>Which network interface should it listen on (for a server socket) or connect to (for a client\nsocket)?<\/li>\n  <li>Which port should it listen on (for a server socket) or connect to (for a client socket)?<\/li>\n<\/ul>\n\n<p>When the socket connection is established, we can  use commands like <code class=\"highlighter-rouge\">read<\/code> and <code class=\"highlighter-rouge\">write<\/code> on it. In\nthe next section, we will replace our netcat server with a small Ruby script.<\/p>\n\n<h2 id=\"our-first-ruby-server\">Our first Ruby server<\/h2>\n\n<p>When we want to work with sockets in Ruby, we first require the <code class=\"highlighter-rouge\">socket<\/code> standard library and then\ninitialize our server socket:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">require<\/span> <span class=\"s2\">\"socket\"<\/span>\n\n<span class=\"n\">server_socket<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"ss\">:INET6<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:STREAM<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"highlighter-rouge\">:INET6<\/code> argument means that we want to use IPv6, the <code class=\"highlighter-rouge\">:STREAM<\/code> argument means we want to use\nTCP (and not UDP \u2013 which would be <code class=\"highlighter-rouge\">:DGRAM<\/code>). After creating the socket, we need an address (the\ncombination of a port and the loopback address):<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">address<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">pack_sockaddr_in<\/span><span class=\"p\">(<\/span><span class=\"mi\">8080<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"::1\"<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We then continue by binding our socket to this address:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">server_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">bind<\/span><span class=\"p\">(<\/span><span class=\"n\">address<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Then we need to tell the socket to listen for incoming connections. When we do that, we additionally\nprovide a size for the listen queue, which keeps track of incoming connections. When we are ready to\nanswer another connection, we look into the listen queue. When we tell our socket to listen, we need\nto provide a size for that queue. We will accept five connections for now:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">server_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">listen<\/span><span class=\"p\">(<\/span><span class=\"mi\">5<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Ok, we are now ready and listening. Let\u2019s accept a connection:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">connection_socket<\/span><span class=\"p\">,<\/span> <span class=\"n\">addrinfo<\/span> <span class=\"o\">=<\/span> <span class=\"n\">server_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">accept<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We get back another socket: This socket represents this specific connection to a specific client.\nThe <code class=\"highlighter-rouge\">addrinfo<\/code> is a bit of information about our client. For now, we will output the info and close\nthe socket:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">pp<\/span> <span class=\"n\">addrinfo<\/span>\n<span class=\"n\">connection_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Put these lines of code into a Ruby file and start the program. It will create no output, but it\nwill also not quit. Instead, it just waits for a connection, like netcat in our previous example. We\nalready know how to make a connection with netcat, so let\u2019s start our client in a separate terminal\nwindow:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">echo<\/span> <span class=\"s2\">\"hello from netcat\"<\/span> <span class=\"o\">|<\/span> <span class=\"n\">nc<\/span> <span class=\"o\">::<\/span><span class=\"mi\">1<\/span> <span class=\"mi\">8080<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We have made a connection. The program outputs a line and then quits:<\/p>\n\n<div class=\"highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>#&lt;Addrinfo: [::1]:62392 TCP&gt;\n<\/code><\/pre><\/div><\/div>\n\n<p>Well. Interesting. But wouldn\u2019t it be nice if it keeps running and waits for more clients to\nconnect? Let\u2019s wrap the two lines that accept and close connections in a loop:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kp\">loop<\/span> <span class=\"k\">do<\/span>\n  <span class=\"n\">connection_socket<\/span><span class=\"p\">,<\/span> <span class=\"n\">_<\/span> <span class=\"o\">=<\/span> <span class=\"n\">server_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">accept<\/span>\n  <span class=\"n\">pp<\/span> <span class=\"n\">addrinfo<\/span>\n  <span class=\"n\">connection_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Here is the entire script:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">require<\/span> <span class=\"s2\">\"socket\"<\/span>\n\n<span class=\"n\">server_socket<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"ss\">:INET6<\/span><span class=\"p\">,<\/span> <span class=\"ss\">:STREAM<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">address<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">pack_sockaddr_in<\/span><span class=\"p\">(<\/span><span class=\"mi\">8080<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"::1\"<\/span><span class=\"p\">)<\/span>\n\n<span class=\"n\">server_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">bind<\/span><span class=\"p\">(<\/span><span class=\"n\">address<\/span><span class=\"p\">)<\/span>\n\n<span class=\"n\">server_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">listen<\/span><span class=\"p\">(<\/span><span class=\"mi\">5<\/span><span class=\"p\">)<\/span>\n\n<span class=\"kp\">loop<\/span> <span class=\"k\">do<\/span>\n  <span class=\"n\">connection_socket<\/span><span class=\"p\">,<\/span> <span class=\"n\">addrinfo<\/span> <span class=\"o\">=<\/span> <span class=\"n\">server_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">accept<\/span>\n  <span class=\"n\">pp<\/span> <span class=\"n\">addrinfo<\/span>\n  <span class=\"n\">connection_socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Ok, let\u2019s try it again:<\/p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">echo<\/span> <span class=\"s2\">\"hello from netcat\"<\/span> | nc ::1 8080\n<\/code><\/pre><\/div><\/div>\n\n<p>This time, the server stays open. We can run our client again and again. Keeping the server open and\nanswering connections in a loop is a very common pattern for server sockets. It is so common that\nRuby offers us a single method to do it:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">require<\/span> <span class=\"s2\">\"socket\"<\/span>\n\n<span class=\"no\">Socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">tcp_server_loop<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"::1\"<\/span><span class=\"p\">,<\/span> <span class=\"mi\">8080<\/span><span class=\"p\">)<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">connection<\/span><span class=\"p\">,<\/span> <span class=\"n\">addrinfo<\/span><span class=\"o\">|<\/span>\n  <span class=\"n\">pp<\/span> <span class=\"n\">addrinfo<\/span>\n  <span class=\"n\">connection<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will do the same thing as the code above. Notice that we also replaced <code class=\"highlighter-rouge\">connection_socket<\/code> with\n<code class=\"highlighter-rouge\">connection<\/code>. It is still a socket, but <code class=\"highlighter-rouge\">connection<\/code> is a little shorter. To wrap up our work,\nlet\u2019s print out what the client is sending us and reply with a friendly \u201chello from Ruby\u201d response.\nThis is where the \u201ca socket is like a file\u201d part comes into play. In the same way that we can call\n<code class=\"highlighter-rouge\">gets<\/code> and <code class=\"highlighter-rouge\">puts<\/code> on files, we can call them on our socket:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">require<\/span> <span class=\"s2\">\"socket\"<\/span>\n\n<span class=\"no\">Socket<\/span><span class=\"p\">.<\/span><span class=\"nf\">tcp_server_loop<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"::1\"<\/span><span class=\"p\">,<\/span> <span class=\"mi\">8080<\/span><span class=\"p\">)<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"n\">connection<\/span><span class=\"o\">|<\/span>\n  <span class=\"nb\">puts<\/span> <span class=\"n\">connection<\/span><span class=\"p\">.<\/span><span class=\"nf\">gets<\/span>\n\n  <span class=\"n\">connection<\/span><span class=\"p\">.<\/span><span class=\"nf\">puts<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"hello from Ruby\"<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">connection<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If we start our server and run <code class=\"highlighter-rouge\">echo 'hello from netcat' | nc ::1 8080<\/code> in the other window, we see\nthe messages from netcat on the server and the message from Ruby on the client. We have written a\nTCP-based server in Ruby!<\/p>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>The tiny server we built talks to us. Most servers will, however, not talk to a human being.\nInstead, they will speak with another application. Computers are not huge fans of informal\ncommunication. So when we want two computers to talk to each other, we need a language that they can\nuse. We call these languages <strong>application protocols<\/strong>. This will be the topic of the next blog\npost.<\/p>\n\n<h2 id=\"thanks\">Thanks<\/h2>\n\n<p>Thanks to Essy, <a href=\"https:\/\/bascht.com\">Bascht<\/a>, <a href=\"https:\/\/prepitaph.org\">FND<\/a>, <a href=\"https:\/\/www.innoq.com\/de\/staff\/stefan-bodewig\">Stefan\nBodewig<\/a> and <a href=\"https:\/\/mkhl.codeberg.page\">mkhl<\/a> for\ntheir feedback \u2764\ufe0f<\/p>\n\n<h2 id=\"footnotes\">Footnotes<\/h2>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n  <ol>\n    <li id=\"fn:ip-protocol-adoption\">\n      <p>This layer is moving quite slowly: The IPv6 standard became a draft in\n1998, and was ratified in 2017. <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc9386.html\">RFC 9386<\/a>\nestimates that in January 2022, about 30% of Internet users were using IPv6 \u2013 leaving 70% using\nits predecessor IPv4, introduced in 1981.\u00a0<a href=\"#fnref:ip-protocol-adoption\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:loopback\">\n      <p>To be precise, there are entire blocks reserved for loopback: In IPv4 the address block\n<code class=\"highlighter-rouge\">127.0.0.0\/8<\/code> and <code class=\"highlighter-rouge\">::1\/128<\/code> in IPv6.\u00a0<a href=\"#fnref:loopback\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:port-ranges\">\n      <p>This is due to\n<a href=\"https:\/\/de.wikipedia.org\/wiki\/Internet_Assigned_Numbers_Authority\">IANA<\/a> reserving a list of\nstandardized ports (up to port 49.152). This is why we we need special privileges to listen on\nports lower than 1024 on some systems. They are not as relevant today, and in most cases you can\nchoose your ports arbitrarily. You can <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc6335#section-6\">learn more about the port number\nranges<\/a> and the <a href=\"https:\/\/www.iana.org\/assignments\/service-names-port-numbers\/service-names-port-numbers.xhtml\">registered service\nnames Transport Protocol Port Number\n<\/a>.\u00a0<a href=\"#fnref:port-ranges\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:transport-layer-security\">\n      <p>In the <a href=\"https:\/\/en.wikipedia.org\/wiki\/OSI_model\">OSI model<\/a>, TLS would\nbe on the session layer rather than at the transport layer. The Internet Protocol Suite combines\nthese layers into one layer, this leads to this weirdness.\u00a0<a href=\"#fnref:transport-layer-security\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>","author":{"name":"Lucas Dohmen"}}]}